* 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>
* feat(#607): rebuild get-shit-done-cc → gsd-core migration
Leftover get-shit-done-cc installs poisoned the shared update cache,
causing a permanent false "update available". Rebuild the migration so a
stale old install is both harmless and actively removed.
- Per-package update cache filename (gsd-update-check-<slug>.json) in the
shared ~/.cache/gsd dir, single-sourced via package-identity; writers
stamp package_name and readers reject foreign/absent lineage. Multi-
runtime visibility preserved (same shared dir + filename across runtimes).
- New get-shit-done/bin/lib/legacy-cleanup.cjs seam: detects code-file
references to the old package + the legacy fixed-name cache across home
runtime dirs; installer auto-cleans on every install; --dry-run previews
and mutates nothing. User hooks and dev-preferences are never touched.
- update.md cache-clear globs gsd-update-check*.json across ALL supported
runtimes (adds cursor/windsurf/augment/trae/qwen/hermes/codebuddy/cline).
- Fix worker MODULE_NOT_FOUND post-install (ship managed-hooks-registry.cjs
+ degrade gracefully) so the per-package cache is always written.
- Diataxis how-to: docs/cleanup-get-shit-done-cc.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#607): add changeset for PR #611
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#607): set USERPROFILE alongside HOME in dry-run install test for Windows
os.homedir() reads USERPROFILE on win32, so HOME-only isolation let the
spawned installer scan the real runner home on windows-latest. Set both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(hooks): stamp gsd-hook-version in .sh hooks and fix stale detection regex (#2136, #2206)
Three-part fix for the persistent "⚠ stale hooks — run /gsd-update" false
positive that appeared on every session after a fresh install.
Root cause: the stale-hook detector (gsd-check-update.js) could only match
the JS comment syntax // in its version regex — never the bash # syntax used
in .sh hooks. And the bash hooks had no version header at all, so they always
landed in the "unknown / stale" branch regardless.
Neither partial fix (PR #2207 regex only, PR #2215 install stamping only) was
sufficient alone:
- Regex fix without install stamping: hooks install with literal
"{{GSD_VERSION}}", the {{-guard silently skips them, bash hook staleness
permanently undetectable after future updates.
- Install stamping without regex fix: hooks are stamped correctly with
"# gsd-hook-version: 1.36.0" but the detector's // regex can't read it;
still falls to the unknown/stale branch on every session.
Fix:
1. Add "# gsd-hook-version: {{GSD_VERSION}}" header to
gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh
2. Extend install.js (both bundled and Codex paths) to substitute
{{GSD_VERSION}} in .sh files at install time (same as .js hooks)
3. Extend gsd-check-update.js versionMatch regex to handle bash "#"
comment syntax: /(?:\/\/|#) gsd-hook-version:\s*(.+)/
Tests: 11 new assertions across 5 describe blocks covering all three fix
parts independently plus an E2E install+detect round-trip. 3885/3885 pass.
Approach credit: PR #2207 (j2h4u / Maxim Brashenko) for the regex fix;
PR #2215 (nitsan2dots) for the install.js substitution approach.
Closes#2136, #2206, #2209, #2210, #2212
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(hooks): extract check-update worker to dedicated file, eliminating template-literal regex escaping
Move stale-hook detection logic from inline `node -e '<template literal>'` subprocess
to a standalone gsd-check-update-worker.js. Benefits:
- Regex is plain JS with no double-escaping (root cause of the (?:\\/\\/|#) confusion)
- Worker is independently testable and can be read directly by tests
- Uses execFileSync (array args) to satisfy security hook that blocks execSync
- MANAGED_HOOKS now includes gsd-check-update-worker.js itself
Update tests to read worker file instead of main hook for regex/configDir assertions.
All 3886 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(2136): add failing test for MANAGED_HOOKS missing bash hooks
Asserts that every gsd-*.js and gsd-*.sh file shipped in hooks/ appears
in the MANAGED_HOOKS array inside gsd-check-update.js. The three bash
hooks (gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh)
were absent, causing this test to fail before the fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(2136): add gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh to MANAGED_HOOKS
The MANAGED_HOOKS array in gsd-check-update.js only listed the 6 JS hooks.
The 3 bash hooks were never checked for staleness after a GSD update, meaning
users could run stale shell hooks indefinitely without any warning.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Move .claude to the front of the detectConfigDir search array so Claude Code
sessions always find their own GSD install first, preventing false "update
available" warnings when an older OpenCode install coexists on the same machine.
Closes#1860
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The stale hooks detector in gsd-check-update.js used a broad
`startsWith('gsd-') && endsWith('.js')` filter that matched every
gsd-*.js file in the hooks directory. Orphaned hooks from removed
features (e.g., gsd-intel-*.js) lacked version headers and were
permanently flagged as stale, with no way to clear the warning.
Replace the broad wildcard with a MANAGED_HOOKS allowlist of the 6
JS hooks GSD currently ships. Orphaned files are now ignored.
Regression test verifies: (1) no broad wildcard filter, (2) managed
list matches build-hooks.js HOOKS_TO_COPY, (3) orphaned filenames
are excluded.
Fixes#1750
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hooks): use semver comparison for update check instead of inequality
gsd-check-update.js used `installed !== latest` to determine if an
update is available. This incorrectly flags an update when the installed
version is NEWER than npm (e.g., installing from git ahead of a release).
Replace with proper semver comparison: update_available is true only
when the npm version is strictly newer than the installed version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(hooks): use semver comparison for update check instead of inequality
gsd-check-update.js used `installed !== latest` to determine if an
update is available. This incorrectly flags an update when the installed
version is NEWER than npm (e.g., installing from git ahead of a release).
Fix:
- Move isNewer() inside the spawned child process (was in parent scope,
causing ReferenceError in production)
- Strip pre-release suffixes before Number() to avoid NaN
- Apply same semver comparison to stale hooks check (line 95)
update_available is now true only when npm version is strictly newer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add semver comparison tests for gsd-check-update isNewer()
12 test cases covering: major/minor/patch comparison, equal versions,
installed-ahead-of-npm scenario, pre-release suffix stripping,
null/empty handling, two-segment versions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: explain why isNewer is duplicated in test file
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bring the latest main branch updates into feat/kilo-runtime-support while preserving KILO_CONFIG resolution, Kilo agent permission conversion, and relative .claude path rewrites.
Two fixes for multi-runtime installations:
1. Update cache now writes to ~/.cache/gsd/ instead of the runtime-
specific config dir, preventing mismatches when check-update and
statusline resolve to different runtimes. Statusline reads from
shared path first with legacy fallback.
2. Stale hooks detection now checks configDir/hooks/ where hooks are
actually installed, not configDir/get-shit-done/hooks/ which does
not exist.
Closes#1421
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gsd-check-update.js looked for hooks in configDir/hooks/ (e.g.,
~/.claude/hooks/) but the installer writes hooks to
configDir/get-shit-done/hooks/. This mismatch caused false stale
hook warnings that persisted even after updating.
Also clears the update cache during install so the next session
re-evaluates hook versions with the correct path.
Closes#1249
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gsd-check-update.js scanned ALL .js files in the hooks directory and
flagged any without a gsd-hook-version header as stale. This incorrectly
flagged user-created hooks (e.g. guard-edits-outside-project.js),
producing a persistent 'stale hooks' warning that /gsd:update couldn't
resolve.
Fix: filter hookFiles to f.startsWith('gsd-') && f.endsWith('.js')
since all GSD hooks follow the gsd-* naming convention.
Includes regression test validating the filter excludes user hooks.
Closes#1200
* fix: hook version tracking, stale hook detection, and stdin timeout increase
- Add gsd-hook-version header to all hook files for version tracking (#1153)
- Install.js now stamps current version into hooks during installation
- gsd-check-update.js detects stale hooks by comparing version headers
- gsd-statusline.js shows warning when stale hooks are detected
- Increase context monitor stdin timeout from 3s to 10s (#1162)
- Set +x permission on hook files during installation (#1162)
Fixes#1153, #1162, #1161
* feat: add /gsd:session-report command for post-session summary generation
Adds a new command that generates SESSION_REPORT.md with:
- Work performed summary (phases touched, commits, files changed)
- Key outcomes and decisions made
- Active blockers and open items
- Estimated resource usage metrics
Reports are written to .planning/reports/ with date-stamped filenames.
Closes#1157
* test: update expected skill count from 39 to 40 for new session-report command
Hooks hardcode ~/.claude/ as the config directory, breaking setups
where Claude Code uses a custom config directory (e.g. multi-account
with CLAUDE_CONFIG_DIR=~/.claude-personal/). The update check hook
shows stale notifications and the statusline reads from wrong paths.
- gsd-check-update.js: check CLAUDE_CONFIG_DIR before filesystem scan
- gsd-statusline.js: use CLAUDE_CONFIG_DIR for todos and cache paths
Closes#870
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three files had hardcoded `.claude` paths that break OpenCode and
Gemini installations where the config directory is `.config/opencode`,
`.opencode`, or `.gemini` respectively.
Changes:
- hooks/gsd-check-update.js: add detectConfigDir() helper that checks
all runtime directories for get-shit-done/VERSION, falling back to
.claude. Used for cache dir, project VERSION, and global VERSION.
- commands/gsd/reapply-patches.md: detect runtime directory for both
global and local patch directories instead of hardcoding ~/.claude/
and ./.claude/
- workflows/update.md: detect runtime directory for local and global
VERSION/marker files, and clear cache across all runtime directories
Closes#682
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Windows, child.unref() alone is insufficient for proper process
detachment. The child process remains in the parent's process group,
causing Claude Code to wait for the hook process tree to exit before
accepting input.
Adding detached: true allows the child process to fully detach on
Windows while maintaining existing behavior on Unix.
Closes#466
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The update check script always looked for VERSION in ~/.claude/get-shit-done/,
but GSD can be installed locally in ./.claude/get-shit-done/. This caused
false "update available" notifications when using local installations.
Changes:
- Check project directory first, then fall back to global
- Remove unused execSync import
On Windows, the detached spawn option creates a visible console window.
Combined with npm (which is a .cmd batch file), this causes a brief
window flash that steals focus from the user's terminal.
- Remove detached: true (not needed, child.unref() handles non-blocking)
- Add windowsHide: true to both spawn() and inner execSync()
- Convert statusline.sh and gsd-check-update.sh to Node.js
- Remove gsd-notify.sh (blocking popups, not true toasts)
- Remove --force-notify and --no-notify flags from installer
- Use `node` command for cross-platform hook execution
Fixes#114
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>