Mirror of code, issues, and PRs from the upstream gsd-build/get-shit-done,
which appears compromised or abandoned (maintainer unreachable since
2026-04-01; $GSD token linked to rug-pull).
- Adds rebrand notice block at top of English README
- Removes $GSD token badge and @gsd_foundation X badge (keeps Discord)
- Renames npm packages: get-shit-done-cc -> get-shit-done-redux,
@gsd-build/sdk -> @gsd-redux/sdk
- Updates all repo URLs across docs, workflows, package.json, bin/
- Updates ci@gsd-build -> ci@gsd-redux in workflow git identities
- Leaves CHANGELOG and .changeset/* alone (historical, time-stamped)
* test(#3426): add RED test for Codex Windows hooks .cmd shim requirement
Drive buildCodexHookWindowsShimIR (typed IR) + ensureCodexHooksJsonSessionStart
integration against mocked win32 platform. Counter-tests confirm darwin/linux
paths remain unchanged.
NOTE: Windows wall-clock verification depends on Docker matrix Windows
runners. Local test exercises the generator IR shape only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#3426): Codex Windows hooks use .cmd shim to avoid bash.exe POSIX-exec failure
Root cause: Codex on Windows runs hook commands from PowerShell/cmd. The previous
hooks.json command format was `"node.exe" "script.js"`. Codex's hook-dispatch shell
(Git Bash / MSYS) tried to POSIX-exec node.exe (a Windows PE binary) via execvp(),
which fails with ENOEXEC — reported as `bash.exe: cannot execute binary file`.
Fix: `ensureCodexHooksJsonSessionStart` now calls `buildCodexHookWindowsShimIR` on
win32 to write a .cmd shim alongside the .js hook file. cmd.exe executes .cmd files
natively via CreateProcess, bypassing the POSIX exec layer entirely. Non-Windows
paths (darwin, linux) are unchanged: they continue to use the node-runner command.
Also adds `gsd-check-update.cmd` to the codex-hooks-json managed-basename set so
reconcileCodexHooksJsonSessionStart correctly replaces stale node-runner entries on
reinstall.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(3426): update changeset to reference PR #3768
* fix(3426): fail-loud on Codex Windows shim-write failure instead of silently restoring broken command
Replace the silent fallback to `projectManagedHookCommand` (the old
`node.exe script.js` form) with an explicit warn-and-skip path.
When `atomicWriteFileSync` fails to write the `.cmd` shim, the previous
code silently called `reconcileCodexHooksJsonSessionStart` with the
legacy node-runner command. That command triggers the exact
`bash.exe: cannot execute binary file` POSIX-exec failure that #3426
exists to fix — so a successful-looking install was secretly restoring
the original bug.
New behaviour:
- Emit `console.warn` with the failure reason and a remediation hint,
matching the `${yellow}⚠${reset} Skipped …` idiom used at line 9098.
- Return `{ changed: false, wrote: false }` to skip registration for
this runtime entirely, so the outer caller can surface "NOT installed"
instead of "installed (but broken)".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(3426): typed-IR assertions on .cmd shim eol/quoting/passthrough + IR extension
Extend `buildCodexHookWindowsShimIR` to expose two new typed fields on
the returned IR object (CONTRIBUTING.md L558-L565 IR-first discipline):
eol: { cmd: '\r\n' } — CRLF is canonical for cmd.exe .cmd files
passthroughArgs: true — shim forwards all args via %*
Add a new describe block (Step 2b) with three IR-level assertions:
1. `eol.cmd === '\r\n'` — prevents silent EOL regression that could
break parsing on Windows versions that require CRLF.
2. `invocation.target` is the raw unquoted path (no shell-metachar
leakage) — quoting happens only at render time.
3. `passthroughArgs === true` — the %* forwarding contract is
explicitly typed so regressions fail before the text is rendered.
All assertions operate on the typed IR returned by the generator, NOT
on the rendered `.cmd` file content — text-matching is the anti-pattern
CONTRIBUTING.md L522-582 prohibits.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(3426): fix Windows CI failures — update hook-command filter patterns
Four test files filtered for managed hooks in hooks.json using the
literal string `gsd-check-update.js`. On Windows the PR-introduced
.cmd shim changes the hooks.json command to
`"path/gsd-check-update.cmd"` (no node prefix, .cmd extension), so
those filters matched 0 entries and 24 Windows subtests failed.
Fixes:
- bug-2760-codex-install-defensive.test.cjs (7 filters): change
`/gsd-check-update\.js/` → `/gsd-check-update/` to match both
.js (POSIX) and .cmd (Windows) commands.
- bug-3357-codex-legacy-hooks-json-migration.test.cjs (3 filters):
same `.js` → no-extension change.
- bug-3427-3433-codex-install-shape.test.cjs (2 filters): same fix;
add explanatory comment to uninstall assertion.
- codex-config.test.cjs (9 filters + 1 exact-command assertion):
bulk-replace all `hooksJsonCommands.filter(cmd => cmd.includes('gsd-check-update.js'))`
with `gsd-check-update`; make the `fresh CODEX_HOME` test platform-
aware — on win32 assert `.cmd` shim path, on POSIX assert the
existing `"runner" "script.js"` form (#3017).
All four suites pass locally (macOS / darwin). Windows subtests
verified against the Windows CI failure log patterns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3426): address pr-review-toolkit + codex review findings
- fix(uninstall): add gsd-check-update.cmd to gsdHooks cleanup list so
the .cmd shim is removed from disk on Windows uninstall (was left as
orphan artifact — silent failure post-uninstall)
- test(3426): add uninstall test asserting gsd-check-update.cmd is
deleted from hooks dir after `uninstall(true, 'codex')` (no coverage existed)
- fix(comment): correct JSDoc on buildCodexHookWindowsShimIR — shim
content is three-line @ECHO OFF/@SETLOCAL/@runner snippet, not bare
`@node "script.js" %*` as the old comment claimed
- fix(comment): update stale assertion message in codex-config.test.cjs
L1457 — said "config.toml references it" but the hook is in hooks.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(workflows): add word-boundary anchoring to UI safety gate grep
Replace unanchored grep -iE "UI |..." alternation with POSIX ERE
word-boundary-anchored form:
LC_ALL=C grep -iE "(^|[^[:alnum:]])(UI|...)([^[:alnum:]]|$)"
Unanchored form matched 'ui' inside 'requirements', 'view' inside
'overview' and 'review', 'form' inside 'performance'/'platform'/
'transform' — producing HAS_UI=0 on 100% of standard roadmap phases
(every phase contains a **Requirements**: field).
Fix applied to both plan-phase.md:625 and autonomous.md:284.
LC_ALL=C added for POSIX locale portability on both BSD and GNU grep.
Closes#3706
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(workflows): add regression tests for UI safety gate false-positives (#3706)
- bug-3706-ui-safety-gate-false-positives.test.cjs: 36-test suite covering
both plan-phase.md and autonomous.md gate behavior; verifies that
Requirements/overview/performance/platform/transform/review/build/screening
do NOT trigger the gate, while standalone UI/view/form/screen/dashboard/
component/lowercase-ui/hyphenated-non-UI DO trigger it.
- autonomous-ui-steps.test.cjs: update stale assertion that checked for the
old broken grep pattern; now asserts the word-boundary-anchored form.
Test strategy: extract the POSIX ERE pattern from the workflow file and
simulate grep match semantics in JS (no shell exec, no source-grep).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(changeset): add Fixed fragment for PR #3718 (UI safety gate false-positives)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(workflows): document compound-token boundary contract; add comment to gate
Addresses adversarial review finding: word-boundary anchoring intentionally
does not match tokens embedded in compound alphanumeric words (e.g.
"microfrontend", "dashboardWidget", "uiSpec"). This is correct behavior —
gsd-roadmapper generates natural English prose, not camelCase compounds.
Hyphenated forms ("micro-frontend") and spaced forms are caught by the
anchored pattern (hyphen is [^[:alnum:]]).
Add inline comment in both workflow files explaining the pattern intent,
the false-positive prevention, and the compound-word contract.
Add 4 tests (2 per workflow) documenting the compound-word contract:
- "microfrontend" (compound) must NOT trigger gate (documented behavior)
- "micro-frontend" (hyphenated) MUST trigger gate (correct true-positive)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3718): replace shell grep gate with shell-free Node.js helper
Moves UI safety gate logic from `LC_ALL=C grep -iE` (silently broken on
Windows PowerShell — locale env-var prefix not recognised by pwsh) to
`bin/lib/ui-safety-gate.cjs` (Node.js, reads via stdin to avoid ARG_MAX).
Path is anchored via `git rev-parse --show-toplevel` (GSD_REPO_ROOT) to
avoid CWD-sensitive failure when Claude Code executes from a subdirectory.
Word-boundary regex is identical to the original POSIX ERE pattern:
(^|[^a-zA-Z0-9])(TOKEN)([^a-zA-Z0-9]|$)
Exit codes mirror grep: 0 = UI found, 1 = not found.
Tests: 51/51 pass — includes spawnSync shell:false + stdin cross-shell
portability tests and ARG_MAX large-input test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3706): address pr-review-toolkit + codex review findings
- Multi-token-per-line: use matchAll to capture all UI tokens
- Add test for multiple distinct tokens on same line
- Clarify ASCII vs POSIX [:alnum:] in word-boundary comment
- Correct misleading "path anchored" comment in plan-phase/autonomous workflows
- Remove UI_GATE_PATTERN from module.exports (internal implementation detail)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(state): restore ACQUIRE_LOCK_RETRY_ERRNOS in acquireStateLock (#3718)
Commit 473c279c removed ACQUIRE_LOCK_RETRY_ERRNOS and replaced the
correct `throw err` path with `return lockPath`, which silently
"succeeds" on any non-EEXIST error — allowing two concurrent processes
to both hold the lock simultaneously and causing lost updates.
This restores the set of recoverable transient errno codes (Docker
overlay-fs EINVAL/EIO/ENOENT, NFS ESTALE, POSIX EAGAIN/EINTR, Windows
EPERM/EBUSY) that should retry, and restores `throw err` for genuinely
fatal codes. Equivalent to commit 47983914 on main.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3683): normalize /gsd:<cmd> → /gsd-<cmd> in command, workflow, and reference bodies
Extends #3677's agent-body normalizer to all body text staged through
copyWithPathReplacement (commands, workflows, references). The initial
isCommand guard was structurally redundant — normalizeAgentBodyForRuntime
already self-gates on shouldNormalizeHyphenNamespaceInAgentBody(runtime),
so dropping it covers all hyphen-name runtimes (Claude / Qwen / Hermes)
without affecting colon-canonical runtimes (Gemini).
Addresses the user-visible symptom in #3683: workflows like
get-shit-done/workflows/discuss-phase.md (7 colon refs) leaked /gsd:<cmd>
markers to the model context, which the model echoed at the end of
/gsd-discuss-phase runs.
Source-prose drift caught by the new cross-reference invariant test:
- commands/gsd/plan-phase.md: removed a slash-form mention of the
deleted /gsd-research-phase command (#3042)
- commands/gsd/profile-user.md: replaced a slash-form artifact
reference with a backticked bare name (the referenced item is a
skill config, not a user-callable slash command)
Tests:
- tests/bug-3683-command-colon-namespace-leak.test.cjs — runtime-form
regression for commands/gsd/*.md staging
- tests/bug-3683-command-cross-reference-invariant.test.cjs — locks
cross-reference coherence: every /gsd-X / /gsd:X reference in a
command body must resolve to commands/gsd/X.md (so a future rename
forces every cross-reference to update)
- tests/bug-3683-workflow-colon-namespace-leak.test.cjs — runtime-form
regression for workflows + references; negative test for gemini
asserting the colon form is preserved
Fixes#3683
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(changeset): remove undefined cycle reference
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3677): normalize /gsd:<cmd> → /gsd-<cmd> in agent bodies for hyphen-name runtimes
Closes#3677
The executor agent bodies installed to `~/.claude/agents/gsd-*.md` (and
the Qwen / Hermes equivalents) still contained retired `/gsd:<cmd>`
colon-form references in their prose. Every GSD skill / agent has
registered under the canonical hyphen `name:` form since #2808, so the
colon form is unroutable — Claude Code rejects it with `Unknown command:
/gsd:execute-phase. Did you mean /gsd-execute-phase?`. Reporter measured
~28 agent files / ~96 leaked refs on a full Claude global install.
This is the agent-body surface of the same class of bug as the two
already-fixed sibling surfaces:
- #3583 (SKILL.md skill bodies) — fixed via #3629
- #3584 (user-facing runtime "Next step: /gsd:…" emissions) — fixed via #3606
The agent-body surface in `bin/install.js`'s agent install loop was
never covered: the Claude-default / Qwen / Hermes branches register
hyphen `name:` but copy bodies verbatim (Qwen/Hermes do branding-only
swaps; Claude-default falls through with no body conversion at all), so
the colon refs leak.
Fix:
1. Add a pure predicate `shouldNormalizeHyphenNamespaceInAgentBody(runtime)`
backed by an explicit allow-list `HYPHEN_NAME_AGENT_RUNTIMES =
{claude, qwen, hermes}`. Unknown / future runtimes default to false
(better to leak than to mangle).
2. Add `normalizeAgentBodyForRuntime(content, runtime, cmdNames)` that
conditionally applies the shared `transformContentToHyphen` from
`scripts/fix-slash-commands.cjs` (same transform #3629 used for
SKILL.md bodies).
3. Call `normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames())`
in the agent install loop right before `fs.writeFileSync`, so it
composes with all the existing runtime branches. For Gemini and
self-converting runtimes the predicate short-circuits, so their
convertClaudeAgentToXAgent output is not re-rewritten.
4. Export both functions from `bin/install.js` for the regression test.
Regression test (`tests/bug-3677-agent-colon-namespace-leak.test.cjs`):
24 tests across 4 groups — A (exports exist), B (predicate matrix
covering all 15 runtimes in the layout table + an unknown-runtime case),
C (normalize helper applies/skips correctly for claude/qwen/hermes/
gemini/copilot), D (sanity check of the underlying transform).
Verification:
- node --test tests/bug-3677-*: 24/24 pass
- Sibling-regression (6 slash-namespace test files): 76/76 pass
- All install-minimal-all-runtimes suites: 54/54 pass after `npm run
build:sdk` (the prior 27 fails were pre-existing — missing local
sdk/dist build, not introduced by this change)
- Full docker suite (gsd-test-summary): 11769/0 fail
(11751 baseline + 18 new = my 24 tests with some collateral pickups)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(changeset): set PR number 3680 (Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>)
* test(3677): port real-source efficacy + idempotence tests from #3681
Adds describe group E with 5 behavioral tests credited to John Turner
(johnzilla, PR #3681 — closed in favor of this PR by its author):
E0: command roster is populated and includes symptom commands
E1: every agents/gsd-*.md transforms clean — real-source efficacy
E2: idempotent — repeat transform on hyphenated input is a no-op
E3: word boundary — /gsd:plan-phase-extra is not a roster match
E4: rewrites bare gsd:<cmd> shorthand (no leading slash)
E1 is the test that would have caught the original bug — pure-function
tests can pass while the install.js wiring silently bypasses the
transform. E2 guards against double-rewrite mangling during reinstall.
29/29 tests pass (24 original + 5 ported).
Co-Authored-By: John Turner <johnzilla@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: John Turner <johnzilla@users.noreply.github.com>
Phase 2 of #3660 / ADR-3660. Routes both lifecycle verbs through the
Runtime Artifact Layout Module landed in Phase 1 (#3663):
- Add installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile)
and uninstallRuntimeArtifacts(runtime, configDir, scope) as the public
orchestrators. Both pre-prune stale gsd-* entries before staged copy;
installRuntimeArtifacts brackets the prune+copy with preserveUserArtifacts /
restoreUserArtifacts so user-owned content (e.g. gsd-dev-preferences) survives
wipe-and-replace for claude/qwen/hermes runtimes.
- Add applyRuntimeContentRewritesInPlace as the per-runtime path/branding
post-stage step (preserves byte-output equivalence with the legacy
copyCommandsAs* pipeline, including Qwen/Hermes branding rewrites).
- Add _copyStaged, _removeGsdEntries kind-aware filesystem helpers.
- Add _runLegacyInstallMigrations, _runLegacyUninstallCleanup as thin
dispatchers over existing ADR-0008 legacy migrations (Hermes flat->nested
per #2841, dev-preferences-as-skill per #2973). For Hermes, also clean up
the intermediate skills/gsd/gsd-*/ layout that pre-Phase-2 installs left
on disk.
- Delete the 9 copyCommandsAs*Skills functions (Codex / Cursor / Windsurf /
Trae / CodeBuddy / Copilot / Claude / Antigravity / Augment) and the
_copyCommandsAsSkillsViaConverter helper. All test entry points migrated
to call installRuntimeArtifacts directly through the unified seam.
- Collapse the 9-branch uninstall ladder to one uninstallRuntimeArtifacts
call plus preserved non-layout side-effects (Codex TOML, Copilot
instructions, hooks).
- Unify install dispatcher: a single _isSkillsRuntime gate routes all 11
skills runtimes through installRuntimeArtifacts for both full and core/
minimal profiles. Removes 11 per-runtime if-else branches (3 minimal-mode
shim branches + 8 dead after-the-gate branches).
Net delta on bin/install.js: 11,495 -> 11,174 (-321 LOC).
New tests:
- tests/install-uninstall-layout-loop.test.cjs (34 tests) - per-runtime
fixture assertions on install/uninstall/legacy-migration ordering.
- tests/install-hermes-regressions.test.cjs (6 tests) - covers the six
defects surfaced by iterative review: Hermes upgrade leaves stale dirs,
--hermes --profile=core fall-through, --qwen --profile=core fall-through,
minimal-mode dev-preferences migration skipped (Hermes/Qwen/Claude-global),
and ordering bug in _runLegacyInstallMigrations.
Existing tests (10,038 prior + 40 new) all green: 10,078/10,078 pass.
Refs #3664
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
convertClaudeCommandToClaudeSkill(content, skillName, runtime, cmdNames) uses
the runtime arg to gate Hermes/Qwen branding and version: frontmatter emission
(#2808, #3583). Previously the layout module called it with only 2 args so the
runtime-specific formatting was never applied.
Changes:
- skillsKind() gains a runtime param (5th arg after converterName).
- stage() computes cmdNames = readGsdCommandNames() once per call (perf: avoids
repeated fs.readdirSync in the converter) and wraps the real converter so all
4 args are forwarded.
- readGsdCommandNames added to bin/install.js GSD_TEST_MODE exports block so the
stage closure can call it without requiring the script separately.
- All switch arms updated to pass the canonical runtime string.
Converters that do not inspect runtime/cmdNames (Cursor, Codex, Copilot, etc.)
accept and ignore the extra arguments — no behaviour change for those runtimes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(3579): ship graphify hook + lib/ helper through build-hooks + install
`scripts/build-hooks.js` `HOOKS_TO_COPY` did not include
`gsd-graphify-update.sh` (added in #3347 / PR #3557), so it never landed
in `hooks/dist/` and `bin/install.js` — which `readdirSync`s the dist —
never copied it to `~/.claude/hooks/`. The hook's detached rebuild
helper at `hooks/lib/gsd-graphify-rebuild.sh` was also silently dropped
because both build-hooks.js (flat allowlist) and bin/install.js (readdir
+ isFile filter) only walked top-level files.
The published tarball gap (Gap 3 in the issue body) is not reproduced
on origin/main — `npm pack --dry-run --json` shows both source files
are present today. Only Gaps 1 and 2 are in scope.
Changes:
- Add `gsd-graphify-update.sh` to `HOOKS_TO_COPY`.
- Add `HOOKS_SUBDIRS_TO_COPY = ['lib']` and copy whitelisted hook
subdirectories (`hooks/<dir>/*` → `hooks/dist/<dir>/*`) in
build-hooks.js, with the same syntax-check + atomic-rename path the
top-level loop uses.
- `bin/install.js`: when copying `hooks/dist/`, recurse one level into
any directory entry so subdir files (e.g. `lib/gsd-graphify-rebuild.sh`)
land at the mirrored target path the hook's REBUILD_SCRIPT lookup
expects. Top-level if/else structure for files is unchanged.
Regression test `tests/bug-3579-graphify-hook-publish.test.cjs`:
- Drift guard: every top-level `hooks/*.sh` must appear in
`HOOKS_TO_COPY`. Generalizes beyond graphify so the next .sh hook
added cannot regress.
- After build: `hooks/dist/gsd-graphify-update.sh` AND
`hooks/dist/lib/gsd-graphify-rebuild.sh` exist.
- After install: both files land at the target, and no
"Missing expected hook: gsd-graphify-update.sh" warning is emitted.
Fixes#3579
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(3579): replace source-grep drift guard with filesystem-behavior assertion
The Gap-1 drift guard read scripts/build-hooks.js as text and regex-parsed the
HOOKS_TO_COPY literal, which tripped lint-no-source-grep and is brittle under
refactors. Replace with a behavior-based assertion: run the build, then for
every top-level hooks/*.sh assert hooks/dist/<name> exists. Strictly stronger
— catches both the original allowlist gap and any future regression that
silently drops a hook for any other reason.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3406): detect + warn on stale @gsd-build/sdk@0.1.0 global shadow
`@gsd-build/sdk@0.1.0` is the only published version of the standalone
SDK package (the SDK now ships embedded in get-shit-done-cc). When a
user has the stale 0.1.0 globally installed, its `gsd-sdk` bin shadows
the shim get-shit-done-cc wires up — and the 0.1.0 binary only knows
`run | auto | init` (no `query`), so every `gsd-sdk query <cmd>` call
from skills and hooks fails silently until the user runs
`npm uninstall -g @gsd-build/sdk`.
Per maintainer triage decision (option 2): detect at install time and
surface the remediation, instead of waiting for the user to discover
the failure through a broken workflow.
Changes:
- New helper `detectStaleStandaloneSdk(runNpmLs)` (pure function,
accepts an injected executor). Returns `{stale: true, version}` when
`@gsd-build/sdk` is in the top-level dependency tree; returns
`{stale: false}` for every other input including executor throws,
malformed JSON, missing keys, and null/undefined returns.
- New helper `formatStaleStandaloneSdkWarning(info)` — message names
the package, version, the exact `npm uninstall -g @gsd-build/sdk`
remediation command, and references the issue.
- Call site in `install()` for `isGlobal` runs. Spawns
`npm ls -g @gsd-build/sdk --json --depth=0`, recovers the JSON
attached to the non-zero-exit error (npm's "absent" signal),
forwards to detectStaleStandaloneSdk, prints the warning if stale.
Best-effort: any failure is swallowed so detection never blocks
install.
- `GSD_SKIP_STALE_SDK_CHECK=1` opt-out for CI/test environments that
need silence (also used by the install-side test below).
Regression test `tests/bug-3406-stale-sdk-shadow-detect.test.cjs`:
- 8 unit tests pinning every detectStaleStandaloneSdk path (exported,
absent, present, executor-throws, malformed-JSON, no-deps-field,
null/undefined, format).
- 1 install-side end-to-end test confirming that when the package is
absent, the install run does NOT mention `@gsd-build/sdk` or `#3406`
in stdout. Uses a per-test `npm_config_prefix` so the test never
depends on the host's npm dependency tree.
Fixes#3406
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3406): correct changeset pr field — 3406 was the issue number, not the PR
CodeRabbit caught that .changeset/fix-3406-detect-stale-sdk-shadow.md
referenced `pr: 3406` (the issue number) instead of `pr: 3641` (the
PR number). Per CONTEXT.md PRED.k329 changeset frontmatter pr: must
reference the pull request number.
Local: docker gsd-test-summary 11214/0 on plex2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(3406): two CR follow-ups on bin/install.js stale-shadow check
Round-2 CodeRabbit findings on PR #3641:
1. bin/install.js:7769 — GSD_SKIP_STALE_SDK_CHECK opt-out now matches
only explicit "1" / "true" / "yes". The previous any-truthy check
silently disabled the warning for `GSD_SKIP_STALE_SDK_CHECK=0` and
`GSD_SKIP_STALE_SDK_CHECK=false`.
2. bin/install.js:10568 — detectStaleStandaloneSdk now gates stale=true
on version === '0.1.0' (the known-bad shadow). Any newer published
version is intentional and must not flag a "stale shadow" warning
on every install. Added a regression test for non-0.1.0 versions
(1.50.0-canary.0 and 2.0.0) returning stale:false.
Local: 11/11 in the bug-3406 test file + docker gsd-test-summary 11215/0
on plex2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add first-class grok runtime support (maps to ~/.agents); wire installer, runtime-homes.cjs and sync-skills; update Grok Build engine in local ~/.agents to latest; record session progress in discussion doc
* Normalize gsd colon references to hyphen in generated Claude SKILL.md bodies using the shared transformer. Fixes#3583.
* Refine #3583 implementation after review: cache command names, improve tests, clean up comments
* Harden gsd colon-to-hyphen transformer with bidirectional word boundaries and body-only regression guard
* Track quick-wins batch status and local session notes for #3583/#3579 handoff
* Port installer robustness (hoist copyLibDir + selective Codex hooks) from 3579 to make Codex tests pass on this branch. Fixes ReferenceError and prevents extra hook pollution in Codex installs.
* Restore #3583 transformer wiring and Codex .sh GSD_VERSION branch lost in 50ff8f17 port
Commit 50ff8f17 ('Port installer robustness from #3579') accidentally reverted:
- the top-level require of transformContentToHyphen/readGsdCommandNames
- the body normalization inside convertClaudeCommandToClaudeSkill
- the Codex hook loop's .sh branch with {{GSD_VERSION}} substitution
These were the actual #3583 fix and the Codex half of the #2136 invariant.
Failing tests fixed: bug-2808-skill-hyphen-name, claude-skills-migration #3583
case, bug-2136 Codex .sh substitution.
* Exempt 'sync-skills' slug from docs-parity check (skill dir name in path references)
gsd-sync-skills is an installed Claude skill name and a workflow file but
not a registered slash command. The docs-parity regex catches /gsd-sync-skills
from filesystem path references like ~/.agents/skills/gsd-sync-skills/ in
docs/discussions/grok-build-support-2026-05.md.
Adding to INTERNAL_COMPONENT_SLUGS matches the existing exemption pattern
for 'statusline', 'workspaces', 'graphify-update', etc.
* Restrict hooks/lib/ install to hook-enabled runtimes and managed allowlist
Codex/Copilot/Cursor/Windsurf/Trae/Cline already skip the hooks block but were still copying hooks/lib/ helpers, contradicting the downstream Codex comment. Gate the call on the same runtime check and pass GSD_HOOK_LIB_FILES so install scope matches the uninstall/manifest scope.
`npx get-shit-done-cc@latest --codex` aborted with
"installer migration blocked pending user choice" listing 12 hooks/gsd-*
files. Those files are part of the GSD npm distribution
(hooks/gsd-prompt-guard.js, hooks/gsd-context-monitor.js, etc.), not
user-owned content, so asking the user to choose between keep/remove for
them was a UX bug, not a real choice. The installer is about to write
the fresh bundled versions in their place.
Root cause: `classifyPromptUserAction` in
get-shit-done/bin/lib/installer-migration-report.cjs knew two
unambiguous categories (`stale-sdk-build-artifact`, `user-facing-skill`)
but had no rule for the bundled GSD hooks. The first-time-baseline scan
classified them as `stale-gsd-looking` prompt-user blockers, and
`assertInstallerMigrationsUnblocked` threw.
A second gate compounded the bug: the safe-default resolver in
bin/install.js was wrapped in `if (!_migrationIsTty)`, so even with a
correct classification rule, TTY runs (every `npx get-shit-done-cc`
invocation) skipped the resolver and went straight to the hard throw.
Fix:
1) Add `hooks/gsd-<name>.(js|sh|cjs|mjs)` to `classifyPromptUserAction`
as `bundled-gsd-hook` → `remove`. The regex is anchored at the
top-level `hooks/` directory so nested paths like
`hooks/gsd-helpers/index.js` (or any user-owned helper directory) do
NOT auto-classify.
2) Remove the `!_migrationIsTty` gate from the resolver call in
bin/install.js. The classifier-based path is unambiguous and must
apply regardless of TTY; the env-override branch
(GSD_INSTALLER_MIGRATION_RESOLVE) still applies only when isTty=false
inside the resolver, preserving the #3541 semantic.
Regression test added
(tests/bug-3610-installer-migration-bundled-hooks-classification.test.cjs):
- Positive: hooks/gsd-*.{js,sh} → category=bundled-gsd-hook, choice=remove.
- Counter-test: hooks/my-custom-hook.js → classifier returns null
(user files are preserved).
- Boundary: hooks/gsd-helpers/index.js → classifier returns null
(nested directories don't auto-classify).
- End-to-end: 12 reporter-exact bundled hooks + empty manifest →
resolver clears every blocker, assertInstallerMigrationsUnblocked
does not throw.
Test exercises the real installer-migration code path
(`runInstallerMigrations` + `resolveInstallerMigrationPromptsForNonTty`
+ `assertInstallerMigrationsUnblocked`) — no source-grep, no raw text
matching on outputs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-up adjustments after running the full suite:
1. Reverted the rewriteTomlKeyLines() change. The original
`match.keyRaw || key` fallback respects user ownership of pre-existing
legacy lines (#2760 defensive principle). My fix now applies the
canonical-vs-legacy split at the INSERTION points only: fresh installs
write `hooks = true`, but a pre-existing user-authored
`codex_hooks = true` is preserved verbatim. Codex's own runtime
legacy_key alias handles backward-compat at the Codex layer.
2. Updated 12 test cases in tests/codex-config.test.cjs that pinned the
old fresh-write key. These assertions now expect canonical `hooks` for
fresh-write scenarios; tests covering legacy-line preservation already
pass against the narrowed fix without further edits.
Also updated bug-3566 regression-test cases for the legacy-preservation
path — they now verify that user-owned `codex_hooks` survives an install.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes#3566
Codex itself marks codex_hooks as a legacy_key in
codex-rs/features/src/legacy.rs. The canonical current Codex feature flag
under [features] is hooks. The GSD installer was still writing codex_hooks
on every fresh install / reinstall, leaving deprecated config behind on
Codex CLI >= 0.130.0.
Introduces a canonical/legacy split in bin/install.js:
CODEX_HOOKS_FEATURE_KEY = 'hooks'
CODEX_HOOKS_FEATURE_LEGACY_KEYS = ['codex_hooks']
isCodexHooksFeatureKey(key) // recognizes canonical OR any legacy alias
Threaded through:
- ensureCodexHooksFeature(): emits canonical hooks; recognizes legacy
codex_hooks; migrates legacy -> canonical in section, root-dotted, and
block-fallback insertion paths.
- hasEnabledCodexHooksFeature(): accepts either canonical or legacy.
- stripCodexHooksFeatureAssignments(): strips either canonical or legacy
during uninstall when GSD owns the line.
- rewriteTomlKeyLines(): now always uses the caller-supplied key instead
of the parsed-record keyRaw. The old `match.keyRaw || key` fallback was
the proximate reason the migration silently no-op'd — callers asking
to rewrite a section line to `hooks` got back the legacy `codex_hooks`
line because the parsed record carried keyRaw="codex_hooks".
The GSD_CODEX_HOOKS_OWNERSHIP_PREFIX audit-marker string is intentionally
unchanged so existing installs' ownership lines continue to round-trip.
Tests:
- New tests/bug-3566-codex-hooks-feature-canonical-key.test.cjs (6 cases):
fresh install writes hooks; section-form legacy migrated; root-dotted
legacy migrated; user-owned hooks preserved; uninstall removes
GSD-owned canonical; uninstall preserves user-owned hooks.
- Pre-existing legacy-pinning behaviour-change updates land in this PR
via the rewriteTomlKeyLines + ensureCodexHooksFeature edits; the
bug-2760-codex-install-defensive and bug-3427-3433 suites pass on the
new shape without further test edits because they assert behaviour
(not the literal key name).
Docs:
- docs/ARCHITECTURE.md row for Codex notes [features].hooks (canonical,
legacy codex_hooks recognized and migrated forward).
- docs/installer-migrations.md row updated to reflect canonical key
and the new Codex 0.130.0 features.hooks compatibility sentinel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes#3562
Codex CLI 0.130.0 only registers commands from skills/<name>/SKILL.md; it
does NOT auto-discover from get-shit-done/workflows/*.md or agents/*.md.
Prior installer logic (#3427/#3433) removed the gsd-* skill copies under the
assumption that Codex would discover the official skills directly. That
assumption does not hold — users ended up with workflows on disk and zero
$gsd-* entrypoints after restart.
Fix: re-wire copyCommandsAsCodexSkills() (line 5519, already present) into
the Codex install dispatch path (line 8090). Generates one
~/.codex/skills/gsd-<name>/SKILL.md per commands/gsd/*.md — same shape the
Copilot/Antigravity/Cursor/Windsurf/Augment/Trae installs use.
Behaviour change: the pre-existing test in bug-3427-3433-codex-install-shape
asserted "does not regenerate gsd-* skill copies". Updated it to assert
the new behaviour (regenerate gsd-* with refreshed body, preserve non-gsd
user skills).
Tests:
- New tests/bug-3562-codex-install-skill-surface.test.cjs (4 cases):
- skills/gsd-help/SKILL.md exists
- SKILL.md has YAML frontmatter with name: gsd-help
- >= 10 gsd-* skill directories produced (lower-bound, currently 67)
- Pre-existing custom-user-skill directory preserved
- tests/bug-3427-3433-codex-install-shape.test.cjs: updated to assert
regeneration + body refresh + unrelated-skill preservation.
Verified by re-running the issue's repro: `node bin/install.js --codex
--global --config-dir <tmp>` now produces 67 gsd-* skills and the target
~/.codex/skills/gsd-help/SKILL.md exists with valid frontmatter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
migrateCodexHooksMapFormat re-emitted the raw `[hooks.<X>]` path segment as
the leaf TOML key of the new `[[hooks.<EVENT>]]` block. When the legacy
table key was a `<file>:<event>:<line>:<col>` location identifier and the
real event lived in an `event = "..."` body field, the migration emitted a
header like `[[hooks."C:\\Users\\helen\\.codex\\config.toml:session_start:0:0"]]`
that Codex 0.124.0+ refuses to load — causing `npx get-shit-done-cc@latest`
to abort the Codex runtime install on Windows configs that pre-date AoT.
Mirror the flat-AoT branch in the map-format and stale-namespaced-AoT
branches: when the section body declares `event = "..."`, that name wins
as the leaf key and `event` is excluded from the re-emitted handler body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(skill-deps): add requires: frontmatter to all 51 skills with cross-skill references
Mechanical migration from docs/research/data/2026-05-12-skill-audit.json.
Every skill whose body references another GSD skill now declares those
dependencies in `requires:` YAML frontmatter (flow-style array).
Notable: discuss-phase, plan-phase, and execute-phase all reference `phase`,
which confirms the latent gap in MINIMAL_SKILL_ALLOWLIST — `phase` is pulled
by the core loop but was never in the allowlist. The profile closure model
(ADR-0010 Phase 1) resolves this automatically.
Closes part of #3408.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(skill-surface-budget): add PROFILES map, resolveProfile, loadSkillsManifest, staging, marker IO
Implements the Skill Surface Budget Module core (ADR-0010, Phase 1):
- PROFILES Object.freeze map: core (6 skills), standard (~13), full ('*')
- loadSkillsManifest: parses requires: frontmatter from commands/gsd/*.md
into a Map<stem, string[]> without external YAML dep
- resolveProfile({modes, manifest}): computes transitive closure over the
requires: graph; composable (modes=['core','audit'] unions closures)
- stageSkillsForProfile / stageAgentsForProfile: filesystem staging with
same exit-cleanup machinery as the legacy stageSkillsForMode
- readActiveProfile / writeActiveProfile: .gsd-profile marker round-trip
- Back-compat shims preserved: MINIMAL_SKILL_ALLOWLIST, isMinimalMode,
shouldInstallSkill (overloaded), stageSkillsForMode — all legacy tests pass
The phase latent bug is now resolved by closure: discuss-phase, plan-phase,
and execute-phase all require phase, so any profile including any of them
automatically includes phase via transitive closure.
Tests: 22 manifest+resolve, 9 stage, 10 marker (41 new tests, all green).
Back-compat anchor: 80/80 passing.
Closes part of #3408.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(skill-surface-budget): add lint-skill-deps.cjs CI gate and fix 19 missed requires: entries
Two lint checks (scripts/lint-skill-deps.cjs):
a) Frontmatter-body consistency: skill body references must appear in requires:
b) Profile closure: every requires: dep of any profile skill must be in closure
Running the lint revealed 19 body references missed by the audit JSON (the
audit used static analysis; some bodies have conditional references). Fixed:
complete-milestone: +audit-milestone, discuss-phase, plan-phase, execute-phase, new-milestone
fast: +quick
health: +thread
map-codebase: +new-project, plan-phase
new-milestone, new-project, review, ultraplan-phase: +plan-phase
ship: +verify-work
sketch, spike: +new-project
verify-work: +execute-phase
workstreams: +new-milestone, resume-work
Wired into package.json as lint:skill-deps and added to pretest.
8 fixture-based tests: all green.
Closes part of #3408.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(skill-surface-budget): wire --profile= arg, profile marker write/read in bin/install.js
- Add --profile=<name> / --profile=<n1>,<n2> arg parsing (composable).
Mutually exclusive with --minimal / --core-only (aliases for --profile=core).
Default (no flag): full.
- Import readActiveProfile / writeActiveProfile from install-profiles.cjs.
- After writeManifest: persist active profile to .gsd-profile marker.
- gsd update path: if no --profile flag given, read existing .gsd-profile
marker so non-full profiles are not silently re-expanded to full (ADR-0010).
- Update --help block to document --profile= with per-tier token costs.
New test: install-minimal-backcompat.test.cjs (6 tests):
- PROFILES.core === MINIMAL_SKILL_ALLOWLIST (contract)
- --minimal writes .gsd-profile marker "core"
- --profile=core, --profile=standard write correct markers
- default install writes marker "full"
Closes part of #3408.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(changeset): add feat-3408-skill-profiles changelog fragment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(install-profiles): derive agents from skill body refs and wire into resolveProfile
Deviation 1 of ADR-0010 phase 1b: tiered profiles (core, standard) now produce
a non-empty agents Set instead of always returning empty. resolveProfile() scans
each skill body for gsd-* agent name references (via new parseCallsAgents()),
stores them in _calls_agents_<stem> manifest entries, and unions them across the
resolved skill closure. stageAgentsForProfile() already checked resolvedProfile.agents
— it now gets real data so tiered profiles install the correct subset of agents
instead of zero.
Closes#3408 (partial — Deviation 1 only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(install): honor .gsd-profile marker on update, add resolveEffectiveProfile/mostRestrictiveProfile
Deviation 2 of ADR-0010 phase 1b: the marker written during installation is now
actually honored when re-running without explicit flags (e.g. gsd update). The
dead-end logging block is replaced by resolveEffectiveProfile(), which picks the
marker profile over 'full' when no explicit --profile= flag was given. The resolved
profile is piped through to all 13 stageSkillsForMode dispatch sites (now _stageSkills)
so updates install only the previously-chosen skill subset.
--minimal retains its back-compat behavior (strict 6-skill allowlist, no closure)
while writing 'core' to the marker. mostRestrictiveProfile() is exported for callers
that need to reconcile disagreeing markers across runtimes (smallest skill set wins).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(surface): add CLUSTERS data + state IO module
Add clusters.cjs with 10 named skill groups covering all 66 skills
(verified by surface-clusters.test.cjs). Add surface.cjs with readSurface/
writeSurface atomic IO, resolveSurface, applySurface, and listSurface.
Tests: 17 passing (11 state IO + 6 cluster integrity).
Closes#3408
* docs(adr): add ADR-0011 Skill Surface Budget Module (Phase 1 accepted, Phase 2 amendment)
Records the install-time profile staging decision (Phase 1, landed) and the
runtime /gsd:surface cluster-toggle decision (Phase 2, in flight) as an
amendment. Updates the ADR README index.
Closes#3408
* docs(install-profiles): update module docblock for Phase 2 and ADR-0011
Corrects the ADR reference from 0010 to 0011, documents the three-profile
model and back-compat aliases, adds resolveEffectiveProfile precedence rule,
and notes the companion surface.cjs Phase 2 engine.
* docs(context): add Skill Surface Budget Module canonical entry
Adds the Domain terms entry for the Skill Surface Budget Module covering
both Phase 1 (install-time profiles, .gsd-profile marker) and Phase 2
(runtime /gsd:surface cluster toggles, clusters.cjs, .gsd-surface.json),
per ADR-0011 Consequences requirement.
* feat(surface): add resolveSurface and applySurface engine + tests
Tests cover: profile → surface equivalence, cluster disable/enable,
explicitAdds transitive closure, applySurface file sync (add missing,
remove superseded, preserve non-gsd files), listSurface token cost.
16 new tests passing.
* docs(readme): document --profile= flag and /gsd:surface command
Brief user-facing mention of install profiles (core/standard/full) and the
/gsd:surface slash command in the Commands table. Points to ADR-0011 for details.
* feat(surface): add /gsd:surface slash command runbook
New skill: gsd:surface — runtime profile/cluster toggle without reinstall.
Sub-commands: list, status, profile <name>, disable/enable <cluster>, reset.
Persists state to .gsd-surface.json (independent of .gsd-profile).
Description 96 chars (≤100 limit). lint:descriptions + lint:skill-deps: 0 violations.
* feat(surface): add changeset fragment for /gsd:surface runtime toggle
* feat(surface): add surface skill stem to utility cluster
surface.md is a new skill; add it to the utility cluster so the
surface-clusters.test.cjs coverage invariant stays satisfied.
* docs(adr): fix ADR references to 0011 and record Phase 2 as shipped
ADR-0010 number was already claimed by the file-operation-engine ADR; this
ADR landed as 0011-skill-surface-budget-module.md. Update inline ADR
references in clusters.cjs, surface.cjs, install-profiles.cjs, and the
Phase 2 changeset to ADR-0011. Update the ADR Status section to record
Phase 2 artifacts as shipped on this branch rather than "in progress".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(research): port skill-surface-budget memo and audit data
ADR-0011 references docs/research/2026-05-12-skill-surface-budget.md and
docs/research/data/2026-05-12-skill-audit.json, which only existed in the
research worktree. Port both onto this branch so the ADR's References
section resolves and reviewers can read the cluster taxonomy (§3.2),
dependency topology (§3.1), and option grading (§4) that justify Phase 1
and Phase 2 decisions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(registration): register surface/clusters in INVENTORY, COMMANDS, and help.md
- surface.md: convert allowed-tools from inline YAML array to block style
(was parsed as a single tool name "[Read, Write, Bash]" by test harness)
- docs/INVENTORY.md: add CLI module rows for clusters.cjs and surface.cjs;
add Commands row for /gsd-surface; bump CLI Modules count 55→57, Commands 66→67
- docs/INVENTORY-MANIFEST.json: add entries for clusters.cjs, surface.cjs,
and /gsd-surface (filename-based command key)
- docs/COMMANDS.md: add ### `/gsd-surface` heading in Configuration Commands
- get-shit-done/workflows/help.md: add /gsd:surface entry in Configuration section
Fixes registration failures introduced by Phase 2 of #3408.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(surface,docs): scrub .claude leakage and escape hypothetical slash tokens
Two PR regressions introduced earlier on this branch:
1. surface.cjs JSDoc comments contained the canonical paths
(~/.claude/commands/gsd, ~/.claude/agents) as example values, which the
cline-install leak regex (~\/\.claude\/(?:get-shit-done|commands|agents
|hooks)) flagged as install-time path leaks. Reworded the docblocks to
describe runtime-resolved paths without literal ~/.claude tokens.
2. The ported research memo proposed hypothetical Option C dispatchers
using slash syntax (/gsd:milestone, /gsd:research). The
docs-parity-live-registry test enforces that every slash-command token
in docs/ resolves to a real command. Rewrote the Option C sketch
without the slash prefix and added a clarifying note that the
dispatchers are illustrative, not shipped.
Targeted tests now pass: tests/cline-install.test.cjs and
tests/docs-parity-live-registry.test.cjs both green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: remove raw output/source grep in lint tests
* fix: close coderabbit profile and requires issues
* test: align surface token-cost assertion wording
* fix(install): align core profile alias and defer profile marker write
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: cover Windows hook shell drift for Claude (#3413)
* fix: scope Windows hook syntax to Gemini runtime (#3413)
* docs: add changeset for #3413
* docs: set changeset pr for #3413
* fix: route Windows hook formatting through runtime-aware projection seam
* test: cover runtime projection edge cases for Windows hooks
* fix(docs): add shell-command-projection to inventory parity
* docs: align CLI module shipped count after rebase