* test(#1953): failing-first suite for the complexity-triggered refactor hook 60 behavioral cases against src/complexity-trigger.cts, which does not exist yet: decision-point counting, the comment/literal stripping leak surface, threshold and jump-delta boundaries at limit-1/limit/limit+1, stable-anchor baseline semantics, and fs fault injection via mock.method. Two fast-check properties assert that stripping never manufactures a decision point and that comments and string literals are score-neutral. Also registers the refactor-trigger capability manifest (inert until refactor.trigger_enabled) and regenerates the capability registry and matrix. Verified RED on the remote runner before any implementation exists. * feat(#1953): complexity-triggered refactor extension point Adds the opt-in refactor-trigger capability. After a phase executes, an execute:post step measures per-function complexity for the files the phase touched and writes a scoped refactor proposal when a function crosses the configured threshold or drifts past its recorded anchor. Design notes worth carrying: - The signal is computed in-core (decision-point counting over comment- and literal-stripped source, Node builtins only) rather than via Memtrace or a shelled-out analyzer. The hook fires as a deterministic CLI, not an agent with MCP tools, and core takes no external dependencies — this is the only option a behavioral test can bind to. The metric sits behind a seam. - The baseline is a stable anchor, not a rolling value: set on first observation, moved only on disposition. A rolling baseline makes the delta the single-phase change, so a function creeping +2 per phase never trips a delta of 5 and the jump check adds nothing over the absolute threshold. - Strict mode records an open deviation window in the broken-windows ledger rather than declaring its own ship:pre gate. ship.md has no generic ship:pre gate dispatch — only two hardcoded branches — so a third gate of any kind would be declared and never evaluated. - The gate clears on the proposal being dispositioned, never on the score improving. A blocking complexity number is one an executor can satisfy by splitting a coherent function in two. execute-phase.md gains a generic execute:post step-dispatch contract; it previously matched only ref.skill == "code-review", so any other step registered there was declared and never run. The code-review branch is unchanged. Full rationale in ADR-1953. Closes #1953 * fix(#1953): close git option injection and symlink escape in the refactor hook Three findings from the isolated security review, all fixed inline. HIGH — changedFilesSince interpolated the --since value into a revision token placed before the -- separator. A -- only stops PATHSPEC parsing of arguments after it; git still option-parses what comes before. So --since '--output=/tmp/x' became --output=/tmp/x..HEAD, which git accepts as --output=<file> and uses to redirect diff output — an arbitrary write. Fixed with --end-of-options before the revision range plus a conservative ref validator. The validator deliberately permits ~ ^ @ { } because those are legitimate git REVISION syntax (HEAD~1, main@{yesterday}) as distinct from ref-NAME syntax; --end-of-options is the actual barrier. The doc comment asserting the trailing -- was sufficient was wrong and is corrected. MEDIUM — resolveConfinedPath confined by string prefix only, so a symlink committed inside the repo passed the check (its own path is under cwd) and readFileSync then followed it outside the root. Now lstat-checks for a regular file and skips anything else with REFACTOR_FILE_UNREADABLE, so one bad path skips one file and the run continues. LOW — the new execute:post dispatch contract showed the gsd_run example before the rule requiring ref.command be validated first. That prose is executed by an agent, so textual order is execution order. Reordered. Refs #1953 * fix(#1953): make the analyzer able to see TypeScript at all Found by running the shipped analyzer over its own source: it reported functions=1 for a 940-line module with 24 function forms. A return-type annotation or a generic parameter list made a function invisible — `function f(a): number {}` and `function f<T>(a: T): T {}` both detected as zero. Since gsd-core is written in .cts and the capability declares .ts/.cts/.mts analyzable, the feature silently found nothing in this repo's own primary language while reporting success. A safety net that reports "all clear" because it cannot see is worse than no safety net. All 98 tests passed over this, because every fixture was plain JS — the exact failure the test matrix's own "assert against the shape production uses" warning describes. Adds a TypeScript-shapes suite covering return types (including unions, generics, object literals and type predicates), generic parameter lists (constrained and defaulted), export/async/generator combinations, annotated arrows, class-method modifiers, and optional/ default/rest params — plus the two traps: an overload signature has no body and must not count, and `a < b && c > d` is a comparison, not a generic. Detection now reports 24/37/21 functions for the three source files, which matches a hand count exactly. Also from review: - The strict-mode ledger dedup identified entries by parsing a prose description string. That is banned by CONTRIBUTING's raw-text-matching rule and was a real bug: the "exactly one window per untriaged proposal" guarantee rested on prose matching, so rewording a description or editing WINDOWS.md by hand silently produced duplicates. Now matches structurally on kind + phase + file + line. - A property test asserted on the stripper's output text. Reframed to assert the same invariant through analyzeSource's score. - nextBaseline's `candidates` parameter has been dead since the anchor change; removed from the signature and all call sites. - Extracted the duplicated require-or-degrade and capability-check boilerplate. - ADR-1953's Implementation bullet still named a `refactor.ship-gate` in check-command-router.cts — a leftover from the design cut D6 rejects. That file is untouched and no such gate exists. Removed. Refs #1953 * fix(#1953): keep execute-phase.md under its byte ceiling; un-vacuum the large-file test Five of the seven remote-runner failures were one cause: the execute:post dispatch contract, written out inline, grew execute-phase.md 1876 bytes (93,400 -> 95,276) against a frozen PRE_PHASE6 ceiling of 93,600. A drift-ack does not clear that — tests/phase6-capstone-conformance.test.cjs and tests/fix-2285-claude-orchestration-wiring.test.cjs assert the file is literally under the cap. The contract now lives in gsd-core/references/loop-hook-dispatch.md, which already claimed to be the point-agnostic dispatch reference and already documented ref.skill and ref.agent. It gains the ref.command shape, its in-context validation rule, the advisory-by-construction statement, and a note that a point whose workflow hand-rolls one kind is not implementing this contract. execute-phase.md now defers to it in one line: 145 bytes of growth, 55 B of headroom under the cap. Better placement than the first cut — the reference was overstating its coverage, and this makes the claim true rather than duplicating prose next to it. Acknowledged by appending to tests/emitted-drift-acks/2930-*.json rather than a new 1953-*.json: two ack sources may never name the same path, and that fragment is already the accumulating ack for this file. Sixth and seventh failures: analyzesLargeFileWithinBounds tripped its own vacuity guard — the fixture generated ~480 KB against a `> 500000` assert, so the guard fired and the three assertions after it never ran. The test has been vacuous since it was written. The matrix row specifies ~1 MB, so N goes 8000 -> 20000 (1.17 MB, 17% margin) and the guard to > 1_000_000. Verified by reproducing the exact body against the compiled module: 1168888 bytes, 118 ms, all four assertions hold. Refs #1953 * fix(#1953): fold the execute:post step deferral into the existing resolve line The remaining two failures were one test: execute-phase.md carries a SECOND, tighter assertion than the 93,600 ceiling — `<=93400`, which is exactly its current size. The file cannot grow by a single byte. My previous fix got it under 93,600 but not under 93,400, so it still failed. ("H." in the report is just the parent describe of that same test, not a separate defect.) Rather than add a paragraph, the deferral now REPLACES the existing hook resolution line. It read: Resolve active step hooks from `EXECUTE_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "code-review"`. which is the bug itself written down — only code-review was ever dispatched. It now reads: Dispatch each `kind == "step"` hook per @gsd-core/references/loop-hook-dispatch.md. For `code-review`: The following prose already begins "If no active code-review step hook exists", so it reads correctly and the code-review handling is untouched. Net effect on the file is -11 bytes: 93,400 -> 93,389, under the margin assertion rather than merely under the ceiling. That also removes the need for a drift-ack: the file shrank, so there is no growth to acknowledge, and the append to the shared 2930-*.json fragment is reverted. Leaving it would have shipped a claim of "145 bytes of growth" that is no longer true, on a file six other issues share. The test's own comment states the principle this ended up honoring: "the host loop must stay small — optional-feature detail belongs in the capability fragment, not the host workflow." Putting the dispatch contract in the reference rather than inline is that rule, applied. Refs #1953 * fix(#1953): keep the code-review hook literal the workflow test requires tests/code-review.test.cjs extracts the <step name="code_review_gate"> block and asserts it contains `ref.skill == "code-review"` verbatim. The previous commit replaced the line carrying that literal, so the token vanished and the test went red — a fair assertion: code-review IS the bespoke branch there and the workflow should still name it. Restored inside the same one-line deferral, which now reads: Dispatch `kind == "step"` hooks per @gsd-core/references/loop-hook-dispatch.md. `ref.skill == "code-review"`: 93,396 bytes — still under the `<=93400` margin assertion and 4 bytes below the base, so the file continues to shrink rather than grow. Because three consecutive runs were each reddened by a different assertion on this one file, this change was verified by sweeping ALL of them at once rather than one run at a time: every test under tests/ that reads execute-phase.md or references/loop-hook-dispatch.md was located by resolving its path constants, and each content/size assertion was evaluated directly against the working tree — 22 assertions, plus two real executions (gen-section-manifest --check, and emitted-attribution's full real-tree differential). All pass. That sweep also confirms the earlier judgement call: the net change to execute-phase.md is a SHRINK, and the size ratchet only gates growth, so reverting the append to the shared 2930-*.json ack fragment was correct — an ack would have been both unnecessary and factually wrong. Refs #1953 * chore(#1953): backfill changeset pr number to 3261 * docs(#1953): add the missing how-to for acting on a refactor proposal Reference and explanation shipped (COMMANDS.md, CONFIGURATION.md, FEATURES.md 159, ADR-1953) but the Diataxis how-to quadrant did not, and that is the one a user reaches for. CONTRIBUTING's required-docs table is 'new command -> COMMANDS.md + FEATURES.md', so CI was green on a gap. Enabling this feature is genuinely multi-step and no single page walked it: turn it on, tune the threshold, understand advisory vs strict, discover that strict needs a SECOND toggle on a DIFFERENT capability, and know what to do when a proposal appears. The two-toggle subtlety in particular was a footnote in a config table; here it is a section with both commands. Follows the shape of its closest siblings, resolve-edge-coverage-findings and resolve-prohibition-findings — both 'the loop surfaced a finding, here is what to do with it'. Includes a reason-code table for the silent cases, since the analyzer is deliberately quiet in six situations and a user who expected a proposal needs to tell 'nothing to report' from 'could not look'. Indexed from docs/README.md beside the other loop how-tos. Docs-only: exempt from the push gate, no re-verification, pass marker on 2af188b4 untouched. Refs #1953 * feat(#1953): warn when strict mode is on but nothing will actually block Closes acceptance criterion 5, which I had wrongly marked satisfied. refactor.trigger_strict records an untriaged proposal as an open deviation window, but a ship only STOPS if workflow.windows_enforce is also on — a toggle owned by the broken-windows capability that this feature neither sets nor requires. So a user could enable strict, believe ship was gated, and find out otherwise at ship time. The split itself stays: requires:["broken-windows"] would force-install the ledger on advisory users who never enable strict, and a ship:pre gate of our own would never fire because ship.md has no generic ship:pre gate dispatch. What was missing was discoverability, so that is what this fixes. `refactor evaluate` now emits a typed REFACTOR_STRICT_NOT_ENFORCING warning, naming the exact remediation command, whenever strict is on and either workflow.windows_enforce is off or broken-windows is unavailable. It fires only on a run that produced a candidate — with nothing to block on there is nothing to warn about, and warning every run would be noise. Reads workflow.windows_enforce through the same resolveConfigKey walk the router already uses for its own keys rather than a second config reader. Four tests cover the matrix: strict+enforce-off warns, strict+enforce-on does not, strict+ledger-absent warns, strict-off never warns. Also corrects a user-facing message in this same file that told the user to run `gsd-tools config-set` — the wrong form. docs/CONFIGURATION.md and the broken-windows capability both use `gsd config-set`, and gsd-tools is invoked as `node gsd-tools.cjs`, so the bare form may not resolve. The two adjacent messages in this file now agree. Refs #1953 --------- Co-authored-by: sim <sim@local>
11 KiB
Capability matrix reference
Generated file — do not edit by hand. This matrix is generated from the capability registry by
scripts/gen-capability-matrix.cjsand kept honest by a drift guard (tests/capability-matrix-sync.test.cjsruns--check). Any manual edit is overwritten on the next generation run. To change a capability's declared metadata, edit the correspondingcapabilities/<id>/capability.jsonand runnode scripts/gen-capability-matrix.cjs --write.
See also: ADR-1244 — Capability manifest fields — The capability trust model
Column definitions
| Column | Description |
|---|---|
| id | Canonical capability identifier; unique across first- and third-party capabilities. Reserved prefixes: gsd-, gsd-core-, anthropic-. |
| role | feature — extends what the loop does; runtime — adapts GSD to a specific AI runtime/IDE; reviewer — declares a cross-AI reviewer lane (ADR-2782). A capability may be both a runtime and a reviewer. |
| tier | core — always active; standard — active when the runtime supports it; full — opt-in or runtime-specific. |
| engines.gsd | Semver RANGE expressing host-version compatibility. A hard gate at install and at load. — means the capability declares no range. |
| extension points | The loop points this capability registers hooks into (from the registry's byLoopPoint index). — means it registers none (typical for runtime capabilities, whose job is surface emission). |
| hook kinds | Which of step, contribution, gate the capability's hooks use. — means none. |
| source | first-party — ships with GSD Core; third-party — installed from an external source via gsd capability install. |
On versions. This matrix intentionally omits a per-capability
versioncolumn. First-party capabilities are versioned in lockstep with the GSD Core package (theircapability.jsonversionalways equals the GSD release version), so a per-row version would simply repeat the package version and churn the committed file on every release. The stable host-compatibility signal —engines.gsd— is shown instead. A third-party capability's exact version is recorded in the per-runtime ledger (.gsd-capabilities.json) at install time.
Native (first-party) capabilities
First-party capabilities are implicitly trusted: they ship as part of the GSD Core package and are stamped with the package version at release (per ADR-1244 D6). They are not subject to the consent or integrity-pin flow applied to third-party capabilities.
Feature capabilities (role: feature) — 21
Feature capabilities extend what the loop does — contributing research, planning, execution, verification, or ship artefacts at the loop extension points.
| id | role | tier | engines.gsd | extension points | hook kinds | source |
|---|---|---|---|---|---|---|
ai-integration |
feature | full | >=1.6.0 |
plan:pre, verify:pre |
step, contribution, gate | first-party |
assumption-delta |
feature | full | >=1.6.0 |
plan:pre |
contribution | first-party |
audit |
feature | full | >=1.6.0 |
— | — | first-party |
broken-windows |
feature | full | >=1.7.0 |
ship:pre |
gate | first-party |
claude-orchestration |
feature | full | >=1.7.0 |
plan:post, execute:wave:pre |
contribution | first-party |
code-review |
feature | full | >=1.6.0 |
execute:post |
step | first-party |
drift |
feature | full | >=1.6.0 |
plan:pre, execute:wave:post |
gate | first-party |
external-job |
feature | full | >=1.7.0 |
plan:post, execute:wave:post |
contribution | first-party |
gap-analysis |
feature | standard | >=1.6.0 |
plan:post |
gate | first-party |
graphify |
feature | full | >=1.6.0 |
— | — | first-party |
intel |
feature | full | >=1.6.0 |
plan:pre |
step | first-party |
mempalace |
feature | full | >=1.6.0 |
discuss:pre, discuss:post, plan:pre, plan:post, execute:wave:post, verify:post, ship:post |
step, contribution | first-party |
nyquist |
feature | full | >=1.6.0 |
verify:post |
step | first-party |
pattern-mapper |
feature | full | >=1.6.0 |
plan:pre |
step | first-party |
profile-pipeline |
feature | full | >=1.6.0 |
— | — | first-party |
refactor-trigger |
feature | full | >=1.10.0 |
execute:post |
step | first-party |
research |
feature | standard | >=1.6.0 |
plan:pre |
step | first-party |
schema-gate |
feature | full | >=1.6.0 |
plan:pre |
contribution | first-party |
security |
feature | full | >=1.6.0 |
plan:pre, verify:post, ship:pre |
step, contribution, gate | first-party |
tdd |
feature | full | >=1.6.0 |
plan:pre, execute:post |
contribution, gate | first-party |
ui |
feature | full | >=1.6.0 |
plan:pre, execute:wave:post, verify:post |
step, gate | first-party |
Runtime capabilities (role: runtime) — 19
Runtime capabilities adapt GSD to a specific AI runtime or IDE — emitting
skills, agents, hooks configuration, and surface files for that host. They
typically register no loop hooks (their primary responsibility is surface
emission), so their extension-point and hook-kind cells are —.
| id | role | tier | engines.gsd | extension points | hook kinds | source |
|---|---|---|---|---|---|---|
antigravity |
runtime | core | >=1.6.0 |
— | — | first-party |
augment |
runtime | core | >=1.6.0 |
— | — | first-party |
claude |
runtime | core | >=1.6.0 |
— | — | first-party |
cline |
runtime | core | >=1.6.0 |
— | — | first-party |
codebuddy |
runtime | core | >=1.6.0 |
— | — | first-party |
codex |
runtime | core | >=1.6.0 |
— | — | first-party |
copilot |
runtime | core | >=1.6.0 |
— | — | first-party |
cursor |
runtime | core | >=1.6.0 |
— | — | first-party |
hermes |
runtime | core | >=1.6.0 |
— | — | first-party |
kilo |
runtime | core | >=1.6.0 |
— | — | first-party |
kimi |
runtime | core | >=1.6.0 |
— | — | first-party |
kimi-code |
runtime | core | >=1.7.0 |
— | — | first-party |
opencode |
runtime | core | >=1.6.0 |
— | — | first-party |
pi |
runtime | core | >=1.7.0 |
— | — | first-party |
qwen |
runtime | core | >=1.6.0 |
— | — | first-party |
trae |
runtime | core | >=1.6.0 |
— | — | first-party |
vscode |
runtime | core | >=1.7.0 |
— | — | first-party |
windsurf |
runtime | core | >=1.6.0 |
— | — | first-party |
zcode |
runtime | core | >=1.6.0 |
— | — | first-party |
Reviewer capabilities (role: reviewer) — 5
Reviewer capabilities declare a cross-AI reviewer lane — one external CLI or
model endpoint /gsd-review hands a plan to (ADR-2782 D3). They are not install
targets: they emit no skills, agents, hooks or surface files, so their
extension-point and hook-kind cells are —. A host that is also a reviewer
(Claude, Codex, Cursor, OpenCode, Qwen, Antigravity) keeps one manifest and
appears under runtime above, carrying its lane alongside its runtime body;
only lanes that GSD never installs into appear here.
Because a lane receives the plan text, requirements, research findings and
CONTEXT.md decisions, it is a disclosed executable surface and is consent-gated
at install like any other — see
the trust model.
| id | role | tier | engines.gsd | extension points | hook kinds | source |
|---|---|---|---|---|---|---|
coderabbit |
reviewer | full | >=1.8.0 |
— | — | first-party |
gemini |
reviewer | full | >=1.8.0 |
— | — | first-party |
llama-cpp |
reviewer | full | >=1.8.0 |
— | — | first-party |
lm-studio |
reviewer | full | >=1.8.0 |
— | — | first-party |
ollama |
reviewer | full | >=1.8.0 |
— | — | first-party |
Third-party capabilities
This matrix is the first-party catalogue: it is generated from the committed
registry and therefore lists only the capabilities that ship with GSD Core.
Installed third-party capabilities are NOT written into this committed file. Once a
user installs one via gsd capability install <spec> it enters the runtime
registry overlay (ADR-1244 D2); the overlay-aware view of what is installed on a
given machine is gsd capability list (see the
gsd capability command reference), which reports
first-party and installed third-party capabilities together using the same column
fields described below, with source = third-party.
Column values for third-party rows
| Column | Value |
|---|---|
| id | As declared in capability.json. Must not use reserved prefixes (gsd-, gsd-core-, anthropic-). |
| role | feature, runtime, or reviewer, as declared. |
| tier | core, standard, or full, as declared. |
| engines.gsd | Range from capability.json; verified at install and at each load. |
| extension points | The loop points the capability registers into, validated against the known 12 identifiers. |
| hook kinds | step, contribution, and/or gate as declared. Disclosed in the consent summary at install. |
| source | third-party |
Community registry
Whether GSD operates or advertises a central community registry of third-party capabilities is TBD/TBA (PRD). The matrix mechanic and all manifest fields ship regardless of that decision; URL/git/npm/tarball import does not depend on a central registry.
Manifest field reference
The fields below are defined in capability.json and govern how a capability
appears in this matrix. For the full schema, see
ADR-1244 D1
and the capability manifest reference.
| Field | Required | Type | Purpose |
|---|---|---|---|
version |
Yes | semver string | Capability version. The registry rejects manifests without it. |
engines.gsd |
Recommended | semver range | Host-version compatibility gate. Enforced at install and load. |
compatVersions |
No | object: cap-version → gsd-range | Graceful-downgrade table for sources that enumerate versions (git tags, registry, npm). |
integrity |
No | sha512-<base64> |
SHA-512 digest of the fetched bundle. Verified before extraction when present; mismatch aborts. |
provenance |
No | { sourceRepo, commit } |
Source provenance; populated in CI for first-party/curated capabilities. |
Related documents
- ADR-1244 — Capability Ecosystem
- The capability trust model — why the trust rules are structured as they are
- The phase loop — the 12 loop extension points in context
- Capability manifest reference — the full
capability.jsonschema - ADR-857 — the original capability architecture (D7/D8 extended by ADR-1244)