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>
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.
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>
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>
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>
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
- 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.
- 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).
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.
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>
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.
* 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>
* 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.
* 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>
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.
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
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
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
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
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
Migrate the STATE.md write path inside cmdPhaseComplete (phase.cts) onto the
STATE.md Transition Module substrate (ADR-1769, epic #1769).
- Add {kind: 'completePhase'} to StateTransitionIntent + completePhaseCore in
src/state-transition.cts. Pure body field mutations (Current Phase shape/name,
Status, Current Plan, Last Activity + Description, Completed/Total Phases +
Progress percent), consulting the field-classification table for touched keys.
Roadmap progress injected via a new optional deps.roadmapProvider.
- Collapse the ~90-line inline STATE.md transform in cmdPhaseComplete to a
transitionCore dispatch. The adapter retains updatePerformanceMetricsSection
(section table) + syncStateFrontmatter (disk-scan post-sync) and the
multi-file atomic transaction (STATE is committed with ROADMAP/REQUIREMENTS,
so readModifyWriteStateMd is not used here).
- deriveProgressFromRoadmap/clampPercent/stateReplaceFieldWithFallback move from
the phase.cts call site into the pure core (no circular dep: phase-lifecycle
and state-document don't import state.cts).
- Characterization tests in tests/state-transition.test.cjs pin the field
updates, roadmap progress derivation, #1255 frontmatter parity, and the
'Phase:' fallback. All 44 transition + 193 phase tests pass.
No user-visible behavior change. cmdPhaseComplete CLI output and 'phase complete'
behavior unchanged.
Closes#1784
* refactor(#1771): ADR-1769 Phase 1 — STATE.md Transition Module substrate + beginPhase
Lands the Phase 1 substrate per ADR-1769:
- src/state-transition.cts (new Module):
- Field-classification table (FieldClass enum + FIELD_CLASSIFICATION rows)
- STATE_MD_SECTIONS constants block
- Pure transitionCore(content, intent, deps) dispatch
- beginPhase intent implementation (first-time + #3127 resume paths)
- src/state.cts:cmdStateBeginPhase — collapses ~190 lines to a thin
dispatch onto transitionCore via readModifyWriteStateMd. The lock,
no-op write guard, and #1230 post-sync delta heuristic stay in the
RMW seam; the body-mutation policy moves to transitionCore.
- tests/state-transition.test.cjs (24 tests):
- Substrate invariants (table enum, section constants)
- Characterization: 6 first-time body field updates
- Characterization: 5 #3127 idempotency-guard resume behaviors
- Characterization: 3 Current Position section mutations
- Characterization: Current focus body text line (#1104)
- Property (RULESET.TESTS.property-based-testing): beginPhase status
propagation + FIELD_CLASSIFICATION own-property contract
- Resume Current Position mutation (preserves Plan/Phase/Status)
No external behavior change. Full state.test.cjs regression (177 tests)
plus bug-3127/#3242/#905/#948 pass. Property tests surfaced two
pre-existing quirks (state-document.cjs greedy \s* on whitespace-only
field values; Object.prototype method leakage on FIELD_CLASSIFICATION
lookups for strings like 'toString') — documented in test comments;
fix-out-of-scope for Phase 1.
Closes#1771
* refactor(#1771): ADR-1769 Phase 1 codex review corrections
Addresses 3 blocking findings from codex gpt-5.5/high review:
1. FIELD_CLASSIFICATION shape (state-transition.cts):
- Was flat FieldClass enum (collapsed source + preservation)
- Now two-column {source, preservation} rows per ADR-1769 §4
- Added missing fields verified via Memtrace against
buildStateFrontmatter (state.cts:1633-1653): gsd_state_version,
last_updated, last_activity_desc, progress.{total_phases,
completed_phases, total_plans, completed_plans, percent}
- Field 2-7 preservation dispatch can now consult the table
2. Prototype-pollution hardening (state-transition.cts):
- Table is now Object.freeze(Object.assign(Object.create(null), {...}))
- getFieldClassification() helper uses Object.hasOwn; returns null
for inherited prototype methods (toString/valueOf/__proto__)
- Old code: FIELD_CLASSIFICATION['toString'] returned the function
3. STATE_MD_SECTIONS aligned to canonical template:
- Verified against gsd-core/templates/state.md via Memtrace
- Was: 8 entries including non-template sections (## Session,
## Decisions, ## Operator Next Steps, ## Session Log,
## Roadmap Evolution)
- Now: 6 canonical top-level sections (## Project Reference,
## Current Position, ## Performance Metrics, ## Accumulated
Context, ## Deferred Items, ## Session Continuity)
Also: beginPhase now consults getFieldClassification() per touched
field (codex finding: 'table not consulted by transitionCore').
Unknown fields raise immediately — adding a field without a table
row is caught at runtime.
Repo-hygiene catches from gsd-test (not node --test, which missed
these):
- gsd-core/bin/lib/state-transition.cjs added to eslint.config.mjs
ignore list (ADR-457 tsc-generated)
- docs/INVENTORY-MANIFEST.json regenerated via
node scripts/gen-inventory-manifest.cjs --write
Property test for Object.prototype leakage tightened to verify
getFieldClassification() returns null for toString/valueOf/__proto__.
Ref #1771
* fix(#1771): cast Object.create(null) to satisfy @typescript-eslint/no-unsafe-assignment
ESLint CI failed on src/state-transition.cts:72:14 — Object.create(null)
returns `any`, which leaked through Object.assign to the typed
`FIELD_CLASSIFICATION` declaration. Adding an explicit cast to
`Record<string, FieldClassification>` eliminates the unsafe-assignment
while preserving the null-prototype protection codex review recommended.
gsd-test: 21888/21888 PASS.
* fix(#1771): add 'see #1771' to allow-test-rule exemption per ADR-456
CI lint-allow-test-rule-refs failed: 'New allow-test-rule exemption
without an issue ref — add `see #NNN` per ADR-456'. Updated comment
on tests/state-transition.test.cjs to reference the Phase 1 issue.
* fix(#1771): remove unnecessary allow-test-rule exemption
The exemption was added speculatively. The test file does not use
readFileSync + .includes()/.match()/.startsWith() on source content —
it calls transitionCore() with string literals and verifies results
via stateExtractField() and array .includes() on the updated[] array.
No exemption needed.
ADR-1235 step 1: route the trivial-converter runtime group (cursor, windsurf, augment, trae, codebuddy) off the inline install() agent loop onto the descriptor-driven installRuntimeArtifacts path. Establishes the converter-context foundation (pre-converter cross-cutting + no agent-stamp). Agent install output is byte-identical for all 16 runtimes (golden-parity, global + verified local). cline deliberately excluded (local rules-only). Closes#1763.
ADR-1239 Phase B (parent #1679). getDirName was a hand-maintained 15-branch
if-chain mapping each runtime to its local content-rewrite dot-dir. Relocate
those values into a documented runtime.localConfigDir descriptor field; derive
getDirName from registry.runtimes[id].runtime.localConfigDir (fallback .claude).
- 16 capability.json gain runtime.localConfigDir (byte-identical values)
- capability-validator.cjs requires it (non-empty dot-dir); registry regenerated
- docs/reference/capability-manifest.md documents the field + the three
divergent values (copilot=.github, antigravity=.agents, kimi=.kimi-code)
- drift-guard test: golden value map + key-set equality both ways
Byte-identical install output for all 16 runtimes (golden-parity harness #1730).
Closes#1756
Co-authored-by: review-bot <review-bot@gsd>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1708): typed documentation-sourced #853 dispatch-flatten
Graduate the #853 orchestrator-backgrounding decision from a scattered RUNTIME==='codex' prose check to a typed, documentation-sourced engine decision. Adds a backgroundDispatch dispatch sub-axis (sourced per host: codex+cursor documented true, 9 documented false, 5 undocumented), shouldFlattenDispatch(dispatch) (inline UNLESS background && backgroundDispatch, fail-closed), and a gsd_run query dispatch-should-flatten the plan/execute workflows call. Cursor is newly background-eligible per its docs (inline->background) — a documentation-justified behavior change. No RUNTIME-name residue for this decision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1708): backgroundDispatch citations in matrix + CONTEXT note
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1708): address review findings on typed dispatch-flatten
Code/adversarial review: convert the manager.md/autonomous.md Compound Action preamble from hardcoded 'On Codex' to FLATTEN-based branching (the handlers already use the query; the preamble contradicted them and was wrong for cursor); make shouldFlattenDispatch null-safe + type-honest (accepts raw 'undocumented' registry values); make backgroundDispatch a required descriptor field (matching its siblings, all 16 carry it); strengthen the config.runtime behavioral test; update the bug-853 prose-pin test + comment. Security review clean; Codex confirmed no fail-open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1708): backfill backgroundDispatch in role:runtime test fixtures
Making backgroundDispatch a required descriptor field broke role:runtime fixtures in capability-manifest-version/capability-registry/host-integration-descriptors tests that build a dispatch object without it (caught by full gsd-test, not scoped npm test). Backfill backgroundDispatch:false into the well-formed fixtures; the deliberately-malformed 'required-field' test fixture is left malformed by design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1708): update fix-1521 dispatch-gating assertion to the FLATTEN gate
fix-1521 pinned the codex-specific run_in_background prose that #1708 graduated to the typed dispatch-should-flatten/FLATTEN gate. Update its assertions to verify FLATTEN=false gating (not a runtime name) + that the old RUNTIME===codex gate is gone. Caught by full gsd-test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1708): add changeset for typed dispatch-flatten
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#1708): remove stray temp PR-body file
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1708): add issue ref to bug-853 allow-test-rule annotations
ADR-456 requires every allow-test-rule exemption to carry a see #NNN reference; the source-text-is-the-product annotations added when migrating the prose assertions lacked it (lint-tests CI gate). Add (see #1708).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1679): confine install writes within configHome
ADR-1239 Phase B write-confinement: a pure assertDestWithinConfigHome(configDir, destSubpath) rejects a destSubpath that escapes configHome (path traversal / NUL byte) at plan-build time on BOTH the install and uninstall plan paths; surface.applySurface and installOpencodeFamilySkills route through it, and _copyStaged carries a defense-in-depth containment check. Security-load-bearing for the Phase C third-party-descriptor loader.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1704): add changeset for destSubpath write-confinement
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1704): fix windows path-portability in confinement test
The N1 'accepts a true child subpath' assertion compared against path.join (no drive resolution) while the helper uses path.resolve — on Windows that mismatches the C: drive prefix. Compute the expected via path.resolve to mirror the helper. Windows-CI-only failure (local gsd-test is Mac+Linux).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1684): add negotiated host-integration interface module
ADR-1239 Phase A: a pure, additive, no-I/O module exposing PROTOCOL_VERSION, the 8-axis HOST_INTEGRATION_AXES closed vocabulary, the UNDOCUMENTED fail-closed sentinel, negotiateHostCapabilities (effective subset of host-declared and engine-known), a typed degradation ladder, and host-capability profiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1684): validate and document host-integration axes (16 runtimes)
Extend validateRuntimeBody to validate the 8 hostIntegration axes (closed enums + undocumented sentinel + dispatch struct + reserved-key guards) and the widened runtime vocabulary; author a documentation-sourced hostIntegration block in all 16 runtime descriptors; regenerate the registry. Every per-CLI value is documented (cited) or the explicit undocumented sentinel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1684): add host-integration capability matrix and adr amendment
New per-CLI, per-axis citation reference (value/source/evidence for all 16 CLIs); ADR-1239 Phase-A-implemented amendment; CONTEXT.md glossary seam entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1684): harden dispatch negotiation edge cases
Code-review hardening: treat NaN/Infinity maxDepth as missing (fail-closed, +warning); reset nested/background when namedDispatch collapses to false (struct consistency); SAFE_DEFAULTS dispatch floor to read-only; warn on non-finite protocolVersion; symmetric undocumented warnings for dispatch fields. Pure module — no consumers; behaviour fail-closed throughout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#1684): register host-integration.cjs in lint-ignore and inventory
New tsc-generated bin/lib artifact: add to the eslint ignore list (ADR-457 — lint the .cts source), regenerate docs/INVENTORY-MANIFEST.json, and add the docs/INVENTORY.md CLI-modules row. Fixes the 3 gsd-test failures (551-eslint-bin-lib-coverage x2 + inventory-manifest-sync).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1684): add changeset fragment for host-integration interface
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1684): add how-to for sourcing a host's integration axes
Diataxis how-to guide for adding/updating a host's runtime.hostIntegration axes from authoritative docs, the undocumented-sentinel rule, validation, and extending the closed vocabulary. Completes the Step-5 doc quadrants (reference + explanation + how-to). Indexed in docs/README.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1650): fix stale opencode install-path claim in core settings
* feat(#1688): warn on stale model bake for static-frontmatter runtimes
* chore(#1688): backfill changeset pr field with real PR number
* test(#1688): make resolveAgentDir assertions use path.join for windows
* docs(#1688): codify windows path-literal-in-assert anti-pattern + align test
* fix(#1634): honor capability hook matcher and node-prefix command
Capability hook install (applyCapabilitySharedEdits) wrote each settings.json
hook entry with no `matcher`, so a tool-scoped hook fired on every tool (a
fail-closed guard could then block the whole session), and emitted a bare
single-quoted script path so a .js-family hook from a git/tarball source
without +x failed with Permission denied on every matching call.
- Pass through an optional declared `matcher` (entry-level sibling of `hooks`);
absent => omitted (match-all), so existing shipped capabilities are unchanged.
- Validate `matcher` in the declaration (non-empty string, no control chars).
- Emit `node <quoted-path>` for .js/.cjs/.mjs hooks (mirrors first-party);
.sh and others keep the bare quoted path (unchanged).
Root cause: the manifest hook schema (validator rule C4) was {event, script}
only with no matcher, and applyCapabilitySharedEdits never read or wrote one;
the command used shellSingleQuote(absScript) with no node prefix.
Regression tests fail-first on both defects (matcher dropped; bare path) and
pass after the fix; #1460 command assertions updated for the node prefix.
* chore(#1634): backfill changeset pr:1638
* fix(#1634): resolve lint and windows CI failures
- validator: replace the control-character range regex with a char-code loop.
The literal /[\x00-\x1f\x7f]/ tripped ESLint's no-control-regex rule; char
codes are equally precise and lint-clean. Behavior unchanged (still rejects
matchers containing ASCII control characters incl. DEL).
- test: gate the executable-bit precondition on POSIX. Windows fs does not
honor POSIX write modes (a 0o644 write reads back as 0o666), so the
precondition is meaningless there and failed the windows-latest lane. The
node-prefix assertion — the actual fix — is platform-independent and still
runs everywhere.
* docs(#1634): amend ADR-894 for optional lifecycle hook matcher
The `role: "feature"` `hooks[]` entry now carries an optional `matcher`
(settings.json tool-scoping pattern: exact/pipe/wildcard/regex). Document
the field in the §2 schema table and record a Grilling-amendments entry:
the install path projects a declared matcher onto the emitted settings.json
hook entry (absent = match-all, so shipped capabilities are unchanged), and
per-runtime matcher projection (ADR-857 D8) stays a separate concern. This
amendment ships with the fix that introduced the field rather than as a
follow-up.
* docs(#1634): record WINDOWS-POSIX-MODE-BIT-ASSERT defect in CONTEXT.md
Capture the CI failure pattern from #1634/PR #1638 so it is not repeated: a
test that writes a file with a POSIX mode and then asserts statSync().mode
& 0o777 === <octal> passes on macOS/Linux but fails on windows-latest
(Windows fs does not honor POSIX write modes — reads back 0o666). Added as a
machine-greppable DEFECT predicate (symptom/examples/detect/fix-forward/
prevention) next to DEFECT.WINDOWS-TEST-PORTABILITY, with the fix-forward:
gate the mode-bit precondition on process.platform !== 'win32' and keep the
platform-independent behavioral assertion running everywhere.
Add a non-blocking, warn-only codebase-drift gate at plan:pre so a stale
STRUCTURE.md is surfaced before /gsd:plan-phase spawns the planner, instead
of being discovered mid-execution by the existing execute:wave:post gate.
Gated on a dedicated workflow.plan_drift_precheck toggle (default true),
independent of schema_drift_gate. Never blocks planning, never spawns the
mapper agent at plan time.
Review feedback (#1595):
- Use a documented conventional-commit type (feat, not enhance) per
CONTRIBUTING.md / gsd-validate-commit.sh.
- Normalize the plan_drift_precheck command references to the colon prose
form (/gsd:plan-phase, /gsd:map-codebase) to match plan-phase.md §5.65;
registry regenerated from capability.json.
- Make the test temp dirs hermetic: drain mkdtemp dirs in an after() hook
via the helpers.cleanup() budget (local/no-raw-rmsync-in-tests-compliant).
Closes#1592
Claude-Session: https://claude.ai/code/session_016JBiXEAofvB3prJim29XMS