- 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.
The compiled gsd-core/bin/lib/*.cjs modules are gitignored build artifacts
(ADR-457) shipped prebuilt in the npm tarball. A Claude Code plugin-marketplace
/ git-clone install materializes the repo tree directly and never runs
`npm run build:lib`, so those ~148 files are absent and every CLI command dies
at load with `Cannot find module './lib/cli-exit.cjs'`.
gsd-tools.cjs now calls ensureRuntimeBuild() before its ./lib requires: a new
committed bin helper that compiles the tree once on demand (lock-guarded,
incremental-cache-cleared, portable `node <tsc>` invocation) when the sentinel
cli-exit.cjs is missing, and surfaces an actionable error when TypeScript is
unavailable. The already-built npm path is a single fs.existsSync no-op.
ADR-457 (gitignored, build-at-publish) is preserved; no release-pipeline change.
Verified against a simulated marketplace checkout (148 build outputs removed):
the CLI self-heals and the result is byte-identical to `npm run build:lib`.
Closes#2002
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#2012): scope Progress-row regex to ## Progress section (was binding to earlier table)
The Progress-row writer used a non-global regex that matched ANY table row
starting with the phase number. When an earlier table (e.g. Requirements
coverage | Phase | Requirements | Count |) preceded ## Progress, the regex
bound to the wrong row (3-column), no-op'd, and never reached the real
Progress row. roadmap_updated stayed true (it's existsSync), masking the
failure.
- src/phase.cts: scope the tableRowPattern regex to the ## Progress section
(indexOf + slice) so it only matches Progress-table rows.
- tests/phase.test.cjs: regression test — ROADMAP with a phase-numbered
Requirements table before ## Progress → Progress row updated, Requirements
row untouched.
Closes#2012
* docs(#2012): backfill changeset pr 2032
* fix(#2018): applySurface empty manifest no longer deletes gsd-* agents
The agent-prune loop in _syncGsdDir deleted any gsd-*.md not in the staged
set. When the manifest resolved empty (null/non-object, no entries, no files
key, or unresolvable install source root), the staged set was empty → every
gsd-* agent was deleted. Skills were guarded by pruneSkillDirs' manifest-
membership check; agents had no equivalent.
- src/surface.cts: skip the agent-prune loop when the manifest is empty/absent
(copy still runs — genuinely new agents are added).
- tests/surface-empty-manifest-agents.test.cjs: boundary matrix — empty
manifest preserves agents (Map() + undefined); non-empty manifest + empty
staged still prunes (boundary); empty manifest + new staged copies new +
preserves existing.
Closes#2018
* docs(#2018): backfill changeset pr 2031
* fix(#2017: grant mcp__plugin_context7_context7__* for plugin-marketplace context7
The 8 context7-using agents granted only mcp__context7__* (standalone server
form). Claude Code's plugin-marketplace context7 install names tools
mcp__plugin_context7_context7__*, so the grant never matched and every
researcher/planner/executor silently lost doc lookup (fell back to WebSearch).
- 8 agents: add mcp__plugin_context7_context7__* alongside mcp__context7__*.
- scripts/research-profiles.cjs: update the researcher profile tools to match.
- tests/context7-plugin-grant-parity.test.cjs: regression guard — no agent
grants the standalone form without the plugin form.
Closes#2017
* docs(#2017): backfill changeset pr 2029
* fix(#2022): gate roadmap update-plan-progress checkbox on verification passed
cmdRoadmapUpdatePlanProgress stamped the phase checkbox + completion date
the moment all summaries landed, with NO verification gate — unlike
cmdPhaseComplete (phase.cts:1436) which checks readVerificationStatus. Since
update-plan-progress is called after every wave and every plan, the checkbox
fired before gsd-verifier confirmed the phase.
- src/roadmap.cts: isComplete now requires summaryCount >= planCount AND
readVerificationStatus(phaseDir).status === 'passed'.
- tests/roadmap.test.cjs: 2 regression tests (no VERIFICATION.md → not
complete; gaps_found → not complete) + updated 3 existing complete tests
to include a passed VERIFICATION.md.
Closes#2022
* docs(#2022): backfill changeset pr 2030
* fix(#2020): remove dead SDK file refs that triggered infinite find.exe on Windows
gsd-executor.md referenced sdk/src/query/QUERY-HANDLERS.md and reapply-patches.md
referenced sdk/dist/cli.js — both retired with the SDK (ADR-0174). AI runtimes
that locate doc refs via filesystem search ran find /, which on Git Bash for
Windows traverses the whole drive (14h+, orphaned find.exe, 4M+ handles).
- agents/gsd-executor.md: drop dead QUERY-HANDLERS.md ref.
- workflows/reapply-patches.md: drop dead sdk/dist/cli.js clause.
- tests/no-dead-sdk-refs.test.cjs: regression guard — no sdk/src|dist|handlers
file refs in agents/workflows/references markdown.
Closes#2020
* docs(#2020): backfill changeset pr 2027
* fix(#2019): planning-config.md global-learnings path ~/.gsd/learnings → ~/.gsd/knowledge
The features.global_learnings row pointed at ~/.gsd/learnings/ but the
implementation (src/learnings.cts, execute-phase.md) uses ~/.gsd/knowledge/.
Docs now match the code.
Closes#2019
* docs(#2019): backfill changeset pr 2026
* fix(#1921): preserve verify-work state across gap-closure + defer follow-ups
Resuming /gsd:verify-work after /gsd:execute-phase --gaps-only lost the
verification state: UAT ## Gaps still read status: failed even after their
fix plans executed, so verify-work re-diagnosed them as fresh blockers,
spawned a new gap plan, and reported only the new plan verified. A new
state contract links each gap to its fix plan so fixed gaps are recognized.
- verify-work.md gap YAML: stable gap_id (G-{phase}-{N}) per gap.
- plan_gap_closure planner prompt: each *-PLAN.md tags the gap_ids it
addresses in its frontmatter (gap_closure: true, gap_ids: [...]).
- new reconcile_gaps step (run at resume_from_file entry): marks a gap
status: resolved when its plan has a matching *-SUMMARY.md — fixed gaps
are not re-diagnosed and do not spawn new gap plans; a re-reported break
is treated as a fresh regression with a new gap_id.
- deferred-follow-up branch: a future-work idea (signals: 'later',
'next version', 'out of scope', ...) is captured to UAT ## Deferred
Follow-Ups instead of becoming a blocking gap/plan.
- workflow-size baseline recaptured (verify-work LARGE tier, 38221/61440).
Closes#1921
* docs(#1921): backfill changeset pr 2025
* fix(#1921): balance <step> tags in verify-work.md via 4-backtick outer fence
The plan_gap_closure step nested a ```yaml example inside a ``` block;
same-length fences made stripFencedCode close the outer block early,
swallowing the step's </step> (14 opens / 13 closes). Widen the outer
fence to 4 backticks so the nested yaml is contained. Regenerate golden
fixtures + size baselines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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>
* feat(#1825): configurable graphify graph location (graphify.graph_path)
Add a graphify.graph_path config key (.planning/config.json) that overrides
where /gsd-graphify query|status|diff read the knowledge graph, so one curated
umbrella-level cross-repo graph can serve multiple sibling projects without N
drifting ~5 MB mirror copies. Previously the graph location was hardcoded to
<cwd>/.planning/graphs/.
- src/graphify.cts: resolveGraphLocation(cwd, planningDir) honors the key
(resolved relative to project root; absolute paths honored via path.resolve);
falls back to the historical .planning/graphs/graph.json when unset/blank/
non-string (byte-identical). Wired into graphifyQuery, graphifyStatus,
graphifyDiff (snapshot travels with the configured graph via dirname), and
writeSnapshot. Configured-but-missing -> actionable error naming the path.
Build stays project-scoped (skill hardcodes the cp dest); umbrella graph is
built in the umbrella project, sub-projects only READ it.
- config-schema.manifest.json: register graphify.graph_path in validKeys.
- tests/graphify-graph-path.test.cjs: boundary matrix (unset byte-identical,
set+present reads configured graph not default, set+missing actionable error,
relative resolved vs project root, blank treated as unset, snapshot alongside
configured graph, diff from configured dir, build project-scoped) +
VALID_CONFIG_KEYS registration.
- docs: CONFIGURATION.md row, FEATURES.md REQ-GRAPH-06, CONTEXT.md module note,
.changeset (Added).
Closes#1825
* docs(#1825): backfill changeset pr number 2013
* fix(#1864): balance <step> tags in settings-advanced.md §8 Model Policy
§8 Model Policy ended with </step> but had no matching opening tag (5 opens /
6 closes), leaving it as loose inter-step content. Add the missing
<step name="model_policy"> opener so the section is a proper step.
- gsd-core/workflows/settings-advanced.md: add <step name="model_policy">
- tests/workflow-step-tag-balance.test.cjs: regression guard — every top-level
workflow must have balanced <step>/</step> (fenced code stripped), plus a
focused assertion that §8 is wrapped in model_policy.
- goldens + workflow-size baseline recaptured.
Closes#1864
* docs(#1864): backfill changeset pr 2014
* fix(#1865): runtime launcher honors CLAUDE_CONFIG_DIR
The gsd_run preamble resolved the Claude global install only at
$HOME/.claude/gsd-core/bin/, but the installer honors CLAUDE_CONFIG_DIR —
so a global install redirected via CLAUDE_CONFIG_DIR was invisible to every
gsd_run call (every command failed with 'gsd-tools.cjs not found').
The Claude resolver arm now uses ${CLAUDE_CONFIG_DIR:-$HOME/.claude},
matching the installer + the other runtimes' ${VAR:-default} pattern.
Default $HOME/.claude behavior is unchanged.
- _runtime-launcher.snippet.sh: Claude arm honors CLAUDE_CONFIG_DIR.
- sync-runtime-launcher.cjs re-run: 95 workflows/agents re-synced.
- review.md / discuss-phase.md: trimmed to stay under their byte budgets.
- runtime-launcher-parity.test.cjs: (A) substring updated for the new form
+ explicit #1865 assertion that the snippet honors CLAUDE_CONFIG_DIR.
- goldens + size baselines recaptured.
Closes#1865
* docs(#1865): backfill changeset pr 2024
* fix(#1936): reconstruct OpenCode review from JSON events; diagnosable empty-output stub
On a large review prompt, OpenCode's default `build` agent runs a few read
tool calls then ends its turn with zero output tokens (reason:"stop",
output:0), so `opencode run --format default` emits empty stdout. The reviewer
block redirected stderr to /dev/null and wrote a generic "failed or returned
empty output" stub — so the phase silently lost its second independent reviewer
with no diagnostic and no timeout.
Rewrite the OpenCode reviewer block to invoke `--format json` as the primary
call and reconstruct the review from the assistant `text` parts (jq). Capture
stderr to a `.err` sidecar (mirrors the Codex block). When the agent emits no
text, surface the stop reason, output-token count, and stderr so the failure is
diagnosable. Gate the stub on the extracted CONTENT, not the output file size —
an empty jq extraction still prints a lone newline that a `[ -s file ]` check
would treat as populated. Document the wall-clock timeout as a Bash-tool param
(macOS lacks GNU timeout; opencode has no native timeout flag).
review.md was already at the DEFAULT size-tier ceiling (40956/40960), so the
fix cannot fit without reclassifying it into the LARGE tier (it is a
multi-reviewer orchestration file that outgrew "focused single-purpose"; 43.4 KB
sits well under the LARGE high-water mark). Recapture the 16 golden-install
fixtures — the diff is exactly one review.md hash per runtime. Regression block
folded into review-default-reviewers-workflow.test.cjs (new bug-NNNN test files
are not accepted).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#1936): add changeset
* test(#1936): property-test the OpenCode review jq reconstruction
Address the re-review's one actionable finding: the jq JSON-event → text
reconstruction had no fast-check property test.
Add tests/opencode-review-reconstruction.property.test.cjs. It extracts the two
shipped jq programs (OPENCODE_REVIEW, OPENCODE_DIAG) verbatim from
gsd-core/workflows/review.md and runs the real jq — not a reimplementation — so
the shipped logic is what gets tested. Properties: the reconstructed review
equals the newline-join of every assistant text part (order preserved); a stream
with no text part reconstructs to empty (drives the #1936 stub); null/absent text
parts are dropped, never rendered as "null". Plus example-based coverage of the
diagnostic edges the reviewer cited: missing .tokens.output and no step_finish
degrade to "?"; non-JSON stdout makes jq fail rather than masquerade as a review.
Verified the invariant has teeth (a comma-join jq fails the property).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#1936): skip jq reconstruction property test when jq is absent
The property test shells out to `jq`, which GitHub's windows-latest runners do
not ship (macOS/Linux runners do). `execFileSync('jq')` therefore ENOENT-failed
the whole file on `test (windows-latest, *)`. Probe `jq --version` at load and
skip the suite when jq is not on PATH — the reconstruction logic is
platform-independent, so the assertions still run in full on every jq-present
runner (mirrors how golden-install-parity skips on win32).
Verified: jq present → 7 pass; jq removed from PATH → 7 skipped, 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(#1936): skip jq reconstruction property test on Windows, not just when jq is absent
The prior guard skipped only when `jq` was absent from PATH — but the
windows-latest runners DO ship jq, so the suite still ran there and failed with
`jq: parse error: Invalid numeric literal` (confirmed from the CI job log). Root
cause is Node's child_process argument quoting mangling the jq program (it embeds
double quotes) on Windows, not the shipped review.md logic — the macOS/Linux legs
pass. Gate the suite on `process.platform === 'win32'` (still also skipping when
jq is absent), mirroring golden-install-parity's win32 skip. Logic is
platform-independent and fully asserted on every macOS/Linux CI leg.
Verified: macOS → 7 pass; simulated win32 → skips.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <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.
* fix(#1993): milestone --ws requirements archive header points at workstream path
The requirements archive header hardcoded the root .planning/REQUIREMENTS.md
path, so a workstream (--ws) archive pointed readers at the wrong file even
though #1917 fixed the archive LOCATIONS to land inside the workstream.
Derive the display path from the same workstream-aware reqPath the writer
already uses (path.relative(cwd, reqPath)). Root behavior is byte-identical
('.planning/REQUIREMENTS.md'); the --ws case now correctly reads
'.planning/workstreams/<ws>/REQUIREMENTS.md'.
- src/milestone.cts: reqDisplay interpolation in the archive header.
- tests/milestone.test.cjs: #1993 regression in the #1911 --ws block — header
references the workstream path, not the root literal.
Closes#1993
* docs(#1993): backfill changeset pr 2015
* fix(#1993): use posix separators in archive header (Windows CI) + CRLF-safe test split
- src/milestone.cts: normalize path.relative output to POSIX separators so
the workstream archive header renders forward slashes on Windows too
(path.relative yields backslashes there; the original literal was posix).
- tests/milestone.test.cjs: .split(/\r?\n/) for the CRLF-fragile lint rule.
* fix(#1988): exclude stray non-plan *-SUMMARY.md from phase completion count
Stray remediation/gap-closure summaries (30-FIX-CR02-SUMMARY.md,
30-GAPCLOSURE-SUMMARY.md, …) inflated summary_count, and once
summary_count >= plan_count the phase silently flipped to Complete even
though several plans had no summary. A summary now counts toward completion
only if it pairs with a real plan file.
- core-utils.cts: new countMatchedSummaries(planFiles, summaryFiles) —
layout-agnostic pairing via the PLAN→SUMMARY marker swap (root/nested/bare)
plus the <stem>-SUMMARY.md form (bare PLAN.md↔PLAN-SUMMARY.md); the swap is
applied to the basename only so a 'plans/' dir prefix isn't corrupted.
- plan-scan.cts: scanPhasePlans.summaryCount/.completed use the matched count
(summaryFiles array still holds every summary on disk for listing/reading).
Fixes roadmap listing, state sync, verification, workstream inventory.
- roadmap.cts: cmdRoadmapUpdatePlanProgress uses the matched count.
- tests/roadmap.test.cjs: countMatchedSummaries unit tests (root/nested/bare/
stray) + E2E reproducing the exact #1988 report (4 plans, 1 plan summary,
3 strays → 1/4 In Progress, NOT Complete).
Closes#1988
* docs(#1988): backfill changeset pr 2016
* test(#1988): strengthen countMatchedSummaries unit tests for mutation coverage
Add direct unit tests for the extended (N-PLAN-MM-slug↔N-MM-SUMMARY), bare
(PLAN↔SUMMARY, PLAN↔PLAN-SUMMARY), legacy (N-PLAN-NN↔N-PLAN-NN-SUMMARY), and
stray-exclusion pairings so every branch of countMatchedSummaries is exercised
(Stryker mutation-score coverage).
* test(#1988): move countMatchedSummaries unit tests into core-utils.test.cjs
The Stryker core-utils shard runs ONLY tests/core-utils.test.cjs (per
scripts/mutation-matrix.cjs), so the unit tests for countMatchedSummaries
must live there to be mutation-covered (previously in roadmap.test.cjs, the
shard never ran them → mutants survived → Stryker gate failed). The E2E
#1988 reproduction stays in roadmap.test.cjs. Added an absolute-path case to
guard the lastIndexOf('/') >= 0 boundary.
* fix(#1581): config-set no longer silently coerces Infinity/project_code
The value parser used !isNaN(Number(val)), which admits Infinity/-Infinity;
JSON.stringify then renders those as null on disk while the CLI echoed the
non-finite value (output ≠ disk). Leading-zero strings like project_code
'007' were also silently number-coerced to 7.
- config.cts parser: Number.isFinite instead of !isNaN, so Infinity falls
through to the JSON branch (rejected) and stays a string.
- project_code: always persisted as a string (identifier; leading zeros
matter), bypassing number coercion.
- context_window: new per-key validator — must be a finite positive integer
(rejects Infinity/0/negatives/non-integers with a non-zero exit).
- tests/config.test.cjs: #1581 regression (Infinity rejected, 0 rejected,
200000 accepted finite, project_code '007' string-preserved, granularity
numeric coercion unchanged).
Closes#1581
* docs(#1581): backfill changeset pr 2023
* 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>
* fix(#1906): require node-test clean-fixture causation control
The node-test fail-first proof accepted a deceptive content-independent
negative test — one that reds merely because GSD_PROHIB_SUBJECT is set,
ignoring the subject's content — whenever no cleanFixture was supplied,
because #1346's causation control was opt-in. The proof's observed signal
(RED) thus diverged from its target (RED caused by content) by default.
Make the causation control mandatory for the node-test kind: a descriptor
that omits cleanFixture is un-provable (fail-closed), never accepted under
the weaker violation-only proof. When a clean fixture is present, fail-first
is proven exactly as before (RED on violation AND non-vacuous GREEN on clean).
The lint-rule kind is unchanged (its subject IS the linted file; no
GSD_PROHIB_SUBJECT indirection).
Breaking (Hyrum): a previously-green node-test prohibition with no clean
fixture now hard-gates — blast radius is zero in-tree (no node-test
prohibition ships today; only the lint-rule local/no-source-grep dogfood).
Supersedes ADR-1606 Decision 4 / ADR-550 #1346 addendum's opt-in.
Closes#1906
Claude-Session: https://claude.ai/code/session_017vYn26e3nkDNxcpty1ciPJ
* docs(#1906): supersede the #1346 opt-in causation control (mandatory for node-test)
Record the node-test mandatory-causation-control supersede across the
governing surfaces:
- ADR-1606 (the enforcement decision-of-record): addendum + Decision 4
annotated + the "Mandatory causation control — REJECTED" alternative
flipped to accepted (premise no longer holds: zero in-tree node-test
consumers).
- ADR-550: the 2026-06-21 #1346 "Why opt-in, not required" paragraph
marked SUPERSEDED, pointing at ADR-1606.
- spec-phase.md: check_clean_fixture is now REQUIRED for node-test
(was "optional").
- CONTEXT.md: PROHIB.enforce.causation predicate updated.
Regenerated the shipped-artifact cascade from the spec-phase.md edit
(+149 B, well under the 40960 cap): 16 golden-install-parity fixtures
and the workflow size baseline.
Refs #1906
Claude-Session: https://claude.ai/code/session_017vYn26e3nkDNxcpty1ciPJ
* 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.
* docs: design spec for /gsd smart-entry command
Hybrid approach porting gsd-pi's smart-entry wizard to gsd-core:
deterministic classifier (gsd-tools smart-entry --json) + markdown
command/workflow with AskUserQuestion + --text fallback. Routing-first
('what now?' menu), 10 situations redesigned for gsd-core's phase loop.
* feat: add /gsd-start smart-entry command
State-aware front door adapted from gsd-pi's smart-entry wizard,
redesigned for gsd-core's markdown-first, multi-runtime architecture.
- src/smart-entry.cts: deterministic situation classifier (no-project,
paused, blocked, verify-failed, needs-first-phase, planning, executing,
verify-pending, idle-stranded, complete, unknown). Reads STATE.md,
ROADMAP.md, git, and verify signals; emits JSON the workflow consumes.
- gsd-tools.cjs: wire case + help listing.
- commands/gsd/start.md + gsd-core/workflows/gsd.md: thin markdown
dispatcher presenting an AskUserQuestion menu (with --text fallback for
non-Claude runtimes) and dispatching to existing commands. Falls back
to /gsd:progress if detection is unavailable.
- help.md: document /gsd:start (parity with bug-2954).
- tests: smart-entry.unit.test.cjs (classifier behavior across all
situations + priority + JSON shape) and gsd-workflow.structure.test.cjs
(markdown-layer invariants + every emitted command resolves to a real
slash command).
Spec: docs/superpowers/specs/2026-06-27-gsd-smart-entry-design.md
Note: command-contract (ADR-0002) requires a gsd:* prefix, so the bare
/gsd from the spec surfaces as /gsd-start.
* refactor: rename smart-entry command to /gsd:next
Rename the command from /gsd:start to /gsd:next per feedback. The
command file is now commands/gsd/next.md (name: gsd:next) and the
backing workflow is gsd-core/workflows/smart-entry.md (named for the
smart-entry classifier and gsd-tools smart-entry subcommand; does not
collide with the existing workflows/next.md, which is the progress
--next sub-workflow). help.md and the spec updated to match.
All affected tests (188) pass; lint:ci clean.
* fix: smart-entry reads real STATE.md schema (nested progress YAML + body Phase field)
Codex review found the classifier misread this repo's own STATE.md: it
looked only for scalar current_phase/total_phases frontmatter and body
fields named 'Current Phase'/'Total Phases', but real STATE.md stores
the phase as body 'Phase: N' and total_phases/percent under a nested
'progress:' YAML object. Both came back null, so active projects
(e.g. this repo at Phase 3 / verifying) wrongly classified as
needs-first-phase.
- detectSignals now reads total_phases + percent from nested progress{}
first, then scalar fm, then body; current_phase falls back to the
body 'Phase:' field (parseProsePhaseField lineage).
- Add regression tests against the real schema (nested progress YAML +
body Phase field) covering verify-pending + executing situations.
Verified against this repo: now classifies verify-pending (was
needs-first-phase). Coverage 93.25% lines / 86.99% branches.
* fix(workflow): tiered fallback when gsd-tools is broken (not just smart-entry)
Live test exposed a self-defeating fallback: when smart-entry --json
failed because gsd-tools itself was broken (missing
markdown-sectionizer.cjs), the workflow fell back to /gsd:progress —
which also depends on gsd-tools and would dead-end too.
Replace the single /gsd:progress fallback with a tiered recovery:
1. Probe gsd_run state-snapshot. If it ALSO errors, the whole tool
layer is down — read .planning/STATE.md directly with the Read tool
and synthesize a minimal situation + actions menu so /gsd:next stays
useful. Surface a rebuild hint.
2. Only if smart-entry alone is missing (older gsd-core), fall back to
/gsd:progress as before.
Matches the direct-read resilience the live agent already did by hand.
* docs: add gsd-next skill surface
* chore: trigger no-mistakes validation
* no-mistakes(review): Fix smart-entry phase ordering
* no-mistakes(review): Fix decimal smart-entry phase ordering
* no-mistakes(test): Fix smart-entry next test contracts
* no-mistakes(document): Docs synced for smart entry
* chore: add changeset fragment for #1798 (/gsd:next smart-entry workflow)
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
* fix: shorten next.md description and update golden install parity fixtures
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
* fix: update /gsd-next refs to /gsd:next in docs and add Smart Entry topic alias
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
* chore: trigger no-mistakes validation
* fix: regenerate INVENTORY-MANIFEST.json for new /gsd-next files
Full CI caught that adding commands/gsd/next.md + gsd-core/workflows/smart-entry.md
left docs/INVENTORY-MANIFEST.json stale (not in the affected-test scope that
no-mistakes' test gate runs, so it surfaced in CI). Regenerated via
node scripts/gen-inventory-manifest.cjs --write; inventory-manifest-sync
test now passes.
* fix: add 'next' to core_loop cluster, update INVENTORY-MANIFEST, fix gates.md ref
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
* fix: regenerate golden install parity fixtures for /gsd:next
Full CI (shard 3/3) caught that adding commands/gsd/next.md + the
smart-entry workflow/lib made the per-runtime golden install parity
fixtures stale across all 16 runtimes. Regenerated via
UPDATE_GOLDEN=1 node --test tests/golden-install-parity.test.cjs.
All 16 fixtures + inventory-manifest-sync now pass.
* Fix smart-entry verify-failed phase scoping and empty resolve shim step
Scope detectVerifyFailed to STATE.md's current phase so leftover higher
phase directories cannot force verify-failed routing. Move the gsd_run
shim resolver into the workflow resolve step so agents define gsd_run
before the detect step runs smart-entry.
* fix: recapture golden fixtures with updated gates.md hash (/gsd:next)
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
* fix: recapture all 16 golden fixtures with updated smart-entry.md hash
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
* chore: regenerate fixtures + inventory manifest after rebase onto next
Rebased onto next which adopted #1837 (package-version normalization to
<VERSION> in golden-install-parity hashes). Recaptured the golden fixture
that needed it (hermes), re-sorted INVENTORY-MANIFEST.json, and regenerated
the gsd-next / ns-workflow skill descriptions to match the command surface.
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
* refactor(#1787): delegate /gsd:next in-project advancement to gated /gsd:progress --next
Reconciles the /gsd:next smart-entry front door with the existing
/gsd:progress --next engine (davesienkowski review on PR #1798). The
classifier previously recommended /gsd:execute-phase directly for the
`executing` situation, bypassing workflows/next.md Route 0
(resume-incomplete-phase invariant, #160) and Gates 1-3 — reproducing the
duplication that got the old flat /gsd-next removed (#3054), plus a
correctness hazard (executing the recorded current phase while an earlier
phase is silently incomplete).
Now planning/executing/verify-pending recommend `/gsd:progress --next`
(single gated engine); the specific command stays an explicit secondary.
Off-path states (no-project, paused, blocked, verify-failed,
idle-stranded, complete) keep direct recommendations — smart-entry's
distinct value over --next. Adds docs/adr/1787-gsd-next-smart-entry.md and
a regression test locking the delegation contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1787): avoid literal /gsd-next token in ADR (bug-3054 guard)
The repo-invariants #3054 guard bans the removed /gsd-next slash form in
docs surfaces. Refer to the removed command as `gsd-next` (prose) — the
historical reference is unchanged, just the banned token is dropped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: gitignore compiled host-integration-sdk + handshake-serialized .cjs
Pre-existing gap from #1683: these two src/*.cts modules compile to
gsd-core/bin/lib/*.cjs but were omitted from the per-file ignore list, so
`npm run build`/`npm test` left them as untracked build artifacts (dirty
tree + accidental-commit footgun). Adds them alongside their siblings
(host-integration.cjs, mcp-server.cjs, …). Found while finishing #1798.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#1787): lock per-situation action invariants for all 11 situations + ADR typo
Adversarial-review follow-ups:
- Add a test asserting every situation's action set has exactly one
recommended action, 1-4 unique-id /gsd:* actions (previously the
one-recommended/1-4 invariant was only sampled for 6 of 11 situations).
- Fix ADR typo: /gsd-progress → /gsd:progress.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1798): split oversized test chunks so a slow shard can't trip the per-chunk timeout
Root-cause of the intermittent `full test (windows-latest, 22, shard 1/3)`
failure. It was NOT a leaked handle (the runner's kill message guesses that,
but --test-force-exit already exits leaks cleanly). Diagnosis:
- Ran every shard-1/3 file WITHOUT --test-force-exit + a 45s kill-timer:
zero hangs, zero leaks — every file self-exits. So no leaked handle / hang.
- CI activity profile: output kept flowing (slowly) right up to the 600.0s
kill — a dead hang would go silent. => pure slowness.
- Per-file timing: install-minimal-hooks.test.cjs is a 4987-line / 250-case
consolidation file doing dozens of real installs — 41s even on a fast Mac
(much worse on the slow Windows I/O path), plus an install-heavy cluster.
Mechanism: MAX_FILES_PER_CHUNK=180 packed the whole ~171-file shard into ONE
`node --test` chunk, so the entire shard's wall-clock ran against a single
600s per-chunk backstop. On slow Windows runners that single chunk crossed
600s and was killed mid-run — an intermittent false-negative gate that also
hits `next` directly.
Fix: lower MAX_FILES_PER_CHUNK 180 -> 90 so each shard splits into ~2 chunks,
each with its own fresh 600s budget and a fresh node process (also relieves
per-process memory pressure). Verified locally: shard 1/3 now runs as
chunk 1/2 (90 files) + chunk 2/2 (81 files), 5323 tests, 0 fail. Also made the
timeout kill-message name slowness as a cause instead of asserting a leak, so
the next debugger isn't sent hunting a nonexistent handle leak.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* enhance(#1872): document Claude Code advisor inheritance in model profiles
Add an "Advisor Tool (Claude Code)" section to
gsd-core/references/model-profiles.md: session-level advisor is inherited
by all GSD subagents and composes with the per-agent profile/tier system,
candidate executor/advisor pairings per profile (cost/quality/caching
claims attributed to Anthropic's advisor-tool docs, not asserted as GSD
behavior), when it is worth enabling vs not, and the session-level /
no-per-agent-control constraint linking anthropics/claude-code#73072.
Docs-only. Golden-install-parity fixtures recaptured for the edited
reference file (hash-only, one line per runtime).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#1872): add changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#1872): use Documentation changeset type for docs-only change
Changeset type was `Changed`, which triggers the docs-required lint
(TRIGGERING_TYPES in scripts/lint-docs-required.cjs). This PR only
touches gsd-core/references/model-profiles.md, so there is no docs/
file to pair with and docs-lint failed. `Documentation` is the correct
type for a docs-only enhancement and is exempt from the trigger.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#1872): put docs-exempt marker on its own line, revert to type Changed
The prior fix (type: Documentation) was invalid — parse.cjs ALLOWED_TYPES
is {Added, Changed, Deprecated, Removed, Fixed, Security}, so both
changeset-lint and docs-lint failed with invalid_type.
Real root cause of the original docs-lint failure: DOCS_EXEMPT_RE is
anchored to match the `<!-- docs-exempt: ... -->` marker only on its own
line, but the marker was tacked onto the end of the prose line, so it was
never captured (docsExempt: null) and the triggering `Changed` fragment
had no docs/ pairing -> fail_docs_missing.
Fix: keep the valid `type: Changed` and move the marker to its own line.
Verified locally: changeset-lint -> ok_fragment_present,
docs-lint -> ok (own-line marker parses to ok_fragments_exempt).
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>
* refactor(#1852): lazy-split plan-phase.md into steps/
Extract 3 self-contained, rarely-hit sections into gsd-core/workflows/plan-phase/steps/
via lazy 'Read and execute' pointers (mirrors execute-phase/steps/, ADR-1610 progressive
disclosure — no eager @-import): closed-phase-gate (1.5), prd-express-path (3.5),
windows-troubleshooting. Byte-invariant: plan-phase.md 94459 -> 89775 (-4684), each step
< 32 KiB anchor, resolved instructions unchanged.
Scope note: only 3 sections were extractable. plan-phase.md is guarded by a dense net of
content-presence tests (plan-bounce/enh-3209/phase6-planning-capabilities assert specific
sections/flags inline) that block extracting the larger blocks without also refactoring
those tests — durable low-80s headroom is deferred pending maintainer re-scope (issue #1852).
Cascade: size:baseline regen, 16 golden-install-parity fixtures regen, INVENTORY in sync.
Full plan-phase test surface green (4129/4130, 0 fail); lint:ci exit 0.
* docs(changeset): Changed fragment for #1934 (plan-phase lazy-split)
* docs(changeset): mark #1934 fragment docs-exempt (internal workflow refactor)
* fix(#1852): add gsd_run launcher preamble to prd-express-path step
The extracted prd-express-path.md calls gsd_run but the canonical launcher
preamble lived in the parent plan-phase.md — runtime-launcher-parity (#373)
walks workflows/ recursively and requires every .md using gsd_run to carry
exactly one preamble + the $HOME/.claude fallback arm (same as the existing
execute-phase/steps/ files). Injected via scripts/sync-runtime-launcher.cjs;
golden fixtures + size baseline regenerated. plan-phase.md unchanged (89775).
---------
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
* feat(#1942): VS Code extension — repo-local IDE host with reachability proof
The VS Code extension (vscode/extension.js + vscode/package.json) is a
repo-local, buildable extension (not Marketplace-published). The gsd.invoke
handler dispatches in-process through the GSD command-routing hub. The handler
is exported separately from activate() so it is testable without a VS Code host.
tests/vscode-extension-reachability.test.cjs: proves the handler dispatches
through the hub + returns a result (keystone wired); the manifest declares the
command + engine; resolveEngineRoot finds the gsd-core/ dir.
* docs(changeset): VS Code extension (#1966)
* fix(#1942): register vscode/package.json in VERSIONED_MANIFESTS (#844)
* fix(#1942): align vscode/package.json version with package.json (1.7.0-rc.1)
* fix(#1967): build hooks/dist once upfront in run-tests to close scoped-CI empty-dir race
hooks/dist/ is gitignored and not built by prepare (build:lib only), so the
scoped CI lane starts with it absent. The first install test's before() hook
triggers build-hooks.js, which creates DIST_DIR empty then fills it file-by-
file; a concurrently-spawned install.js reader can observe the empty window and
fail with 'Failed to install hooks: directory is empty' (intermittently failing
e.g. bug-3683-workflow-colon-namespace-leak on scoped legs).
Add ensureBuiltHooks() to scripts/run-tests.cjs — the same upfront chokepoint as
ensureBuiltArtifacts — to build hooks/dist once, single-process, before any
concurrent test spawns install.js. Completeness is checked against
build-hooks.js HOOKS_TO_COPY (absent/empty/partial/zero-byte -> rebuild; complete
-> no-op). Folds regression coverage into bug-969-test-infra-flake-hardening
(Part C), proven fail-first (ensureBuiltHooks undefined on next).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#1967): set changeset pr to 1968
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1944): pi extension — installable ExtensionAPI host-plugin with reachability proof
The pi extension (pi/gsd.cjs) is a real, jiti-loadable ExtensionAPI module that
registers /gsd (dispatches through the GSD command-routing hub) + gsd_invoke tool
+ tool_call event. Engine entry: in-process CJS require (Bun-compatible). The
extension resolves the engine tree via walk-up (like the OpenCode plugin).
tests/pi-extension-reachability.test.cjs proves the /gsd handler DISPATCHES
through the hub (returns a JSON result) — the keystone-wired proof that the
command is user-reachable, not just registered on a mock (#1944 AC).
* docs(changeset): pi extension (#1965)
* fix(#1944): add docs-exempt marker to changeset (no standalone docs file)
The flattened install layout broke the third-party capability ecosystem in two
ways. Both are fixed at the host-version / installer boundary.
Gap 1 — host version read as 0.0.0. The running GSD version was resolved via
require('../../../package.json') (loader/source) and require('../../package.json')
(the gsd-tools CLI), which in the installed layout is the versionless CommonJS
marker → the fail-closed fallback reported 0.0.0, so `capability install` rejected
any manifest with a real engines.gsd range as "incompatible with GSD 0.0.0". For
runtimes that get no marker, and for local installs, that walked-up package.json
could even be the USER's own project, reporting a wrong version. Fix:
readHostVersion() (capability-loader.cts, capability-source.cts) and capHostVersion()
(gsd-tools.cjs) now prefer the authoritative gsd-core/VERSION the installer already
writes for EVERY runtime (mirrors resolveVersionFrom(), #1383), falling back to the
runtime-root package.json for the dev/source tree, then fail-closing. This fixes
every runtime and the actual `capability install` CLI path without touching the
marker package.json, so uninstall is unchanged (no data-loss surface).
Gap 2 — scripts/gen-capability-registry.cjs (+ its sibling
gen-loop-host-contract.cjs) were never copied by the installer, so the loader's
never-crash invariant discarded every overlay and fell back to the frozen
first-party registry (installed capabilities silently inert). Now copied,
uninstalled, and manifest-tracked exactly like fix-slash-commands.cjs (#1223).
Regenerates the 16 golden-install-parity fixtures to capture the two newly shipped
generator scripts and the gsd-tools.cjs change.
Regression tests (RED→GREEN): readHostVersion VERSION-first / fallback / fail-closed
resolution; an end-to-end `capability install` against a REAL installed layout
proving the engines gate sees the real host version, not 0.0.0; and a real-install
check that both generators are shipped and manifest-tracked.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#1682): OpenCode session.idle + opencode-subset dialect + Claude parity — Slice 1b/c
- Plugin (.opencode/plugins/gsd-core.js): handle session.idle (↔ Claude Stop
lifecycle point; no-op sentinel — state already persisted to .planning/).
Completes the compaction/idle pair (#1914 shipped compaction).
- Declare hookEvents: 'opencode-subset' in the OpenCode descriptor — the
reserved dialect now has a real consumer (no longer zero-consumer).
- host-integration.cts: add HOOK_EVENT_SURFACES + hookEventSurfaceFor() — the
pure consumer that resolves a dialect to its host-fireable event surface.
opencode-subset = session/tool/file subset with NO workflow-phase events
(engine owns phase sequencing; ADR-1239 §OpenCode binding).
- Tests: hookEventSurfaceFor unit tests (claude/gemini/opencode-subset/null);
plugin session.idle no-throw; compaction breadcrumb; opencode-subset surface
parity vs the plugin's handlers (Claude parity).
* fix(#1682): don't declare hookEvents on opencode (hooksSurface:none invariant)
The repo invariant couples runtime.hookEvents to the managed settings.json hook
surface: hooksSurface:'none' runtimes (opencode — plugin owns hooks) must NOT
declare hookEvents. Declaring 'opencode-subset' there violated 3 capability-
registry invariants + 2 install-plan golden masters + opencode golden parity.
The opencode-subset dialect is still IMPLEMENTED — just not via the legacy
descriptor field: hookEventSurfaceFor() (host-integration.cts) is its consumer,
and the OpenCode plugin consumes the subset events at runtime (session.idle
added here; compaction shipped in #1914). Declaring it on the descriptor would
require weakening the hooksSurface:none ⇔ no-hookEvents invariant (flagged for
decision).
* fix(#1682): refresh opencode golden parity for plugins/gsd-core.js (session.idle)
* docs(changeset): OpenCode session.idle + opencode-subset dialect (#1682)
* docs(changeset): backfill PR #1930
* feat(#1914): OpenCode native plugin integration (Option 1 file-copy)
Ship a native OpenCode plugin (.opencode/plugins/gsd-core.js) plus the
installer step that delivers it, so GSD's lifecycle hooks run on OpenCode.
OpenCode declares hooksSurface:'none', so GSD's hook scripts already ship to
<configDir>/hooks/ but nothing invokes them; the plugin bridges OpenCode's
event bus onto those scripts as subprocesses (prompt/read/worktree/workflow
guards, injection scanner, context monitor).
Distribution is Option 1 (file copy) per the #1914 triage decision: no
scripts.build rename, no prepare/prepack removal. package.json gains
main + .opencode in files[] for discovery.
Corrected against OpenCode's docs + loader source (not the reference branch):
- Auto-discovery globs {plugin,plugins}/*.{ts,js} — .cjs is never matched, so
the installed adapter must be .js (config dir carries {"type":"commonjs"}).
- No opencode.json plugin-array patch — that array is npm-only; local files
are auto-discovered.
- REPO_ROOT is resolved by walking up to the dir holding hooks/ + gsd-core/,
correct for package tree, global install, and local install.
- Config-hook registration is gated (IS_PACKAGE_TREE) so it never
double-registers commands/agents/skills already delivered by native copy.
Also fixes an incidental .gitignore drift: 8 ADR-1239 .cts-generated .cjs
artifacts were untracked-and-not-ignored (leak risk) — now ignored.
Tests: tests/opencode-plugin-adapter.test.cjs (14, pure helpers + real
subprocess bridge against stub hooks); golden-install-parity regenerated.
Green on Mac + Linux (gsd-test): 0 failures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1914): harden OpenCode plugin export shape + advisory accumulation (adversarial findings)
Address Codex adversarial-review findings:
- HIGH: export `{ id, server }` could trip OpenCode's loader
(`for (entry of Object.values(mod)) getServerPlugin(entry)` throws on a
non-extractable value). Make `id` NON-ENUMERABLE and assign module.exports
from a variable (not a literal) so no string `id` is ever iterated — verified
loader-safe under real import(pathToFileURL) (default + module.exports alias,
both objects with .server; no bare id string).
- MEDIUM: sequential advisory hooks clobbered output.metadata._gsdAdvisory;
now accumulate into an array.
- LOW: resolveRepoRoot fallback returned ".." while the comment said "../.." —
aligned to "../.." (package-tree depth).
Tests: added a faithful loader-loop emulation (raw CJS + ESM namespace views),
advisory-accumulation, and a real bin/install.js copy→manifest→uninstall
integration test. golden regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#1914): add changeset fragment for OpenCode plugin integration (PR #1923)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1914): Windows — assert rewritten path with string include, not path-regex
The Read content-rewrite test built a RegExp from `path.join(root,'gsd-core')`.
On Windows the backslashes in the path are interpreted as regex escapes, so the
assertion never matched and `test (windows-latest, 24)` failed — even though the
adapter rewrote the path correctly. Replace the RegExp with a separator-agnostic
`String.includes` check (the repo's no-path-literal-in-assert concern).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#1863): use named flags for state.* calls in executor + workflows
The named-only state-command router (parseNamedArgs) silently drops
positional args, so state.cjs threw its required-arg error and
metrics/decisions/blockers/session continuity were never recorded.
Convert record-metric / add-decision / add-blocker / record-session in
agents/gsd-executor.md to the named-flag form (mirroring execute-plan.md),
and fix the two remaining positional record-session calls in
gsd-core/workflows/milestone-summary.md and forensics.md. Recapture the
golden-install-parity fixtures and size baselines for the edited files.
Also fix a pre-existing detached-rebuild handle leak in
tests/graphify-auto-update.slow.test.cjs: three dispatch tests returned
after observing only the synchronous "running" status without awaiting the
detached rebuild's terminal state. That leak was latent until the new
#1863 regression block's added runtime shifted --test-force-exit timing
and surfaced it as a non-zero chunk exit. The three tests now await
terminal status via the file's existing waitForBuildStatus helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#1863): add changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
The bug-1367 install test ran install.js without building hooks/dist (a
gitignored build artifact). The unit lane's ensureBuiltArtifacts() builds only
bin/lib, not hooks — so on a lane without pre-built hooks the installer's
verifyInstalled(hooks) found the dir empty and hard-failed 'directory is empty',
throwing in before() → hookFailed → L0-L5 cancelledByParent cascade on the
Windows CI lane.
Build hooks in before() via scripts/build-hooks.js (mirrors golden-install-parity's
BUILD_SCRIPT pattern). Self-contained — no lane-ordering dependency.
Closes#1926
* fix(#1716): route resume_from_file to complete_session when no pending tests remain
When a UAT session has status:partial with blocked_count>0 and pending_count==0 (all remaining tests are blocked, none are pending), resume_from_file found no [pending] test and terminated silently — never routing to complete_session. This blocked the issues==0 auto-transition path even when there were zero code defects.
Guard clause added immediately after the find-pending step: if no [pending] test is found, route to complete_session. complete_session then correctly sets status:partial (because blocked_count>0) without presenting further tests.
Closes#1716
* chore(#1716): add changeset fragment and regenerate golden-install-parity fixtures
Changeset fragment for PR #1722 (type: Fixed).
Golden-install-parity fixtures regenerated for all 16 runtimes — the workflow fix shifts verify-work.md's byte-stable hash in the golden manifest. Regenerated via UPDATE_GOLDEN=1 node --test tests/golden-install-parity.test.cjs.
* fix(#1907): validate per-item shape in isValidReport so adapter garbage fails closed
isValidReport checked only the container (items is-array, coverage scalars), so a report
like {items:[{}]} sailed through runProbeCli and stringified as green output — despite the
docstring promising it 'fails closed on adapter garbage'. Add a per-item Item-contract guard
(requirement_id/category/status + typed nullable fields) so a future adapter that bypasses
the analyzeCoverage merge and returns per-item garbage inside a well-shaped envelope fails
closed (exit 2) instead of emitting it as valid coverage.
analyzeCoverage always emits fully-populated, validated Items, so the 2 shipped adapters are
unaffected (verified across the edge/prohibition suites).
Refs #1907, epic #1904.
* chore(changeset): Fixed fragment for #1910 (isValidReport per-item)
* fix(#1905): normalize hand-authored backstop marker so it can't degrade to green
A hand-authored non-inferable `backstop` truth with a stray trailing space (or
surrounding quotes) silently graded {status:green} instead of abstaining — the exact
#1154 false-pass. `truthVerification` returned null for any value != 'backstop'/'explicit',
and the frontmatter continuation-KV parser preserved the stray whitespace captured inside
the quotes. Normalize the marker before comparison (Postel) AND trim the continuation-KV
value at the parser (durable root cause; also cleans the sibling check_target path).
Regression: a trailing-space/quoted backstop truth now abstains (insufficient_spec),
end-to-end from a hand-authored must_haves.truths block (#1820 rail).
Refs #1905, epic #1904.
* chore(changeset): Fixed fragment for #1909 (backstop marker normalize)
* fix(#1729): resolve phase headers with a pre-colon parenthetical tag
A phase header may carry a parenthetical tag between the number and the
colon, e.g. `### Phase 26 (Cluster B): Title`. Every phase-header regex
built `Phase\s+<num>` immediately against the colon delimiter, so the
tagged phase was invisible: the resolver returned found:false and, just
as bad, the capture-all enumeration/parse paths (roadmap analyze,
milestone listing + milestone-scope filter, verify, init/import, state
total_phases, validate, the command router, preamble stripping, and the
phase-remove renumbering rewrite) silently dropped, miscounted, or
failed to renumber it — wrong phase_count, progress_percent, next_phase,
or corrupt numbering after a removal.
The fix tolerates the tag at the header seam. Parameterized resolver
sites compose the exported OPTIONAL_PHASE_TAG_SOURCE fragment; literal
enumeration sites inline its character-for-character mirror
`(?:\s*\([^)\n]*\))?`, placed immediately before the colon so it cannot
alter an existing match (optional, single-line, one paren pair, no
capture-group shift). In the renumber-on-removal rewrite the tag is
folded into the re-emitted suffix capture so it survives verbatim. Both
forms are documented to change together and a drift-guard test asserts
their behavioral equivalence over a header corpus.
Deliberately excluded: roadmap-upgrade.cts (legacy one-time migration),
where tolerating the tag would silently drop it on header rewrite — that
needs its own data-preserving treatment. Known boundaries left for
follow-up: checklist/bullet-style phase entries (`- [ ] Phase N (tag):`)
and a malformed space-before-colon variant, both pre-existing.
Validated empirically against the issue's reproduction: `roadmap
get-phase 26` resolves and `roadmap analyze` lists Phase 26 with the tag
excluded from the name (phase_count 2, next 26); an all-tagged versioned
roadmap now scopes correctly instead of falling back to a pass-all
filter. Regression coverage in tests/phase.test.cjs asserts resolver
parity (pre- vs post-colon), padding tolerance (#3537), decimal
sub-phases, no cross-phase false match, the shared seam, enumeration
coherence, renumber-preserves-tag, and seam/mirror drift. Full unit
suite green (7291 pass, 0 fail); eslint + regression-name +
resolution-provenance + changeset lints pass. Reviewed by Codex
(no critical/high; the two enumeration misses it surfaced are folded in).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#1729): add changeset for pre-colon phase-tag fix
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>