Phase 2 of #3464, following #3465. Rewrites every assertion that read a
shipped .cjs/.js file and text-searched it, so the file no longer needs an
allow-test-rule exemption. 6 of the 8 files are now marker-free; the ceiling
drops 285 -> 278 against a measured 277.
A source-grep passes when a STRING is present, not when the code WORKS. It
survives a refactor that keeps the string but breaks the behavior, and breaks
on a refactor that keeps the behavior but renames the string. Both failure
modes are silent about the thing the test claims to protect. That is the
anti-pattern ADR-456 and local/no-source-grep exist to prevent.
Rewrites, each against the real exported seam:
- discuss-mode: calls cmdInitPlanPhase() against a fixture whose config sets
workflow.text_mode, asserts the value propagates to its emitted JSON.
- effort-surface-axis: runs review-lane invoke against a real project with a
fake `claude` PATH shim, asserts the resolved --effort actually lands in the
shim's captured argv.
- install-minimal-hooks: calls applySettingsJsonHooks() with a hook source
missing, asserts it is neither registered nor silently registers wholesale,
with sibling present hooks as the positive control.
- install: calls the exported inferPreferredRuntime({fs, env,
preferredConfigDir}) via its injected fs seam, asserting 'kilo' from both
the config-marker and env-var paths.
- opencode-permissions: spawns the real installer with a custom config dir,
asserts the written opencode.json permission paths are anchored on it.
- repo-layout: spawns the installer for copilot local vs global, asserting
AGENTS.md is written only in the local case.
- runtime-config-adapter-registry: stubs resolveInstallPlan and force-reloads
install.js, asserting the runtime's artifact stops being written -- proving
install.js genuinely routes through the registry.
- runtime-homes-descriptor-drive: calls buildAgentSkillsBlock() for cursor and
claude against real fixture SKILL.md files, asserting each runtime's refs
land under its own config dir and never the other's.
Every rewrite was mutation-checked before its marker was dropped. Because
node --test is not runnable locally in this repo, each assertion was
replicated in a standalone probe that requires the same module: run green
against the real file, then red against a deliberately broken one (text_mode
propagation deleted, existsSync guard removed, config dir hardcoded, !isGlobal
guard dropped, kilo branch removed, effort resolution bypassed, skills base
hardcoded back to .claude), then the production file restored and confirmed
byte-identical. An assertion that could not be made to fail would not have
shipped -- a behavioral test that passes regardless of correctness is strictly
worse than the source-grep it replaces, because it looks rigorous while
asserting nothing.
Three claims were checked rather than trusted. All three were wrong:
- repo-layout's own marker cited #1188 asserting the `!isGlobal` lexical scope
was "unprovable at runtime". It is provable: the guard decides whether
AGENTS.md is written, which is directly observable. Both directions verified.
- The triage for runtime-config-adapter-registry claimed its source-grep was
redundant with the file's EXPECTED_TABLE tests, so deletion would be safe.
Those tests only exercise resolveInstallPlan() directly and never load
bin/install.js, so they do not cover it. A real behavioral assertion was
written instead of deleting coverage.
- An earlier revision of this change dropped runtime-config-adapter-registry's
two markers on the grounds that ESLint stayed silent without them. An
adversarial review caught that this was wrong, and it is restored here. The
file still genuinely source-greps bin/install.js at two sites; ESLint is
silent only because no-source-grep's TEXT_METHODS omits matchAll. Dropping a
marker because the linter cannot see the violation is exploiting the blind
spot, not resolving it -- and it would go red the moment the rule is
widened. Those two assertions are also irreducible: they assert that EVERY
inline `runtime === '...'` branch in install.js names a registry-known
runtime, and a branch naming an unregistered runtime would simply never
execute, so no runtime observation can prove its absence. The markers now
say so explicitly.
Two coverage gaps in no-source-grep surfaced while doing this, recorded on
#3464 rather than fixed here, since widening the rule is its own change with
its own blast radius:
- TEXT_METHODS omits matchAll, so a matchAll source-grep never trips the rule
(the case above).
- The path test requires a literal quoted bin/lib/gsd-core/src segment and
tracks the binding one hop, so a read through a dynamic path or an
intermediate variable evades it. install-minimal-hooks' genuinely
load-bearing read at line 975 is itself unmarked for a related reason.
install-minimal-hooks therefore keeps its markers too: its remaining real
source-grep of bin/install.js (the Codex legacy gsd-update-check migration
check, line 975) is outside this issue's 8 sites. It is the last blocker for
that file and is a clean follow-up.
Closes#3466
Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* test(#3336): fold the installer & runtime surface issue-* cluster — Wave 4
Folds 10 legacy issue-*.test.cjs regression files (79 test() blocks) into
their module's main suite, per H3 (#3315) of the test-hygiene epic (#3053).
First of 4 issue-* waves (following the 3 fix-* waves, all merged).
- 1 file with no prior target coverage: renamed (git mv) into
legacy-cleanup.test.cjs (sole comprehensive suite for that module).
- 9 files merged into 6 pre-existing suites: golden-parity-single-source,
runtime-artifact-layout-surface, codex-config (4 sources merged jointly
in one pass per the issue's own instruction, to catch overlap between the
4 sources themselves, not just against the pre-existing target — zero
overlap found, all 20 blocks additive), runtime-config-adapter-registry
(1 of 10 source blocks dropped as a proven subset of existing coverage),
cline-install, install.test.cjs.
Incidental fixes required to keep this wave's own ratchets green:
- Fixed a stale ADR doc reference (docs/adr/1235) to a folded-away filename.
- scripts/lint-allow-test-rule-refs: pruned 4 stale allowlist entries for
renamed/merged-away files, cited 2 previously-uncited allow-test-rule
comments that surfaced as "new" only because their file path changed,
added 1 fresh allowlist entry for a pre-existing uncited comment that
predates this PR, and tightened the exemption-file ceiling 309 -> 305
to match the real post-fold high-water mark.
Zero net test-coverage loss. No production code changed.
* test(#3336): fix orthogonal-review findings — Wave 4 fold
Standards-axis review + Memtrace graph pass found real issues in the
just-folded suites, all fixed here:
- Standardized the fold-wrapper convention (block-scoped __foldDescribe)
across golden-parity-single-source.test.cjs, runtime-artifact-layout-
surface.test.cjs, runtime-config-adapter-registry.test.cjs, and
cline-install.test.cjs to match the pattern already used by
codex-config.test.cjs and install.test.cjs in this same wave (and by
earlier folds elsewhere in the epic) — repeats the exact inconsistency
Wave 3 (#3335) already fixed once in this epic.
- Fixed a stale allowlist entry's alphabetical position (cosmetic, not
tool-gated, caught by review anyway).
- Fixed two stale test-filename references in PRODUCTION code comments
(src/capability-writer.cts, src/runtime-config-adapter-registry.cts)
caught by lint-removed-but-needed — a class of stale reference this
wave's fold agents didn't check for, since they were scoped to docs/
and gsd-core/references/ only, not src/. First fix attempt wrongly
edited the gitignored gsd-core/bin/lib/*.cjs BUILD OUTPUT instead of
the tracked .cts source; caught and corrected before commit.
- Fixed one remaining stale doc reference in docs/adr/1235 (a prior
partial fix in this same wave missed it).
No test() count changed in any file. No production code BEHAVIOR
changed — comment-only fixes in src/.
---------
Co-authored-by: sim <sim@local>
VS Code is a net-new EoS runtime that — unlike every prior migration — is NOT
CLI-installed (Marketplace/VSIX extension). It has zero runtime==='vscode'
branches in bin/install.js and stays that way (regression-guarded); it is driven
entirely through the negotiated imperative Host-Integration adapter.
Registry + validator (the hard part):
- capabilities/vscode/capability.json (role:runtime): full hostIntegration block
(imperative / palette / active vscode.lm model / engine hook bus /
sandboxed-storage / mcp transport / sandboxed-web runtime; dispatch nested,
maxDepth 5 per VS Code's documented subagent depth).
- capability-validator.cjs extended so a role:runtime capability can legitimately
declare "extension-distributed, no config directory": new configHome.kind:'none'
+ installSurface:'none' (+ GATE-A pairing + the parity maps), with localConfigDir
and configHome.name made conditional on kind!=='none'. All 18 runtimes still
validate; getDirName returns a distinct sentinel (not '.claude') for a no-config
runtime.
- The add-a-registry-runtime tax: NON_INSTALLABLE_RUNTIMES exemption in the
runtime-flags drift guard, vscode added to global-config-home SPECIAL_CASED,
EXPECTED_PROFILES.vscode='ide', and the config-adapter/derivation/pin-count
guards updated. No golden-install fixture, model-catalog, or CONFIGURATION rows
(vscode never enters allRuntimes).
Dispatch + extension surface:
- Fixed vscode/extension.js's createHub()-no-args bug (every dispatch was
UnknownCommand, masked by a vacuous reachability test) — now reuses the shared
dispatchGsdCommand subprocess-shim (Node/desktop); the reachability test is
tightened to assert real dispatch.
- Promoted the #1933 host binding to a shipped vscode/host-binding.js; activate()
now composes the model/hookBus/stateIO seams through it. Corrected the model
seam to VS Code's real API (vscode.lm.selectChatModels() -> model.sendRequest();
vscode.lm.sendRequest does not exist) so the binding actually composes on real
desktop VS Code instead of throwing.
- New vscode/browser.js Web Extension entry with ZERO Node APIs (the engine's
config/capability loading is Node-bound, so the web entry registers the surface
and directs full dispatch to the native MCP server — honestly documented).
- UPGRADE 1: GSD skills as native Language Model Tools (contributes.languageModelTools
+ vscode.lm.registerTool), invoke() dispatching through the hub.
- UPGRADE 2: native subagent dispatch wired onto #runSubagent /
chat.subagents.allowInvocationsFromSubagents (fail-soft on API availability,
maxDepth 5 enforced).
- vscode/package.json: browser entry, engines.vscode ^1.105, chatParticipants +
languageModelTools contributions; fixed a stale activationPoints->activationEvents
manifest key. Added "vscode" to the package files array.
Docs (## vscode matrix section) + changeset (Added).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
* 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>
Fold 94 issue-named command/module regression files into the canonical test file
that owns each subject-under-test, across 52 existing suites (state, config, frontmatter,
roadmap-parser, capability-registry, shell-command-projection-dispatch, plan-phase-drift-guard,
health-validation, runtime-converters, commands, etc.). Verbatim block-scoped describe
wrappers; 881 subtests conserved 1:1. No new test files.
Host-env pre-check (per B2): the only GSD_WORKSTREAM/GSD_PROJECT-touching destinations
(intel, planning-workspace) clear those vars hermetically, so folded CLI tests are safe.
Regenerates regression-name allowlist (222->162), ratchets file-count allowlist across
8 buckets (validate entry removed after dropping <=2), makes 34 relocated allow-test-rule
exemptions issue-ref-compliant (ADR-456; prunes 34 stale ids). Repoints CONTEXT.md +
ADR-0002/443/1235/3524 test-file references. lint:ci green.
Part of epic #1969. Closes#1972.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#777): register Cursor-native hooks (.cursor/hooks.json) for session-start/post-tool parity
- Add gsd-cursor-session-start.js: injects STATE.md presence reminder (or
new-project nudge) into Cursor sessions via the sessionStart hook event
- Add gsd-cursor-post-tool.js: emits an additional_context nudge when
write-class tool calls touch .planning/ files (postToolUse hook event)
- Add 'cursor-hooks-json' installSurface to runtime-config-adapter-registry;
writeCursorHooksJson/reconcileCursorHooksJson write the canonical
{ version: 1, hooks: { sessionStart, postToolUse } } JSON shape with
idempotent reconciliation that preserves user-owned hook entries
- Hook scripts are copied with /gsd:→gsd- rewrite so installed files
contain no colon-form slash-command refs (bug-376 invariant)
- 20 new tests in tests/cursor-hooks.test.cjs cover all reconciler paths,
entry helpers, removal, runtime adapter surface, and hook script behavior
- Update CONTEXT.md, ARCHITECTURE.md, installer-migrations.md, and
000-first-time-baseline.cts to include Cursor hooks.json surface
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#777): build hooks/dist on demand in bug-376 test for scoped/windows CI
hooks/dist is gitignored and only produced by `npm run build:hooks`.
The CI scoped (ubuntu-latest/node-22) and windows (windows-latest/node-24)
test jobs do NOT run build:hooks before executing tests, so bug-376's
prerequisite suite was failing with "hooks/dist not found" on both legs.
Add ensureHooksDist() helper (mirrors bug-3357 pattern) that builds
hooks/dist on demand in the before() hooks of prerequisite and Suite 3.
Also add ensureHooksDist() call to Suite 3's before() so the snapshot
step is also hermetic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(#60): make runtime config adapter registry explicit
Replace scattered inline `runtime === '...'` config-mutation branching in
bin/install.js with an explicit, typed adapter registry. The new
src/runtime-config-adapter-registry.cts maps each of the 15 supported
runtimes to a config intent { installSurface, writesSharedSettings,
finishPermissionWriter }; install()/finishInstall() dispatch by resolved
intent instead of runtime-name checks (cursor/windsurf/trae collapse to one
profile-marker-only branch).
Behavior-preserving: the same config files are written for the same runtimes
(opencode still writes both settings.json and its permissions; kilo writes
only its permissions; codex minimal-mode and opencode GSD_TEST_MODE guards
unchanged). Unknown runtimes fail loudly via TypeError, with an Object.hasOwn
barrier so prototype-chain keys (__proto__/constructor) also throw rather than
returning a bogus intent. Leads the installer-refactor chain (#58 -> #60 ->
#56), building on ADR-58.
Closes#60
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#60): add changeset for runtime config adapter registry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#60): register Runtime Config Adapter Registry in CONTEXT.md glossary
Per docs/contributor-standards.md, every new Module/seam must get a
`### <Name>` entry under the domain glossary. Adds the entry for the
runtime-config-adapter-registry seam introduced in this PR (interface,
policy boundary, source file, ADR-58 / #60 cross-references).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>