The three shell scanners exempt their own adversarial test fixtures by exact
filename; the *.security.test.cjs renames broke those entries, so the PR diff
scan flagged the scanners' own test payloads. Verified locally with all three
scanners in --diff origin/next mode (0 findings) and the security suite
(207/207). The .sh files were missed in the original reference sweep because
the rename grep filtered to .cjs/.yml/.json/.md extensions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
13 bug-* files landed upstream between the audit baseline and this branch's
rebase; they predate the ratchet policy, so they are grandfathered.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review-pass fixes: lint:ci composes npm run lint (one eslint invocation
home); the scripts/ coverage floor moves to package.json
(test:coverage:scripts-floor) so both thresholds live together; the ratchet
test uses helpers.createTempDir; TESTING-SUITES.md clarifies what the
Windows scoped lane runs and why feat-*/enh-* files are exempt from the
bug-* ratchet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Coverage gate (test:coverage:unit) now runs inside the ubuntu/24 full lane;
the standalone coverage job duplicated that lane's entire unit run (~4 min
of runner time per PR). required-tests gate updated accordingly.
- New second-tier floor: c8 check-coverage --lines 55 over scripts/**
re-slices the same V8 data (measured 65.95%) — the CI/release/lint tooling
was previously enforced at 0%.
- Coverage artifact now excludes coverage/tmp (>1 GB of raw V8 dumps).
- lint-tests runs npm run lint:ci — one orchestrated step, identical set
locally and in CI; drops the no-op eslint --cache flag (CI never restored
the cache directory).
- Delete unreferenced scripts/run-cross-platform-tests.cjs (+ its test);
document the mutation UNMUTATED blind spot (~48% of lib lines) in
stryker.config.mjs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
244 one-off bug-* files (~38% of the suite) are grandfathered in
lint-regression-test-names.allowlist.json; new ones fail lint with
fold-into-module guidance, and deletions force allowlist pruning so the
baseline only shrinks. Wired into npm run lint:ci (new single entry point
for every CI lint). Policy documented in docs/TESTING-SUITES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renames (git mv) with all references updated (ci-test-scope RULES,
windows-parity allowlist, test-file-count allowlist, docs in 6 locales):
- 5 scanner tests -> *.security.test.cjs — the 'Run security tests' CI step
ran zero files since the suite taxonomy landed; it is now honest.
- graphify-auto-update -> *.slow.test.cjs (36s, slowest file in the suite;
e2e gsd-tools spawns) — runs on full-matrix lanes and push to next.
- installer-migration-install-integration -> *.integration.test.cjs
(13s; an integration test by its own name).
Coverage gate measured after retags: 88.55% lines (gate 70%).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
full_matrix fired on 15/15 sampled PRs because any tests/** change forced it,
costing ~25 runner-minutes each. A changed test file now always joins the
scoped windows lane (covering the #482 OS-specific failure class per-file)
and still runs on ubuntu 22/24 via targeted_tests; the residual macOS /
windows-node-22 cross-product is covered on every push to next.
Also narrows WINDOWS_HINTS from 6 substrings (102/633 files, a ~10-minute
scoped lane) to windows/win32/shell/path — the dropped hints (workflow,
install, hook) are either platform-independent lint tests or already covered
by fullMatrix rules.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(#941): regression test for managed-hooks-registry.cjs manifest omission
Adds bug-941-managed-hooks-registry-manifest.test.cjs which verifies:
- managed-hooks-registry.cjs appears in gsd-file-manifest.json after install
- manifest covers the full HOOKS_TO_COPY set (forward-proof)
- detect-custom-files reports 0 custom files after a clean install
- manifest hook keys use forward slashes (cross-platform)
All four assertions fail before the fix, confirming the bug is reproducible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#941): track managed-hooks-registry.cjs in file manifest
The writeManifest() hooks loop in bin/install.js filtered hook filenames
with `file.startsWith('gsd-') && (file.endsWith('.js') || file.endsWith('.sh'))`.
managed-hooks-registry.cjs fails both predicates (wrong prefix, .cjs extension),
so it was never recorded in gsd-file-manifest.json even though it is shipped to
users as part of HOOKS_TO_COPY.
detect-custom-files scans the installed hooks/ dir and reports any file with no
manifest entry as a custom file, producing a perpetual false-positive
"Found 1 custom file(s)" warning on every /gsd-update for all users.
Fix: import HOOKS_TO_COPY from scripts/build-hooks.js and drive the manifest
hooks loop from that set (as a Set for O(1) lookup), so the manifest set is
structurally identical to the build set. Any future hook of any prefix or
extension added to HOOKS_TO_COPY is automatically covered. The new regression
test asserts full HOOKS_TO_COPY coverage and zero detect-custom-files
false-positives after a clean install.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#941): add changeset for managed-hooks-registry.cjs manifest fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#941): assert manifest hash matches installed hook contents (adversarial review)
Strengthen the regression test to not only verify that the manifest KEY
`hooks/managed-hooks-registry.cjs` is present after install, but also that
the stored hash equals the SHA256 of the actual installed file bytes — the
same algorithm used by the installer's fileHash() function. A future
refactor that records the right key from the wrong path or content would
now fail this assertion immediately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Design how a Capability contributes a gsd-tools CLI command family and how the
hardcoded 73-case runCommand switch opens to registry-driven dispatch. Realizes
ADR-857 decision 7's reserved commands/module field (deferred by ADR-894).
Grilled to its leanest form: the registry DISCOVERS a standard route*Command
(no rebuilt handler table, no new arg convention); dispatch sits in the default
case (collision structurally impossible, no shadowing gate needed); graphify is
the first real cutover (lowest blast radius, has skill+cluster+config gate,
full-only so 4c stays no-op), proven equivalent and serving as the phase-6
template.
Design-only; CONTEXT.md gains a "Capability Command Family [Planned]" entry.
Closes#959
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Make install + surface read the registry's derived profileMembership/
capabilityClusters so a capability's tier drives what installs + surfaces.
resolveProfile (when given the registry) unions capability skills for the
profiles its tier implies before the requires: closure; resolveSurface merges
capabilityClusters into the cluster map. bin/install.js, /gsd:surface, and the
capability-state resolver all thread the registry.
Shipped as a proven no-op: the UI capability is reconciled to tier:full (its
skills were full-only in the hand-authored profiles), so it contributes only to
the full profile (already the '*' sentinel) and core/standard are unchanged.
Equivalence tests prove resolveProfile/resolveSurface/listSurface/staging/
capability-state are identical with vs without the registry; the core-alias
staging path is verified equivalent (empty manifest → raw PROFILES.core).
Closes#949
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Combined PRD + ADR for wiring MemPalace (local-first AI memory) into the
GSD loop as an ADR-857 feature capability. Bidirectional sync, three
selectable memory-relationship modes (augment/kg_backend/replace),
loop-point recall+capture map, opt-in tier:full, MCP-primary/CLI-fallback.
Marked Pre-Proposal: the first-party-plugin proposal standard is not yet
established and ADR-857 phase-6 loop wiring is pending. First of a planned
series; PRD/ADR format is provisional pending PM-method evaluation.
Refs #956
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add a read-side query composing the three toggle systems into one
per-capability view. resolveCapabilityState({registry, installedSkills,
surfacedSkills, config, cwd}) reports installed (skills ⊆ resolved install
profile), surfaced (skills ⊆ resolved surface), and per-hook active (no when →
active; non-empty-string when → resolved via _resolveActivationValue; empty/
non-string → inactive), with no forced composite verdict. cmdCapabilityState
does the I/O (resolveProfile + resolveSurface + loadConfig), resolves the
runtime config dir via the canonical getGlobalConfigDir (--config-dir override),
and surfaces resolution failures as warnings rather than a false installed='*'.
Routed as `gsd-tools capability state`.
Additive: install/surface/workflows untouched; consumed by nothing.
Closes#945
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Establish `tier` as the source of install-profile + cluster membership
(ADR-894 §4). The registry now derives two views: capabilityClusters
(capId → its skills) and profileMembership (capId → {tier, profiles}, where
profiles is the PROFILE_RANK suffix from the capability's tier). A consistency
gate cross-checks them against the hand-authored PROFILES/CLUSTERS: HARD (throws)
on a capId-matching-a-cluster-name with a different skill set; SOFT
(pending-reconciliation stderr warning, not serialized) when a capability skill
isn't yet in the closure-resolved hand-authored profile.
The SOFT gate loads the real skills manifest and resolves each profile's closure
once so transitively-included skills don't false-warn; warnings are de-duped to
one per (capability, skill); both derived views are scoped to skill-owning
capabilities; serialized with a global capId sort for determinism; reserved-name
guards at every write site; lazy requires of the built constants.
Behavior-preserving: install/surface untouched; derived views consumed by
nothing. Full generation rides along with the phase-6 migration.
Closes#942
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#934): reapply verifier handles missing pristine baseline post-rename
Gap 1 (verify-reapply-patches.cjs): when backup-meta.json records a
pristine_hash for a file but gsd-pristine/ has no corresponding snapshot
on disk, the verifier fell to over-broad mode and produced false
FAIL_USER_LINES_MISSING. Fix: return advisory OK_NO_BASELINE (non-blocking,
exit 0) so the verifier does not block on files it cannot reason about.
Gap 2 (new migration 004): migration 003 removed legacy get-shit-done/
runtime files but left gsd-pristine/get-shit-done/ orphan snapshots in
place. Those stale snapshots referenced get-shit-done/... key paths that
no longer match the active gsd-core/... layout. Fix: add migration
004-prune-stale-pristine-get-shit-done (NOT editing 003, preserving its
checksum — ref #670 guard) to remove all files under
gsd-pristine/get-shit-done/ as GSD-managed pristine snapshots.
Includes tests: bug-934 OK_NO_BASELINE assertions in the verifier test,
new installer-migration-prune-stale-pristine.test.cjs, updated
installer-migrations baseline-lock checksum for 004.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#934): rename migration to satisfy legacy-name guard + mark intentional path refs
Rename src/installer-migrations/004-prune-stale-pristine-get-shit-done.cts
→ 004-prune-stale-pristine-snapshots.cts so the filename no longer contains the
forbidden token. Update .gitignore and eslint.config.mjs to track the new built
path. Add gsd-allow-legacy-name markers to the remaining intentional uses of the
legacy path string in the migration body (lines 3 and 100) and in tests
(installer-migration-prune-stale-pristine.test.cjs lines 202 and 226; and the
baseline-lock key in installer-migrations.test.cjs:1469). Update the baseline
checksum for migration 2026-06-09-prune-stale-pristine-get-shit-done to reflect
the two new marker comments added to its body.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Both sites in plan-review-convergence.md that wrapped gsd-plan-phase in
Agent() (initial planning + replan loop) are now bare Skill() calls at depth 0.
On Claude Code, a depth-1 Agent has no Agent tool so wrapped plan-phase could
never spawn gsd-planner/gsd-plan-checker — the replan loop silently produced no
revised plan when HIGHs were found. Running plan-phase inline from the depth-0
orchestrator (which retains the Agent tool) restores the full sub-agent chain.
A full audit of all workflow files confirmed these two sites were the only
instances of the anti-pattern (no other workflow wraps a spawner orchestrator
in Agent() without a RUNTIME carve-out).
Added structural guard test bug-936-no-nested-spawner-wrap.test.cjs that
dynamically derives the spawner set (workflows containing subagent_type=) and
asserts no workflow wraps a spawner inside Agent() without a RUNTIME != claude
carve-out — prevents silent regression. Test passes on fixed code, would fail
on pre-fix code at the two de-wrapped sites.
Also applied two low-severity prose nits flagged in review:
- commands/gsd/plan-review-convergence.md: orchestrator role updated to
describe inline plan-phase + Agent for review (was generic "spawn Agents")
- gsd-core/workflows/plan-review-convergence.md success_criteria: narrowed
"Each Agent fully completes" to the review Agent (plan-phase is inline now)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- bin/install.js now copies scripts/changeset/ and scripts/lib/ into
<configDir>/scripts/ so $GSD_DIR/scripts/changeset/cli.cjs resolves
at runtime; aborts install with an explicit failure if the source
directory is missing from the package.
- gsd-core/workflows/update.md: corrected path from
gsd-core/scripts/changeset/cli.cjs to scripts/changeset/cli.cjs;
added an explicit [ ! -f ] guard so a missing CLI surfaces a clear
message rather than silently swallowing the error; stderr captured
via 2>&1 sentinel so node errors are visible in the preview output.
- release.yml's changeset-CLI invocations (node scripts/changeset/cli.cjs)
remain at the repo-root path and are unaffected by this change.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Scans `gsd-ns-<router>/skills/<stem>/SKILL.md` in addition to the
existing flat `<stem>/SKILL.md` layout, so gsd-health and gsd-settings
report the correct concrete skill count on nested-layout runtimes
(cline, qwen, hermes, augment, trae, antigravity).
Guard: descent into a `skills/` subdir is restricted to `gsd-ns-*`
router directories — unrelated user dirs that happen to have a
`skills/` subdir are not traversed. Dual-routed concretes (same name
under two routers) are deduped within each root.
Adds a negative-case regression test: verifies that a non-`gsd-ns-*`
dir (e.g. `my-tool/`, `gsd-settings/`) with its own `skills/`
subdir does NOT contribute nested entries to the manifest.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#930): remove self-masking next dist-tag repoint from release finalize
The "Clean up next dist-tag" step silently failed under OIDC trusted
publishing (which can't write dist-tags) while unconditionally reporting
success via || true + an echo. It also violated the release model by
trying to repoint @next→stable; @next is managed exclusively by the rc
job's --tag next publish.
Closes#930
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update ADR-660 to reflect removal of next dist-tag repoint
The finalize job no longer runs `npm dist-tag add … next`; update the
ADR-660 description of step 4 to match the new behavior — @next is
managed exclusively by the rc job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
PR #883 nested Claude skills 3 levels deep under gsd-ns-*/skills/<stem>/SKILL.md.
Claude Code's Skill tool scans only one level under ~/.claude/skills/ — nested
concrete skills were never listed and Skill(skill="gsd-plan-phase") calls failed.
Revert to flat layout: all ~61 concrete skills at ~/.claude/skills/gsd-<name>/SKILL.md.
The 6 other runtimes confirmed as non-recursive scanners (cline, qwen, hermes, augment,
trae, antigravity) retain their nested layout — only Claude changes.
Tradeoff: ~61 top-level skill dirs return to the flat install, but they are discoverable
and invokable. Nested concretes were invisible to the Skill tool entirely.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
`context: fork` strips the `Agent` tool from a subagent's environment.
Spawning orchestrators (`/gsd-autonomous`, `/gsd-execute-phase`,
`/gsd-plan-phase`) depend on `Agent` to dispatch sub-agents; running
them forked silently disables the core capability they exist to provide
(#921). Remove `context: fork` from all three command frontmatter files.
`effort: xhigh` (introduced by #769) is preserved.
The `<runtime_compatibility>` Agent-availability guard added by #913 was
checking whether `Agent` was present *before* attempting the call. On
runtimes where the tool list is dynamically resolved this produced
false-negative aborts in sessions that have the tool (#922). Replace
the introspection-based pattern with an attempt-based gate: always
attempt the `Agent()` call; stop only if a real tool-unavailable error
is returned. This preserves #853's backgrounded-session close-off and
#913's intent of preventing inline role-collapse, while eliminating
false negatives.
Tests updated: enh-769-context-fork-effort.install.test.cjs asserts the
three orchestrators lack `context: fork` and that the converter still
passes the field through for non-orchestrator commands; plan-phase-drift-
guard.test.cjs adds four assertions for the attempt-based gate language;
workflow-size-budget unchanged (budgets not exceeded).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Read `data.hook_event_name` from the stdin payload and fall back to the
Gemini/non-Gemini heuristic only when the field is absent or blank.
Fixes Claude Code rejecting output with "expected Stop but got PostToolUse"
when the monitor is called by Stop, SubagentStop, or PreCompact hooks.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add the loop.render-hooks resolver: the first registry-consuming query.
`gsd-tools loop render-hooks <point>` validates the point against the
authoritative canonical 12, reads the registry's materialized byLoopPoint
hooks, filters them by activation, and emits a JSON envelope {point,
activeHooks, rendered} with ordered markdown.
Activation resolves each hook's `when` key by precedence: loadConfig value
(post-cutover federated) -> raw config.json workstream/root single-key lookup
(pre-cutover central override) -> registry configSchema default (so a
default:true capability hook is active out-of-the-box) -> inactive. Guarded
single-value reads only (no merged object built from untrusted keys).
Registry-only: no workflow calls the resolver yet (wiring is the phase-6
cutover). Completes the phase-3 trio.
Closes#918
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The bug #1998 subtest "checkbox updated when archived milestones exist in
<details>" flaked under the high-concurrency docker run (~672 test files in
parallel): the current-milestone checkbox was left unchecked.
Root cause: `gsd-tools phase complete` writes ROADMAP.md as its LAST step,
after a read-heavy parse/lock sequence. Under heavy parallel CPU/IO
contention the test's tight `timeout: 10000` fired mid-parse and SIGTERM'd
the subprocess before that write landed, leaving ROADMAP.md pristine (both
phases `- [ ]`). The bare `catch {}` silently swallowed the kill, so a
timeout masqueraded as a "checkbox not checked" assertion failure. All I/O
is scoped to each test's tmpDir, so there is no cross-process race — the
timeout was the sole cause.
Consolidate all 7 duplicated `phase complete` call sites (suites #1998,
#2005, #2526) into a shared runPhaseComplete() helper that:
1. raises the timeout to 60s so the test's own timer never kills the
subprocess under load;
2. never silently swallows a signal/timeout kill (rethrows loudly with
captured output) while still tolerating a clean non-zero exit for the
ROADMAP-asserting tests via { tolerateExit: true }.
No retry loop. Verified with 3x `gsd-test --reset` full docker runs
(13207 tests / 2311 suites each, 0 failures, flaky subtest green every
round).
Closes#916
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Build the federated config merge: each capability owns its config-key slice
(ADR-857 decision 3 / ADR-894), and loadConfig merges them defensively. The
registry now emits a full configSchema index ({key:{owner,type,default,
description}}, generator-validated); a new src/federated-config.cts resolves
federated keys defensively (skip central keys -> pending-migration warning,
skip malformed slices -> warning never throw, else type-checked user override
?? default, with nested dotted-path lookup and enum validation); and loadConfig
applies the overlay on every return path.
Wired as a provably-empty no-op channel: every UI-pilot key is still central,
so validKeys is empty and loadConfig returns byte-identical output on all paths
(identity return when the overlay is empty; shared CONFIG_DEFAULTS never
mutated). Registry-only; no key is cut over; nothing in the live loop changes.
Closes#910
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Three-part fix for the top-level inline collapse bug:
1. plan-phase.md: add <runtime_compatibility> block after
</available_agent_types> that makes the Agent-availability
requirement explicit; workflow fails-closed (stops with a clear
log) in genuinely Agent-less contexts.
2. plan-phase.md: rename 7 "ORCHESTRATOR RULE — CODEX RUNTIME"
labels to "ALL RUNTIMES" so the spawn guard applies universally
(not just when Codex is detected).
3. execute-phase.md: scope the existing "Other runtimes" inline-
fallback prose to non-Claude contexts, preserving the #853
backgrounded-agent behaviour for Claude Code background agents.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
syncStateFrontmatter was silently dropping current_phase, current_phase_name,
current_plan, and progress when body annotations were absent (e.g. after an
agent or tool rewrote the body). These scalars can only be derived from body
annotations — when absent, buildStateFrontmatter returns nothing for those
keys. Added existingFm fallbacks mirroring the same pattern already applied in
cmdStateJson, so every writeStateMd call preserves the existing values instead
of stripping them. Also extended cmdStateJson with the same fallbacks for the
three non-progress scalars.
Adds regression test (7 cases) + lint-test-file-count allowlist entry.
Closes#905
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
buildRoadmapPhaseVariants() only matched heading-style phases (## Phase N:),
silently skipping the supported checklist format (- [x] **Phase N: name**).
This caused W007 false-positives for every on-disk phase dir when the project
uses a checklist ROADMAP. Fix adds a second regex pass (mirroring the existing
buildNotStartedPhaseVariants() approach). Also refactors the duplicate
inline heading-only regex in cmdValidateConsistency() to delegate to
buildRoadmapPhaseVariants() (DRY). Regression test in
tests/bug-892-validate-checklist-roadmap-phases.test.cjs covers both paths.
Closes#892
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
`loadConfig` in configuration.cts was superseded by config-loader.cts
(ADR-857 phase 2e, #885). Exhaustive grep confirms no caller imports
loadConfig from configuration.cjs — all live callers use config-loader.cjs
or the core.cjs back-compat re-export. configuration.cts now provides only
the pure normalization and defaults primitives (normalizeLegacyKeys,
mergeDefaults, migrateOnDisk, CONFIG_DEFAULTS) that config-loader.cts
depends on. Updated CONTEXT.md and docs/INVENTORY.md to reflect the
narrowed module surface.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- Updated `gsd-core/workflows/_runtime-launcher.snippet.sh` with 15 new
`elif` arms covering Hermes, Cursor, Codex, Gemini, Copilot, Windsurf,
Augment, Trae, Qwen, CodeBuddy, Cline, Grok, Antigravity, OpenCode, and
Kilo (respecting each runtime's env-var override with a `$HOME`-relative
default).
- Re-ran `scripts/sync-runtime-launcher.cjs` to propagate the expanded
snippet into all `gsd-core/workflows/*.md` files (~70 files).
- Manually applied the same snippet update to `commands/gsd/import.md`
(1 occurrence) and `commands/gsd/graphify.md` (5 occurrences) — these
are not covered by the sync script.
- Updated `tests/workflow-size-budget.test.cjs` budgets (XL/LARGE/DEFAULT
+ discuss-phase target) to account for the ~3 KB snippet expansion.
- Added regression test `tests/bug-891-non-claude-runtime-home-fallback.test.cjs`
(6 tests: structural probe presence, ordering, behavioral HERMES_HOME
env-var + default-path stubs, resolution order, and workflow propagation).
- Added `.changeset/891-launcher-non-claude-runtime-homes.md` (Fixed).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Wrap the {phase} substitution in normalizePhaseName() at both fix sites:
- src/init.cts — cmdInitExecutePhase branch_name output
- src/commands.cts — cmdCommit pre-execution branch derivation
When project_code is set (e.g. "CK"), extractPhaseToken returns the
full prefixed token "CK-01" as phase_number. Without normalization the
generated branch was "gsd/phase-CK-01-foundation"; after this fix it is
"gsd/phase-01-foundation", matching the documented {phase} contract
(padded numeric only).
Adds a regression test in tests/init.test.cjs.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Replace the inline LOOP_HOST_CONTRACT constant in the Capability Registry
generator with a generated-from-workflows contract (ADR-894 §3). The contract
is now derived from inert `<!-- gsd:loop-host ... -->` marker blocks in the five
step workflows, emitted as the committed gsd-core/bin/lib/loop-host-contract.cjs,
and required by gen-capability-registry.cjs — one source of truth, no drift.
Drift guards in gen-loop-host-contract.cjs: per-step point ownership (each step
must declare exactly its canonical loop points), multiple-block + duplicate-key
hard errors, and a word-boundary agent-role cross-check. Contract content is
byte-identical to the former constant; registry-only, nothing wired into the
live loop.
Closes#903
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#896): Capability Registry generator + UI pilot (ADR-857 phase 3a-impl)
First phase-3 code: the Capability Registry generation pipeline, built against
the ADR-894 contract and NOT wired into the live loop (registry-only, per the
staged-cutover design).
- capabilities/ui/capability.json — the UI pilot (ADR-894 worked example): 2
skills, 2 agents, 3 config keys, 2 steps + 1 gate, with `when` activation.
- scripts/gen-capability-registry.cjs — --write/--check generator. Hand-rolled
schema validation (envelope + role-typed feature/runtime bodies + typed
steps/contributions/gates + when + gate-check variants); cross-capability
invariants (single ownership; requires exist+acyclic+tier-monotone; config-key
ownership exclusive, collision-vs-central as a pending-migration warning);
hooks validated against an inline LOOP_HOST_CONTRACT (3a-impl-2 swaps its
source to the generated-from-workflows contract); GLOBAL point-ordered
consumes-satisfiability; materialized byLoopPoint ordering (produces/consumes
topo-sort); emits gsd-core/bin/lib/capability-registry.cjs (role-partitioned
indexes + requiresClosure). Prototype-pollution guards (Object.create(null) +
inline literal key checks) + fragment.path traversal guard.
- gsd-core/bin/lib/capability-registry.cjs — committed generated artifact
(mirrors package-identity.cjs: script-generated, tracked, linted, regenerated
on build, drift-tested), wired via the new `gen:capability-registry` build step.
- tests/capability-registry.test.cjs — 72 tests: schema + invariant + hook +
ordering + adversarial (path-traversal, proto-pollution, runtime body,
self-consume, cycles, collisions) + committed-file staleness guard.
New-CLI-module checklist (INVENTORY 97->98, MANIFEST, ARCHITECTURE), CONTEXT.md
"Capability Registry" un-[Planned]'d. Nothing wired into install/surface/loop.
Gates: lint, code-review (4 bugs fixed), security-review (path-traversal +
prototype-pollution fixed), codex adversarial-review ×3 (8+ findings fixed,
confirmed sound), clean-build docker 13190 pass / 0 fail.
Closes#896
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#896): CRLF-agnostic --check for capability-registry staleness (Windows)
The committed capability-registry.cjs staleness guard failed on Windows CI only:
git checks out the committed .cjs as CRLF (autocrlf, no .gitattributes) while the
generator emits LF, so the byte-for-byte --check comparison mismatched. Normalize
line endings on both sides of the --check comparison (no .gitattributes change,
no change to the LF the generator writes). Adds a regression test simulating the
Windows CRLF checkout.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#894): ADR-894 Capability declaration format + registry generation
ADR-857 rollout phase 3a (design-only). Resolve ADR-857's deferred open
question — the on-disk Capability declaration format — as a reviewable design
ADR before any generator code.
Specifies: the capabilities/<id>/capability.json folder layout (migration-staged
ownership — declarations reference existing stems until the phase-6 move); the
capability.json schema for role:feature (skills/agents/hooks/federated config/
loopHooks) and role:runtime (the six closed projection-primitive axes); the 12
named Loop Extension Points; the gen-capability-registry.cjs generator design
(validation + cross-capability invariants + --write/--check drift gate, mirroring
gen-inventory-manifest); the generated capability-registry.cjs shape (by-id /
by-skill / by-loop-point indexes + requires-closure); and a full worked example
(the UI capability: ui-phase + ui-review + agents + config + two loop hooks).
No code — design artifact only; the generator build, federated config loader
(3b), and loop seam (3c) implement against this contract.
Closes#894
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#894): amend ADR-894 with grilled capability declaration format
Stress-tested the declaration format before merge; the format changed
materially. Amendments:
- loopHooks[] -> three typed arrays (steps/contributions/gates), each with its
own shape (step: ref+produces/consumes; contribution: fragment+into agent-role;
gate: check+blocking).
- Add the Loop Host Contract (§3): each step publishes its points, agent roles,
and core artifacts so the generator validates hooks against reality, not
trusted strings.
- requires = capability ids only (host implicit); add tier-monotone invariant;
drop the requires:["plan"] error from the example.
- Config federation = atomic move: a migrated key leaves the central schema in
the same PR; presence in both is a collision (invariant stays).
- One registry, role-partitioned indexes (feature indexes vs runtimes index).
- Rework the UI worked example to the split-array shape (2 steps + 1 gate) +
a contribution illustration.
Adds a "Grilling amendments" section recording the six changes.
* docs(#894): amend ADR-894 with round-2 grilling (operational reality)
Second design-grill round, folded in before merge:
- Loop Host Contract is GENERATED from structured workflow markers
(<loop-point>/<agent-role>/<loop-artifact>) via gen-loop-host-contract.cjs —
it can't drift from the real workflows.
- Hook activation `when`: cheap deterministic config-level gating evaluated by
loop.render-hooks; deeper phase-context applicability self-gates inside the
dispatched skill (no phase-context vocabulary to keep honest).
- `tier` is the source of install-profile + cluster membership; profiles and
clusters are generated from tier + requires-closure (/gsd:surface operates on
capabilities) — collapses ADR-857's multiple toggle systems.
- Gate `check` = query | declarative-predicate | agentVerdict; agentVerdict is
forced advisory; only deterministic checks may block.
- byLoopPoint ordering is materialized in the registry; render-hooks filters the
active set + renders. Same-capability hooks degrade gracefully when an entry
step self-gates.
- Rollout: registry-only until atomic per-feature cutover (no double-execution
with still-inlined workflow features).
Updates the Grilling amendments / Consequences / Alternatives / Open questions
sections; reworks the UI example with `when`.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Three test files copy-pasted the concrete-skill->namespace-router CHILD_ROUTER
map verbatim, and two more duplicated an identical local parseRouterRequires
regex. Introduce tests/helpers/nested-layout.cjs that derives the child->router
map from the authoritative commands/gsd/ns-*.md requires: lists once, reusing
the production parseRequires (now exported from install-profiles) plus a
nestedSkillPath(skillsRoot, prefix, stem) helper. Refactor all five test files
to import from it.
Test-only + a single internal export addition; no production behavior change.
Closes#887
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ADR-857 rollout phase 2e — the largest core.cts extraction. Move the
configuration-loading subsystem (loadConfig + _getConfigDefault/
_getNestedConfigDefault/CONFIG_DEFAULTS/_deepMergeConfig, isGitIgnored +
_gitIgnoredCache, _warnUnknownProfileOverrides + RUNTIME_OVERRIDE_TIERS + the
dedup Sets, _resetRuntimeWarningCacheForTests) out of core.cts into a new leaf
module src/config-loader.cts. core.cts re-exports the public surface
(loadConfig, isGitIgnored, CONFIG_DEFAULTS, RUNTIME_OVERRIDE_TIERS,
_resetRuntimeWarningCacheForTests); 12+ callers unchanged.
Cycle-free: config-loader imports only leaves (configuration, config-schema,
planning-workspace, shell-command-projection, core-utils, model-catalog). All
core-internal helpers loadConfig touches moved with it to avoid a cycle. core
keeps CANONICAL_CONFIG_DEFAULTS for its model-resolver functions, which now
resolve loadConfig via the binding — this unblocks the final model-resolver
extraction (2f). core.cts: 1275 -> 792 lines.
Repointed tests/config-field-docs.test.cjs (a docs-parity source check) to read
the CONFIG_DEFAULTS literal from its new home (config-loader.cjs). New-CLI-module
checklist done (.gitignore, eslint, INVENTORY 95->96 + row, manifest,
ARCHITECTURE, CONTEXT.md "Config Loader Module"). Adds tests/config-loader.test.cjs
(27 tests: behavioral + shim-identity + adversarial config fixtures).
Gates: lint, code-review, security-review (prototype-pollution guard confirmed
intact), codex adversarial-review (0 findings; byte-identical move). Mac 4115
pass; clean-build docker: full-suite hit the local mirror's known incremental-tsc
non-determinism on an unrelated re-exported symbol (findPhaseInternal, from
already-merged 2d), but a clean targeted rebuild of the affected file passed
163/0 — CI's clean full matrix is the authoritative gate.
Closes#885
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Emit the 6 gsd-ns-* routers as the only top-level skill bundles and nest
the ~61 concrete skills under <router>/skills/<name>/SKILL.md on runtimes
with confirmed non-recursive skill loaders (claude global, cline, qwen,
hermes, augment, trae, antigravity). Router bodies rewrite their routing
tables from Skill-tool dispatch to a Read skills/<name>/SKILL.md pattern.
Recursive/unconfirmed loaders (cursor, codex, copilot, windsurf, codebuddy,
opencode, kilo) keep the flat layout. Completes the v1.40 namespace
architecture (#2792) so the eager skill listing drops to ~6 entries.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>