* test(#1962): add failing-first repro-hardening contract tests
Epic #1957 Phase 3A. Source-text-is-the-product contract tests: PBT shrinking
(fast-check/Hypothesis, minimized seed, manual-minimization degradation), the
four oracle types (specified/derived/metamorphic/implicit with implicit flagged
weakest), boundary neighbors (off-by-one/min-max/empty-singleton tied to the
equivalence class), oracle_type in DEBUG Resolution, and the Phase 1A tie-in
(minimized seed + real oracle => the mutation guardrail bites).
Failing-first: reference, agent cross-refs, and template field do not yet exist.
* feat(#1962): harden regression tests (PBT shrinking + oracle classification + boundaries)
Epic #1957 Phase 3A. Extends Minimal Reproduction (shrinking) and Test-First
Debugging (oracle classification + boundary neighbors):
- Shrinking: wrap an input-space failing input in a property (fast-check JS/TS,
Hypothesis Python) and store the MINIMIZED counterexample as the regression
seed; degrade to manual minimization when no PBT framework is present.
- Oracle classification: state specified / derived (contract/model) /
metamorphic / implicit (crash, weakest) before writing the assertion; record
under Resolution.oracle_type; never default to implicit silently.
- Boundary neighbors: off-by-one, min/max, empty/singleton around the fixed
defect's equivalence class.
Together they turn the regression test into a root-cause check — what the Phase
1A mutation guardrail needs to bite. Full rules extracted to gsd-core/references/
debugger-repro-hardening.md. INVENTORY + manifest + agent-size baseline +
install-parity goldens + AGENTS.md + DEBUG template updated.
* fix(#1962): address orthogonal review (bounding, provenance, oracle scope, sufficient-triple)
- HIGH: added a 'Bound the property/shrink run' section (60s timeout, degrade-
to-manual on timeout, do-not-raise-default-run-limits, argv-not-shell) —
the gauntlet violation the sibling references already honored.
- Medium: test-provenance caveat (the failing input often comes from the bug
report — author the generator from a sanitized description, cross-ref
debugger-fix-acceptance.md).
- Medium: oracle scope note — the 4 types cover deterministic bugs; non-
deterministic failures re-route to stability-stress per bug-taxonomy.
- Medium: Phase 1A tie-in corrected — seed+oracle is necessary not sufficient;
boundary neighbors close the adjacent-input escape; the sufficient triple is
seed+oracle+neighbors.
- Low: preserve the original noisy repro as a secondary reference; operationalize
'equivalence class' (the predicate the fix draws). Nit: degradation reworded.
* chore(#1962): backfill changeset pr number (PR #2409)
---------
Co-authored-by: sim <sim@local>
* test(#1961): add failing-first bug-taxonomy routing contract tests
Epic #1957 Phase 2B. Source-text-is-the-product contract tests (3 taxonomy
classes, explicit class->technique routing table, Bohrbug->repro+SBFL+bisect,
Heisenbug->record-replay/stability+SKIP-SBFL, Concurrency->atomicity/order/
deadlock checklist, bug_class in DEBUG Current Focus, supersede-not-append)
plus a routing-table specification object pinning the documented decisions
(SBFL forbidden on Heisenbug is the load-bearing 1B/2B seam).
Failing-first: reference, Phase 1.75, and routing-table reframe do not yet exist.
* feat(#1961): add bug-taxonomy classification + strategy routing to gsd-debugger
Epic #1957 Phase 2B (reliability-critical). Adds Phase 1.75: classify the
failure as Bohrbug / Heisenbug-Mandelbug / Concurrency, then route the
investigation technique via an explicit class->technique table (Kernighan: no
opaque heuristic). Bohrbug -> reproduction + SBFL (Phase 1.25) + git bisect;
Heisenbug/Mandelbug -> record-replay (rr) + stability-stress + statistical
sampling, with SBFL explicitly SKIPPED (a flaky spectrum poisons the Ochiai
ranking — the load-bearing 1B/2B seam); Concurrency -> the
atomicity/order/deadlock checklist first.
Reframes (supersedes, not appends — Zawinski) the flat 'Technique Selection by
situation' table into a class-routed table; the 11 techniques remain as routed
targets. bug_class recorded in Current Focus (DEBUG template); common-bug-
patterns catalog cross-referenced to the taxonomy.
Full rules extracted to gsd-core/references/debugger-bug-taxonomy.md. INVENTORY
+ manifest + agent-size baseline + install-parity goldens + AGENTS.md updated.
* fix(#1961): address orthogonal review (phase-name drift, General lane, revoke framing, row-scoped tests, bounding)
- HIGH: reference said 'Phase 1B' (epic shorthand); corrected to the deployed
'Phase 1.25' (matches the agent + SBFL reference).
- HIGH: 6 of 11 techniques (Rubber duck, Delta, Working backwards,
Differential, Comment-out, Follow-the-indirection) were orphaned by the
situation-table reframe. Added a 'General (any class, situation-cued)'
lane to BOTH the reference routing table and the agent's Technique
Selection table that re-homes them — supersede-not-append now holds.
- MEDIUM: the SBFL-skip is structurally retroactive (Phase 1.25 runs before
Phase 1.75 classification), so reframed the table column from 'Do NOT use'
to 'Revoke if already run' + an explicit 'retroactive revocation, not
proactive skip' note stating the ordering honestly.
- MEDIUM: contract tests are now row-scoped (parse the table by class, assert
per-row) instead of presence-only; added a guard that the previously-
orphaned techniques now have a General-lane route.
- LOW: pinned the canonical bug_class value form (lowercase-kebab:
bohrbug|heisenbug-mandelbug|concurrency; prose may use title-case).
- NIT: added a 'Bound the Heisenbug-chase runs' note (rr/stability/sampling
timeouts) per the unbounded-subprocess gauntlet.
* chore(#1961): backfill changeset pr number (PR #2407)
* test(#1960): add failing-first RCA-branching contract + schema-invariant tests
Epic #1957 Phase 2A. Source-text-is-the-product contract tests (fishbone
>=2 categories, AND-gate, multi-cause root_cause, backward compat, reasoning
checkpoint candidate_causes+and_gate fields, debugger-philosophy single-cause
note, DEBUG template) plus behavioral schema-invariant checks on two fixtures:
two contributing causes (AND-gate yes) -> both recorded; single-cause
(AND-gate no) -> one root_cause, identical to today.
Failing-first: reference, agent edits, and template note do not yet exist.
* feat(#1960): add RCA branching (fishbone + AND-gate) to gsd-debugger
Epic #1957 Phase 2A. Guards against 5-Whys single-cause bias: before committing
root_cause, the debugger enumerates candidate causes across >=2 Ishikawa
categories (code/config/environment/data) and explicitly answers an AND-gate
question. When the AND-gate fires, every contributing cause is recorded, so a
multi-cause fix no longer recurs via the unaddressed second cause.
Resolution.root_cause may hold one OR a small set (additive; single-cause
sessions are byte-identical to today). The Structured Reasoning Checkpoint gains
candidate_causes + and_gate fields; debugger-philosophy.md adds the
single-cause-bias trap.
Full rules extracted to gsd-core/references/debugger-rca-branching.md (slim
Phase 2 routing + 2 checkpoint fields kept in the agent). INVENTORY + manifest +
agent-size baseline + install-parity goldens + AGENTS.md + DEBUG template updated.
* fix(#1960): address orthogonal review (AND-gate self-consistency, parity guard, narrowed claim, ripples)
- Reference: the collapse rule now enforces AND-gate self-consistency —
and_gate=yes with a single confirmed cause is flagged as incomplete
(return to Phase 3); a race/timing note clarifies such bugs bridge
categories; the 'byte-identical' backward-compat claim narrowed to
'root_cause shape unchanged; reasoning_checkpoint gains 2 fields in every
session'.
- DEBUG.md: stale 'five-field' mirror prose -> seven-field (parallel-surface
drift the reviewer flagged); new debug-session-management parity test pins
the field-count claim to the gsd-debugger.md YAML keys (CRLF-safe).
- Scalar-assuming consumers of set-valued root_cause updated: session-manager
compact summaries (319/332), diagnose-only return (1062), archive entry
(1216), ROOT CAUSE FOUND return (1322).
- Test: added the AND-gate-yes/single-cause invariant + fixture; rephrased the
fixture describe block honestly as a schema-invariant specification.
- Phase 2 bullet phrasing clarified ('at hypothesis formation, before the
Phase 4 commit').
* test(#1960): parity regex accepts word-form count ('seven-field' or '7-field')
* test(#1960): parity regex counts array-valued YAML keys (no inline value)
* chore(#1960): backfill changeset pr number (PR #2405)
* test(#1959): add failing-first SBFL contract + Ochiai correctness tests
Epic #1957 Phase 1B. Source-text-is-the-product contract tests (Ochiai
formula documented, Tarantula fallback, top-N seeding, no-coverage skip
logged, ranking->Evidence, Bohrbug gating) plus a behavioral Ochiai
formula-correctness section: bound [0,1], max-score invariant, a known-fault
fixture proving the fault ranks #1 (criterion 2), clean degradation on
zero failing tests, and two fast-check properties.
Failing-first: reference file and agent routing do not yet exist.
* feat(#1959): add spectrum-based fault localization (Ochiai) pre-filter to gsd-debugger
Epic #1957 Phase 1B. When a runnable test suite with per-test coverage exists
(>=1 failing AND >=1 passing test), the debugger computes an Ochiai
suspiciousness ranking over the coverage spectrum and seeds the top-N
suspicious locations into Evidence as first-class hypothesis candidates,
narrowing the search space deterministically before LLM reasoning. Tarantula
documented as fallback. Degrades cleanly (logged, never silent) when there is
no test suite, no failing tests, or no per-test coverage, and is explicitly
not trusted on flaky/Heisenbug spectra (pairs with Phase 2B bug-taxonomy).
Full rules extracted to gsd-core/references/debugger-sbfl.md (slim Phase 1.25
routing kept in the agent to respect the size cap). No new coverage framework
— reuses the project's existing test/coverage runner. INVENTORY + manifest +
agent-size baseline + install-parity goldens + AGENTS.md updated.
* test(#1959): bound property generators to valid coverage counts
The [0,1] property generated failedExec independently of totalFailed, but
Ochiai's score is only bounded by 1 under the coverage invariant
failedExec <= totalFailed (a failing test that executed s is one of the
totalFailed failing tests). Out-of-domain inputs (failedExec=100, totalFailed=5)
make the formula correctly return >1. Bound failedExec by totalFailed via
fc.chain so the property tests the real domain. Also cleaned up the ranking
property (removed dead code).
* fix(#1959): address orthogonal review (monotonicity property, degradation row, coverage bounding)
- Replace vacuous ranking property (true-by-sort-construction) with a
non-trivial monotonicity property: holding totalFailed + passedExec fixed,
ochiai is non-decreasing in failedExec. An inverted formula would fail it.
- Add the missing 'no passing tests' degradation row (preconditions require
>=1 passing test; Tarantula would divide by totalPassed=0).
- Bound the coverage subprocess (CLAUDE.md gauntlet): cap the coverage run,
degrade-to-skip on timeout, never hang the debug session.
- Reword 'discard the ranking' -> 'mark the Evidence entry as revoked (do not
delete)' per Kernighan auditability.
* test(#1959): bound monotonicity-property generator to valid coverage (failedExecA <= totalFailed)
* chore(#1959): backfill changeset pr number (PR #2403)
Adds --raw to config-get workflow.build_command|test_command reads in the post-merge, regression, verify-phase, and audit-fix gates so an unset key is a genuinely empty string, not the literal "" — restoring the auto-detect cascade and graceful skip instead of a false exit-127 failure. Regression guard sweeps all four gate files. Fixes#2350.
* test(#1958): add failing-first guardrail contract tests
Epic #1957 Phase 1A. Adds source-text-is-the-product tests asserting the
5-signal fix-acceptance guardrail contract (target test, mutation check,
no-op/deletion detector, adjacent tests, revert-and-reconfirm), graceful
degradation, FIX REJECTED BY GUARDRAIL return path, per-signal debug-file
recording, and subprocess bounding.
Failing-first: reference file and agent sections do not yet exist.
* feat(#1958): add multi-signal fix-acceptance guardrail to gsd-debugger
Epic #1957 Phase 1A. Prevents accepting a fix that merely greens the test
(Goodhart defense / APR overfitting). Adds a 5-signal gate run before fix
acceptance: target test, mutation check (Stryker), no-op/behavior-deleting
detector, adjacent/held-out tests, revert-and-reconfirm. Degrades gracefully
when Stryker or a test suite is absent (each skip logged, never a silent pass),
records per-signal results under Resolution.verification, and returns a
FIX REJECTED BY GUARDRAIL outcome the session-manager surfaces for
revise / accept-as-debt / abandon.
Full rules extracted to gsd-core/references/debugger-fix-acceptance.md (slim
routing kept in the agent to respect the agent-size cap). Debug template +
INVENTORY + manifest + agent-size baseline + AGENTS.md updated.
* test(#1958): correct newline-tolerant assertion + regen install-parity goldens
The revert-and-reconfirm assertion collapsed whitespace before matching so
markdown line-wrapping does not break it. Regenerated the golden-install-parity
and install-tree fixtures (npm run gen:golden) to absorb the intentional
gsd-debugger.md / gsd-debug-session-manager.md / DEBUG.md / new reference-file
changes to the installed artifact tree.
* fix(#1958): tighten guardrail per orthogonal review
Addresses the isolated reviewer's findings:
- signal 5 now states its recorded-repro dependency and routes the no-repro
case to the degradation row; revert mechanism specified (git stash / git
revert -n); minimality flag tied to diff structure, not revert-ability.
- bounded-subprocesses section now bounds the git subprocess (5-30s) too,
requires argv-array argument passing, and scopes Stryker to the driving
regression test (a mutant killed only by a non-driving test is a finding).
- new test-provenance (security) clause: the driving test must be
agent-authored; bug-report repro scripts are DATA, never executed verbatim.
- tightened 3 contract assertions to bind to specific clauses
(guardrail_verdict field, deletion-reject-unless-RCA, 60s+git bounding).
- Goodhart framing softened to 'partially-independent'; DEBUG.md template
verification field notes the nested map shape.
* chore(#1958): backfill changeset pr number (PR #2396)
* fix(#1958): add issue ref to allow-test-rule annotation (ADR-456)
CI lint-allow-test-rule-refs requires every allow-test-rule exemption to
carry a 'see #NNN' issue ref per ADR-456. The new test file's annotation
lacked it; this adds (see #1958).
* refactor(#2384): leaf dispatch table + runCommand collapse (ADR-2346 P4)
Cutover all 55 remaining case arms from runCommand's switch to
HOST_COMMAND_ROUTERS. runCommand now contains only its default case
(~40 lines): the three-layer dispatch (capability → overlay → host table)
plus the unknown-command diagnostic. The 73-case switch is dissolved.
Each case body was relocated verbatim to a module-scope route*Command
function via a brace-matching extractor; inner break; statements (from
_dispatchNonFamily early-exit patterns) were converted to return;
(5 arms affected); loop break; statements preserved.
Closes#2384
* chore: retrigger CI
* chore(#2371): representative gate-fixture corpus + document-shaped property test
Adds tests/fixtures/representative/ — a permanent corpus of verbatim,
incident-sourced fixtures (never author-invented) from #2286, #2347,
#2365, #2366, each labeled with its expected gate verdict in a
MANIFEST.json and driven through the real CLI gate entrypoint via
tests/representative-corpus.test.cjs.
Adds a document-shaped fast-check property test alongside the existing
writer-seeded bijection test in tests/api-coverage.test.cjs: the existing
generator produces rows and renders them through the writer, so the
document shape is a constant and it cannot fail against a decoy table;
the new one generates the document space instead.
Two gates (#2365, #2347) are still open, so their corpus/property
assertions are marked with node:test's official `todo` option — the test
executes and reports its failure without affecting the process exit code
(https://nodejs.org/api/test.html#test-options). The audit-uat corpus
(#2286, fixed by #2317) is a normal passing assertion, proving the
methodology works end to end and not just cataloguing gaps.
Records the fixture-provenance rule in CONTRIBUTING.md: a gate's fixtures
may not be derived from the gate's own writer, grammar, or docstring
examples; a negative fixture must come from a source that doesn't know
the gate exists.
No production src/*.cts changes — validation only, per #2371's scope.
Closes#2371
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#2371): address orthogonal-review findings — dedup generators, fix field naming, wire dead fields
Standards-axis review findings, all fixed:
- Deduplicated the row-shape generators (capabilityGen/rowGen/validRowGen)
that were copy-pasted between the parse/render bijection test and the
new document-shaped property test in tests/api-coverage.test.cjs — a
future edit to one could have silently desynced the two properties.
Hoisted to a single module-scope declaration both tests reference.
- Renamed decision-coverage-guard/MANIFEST.json's expectedOutcome ->
expectedReason. It asserted against the gate's `reason` field, but this
codebase already has a real, different `outcome` field at parser
altitude (extractDecisions' DecisionOutcome) — naming the manifest
field after the wrong altitude's term was exactly the ambiguity the
"Fixture provenance" rule this PR adds exists to eliminate.
- Removed the unused `role` field from three MANIFEST.json files (never
read by any test) and wired the previously-dead per-fixture
`expectedMinItems` in audit-uat/MANIFEST.json into a real per-file
assertion in tests/representative-corpus.test.cjs, using cmdAuditUat's
`results` array — catches a regression that moves items between the
two fixture files while preserving the aggregate total, which the
existing total_items check alone would miss.
No changes to test intent or coverage — same assertions, correctly named
and fully wired.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: remove dead capabilityState/capabilityWriter requires from gsd-tools.cjs
Surfaced by the mandatory pre-PR lint gate (no-unused-vars) while
preparing this PR — unrelated to #2371's own changes, but a defect
found while working is fixed in place rather than deferred.
Leftover from #2368/#2370 (merged just before this branch rebased onto
it): the case 'capability' arm that needed these two requires was
relocated to bin/lib/capability-command-router.cjs, which already
requires both modules directly (lines 24-25) and is their only real
consumer (cmdCapabilityState, resolveCapabilityRuntimeState,
cmdCapabilitySet). The two requires left behind in gsd-tools.cjs had
zero other references in the file and were never re-exported —
confirmed via grep across the file and its module.exports.
Behavior-preserving: Node's require cache means the underlying modules
still load exactly once via capability-command-router.cjs's own
requires; gsd-tools.cjs never used its now-removed local bindings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#2371): replace todo-marked assertions with characterization tests
gsd-test's own JSONL result parser (gsd-test-runner's
internal/pipeline/parse.go, verified directly against that repo's
source) has no concept of node:test's `todo` option — it only
recognizes kind:"pass"|"fail" and hard-errors on anything else. A
{ todo: true } test whose body throws is counted as a real failure in
gsd-test's own verdict, exactly as if it weren't marked todo — proven
by an actual gsd-test run against this branch, which reported
outcome:"failed" with all six todo-marked assertions (the property
test plus five representative-corpus fixtures) in the failure list,
each carrying the correct raw node:test `todo` field the tool's parser
simply doesn't read.
Replaces todo with characterization: MANIFEST.json now carries both
the correct target verdict (expected*) and the exact current observed
verdict (currentBuggyOutput, directly verified against live CLI
output for all five fixtures). Tests assert currentBuggyOutput — an
honest, non-vacuous pin of today's known-broken reality that passes
today and will fail loudly the moment the referenced fix changes the
observed output, at which point the assertion should be flipped to
expected* and currentBuggyOutput deleted.
The document-shaped property test switches from throwing fc.assert to
non-throwing fc.check (returns RunDetails per fast-check's own docs)
and asserts report.failed === true directly, for the same reason.
Updates all prose (CONTRIBUTING.md, the fixture READMEs) that
previously claimed todo would be respected — that claim was
factually wrong for this repo's actual tooling and must not ship.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: regenerate golden-install-parity fixtures after rebase onto next
Rebasing onto the current next (which now includes #2381's
todo-severity changes to gsd-core/bin/gsd-tools.cjs) produced real
conflicts in all 18 golden-install-parity fixtures — expected, since
both branches changed the same gsd-tools.cjs hash entry. Resolved by
taking one side to unblock the rebase, then regenerating fresh from
source via npm run gen:golden and verifying the result; every file's
diff is exactly the one hash line for gsd-core/bin/gsd-tools.cjs,
correcting a stale intermediate hash from the arbitrary conflict pick.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(#2373): cutover resolve/git/config/research host routers (ADR-2346 P3)
Relocate 13 case arms from runCommand's switch to HOST_COMMAND_ROUTERS:
- resolve: resolve-model, resolve-granularity, resolve-execution (3)
- git: git (1)
- config: config-ensure-section, config-set, config-set-model-profile,
config-get, config-new-project, config-path, migrate-config (7)
- research: research-store, research-plan (2)
Each case body moved verbatim to a module-scope route*Command function
(closures over config/commands/output/_dispatchNonFamily preserved).
dispatchHostCommand extended to pass defaultValue + workstreamContext
(needed by config-get and config-path). No new files, no logic change.
Closes#2373
* chore: retrigger CI
add-todo.md gains a confirm-based infer_severity step (infer from the blocker/major/minor/cosmetic taxonomy, confirm via AskUserQuestion with TEXT_MODE fallback, before writing) and a severity frontmatter field. cmdListTodos and cmdInitTodos now surface severity, backward-compatible (key omitted when absent), in parity.
Closes#2337. Admin-merged (self-review bypass) with full green CI.
* refactor(#2368): extract capability arm to capability-command-router (ADR-2346 P2)
Behavior-preserving relocation of the 706-line case 'capability': arm from
gsd-tools.cjs into a new hand-authored bin/lib/capability-command-router.cjs
(sibling of ensure-runtime-build.cjs). The 15 bin/-relative require paths are
rewritten to sibling-relative (correct for bin/lib/). dispatchHostCommand is
now async (capability's install/upgrade/consent ops await the lifecycle); sync
routers (state/phase/…) pass through await unchanged. case 'capability':
removed; capability dispatches via HOST_COMMAND_ROUTERS.
Validated by the existing capability-lifecycle / -consent / -trust / -loader
test suites (no logic changed). Golden install-parity fixtures regenerated.
Closes#2368 (Slice 1 — relocation). Probe consolidation (capHostVersion→
readHostVersion, capReadStrict dedup) deferred to a follow-up slice.
* fix(#2368): add capabilityState/capabilityWriter requires + INVENTORY row
The relocated capability arm references capabilityState (cmdCapabilityState,
resolveCapabilityRuntimeState) and capabilityWriter (cmdCapabilitySet) — both
module-scope requires in gsd-tools.cjs (L288/289) that the initial closure-dep
scan missed. Added as sibling requires to capability-command-router.cjs. Also
adds the new cli module to docs/INVENTORY.md + regenerates the manifest.
* fix(#2368): correct capHostVersion __dirname depth for bin/lib/ relocation
capHostVersion's VERSION/package.json paths were bin/-relative ('..' and
'..','..'); on relocation to bin/lib/ they resolved one level too deep,
so capHostVersion returned 0.0.0 and capability install failed the
engines.gsd gate (#1920). Added one more '..' to each (now resolves
gsd-core/VERSION and repo-root package.json correctly).
* test(#2368): drop capability from the invocation loop (async/FS vs /fake/cwd)
capability is async and does FS/config reads, so invoking it against the
unit test's /fake/cwd is fragile. The 6 sync Tier-1 routers stay in the
invocation loop; capability is covered by the non-invoking registry-
ownership assertion + the dedicated capability-* test suites.
* chore: retrigger CI (no-changelog label now present)
* refactor(#2360): host dispatch table + state cutover pilot (ADR-2346 P1)
Pilot cutover for ADR-2346 Phase 1 (epic #2345). Introduces the Layer-2 host
dispatch table — dispatchHostCommand + HOST_COMMAND_ROUTERS, consulted in
runCommand's default case after capability/overlay dispatch, before the
unknown-command error. Migrates 'state' as the pilot: removes the hardcoded
case 'state': arm; state now dispatches default -> dispatchHostCommand ->
routeStateCommand, byte-identical to the old path (proven by the new
state-command-cutover equivalence test, 5-category template).
Host commands are NOT capabilities (core, non-toggleable, no tier/activationKey)
— the capability registry stays reserved for toggleable feature bundles per
ADR-959. This is the host-vs-capability distinction the merged ADR-2346 lacked;
the ADR is corrected here alongside the code that realizes it.
- gsd-core/bin/gsd-tools.cjs: HOST_COMMAND_ROUTERS + dispatchHostCommand
(prototype-pollution-safe); wired into default case; case 'state': removed;
dispatchHostCommand + HOST_COMMAND_ROUTERS exported for tests.
- tests/state-command-cutover.test.cjs: UNIT/DISPATCH/BEHAVIOR/REGISTRY
equivalence (recording-mock + runGsdTools end-to-end + pollution guard).
- docs/adr/2346-*.md: refine Decision 1/2 to the host-table vs capability-
registry model (correction that did not land in the merged #2355).
Behavior-preserving. Subsequent P1b/c PRs migrate phase/init/roadmap/validate/
verify using this proven template.
Closes#2360.
* test(#2360): regenerate golden fixtures + allowlist for state cutover
Bookkeeping for the gsd-tools.cjs change: npm run gen:golden regenerates the
install-parity fixtures (gsd-tools.cjs content hash changed), and the new
tests/state-command-cutover.test.cjs is added to the lint-test-file-count
allowlist under the 'state' prefix.
* refactor(#2360): migrate remaining Tier-1 routers (phase/init/roadmap/validate/verify)
Completes P1: all 6 Tier-1 host routers now dispatch via HOST_COMMAND_ROUTERS
(state landed in the pilot commit). init preserves its #1688 warnIfStaleBake
pre-hook; validate binds the output emitter. Cutover test extended to assert
all 6 are consumed + owned. Golden install-parity fixtures regenerated.
* fix(#2308): make new-milestone workstream-aware; stop clobbering shared PROJECT.md
Step 4 rewrote the `## Current Milestone` heading in the shared root PROJECT.md
unconditionally. references/workstream-flag.md marks PROJECT.md `# Shared`, and
per-workstream milestone state already lives in the workstream's own STATE.md /
ROADMAP.md / REQUIREMENTS.md. With parallel milestones — the sanctioned design —
whichever workstream ran new-milestone last silently won the shared heading.
Step 4 is now skipped when a workstream is active; step 6 no longer stages
PROJECT.md in that mode (cmdCommit returns nothing_to_commit rather than failing
when a staged path is unchanged).
Also fixes a second defect found while diagnosing this, same root cause (the
workflow was workstream-unaware): step 1 parsed only --reset-phase-numbers and
the milestone name, so GSD_WS was never set — yet ${GSD_WS} was interpolated at
the routing lines. It always expanded to empty, so `/gsd:new-milestone --ws x`
suggested `/gsd:discuss-phase [N]` with the workstream scope silently dropped,
violating the routing-propagation contract. Step 1 now parses --ws using the
established idiom from verify-work.md.
Guard is keyed on GSD_WS, not $GSD_WORKSTREAM: the runtime launcher does not
export the latter and it is only priority 2 of 5 in resolution, so it would miss
the --ws flag case that is the actual repro.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* test(#2308): regenerate install goldens for the new-milestone workflow change
gsd-core/workflows/ ships as an installed artifact, so new-milestone.md's content
hash is pinned in all 18 runtime golden fixtures. Only that hash changed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* fix(#2308): address review — inert step-6 guard, dropped Evolution repair, tautological tests
Independent review found the first pass was partly cosmetic:
1. The step-6 `if [ -n "$GSD_WS" ]` branch was INERT. GSD_WS is assigned in
step 1's shell and each step's bash block runs in its own shell — this file
already proves it, since step 5 round-trips OUTGOING_MILESTONE through a file
for exactly that reason (#2288). The guard read an unset variable, always took
the flat branch, and staged PROJECT.md anyway. Rather than re-deriving GSD_WS
in step 6, the branch is removed entirely: step 4 Part A's guard is what
protects the shared heading, so post-guard the only content PROJECT.md can
carry is Part B's idempotent Evolution backfill — which must be staged, not
stranded. A regression test now asserts no cross-step GSD_WS branch returns.
2. Skipping ALL of step 4 also dropped the `## Evolution` structural repair — a
shared, idempotent backfill that is not workstream state. A pre-Evolution
project running only `--ws` would never get the section that transition and
complete-milestone expect. Step 4 is now split: Part A (milestone-state write)
is workstream-guarded; Part B (Evolution) always runs.
3. The tests were tautological prose-pinning — including one asserting a comment
mentions "#2308". The step-6 test asserted the guard's TEXT was present, so it
passed on the inert guard it existed to catch. Replaced with executable tests
that extract the step-1 and step-6 fences and run them under bash with stubbed
gsd_run, asserting real parse and --files behavior.
4. --ws is now stripped from the milestone name (step 1 previously left
"--ws search" in the remaining text), and documented in argument-hint,
help/modes/full.md, and docs/COMMANDS.md.
5. Changeset no longer overstates: --ws reaches the prose guard and routing hints
only, not the SDK calls (state.milestone-switch/phases.clear/init.new-milestone
still take no ${GSD_WS} — out of scope here).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* chore(#2308): regenerate SKILL.md, goldens, and size baseline for the argument-hint change
skills/gsd-new-milestone/SKILL.md is generated from commands/gsd/new-milestone.md,
so documenting --ws in the argument-hint made it stale (caught by lint:ci's
gen-plugin-skills --check). Regenerated it plus the install goldens and workflow
size baseline, since commands/, skills/, and gsd-core/workflows/ all ship.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2308): backfill PR number 2338 into changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#2070): fail-first tests for adaptive model_profile and models tier validation
Encodes the three acceptance criteria from #2070 plus the boundary cases the
resolver silently ignores today (non-string values, empty string, mistyped
phase-type key), and pins VALID_TIERS to a catalog-derived set.
Red phase: these fail against current src/ by design.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* fix(#2070): accept adaptive model_profile in validate health; warn on invalid models tiers (W022)
W004 sourced its profile list from a hand-maintained literal that predated the
adaptive profile, so `"model_profile": "adaptive"` was false-flagged. It now
reads VALID_PROFILES, which model-catalog.cts derives from model-catalog.json.
models.<phase_type> was validated nowhere: the resolver's tier gate silently
drops unknown values, so a typo like `"planning": "opuss"` was an undiagnosable
no-op. A new W022 flags unknown phase-type keys and invalid tier values
(including non-string values, which the same gate also drops).
VALID_TIERS moves from a function-local literal in model-resolver.cts to a
catalog-derived export, so health and the resolver cannot disagree by
construction rather than by parity test. Object.values(adaptiveTierMap) is
['opus','sonnet','haiku'] plus 'inherit' — identical to the previous literal,
so resolution behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2070): changeset for validate health adaptive profile + W022
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* fix(#2070): close review findings — malformed models, tier-list duplication, changeset gate
Review of the initial fix surfaced three real defects, folded in per the
no-defer rule:
1. verify.cts: the W022 guard skipped a top-level `models` that is present but
not a plain object (`[]`, `"opus"`, `5`, `true`). The resolver ignores those
identically, so they were the same undiagnosable no-op #2070 targets — just
one level up. They now warn; absent/null/{} stay silent.
2. config-loader.cts: RUNTIME_OVERRIDE_TIERS was a second hardcoded copy of the
tier vocabulary this change had just de-hardcoded elsewhere. It now derives
from the catalog via ADAPTIVE_TIER_VALUES (no 'inherit' — runtime overrides
resolve to a concrete tier). Byte-equivalent to the old literal.
3. scripts/changeset/lint.cjs: USER_FACING_PREFIXES omitted `src/`. Post-ADR-457
the product source is src/*.cts compiled to a gitignored gsd-core/bin/lib,
so the `gsd-core/` prefix is dead coverage for library code and a src/-only
PR could merge with no release note — including this one. Adding `src/`
closes the gate; tests/ stays non-user-facing.
Also corrects a false docstring in the VALID_TIERS test: value-equality cannot
detect a re-hardcoded literal, so the test no longer claims it does.
Two review findings were rejected with evidence rather than actioned:
- W021 double-allocation is governed by ADR-612 ("W021 renumber -> void ...
kept, message-disambiguated"), not a defect.
- Global-defaults validation would be a false-positive generator: config-loader
reads ~/.gsd/defaults.json only on the "no .planning/" branch, and health
early-returns E001 without .planning/, so those values provably never affect
resolution in any context health can run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* test(#2070): regenerate install goldens for the changeset-lint change
scripts/ ships as an installed artifact, so scripts/changeset/lint.cjs's content
hash is pinned in all 18 runtime golden fixtures. Adding 'src/' to
USER_FACING_PREFIXES changed that hash and tripped every golden parity check.
Regenerated via `npm run gen:golden`; the only delta is the lint.cjs hash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2070): backfill PR number 2336 into changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#2297): scope resolve_model_ids:"omit" to the resolving runtime
The installer writes resolve_model_ids:"omit" for non-alias runtimes into the
machine-wide ~/.gsd/defaults.json (#1156); any runtime read it back, so install
order silently flipped Claude's adaptive tier aliases (executor->sonnet,
planner->opus) to '' in no-project sessions.
Resolution is now scoped to the runtime actually resolving, identified by a new
per-install <install>/gsd-core/.gsd-runtime marker (installer writes it beside
VERSION). The "omit" branch returns '' only when the PROJECT explicitly set omit
(honored for all runtimes, #2517 finding #4) OR the active runtime lacks native
aliases. Claude ignores a global-defaults-only omit and keeps its aliases; the
active runtime is canonicalized (GSD_RUNTIME -> config.runtime -> marker ->
claude) so alias/case spellings can't defeat the check; explicit project
omit is workstream/project-scope aware; explicit true still materializes IDs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(#2297): backfill PR number 2332 into changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#2293): recognize --agy/--antigravity in plan-review-convergence whitelist
The convergence reviewer-flag whitelist predated the 1.7.0 Antigravity CLI
adapter and silently dropped --agy/--antigravity, so convergence fell back to
--codex only and the working adapter was unreachable (worse after Gemini CLI's
upstream shutdown). Add both flags to the workflow grep whitelist, the command
argument-hint + flag docs, and the regenerated SKILL.md; they pass through to
/gsd-review unchanged. --gemini behavior is untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(#2293): backfill PR number 2325 into changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#2288): archive phase history under the outgoing milestone version
phases.clear derived its archive directory from a live getMilestoneInfo()
read, but new-milestone.md switches the milestone BEFORE phases.clear runs,
so phase history was filed under the NEW milestone's <version>-phases/ dir.
Add a --archive-version override (threaded from new-milestone.md, captured
before the switch) with precedence override -> live read -> dated label.
Harden the version label against path traversal on both phases.clear and the
sibling milestone-complete sink (the label is a moved directory name), and
persist the outgoing version via a file + quoted shell expansion so untrusted
STATE.md content is never re-parsed by the shell.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(#2288): backfill PR number 2323 into changesets
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The executor SCOPE BOUNDARY convention (agents/gsd-executor.md) logs
out-of-scope discoveries to a phase directory's deferred-items.md, but no
reader ever consumed it — forensic_audit, cmdAuditUat, and capture --list
all skipped it — so deferred items were permanently invisible.
cmdAuditUat (src/uat.cts) now scans each phase dir's deferred-items.md via
a new parseDeferredItems (reusing the collectSection/splitGapsEntries/
extractGapEntryFields seams) and surfaces entries whose status != resolved
(fail-safe: a missing/garbled status surfaces rather than hides, matching
the false-negative-averse posture of #2286). forensic_audit
(gsd-core/workflows/progress.md) gains Check 7 that globs
.planning/phases/*/deferred-items.md and reports unresolved entries.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The claude-orchestration capability (#1143) shipped registered 'active'
but fully inert: detectWorkflowBackend/emitWorkflowScript had no caller
outside their own CLI router, and execute-phase.md declared an
execute:wave:pre hook point that the workflow body never rendered — so
claude_orchestration.enabled:true had zero effect on real runs.
Approach B (maintainer-chosen):
- execute-phase.md now renders the execute:wave:pre hook
(gsd_run loop render-hooks execute:wave:pre) at a new step 2.75,
immediately before each wave's Agent() dispatch — fixing the latent
dead-hook gap for any pre-wave capability.
- Move the claude-orchestration contribution execute:wave:post ->
execute:wave:pre (a pre-wave backend selector belongs before dispatch,
not after); rename fragments/execute-wave-post.md -> execute-wave-pre.md
with prose instructing the orchestrator to call resolve-wave-dispatch
before step 3. Unrelated wave:post contributions (ui.safety-gate, drift,
external-job, mempalace) untouched.
- New .cts seam resolveWaveDispatch(input) composes detectWorkflowBackend
+ emitWorkflowScript into one {backend:'inline'|'workflow', ...} result;
exposed as gsd-tools claude-orchestration resolve-wave-dispatch. This is
a real non-CLI-router, non-test caller of both functions.
Fail-closed: any gate miss (disabled, non-Claude runtime, Workflow tool
absent, SDK below floor, execution_backend:inline, malformed input) or an
emit failure resolves to inline with a byte-identical result shape — no
regression to the default-off execute-phase path.
Regression tests (tests/fix-2285-*) cover happy-path activation + SDK-floor
BVA, the fail-closed gate-miss table with detectWorkflowBackend parity, a
fast-check composition property, capability.json contribution assertions,
and a source-contract guard that execute:wave:pre is now actually rendered.
Dependent registry-shape assertions updated in-scope.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /gsd-debug orchestrator handled the gsd-debug-session-manager return
with only two literal-string checks (DEBUG SESSION COMPLETE, ABANDONED)
and no else branch, so a usable-but-non-terminal progress summary (the
manager's own turn/context budget exhausted mid-loop, with a valid
on-disk checkpoint) fell through to the user as if the debug were
complete. Same gap at the continue subcommand.
Callee side (agents/gsd-debug-session-manager.md): add an explicit
non-terminal CONTINUE_REQUIRED return marker, distinct from the two
terminal shapes and from a genuine user-input checkpoint.
Orchestrator (gsd-core/workflows/debug.md Sections 4 and 1c): classify
returns exhaustively — recognized terminal markers behave as before,
anything else is non-terminal and auto-resumes by re-spawning the
session manager from the same slug/checkpoint. Anti-loop guard: after
two consecutive no-progress resumes (unchanged next_action/updated),
emit a blocker report instead of looping.
Regression test (source-text contract guard, fix-2196 idiom) asserts
both sections' non-terminal/auto-resume branch, the CONTINUE_REQUIRED
marker, and the anti-loop bound.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(#2162): opt-in compact GSD-state format for the statusline
New statusline.state_format config, enum full|compact (default full —
existing rendering untouched). "compact" renders the state segment as
"<version> · P<phase>/<total> · <status>", e.g. "v1.12 · P7/12 ·
executing" — dropping the milestone name and progress bar (the two
biggest width costs) and collapsing narrative statuses to a single
keyword. Per the #2162 approval conditions, the keyword set is the
canonical vocabulary from normalizeStateStatus() in state-document.cjs
(discussing/planning/executing/verifying/completed/paused) — no
parallel hand-rolled list, so the vocabularies can't drift — and the
canonical stuck state "paused" renders uppercase as PAUSED (no new
"blocked" lifecycle state). Statuses the normalizer passes through
unrecognized fall back to their first word capped at 16 chars.
Lifecycle scenes preserved: active_phase wins over the body phase
number, milestone completion renders "complete", idle-with-next-action
renders "next <action> <phases>".
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* docs(#2162): changeset fragment for PR #2175
* fix(#2162): review fixes — ENUM_KEYS coverage, cap boundary tests, changeset format
- register statusline.state_format in the fix-1628 coercion-bypass matrix
- 15/16/17-char boundary tests for the shortGsdStatus fallback cap
- changeset body ends with the (#2162) citation per house convention
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(#2162): round-2 review fixes — scene exclusivity, direct config-set coverage
- compact renderer gates the milestone-complete scene behind the absence of
an in-flight phase id, mirroring formatGsdState's if/else precedence
(Scene 1 beats Scene 3); regression test covers the non-atomic
active_phase + percent=100 STATE.md shape
- direct config-set accept/reject test for statusline.state_format plain
strings (ENUM_KEYS matrix covers only the JSON coercion shapes)
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* test: regenerate golden-install-parity fixtures for the statusline hook change
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(#2162): complete-scene gate matches formatGsdState exactly (+property tests)
Re-review Major: gating done on !phaseId held completion back for the
legacy phaseNum shape — formatGsdState reaches Scene 3 on percent=100
regardless of phaseNum, so compact must too. Gate is now !s.activePhase.
The phaseNum-only test now expects 'complete' and cross-checks the full
renderer; a parity test feeds identical inputs to both renderers.
Re-review Minor: shortGsdStatus gets fast-check property coverage
(totality, canonical fixed points, separator safety, fallback shape).
Golden fixtures regenerated for the hook byte change.
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(#2220): replace invalid 'mine --room' with detect_room() staging approach
mempalace mine has no --room flag (only search does) — verified against
MemPalace 3.5.0 official docs (mempalaceofficial.com/reference/cli.html).
The headless capture path used --room, causing every headless/no-MCP run
to fail with 'unrecognized arguments: --room' and silently skip capture.
Fix: replace the flag with a staging-based approach that uses detect_room()'s
documented folder-path match — stage the artifact under a room-named subfolder
with a mempalace.yaml room taxonomy, then run 'mempalace mine <stage> --wing'.
Docs sources cited in-file:
- CLI reference: https://mempalaceofficial.com/reference/cli.html
- Mining guide: https://mempalaceofficial.com/guide/mining.html
- Config guide: https://mempalaceofficial.com/guide/configuration.html
Changes:
- skills/gsd-mempalace-capture/SKILL.md: headless staging instructions
- commands/gsd/mempalace-capture.md: same
- capabilities/mempalace/fragments/capture-problems.md: reference staging
- .gitignore: exclude .planning/.mempalace-stage/
- tests/mempalace-capture-headless-invocation.test.cjs: regression test
- Golden install parity fixtures + workflow-size baseline regenerated
* docs: backfill changeset PR number (#2260)
* fix: regenerate golden fixtures after next merge
* fix(#2204): phase-completion writes 'All phases complete' per ADR-2207
completePhaseCore was writing the overloaded bare 'Milestone complete' on the
last phase — the same string space the milestone-close verb owns for terminal
state. Per ADR-2207, phase-completion now writes the existing intermediate
value 'All phases complete' (already used in gsd2-import.cts). Milestone
termination ('<version> milestone complete' / 'Awaiting next milestone')
remains solely with milestoneCompleteCore.
Status lifecycle: Ready to plan → All phases complete → <version> milestone
complete → Awaiting next milestone.
Changes:
- src/state-transition.cts: completePhaseCore status value
- src/phase.cts: #2028 guard comment
- tests/state-transition.test.cjs: assertion + test name
- tests/phase.test.cjs: 8 assertion updates (positive + negative)
- tests/state.test.cjs: normalizeStateStatus test case + reset regex
- tests/workstream.test.cjs: fixture status to terminal value
- gsd-core/workflows/progress.md: Route D label
- gsd-core/workflows/transition.md: Route B label
- CONTEXT.md: Status lifecycle glossary entry (ADR-2207)
- .changeset/brave-geese-jump.md
* test(#2204): regenerate golden-install-parity fixtures + workflow-size baseline
Workflow file edits (progress.md, transition.md) changed install payload
hashes and pushed past the committed workflow-size baseline. Regenerated
all 17 golden-install-parity fixtures + claude-local via the standalone gen
script (which now also covers the local-scope claude layout). Updated
workflow-size-baseline.json and agent-size-baseline.json via size:baseline.
* fix(#2204): correct claude-local golden hashes + document gen-script limitation
The gen-script's claude-local generation produces macOS-specific hashes
incompatible with Linux CI (local-scope install embeds platform-varying
node-runner paths). Reverted to manual update using Linux FAILURES.md
+actual hashes for the 2 changed workflow files. Added explanatory
comment in the gen script.
* test(#2204): add isCompletedInventory coverage + clarify CONTEXT.md glossary
Addresses orthogonal code-review findings (Medium #1 + #2):
- Add isCompletedInventory test cases for ADR-2207 status lifecycle
(terminal 'milestone complete' → true; intermediate 'All phases
complete' → false; archived → true; active statuses → false)
- Clarify CONTEXT.md glossary: note that isCompletedInventory
intentionally excludes the intermediate value
* docs: backfill changeset PR number (#2259)
* docs(#2204): add Status lifecycle table to state-md reference (ADR-2207)
* chore(#2143): markdown table model + schema registry + fail-loud pilot — Phase 1
Phase 1 of epic #2143 (ADR-2143): consolidate markdown table parsing onto a
canonical seam and migrate the pilot reader.
- Add src/markdown-table.cts: parseMarkdownTable (GFM tables -> typed
{columns, rows} addressed by column NAME; ragged rows are typed parse
errors, not silent), a single-source TABLE_SCHEMAS registry
(RoadmapProgress / RequirementsTraceability / QuickTasks / Security, with
variants under one id), matchTableSchema, and findTableBySchema. Result<T>
is scoped to this seam (distinct from the dispatch Result).
- Migrate deriveProgressFromRoadmap (src/phase-lifecycle.cts) off the
position-anchored regex to name-based resolution via the seam — fixes#2137
(the 5-column milestone-grouped Progress table previously returned all-null).
- Add a schema-backed `gsd-tools quick-tasks-append` subcommand and route
fast.md's log_to_state through it, retiring the inline `awk NF-2` column
arithmetic — fixes#2133 (addresses #2012, #2119). Cell values are escaped
(| and newlines) and the STATE.md read-modify-write is atomic under
readModifyWriteStateMd (lost-update race, cf. #500/#905/#1230).
- Writer/reader/template parity test guards TABLE_SCHEMAS against drift
(ADR-2143 §3 Generative-Fix-Divergence).
Registration: .gitignore, eslint.config.mjs, docs/INVENTORY.md +
INVENTORY-MANIFEST.json, CONTEXT.md glossary, docs/CLI-TOOLS.md.
Behaviour-preserving for the canonical 4-column Progress table; the named
bugs are driven fail-first. Extend-never-mutate (ADR-2143 §2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#2242): backfill changeset PR number (#2248)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#2242): escape backslash before pipe in markdown-table cell escaping
CodeQL js/incomplete-sanitization (high): escapeCell escaped | -> \| but not
the backslash itself. Now escapes \ -> \\ before | -> \|, and splitTableRow
unescapes both \\ -> \ and \| -> | symmetrically so cell values (incl.
literal backslashes) round-trip exactly. Added backslash round-trip tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#2242): read ROADMAP Progress table by column name — supersede #2168 ad-hoc scan
Rebase reconciliation with #2168 (the tactical #2137 fix that marked itself
"pending #2143"). deriveProgressFromRoadmap now resolves the Progress table via
a new seam helper findTableWithColumns (first table whose header is a superset of
Phase/Plans Complete/Status/Completed, any order, extra columns ignored) and reads
cells by NAME — order/injection-invariant per ADR-2143 §3 — instead of the exact
TABLE_SCHEMAS match. This satisfies #2168's column-invariance property test while
staying seam-based and preserving its `## Progress` scoping (#2012/#1445).
Ragged Progress tables now resolve to null (ADR-2143 fail-loud); updated the stale
state.test.cjs assertion that predated the Phase-1 migration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#2176): ground the Antigravity reviewer in the repo under review
- capability-probe --add-dir (mirrors the Codex bypass-flag probe) and pass
the repo root on both invocation arms
- anchor _AGY_PROMPT to the absolute repo root; mandate a
REVIEWED-WITHOUT-REPO-ACCESS self-report when the repo is unreadable
- stamp a [reviewed-without-repo-access] marker on self-reported or
scratch-anchored output; Consensus Summary down-weights marked reviews
- apply the same absolute-root anchor to the cursor-agent prompt (AC5)
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* docs(#2176): changeset fragment for PR #2184
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(#2176): review fixes — size baseline, cursor root anchor, anchored blind tells
- regenerate tests/workflow-size-baseline.json for review.md's growth
- cursor anchor uses git rev-parse --show-toplevel (bare pwd resolved the
wrong root from a repo subdirectory)
- blind-review tells anchored: self-report to the first lines of output,
scratch tell to a workspace-declaration phrasing — a grounded review
quoting either string is no longer mis-stamped
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(#2176): round-2 review fixes — scratch-tell bridge, behavioral test, changeset
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* test: regenerate golden-install-parity fixtures for the review.md change
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* test(#2176): pass the transcript path to bash with forward slashes
The behavioral detection test substitutes a mkdtemp path into the bash
compound; on Windows runners that path contains backslashes, which bash
strips, so the transcript is never found and the first assertion fails
(windows-latest/24 lane). Git Bash accepts D:/-style paths.
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(#2176): use /gsd:review namespace syntax in workflow comment
The slash-command namespace invariant (#3443) bans retired /gsd-<cmd>
references in Claude-facing sources; a cursor-anchor comment used
/gsd-review. Size baseline + golden fixtures regenerated for the byte
change.
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* test(#2176): derive the POSIX path via path.sep, not a hardcoded separator
Review finding: out.replaceAll('\\', '/') hardcodes both separators;
use the separator-safe out.split(path.sep).join(path.posix.sep) idiom.
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* test(#2176): use the merged toPosixPath seam for the bash path
Per maintainer note: #2247's shell-command-projection now centralizes
running-OS → POSIX path conversion; import it instead of the inline
split/join idiom.
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(execute-phase): honor gate="blocking-human" in auto-mode checkpoint handling
The package-legitimacy gate (#2827) spans two layers. gsd-executor refuses to
auto-approve a gate="blocking-human" checkpoint and escalates it so a human can
vet the package. execute-phase's checkpoint_handling step then dispatched purely
on checkpoint *type* and never read gate -- so under --auto/--chain it
auto-approved the checkpoint the executor had just refused to auto-approve.
Net effect: the slopsquatting defence was inert in exactly the unattended mode
where it matters. An [ASSUMED]/[SUS] package reached install with no human ever
seeing the prompt.
- gsd-core/workflows/execute-phase.md: carve out gate="blocking-human" (and the
package-legitimacy what-built markers) ahead of every auto-mode branch.
- gsd-core/references/checkpoints.md: document the gate attribute and its two
values. blocking-human previously appeared nowhere outside gsd-executor.md,
so no planner had a documented way to author a non-auto-approvable checkpoint.
- tests/package-legitimacy-gate.test.cjs: the existing regression test asserted
the executor half only, which is why it stayed green while the gate was open.
Now asserts the orchestrator half too.
* chore(changeset): link to issue #2107
* chore(changeset): backfill PR number 2113
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JNR8m2pv5U7ubn4iiXVrMa
* test(#2107): refresh golden-install-parity hashes for edited gsd-core files
The golden fixtures pin content hashes for gsd-core/references/checkpoints.md
and gsd-core/workflows/execute-phase.md, both edited by this fix. Regenerated
via UPDATE_GOLDEN=1; only those two keys change across all 17 runtime fixtures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JNR8m2pv5U7ubn4iiXVrMa
* fix(#2107): keep the carve-out inside the ADR-857 host-loop budget
The ADR-857 phase-6 ratchet pins execute-phase.md below 93600 LF bytes so
optional-feature logic keeps migrating out of the host loop. The carve-out
first landed 623 bytes over that ceiling.
Move the two-layer rationale (why gsd-executor escalates these checkpoints)
into references/checkpoints.md, where the gate is now documented, and reduce
the workflow to the operative rule. execute-phase.md is 93589 bytes, under
the ceiling; the gate token and both <what-built> marker strings are kept
because the orchestrator matches on them.
Refresh the two baselines the edit invalidates: golden-install-parity
fixtures (only the checkpoints.md and execute-phase.md hashes move) and
workflow-size-baseline.json (one line). The ADR-857 ceiling itself is
untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JNR8m2pv5U7ubn4iiXVrMa
* fix(#2107): executor honors blocking-human on the decision branch + gate transport
Review found the fix incomplete one layer down. Two executor-layer gaps:
1. Blocker — agents/gsd-executor.md auto-mode dispatch gated
checkpoint:human-verify on gate="blocking-human" but the checkpoint:decision
branch below auto-selected the first option with no gate check. The executor
resolves a decision itself (auto-selects and continues) without returning it,
so the orchestrator carve-out never runs for it. A planner following the new
checkpoints.md rule 6 ("gate a decision whose default would be wrong to
assume") would have it silently auto-selected under --auto/--chain — the exact
#2107 harm, one checkpoint type over. The decision branch now STOPs and
returns for an explicit human decision when gate="blocking-human".
2. Major (transport) — checkpoint_return_format carried no field conveying the
gate to the freshly-spawned orchestrator, so recognition of the proactive
pre-install checkpoint rested on freeform prose. Added a **Gate:** field to
the return format and re-pointed the execute-phase carve-out at it
("If the returned Gate: is blocking-human"). Net byte-negative: execute-phase.md
drops 93589 -> 93583, widening ADR-857 headroom from 11 to 17 bytes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#2107): cover decision carve-out + gate transport, de-vacuum conditional tests
- New: 'auto mode does not auto-select a blocking-human decision checkpoint'
asserts the executor decision branch STOPs on blocking-human. Verified red on
the pre-fix executor (2 fail), green with the fix (27 pass).
- New: 'checkpoint_return_format transports the gate ...' asserts the **Gate:**
field carries blocking-human across the executor->orchestrator boundary.
- New: 'auto-select rule for decision is conditional' — orchestrator-side mirror
of the human-verify conditional test, for the execute-phase decision branch.
- Fix vacuous test: both conditional tests now assert the anchor matched
(length > 0) before iterating, so anchor drift can no longer pass with zero
assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#2107): refresh golden + size baselines for executor + execute-phase edits
Regenerated via UPDATE_GOLDEN=1 and update-size-baseline.cjs. Only the
gsd-executor.md and gsd-core/workflows/execute-phase.md hashes move across the
runtime fixtures (35 ins / 35 del, no keys added or removed); checkpoints.md is
unchanged this round. Size baselines: gsd-executor.md 43607 -> 43973,
execute-phase.md 93589 -> 93583 (still under the ADR-857 ceiling).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
* feat(#2161): opt-in absolute token count on the statusline context meter
New statusline.show_context_tokens config (default false). When enabled,
the context meter shows the absolute token total after the percentage,
e.g. "████░░░░░░ 46% (156k)" — summing input, cache-creation, cache-read,
and output tokens from context_window.current_usage (matching /context).
Default output is byte-for-byte unchanged when the flag is absent or
false. The .planning config is now read once per render and shared with
the last-command/position block instead of being re-read.
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* docs(#2161): changeset fragment for PR #2174
* fix(#2161): review fixes — k-to-M threshold, boundary tests, changeset format
- formatTokens promotes to the M branch when k-rounding reaches 1000
(999,500-999,999 rendered "1000k" instead of "1.0M")
- boundary tests at 999499/999500/999999/1000000/1000001
- Number() guards on the four usage fields (silent string-concat gap)
- changeset body ends with the (#2161) citation per house convention
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* fix(#2161): round-2 review fixes — config-set coverage, precision claim, exports style
- config-set accept/reject tests for statusline.show_context_tokens
(mirrors the post-planning-gaps precedent the issue scope names)
- changeset + docs no longer claim parity with /context: the suffix sums
four fields while the meter %% derives from used_percentage (three), so
the figures can diverge slightly
- module.exports one entry per line
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
* test: regenerate golden-install-parity fixtures for the statusline hook change
Claude-Session: https://claude.ai/code/session_01Hme55Pvq6BhpgwBcyC5HAg
---------
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
* fix(2119): single SECURITY.md writer — auditor is return-only
The gsd-security-auditor held Write/Edit and was instructed to write
SECURITY.md (no <N>- prefix, no template frontmatter), while the
orchestrator's Step 6 also wrote the correct padded <N>-SECURITY.md
from templates/SECURITY.md. Two writers, two naming conventions, two
shapes — the auditor's unprefixed file was invisible to the workflow's
*-SECURITY.md glob detector and unparseable for the threats_open gate.
Fix (option 1 from the issue): make the auditor return-only.
- Remove Write/Edit from auditor's tools
- Rewrite all 'Write SECURITY.md' instructions to 'Return structured
verdict' with threats_open count
- Add explicit constraint in workflow Step 5 spawn prompt
- Update existing test (was asserting Write in tools — now asserts absence)
- Add new regression test for single-writer contract
- Update docs/AGENTS.md stale Tools/Produces rows
- Regenerate golden fixtures + agent size baseline
* docs(changeset): backfill PR number (#2154)
* chore(#2119): regenerate pi/qwen golden fixtures after next merge
The single-writer change edits gsd-core/workflows/secure-phase.md and
agents/gsd-security-auditor.md; pi.json (added on next) and qwen.json (merge
straggler) were the only runtime fixtures still holding pre-change hashes for
those files. All other runtimes already reflect the change. Regenerated via
the sanctioned gen-golden-install-parity script.
* merge origin/next — regenerate goldens + baseline for merged state
* fix slash-command syntax: /gsd-secure-phase → /gsd:secure-phase (#2154 CI fix)
* fix(2112): scope commit to --files pathspec, not entire index
cmdCommit/cmdCommitToSubrepo/cmdPrSubrepo staged exactly the files
named in --files but then ran a bare 'git commit' with no pathspec,
absorbing anything else in the index into a commit whose message
described only the named files (#2112).
Fix: append '-- ...stagedPaths' to the commit args when the caller
declared a scope. Three guards are load-bearing:
- stagedPaths (not filesToStage) excludes skipped missing files (#2014)
- explicitFiles gate keeps the default .planning/ path byte-identical
- MERGE_HEAD check via 'git rev-parse' falls back to bare commit during merge
- --amend is left without pathspec (different operation)
cmdPrSubrepo pathspec uses changedFiles (old+new for renames) so the
full rename is captured atomically.
Also fixes workflow markdown in spec-phase.md and add-tests.md.
All-files-missing now short-circuits to nothing_to_commit instead of
absorbing the entire index under a message describing files that
were not committed.
* docs(changeset): backfill PR number (#2148)
* test: update golden-install-parity fixtures for workflow markdown changes (#2112)
* test: update golden fixtures + workflow baselines for #2112 changes
- claude-local.json golden fixture (now generated via gen script)
- workflow-size-baseline.json (add-tests.md +16, spec-phase.md +42 bytes)
- Extended gen-golden-install-parity-zcode.cjs to also regenerate the
claude local-layout fixture
* fix(2118): honor --dry-run in milestone complete with zero-mutation preview
milestone complete treated --dry-run as a no-op: the flag was neither
parsed nor rejected, so a caller who expected a preview instead
triggered the full destructive mutation (archive phases → move audit
artifacts → rewrite STATE.md) with no way to back out.
Fix (option 2 from the issue): add dryRun to MilestoneCompleteOptions,
parse --dry-run in the dispatcher, and return a JSON preview plan
(would_archive, would_update) after the read-only stats gathering but
before any mutations. Also gated platformEnsureDir on !dryRun so the
archive directory is not created during preview.
3 regression tests: no-mutation happy path, --no-archive-phases combo,
and --force bypass combo.
* docs(changeset): backfill PR number (#2155)
* chore(#2118): regenerate pi/qwen golden fixtures after next merge
The milestone --dry-run fix changes gsd-core/bin/gsd-tools.cjs; qwen.json
(missed at authoring) and pi.json (added on next, never carried the fix)
were the only two runtime fixtures still holding the pre-fix hash. All
other runtimes already reflect the change. Regenerated via the sanctioned
gen-golden-install-parity script.
* fix(#2118): surface accomplishments in dry-run preview; fix --dry-run --raw
Orthogonal review findings on the --dry-run preview:
- The preview omitted the already-computed accomplishments (the primary
MILESTONES.md content a real run writes); surface it as a top-level field,
mirroring the real-run result.
- `--dry-run --raw` discarded the structured payload and printed the literal
string "dry-run"; drop the raw-value arg so --raw emits the full preview
JSON, matching the real-run output() call.
Adds tests: --dry-run --raw is parseable JSON, preview includes accomplishments,
and --dry-run --force is proven zero-mutation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The Gemini, Claude, and Codex reviewer blocks invoked the CLIs with no explicit
timeout, so each inherited the host default (~2 min on Claude Code). A source-
grounded review of a large plan set takes ~570s (Codex xhigh) / ~525s (headless
Claude) — both exceed that window, so the lane is killed mid-review, its output
is empty, and the cross-AI review silently proceeds with fewer lanes. CodeRabbit
and OpenCode already documented a timeout; the four main lanes did not.
Add a shared timeout-guidance note directing a high Bash timeout (>= 900000;
1200000 for Codex xhigh / headless Claude), referencing BASH_MAX_TIMEOUT_MS for
the Claude Code host cap, and framing a slow-lane empty output as a timeout kill
(not the 0xc0000142 crash it gets misdiagnosed as) so operators re-run with more
time instead of diagnosing a CLI failure.
Closes#2194
Recaptures the 18 golden-install-parity fixtures + workflow-size baseline (only
the review.md entry changed in each; review is LARGE-tier, 47168 < 61440).
Review (MEDIUM): `git push ... 2>&1` did not check exit code, so a silent push
failure (auth-token expiry, network blip, non-fast-forward) would proceed to the
report step and declare success — silently reproducing the exact #2138 defect.
Add a fallback warning naming the rerun command so a failed push is visible
(best-effort: the PR already exists, so we still report it rather than abort).
Recaptures goldens + size baseline.
track_shipping committed the STATE ship-note ('Phase N shipped — PR #N') AFTER
create_pr and never pushed it, so the commit stayed local-only. When the GitHub
PR merged (especially fast/auto-merge) the ship-note was not in the source branch
and never reached the default branch — STATE's ship-status was silently lost,
recoverable only by STATE self-heal on the next /gsd-start.
Push the ship-note commit onto the PR branch with a [ci skip] trailer. GitHub
honors [ci skip]/[skip ci], so this lands the note on merge without triggering a
redundant pipeline, and preserves the PR number in STATE.
Recaptures the 18 golden-install-parity fixtures + the workflow-size baseline
(only the ship.md entry changed in each).
Closes#2138
The fast.md log_to_state fix changes an installed workflow file, so the per-
runtime golden-install-parity fixtures (18 runtimes) and the workflow-size
baseline are recaptured. Diff is exactly one entry per fixture (the fast.md
hash) and one baseline byte count — no spurious drift.
Four require() examples in commands/gsd/surface.md used bare
'gsd-core/...' specifiers that Node cannot resolve (wrong package
name + runtime-mirror layout off module path). Now derives the path
from runtimeConfigDir. Also fixes reinstall hint from
'npm i -g gsd-core' to 'npm i -g @opengsd/gsd-core'.
audit-milestone's Nyquist scan classified a phase from `nyquist_compliant`
alone, so a phase seeded by plan-phase but never run through validate-phase
read PARTIAL — identical to a phase that validated and genuinely failed. The
template's `status` field could discriminate the two, but no workflow ever
promoted it off `draft`, so it was dead.
Make `status` live and read it:
- validate-phase.md §6: set `status: validated` in both the create (State B)
and update (State A) VALIDATION.md paths.
- audit-milestone.md §5.5: parse `status`; add a distinct NOT-VALIDATED bucket
keyed on `status: draft`, gate COMPLIANT/PARTIAL on `status: validated`, and
report `not_validated_phases` in the audit YAML.
- VALIDATION.md template: document the draft → validated lifecycle.
Tests & generated artifacts:
- Regression test folded into policy-138 (owning workflow-contract file);
fail-first verified vs origin/next (0 matches pre-fix).
- Regenerate golden-install-parity fixtures cleanly: adds the previously-missed
qwen.json and removes a contaminated `settings.local.json` entry that had
leaked into claude-local.json (the harness excludes hook-config files).
- Correct a stale validate-phase.md workflow-size-baseline entry.
Closes#2117
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Net-new EoS/pi installable runtime — purely additive (no prior runtime==='pi'
branches). pi is a bun-runtime programmatic-CLI whose /gsd command is registered
by a native ExtensionAPI extension and dispatches through the embedded engine.
Stage 1 (install plumbing):
- capabilities/pi/capability.json: full hostIntegration descriptor (imperative /
slash-programmatic / active-model / native-extension / bun) + hostBehaviors
{nativePlugin, pluginOnlyInstall}.
- --pi flag + interactive-menu renumber (All 17->18); pi added to RUNTIME_FLAG_IDS,
RUNTIME_LABELS, RUNTIME_META, allRuntimes/runtimeMap, model-catalog defaults.
- Install mirrors OpenCode: pi installs the gsd.cjs extension + the shared engine
payload (gsd-core + scripts + config markers) + the shared hooks bundle (spawned
by the extension at lifecycle events, like OpenCode's plugin). pluginOnlyInstall
EXCLUDES declarative command/agent/skill markdown, which pi has no host-read
surface for (its /gsd is programmatic). _installNativePluginIfDeclared (extracted
from the opencode-family path) copies pi/gsd.cjs -> ~/.pi/agent/extensions/gsd.cjs
(global) / .pi/extensions/ (local). pi added to package.json files.
- Golden: new pi.json (320 files: extension + engine + 27-file hooks bundle, no
markdown); the 16 other fixtures + claude-local change only by the shared
model-catalog hash line.
Stage 2 (real dispatch + upgrades):
- Shared dispatchGsdCommand() (shell-command-projection): bounded, no-throw
subprocess-shim to gsd-tools.cjs (the only full-surface dispatch path; no
in-process full-hub factory exists). Fixes pi/gsd.cjs's createHub()-no-args bug
(every dispatch was UnknownCommand) AND the identical bug in mcp-server.cts's
gsd_invoke_command, which a vacuous unknown-family-only test had masked (now has
a real dispatch regression test).
- pi/gsd.cjs: /gsd handler now (args, ctx) - tokenizes (quote-aware, via the
shipped hooks/lib/git-cmd.js) + dispatches real family/subcommand (not hardcoded
query/help); gsd_invoke gets a TypeBox (JSON-schema-fallback) parameters schema +
consumes params; getArgumentCompletions; before_provider_request active-model
steering (fail-open on null resolution); functional session_start /
before_agent_start / session_before_compact hook bridges (spawn the shipped GSD
hook scripts).
- EXTENSION_EVENT_SURFACES.pi expanded from ['tool_call'] to the full 30-event
vocabulary.
Docs (host-integration matrix + how-to) + changeset (Added).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold remaining isKilo logic branches into descriptor-driven reads:
finishPermissionWriter (uninstall cleanup), skipSharedHooksInstall (hooks
copy), and a skills converter-name registry (the artifactLayout.converter
field is now load-bearing, not decorative). frontmatterDialect stays the
documented dispatch key for frontmatter (no descriptor field for it). Dead
isKilo destructure bindings removed. Byte-identical golden parity for all 16
runtimes (opencode, which shares kilo's combined-family path, verified clean).
UPGRADE 1 (hook bus): install .kilo/plugins/gsd-core.js native plugin +
extensionEvents:"kilo" + EXTENSION_EVENT_SURFACES.kilo (OpenCode-fork bus).
UPGRADE 2 (active model): populate runtimeTierDefaults.kilo + thread
modelOverride through convertClaudeToKiloFrontmatter — model no longer stripped
from agents. UPGRADE 3 (MCP): document the gsd-core MCP companion under kilo's
mcp config key. UPGRADE 4 (named dispatch): agents/*.md mode:subagent roster is
the Task-tool dispatch surface (tested); subagentToolkit stays 'undocumented'
per AC so dispatch degrades to 'degraded' by design.
Model-catalog single-source edit ripples the shared model-catalog.json hash
into all 16 golden fixtures (expected). Inline defect fixes (no-defer): stale-
bake-guard resolveAgentDir 'agent'->'agents' (was a silent no-op for opencode/
codex), hardcoded 'Removed OpenCode plugin' uninstall log -> generic, and the
connect-gsd-mcp-server.md OpenCode mcpServers->mcp doc error.
Tests: kilo-imperative-reference (adapter/axes/fail-closed/degradation/
hostBehaviors + widened isKilo source-grep across 4 modules) + kilo-upgrades
(plugin parity+load, model-override converter, agents dispatch surface, MCP
doc). Matrix + how-to + config docs updated; changeset added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update runtimeTierDefaults.codex and providerPresets.openai in
model-catalog.json to the GPT-5.6 family (gpt-5.6-sol/terra/luna),
advancing from the superseded GPT-5.4/5.5 generation.
Model IDs verified against OpenAI developer API docs:
- gpt-5.6-sol: flagship, /, reasoning xhigh
- gpt-5.6-terra: balanced, .50/, reasoning medium
- gpt-5.6-luna: fast/cheap, /, reasoning medium
Tier mapping is 1:1 (Sol↔flagship, Terra↔balanced, Luna↔fast),
so profile semantics are unchanged — only the underlying IDs advance.
Updates: catalog JSON, test assertions (catalog defaults), docs
(CONFIGURATION.md + zh-CN/pt-BR translations, workflow settings),
and changeset.
Closes#2122
Code review (HIGH): a hardcoded 'timeout 600 agy' fails with rc 127 on stock
macOS (no GNU timeout/gtimeout), silently losing the agy reviewer. Probe for
'timeout'/'gtimeout' via command -v and fall back to agy's native --print-timeout
alone when neither exists (mirrors scripts/base64-scan.sh). External cap (600s)
stays >= --print-timeout (540s) so it only backstops a pre-session stall. Factor
the prompt into _AGY_PROMPT to avoid duplicating the long -p string across both
branches. Update the agy + #687 tests to assert the probe + bound + fallback,
regen the 17 goldens + size baseline, refresh the maintainer-note version stamp
to 1.0.16.