* fix(#2777): remove stale codex local home metadata
* chore(#2777): add changeset for codex local layout metadata
---------
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
* test(#2624): failing-first regression for stale .gsd-source marker read-before-write
Adds failing-first regression proving a Claude-global upgrade currently lets
staging read a stale prior-version .gsd-source marker before install() rewrites
it. Pre-seeds a stale marker pointing at a still-existing fake source, spies on
findInstallSourceRoot to capture which source staging resolves, and asserts
staging never resolves the stale path. Also covers fresh-install and ghost-marker
negative space.
* fix(#2624): write the .gsd-source marker before staging reads it
A Claude-global upgrade silently installed skill content from the PREVIOUS
version. findInstallSourceRoot prefers <configDir>/.gsd-source, and install()
used to rewrite that marker AFTER staging had already read it — so on an
upgrade the marker still pointed at the prior version's source (an npx cache
dir that still exists on disk) and every converted skill was generated from
the OLD commands/gsd, with generateManifest then recording the stale content's
hash as correct.
Extract the marker write into _writeGsdSourceMarker(runtime, targetDir, src,
isGlobal) and call it BEFORE the staging pass (before the _isSkillsRuntime
branch), preserving the original sourceMarkerFile && isGlobal guard, the
half-published-package existsSync guard, and the non-fatal write-failure warn.
This closes the read-before-write hole for every findInstallSourceRoot
consumer (skills, commands, /gsd-surface, capability-state) in one move.
Long-standing (marker write added in ee6f3b70c / #1477, already in v1.7.0);
triggers on any Claude-global upgrade where skill content changed and the
prior source path still exists (the common npx-persistent-cache case).
* test(#2624): use t.mock.method for process.exit/console per CONTRIBUTING rules
Address code-review finding: replace manual process.exit/console monkeypatch
and try/finally in the #2624 test helpers with t.mock.method (auto-restored),
matching CONTRIBUTING.md test conventions and the t.mock.method idiom used
elsewhere in the suite.
* docs(changeset): #2624 install-source-root-stale-marker
* docs(changeset): backfill #2624 PR number to 2811
lint-fix-has-regression-test.cjs: new gate that fails if a fix(#NNNN)
or feat(#NNNN) commit has zero behavioral test files (*.test.cjs,
excluding auto-generated fixtures/baselines) in its diff. Wired into
lint:ci so it runs before PR creation.
Missing regression tests added:
- #2429: codex local scope does not set $HOME/.agents skills home;
global scope does (tests/runtime-artifact-layout.test.cjs)
- #2279: map-codebase instructions say to overwrite existing dates,
not just replace [YYYY-MM-DD] placeholders (tests/commands.test.cjs)
* test(#2329): fail-first tests for opencode commands/ (plural) command dir
Red phase, empirically probed: global/local install lands in command/ (singular)
with 71 gsd-*.md files and no commands/; the manifest records 71 keys under
command/ and zero under commands/; all four declaring sites report 'command'.
Migration coverage is black-box (two sequential install runs against one
configDir) so it holds regardless of how the fix implements cleanup.
The Kilo guard passes today by design — a forward-looking no-collateral check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* fix(#2329): write opencode commands to commands/ (plural), migrate legacy command/
OpenCode discovers slash commands from commands/ (plural); the installer wrote
them to command/ (singular), so none of the ~71 /gsd-* commands appeared in the
TUI. Five sites declared the directory and all had to agree:
- capabilities/opencode/capability.json: both artifactLayout destSubpath entries
(global + local) and hostBehaviors.flatCommandDir
- bin/install.js: the manifest prefix was a SEPARATE hardcoded 'command/' literal,
so the manifest would have diverged from the descriptor even after a rename. It
now derives from _hostBehaviors(runtime).flatCommandDir.
- src/install-engine.cts installOpencodeFamilyArtifacts: the actual write target,
which bypasses resolveRuntimeArtifactLayout via combinedFamilyInstall. This was
a fifth site the issue did not list — without it the descriptor change alone
would not have moved a single file.
Migration: an upgrade over a pre-fix install removes only manifest-proven
GSD-managed files from the legacy command/ dir and rmdirs it once empty.
Unmanifested user files are preserved, never deleted.
Kilo shares the opencode family install path and is explicitly unaffected —
pinned by a no-collateral test.
Note on the tests: the migration cases originally built their legacy fixture by
running the installer and relying on it to produce command/ — i.e. they depended
on the bug to set up the fixture, and became unsatisfiable the moment it was
fixed (block 1 requires command/ to be absent after a fresh install). They now
fabricate the legacy layout explicitly, including rewriting the manifest keys to
the command/ prefix — which is load-bearing, since the migration only removes
manifest-proven files and an unrewritten fixture would silently no-op and pass
even against a broken migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* test(#2329): regenerate opencode install golden after rebase onto next
The golden conflicted on rebase because #2322 also regenerated it. Resolved by
regenerating from the merged source rather than hand-merging a generated file;
the only delta is the 71 command/gsd-*.md -> commands/gsd-*.md key renames.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* test(#2329): update stale tests that pinned opencode's singular command/ dir
Seven tests encoded the old contract (opencode: command/gsd-help.md exists, the
descriptor's flatCommandDir, the install-integration contract, and the
resolveRuntimeArtifactLayout golden). They passed in the red phase precisely
because they pinned the buggy singular dir; the fix intentionally changes that
contract, so these are stale-test corrections, not regressions.
Kilo shares the opencode family install path and is deliberately NOT changing —
it stays on command/ (singular). The shared opencode/kilo test is now split via
an explicit per-runtime dir map so the two cannot be conflated, and Kilo's own
layout test is untouched. tests/opencode-command-dir-plural.test.cjs
independently pins Kilo unchanged end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2329): changeset for opencode commands/ dir fix
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2329): backfill PR number 2354 into changeset
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2329): correct the changeset — do not assert opencode ignores command/
The changeset repeated the issue's stated mechanism ("OpenCode discovers them
from commands/ ... a clean install produced no usable commands in the TUI at
all"). OpenCode's source contradicts that: packages/core/src/v1/config/command.ts
globs {command,commands}/**/*.md, so BOTH names resolve, and its own skill doc
still calls .opencode/command/ typical. Shipping that claim as a release note
would document a mechanism that does not exist.
The change is still right, for the stronger reason: OpenCode's config docs list
plural as the convention and singular as backwards compatibility, so GSD was
shipping on the alias the vendor may withdraw. Reworded to describe it as the
alignment it is, decided on OpenCode's source and docs rather than on bug reports
in either repo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* fix(#2329): baseline opencode's commands/ surface — closes a data-loss path this PR opened
Not a bookkeeping gap. Moving opencode's command dir to commands/ moved the
install destination to a surface the first-time baseline scan does not cover:
000-first-time-baseline's RUNTIME_SURFACES.opencode lists ['gsd-core','command',
'skills','agents'] — no 'commands'.
installOpencodeFamilyCommands unconditionally unlinks every gsd-*.md under its
destination before writing the fresh set (install-engine.cts:870-873), with zero
manifest or migration involvement. The only thing that protects a pre-existing
file is assertInstallerMigrationsUnblocked, which runs before materialization and
halts when the baseline scan flags an unknown file at a KNOWN surface.
Probed: a pre-existing commands/gsd-plan.md is silently destroyed (install exits
0). The identical file under the legacy, already-baselined command/ surface
correctly halts the install with "installer migration blocked pending user
choice". So this PR would have traded a protected surface for an unprotected one.
Fixed with a NEW fix-forward migration rather than editing 000, per
docs/installer-migrations.md:131-134 — an applied migration never re-runs, so
editing 000 would only protect fresh installs and leave every existing machine
exposed. A new id runs for both populations and drifts no shipped checksum;
adding its entry to EXPECTED_CHECKSUMS is the case that test explicitly sanctions.
All five pre-existing shipped checksums verified byte-identical.
Kilo is excluded by the migration's runtimes filter and keeps command/.
This was previously deferred as a PR-body note claiming "low impact — nothing
else acts on baseline-scan misses". That claim was never probed and was wrong.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
* docs(#2329): drop the parenthetical product description from the changeset
The product-name purity guard (#1777) rejects "Kilo (which still uses
command/)" — fragment prose renders verbatim into CHANGELOG.md, so a product
name must not carry a parenthetical. Reworded to a plain sentence; the meaning
is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 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>
Address re-review minors on #1487:
- bin/install.js: replace the silent .gsd-source marker catch with a
console.warn so a failed write is diagnosable (walk-up also fails on the
Claude-global layout, so a swallowed error still breaks /gsd-surface).
- tests/runtime-artifact-layout.test.cjs: add a writer fault-injection case
(mock fs.writeFileSync to throw for the marker) proving the catch branch is
reachable, install stays non-fatal, and the warning is emitted.
ADR-1235 step 1: route the trivial-converter runtime group (cursor, windsurf, augment, trae, codebuddy) off the inline install() agent loop onto the descriptor-driven installRuntimeArtifacts path. Establishes the converter-context foundation (pre-converter cross-cutting + no agent-stamp). Agent install output is byte-identical for all 16 runtimes (golden-parity, global + verified local). cline deliberately excluded (local rules-only). Closes#1763.
Address maintainer re-review nits:
- Changeset body no longer claims install-exports resolution (Failure 2 is
now handled upstream by #1511 / ADR-1508 Phase 2). This PR delivers the
.gsd-source marker writer, which fixes list/status (Failure 1).
- Add negative tests locking the `runtime === 'claude' && isGlobal` write
guard: a non-claude global install and a claude *local* install both
provision no marker. Verified must-fail-first against a relaxed guard.
Address second maintainer review on #1487:
- Blocker 1: getInstallExports now caches per runtimeConfigDir (Map) so a
no-arg warm-up via the legacy walk-up path cannot poison a later
getInstallExports(configDir) call. Add a regression test (verified to fail
on the singleton cache) proving the no-arg warm-up does not poison the
configDir-keyed resolution.
- Blocker 3: document the .gsd-source two-party provisioning contract in the
CONTEXT.md Runtime Artifact Layout Module entry (writer, reader, content,
guard, fall-through, package-root invariant, per-configDir cache).
Claude-Session: https://claude.ai/code/session_01XNT3SWgzjmEycNuweURDme
The lint-regression-test-names gate rejects new bug-NNNN-*.test.cjs files: a
new regression case must live in its owning module's test file. Relocate the
#1477 cases into tests/runtime-artifact-layout.test.cjs (the home of the
findInstallSourceRoot / getInstallExports resolution seam at the heart of the
bug) as a dedicated describe block, and delete the standalone file.
Claude-Session: https://claude.ai/code/session_01XNT3SWgzjmEycNuweURDme
* fix: recurse test discovery so subdir test suites actually run
scripts/run-tests.cjs discovered tests with a flat readdirSync(testDir),
silently excluding tests/observability/ (4 files), tests/dispatch/ (1) and
tests/installer-migrations/ (1) — 94 passing tests — from `npm test` and all
CI lanes. Walk the tree recursively (relative subpaths preserved), classify
suites by basename, and add a fail-on-zero-executed guard for suite/default
runs (escape hatch GSD_ALLOW_EMPTY_SUITE=1) while preserving the empty
--files/--files-from path the CI inert lane relies on.
Unit suite 735 -> 741 files; surfaces ADR-227's observability/dispatch seam.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: retire 5 verified-worthless tests
Adversarial verification confirmed these 5 prove nothing — their coverage is
provided more strictly elsewhere:
- enh-2790 'has a name: field' spot-checks (command-contract enforces /^gsd[:-]/)
- command-routing-hub duplicate construct + duplicate ERROR_KINDS assertions
- no-cjs-sdk-handsync-tooling (guarded files that never existed on main; bug-190
covers the real retired SDK artifacts)
- runtime-artifact-layout cline edge case (subsumed by the explicit-global test
and bug-782-cline-skills-emission)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: add ADR-218 release version-validation coverage
ADR-218 (reject leading-zero versions like 1.01.0; npm duplicate pre-check) had
zero tests — the logic lived only in release.yml bash. Add a test that extracts
the actual rejection regexes from the workflow and exercises them against a
boundary table (leading-zero/malformed rejected, valid accepted) plus structural
wiring assertions. Goes red if the regex is reverted to [0-9]+.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: redesign weak tests into behavioral, deterministic assertions
Per the ADR test audit, rewrite 27 weak test files (test-only, no source
changes) so each can go red for the defect it guards:
- kill pass-always assert.ok(true) placeholders (research-cli, worktree-baseref,
bug-260 security guard, eslint-rules x24, clusters '|| true')
- replace source-text grep with behavioral calls (install Kilo, sh-hook-paths,
plan-review-convergence) and add a repo-layout governance test
- de-flake real-clock/Math.random coupling (phase last_updated, bug-3707 mtime,
context-utilization property, feat-3594)
- fix independence/shared-state violations (bug-492 singleton, issue-844 tmpRoot,
core reapStaleTempFiles, active-workstream TTY, feat-488 GSD_HOME)
- strengthen property/shape-only tests (research-provider/store classification +
collision) and unconditional plugin.json schema validation (issue-766)
Verified: all 28 files run together 1220 pass / 0 fail / 1 skip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: add no-tautological-assert lint rule, error in test suite
New custom ESLint rule (eslint-rules/no-tautological-assert.cjs) bans asserts
that can never fail: assert(true)/assert.ok(<always-truthy literal>),
'cond || true' inside an assert, and equality asserts comparing two identical
literals. Wired as error on tests/**; full sweep confirmed zero existing
violations so the suite stays green. Prevents the placeholder-assert regressions
the audit redesigns just removed. RuleTester coverage added (6 valid, 8 invalid).
Note: no-only-tests was already enforced via eslint-plugin-no-only-tests, so no
duplicate rule was added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: gate new allow-test-rule exemptions to require an issue ref
ADR-456 requires any allow-test-rule exemption added after the ADR to carry a
tracking issue number, but nothing enforced it. New ratchet gate
(scripts/lint-allow-test-rule-refs.cjs, wired into lint:ci) fails when a NEW
allow-test-rule comment lacks a #NNN/URL reference; the 323 existing untracked
exemptions are grandfathered in an allowlist that ratchets down as they gain
refs. Red-green verified (novel untracked offender fails; compliant passes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: add ADR test-audit evidence report (#1192)
Full risk-first qa-test-architect audit of the ADR portfolio (37 ADRs + 4
platform lenses, adversarial verification of retire verdicts) that drove the
P0 discovery fix, ADR-218 coverage, 5 retires, 27 redesigns, and the two new
lint gates. Filed as point-in-time evidence under docs/issueevidence/, named
for tracking issue #1192.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: replace pre-existing raw NUL byte with escape in feat-3594 fixture
feat-3594's null-byte parser fixture contained a literal NUL byte (pre-existing
on next at b10e5681 — confirmed: base blob has 1 NUL, this fix has 0), which
made git treat the file as binary and would break grep/editors. Switch to the
\x00 escape; the runtime string value (a real NUL in the parser input) is
unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: address adversarial-review findings
Codex adversarial pass over the branch:
- capability-registry drift test no longer mutates the committed generated
capability-registry.cjs in place (concurrency hazard) — uses in-memory
checkPipeline comparison instead.
- allow-test-rule ratchet now detects exemptions in ALL comment forms (block
/* */ too, matching no-source-grep) so a block comment can't bypass it;
one newly-surfaced pre-existing offender grandfathered (323->324).
- install.test Kilo case asserts on what install(false,'kilo') actually writes
rather than manually calling configureKiloPermissions (masked the call site).
- issue-766 drops the undeclared transitive ajv dep for explicit structural
assertions from the schema fixture.
- adr-218 test notes the hotfix leading-zero gap is tracked in #1186.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: address code-review findings (subdir discovery, rule + test gaps)
xhigh code review surfaced 15 confirmed issues, all fixed:
- run-tests.cjs --files now resolves subdir tests by bare basename + handles
Windows backslash paths (ambiguous basenames error clearly).
- affected-tests-lib.cjs listTestFiles made recursive — the targeted CI lane was
silently dropping changed subdir tests (same false-green class the audit fixed).
- no-tautological-assert now catches 'true || cond' and empty []/{} equality.
- verify-test-quality: restore provenance-classification coverage, tighten the
writeFile circular-detection check, guard the module-level file read.
- sh-hook-paths: cover the global-install .sh delegation branch (#2045 guard).
- active-workstream null-guard runs deterministically (no longer skipped on TTY).
- adr-218 structural guards tightened (major/minor leading-zero; needs: membership).
- repo-layout AGENTS.md guard no longer false-alarms on equivalent refactors.
- cross-ai ordering guard fails red when the step is missing.
- issue-766 parses required fields from the schema fixture (auto-enforced).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: stub USERPROFILE alongside HOME in feat-488 (Windows parity)
The feat-488 redesign stubbed process.env.HOME but not USERPROFILE; os.homedir()
resolves from USERPROFILE on Windows, so the home stub was not hermetic there —
caught by windows-test-parity-guard (stubsHomeNoUserProfile). Save/set/restore
USERPROFILE symmetrically with HOME (delete-if-originally-undefined).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: reconcile allow-test-rule allowlist after rebase onto next
Rebasing onto current next pulled in merged PR #1170, which added
inventory-headings-countfree.test.cjs (a baseline allow-test-rule exemption) and
deleted inventory-counts.test.cjs. Grandfather the former and prune the latter so
the ratchet matches the merged tree. No new debt from this PR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* test(#947): add regression tests and update stale Hermes assertions
- Add bug-947-hermes-gsd-prefix.test.cjs: 12 TDD tests covering fresh
install canonical layout, bare-stem migration, manifest key format,
and non-Hermes runtime isolation
- Update hermes-skills-migration.test.cjs: bare-stem → gsd-prefixed
path and name assertions (#947 canonical layout)
- Update install-nested-layout.test.cjs: Hermes NEST matrix prefix ''
→ 'gsd-'
- Update install-regressions.test.cjs: Defect #1 now seeds bare-stem
dirs (help/, quick/) and asserts gsd-help/ canonical output; use
real GSD stems so readGsdCommandNames() migration finds them
- Update install-runtime-artifacts.test.cjs: Hermes nested layout and
legacy migration assertions align with gsd- prefix
- Update install.test.cjs: Hermes install test uses gsd- prefixed paths
- Update runtime-artifact-layout.test.cjs: prefix '' → 'gsd-'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#947): restore gsd- prefix on Hermes skills for canonical dispatch
Hermes skills were installing under bare-stem paths
(skills/gsd/<stem>/SKILL.md, name: <stem>) due to prefix: '' set in
ADR-3660 / #3664. This broke /gsd-<stem> dispatch and forced users to
invoke skills without the gsd- namespace prefix.
- src/runtime-artifact-layout.cts: change Hermes skillsKind prefix
from '' to 'gsd-'; skills now land at skills/gsd/gsd-<stem>/SKILL.md
with name: gsd-<stem>
- bin/install.js _runLegacyInstallMigrations: invert the #3664
migration — remove stale bare-stem dirs (using readGsdCommandNames()
to distinguish GSD-owned stems from user content), keep gsd-* dirs
which are now canonical
- bin/install.js _runLegacyUninstallCleanup: also remove bare-stem
dirs on uninstall for clean teardown
- bin/install.js uninstallRuntimeArtifacts: post-cleanup removes
DESCRIPTION.md and empty skills/gsd/ category dir on Hermes
- bin/install.js: remove skillListPrefix Hermes exception (now uses
shared 'gsd-' path)
- docs/adr/3660-runtime-artifact-layout-module.md: document #947
reversal of the bare-stem sub-decision
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: add changeset for #947 fix (#955)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#947): remove ALL pre-migration bare-stem Hermes skills on reinstall (adversarial review)
Replace readGsdCommandNames()-based bare-stem cleanup (which missed skills
not in the commands source tree, e.g. dev-preferences) with
_removeHermesBareStemDirs(), called AFTER the install loop when the exact
set of installed gsd-<stem>/ dirs is authoritative. For every gsd-<stem>/
written this run, the corresponding bare skills/gsd/<stem>/ is removed.
User-owned bare dirs with no gsd-<stem> counterpart are preserved.
Add two adversarial-review regression tests that FAIL on old code:
- bare skills/gsd/dev-preferences/ removed when gsd-dev-preferences/ installed
- user-owned bare dir with no gsd-<stem> counterpart is preserved (no over-deletion)
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 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>
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>
* feat(#789): elevate CodeBuddy — emit slash commands (+ document subagent/MCP scope)
Emit a CodeBuddy slash-command surface so GSD workflows appear in the
'/' menu, reaching parity with other elevated runtimes.
- Add convertClaudeCommandToCodebuddyCommand and register a commands/
artifact kind for the codebuddy runtime (commands/gsd-<name>.md),
consistent with the Cursor (#785) and Augment (#790) commands surfaces.
- Mark emitted skills user-invocable:false so the commands surface is the
sole '/' entry point (no duplicate /gsd-* entries); skills stay
model-invocable. CodeBuddy's SKILL.md supports this field.
- Normalize $HOME/.codebuddy (bare + slash) path forms in runtime
rewrites so --config-dir/local installs don't leak the default home.
- Report installed commands/ count on install; uninstall prunes gsd-*
commands while preserving user-owned commands.
Scope: subagents (~/.codebuddy/agents/) are already emitted by the
generic agents block (unchanged); no mcp.json is written (gsd ships no
MCP server, and CodeBuddy's mcp.json registers only external servers).
Closes#789
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#789): set changeset pr number to 830
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#784): emit native skills for OpenCode + Kilo runtimes
OpenCode and Kilo share a config schema and both discover on-demand
skills from skills/<name>/SKILL.md. The installer previously emitted
only flat commands (command/) and file-based agents (agents/) for these
runtimes. Add a shared OpenCode-family skill writer that stages each GSD
command as a spec-compliant SKILL.md (name matching the directory,
description 1-1024 chars), wired through the runtime artifact layout so
uninstall cleans skills/ automatically. Skills respect the active
install profile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#784): correct skill body paths + preserve user dev-preferences
Address adversarial-review findings:
- Add opencode/kilo cases to _applyRuntimeRewrites so staged SKILL.md
bodies are re-pointed from the converter's hardcoded default config dir
to the actual install target (fixes --local / --config-dir installs;
commands/agents already did this by applying pathPrefix pre-conversion).
- Preserve user-owned skills/gsd-dev-preferences across reinstall in
installOpencodeFamilySkills (snapshot+restore around the gsd-* prune),
matching installRuntimeArtifacts.
- Export installOpencodeFamilySkills and add regression tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#784): guarantee command/skill body parity, fix kilo-alt double-rewrite
Follow-up adversarial-review found the post-conversion path rewrite could
double-rewrite custom Kilo dirs (kilo -> kilo-alt -> kilo-alt-alt) because
the kilo pathPrefix is a $HOME (non-absolute) superset of the hardcoded
default base. Restructure so OpenCode/Kilo skills mirror copyFlattenedCommands
exactly: stage raw commands, apply pathPrefix BEFORE conversion via a new
shared applyOpencodeFamilyPathPrefix() helper (now used by both the command
and skill writers), then convert. This guarantees byte-for-byte command/
skill body parity for global, --local, and --config-dir installs and removes
the prefix-overlap hazard. Drop the fragile _applyRuntimeRewrites opencode/
kilo case. Strengthen the path regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(#784): derive opencode/kilo skills from the same staged command set
Pass the installer's _stageSkills() output directly to
installOpencodeFamilySkills instead of re-staging via the layout, so the
command/ and skills/ surfaces always cover the identical profile-resolved
set — including the --minimal/--core-only alias path, which stages
differently from a plain --profile=core. Verified: minimal install now
emits 8 commands and 8 skills.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#784): set changeset PR number to 810
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#784): fully escape backslashes in test helper (CodeQL js/incomplete-string-escaping)
Replace the dot-only escape `replace(/[.]/g, '\\.')` with a complete
regex-escape pattern `replace(/[\\.*+?^${}()|[\]]/g, '\\$&')` so all
regex metacharacters (including backslash itself) in `defaultBase` are
safely escaped before interpolation into `new RegExp(...)`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Cline added a global skills system (~/.cline/skills/<name>/SKILL.md) in
v3.48.0, but gsd treated Cline as rules-only and emitted zero skills
(getGlobalSkillsBase('cline')=null, empty artifact kinds). This makes gsd
emit skills for Cline at global scope, alongside the existing .clinerules.
- runtime-homes: getGlobalSkillsBase('cline') -> ~/.cline/skills (was null)
- runtime-artifact-layout: cline emits a skills kind for GLOBAL scope only
(local stays .clinerules-only), mirroring claude's scope dispatch
- install.js: convertClaudeCommandToClineSkill emits name+description-only
SKILL.md frontmatter (Cline/agentskills.io spec; no Claude-specific
allowed-tools/argument-hint/agent), hyphen-normalized + .cline/-rewritten
body; global cline routed through the skills path while .clinerules is
still written; _applyRuntimeRewrites cline case handles custom
CLINE_CONFIG_DIR; convertClaudeToCliineMarkdown also rewrites bare
~/.claude and CLAUDE_CONFIG_DIR
- docs: install-on-your-runtime.md documents Cline global skills vs local rules
- tests: converter (name+description-only), global emission, skills+.clinerules
coexistence, scope-aware layout, custom-dir paths, idempotency
Closes#782
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#785): write .cursor/commands/ as Cursor 1.6 slash-command surface
Cursor 1.6 (released 2025-09-12) introduced plain-markdown slash commands
in `.cursor/commands/<name>.md` — no frontmatter, invocable via `/` in the
Agent input. GSD previously emitted only `~/.cursor/skills/` for Cursor.
This PR wires a second artifact kind for `cursor` in
`runtime-artifact-layout.cts`: `convertedCommandsKind('commands', 'gsd-',
'convertClaudeCommandToCursorCommand', configDir)`. The new kind applies the
same `convertClaudeToCursorMarkdown` transforms (tool renames, brand
substitution, slash-command normalisation) and then strips YAML frontmatter
so the output is plain prose. Skills output is unchanged.
`stageCommandsForRuntimeFlat` in `install-profiles.cts` stages each source
`.md` as a flat `<stem>.md` in a temp dir; the existing `_copyStaged` commands
path then prefixes and copies to `<configDir>/commands/`.
`.cursor/mcp.json` is explicitly OUT OF SCOPE: GSD ships no MCP server; the
`mcpServers` schema cannot be usefully populated by the installer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(#785): address review nit
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(#604): rename get-shit-done/ runtime directory to gsd-core/
Renames the installed runtime directory `get-shit-done/` to `gsd-core/` so the
on-disk name matches the package (`@opengsd/gsd-core`), repo, and binary
(`gsd-tools`). The npm package name and binary are unchanged; npx/npm consumers
are unaffected.
Mechanical (bulk, ~90% of the diff):
- `git mv get-shit-done gsd-core`
- Swept path/identifier references across the repo via
`perl -pe 's/get-shit-done(?!-\w)/gsd-core/g'`. The negative lookahead
preserves the five legitimate slug variants that are NOT the directory:
get-shit-done-{OLD,cc,classic,cli,redux} (old package/repo names).
- Build/manifest wiring: package.json (bin, files, coverage globs),
tsconfig.build.json (outDir), ~86 .gitignore build-output entries,
stryker.config.mjs, scan-ignore files, install.js path strings.
- Frozen (not rewritten): CHANGELOG.md history; translated docs
(README.<locale>.md and docs/{ja-JP,ko-KR,pt-BR,zh-CN}/).
New logic (review here):
- src/installer-migrations/003-rename-get-shit-done-to-gsd-core.cts: a proper
ADR-0008 installer migration. On upgrade it walks the legacy
`~/.claude/get-shit-done/` tree, classifies each file via the prior install
manifest, and emits remove-managed / backup-and-remove for managed files
while PRESERVING unknown user-added files. Symlink-safe (skips a symlinked
root and symlinked entries; bounds-checks every path under configDir). The
framework rolls back on install failure. Emptied dirs may remain (framework
has no recursive dir-removal primitive) — documented.
- scripts/lint-legacy-dir-name.cjs: CI regression guard forbidding the bare
`get-shit-done` directory token (split token to avoid self-match; case-
insensitive; `(?!-\w)` lookahead allows the slug variants; allowlists
CHANGELOG, translated docs, and `gsd-allow-legacy-name` marker lines).
Wired into the lint-tests CI job.
- Restored scripts/lint-package-identity-drift.cjs detection regexes (the
mechanical sweep had wrongly rewritten the old-name patterns it exists to
detect) and marked them as intentional legacy references.
- TDD tests for the migration and the guard; do.md slash-command guard regex
tightened so a `/gsd-core/bin` path segment is not mistaken for a command;
changeset + docs/installer-migrations.md row added.
Breaking: the installed runtime path moves `~/.claude/get-shit-done/` ->
`~/.claude/gsd-core/`. Migration 003 removes the stale legacy dir's managed
files (preserving user files) on upgrade. Users with custom hooks/configs
hardcoding the old path must update them.
Closes#604
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): unsweep pending changesets + allowlist injection-example docs
CI fixes for the rename PR:
- Do not sweep pending .changeset/*.md (ephemeral release-note fragments,
like CHANGELOG); reverted those body edits so 5 pre-existing malformed
fragments (missing type/pr) no longer enter the PR diff and trip docs-lint.
Allowlisted .changeset/ in the legacy-name guard accordingly.
- Allowlisted TEST-EXAMPLES.md and docs/explanation/security-model.md in
prompt-injection-scan.sh: they contain intentional injection examples /
security-model prose; the path-reference rewrites are kept.
CodeQL alerts on this PR are pre-existing (alert lines unchanged by this PR;
none in the new migration/guard) and are out of scope for the rename.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): resolve CodeQL alerts surfaced on this PR
The rename diff touched files carrying pre-existing CodeQL findings; per the
no-pre-existing-dismissal rule, fixing every surfaced alert rather than waving
them off. All behavior-preserving:
- scripts/ci-test-scope.cjs: build the config-path match from string
.includes() instead of a RegExp over an arg-derived value (js/regex-injection).
- src/profile-output.cts: escape backslashes before pipe-escaping desc/safeName
so the table-cell escape is complete (js/incomplete-sanitization).
- tests/{bug-2643,bug-2808,docs-parity-live-registry}: two-pass HTML-comment
strip so a bare/unclosed `<!--` cannot survive (js/incomplete-multi-character-sanitization).
- tests/inline-plan-threshold: drop the no-op `\s`->`\s` identity replace,
keep the meaningful POSIX-class conversion (js/identity-replacement).
Verified: build:lib green; the touched test files + ci-test-scope + profile-output
suites pass; lint:legacy-name clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): correctly resolve remaining CodeQL alerts (regex-injection + sanitization)
The prior commit's fixes for two alerts were ineffective:
- ci-test-scope.cjs js/regex-injection: the alert is the CLI-arg-derived `file`
reaching static regex `.test(file)` calls (not the config rule). Removed ALL
regex over file/t — startsWith/includes/=== string checks + an isWindowsHint
helper — so there is no regex sink for the tainted value.
- js/incomplete-multi-character-sanitization (3 test files): a single
`.replace(/<!--...-->/g,'')` can let `<!--` re-form. Replaced with a fixpoint
loop (replace until stable) plus a final bare-opener strip.
Verified: no regex over file/t remains; ci-test-scope + the 3 test suites pass;
lint:legacy-name clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): make ci-test-scope + comment-strippers regex-free to clear CodeQL
CodeQL flags the regex PATTERNS syntactically (regex-injection on the
--files arg split; incomplete-multi-character-sanitization on the <!--...-->
replace), so loop fixes do not satisfy it. Made these paths regex-free:
- ci-test-scope.cjs splitFiles: char-by-char separator tokenizer (no /[,\\s]+/).
- 3 test files: indexOf/slice HTML-comment stripper (no .replace(/<!--/)).
Behavior preserved; ci-test-scope + the 3 suites pass; guard clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): unblock security base64 scan on the large rename diff
The security job hit its 10m timeout: base64-scan.sh choked on the binary
test fixture tests/feat-3594-parser-property-style.test.cjs (embedded NUL/
non-UTF8 bytes -> thousands of bogus blobs + "ignored null byte" warnings),
and the ~800-file rename diff is slow to scan regardless.
- scripts/base64-scan.sh: skip binary-by-content files (grep -Iq .) — they
can't carry base64-obfuscated *text* and feeding NUL bytes through the
per-line scanner is pathologically slow. collect_files already filtered
binary *extensions*; this catches binary *content* in text extensions.
- .github/workflows/security-scan.yml: raise the security job timeout 10m->30m
to accommodate very large diffs (the scan itself is unchanged).
Verified locally: scan skips the fixture, 0 "ignored null byte" warnings,
0 findings, exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): sweep get-shit-done refs introduced by merging next
The branch was updated with next (#614/#384/#618 etc.), which reference the
get-shit-done/ dir (still named that on next). Swept the stale references in
the merged files to gsd-core so the rename stays consistent and lint:legacy-name
passes:
- commands/gsd/discuss-phase.md (runtime-launcher shim paths)
- src/core.cts (getAgentsDir layout comments)
- tests/bug-384-agents-runtime-aware.test.cjs (require path to runtime lib)
Verified: guard 0 violations; build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): exclude gsd-core/ path segments from bug-3683 command cross-ref invariant
The #614 runtime-launcher shim added to discuss-phase.md references
`${_GSD_RUNTIME_ROOT}/gsd-core/bin/...`. bug-3683's REF_PATTERN excluded path-y
refs only via lookbehind, but `}` precedes `/gsd-core/` in the shim, so it
mis-read the directory path as a dangling `/gsd-core` command ref (same class as
the #604 bug-2954 fix). Added a trailing `(?![\w-]*\/)` so `/gsd-<x>/...` path
segments are not treated as slash-command references.
Verified locally on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22 image) full suite: 0 failures
- bug-3683 + bug-2954 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): lazily resolve findProjectRoot in gsd-tools (harden flaky CI)
CI intermittently failed state.test's gsd-tools subprocess with
"findProjectRoot is not a function" (flip-flopping across legs; not reproducible
on mac full suite, gsd-test linux full suite, test:unit, or state.test x8).
findProjectRoot is a re-export from core.cjs (sourced from project-root.cjs);
binding it via destructure at module-load can be undefined under a load-ordering
edge. Resolve it lazily at call time via a small wrapper so the lookup happens
after core.cjs is fully initialized.
Verified green on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22) full suite: 0 failures
- state.test.cjs: 106/106; gsd-tools loads cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#604): allowlist verification-patterns.md placeholder examples in secret scan
The rename git-mv'd references/verification-patterns.md into gsd-core/, pulling
it into the secret-scan diff. It documents stub/placeholder RED-FLAG env-var
examples (illustrative Stripe test-key / database-URL / API-key placeholders) —
not real credentials. Added it to .secretscanignore with the strict annotation,
mirroring the existing gsd-core/workflows/plan-phase.md exception.
Verified locally: secret-scan-lint --strict OK; secret-scan --diff origin/next
exits 0 with 0 findings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>