Closes#260
Moves the step-0b absolute-path guard from prose instructions to a harness-enforced PreToolUse hook (gsd-worktree-path-guard.js). Hard-blocks Edit/Write/MultiEdit calls whose absolute path resolves outside the active worktree root.
Squashed from claude/fervent-booth-fb7b1f. Hardens resolveModelPolicy against prototype pollution and fixes resolveModelForTier to check model_policy before dynamic_routing. 199 tests green.
Steps 2 and 4 of resolveEffortInternal now include an else branch that
consults CANONICAL_CONFIG_DEFAULTS.effort when effortCfg is null, mirroring
the existing Step 3 manifest-fallback pattern for routing_tier_defaults.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#488): add gsd-tools effort sync command to re-apply effort config to installed agents
Effort frontmatter is injected at install time, but there was no way to propagate
config changes (agent_overrides, routing_tier_defaults, default) without a full reinstall.
- Adds `cmdEffortSync` to commands.cjs: scans `<configDir>/agents/gsd-*.md`, resolves
the current effort per agent via `resolveEffortInternal` + `renderEffortForRuntime`,
and rewrites (or injects) the `effort:` frontmatter idempotently.
- Dry-run mode (default) reports pending changes without writing; `--apply` writes.
- Accepts `--config-dir` and `--runtime` overrides; gracefully no-ops on non-claude runtimes.
- Wires the `effort sync` subcommand into `gsd-tools.cjs` and adds it to the help list.
- Five regression tests cover dry-run, apply, no-op, inject-missing, and non-claude runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#488): add gsd-tools effort sync command to re-apply effort config to installed agents
Effort frontmatter is injected at install time, but there was no way to propagate
config changes (agent_overrides, routing_tier_defaults, default) without a full reinstall.
- Adds `cmdEffortSync` to commands.cjs: scans `<configDir>/agents/gsd-*.md`, resolves
the current effort per agent via `resolveInstallTimeEffort` + `renderEffortForRuntime`,
and rewrites (or injects) the `effort:` frontmatter idempotently.
- Uses install-time resolvers (readGsdEffectiveEffortConfig from bin/install.js) rather
than the runtime resolver (loadConfig), so home-level effort changes in ~/.gsd/defaults.json
are correctly picked up even when a project .planning/config.json exists.
- Skips symlinks in agents dir to avoid clobbering symlink targets.
- Dry-run mode (default) reports pending changes without writing; --apply writes.
- Accepts --config-dir and --runtime overrides; gracefully no-ops on non-claude runtimes.
- Rejects unexpected positional arguments in the CLI parser.
- Wires the effort sync subcommand into gsd-tools.cjs and adds it to the help list.
- Eight regression tests: dry-run, apply, noop, inject-missing, non-claude runtime,
home-config gap scenario, CLI positional-arg rejection, and CLI dispatch integration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#570): scope Codex leak scanner to manifest, replace bare ~/.claude refs
Two root causes:
- scanForLeakedPaths walked entire ~/.codex tree, flagging pre-existing
unrelated files; now reads gsd-file-manifest.json to scope scan to
GSD-owned artifacts only
- convertClaudeToCodexMarkdown replaced ~/\.claude/ (slash form) but not
bare ~/\.claude\b; gsd-debugger.toml and gsd-surface/SKILL.md examples
slipped through; bare word-boundary replacement now added
- writeManifest tracked agents/gsd-*.md but Codex installs .toml files;
manifest now also records .toml agent files so the scoped scanner covers them
Closes#570
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: add changeset fragment for #570
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove three source-grep describe blocks from bug-1834 and bug-2136 test
files that anchored on byte-offset windows in install.js source. The same
regressions are already fully covered by the E2E behavioral tests (Section 1
in bug-1834, Part 4 in bug-2136) that invoke the actual installer and inspect
the installed files directly.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#571): forbid Write in doc-writer fix mode; add workflow truncation guard
gsd-doc-writer in fix mode only had Write in its tools list, so when
correcting a specific failing claim it would re-emit the whole file with
only the lines it had in context — truncating untracked docs with no git
recovery path.
Fix 1 (root cause): add Edit to the agent tools frontmatter and rewrite
fix_mode instructions to mandate Edit for surgical corrections and
explicitly forbid Write on existing files. Also reinforced in
critical_rules.
Fix 2 (safety net): add a post-fix line-count guard in the fix_loop step
of docs-update.md. If the file shrank by >90% after a fix agent runs,
the orchestrator restores the file from the existing_content it captured
before dispatch and logs a WARNING. This makes the previously
unrecoverable case recoverable.
Regression test: tests/bug-571-doc-writer-fix-mode-edit-only.test.cjs
covers both the agent contract and the workflow guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: add changeset for fix#571
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#571): address codex adversarial review findings
- Quote {doc_path} in shell snippets to handle paths with spaces (#SECURITY)
- Clarify corrupted doc re-verification vs re-fix distinction (#CORRECTNESS)
- Strengthen regression tests with structural ordering assertions (#REGRESSION)
- Move docs-update.md from global ALLOWLIST to SIZE_ONLY_WORKFLOWS so
injection scanning still runs while only the 50K size finding is exempt (#SECURITY)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(#49): provider-neutral model policy presets
Adds model_policy config surface with known-provider presets (openai/anthropic/google/qwen) and generic provider escape hatch. model_policy.runtime_tiers resolves before legacy model_profile_overrides. reasoning_effort is stripped for unsupported runtimes.
Closes#49
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#49): replace unregistered /gsd-settings-advanced token in docs
docs-parity-live-registry enforces every /token in docs/*.md maps to
a live command. /gsd-settings-advanced is a workflow filename, not a
registered command — use /gsd:settings instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#49): update INVENTORY.md count and manifest for config-types.cjs
inventory-counts and inventory-manifest-sync tests require the headline
count and INVENTORY-MANIFEST.json to reflect every file in bin/lib/.
config-types.cjs (new module added by feat(#49)) was missing from both.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
ADR-0174 retired @opengsd/gsd-sdk; sdk/ no longer exists. Removes the
sdkSrcExists guard + unused existsSync/join imports + sdk/dist/** ignore
from eslint.config.mjs, and drops the stale GENERATED comment + exclusion
for configuration.cjs (hand-authored since SDK removal) from stryker.config.mjs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* enhancement(#558): add liveness hints to all GSD spawn announcements
Append '(runs in a subagent — no output until it returns, ~1–5 min; expected,
not a freeze)' inline to every ◆ Spawning… banner and subagent dispatch
instruction across 26 workflows. Silent subagents look identical to frozen
sessions — this note sets the expectation so users wait instead of killing
healthy in-progress work.
Changes:
- references/ui-brand.md: document liveness convention under Spawning Indicators
- 10 banner workflows: append liveness note to ◆ Spawning… lines in-place
- 18 subagent-only workflows: add print instruction with liveness phrase
- tests/spawn-liveness-banner.test.cjs: new test; fails if any workflow with
subagent_type omits 'runs in a subagent'
- docs/USER-GUIDE.md: troubleshooting entry for frozen-looking spawns
- .changeset/558-spawn-liveness-banner.md: changeset fragment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): add missing pr field to changeset fragment
The changeset lint requires pr: <NNN> in frontmatter; the fragment was
written without it, causing parse.cjs to reject it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): address codex review — missed spawns and tighten test
- plan-phase.md: add liveness note to chunked outline planner and
per-plan chunked planner banners (two missed ◆ Spawning… lines)
- quick.md: add liveness note to research banner and add missing
display line before planner spawn in Step 5
- plan-review-convergence.md: add liveness note to initial planning
and review-agent spawn Display lines
- docs-update.md: add Print instructions with liveness note before
gsd-doc-verifier spawns in Phase 1 and Phase 2
- autonomous.md: add Print instruction with liveness note before
background plan-phase agent dispatch in step 3b
- tests/spawn-liveness-banner.test.cjs: replace single file-level
check with two assertions:
(1) every ◆ Spawning… banner line carries the phrase on that line
(2) every file with subagent_type contains the phrase somewhere
The tighter test would have caught all five missed spawns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): tighten spawn-liveness test regex to catch spawn-word-anywhere variants
Previous SPAWN_BANNER_RE only matched ◆ immediately followed by Spawning|spawning.
Replace with /◆[^\n]*\bspawning?\b/i which matches the spawn word anywhere on the
◆ line — catching "◆ Chunked mode: spawning outline planner..." and similar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#558): rename changeset to PR number 566 and correct pr field
Changeset was filed as 558-spawn-liveness-banner.md (issue#) but the
convention is the PR number. Renamed to 566-spawn-liveness-banner.md
and updated pr: 558 → pr: 566 so release notes link to the right PR.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* enhancement(#40): integrate branch pruning into /gsd-cleanup archival workflow
Adds a prune_local_branches step to cleanup.md (between archive_phases and
commit) that force-deletes local branches whose upstream is gone — keeping
local clones symmetric with delete_branch_on_merge on GitHub.
Key design choices vs. PR #562 (the local-model draft):
- dry-run step shows stale branches using cached tracking refs only; git
fetch --prune is deferred to the execution step so the dry-run is
non-side-effecting
- awk uses { if ($1 != "*") print $1 } form to explicitly exclude the
currently checked-out branch (the * prefix in git branch -vv output),
not a prose note that lets xargs receive literal * as an argument
- git fetch --prune runs exactly once, in prune_local_branches, eliminating
the TOCTOU window between a preview fetch and an execution fetch
- two new negative-contract tests: identify_completed_milestones must not
run git branch commands; show_dry_run must not run git fetch --prune
Closes#40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: regenerate changeset using repo script (correct format)
Replaces hand-written fragment (used `/** ... */` comment syntax)
with one generated by `npm run changeset -- --type Changed --pr 562`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address codex review blockers — protect main/next/trunk, align dry-run with execution
Codex adversarial review (pre-PR gate) flagged two blockers:
1. awk filter only excluded '*' (current branch) but not protected names.
main/next/trunk/develop could be force-deleted if their upstream was
gone. Fix: use !~ /^\*$|^main$|^next$|^trunk$|^develop$/ regex match.
2. Dry-run enumerated from cached tracking refs; execution re-ran
git fetch --prune, creating a TOCTOU window between what the user
confirmed and what got deleted. Fix: move git fetch --prune into
show_dry_run (prefetch for display accuracy); prune_local_branches
now enumerates from the already-fetched state with no second fetch.
Updated 14 structural tests to match new design (added protected-name
exclusion test; inverted show_dry_run fetch assertion).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import `extractCurrentMilestone` from `./core.cjs` into `state.cjs`
- Replace `getMilestonePhaseFilter.phaseCount` usage in `buildStateFrontmatter`
with a direct ROADMAP parse using the same digit-anchored pattern as
`roadmap.analyze` — single source of truth for `total_phases` (#549)
- Apply the same replacement in `cmdStateSync` for consistency
- Add regression test: bug-549-total-phases-overcounts-with-phase-section-heading.test.cjs
- Add changeset fragment: .changeset/549-total-phases-decimal-overcounting.md
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): Milestone marked complete while ROADMAP lists unstarted phases
- Fix 1 — Broaden extractCurrentMilestone() (core.cjs):
(a) Extend sectionPattern to also match <summary> tag content: when a
milestone version appears only inside a <summary> tag, locate the
enclosing <details> block and return its content directly instead of
falling through to stripShippedMilestones().
(b) Extend activeMarkerPattern to include 🔄: change
/\b(?:STARTED|ACTIVE|WIP)\b|in\s+progress|🚧/i to also match 🔄.
(c) Extend the Step 2 fallback regex from /🚧\s*\*\*v(\d+\.\d+)\s/ to
/(?:🚧|🔄)\s*\*\*v(\d+\.\d+)\s/ so the emoji-only fallback also
catches 🔄.
- Fix 2 — Add completion guard (milestone.cjs):
Before writing "milestone complete" to STATE.md, check whether any phase
in the current milestone has no directory on disk (disk_status:
no_directory). When STATE.md milestone version matches the version being
completed and unstarted phases are found, emit an error. Re-run with
--force to override.
- Fix 3 — Add W021 health check (verify.cjs):
Check 14 (W021): if STATE.md status contains "milestone complete" or
"archived", scan the current milestone section of ROADMAP.md; if any
phase has no directory on disk, emit W021 warning: "STATE says milestone
complete but ROADMAP lists N unstarted phase(s)".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): replace hand-written changeset with generated fragment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): address Codex review findings
- core.cjs: add (?!Phase\s+\S) to sectionPattern so phase headings that
mention the milestone version (e.g. ### Phase 1: v1.3 migration) cannot
bypass the <summary> fallback path
- milestone.cjs: replace loose numeric-prefix matching with phaseTokenMatches
+ normalizePhaseName so decimal (2.1) and letter-suffix (12A) phase IDs
are handled correctly in the completion guard
- verify.cjs: same correction in W021 check; also add phaseTokenMatches to
core.cjs import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): fix getMilestonePhaseFilter version-heading false match
getMilestonePhaseFilter's sectionPattern also lacked the (?!Phase\s+\S)
exclusion that extractCurrentMilestone received in the previous commit.
A phase title like "### Phase 4: v1.3 migration" could match as the
milestone section start, mis-scoping the phase set used for completion
stats and archive.
Also handle the case where the version lives only in a <summary> tag:
when there is no heading match but a <summary> match exists, skip
setting missingExplicitVersion so milestone.complete does not incorrectly
error with "no phases found".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes#38
Removes the `"approved" → continue` ack-and-advance shortcut from the `human_needed` verification path in execute-phase. The phase now stays pending until `/gsd:verify-work` completes the UAT and triggers its auto-transition — enforcing the invariant that ROADMAP advances only after a completed verification record.
Also fixes: UAT file format mismatch (`status: testing` + correct `## Current Test` key shape), filename alignment (`{phase_num}-UAT.md`), explicit ack handler covering legacy `approved` keyword, stale `HUMAN-UAT.md` references in agent/reference files.
The @opengsd/gsd-sdk package boundary was retired (ADR-0174, #191/#192) and
the sdk/ tree is no longer tracked. ADR-0174's Supersedes table explicitly
records that the generator-based Shared-Module hand-sync lint is deleted as
part of that collapse. This removes the now-orphaned machinery it left behind:
- scripts/lint-shared-module-handsync.cjs — paired bin/lib/*.cjs files with
sdk/src/**/*.ts sources that no longer exist; wired into no CI workflow or
npm script (dead).
- scripts/shared-module-handsync-allowlist.json — the lint's allowlist; every
entry pointed at a non-existent sdk/src source / generated artifact /
freshness check.
- tests/lint-shared-module-handsync.test.cjs — tested the deleted lint.
Docs corrected to match:
- CONTRIBUTING.md — removed the "CJS↔SDK seam" instruction (it linked the
already-deleted docs/agents/cjs-sdk-seam.md and told contributors to
maintain the allowlist under a retired generator pattern).
- docs/prd/3524-cjs-sdk-hard-seam.md + docs/prd/README.md — marked the PRD
Superseded by ADR-0174, matching the already-superseded ADR-3524.
Added tests/no-cjs-sdk-handsync-tooling.test.cjs as a regression guard so the
retired tooling stays removed and is not silently re-wired into package.json.
ADR-3524 is left in place (already Superseded by ADR-0174); runtime modules and
regression tests that cite it in comments keep resolving. No user-facing change.
Closes#556
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The GENERATED_CJS_IGNORES array in eslint.config.mjs wrongly listed 12
hand-written bin/lib/*.cjs modules as "generated" and excluded them from
linting. Genuinely-generated artifacts are already covered by the
**/*.generated.cjs glob and the ADR-457 semver-compare.cjs entry, so the
array only created a coverage gap. Remove it so the 12 modules are linted
under the existing get-shit-done/bin/**/*.cjs ruleset.
Linting them surfaces two dormant dead-code findings, both removed here:
- phase-lifecycle.cjs: stale `eslint-disable-next-line no-cond-assign`
directive — the loop already uses the parenthesized-assignment form the
rule permits by default, so it suppressed nothing.
- state-document.cjs: vestigial `tempField`/`tempDefaults` locals (and
their comment) left over from a refactor to an inline `.some(...)` check.
Pure dead-code/lint-config cleanup; no user-facing behavior change.
Closes#552
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#551): lint hand-written bin/lib/*.cjs mislabeled as generated
GENERATED_CJS_IGNORES excluded 12 hand-written runtime modules from the
ADR-452 ESLint harness. None carry an @generated header and no generator
emits them — they are hand-written, not generated. Remove the mislabeled
list so they lint like the rest of get-shit-done/bin/**/*.cjs. The genuinely
tsc-generated semver-compare.cjs keeps its own separate ADR-457 ignore.
Also drop the stale "Type-aware via parserOptions.project=tsconfig.lint.json"
comment on the .cjs block: that block sets no parser/project and enables no
@typescript-eslint rules; type-aware linting lives in the src/**/*.cts block.
Lint stays green (0 errors); 2 pre-existing warnings surface (already warn
severity) per the file's warn-first convention, tracked as follow-up cleanup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#551): guard ESLint coverage of hand-written bin/lib/*.cjs
Asserts via ESLint's isPathIgnored API that every get-shit-done/bin/lib/*.cjs
without an @generated header or a src/<name>.cts|.ts source is linted (not
ignored), and that the genuinely tsc-generated semver-compare.cjs stays
ignored (ADR-457). Fails 13/14 against the pre-fix config; passes on the fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#457): rewrite ADR-457 to ground truth and accept build-at-publish
The prior draft asserted a codebase state that never existed (13 tsc-generated
files, src/ trees, a tests/cjs-ts-parity.test.cjs). Corrected to verified ground
truth (84 bin/lib .cjs, 1 value-baked package-identity.cjs, no tsc pipeline),
distinguished value-baking from transpilation so package-identity stops being
miscited as precedent, made check-in-the-artifact vs build-at-publish the central
decision, and flipped status to Accepted (build-at-publish).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* build(#537): pilot TS build-at-publish for bin/lib (semver-compare)
First hand-written module collapsed to a TypeScript source of truth per ADR-457.
src/semver-compare.cts compiles (tsc, strict, noEmitOnError) to a gitignored
get-shit-done/bin/lib/semver-compare.cjs. build:lib is wired into build, pretest,
pretest:coverage, and prepublishOnly so the artifact is built before test and
shipped on publish. Type-aware ESLint on src/**/*.cts immediately caught the
params were over-typed as `unknown` (no-base-to-string); narrowed to a honest
VersionInput domain type. Behavior preserved: semver-compare.test.cjs (14) and
bug-10 (4) pass against the generated output; runtime consumer changeset/cli.cjs
unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): make build-at-publish robust across all CI paths (codex review)
Adversarial review found the pilot's generated artifact would be missing on
clean CI checkouts. `pretest`/`pretest:coverage` only fire for `npm test`, but
CI runs `test:unit`/`test:integration`/`test:install` and `node run-tests.cjs`
directly — none of which built the artifact, so any suite requiring
semver-compare.cjs would hit module-not-found on a clean checkout, and
install-smoke's `npm pack` could ship without it.
- Add a `prepare` script (`npm run build:lib`). `npm ci` runs it automatically,
so every CI test job and install-smoke's pack emit the artifact before use.
This is the idiomatic npm mechanism for compiled-output-not-in-git and fixes
both the test and pack paths in one place.
- Add `src/` + `tsconfig.build.json` to ci-test-scope and the install-smoke /
mutation path filters, so a source-only edit to a migrated module still
triggers its tests and mutation coverage (prevents silent CI skips).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): map src/*.cts to built artifact in mutation changed-files detection
Follow-up to the codex re-review. The prior commit added src/**/*.cts to the
mutation workflow's path trigger but left its "compute changed core lib files"
step diffing only get-shit-done/bin/lib/**/*.cjs — which are now gitignored and
never appear in a diff. A source-only edit would trigger the workflow then
early-exit ("no core lib files changed"), silently skipping mutation testing.
Map each changed src/*.cts to its built get-shit-done/bin/lib/*.cjs path (the
on-disk artifact Stryker mutates after prepare/build:lib), merge with the
hand-written .cjs diff, and apply the test/excluded-module filters once.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): use 'src/' pathspec in mutation diff (git glob doesn't match top-level)
Codex review caught that `git diff -- 'src/**/*.cts'` returns empty for a
top-level file like src/semver-compare.cts — git's default pathspec glob does
not match `**` across zero directories (verified on git 2.50.1). The prior
commit's src-detection therefore never fired, so source-only changes still
skipped mutation. Switch to the dir-scoped pathspec 'src/' + a `.cts` grep
(robust for flat and nested layouts), and broaden the workflow path trigger to
'src/**' to match install-smoke. Verified end-to-end: a change to
src/semver-compare.cts now resolves to get-shit-done/bin/lib/semver-compare.cjs
in the --mutate list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#537): add changeset fragment for build-at-publish pilot
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#537): replace prepare with prepack + build-if-missing; defer mutation wiring
CI surfaced three real issues the local run and codex review missed:
1. lockfile-sync failed on every platform. Root cause: `npm ci --dry-run` (the
repo's lockfile health check) RUNS the `prepare` script, but in dry-run the
devDependencies aren't installed, so `tsc` is not found (exit 127) and the
check reports a misleading "out of sync". `prepare` is the wrong hook for a
build needing a devDep. Replace it with `prepack` (runs only on pack/publish,
when node_modules exists) for the tarball path, and build the artifact inside
scripts/run-tests.cjs (build-if-missing) for the test path — the universal
chokepoint every CI test invocation funnels through, including the direct
`node run-tests.cjs --files-from` step that bypasses npm lifecycle hooks. The
guard is a no-op once built, so the run-tests harness test is unaffected.
2. The Stryker mutation gate ran only 1 test against semver-compare (~0% score,
71/71 mutants surviving) — a Stryker test-selection problem orthogonal to the
build migration, and raising the score needs property tests (ADR-456). Revert
the mutation.yml src wiring; mutation coverage for src-authored modules is a
separate follow-up tracked in #537. (The deletion of the gitignored top-level
.cjs does not match the workflow's `bin/lib/**/*.cjs` git pathspec, so the
gate skips cleanly.)
Verified: clean-room `npm ci --dry-run` exits 0; deleting the artifact then
running a suite rebuilds it; run-tests harness 22/22 green; `npm pack` includes
the built artifact via prepack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#448): resolve UI safety gate helper against the GSD install dir
The §5.6 UI Design Contract Gate (and autonomous §3a.5) resolved
ui-safety-gate.cjs via `git rev-parse --show-toplevel`, i.e. the
consuming project's git root — which has no bin/lib. The node call
failed, its exit code was conflated with "no UI", and the gate
silently no-opped so frontend phases skipped the UI-SPEC prompt.
Resolve the helper against the GSD install dir via RUNTIME_DIR (the
same idiom §1 uses for gsd-tools), with git-toplevel and $HOME/.claude
fallbacks. When the helper genuinely can't be found, fail OPEN with a
stderr warning (assume UI present) rather than silently skipping.
Tests: bug-3706 structural guard now requires RUNTIME_DIR resolution
and forbids the consuming-project GSD_REPO_ROOT anchor; a new
behavioral test resolves and runs the helper from a temp consuming
project (no bin/lib) with RUNTIME_DIR set. autonomous-ui-steps updated
to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#448): add changeset fragment for PR #539
* fix(#448): add get-shit-done/bin/lib/ to UI gate probe and deploy helper there
The installer copies get-shit-done/ to the target but not root bin/lib/, so
the helper was never found for installed users. Placing ui-safety-gate.cjs in
get-shit-done/bin/lib/ ensures the installer deploys it, and probing that path
first makes the gate work correctly in installed runtimes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update changeset to cover get-shit-done/bin/lib/ deployment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update inventory for ui-safety-gate.cjs in get-shit-done/bin/lib/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#447): scope post-planning gap analysis to phase_req_ids
§13e gap-analysis diffed the entire REQUIREMENTS.md against a phase's
plans even when the phase mapped no REQ-IDs, so a phase mapping nothing
reported every unrelated project requirement as "not covered".
Teach the gap-analysis CLI a --phase-req-ids option (null/TBD skips the
requirements comparison, an ID list scopes to it, absent = unchanged
back-compat) and have §13e pass the phase's mapped IDs — mirroring the
§13 Requirements Coverage Gate's null/TBD skip. CONTEXT.md decisions stay
in scope regardless.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#447): source phase_req_ids from init.plan-phase, not roadmap.get-phase
Adversarial-review follow-up. roadmap.get-phase returns the raw phase
markdown (not JSON), so `--pick phase_req_ids` yielded nothing and §13e
would have skipped the requirements comparison for EVERY phase — a
silent regression. phase_req_ids is exposed by init.plan-phase; switch
§13e to it. Adds an integration test asserting the query exposes the
IDs and that gap-analysis scopes to them (and skips when unmapped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#447): add changeset fragment for PR #538
* fix(#447): surface mapped REQ-IDs absent from REQUIREMENTS.md as explicit missing rows
Previously, a phase_req_ids entry not found in REQUIREMENTS.md was silently
dropped from the gap report, allowing the analysis to falsely report full
coverage when the requirement document had drifted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: update changeset to cover missing-REQ-ID detection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#191): migrate gsd-sdk query call sites to gsd-tools query
Retiring the gsd-sdk shim. gsd-tools.cjs already accepts `query` as a
meta-prefix (gsd-tools query <command>), so this is a behavior-preserving 1:1
swap across the runtime reference prompts, the graphify hook's commit-detection
gate, and two bin/lib comment/message references.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#191): remove vestigial gsd-sdk shim code from installer + projection
The gsd-sdk shim was already not wired up (no gsd-sdk bin in package.json;
buildWindowsShimTriple had zero call sites). Remove the dead code:
- shell-command-projection.cjs: buildWindowsShimTriple + formatSdkPathDiagnostic
(+ their now-unused PACKAGE_NAME import) and exports
- install.js: the re-export wrappers + imports, the #3406 stale-standalone-sdk
detection (detectStaleStandaloneSdk/formatStaleStandaloneSdkWarning + its
global-install call site), and the exports
Preserved (retained, not gsd-sdk): buildCodexHookWindowsShimIR (#3426) — only
its comments referenced the gsd-sdk pattern; reworded. Also kept the
homePathCoveredByRc 'reopen your shell' branch in maybeSuggestPathExport — its
logic is bin-dir-agnostic, only the message mentioned gsd-sdk; reworded to use
the actual bin dir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#191): update tests for retired gsd-sdk shim
- bug-3441/bug-3442: drop the formatSdkPathDiagnostic / buildWindowsShimTriple
assertions (functions removed); retained PATH-action + drift-guard tests stay
- bug-505: remove the 'still exported' assertions for detectStaleStandaloneSdk /
formatStaleStandaloneSdkWarning / the shim contract surface (#505 kept them;
#191 removes them)
- graphify-auto-update: migrate the hook-dispatch inputs gsd-sdk query commit ->
gsd-tools query commit to match the migrated commit hook
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#191): point active docs at gsd-tools query (gsd-sdk shim retired)
Update the user/agent-facing docs (AGENTS, COMMANDS, CONFIGURATION, USER-GUIDE,
ship-pr-body-sections) that presented gsd-sdk query as a current command to
gsd-tools query. Historical docs (ADRs, PRDs, release notes) left untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(#191): correct state.load vs state.json description for gsd-tools query
Adversarial-review (codex) finding: the migrated USER-GUIDE line claimed both
'gsd-tools query state.json' and 'state.load' resolve to the frontmatter-rebuild
handler. Verified they don't — state.load returns the CJS load shape
(config + state_raw + flags), state.json returns the frontmatter shape. Both are
available via gsd-tools query; corrected the text to say so.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#191): add changeset for gsd-sdk shim retirement
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* chore: rename npm package + bin to @opengsd/gsd-core (functional)
- package.json: name @opengsd/get-shit-done-redux → @opengsd/gsd-core,
bin key get-shit-done-redux → gsd-core, repository/homepage/bugs URLs
- package-lock.json: regenerated (npm install --package-lock-only)
- tests/**, scripts/**, bin/**, .github/**, agents/**, commands/**,
get-shit-done/bin/**, get-shit-done/workflows/**:
applied the 4-rule replacement (scoped npm ref, GitHub repo path,
bin/clone invocations) per #505 single-source refactor
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: sweep live references to @opengsd/gsd-core
Update all live documentation (README.md + translations, docs/**,
CONTRIBUTING.md, VERSIONING.md, SECURITY.md, CONTEXT.md,
docs/CANARY.md) to reflect the renamed package and repository.
Rules applied:
- @opengsd/get-shit-done-redux → @opengsd/gsd-core (scoped npm name)
- open-gsd/get-shit-done-redux → open-gsd/gsd-core (GitHub repo)
- GSD-redux/get-shit-done-redux → open-gsd/gsd-core (stale badge org)
- bare bin/clone refs → gsd-core
CHANGELOG.md, docs/adr/**, docs/RELEASE-*.md, docs/research/**,
and .changeset/** are preserved byte-identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: add negative lookbehind to slash-command regex in bug-2954 test
The extractSlashReferences regex matched /gsd-core inside npm package
URLs (@opengsd/gsd-core), producing a false /gsd:core command reference.
Adding a negative lookbehind (?<![a-z]) excludes matches preceded by a
letter, so only standalone /gsd-<cmd> and /gsd:<cmd> tokens are found.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(#518): add changeset for package rename
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#518): update package-identity expectations to the renamed coordinates
The rebase regenerated the seam to @opengsd/gsd-core (bin gsd-core, repo
open-gsd/gsd-core). The #498 seam tests assert deriveIdentity against the REAL
package.json, so their expected literals must follow the rename. The drift-lint
unit test is left as-is — its SEAM is a self-consistent fixture and its
stale-literal detection cases would shift if altered; the live-repo scan in it
already passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(#22): add plan_review.source_grounding + _authority config keys
Two additive opt-out keys for the drift guard: source_grounding (bool,
default true) gates the source-grounded reviewer pass; _authority (enum
grep|intel|treesitter|lsp|scip, default grep) selects the resolver rung.
No existing default changed.
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(#22): add intel api-surface renderer + CLI subcommand
Renders .planning/intel/api-map.json into a human-readable API-SURFACE.md
for planner injection. Empty/missing map still writes a surface that
announces itself incomplete (absence = unknown, not 'does not exist').
Gated on intel.enabled like all intel functions.
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(#22): add source-grounding pass to plan-review-convergence
Default-on reviewer pass (plan_review.source_grounding) that enumerates
every symbol a plan cites, excludes declared new artifacts, resolves each
against source via the configured authority adapter, and records
three-valued verdicts. rung-0/1 MISSING is needs-acknowledgement, not a
hard block; UNCHECKABLE is logged in a REVIEWS.md coverage section.
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(#22): inject API-SURFACE.md into planner + require Artifacts section
When intel.enabled, plan-phase regenerates API-SURFACE.md and injects it
as a HINT (prefer, may be incomplete, absence = unknown), never a hard
rule. Every plan must now emit an 'Artifacts this phase produces' section
so the source-grounding reviewer can separate new symbols from references
to existing code.
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(#22): surface drift-guard in setup + settings, add docs
/gsd:new-project asks to enable plan_review.source_grounding (default Y);
/gsd:settings exposes the toggle and authority knob. Documents both config
keys in CONFIGURATION.md, the intel api-surface command in COMMANDS.md,
the drift guard in USER-GUIDE.md, and links ADR 22 from ARCHITECTURE.md.
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#22): respect AskUserQuestion 4-option cap and plan-phase XL line budget
settings drift-guard toggle moved to its own 2-option question; #22
plan-phase additions condensed to bring the file back under the 1810-line
XL budget without dropping the intel gate, the incomplete-surface hint, or
the Artifacts-section requirement.
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#22): use live slash-command forms in drift-guard docs
Doc-parity gate requires every slash-command token in docs/*.md to resolve
to a registered command. Corrected the command form(s) referenced in the
#22 drift-guard / api-surface documentation.
The unresolved token was /gsd-core, matched from the GitHub repo reference
"open-gsd/gsd-core#22" in docs/adr/22-plan-drift-guard.md. This is the
same pattern as the existing 'test-runner' exemption (open-gsd/gsd-test-runner).
Added 'core' to INTERNAL_COMPONENT_SLUGS with a matching explanatory comment.
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(#22): add changeset fragment for drift guard (PR #487)
Refs #22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: CI Rebase Check <ci@gsd-redux>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>