Commit Graph

101 Commits

Author SHA1 Message Date
Tom Boucher
ff9cb6069f fix(#2285): wire claude-orchestration Workflow backend into execute-phase (#2314)
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>
2026-07-15 19:18:28 -04:00
Tom Boucher
27415c780e fix(#2252): exclude PLAN-REVIEW artifacts from plan count (#2263)
* fix(#2252): exclude PLAN-REVIEW artifacts from plan count

The loose /PLAN/i fallback in isRootPlanFile matched *-PLAN-REVIEW.md,
inflating plan counts. Added PLAN_REVIEW_RE exclusion before the fallback.

* docs: backfill changeset PR number (#2263)

* fix: regenerate stale capability-registry after next merge
2026-07-14 15:15:12 -04:00
Tom Boucher
8b70db343b fix(#2204): phase-completion writes 'All phases complete' per ADR-2207 (#2259)
* 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)
2026-07-14 14:47:03 -04:00
Tom Boucher
c1885df9e5 chore(#2143): prohibition-with-teeth + migrate remaining ad-hoc table sites — Phase 4 (final) (#2253)
* chore(#2143): prohibition-with-teeth + migrate remaining table sites — Phase 4

Phase 4 of epic #2143 (ADR-2143 §7). Completes the markdown table/mutation
consolidation by (a) giving the ad-hoc-parsing prohibition teeth and (b)
migrating the last ad-hoc table sites onto the shared seam.

- src/markdown-table.cts: new formatting-preserving `updateTableCell` primitive
  (self-contained, ragged-row-tolerant header/delimiter/cell-range scan; splices
  only the target cell's raw span, preserving all other bytes incl. padding/CRLF;
  no-op-preserves-padding when a transformer returns the current value). Exports
  splitTableRow/isDelimiterRow/findTableStartOffset for tolerant reuse.
- eslint-rules/no-adhoc-markdown-parsing.cjs: TABLE-REGEX detector extended to
  `new RegExp(<literal|static-template>)`; new `.replace()`-mutation detector for
  roadmap/state/content receivers with a table/section-shaped pattern.
- scripts/lint-table-schema-drift.cjs (wired into lint:ci): fails if a TABLE_SCHEMA
  header drifts from its authored table; tests import its logic (single source).
- Migrated onto the seam (behaviour-preserving vs pre-Phase-4 HEAD, verified
  byte-diff old-vs-new): roadmap.cts cmdRoadmapUpdatePlanProgress, phase.cts
  cmdPhaseComplete + traceability, milestone.cts cmdRequirementsMarkComplete,
  uat.cts read path, state.cts metrics/decisions/By-Phase.
- Incidental correctness gains from the migration: a decoy table can no longer
  swallow a phase-progress update (## Progress scoping); a ragged neighbouring
  row no longer silently aborts an edit; completing integer phase N no longer
  touches a decimal sub-phase N.x row; record-metric no longer drops trailing
  section content or duplicates the ## Performance Metrics section.
- Kept justified allow-adhoc-markdown markers only where genuinely not a table
  (security.cts <|role|> token) or a loose non-GFM section (uat human-verify).

Two orthogonal isolated reviews (correctness/adversarial + security) passed;
correctness found 4 behaviour regressions in the first migration pass, all fixed
and re-verified byte-identical-or-better vs OLD.

Surfaced for maintainer (pre-existing, ambiguous domain logic, NOT changed here):
templates/state.md places a By-Phase table under ## Performance Metrics while
cmdStateRecordMetric assumes a Plan|Duration|Tasks|Files table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): match traceability row by first-cell value, not Requirement header

Phase 4's migration matched the REQUIREMENTS.md traceability row by a column
literally named `Requirement` (`row['Requirement']`), but real tables head that
column `REQ-ID`. The by-name lookup found nothing, so `phase complete` and
`requirements mark-complete` left the Status cell `Pending` (regressed #2769 /
#2203, caught by gsd-test — 8 failures, both node 22/24).

- src/phase.cts, src/milestone.cts: match the row by its FIRST cell's value
  (the requirement-ID column) regardless of that column's HEADER name, via
  `Object.values(row)[0]` (updateTableCell builds the record in header order).
  This mirrors OLD's first-cell `\|\s*<id>\s*\|` anchor, restoring header-name
  independence while keeping the seam.
- src/milestone.cts hasTable: broadened from `Requirement`-only to also
  recognize `Requirement ID` / `REQ-ID` / `REQ ID` headers, kept in sync with
  the now-positional rowMatch/hasRow so a REQ-ID-headed table participates in
  the ADR-2143 §6 write-set and the #2140 table_unmatched drift check (it was
  silently omitted before — a checkbox-only partial reconcile against a REQ-ID
  table could report as fully reconciled). The `Requirement`-headed path is
  byte-identical to OLD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#2143): replace stale structural milestone guards with behavioural suite

The `milestone.cjs regex global state fix` block was a source-structure guard
(allow-test-rule: structural-regression-guard) — it readFileSync'd the compiled
milestone.cjs and asserted removed regex idioms (`tablePattern.test`,
`afterTable !== reqContent`, `doneTable = new RegExp(...)`). Phase 4's migration
deleted those regexes (table update is now updateTableCell), making the
assertions obsolete. Per the Test Cleanup rule, replace them in-PR with a
behavioural suite driving the compiled CLI:

- multi-ID mark-complete flips all IDs (guards the lastIndex/global-state class),
- Pending->Complete flip under both `REQ-ID` and `Requirement` headers (#2769),
- idempotent already_complete detection with no corruption,
- REQ-ID-headed table participates in write_set (traceability entry, applied),
- REQ-ID-headed table trips #2140 table_unmatched drift on a missing row.

Pruned the now-nonexistent structural-regression-guard entry from the
lint-allow-test-rule-refs allowlist (the source-text-is-the-product entry for
the same file remains valid).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(changeset): backfill PR number 2253

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): record-metric targets its own metrics table, not By-Phase velocity

`state record-metric` appended its per-plan row (`| Phase 1 P1 | 5min | 3 tasks |
4 files |`) into the FIRST table under `## Performance Metrics` — which on a real
template-derived STATE.md is the By-Phase velocity table `| Phase | Plans | Total
| Avg/Plan |`, polluting it on EVERY plan completion (execute-plan.md:414 is a
per-plan call). The command's own metrics table is `| Plan | Duration | Tasks |
Files |`, which the template does not ship, so the row never reached it; the
scaffold branch also emitted a wrong `| Phase | Plan | Duration | Notes |` header
matching neither the row nor the canonical table.

Pre-existing (predates Phase 4); surfaced while migrating this site and fixed here
per no-defer, on the user's explicit go-ahead.

- src/state.cts cmdStateRecordMetric: locate the metrics table by its own header
  shape (`Plan|Duration|Tasks|Files`, via splitTableRow/isDelimiterRow) rather
  than "first table in the section". When the section exists but has no metrics
  table (only the By-Phase table), self-heal by appending a fresh **Per-Plan
  Metrics:** table to the END of the section body — By-Phase table, Recent Trend
  and footer preserved verbatim, no duplicate `## Performance Metrics` heading,
  created stays false. Absent-section scaffold header corrected to the canonical
  `| Plan | Duration | Tasks | Files |`. Ragged-tolerance + None-yet preserved.
- Not touching templates/state.md (golden-install-parity hashed) — record-metric
  self-creates the table on first use instead.

Failing-first regression test (tests/state.test.cjs) demonstrates the By-Phase
pollution on the pre-fix build, then green after. Verified: no pollution, self-
heal idempotency, both-tables isolation, content/heading preservation, flags,
None-yet, corrected scaffold header (23-check adversarial harness + all existing
record-metric scenarios).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(#2143): deleteSection seam primitive (level-bounded whole-section removal)

ADR-2143 §4 shipped withSection/collectSection (replace a section BODY) but no
way to DELETE a section (heading + body). Phase 4 suppressed the phase-remove
section delete instead of building it. deleteSection(content, predicate, opts)
locates the section via the collectSection machinery and splices out from the
heading's start offset to the next same-or-higher-level heading — so a level-3
`### Phase N` delete stops at a following level-2 `## Progress`, never past it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): phase remove no longer deletes ## Progress on last-phase removal

updateRoadmapAfterPhaseRemoval deleted a `### Phase N` detail section with a
greedy raw regex whose lazy scan, on the LAST phase, ran to EOF and destroyed
the following `## Progress` heading and its entire tracking table — silent data
loss, uncovered by tests (removal tests only exercised a middle phase). Migrated
onto the new deleteSection seam (level-bounded, stops at `## Progress`); dropped
the allow-adhoc-markdown SECTION-DELETION suppression. Failing-first regression
(tests/phase.test.cjs) removes the LAST phase and asserts the ## Progress heading
+ table survive; middle-phase removal is byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(#2143): deleteTableRow seam primitive (row removal, ragged-tolerant)

Sibling of updateTableCell: locates the first GFM table, matches a DATA row by
predicate (ragged-tolerant record build, header order), and splices out that
row's whole line preserving every other byte. Returns {ok:false,reason} on no
table / no match. Enables migrating the phase-remove Progress-table row delete
off its ad-hoc regex (ADR-2143 §7 — the "future row-delete seam" Phase 4 punted).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): phase remove deletes the Progress row via deleteTableRow

The Progress-table row delete used a whole-document regex with two defects:
(a) `\.?\s` required whitespace after the phase number, so a COMPACT row
`|2|Beta|` was never deleted (stale row left behind); (b) unscoped — it could
strike a row in a different table (e.g. an earlier `| Phase | Requirements |`
table). Migrated onto deleteTableRow, scoped to the `## Progress` section
(mirrors deriveProgressFromRoadmap), matching the row by first-cell phase number
(integer zero-pad-insensitive; decimal exact; removing `2` never touches `2.5`).
Both allow-adhoc-markdown suppressions removed. New behavioural tests: compact
unpadded row deleted; padded byte-parity on the surviving rows (their ordinal
correctly renumbers via the pre-existing renumber block).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): deleteTableRow leaves no dangling newline on last EOL-less row

Deleting the final row of a table with no trailing EOL sliced from the row's
start to end-of-string, stranding the newline that terminated the previous line.
Back rowStart over the preceding \r?\n in that branch so the table ends cleanly.
(Caught by the primitive's own unit test on gsd-test; local scenario checks
missed the no-trailing-EOL edge.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): migrate read-only section-collects onto collectSection

Six hand-rolled `## Section` read-extract regexes replaced by the collectSection
seam (behaviour-preserving; extracted bodies feed the same downstream parsers):
state.cts matchSessionSection (## Session / ## Session Continuity) + ## Blockers,
smart-entry.cts ## Blockers, audit.cts ## Current Focus + ## Open Questions.
Removes 6 allow-adhoc-markdown "pending #1372" suppressions. Incidental fix: the
old Session regex `## Session[ \t]*\n` silently failed on a CRLF `## Session\r\n`
heading (Windows STATE.md), nulling all session fields; collectSection is
CRLF-safe, so session state now resolves on Windows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): fence-safe state-transition section writes + dedup stripFrontmatter

- milestoneCompleteCore's `## Current Position` and `## Operator Next Steps`
  section resets used fence-blind raw regexes that a fenced `##` inside the body
  could truncate/mis-target (#2130/#2067/#2080 class). Migrated onto a
  fence-aware tokenizeHeadings-based helper (resetSectionVerbatim) that is
  byte-identical to the old output on the canonical path (9/9 fixtures) and
  correctly ignores a fenced fake heading (proven robustness gain).
- mutateCurrentPositionFirstTime: hand-rolled locate+splice → collectSection +
  replaceSection (byte-parity).
- stripFrontmatter was inlined byte-identically in state.cts AND
  state-transition.cts; hoisted the single canonical copy into frontmatter.cts
  (both call sites now import it) + unit tests — eliminates the divergence risk
  per CLAUDE.md "Generative Fix Divergence". Removes 3 allow-adhoc-markdown /
  #1372 markers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): name-address By-Phase sum + uat parse, eslint recall hole, catches

- state.cts By-Phase "Total plans completed" sum: positional 2nd-cell regex →
  name-addressed splitTableRow read (correct on a reordered header, where the
  old code silently summed the wrong column). Marker removed.
- uat.cts parseVerificationItems: loose pipe regex → splitTableRow within the
  existing table/numbered/bullet union scan (item list byte-identical; does NOT
  reintroduce the reverted strict-parseMarkdownTable item-drop). Marker removed.
- eslint no-adhoc-markdown-parsing: close the `new RegExp(identifier)` recall
  hole — resolve a const-declared table-shaped regex identifier (mirrors the
  .replace() detector) + RuleTester cases; param/call args stay out (boundary).
- commands.cts: delete a lying comment that claimed the scaffold date "stays on
  raw UTC / deferred" — #2136 already moved it to realClock.localToday().
- Empty catches (classified, not blind-swept): removed 4 dead try/catch;
  fixed 3 error-hiding (phase-insert decimal-dir I/O collision now fails loud;
  phase-remove rename partial-failure surfaced; milestone-archive true count via
  finally); left best-effort swallows with justification comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): extractFencedBlock seam + migrate api-coverage named fence

parseCoverageMatrix extracted its ```coverage fenced block with an ad-hoc regex
(the last real allow-adhoc-markdown suppression). Added extractFencedBlock to the
markdown-sectionizer seam (reuses stripFencedCode's CommonMark fence engine —
info-string match, ~~~/backtick, nesting, indent) and migrated onto it; byte-
parity on the parsed CoverageMatrix across 8 fixtures. Only security.cts:367
(a genuine `<|role|>` protocol-token false-positive, not a GFM table) remains
marked in src/ — the "prohibition with teeth" goal (nothing grandfathered but a
true FP) is met.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): By-Phase row insert is name-addressed (insertTableRow seam)

updatePerformanceMetricsSection's INSERT-new-row branch located the By-Phase
table with a canonical-column-order-only regex + a hardcoded positional row
literal, so on a reordered header it silently inserted nothing — inconsistent
with the now name-addressed UPDATE and SUM halves of the same function. Added
insertTableRow (markdown-table seam sibling of updateTableCell/deleteTableRow:
name-addressed, header-order-agnostic, EOL-preserving) and migrated the branch
onto it, mapping By-Phase values by column NAME. Canonical-order output is
byte-identical; a reordered header now inserts a correctly-mapped row; a
pre-existing CRLF mixed-EOL splice glitch is incidentally fixed. Retired the
now-dead byPhaseTablePattern const.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): phase-list checkbox flip via updateBullet seam

Added updateBullet (markdown-sectionizer): a fence-aware, offset-tracked
single-bullet write primitive (GFM 1–4-space marker tolerance) — the write
counterpart to read-only iterateBullets. Migrated mutateMilestonePhase's
phase-list checkbox flip (`- [ ] Phase N …` → `- [x] … (completed <date>)`)
off its whole-slice regex onto it, same milestone-slice scope + clock seam.
Byte-identical across simple / idempotent / metachar-title / double-space /
CRLF scenarios.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): scope the Progress-ordinal renumber to ## Progress via seam

phase remove's integer-renumber decremented Progress-table phase ordinals with a
whole-document `content.replace(/(\|\s*)(\d+)(\.\s)/g, …)` — unscoped, so it also
rewrote any `| N. …` cell in an unrelated/decoy table (same class as the batch-2
row-delete scoping bug). Migrated onto updateTableCell, scoped to the ## Progress
section, decrementing each affected row's leading phase ordinal by column name.
Byte-identical on canonical Progress tables + multi-row + decimal-sibling cases;
a decoy `| 3. … |` row before ## Progress is now correctly left untouched. The
sibling heading / checkbox-bullet / PLAN.md-filename / Depends-on-prose renumbers
are not GFM-table mutations (outside ADR-2143's table/section mandate) — left as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2143): review fixes — scope traceability write, restore Current Position H3-stop

Adversarial review of the remediation (BLOCK verdict) — all 9 findings fixed:
- F1 (BLOCKER): requirements mark-complete / phase complete flipped the checkbox
  but NOT the traceability row on the shipped template, because updateTableCell
  bound to the FIRST table (## Out of Scope, no Status column) instead of the
  ## Traceability table — the #2140 silent-divergence class, re-introduced by the
  seam migration and missed by tests (fixtures had Traceability first). Scoped
  the write + hasRow probe to the ## Traceability section slice (updateTraceability
  Cell helper) in milestone.cts + phase.cts. Failing-first tests on the
  Out-of-Scope-before-Traceability layout; the #2769 first-cell match preserved.
- F2 (MAJOR): mutateCurrentPositionFirstTime restored to locateCurrentPosition
  (STOP_H2_PLUS) — collectSection's default H2-stop swallowed a level-3 subsection
  and the field regexes clobbered it (#2130 class).
- F3/F8: Progress-ordinal renumber re-escapes via escapeCell + keys padding
  recovery by row index (was de-escaping `\|` and losing padding on dup values).
- F4: insertTableRow escapes cell values internally.
- F5: updateBullet accepts a tab after the marker (`[ \t]{1,4}`).
- F7: resetSectionVerbatim consumes CRLF blank lines (byte-parity on CRLF).
- F6/F9: corrected two misleading comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(changeset): data-loss + CRLF-session user-facing fixes (#2253)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#2143): de-flake the G10 windsurf ReDoS-guard wall-clock assertion

The G10 test asserted `elapsedMs < 1000` for a 200k-char payload — a wall-clock
assertion (CLAUDE.md: never assert on wall-clock time) that flaked on a loaded
node24 bench at ~1.1s. It was redundant: runHook's spawnSync `timeout: 10000`
already SIGKILLs a catastrophic-backtracking hook, so the exit-0 assertion is the
real ReDoS guard. Removed the timing assertion; kept exit-0 + documented the
subprocess-timeout mechanism. Surfaced (not caused) by this branch's gsd-test
runs loading the bench; unrelated to the markdown-parsing changes but fixed in
place per the no-flaky-tests rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 14:25:44 -04:00
Tom Boucher
e0f969af6a refactor(#2246): centralize cross-platform path-separator handling (toPosixPath / toNativePath / posixNormalize) (#2247)
Replace every open-coded separator translation across the installer/hooks
source with named, tested seams in shell-command-projection.cts (the platform
seam), removing all hardcoded `/`+`\` from path handling:

- toPosixPath(p)   — this machine's native path → POSIX (running-OS relative;
                     for local filesystem paths).
- toNativePath(p)  — POSIX → native (collapses the win32 `/\//g,'\\'` ternary).
- posixNormalize(p)— unconditional `\`→`/`, OS-independent; for emitting paths
                     to a POSIX/bash TARGET (which may differ from the running
                     OS) and for parsing mixed-separator input.

core-utils.toPosixPath now delegates to the seam, so its 20+ existing consumers
resolve to one implementation; no duplicate helper.

- ~47 sites across runtime-hooks-surface, runtime-artifact-conversion,
  runtime-artifact-install-plan, drift, init, worktree-safety,
  installer-migrations, installer-migration-authoring, install-engine, surface,
  verify, runtime-artifact-layout, schema-detect, check-command-router.
- Closes the latent POSIX-literal-backslash corruption class (the regex form
  corrupts a POSIX path containing a literal backslash; split(path.sep) does not).
- New unit + fast-check property tests for all three helpers.

Closes #2246

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:36:17 -04:00
Tom Boucher
58b20c31f8 fix(#2136): migrate ALL operator-facing date sites to localToday (anti-pattern elimination)
The UTC-slice anti-pattern (deriving a calendar day a human reads by slicing a
UTC instant) remained in several operator-facing sites beyond the original
seamed last_activity set. Eliminate it everywhere a human reads the value as a
calendar day — do not leave known bad code in place:

- commands.cts cmdTodoComplete + cmdScaffold: completion/scaffold dates.
- gsd2-import.cts: migrated STATE.md 'Last activity' / 'Last session'.
- template.cts: plan frontmatter 'completed:' date.
- verify.cts: health --repair session-log date + '(Backfilled: <date>)' header.
- workstream.cts: workstream-create 'Last Activity' / 'created' + archive dirname.
- init.cts: JSON-bundle 'date' (-> localToday) + 'timestamp' (-> nowIso) at all
  three sites; drop the now-dead 'const now = new Date()' in cmdInitTodos /
  cmdInitMapCodebase (cmdInitQuick keeps it for the local branch-id derivation).
- state.cts: prune-archive '## Pruned <date>' header.
- state-transition.cts: the 7 seamed last_activity writes (prior commit).

No realClock.today() / .clock.today() / raw new Date().toISOString().split('T')
operator-facing sites remain in src/. Rebuilds the tracked
bin/lib/state-transition.cjs artifact to match.
2026-07-12 15:34:59 -04:00
github-actions[bot]
27f69cc489 chore: sync next package version to 1.7.0-rc.6 2026-07-12 05:40:45 +00:00
Tom Boucher
a0fafedfa0 feat(#2103): drive VS Code through the Embeddable Orchestration System (ADR-1239)
VS Code is a net-new EoS runtime that — unlike every prior migration — is NOT
CLI-installed (Marketplace/VSIX extension). It has zero runtime==='vscode'
branches in bin/install.js and stays that way (regression-guarded); it is driven
entirely through the negotiated imperative Host-Integration adapter.

Registry + validator (the hard part):
- capabilities/vscode/capability.json (role:runtime): full hostIntegration block
  (imperative / palette / active vscode.lm model / engine hook bus /
  sandboxed-storage / mcp transport / sandboxed-web runtime; dispatch nested,
  maxDepth 5 per VS Code's documented subagent depth).
- capability-validator.cjs extended so a role:runtime capability can legitimately
  declare "extension-distributed, no config directory": new configHome.kind:'none'
  + installSurface:'none' (+ GATE-A pairing + the parity maps), with localConfigDir
  and configHome.name made conditional on kind!=='none'. All 18 runtimes still
  validate; getDirName returns a distinct sentinel (not '.claude') for a no-config
  runtime.
- The add-a-registry-runtime tax: NON_INSTALLABLE_RUNTIMES exemption in the
  runtime-flags drift guard, vscode added to global-config-home SPECIAL_CASED,
  EXPECTED_PROFILES.vscode='ide', and the config-adapter/derivation/pin-count
  guards updated. No golden-install fixture, model-catalog, or CONFIGURATION rows
  (vscode never enters allRuntimes).

Dispatch + extension surface:
- Fixed vscode/extension.js's createHub()-no-args bug (every dispatch was
  UnknownCommand, masked by a vacuous reachability test) — now reuses the shared
  dispatchGsdCommand subprocess-shim (Node/desktop); the reachability test is
  tightened to assert real dispatch.
- Promoted the #1933 host binding to a shipped vscode/host-binding.js; activate()
  now composes the model/hookBus/stateIO seams through it. Corrected the model
  seam to VS Code's real API (vscode.lm.selectChatModels() -> model.sendRequest();
  vscode.lm.sendRequest does not exist) so the binding actually composes on real
  desktop VS Code instead of throwing.
- New vscode/browser.js Web Extension entry with ZERO Node APIs (the engine's
  config/capability loading is Node-bound, so the web entry registers the surface
  and directs full dispatch to the native MCP server — honestly documented).
- UPGRADE 1: GSD skills as native Language Model Tools (contributes.languageModelTools
  + vscode.lm.registerTool), invoke() dispatching through the hub.
- UPGRADE 2: native subagent dispatch wired onto #runSubagent /
  chat.subagents.allowInvocationsFromSubagents (fail-soft on API availability,
  maxDepth 5 enforced).
- vscode/package.json: browser entry, engines.vscode ^1.105, chatParticipants +
  languageModelTools contributions; fixed a stale activationPoints->activationEvents
  manifest key. Added "vscode" to the package files array.

Docs (## vscode matrix section) + changeset (Added).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:24:42 -04:00
Tom Boucher
79d7657eff feat(#2102): make pi a first-class installable runtime + fix its dispatch (ADR-1239)
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>
2026-07-11 21:07:38 -04:00
Tom Boucher
a2c7b879d0 feat(#2101): dogfood ZCode through the EoS declarative adapter + fold shared-hooks exclusion (ADR-1239)
The issue's "0 conditional branches" premise missed a live one: the `!isZcode`
shared-hooks exclusion (bin/install.js). Fold it onto descriptor-driven
hostBehaviors.skipSharedHooksInstall:true (zcode's golden has zero hook files —
byte-parity verified) and drop the now-unused isZcode destructure. Zero live
runtime==='zcode'/isZcode branches remain (AC2 source-grep guard over
bin/install.js + install-engine.cts + surface.cts + runtime-artifact-conversion.cts).
The 6 CLI-bookkeeping zcode mentions (--zcode flag, menu, roster, help) stay.

Reference test (declarative-reference-zcode.test.cjs): profileOf → declarative-cli;
createDeclarativeAdapter({runtime:'zcode'}).kind → declarative; a real install emits
the invocable nested-skills/commands/agents surface (no hooks); negotiateHostCapabilities
fail-closes (empty/corrupt descriptor; the nested/maxDepth undocumented sub-axes degrade
to most-restrictive); validateCapability clean.

UPGRADES documented as BLOCKED (verified doc gaps — NOT guessed, to avoid a
non-functional false-green): both of ZCode's documented capabilities lack a published
on-disk config format.
- Hook automation: zcode.z.ai/en/docs/plugin documents the Hook component only as
  "automation hooks triggered on specific events" (capability detected from directory
  layout) — no config file format/location/event schema. Cannot faithfully wire.
- MCP registration: zcode.z.ai/en/docs/mcp-services says servers are "stored in the
  .zcode configuration file" (UI-only) with no documented on-disk filename/path/schema —
  exactly the settings-filename gap the issue AC anticipated.
Both are documented (with the search trail) in the capability matrix + how-to, per AC4's
block-documentation clause; hookBus/transport stay declared for when ZCode publishes the
formats. No hook scripts or MCP artifacts added → no golden change, no other-runtime impact.

Golden: byte-identical for all 16 runtimes (the fold is byte-parity; no upgrade artifacts).
Matrix ## zcode EoS note + how-to; changeset (Changed). capability-registry regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 17:14:48 -04:00
Tom Boucher
bd613566cb feat(#2100): drive Windsurf through the EoS descriptor + wire Cascade's blocking hook bus (ADR-1239)
Fold all 10 residual isWindsurf branches in bin/install.js onto descriptor-driven
hostBehaviors (byte-parity — no fold changes any install output):
- 2 dead destructures dropped (uninstall, finishInstall); the dead
  `else if (isWindsurf)` legacy agent-loop arm removed (windsurf ∈
  _DESCRIPTOR_AGENTS_RUNTIMES → unreachable).
- skipSharedHooksInstall:true folds the two `!isWindsurf` shared-hooks exclusions.
- legacyDevinSkillsCleanup:true folds the `.devin`→`.windsurf` one-time cleanup gate.
- installsCommandBodiesForWorkflowDelegation:true folds the #1629 command-body copy
  (workflow-delegation target — load-bearing; local-install verified intact).
- verificationStyle:"windsurf-workflows" folds the workflow-count report.
- Corrected stale _LEGACY_SCAN_SUBDIR_NAMES + hooks-json manifest comments (cursor + windsurf).
Zero live runtime==='windsurf'/isWindsurf branches remain across bin/install.js,
install-engine.cts, surface.cts, runtime-artifact-conversion.cts (AC2 guard scans all four).

UPGRADE (Cascade hook bus): wire GSD's write/command safety guards into Windsurf's
native hook bus. New hooksSurface 'windsurf-hooks-json' (VALID_HOOKS_SURFACES 7→8, GATE A
profile-marker-only allowlist, the HooksSurface union) + writeWindsurfHooksJson
(Cursor-templated, Cascade's flat {hooks:{<event>:[{command}]}} shape) writing
.windsurf/hooks.json with two BLOCKING pre-hooks:
- pre_write_code → gsd-windsurf-pre-write.js: blocks writes to a file outside the
  active git worktree / into .git internals.
- pre_run_command → gsd-windsurf-pre-command.js: conservative destructive-command
  deny-list (rm -rf of root/home incl. sudo/env/path-prefixed forms; fork bombs;
  force-push refspec forms — HEAD:main, +main, --force/-f — to main/master/next).
Both use Cascade's protocol (stdin JSON, exit 2 + stderr to block, exit 0 to allow,
fail-open on error/timeout). Tokenize-based classifier (no catastrophic-backtracking regex;
4096-char cap) with the fail-closed false-positives fixed post-review.

The 4 advisory GSD guards + pre_mcp_tool_use + 5 post_* logging events are deliberately
NOT wired: Cascade has no context-injection channel for advisory hooks and GSD has no MCP
guard — porting them would be non-functional padding (documented; codebuddy #2098 / copilot
#2099 faithful-subset precedent). extendedHookEvents stays [].

Golden: the 2 guard scripts ship in the shared hook bundle (HOOKS_TO_COPY + the shared
managed-hooks-registry), exactly like cursor's 6 gsd-cursor-*.js scripts — so the 8
shared-bundle runtimes' fixtures gain the 2 inert windsurf scripts + the registry hash
(functionally inert for non-windsurf; the established cursor pattern). No install-output
change beyond that (the folds are byte-parity; skip-bundle runtimes untouched). New scripts
registered in managed-hooks-registry + build-hooks + INVENTORY. Tests: declarative-reference-
windsurf (adapter/axes/fail-closed + AC2 guard) + windsurf-hooks-bridge (live exit-2 blocking
+ allow/fail-open + ReDoS-bound + writer/reconcile/remove idempotency); VALID_HOOKS_SURFACES
pin updated to 8. Matrix hookBus delta + changeset (Changed). capability-registry regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:04:24 -04:00
Tom Boucher
d1e9491fef feat(#2099): drive GitHub Copilot through the EoS descriptor + multi-event hook bus (ADR-1239)
Fold Copilot's residual runtime-literal branches onto descriptor-driven
hostBehaviors. Several issue premises were inaccurate (verified via research)
and deliberately NOT followed: writesSharedSettings/"legacy exclusion list"
(per-runtime descriptor data, copilot's false is correct); RUNTIME_CONTENT_DISPATCH.copilot
+ installSurface==='copilot-instructions' (already descriptor-driven);
extendedHookEvents (closed Claude/Gemini enum — hooks extended in code instead);
reapply ternary (already folded, kimi #2095).

Real folds (all byte-parity — golden byte-identical for every runtime):
- src/install-engine.cts + src/surface.cts: the two `.agent.md` filename cutovers
  (_copyStaged + _syncGsdDir) unified onto hostBehaviors.agentFileExtension via a
  new exported agentFileExtensionFor() accessor (kills the two-mechanism divergence).
- src/runtime-artifact-conversion.cts: applyAgentPathRewrites' copilot skip →
  hostBehaviors.noPathRewrite:true (antigravity #2096 precedent).
- bin/install.js uninstall: the two isCopilot cleanup branches → installSurface===
  'copilot-instructions' gate (symmetric with install-time).
- bin/install.js: `!isCopilot` in the two skipSharedHooksInstall checks →
  hostBehaviors.skipSharedHooksInstall:true (copilot has no shared gsd-*.js hooks).
- bin/install.js: three dead legacy inline-agent-loop isCopilot refs removed
  (copilot ∈ _DESCRIPTOR_AGENTS_RUNTIMES → unreachable; byte-parity proven by clean
  golden + real reachable-runtime install diffs). isCopilot dropped from 4 destructures.
Zero live `runtime==='copilot'`/`isCopilot` branches remain in bin/install.js,
install-engine.cts, surface.cts, or runtime-artifact-conversion.cts (AC2 guard scans all four).

UPGRADE 1 (multi-event hook bus): buildCopilotHookConfig() now emits preToolUse/
postToolUse/userPromptSubmitted/sessionEnd advisory handlers alongside sessionStart
(static inline bash/powershell — deterministic, golden-trackable). Only copilot.json's
gsd-session.json hash changes.

UPGRADE 2 (background dispatch): surfaced via the negotiated contract only —
dispatch.background:true exceeds the declarative-cli baseline and survives negotiation
with no downgrade warning. NO .agent.md frontmatter field (copilot has none). MCP
companion out of scope (AC4 names only 2 upgrades).

Tests: declarative-reference-copilot (adapter/axes/fail-closed + AC2 4-file source-grep
guard) + copilot-upgrades (live 5-event hook wiring; dispatch.background negotiation).
Matrix EoS note + how-to; changeset (Changed). capability-registry regenerated.

Incidental flaky-test RE-ARCHITECTURE (no-defer, maintainer-directed):
tests/opencode-review-reconstruction.property.test.cjs spawned ~600 synchronous
execFileSync('jq') subprocesses (numRuns:200 × 3 fast-check properties, one jq per
generated stream); a single jq freezing on a contended macos-22 CI runner hung the whole
unit-test chunk to its 600s kill (this PR's CI). --test-force-exit can't interrupt a
synchronous execFileSync, so the cure is to stop spawning per case, not just time-bound
it. Re-architected to run the SHIPPED jq program over the whole fast-check corpus in ONE
jq process: each generated stream is one compact-JSON array per line in a temp file,
`jq -c <PROGRAM>` (no -s) applies PROGRAM to each array (`.` == the array, exactly what
production's `jq -rs <file>` sees after slurping) and emits one result per line —
empirically byte-identical to the per-stream form across embedded-newline/empty/quote/
unicode/null-drop cases, and file-input (like production) so there's no stdin pipe to
deadlock on large I/O. ~600 spawns → 6; coverage unchanged (200-case corpus per property,
deterministic seeds) plus explicit boundary/diagnostic example batches. Still property-
tests the real shipped jq (no JS reimplementation). Per-call jq timeout retained as a
belt-and-suspenders bound.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 13:10:31 -04:00
Tom Boucher
9e1fad1503 feat(#2098): drive CodeBuddy through the EoS descriptor + wire subagent hooks (ADR-1239)
Fold CodeBuddy's residual runtime-literal branches onto descriptor-driven
hostBehaviors (the issue's "zero branches remain" premise was inaccurate — 2
lived):
- Folded the `if (isCodebuddy)` commands/ report duplicate (byte-identical to
  the generic reportCommandsDir block above it) onto
  hostBehaviors.reportCommandsDir:true, matching cursor's #2089 precedent.
- Deleted the dead `else if (isCodebuddy)` agent-conversion arm (codebuddy ∈
  _DESCRIPTOR_AGENTS_RUNTIMES → gated out before the legacy chain, same dead-arm
  pattern removed for augment/trae in #2097).
- Removed isCodebuddy from all 4 destructure sites (now comments only, mirroring
  the #2096 isAntigravity fold). Zero live isCodebuddy reads remain.

UPGRADE 1 (extended hook bus): populate extendedHookEvents with the full
extended set SubagentStop/Stop/PreCompact/SubagentStart (qwen #2092 / kimi
precedent — codebuddy previously had extendedHookEvents:[] so it got NONE of
these). The generic applySettingsJsonHooks loop wires them, so CodeBuddy's
settings.json now gains all four subagent-lifecycle + stop/compact hooks it
previously lacked, matching Qwen/Kimi coverage. No source change (the
HOOK_EVENT_SURFACES SDK catalog is a locked dict; all 4 events are already in
the validator enum; hookEvents already 'claude'). settings.json is golden-
excluded → no golden change.

UPGRADE 2 (background dispatch): surfaced via the negotiated capability contract
only. CodeBuddy's dispatch.background:true legitimately exceeds the declarative-
cli baseline (false) and survives negotiation with no warning. NO agent-file
frontmatter field is emitted: the CodeBuddy CLI (GSD's install target,
~/.codebuddy/agents/) has NO background-dispatch frontmatter field — background
is a caller-side run_in_background invocation param (verified against
codebuddy.ai/docs/cli/sub-agents); the issue's agentMode/enabledAutoRun are
IDE-only (codebuddy.cn, a different product). Emitting them would be a non-
functional false-green, so it is deliberately not done.

Golden: byte-identical for all 16 runtimes (folds are console/failures-report
only; UPGRADE 1 → golden-excluded settings.json; UPGRADE 2 → no artifact
change). Tests: declarative-reference-codebuddy (adapter/axes/fail-closed
negotiation + AC2 source-grep guard) + codebuddy-upgrades (live install asserts
all 4 extended hooks wired; dispatch.background survives negotiation above
baseline). Matrix + install-on-your-runtime updated; changeset (Changed).
capability-registry regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 09:39:34 -04:00
Tom Boucher
17aa35fba7 feat(#2097): migrate Augment onto EoS declarative adapter + settings.json MCP companion (ADR-1239)
Fold augment's runtime-literal conversion branches onto descriptor-driven
hostBehaviors and delete dead code:
- Site A (_applyRuntimeRewrites case 'augment'): the 4 ~/.augment dot-dir
  regexes now derive from getDirName('augment') via escapeRegExp (byte-
  identical; getDirName('augment')==='.augment') — no runtime literal.
- Site B (applyRuntimeContentRewritesForCommandsInPlace): the
  `if (runtime==='augment')` markdown-converter branch now reads
  runtime.hostBehaviors.commandBodyConverter and dispatches through a local
  COMMAND_BODY_CONVERTERS map (degrade-closed on unknown/absent name).
- Deleted dead `claudeToAugmentTools` map (zero refs; orphaned by ADR-1508
  single-sourcing) and the unreachable `else if (isAugment)` agent-conversion
  branch (augment ∈ _DESCRIPTOR_AGENTS_RUNTIMES → gated out upstream).
- Incidental orphan cleanup (no-defer): removed the equally-unreachable
  `else if (isTrae)` agent-conversion arm left behind by trae's already-merged
  migration #2094 (trae ∈ _DESCRIPTOR_AGENTS_RUNTIMES, same upstream gate).
  copilot/windsurf/codebuddy arms are removed by their own pending migrations.

UPGRADE 3 (transport:mcp): register the GSD companion MCP server in Augment's
settings.json under mcpServers.gsd (Augment hosts MCP in settings.json, not a
standalone file). mergeGsdMcpServerIntoSettings mutates the in-memory settings
object finishInstall already writes (gated on hostBehaviors.mcpCompanion===
'settings-json'); non-destructive + idempotent; symmetric uninstall removal.
settings.json is golden-excluded, so no golden change. UPGRADE 1 (named/
background dispatch) + UPGRADE 2 (settings-json hook bus, Claude dialect)
were already live in production — this adds tests exercising both.

Golden: byte-identical for all 16 runtimes (folds preserve regex behavior;
MCP lives in golden-excluded settings.json) — verified by a real double-install
tree diff. Tests: declarative-reference-augment (adapter/axes/fail-closed/
undocumented-sub-axes + source-grep guard scoped to conversion-logic branches)
+ augment-upgrades (dispatch negotiation, hook-bus live install, MCP add/
idempotent/preserve/uninstall). Matrix + connect-gsd-mcp-server + a stale
install-on-your-runtime hook-ownership claim corrected; changeset (Changed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 04:47:56 -04:00
Tom Boucher
5695522d5f feat(#2096): migrate Antigravity onto EoS declarative adapter + permission-writer + MCP companion (ADR-1239)
Fold all antigravity literal branches into descriptor-driven reads:
getConfigDirFromHome (→ configHome.kind 'dot-home-nested'), projectLocalHookPrefix
(→ hostBehaviors.hookPathStyle 'raw'), applyAgentPathRewrites (→ noPathRewrite),
getProjectInstructionFile (→ projectInstructionFile 'GEMINI.md'); removed the dead
inline convertClaudeAgentToAntigravityAgent branch + dead isAntigravity
destructures (antigravity is already on the descriptor-agents path). subagentToolkit
flipped undocumented→full (Context7: antigravity.google/docs/cli/features);
namedDispatch/nested/maxDepth/backgroundDispatch stay undocumented. Byte-identical
golden parity for all 16 runtimes.

UPGRADE 1 (permission-writer): permissionWriter 'antigravity' + configureAntigravityPermissions
merges a scoped permissions.allow block (GSD's own tree + hooks) into Antigravity's
settings.json — non-destructive, idempotent, symmetric uninstall. Added to
VALID_PERMISSION_WRITERS + the FinishPermissionWriter union.
UPGRADE 2 (MCP companion): configureAntigravityMcpConfig writes mcp_config.json
registering the gsd-core companion MCP server (Gemini-successor mcpServers schema,
best-effort — raw schema unpublished). Both writers dispatch from finishInstall.
settings.json is golden-excluded (HOOK_CONFIG_FILES); mcp_config.json (portable,
no absolute paths) is golden-tracked → only antigravity.json changes.

Tests: declarative-reference-antigravity extended (source-grep guard across 4
modules, fail-closed for the 4 undocumented sub-axes, validator acceptance) +
antigravity-upgrades (permission-writer + mcp_config live-install, idempotency,
user-preservation). Matrix + ADR-1016 + capability-manifest + CONTEXT.md +
connect-gsd-mcp-server docs updated; changeset (Changed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 03:07:01 -04:00
Tom Boucher
ab04916682 feat(#2095): migrate Kimi CLI onto EoS imperative adapter + native hook-bus + background dispatch (ADR-1239)
Fold all runtime==='kimi'/isKimi logic branches into descriptor-driven
hostBehaviors (localInstallDeferred, verificationStyle, agentManifestStyle,
reapplyCommand, doneBannerStyle) + add 'kimi' to _DESCRIPTOR_AGENTS_RUNTIMES.
Kimi's skills/kimi-agents dispatch was already descriptor-driven (converter-by-
name + kimi-agents kind). Zero isKimi/runtime==='kimi' branches remain.

UPGRADE 1 (native hook bus): new hooksSurface 'kimi-hooks-toml' + a marker-
delimited config.toml [[hooks]] emitter (buildKimiHooksTomlBlock/writeKimiHooksToml
in runtime-hooks-surface.cts; resolveKimiHooksTomlDir in runtime-homes.cts).
GSD's lifecycle hooks now wire into Kimi's native ~/.kimi/config.toml (Context7-
confirmed path) at SessionStart/PreToolUse/Stop/PreCompact/SubagentStart/
SubagentStop — kimi becomes a hooks/ consumer (the 3 && !isKimi exclusion guards
removed). config.toml holds absolute install paths so it's golden-excluded via
an exact relative-path (.kimi/config.toml), not a basename (which would blind
Codex's config.toml). New hooksSurface value added to the closed enum in
capability-validator + runtime-config-adapter-registry.
UPGRADE 2 (background dispatch): flip dispatch.backgroundDispatch true (Kimi's
Agent tool takes run_in_background; root agent already gets the Agent tool), so
negotiation no longer flattens dispatch. subagentToolkit stays 'undocumented'
per AC (coder/explore/plan have distinct tool policies).
MCP transport explicitly deferred (no installer-driven MCP for any runtime).

Golden: only kimi.json changes (hooks/ scripts now installed); all 15 others +
claude-local byte-identical (kilo/zcode keep their own exclusions). Tests:
kimi-imperative-reference (adapter/axes/fail-closed/hostBehaviors + source-grep
guard) + kimi-upgrades (config.toml [[hooks]] SessionStart + marker idempotency
+ backgroundDispatch negotiation). CONTEXT.md glossary + matrix + how-to updated;
changeset (Added).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:53:34 -04:00
Tom Boucher
f744635b3d feat(#2094): migrate Trae onto EoS imperative adapter + SOLO stage-metadata upgrade (ADR-1239)
Fold trae logic branches into descriptor-driven reads: skipSharedHooksInstall
gates (dropped && !isTrae), and the case 'trae' path-rewrite arm now computes
the self-alias from the descriptor-driven dirName (.trae). Dead isTrae bindings
removed from uninstall/writeManifest/finishInstall. trae's skills dispatch was
already descriptor-driven (converter-by-name). RUNTIME_CONTENT_DISPATCH.trae is
left as a runtime-keyed table registration (its regex/callback rewrites can't be
a byte-identical descriptor map — matches cursor/windsurf/cline). trae stays in
RUNTIME_FLAG_IDS: isTrae still gates the agents-converter selection (agents out
of scope; removal gated on the cross-runtime agents-dispatch migration).
Byte-identical golden parity for all 16 runtimes.

UPGRADE: SOLO stage/trigger metadata — emitted Trae SKILL.md now carries
stage: workflow (descriptor-gated via hostBehaviors.soloStageMetadata) so
Trae's SOLO Agent can auto-invoke GSD skills at the corresponding stage. Field
shape is best-effort/inferred (Trae publishes no formal schema). trae.json
golden regenerated.

Tests: trae-imperative-reference (adapter/axes/fail-closed shouldFlattenDispatch
+ no runtime==='trae' source-grep, isTrae exempted for agents) + trae-upgrades
(stage: workflow on installed SKILL.md, descriptor-gated). Matrix note +
changeset added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:31:10 -04:00
Tom Boucher
f014ec83bd feat(#2093): migrate Kilo onto EoS imperative adapter + hook-bus/model/MCP/dispatch upgrades (ADR-1239)
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>
2026-07-10 19:55:15 -04:00
Tom Boucher
c6ce110efa feat(#2092): migrate Qwen Code onto EoS imperative adapter + native subagents + SubagentStart (ADR-1239)
Fold all runtime==='qwen'/isQwen logic branches (skill-priority frontmatter,
branding/path rewrites, legacy commands/gsd cleanup, hyphen-namespace
normalization, RUNTIME_CONTENT_DISPATCH, hooks-surface label) into
descriptor-driven runtime.hostBehaviors on capabilities/qwen/capability.json,
read via _hostBehaviors(). Shared claude/qwen/hermes legacy-migration branches
in install-engine.cts folded to descriptor flags (claude+hermes descriptors
updated; FALLBACK_HOST_BEHAVIORS.claude floored). Byte-identical golden parity
for qwen/hermes/claude(global+local).

UPGRADE 1: native .qwen/agents/*.md subagent projection — new agents
artifact-layout kind + convertClaudeAgentToQwenAgent converter (name +
description + tools YAML block list; color/model dropped). qwen routed onto
the descriptor-driven agents path (_DESCRIPTOR_AGENTS_RUNTIMES).
UPGRADE 2: SubagentStart hook wired into extendedHookEvents + the
descriptor-gated hook-writer loop (activates only for qwen).

Tests: qwen-imperative-reference (adapter/axes/fail-closed/hostBehaviors +
no runtime==='qwen' source-grep across 4 files) + qwen-upgrades (agents file
validity + SubagentStart mirrors SubagentStop, descriptor-gated). Docs matrix
+ how-to updated; changeset added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:44:19 -04:00
github-actions[bot]
451e58e6d5 chore: sync next package version to 1.7.0-rc.5 2026-07-10 17:09:38 +00:00
Tom Boucher
b321bc04f4 fix(#2128): bound the sibling bracket-prefix clause — complete the ReDoS fix
Review caught that the prior commit bounded only the paren tag clause and left
the SIBLING bracket-prefix `(?:\[[^\]]+\]\s*)?` (same host regexes, before Phase)
UNBOUNDED — the identical quadratic reachable via a `[...]` run (measured ~16s at
1.7MB). Bound `[^\]]+`/`[^\]]*` -> {1,200}/{0,200} across all 19 phase/milestone
heading prefixes. Comprehensive re-measurement now shows EVERY vector linear
(bracket/paren/id/name/milestone all ~2-44ms at 2.45MB; bracket scaling
2k->2ms, 4k->5ms, 8k->10ms). Also: update the #1729 literal-mirror parity test
off its stale unbounded constant, and add limit-1 (199) boundary coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:44:49 -04:00
Tom Boucher
07dcd85c98 fix(#2091): regenerate capability-registry from clean origin/next (includes cline hostBehaviors from #2090)
The previous registry was generated from a stale main-repo state that was
missing cline's hostBehaviors (merged in #2090). CI's lint:generated-sync
detected the staleness. Regenerated from clean origin/next + hermes changes.
2026-07-09 23:19:44 -04:00
Tom Boucher
f152e9a069 feat(#2091): migrate hermes onto EoS imperative adapter + extensionEvents dialect (ADR-1239)
- Fold 7 hardcoded isHermes/runtime === 'hermes' branches in bin/install.js into
  descriptor-driven _hostBehaviors lookups (skillFrontmatterVersion,
  skillsManifestPrefix, trackCategoryDescription, writeCategoryDescription,
  reportSkillsCount, legacyCommandsGsdCleanup, brandingRewrites)
- Add runtime.hostBehaviors block to capabilities/hermes/capability.json
- Register EXTENSION_EVENT_SURFACES.hermes (13 real plugin hook events) in
  src/host-integration.cts — replaces the borrowed hookEvents:'claude' 6-event
  surface that silently never fired on Hermes
- Add extensionEvents:'hermes' to the descriptor
- Add 'hermes' to VALID_EXTENSION_EVENTS in capability-validator.cjs
- Regenerate capability-registry.cjs
- Tests: hermes-imperative-reference (negotiation, axes, fail-closed, source-guard),
  hermes-dispatch-upgrade (dispatch posture, degradation, fail-closed)
- Changeset + docs update
2026-07-09 20:47:35 -04:00
Tom Boucher
760eb71b6b feat(#2090): migrate cline onto imperative adapter + beforeTool/createAgentModel upgrades
Fold all hardcoded runtime === 'cline' / isCline branches in bin/install.js
into descriptor-driven runtime.hostBehaviors lookups (reapplyCommand,
frontmatterDialect, skipSharedHooksInstall, localTargetIsProjectRoot,
clineRulesSurface, localCommandsViaRules). Add cline-sdk-binding adapter
(ADR-1239 Phase D): UPGRADE 1 re-implements the .clinerules/hooks/PreToolUse
guard as a real AgentPlugin.hooks.beforeTool handler (fail-open, same
semantics); UPGRADE 2 wires DefaultGateway.createAgentModel params from
model_overrides/model_profile_overrides resolution (modelMode: active).
Install output is byte-identical (golden parity asserted for cline +
claude/cursor/codex/opencode).
2026-07-09 19:38:27 -04:00
Tom Boucher
b0d985ccb3 feat(#2089): migrate cursor onto imperative adapter + hook-bus/dispatch upgrades 2026-07-09 00:21:39 -04:00
Tom Boucher
6e773d97df feat(#2088): migrate Codex onto the Embeddable Orchestration System (ADR-1239)
Drive Codex install/uninstall through the descriptor-driven Host-Integration
Interface (declarative embedding adapter → engine surface dispatch) and fold
every positive `runtime === 'codex'` / `isCodex` projection into descriptor-driven
`runtime.hostBehaviors`. Install/uninstall output stays byte-parity-gated
(tests/fixtures/golden-install-parity/codex.json); no other runtime changes.

Three Context7-verified upgrades, each with a test on the user-reachable surface:
- Skill root → canonical $HOME/.agents/skills via a skills-kind `home` override,
  with pre-move migration cleanup (stale ~/.codex/skills/gsd-* removed on install
  and uninstall; user content preserved). Fixes getGlobalSkillsBase, writeManifest,
  and the skill-manifest inventory to honor the override so --skills-root /
  sync-skills / the manifest report the real location.
- Six new hooks.json lifecycle events (PreToolUse, PermissionRequest, PreCompact,
  PostCompact, SubagentStop, UserPromptSubmit) shared by install + uninstall;
  extendedHookEvents reconciled [] -> the schema-valid wired subset.
- Explicit `[agents] max_depth = 1` in the managed config.toml block, pinning the
  negotiated dispatch.maxDepth:1 axis. validateCodexConfigSchema now permits a
  known-scalar-only bare `[agents]` AgentsToml table (still rejects [[agents]] and
  unknown-key break-forms, #2760); mergeCodexConfig preserves the user's own
  AgentsToml scalars (max_threads etc.) instead of dropping them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:42:11 -04:00
Tom Boucher
396f44bd0b feat(architecture): [EoS/opencode] Migrate OpenCode onto the Embeddable Orchestration System (ADR-1239, #2087)
Route OpenCode (and its Kilo sibling) through the public Host-Integration Interface and
land two Context7-verified capability upgrades. Byte-identical install output for all 16
runtimes (golden parity asserted).

Through the interface (AC2):
- OpenCode/Kilo's bespoke commands+skills+plugin install (the inline
  `else if (isOpencode || isKilo)` block) moves into the engine
  (installOpencodeFamilyCommands/Artifacts in src/install-engine.cts), dispatched by
  installRuntimeArtifacts when the descriptor declares hostBehaviors.combinedFamilyInstall.
  opencode/kilo now flow CLI -> _runtimeAdapter -> installRuntimeArtifacts like the skills
  runtimes. _isSkillsRuntime no longer excludes them; the bespoke block + dead
  copyFlattenedCommands are removed.
- Every hardcoded `runtime === 'opencode'`/`isOpencode` branch is folded into
  descriptor-driven runtime.hostBehaviors. ZERO `runtime === 'opencode'`/`'kilo'`
  string-equality remain in bin/install.js / install-engine.cts / runtime-artifact-conversion.cts.

Upgrades (AC4):
- Background dispatch: OpenCode shipped experimental background subagents in v1.15 and
  made them default-on in v1.17 -> dispatch.background/backgroundDispatch flip to true;
  shouldFlattenDispatch(opencode) now returns false (behavioral change; type: Changed).
- Expanded event surface: the OpenCode plugin subscribes permission.asked/replied +
  session.error.

Tests: opencode-imperative-reference (adapter/profile, shouldFlattenDispatch pin,
fail-closed negotiate, hostBehaviors, AC2 source-guard) + extended plugin surface test.
Docs: capability matrix v1.15/v1.17 citations. Changeset (Changed). gitignore .memdb//.memtrace/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:17:05 -04:00
Tom Boucher
fdd5e401eb feat(architecture): [EoS/claude] drive claude through the imperative adapter + descriptor-driven hostBehaviors (#2086)
Fold Claude Code's install/uninstall onto the Embeddable Orchestration System
(ADR-1239 Phase D). claude is GSD's tier-1 reference host, but its install path
was still driven by 13 hardcoded `runtime === 'claude'` string-equality branches
scattered across bin/install.js rather than the public Host-Integration Interface.

- Route install()/uninstall() through `createImperativeAdapter({runtime})` — the
  adapter delegates to the SAME installRuntimeArtifacts/uninstallRuntimeArtifacts
  engine calls, so output is byte-identical (proven pre/post, both scopes).
- Replace all 13 `runtime === 'claude'` / `runtime !== 'claude'` branches with
  descriptor-driven `runtime.hostBehaviors` lookups on capabilities/claude/
  capability.json (attributionSource, authorsCanonicalWorkflow, localInstallStyle,
  permissionsSchema, settingsFileByScope, sourceMarkerFile, agentFrontmatterExtensions,
  ownsClaudePaths, nativeModelAliases, skillsGlobalOnboarding). Behavior is
  identical; the brittle string-equality coupling (the add-a-host tax) is gone.
- Single-source the scattered literal 'claude' defaults/rosters behind DEFAULT_RUNTIME.
- Extend golden-install-parity to assert the claude LOCAL legacy layout is
  byte-identical too (AC1 "both scopes"); exclude the platform-varying
  settings.local.json (same reason settings.json is excluded).
- New tests/claude-imperative-reference.test.cjs: adapter kind, programmatic-cli
  profile, fail-closed negotiation on a corrupted/partial descriptor, and an AC2
  source guard that no `runtime === 'claude'` branch remains.

No user-visible install-output change (internal architecture only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:31:53 -04:00
Tom Boucher
6addeccd19 feat(ai-integration): API-coverage verify:pre gate (#1562)
Full API Coverage by Default — Opt Out, Never Opt In. A phase that integrates
an external API/SDK/service can no longer seal without a decided coverage matrix.

- src/api-coverage.cts: deterministic detector (compound verb+noun signal +
  <Service> API/SDK surface; stopword-guarded; strips fenced code) + matrix
  parse/validate/render with field-length caps.
- check api-coverage.verify-pre: blocking seal-time gate; phase arg resolved as
  a token under .planning/phases/ only (traversal-neutralized); validates
  COVERAGE.md or blocks iff a strong integration signal is detected and no
  matrix exists; fail-closed when phases tree exists but phase unresolvable.
- capabilities/ai-integration: workflow.api_coverage_gate config key (default
  true), plan:pre contribution, blocking verify:pre gate. Data-driven.
- gsd-core/workflows/verify-work.md: generic verify:pre gate dispatch.
- Tests: detector FP/FN + matrix validation + fast-check bijection; gate e2e.
  Code+security review findings fixed (stopword FP, scope containment, pipe/cap
  rejection, prompt-injection message hygiene).
- Regenerated registry/matrix/loop-host-contract/goldens/baseline + docs.

Closes #1562
2026-07-07 15:11:12 -04:00
github-actions[bot]
a6263bba8a chore: sync next package version to 1.7.0-rc.4 2026-07-07 06:08:27 +00:00
Tom Boucher
f433db8b88 fix(#1143): address adversarial review — full semver precedence, docs/reality alignment
- compareSemver: implement full SemVer 2.0.0 §11 pre-release identifier
  comparison (two pre-releases of the same triple now order correctly; was 0).
- capability description + fragment: scope the plan-checker/verifier claim
  (this capability delivers the parallel-execution backend; those gates remain
  inline until separately wired). Correct the 'each wave is one barrier' prose
  (a wave splits into multiple sequential parallel() barriers on files_modified
  overlap). Frame detect-backend CLI as a simulation harness; the pure function
  with the live host descriptor is the real detection seam.
- partitionStages docstring: 'near-minimal via greedy first-fit' (not 'fewest');
  document empty-files_modified behavior.
2026-07-06 15:41:19 -04:00
Tom Boucher
e3262d94d3 feat(capabilities): add claude-orchestration capability (Workflow backend) (#1143)
Default-off, BETA, claude-only capability adopting Claude Code's Workflow tool
(/effort ultracode, Agent SDK >= v0.3.149) as an optional parallel-execution
backend for the GSD loop. Restores the wave parallelism + plan-checker + verifier
that #853 forces inline on Claude Code, and folds gsd-ultraplan-phase under one
runtime gate.

- Pure fail-closed core (src/claude-orchestration.cts): detectWorkflowBackend
  (gate ladder: enabled -> Claude -> backend != inline -> nested+background host
  -> valid Agent SDK -> SDK >= floor; every miss degrades to inline) and
  emitWorkflowScript (waves -> parallel() barriers, plans -> gsd-executor +
  worktree, files_modified overlap -> separate stages, resumeFromRunId, budget).
  All interpolated identifiers validated script-safe; briefs JSON-quoted.
- claude-orchestration command family (gsd-tools claude-orchestration
  detect-backend|emit-workflow) for orchestrator invocation.
- Two gated loop contributions at wired points (execute:wave:post, plan:post);
  federated config keys (enabled/execution_backend/min_agent_sdk_version).
- ADR-1143 implementation amendment; CONTEXT.md glossary entry; explanation doc.

On any runtime lacking the Workflow tool, behaviour is byte-identical to today.

closes #1143
2026-07-06 15:18:23 -04:00
Tom Boucher
d671171698 feat(#1575): complete agent-converter descriptor cutover for copilot/antigravity + surface path parity
- Teach applySurface to build agentCtx (pathPrefix + attribution) and pass it
  to kind.stage() for agents kind, mirroring createRuntimeArtifactInstallPlan
  (ADR-1235 §1). Surface-path agents now receive path-rewrite + attribution +
  converter + normalize, matching install output byte-for-byte.

- Pass skills:'*' sentinel for agents staging when no surface state modifications
  exist, so ALL agents are staged (not just those referenced by _calls_agents_).

- Declare converted agents kind in copilot and antigravity capability.json;
  add to _DESCRIPTOR_AGENTS_RUNTIMES in bin/install.js.

- Handle copilot .agent.md filename rename in both _copyStaged (install path)
  and _syncGsdDir (surface path).

- Ship golden-parity harness (ADR-1235 §0): tests/issue-1575-agent-descriptor-
  parity.test.cjs asserts applySurface output is byte-identical to
  installRuntimeArtifacts for all 7 descriptor-driven runtimes, plus stale-
  cleanup convergence and prune data-loss coverage.

- Update ADR-1235 with cutover progress.

Cline remains deferred (rules-only local branch + local/global complication).
2026-07-06 11:34:32 -04:00
Tom Boucher
7c4494bddb chore(#1925): bump zcode descriptor version to 1.7.0-rc.3 (post-rebase sync) 2026-07-06 08:33:02 -04:00
Tom Boucher
69b309e4e0 feat(#1925): add ZCode (Z.ai) as a pluggable runtime descriptor
Add ZCode as a first-party runtime via a declarative capability descriptor
(capabilities/zcode/capability.json) with zero hardcoded runtime === 'zcode'
branches — exercising the de-hardcoded, data-driven runtime path that 1.7.0
(ADR-1016 / ADR-1239) enables.

Descriptor (all axes sourced verbatim from zcode.z.ai docs):
- configHome ~/.zcode; nested skills + flat commands/agents; profile-marker install
- Claude-shaped skill format reuses convertClaudeCommandToClaudeSkill (no new converter)
- hostIntegration: declarative / slash-file / mcp / electron; dispatch background=false
  (foreground-only per docs); nested+maxDepth undocumented; passive model mode

Installer registration (data, not branches): --zcode flag, allRuntimes, runtimeMap,
interactive menu, --all list. getGlobalConfigDir + resolveRuntimeArtifactLayout +
ALLOWED_CONFIG_RUNTIMES + resolveInstallPlan all derive from the descriptor.

Revamped the brittle per-runtime golden-master tests to be count-agnostic,
descriptor-derived property tests (1.7.0 makes runtimes pluggable data, so pinning
frozen '15 runtime' snapshots is the wrong invariant): getdirname, label-policy,
config-adapter-registry (intent + install-plan golden master), capability-registry,
host-integration-descriptors (counts derive from curated maps). Adding a runtime
descriptor now extends coverage with zero edits to those suites.

Welcome banner, --zcode help, supported-runtimes how-to, and the host-integration
capability matrix (every axis cited) updated.
2026-07-06 08:31:51 -04:00
github-actions[bot]
a828d8207c chore: sync next package version to 1.7.0-rc.3 2026-07-06 02:19:50 +00:00
Tom Boucher
ed79902509 feat(#2007): implement mempalace memory_mode kg_backend and replace routing (#2010)
Wire the two forward-declared mempalace.memory_mode modes so they actually
route recall/capture instead of silently behaving as `augment`:

- kg_backend: the palace temporal KG is the primary knowledge-graph source;
  native .planning/graphs/ is the fallback. Non-KG drawer recall stays additive.
- replace: recall resolves through the palace as the source of truth; native
  artifacts are the fallback.

Every mode stays onError:skip and default-resilient — an unreachable palace
degrades to native memory and GSD keeps writing .planning/graphs/, so no memory
is lost. Cross-mode .planning/graphs/ migration remains a documented open
question (PRD/ADR §17), out of scope here.

Surfaces updated (instruction-only contract): recall/capture commands (+ generated
skills), discuss/wave fragments, curator agent, capability.json schema. Docs:
how-to Step 3, CONFIGURATION, FEATURES, CONTEXT glossary. Regenerated
capability-registry, golden install-parity fixtures (mempalace hashes only),
agent-size-baseline. Added a routing-contract + cross-surface parity test.

Incidental (folded per no-defer rule): removed pre-existing unused imports
(spawnSync in capability-registry.test.cjs; fs in issue-498-package-identity.test.cjs)
that eslint flagged in/alongside the touched files.

Closes #2007

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:01:22 -04:00
Tom Boucher
97730e59a1 fix(#1164): wire external-job config keys + document wave:post choice (#2006)
Refinements A-D to the external-job capability (PR #1998 follow-up):

A. Document why the contribution registers at execute:wave:post: #1164 asks
   for wave:pre, but execute-phase.md only dispatches wave:post today (wave:pre
   is declared in the loop host contract but not rendered). Wiring wave:pre is
   a core-loop change #1164 puts out of scope; the executor honors the
   runtime_budget classification guidance before running any tagged task.

B. external_job.artifact_dir is now consumed (was declared but unused): the
   adapter resolves it via the canonical capability-config seam and surfaces
   the resolved root in submit output.

C. external_job.submit_timeout_ms / poll_timeout_ms are now read from config
   (were shadowed by env-only reads). Precedence: env > config > registry
   default; non-numeric config values fall back (no guessing, no NaN).

D. CLI surface gains unit coverage: parseFlags, findPlanningDir,
   resolveExternalJobSettings, formatShowReport.

Regenerates capability-registry.cjs from the updated capability.json.
2026-07-05 14:04:00 -04:00
Tom Boucher
8f2ebbe9bf feat(#1928): remove sunset Gemini CLI runtime, redirect to Antigravity (#1996)
* feat(#1928): remove sunset gemini cli runtime, redirect to antigravity

Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is its official successor (already a first-class GSD runtime). Remove the gemini runtime from the enum (16->15), aliases, labels, config-home fragment, install path, converters (convertClaudeToGemini{Markdown,Toml,Agent}, convertSlashCommandsToGeminiMentions), capability descriptor, gemini-extension.json, RULESET.GEMINI.*, and the interactive menu (renumbered, no gap).

--gemini now prints an explicit deprecation notice citing the 2026-06-18 sunset and redirects to --antigravity (no silent alias, per the issue's Hyrum's-Law rejection). Antigravity is preserved throughout: its GEMINI.md contextFileName, .gemini/antigravity config home, the shared convertGeminiToolName/claudeToGeminiTools tool vocabulary, and the 'gemini' hookEvents dialect it declares. GEMINI.md retargeted as Antigravity's context file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1928): backfill changeset PR number (#1996)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1928): drop Gemini CLI from issue templates (review nit)

Removes the sunset Gemini CLI runtime from the two GitHub issue-template
runtime lists that the removal PR missed, per @davesienkowski's review nit:
- feature_request.yml: 'Applicable runtimes' checkbox (a user could otherwise
  request a feature for a runtime GSD no longer supports)
- bug_report.yml: 'Runtime' dropdown + the stale ~/.gemini/settings.json
  retrieval-help line

Leaves the post-removal templates fully consistent with the Antigravity redirect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 13:32:51 -04:00
Tom Boucher
5ae4ea4c84 feat(#1105): add external-job capability (SLURM scheduler-adapter producer half) (#1998)
* feat(#1105): add external-job capability (SLURM scheduler-adapter producer half)

The async external-job consumer half (#1165) shipped long ago: the core loop
reads .planning/async-jobs/<job>.json manifests and treats a non-terminal one
as the legal external_job_waiting half-state. The PRODUCER half (#1164) was
the remaining unimplemented piece of #1105.

This adds the producer as a default-off capability:

- capabilities/external-job/ — capability.json (execute:wave:post -> executor,
  plan:post -> planner contributions, external_job.* config keys, default-off)
  + fragments teaching runtime-budget classification and externalization.
- src/external-job.cts -> gsd-core/bin/lib/external-job.cjs — pure producer
  module: SLURM state -> manifest-status map (no guessing), manifest
  build/validate (versioned stability contract), sbatch/squeue/sacct parsers,
  and a fail-closed manifest writer (refuses a second non-terminal job for a
  plan_id already in flight; refuses to clobber a malformed manifest). fs/clock
  seams for deterministic tests.
- scripts/slurm-adapter.cjs — operator CLI (submit/poll/show) wrapping bounded
  sbatch/squeue/sacct subprocesses; surfaces manifest commands for confirmation
  and never auto-runs them (trust boundary).
- tests/external-job.test.cjs — 23 behavioral + fast-check property tests.
- docs/reference/long-running-operations.md + docs/how-to/async-external-jobs.md.
- CONTEXT.md glossary entry for the External-job Capability.
- Regenerated capability-registry.cjs; pruned the now-stale test-file-count
  allowlist entry (external-job is at the 2-file cap).

* chore(#1105): backfill PR number in changeset

* fix(#1105): sync capability artifacts + update registry shape-pin tests

gsd-test caught that adding the external-job capability requires its
dependent artifacts regenerated and its registry-shape drift absorbed:

- sync-manifest-versions: stamp 1.7.0-rc.2 into capability.json (was 1.0.0).
- gen-capability-matrix --write: regenerate docs/reference/capability-matrix.md.
- gen-inventory-manifest --write: regenerate docs/INVENTORY-MANIFEST.json.
- check-gap-analysis-plan-post-e2e: plan:post now has 1 contribution
  (external-job planner fragment) instead of 0.
- execute-wave-post-gate-pipeline-e2e: execute:wave:post now has 2
  contributions (mempalace + external-job) instead of 1.

* fix(#1105): regenerate capability-registry after version stamp

sync-manifest-versions re-stamped external-job/capability.json from
1.0.0 to 1.7.0-rc.2 after the last registry regeneration, leaving the
committed capability-registry.cjs stale (CI gen-capability-registry
--check failed). gsd-test masked this because its setup runs the full
'npm run build' (which regenerates the registry); CI's 'npm test'
pretest only runs build:lib.
2026-07-03 19:37:38 -04:00
github-actions[bot]
8f791817f4 chore: sync next package version to 1.7.0-rc.2 2026-07-03 04:15:36 +00:00
Tom Boucher
b51cbf96cf feat(#1943): extensionEvents vocabulary — separate from hookEvents (extension-system surface) (#1946)
* feat(#1943): extensionEvents vocabulary — separate from hookEvents (extension-system surface)

* fix(#1943): re-export VALID_EXTENSION_EVENTS from gen-capability-registry (test import path)

* fix(#1943): regenerate capability-registry.cjs + add changeset fragment

* fix(#1943): import VALID_EXTENSION_EVENTS from validator, not gen-capability-registry (golden parity)
2026-07-02 21:45:35 -04:00
Behruz Nassre Esfahani
50ff7a8707 fix(#1776): scope prune phase resolution to ## Current Position (#1832)
* fix(#1776): scope prune phase resolution to ## Current Position

cmdStatePrune resolved the current phase by extracting the `Phase` field over
the WHOLE STATE.md body. stateExtractField's fallback chain ends in a pipe-table
match (`| Phase | N |`), so a STATE.md lacking a `Current Phase` field and a
prose `Phase:` line — but carrying an unrelated `Phase`-labelled table row (e.g.
a historical verification table) — resolved that stale table cell as the current
phase and computed a wrong cutoff (bailing "Only N phases" or pruning at a stale
boundary).

Resolve the phase via the same canonical chain buildStateFrontmatter uses —
frontmatter `current_phase` → `Current Phase` field → prose `Phase: X of Y` —
but scope ONLY the prose term to the `## Current Position` section via the
fence-aware locateCurrentPosition seam (new exported sliceCurrentPositionSection).
Frontmatter and the explicit `Current Phase` field stay document-wide (they are
unambiguous); the shared stateExtractField is not narrowed for any other caller.

Tests (folded into tests/state-prune.test.cjs): a stray `| Phase | 2 |` table
with the real phase in frontmatter no longer drives the cutoff (fail-first on
base); template-conformant STATE.md is unchanged; and a fast-check
boundary-containment property that a `| Phase | N |` row outside Current Position
never leaks into the scoped resolution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1776): add changeset for prune Current Position scoping

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-06-30 13:16:40 -04:00
github-actions[bot]
feb7036399 chore: sync next package version to 1.7.0-rc.1 2026-06-30 13:28:49 +00:00
Tom Boucher
b5c8a3e590 feat(#1827): rebuildCore + rebuild intent + drift-class unit tests (#1829)
Phase 1 of approved feature #1817. Implements the body-structure
derivability contract landed in ADR-1817 (Phase 0, PR #1828).

Source changes (src/state-transition.cts):
- Add `rebuild` as the 11th intent in StateTransitionIntent (ADR-1769
  transition set extended per ADR-1817 §1).
- Add `phaseInventoryProvider` optional dep + PhaseInventoryRecord type
  (Leaky-Abstractions guard: pure core stays testable without disk I/O;
  rebuild skips table reconciliation when the provider is absent).
- Add `case 'rebuild':` dispatch arm to transitionCore (the missing-case
  compile-time guarantee extends to the 11th case).
- Implement rebuildCore orchestrator + four drift-class helpers per
  ADR-1817 §2:
    * reconcileCurrentPosition — body prose re-derived from frontmatter
    * reconcileByPhaseTable — **By Phase:** table re-derived from disk
      inventory via the new dep
    * stripTemplatePlaceholders — `**Field:** [placeholder]` →
      `**Field:** (pending)` (no canonical source available)
    * deduplicateSessionArchive — keep most-recent 3 archived H3 blocks
- Implement appendRebuildLogSection per ADR-1817 §3 — every mutation
  appends a structured entry (timestamp/kind/section/before/after/reason)
  to `## Rebuild Log`. Idempotency guarantee (ADR-1817 §4): a no-mutation
  rebuild appends NO log entry, so two successive runs on a clean file
  are byte-identical.

Compiled output (gsd-core/bin/lib/state-transition.cjs): regenerated via
`npm run build:lib` (tsc -p tsconfig.build.json).

Tests (tests/state-rebuild.test.cjs, 18 cases):
- Dispatch + idempotency contract (3 tests, including the load-bearing
  'rebuild on a clean file is a no-op' that pins §4).
- Current Position prose reconciliation, criterion #1 (3 tests).
- Template-placeholder removal, criterion #3 (3 tests).
- Session Continuity Archive de-duplication, criterion #4 (4 tests).
- **By Phase:** table reconciliation via phaseInventoryProvider, criterion
  #2 (3 tests, including the Leaky-Abstractions no-op guard).
- Regression guard for sync + prune, criterion #7 (2 tests).

Verified: node --test tests/state-rebuild.test.cjs → 18/18 pass.
Verified: node --test tests/state-transition.test.cjs → 85/85 pass (no
regression on the existing 10 transitions).

Phase 2 (#1826) wires the CLI surface (cmdStateRebuild + --dry-run +
integration tests + docs + changeset). This PR adds the engine only — no
user-visible command yet, so no-changelog label applied.
2026-06-29 15:59:37 -04:00
Tom Boucher
21f0b4316f refactor(#1796): ADR-1769 Path A — finish STATE.md preservation consolidation (#1799)
Extract readModifyWriteStateMd's post-sync preservation block into a pure,
field-classification-table-driven applyStatePreservation in the STATE.md
Transition Module. progress / status / stopped_at now join current_phase_name
as table-governed (getFieldClassification), so a preservation-policy change is
a one-row table edit instead of a per-call-site patch.

This realizes the consolidation ADR-1769 / CONTEXT.md already claimed shipped
('Absorbs readModifyWriteStateMd post-sync preservation block') and routes the
#1264 preservation policy through the single field-classification table — the
bug class is now structurally guarded by the table, not just the call-site
shouldResync flag.

Behavior is byte-identical to the pre-amendment inline block (Hyrum-safe — the
15 readModifyWriteStateMd callers' observable preservation is unchanged):
- state/frontmatter/transition + bug regression suite: 847 pass
- phase/milestone/verify (other RMW consumers): 582 pass
- codex (gpt-5.5/high) adversarial review: CLEAN (58,564-case equiv sweep)

ADR-1769 amendment appended documenting #1796.

Closes #1796
2026-06-27 22:49:52 -04:00
Tom Boucher
79c69d443b refactor(#1793): ADR-1769 Phase 7 — sync, prune, update migrations (#1760, #1761) (#1794)
Migrate cmdStateSync, cmdStatePrune, cmdStateUpdate (state.cts) onto the
STATE.md Transition Module substrate and close the maintenance bug pair
#1760/#1761 (ADR-1769, epic #1769). Completes the substrate: all 10
lifecycle/maintenance transitions now route through transitionCore.

- Add {kind: 'sync'|'prune'|'update'} to StateTransitionIntent with syncCore,
  pruneCore, updateCore in src/state-transition.cts.
- updateCore: body-only single-field update (strip/reassemble), mirroring the
  pre-migration cmdStateUpdate contract.
- pruneCore: pure content→content section pruning (Decisions / Recently
  Completed / resolved Blockers / Performance Metrics rows at or below cutoff),
  byte-identical tokenizeHeadings splicing. Adapter owns currentPhase, dry-run,
  and STATE-ARCHIVE.md.
- syncCore: body writes (Total Plans in Phase, Progress bar, Last Activity)
  given disk-derived numbers. Adapter owns the disk scan + roadmap scope.
- #1760: cmdStatePrune now derives currentPhase from 'Current Phase' OR 'Phase'
  (the canonical template emits 'Phase: X of Y'), so prune engages on
  template-conformant STATE.md instead of bailing 'Only 0 phases'.
- #1761: cmdStateSync skips the Progress write (percent=null) when a milestone
  version is set in frontmatter but the ROADMAP has no versioned heading for it
  (milestone cannot be bounded). Projects without a milestone version are
  unaffected. Leaves progress untouched rather than silently writing fallback-
  derived wrong values.
- Regression: bug-1760 (prune engages on template field) + bug-1761 (sync
  leaves progress untouched when unbounded). ADR-1769 marked Accepted.

All 82 transition + 515 regression tests pass.

Closes #1793
2026-06-27 16:53:44 -04:00
Tom Boucher
064f63b299 refactor(#1791): ADR-1769 Phase 6 — patch migration + curated current_phase_name preserve (#1695) (#1792)
Migrate cmdStatePatch (state.cts) onto the STATE.md Transition Module substrate
and close the curated-field clobber bug class #1743/#1695 (ADR-1769, epic #1769).

- Add {kind: 'patch'} to StateTransitionIntent, with patchCore in
  src/state-transition.cts. Applies each caller-supplied {field:value} pair via
  stateReplaceField over the full content (body + frontmatter), tracking
  updated vs. failed. data.updated/data.failed mirror the CLI output shape.
- Collapse cmdStatePatch to a transitionCore dispatch. Field-name validation
  (security) and the resync-progress decision stay in the adapter.
- #1695/#1743 fix: extend the #1230 delta heuristic in readModifyWriteStateMd to
  the curated current_phase_name, table-driven via
  getFieldClassification('current_phase_name').preservation === 'preserve-always'.
  When a write does NOT change the body Phase: source line, the curated
  frontmatter value wins over syncStateFrontmatter's body re-derivation (which
  harvests a wrong parenthetical aside — #1695). begin/planned/complete-phase
  rewrite their body Phase line, so the delta does not fire for them and
  current_phase_name still advances.
- Regression: bug-1695-state-patch-clobbers-phase-name.test.cjs — unrelated
  patch preserves curated current_phase_name; patching the Phase source advances.

All 70 transition + 493 regression tests pass (state/phase/milestone + #905/#397/#3242 lineages).

Closes #1791
2026-06-27 16:21:28 -04:00
Tom Boucher
3ceb83329d refactor(#1789): ADR-1769 Phase 5 — milestoneComplete migration (#1790)
Migrate the STATE.md write path inside cmdMilestoneComplete (milestone.cts)
onto the STATE.md Transition Module substrate (ADR-1769, epic #1769).

- Add {kind: 'milestoneComplete'} to StateTransitionIntent, with
  milestoneCompleteCore in src/state-transition.cts (consulting the
  field-classification table). Owns the closure write: Status
  ('<version> milestone complete'), Last Activity, Last Activity Description,
  a Current Position reset to 'Awaiting next milestone', and an Operator Next
  Steps reset pointing at the next-milestone command.
- Collapse the inline STATE.md transform in cmdMilestoneComplete to a
  transitionCore dispatch. The adapter retains writeStateMd (lock + steady-state
  syncStateFrontmatter post-sync) and resolves the runtime-specific
  next-milestone slash command, injecting it via intent.nextMilestoneCommand.
- The two section resets carry their pre-seam allow-adhoc-markdown waivers
  (regex semantics pinned by existing tests; pending collectSection #1372).
- realClock.today() is byte-identical to milestone.cts's local today
  (both new Date().toISOString().split('T')[0]).
- Characterization tests pin field updates, both section resets (replace +
  insert paths), and frontmatter #1255 parity.

All 66 transition + milestone + state tests pass.

Closes #1789
2026-06-27 15:46:54 -04:00
Tom Boucher
91704c9fcf refactor(#1786): ADR-1769 Phase 4 — plannedPhase + milestoneSwitch migration (#1788)
Migrate cmdStatePlannedPhase and cmdStateMilestoneSwitch (state.cts) onto the
STATE.md Transition Module substrate (ADR-1769, epic #1769).

- Add {kind: 'plannedPhase'} and {kind: 'milestoneSwitch'} to
  StateTransitionIntent, with plannedPhaseCore and milestoneSwitchCore in
  src/state-transition.cts (consulting the field-classification table).
- plannedPhaseCore owns the template-aware Status/Last Activity updates, Total
  Plans in Phase, Last Activity Description, and the Current Position section
  (via the inlined mutateCurrentPositionForAdvance twin). The adapter keeps the
  resync:false readModifyWriteStateMd wrapper (#500 RC1).
- milestoneSwitchCore owns the new-milestone reset: rebuilt frontmatter
  (milestone/name/status='planning'/zeroed progress) + Current Position body
  reset. The adapter keeps acquireStateLock + platformWriteSync (NOT
  readModifyWriteStateMd — milestoneSwitch rebuilds frontmatter directly).
- Collapse both callbacks to transitionCore dispatches. The now-dead
  updateCurrentPositionFields helper and KNOWN_STATUS_PATTERNS import are
  removed (their behavior lives in the transition module's
  mutateCurrentPositionForAdvance).
- Characterization tests pin the template-aware preserve-authored invariant,
  Total Plans, Last Activity narrative, Current Position update, and the full
  milestone reset (frontmatter + position body + gsd_state_version preserve +
  Accumulated Context preserve).

All 58 transition + 177 state + phase + bug-2630/bug-905 regression tests pass.

Closes #1786
2026-06-27 15:24:18 -04:00