* fix(config): bump MODEL_ALIAS_MAP and RUNTIME_PROFILE_MAP to claude-opus-4-7
Opus 4.7 shipped Q1 2026 but MODEL_ALIAS_MAP and RUNTIME_PROFILE_MAP.claude.opus
were still pinned to claude-opus-4-6. Users with resolve_model_ids: true received
stale model IDs in logs and agent-tool calls.
Also adds a resolve_model_ids: true test suite — this path had zero coverage,
which is why the stale ID survived undetected.
Closes#2712
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(config): derive RUNTIME_PROFILE_MAP.claude from MODEL_ALIAS_MAP (coderabbit)
RUNTIME_PROFILE_MAP.claude was duplicating model IDs that MODEL_ALIAS_MAP
already owns. Future model bumps now only require updating MODEL_ALIAS_MAP.
Also fixes stale test assertion (claude-opus-4-6 → claude-opus-4-7).
* fix(tests): update stale claude-opus-4-6 refs to claude-opus-4-7; DRY: derive RUNTIME_PROFILE_MAP.claude from MODEL_ALIAS_MAP
- Update 3 hardcoded `claude-opus-4-6` assertions in tests/issue-2517-runtime-aware-profiles.test.cjs to `claude-opus-4-7`
- Update comment on line 128 that referenced the old model ID
- Replace manual per-tier expansion of RUNTIME_PROFILE_MAP.claude with Object.fromEntries so future alias bumps only require updating MODEL_ALIAS_MAP
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add _deepMergeConfig() with correct null-override semantics
- loadConfig() reads root config.json when GSD_WORKSTREAM is set, then
deep-merges with workstream config (workstream wins on conflict)
- Workstream without config.json falls back to root config entirely
- Migrations and disk writes operate on fileData (on-disk content) only,
never on the merged result, to prevent workstream pollution
- Fixes null-override bug from PR #2717: explicit null in workstream now
correctly overrides root value instead of falling back to root
- Tests: inherit root model_overrides, workstream override, nested
workflow.* deep merge, explicit null override, missing workstream config
Closes#2714
graphify . --update was removed in favor of graphify update . in v0.4.x.
Also improves version detection to try `graphify --version` before
falling back to python3 importlib query.
Closes#2732
* fix(#2722): forensics gh commands pin --repo gsd-build/get-shit-done
gh issue create and gh label list both defaulted to the repo inferred
from $PWD, causing issues to be submitted to the user's current project
instead of this repo. Added --repo gsd-build/get-shit-done to both
commands. Added two regression tests covering both gh calls.
Closes#2722
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#2723): scope forensics tests to specific gh commands, not whole file
CodeRabbit found that the gh issue create test searched the whole
workflow file, so it would pass even if gh issue create lacked --repo
(because gh label list already contains the repo string elsewhere).
Also replaced the brittle 200-char slice in the label-list test with
a regex. Both tests now use assert.match() with command-scoped regexes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(#2306): plan-review-convergence v2 — CYCLE_SUMMARY contract, config gate, local model reviewers
Fixes the false-stall detection bug in the plan→review→replan convergence
loop. REVIEWS.md accumulates history across cycles so raw grep inflated
HIGH counts; HIGH count now comes from a per-cycle CYCLE_SUMMARY contract
emitted in the review agent's return message.
Key changes:
- workflow.plan_review_convergence config gate (disabled by default, same
pattern as workflow.code_review / workflow.nyquist_validation)
- Review agent prompt defines CYCLE_SUMMARY: current_high=<N> contract with
PARTIALLY RESOLVED / FULLY RESOLVED counting rules
- Orchestrator aborts on absent/malformed CYCLE_SUMMARY (distinguishes both)
- Warns when HIGH_COUNT > 0 but ## Current HIGH Concerns section is missing
- Stall detection and --ws forwarding preserved and tested
- Local model reviewers: --ollama, --lm-studio, --llama-cpp flags added to
convergence workflow and review workflow; all three use OpenAI-compatible
/v1/chat/completions endpoint with jq --rawfile for safe JSON encoding
- review.ollama_host / review.lm_studio_host / review.llama_cpp_host config
keys registered and documented (default to localhost:11434/1234/8080)
- review.models.ollama / .lm_studio / .llama_cpp model-name config support
- 58 tests (up from 29 in PR #2339), all passing
Closes#2306Closes#2339
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): sync sdk/src/query/config-schema.ts with CJS schema (#2306)
Add workflow.plan_review_convergence, review.ollama_host,
review.lm_studio_host, and review.llama_cpp_host to the SDK-side
TypeScript mirror — required by the CJS↔SDK parity test (#2653).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#2306): resolve CodeRabbit review findings
- Anchor HIGH_COUNT extraction with head -1 to prevent multi-match when
agent return message contains multiple CYCLE_SUMMARY lines (e.g. quoted
back from prompt context)
- Replace hardcoded reviewers list in REVIEWS.md frontmatter template with
runtime-derived placeholder — the static list did not reflect which
reviewers were actually invoked
- Broaden workflow.plan_review_convergence docs to include local reviewers
(Ollama, LM Studio, llama.cpp) alongside cloud reviewers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): restore reviewers frontmatter list with runtime note
The cursor-reviewer.test.cjs (and equivalent per-reviewer tests) assert
that each supported reviewer appears on the reviewers: line — these are
wiring tests that catch when a new reviewer is added to invocation but
not to the REVIEWS.md template. Replacing the list with a placeholder
broke those tests.
Restore the full static list and add an inline comment clarifying that
the actual committed frontmatter should be filtered to only the reviewers
invoked that run — satisfying both the per-reviewer tests and the
CodeRabbit correctness note.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
VALID_PROFILES was derived solely from Object.keys(MODEL_PROFILES['gsd-planner']),
which only contained the named tiers (quality/balanced/budget/adaptive). The
cmdConfigSetModelProfile validator rejected 'inherit' even though the runtime has
supported it since #1829. Fix: append 'inherit' to VALID_PROFILES and handle it
in getAgentToModelMapForProfile so the agent→model table shows 'inherit' instead
of undefined.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
All workflow, command, reference, template, and tool-output files that
surfaced /gsd:<cmd> as a user-typed slash command have been updated to
use /gsd-<cmd>, matching the Claude Code skill directory name.
Closes#2697
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#2661): unconditional plan-checkbox sync in execute-plan
Checkpoint A in execute-plan.md was wrapped in a "Skip in parallel mode"
guard that also short-circuited the parallelization-without-worktrees
case. With `parallelization: true, use_worktrees: false`, only
Checkpoint C (phase.complete) then remained, and any interruption
between the final SUMMARY write and phase complete left ROADMAP.md
plan checkboxes stale.
Remove the guard: `roadmap update-plan-progress` is idempotent and
atomically serialized via readModifyWriteRoadmapMd's lockfile, so
concurrent invocations from parallel plans converge safely.
Checkpoint B (worktree-merge post-step) and Checkpoint C
(phase.complete) become redundant after A is unconditional; their
removal is deferred to a follow-up per the RCA.
Closes#2661
* fix(#2661): gate ROADMAP sync on use_worktrees=false to preserve single-writer contract
Adversarial review of PR #2682 found that unconditionally removing the
IS_WORKTREE guard violates the single-writer contract for shared
ROADMAP.md established by commit dcb50396 (PR #1486). The lockfile only
serializes within a single working tree; separate worktrees have
separate ROADMAP.md files that diverge.
Restore the worktree guard but document its intent explicitly: the
in-handler sync runs only when use_worktrees=false (the actual #2661
reproducer). Worktree mode relies on the orchestrator's post-merge
update at execute-phase.md lines 815-834, which is the documented
single-writer for shared tracking files.
Update tests to assert both branches of the gate:
- use_worktrees: false mode runs the sync (the #2661 case)
- use_worktrees: true mode does NOT run the in-handler sync
- handler-level idempotence and lockfile contention tests retained,
scope clarified to within-tree concurrency only
* fix(#2660): capture prose after label in extractOneLinerFromBody
The regex `\*\*([^*]+)\*\*` matched the first bold span, so for the new
SUMMARY template `**One-liner:** Real prose here.` it captured the label
`One-liner:` instead of the prose. MILESTONES.md then wrote bullets like
`- One-liner:` with no content.
Handle both template forms:
- Labeled: `**One-liner:** prose` → prose
- Bare: `**prose**` → prose (legacy)
Empty prose after a label returns null so no bogus bullets are emitted.
Note: existing MILESTONES.md entries generated under the bug are not
regenerated here — that is a follow-up.
Closes#2660
* fix(#2660): normalize CRLF before one-liner extraction
Windows-authored SUMMARY files use CRLF line endings; the LF-only regex
in extractOneLinerFromBody would fail to match. Normalize \r\n and \r
to \n before stripping frontmatter and matching the one-liner pattern.
Adds test case (h) covering CRLF input.
/gsd-insert-phase step 4 instructed the agent to directly Edit/Write
.planning/STATE.md to append a Roadmap Evolution entry. Projects that
ship a protect-files.sh PreToolUse hook (a recommended hardening
pattern) blocked the raw write, silently leaving STATE.md out of sync
with ROADMAP.md.
Adds a dedicated SDK handler state.add-roadmap-evolution (plus space
alias) that:
- Reads STATE.md through the shared readModifyWriteStateMd lockfile
path (matches sibling mutation handlers — atomic against
concurrent writers).
- Locates ### Roadmap Evolution under ## Accumulated Context, or
creates both sections as needed.
- Dedupes on exact-line match so idempotent retries are no-ops
({ added: false, reason: "duplicate" }).
- Validates --phase / --action presence and action membership,
throwing GSDError(Validation) for bad input (no silent
{ ok: false } swallow).
Workflow change (insert-phase.md step 4):
- Replaces the raw Edit/Write instructions for STATE.md with
gsd-sdk query state.patch (for the next-phase pointer) and
gsd-sdk query state.add-roadmap-evolution (for the evolution
log).
- Updates success criteria to check handler responses.
- Drops "Write" from commands/gsd/insert-phase.md allowed-tools
(no step in the workflow needs it any more).
Tests (vitest, sdk/src/query/state-mutation.test.ts): subsection
creation when missing; append-preserving-order when present;
duplicate -> reason=duplicate; idempotence over two calls; three
validation cases covering missing --phase, missing --action, and
invalid action.
This is the first SDK handler dedicated to STATE.md Roadmap
Evolution mutations. Other workflows with similar raw STATE.md
edits (/gsd-pause-work, /gsd-resume-work, /gsd-new-project,
/gsd-complete-milestone, /gsd-add-phase) remain on raw Edit/Write
and will need follow-up issues to migrate — out of scope for this
fix.
Closes#2662
* fix(#2633): use ROADMAP.md as authority for current-milestone phase counts
initMilestoneOp (SDK + CJS) derives phase_count and completed_phases from
the current milestone section of ROADMAP.md instead of counting on-disk
`.planning/phases/` directories. After `phases clear` at the start of a new
milestone the on-disk set is a subset of the roadmap, causing premature
`all_phases_complete: true`.
validateHealth W002 now unions ROADMAP.md phase declarations (all milestones
— current, shipped, backlog) with on-disk dirs when checking STATE.md phase
refs. Eliminates false positives for future-phase refs in the current
milestone and history-phase refs from shipped milestones.
Falls back to legacy on-disk counting when ROADMAP.md is missing or
unparseable so no-roadmap fixtures still work.
Adds vitest regressions for both handlers; all 66 SDK + 118 CJS tests pass.
* fix(#2633): preserve full phase tokens in W002 + completion lookup
CodeRabbit flagged that the parseInt-based normalization collapses distinct
phase IDs (3, 3A, 3.1) into the same integer bucket, masking real
STATE/ROADMAP mismatches and miscounting completions in milestones with
inserted/sub-phases.
Index disk dirs and validate STATE.md refs by canonical full phase token —
strip leading zeros from the integer head only, preserve [A-Z] suffix and
dotted segments, and accept just the leading-zero variant of the integer
prefix as a tolerated alias. 3A and 3 never share a bucket.
Also widens the disk and STATE.md regexes to accept [A-Z]? suffix tokens.
* fix(#2636): surface gsd-sdk query failures and add workflow↔handler parity check
Root cause: workflows invoked `gsd-sdk query agent-skills <slug>` with a
trailing `2>/dev/null`, swallowing stderr and exit code. When the installed
`@gsd-build/sdk` npm was stale (pre-query), the call resolved to an empty
string and `agent_skills.<slug>` config was never injected into spawn
prompts — silently. The handler exists on main (sdk/src/query/skills.ts),
so this is a publish-drift + silent-fallback bug, not a missing handler.
Fix:
- Remove bare `2>/dev/null` from every `gsd-sdk query agent-skills …`
invocation in workflows so SDK failures surface to stderr.
- Apply the same rule to other no-fallback calls (audit-open, write-profile,
generate-* profile handlers, frontmatter.get in commands). Best-effort
cleanup calls (config-set workflow._auto_chain_active false) keep
exit-code forgiveness via `|| true` but no longer suppress stderr.
Parity tests:
- New: tests/bug-2636-gsd-sdk-query-silent-swallow.test.cjs — fails if any
`gsd-sdk query agent-skills … 2>/dev/null` is reintroduced.
- Existing: tests/gsd-sdk-query-registry-integration.test.cjs already
asserts every workflow noun resolves to a registered handler; confirmed
passing post-change.
Note: npm republish of @gsd-build/sdk is a separate release concern and is
not included in this PR.
* fix(#2636): address review — restore broken markdown fences and shell syntax
The previous commit's mass removal of '2>/dev/null' suffixes also
collapsed adjacent closing code fences and 'fi' tokens onto the
command line, producing malformed markdown blocks and 'truefi' /
'true fi' shell syntax errors in the workflows.
Repaired sites:
- commands/gsd/quick.md, thread.md (frontmatter.get fences)
- workflows/complete-milestone.md (audit-open fence)
- workflows/profile-user.md (write-profile + generate-* fences)
- workflows/verify-work.md (audit-open --json fence)
- workflows/execute-phase.md (truefi -> true / fi)
- workflows/plan-phase.md, discuss-phase-assumptions.md,
discuss-phase/modes/chain.md (true fi -> true / fi)
All 5450 tests pass.
initProgress (and its CJS twin) hardcoded `not_started` for ROADMAP-only
phases, so `completed_count` stayed at 0 even when the ROADMAP showed
`- [x] Phase N`. Extract ROADMAP checkbox states into a shared helper
and use `- [x]` as the completion signal when no phase directory is
present. Disk status continues to win when both exist.
Adds a regression test that reproduces the bug with no phases/ dir and
one `[x]` / one `[ ]` phase, asserting completed_count===1.
Fixes#2646
loadConfig's multiRepo migration and filesystem-sync writers targeted the
top-level parsed.sub_repos, but KNOWN_TOP_LEVEL (the unknown-key validator's
allowlist) only recognizes planning.sub_repos (canonical per #2561). Each
migration/sync therefore persisted a key the next loadConfig call warned was
unknown.
Redirect both writers to parsed.planning.sub_repos, ensuring parsed.planning
is initialized first. Also self-heal legacy/buggy installs by stripping any
stale top-level sub_repos on load, preserving its value as the
planning.sub_repos seed if that slot is empty.
Tests cover: (a) canonical planning.sub_repos emits no warning, (b) multiRepo
migration writes to planning.sub_repos with no top-level residue,
(c) filesystem sync relocates to planning.sub_repos, (d) stale top-level
sub_repos from older buggy installs is stripped on load.
Closes#2638
The SDK's config-set kept its own hand-maintained allowlist (28-key
drift vs. get-shit-done/bin/lib/config-schema.cjs), so documented
keys accepted by the CJS config-set — planning.sub_repos,
workflow.code_review_command, workflow.security_*, review.models.*,
model_profile_overrides.*, etc. — were rejected with
"Unknown config key" when routed through the SDK.
Changes:
- New sdk/src/query/config-schema.ts mirrors the CJS schema exactly
(exact-match keys + dynamic regex sources).
- config-mutation.ts imports VALID_CONFIG_KEYS / DYNAMIC_KEY_PATTERNS
from the shared module instead of rolling its own set and regex
branches.
- Drop hand-coded agent_skills.* / features.* regex branches —
now schema-driven so claude_md_assembly.blocks.*, review.models.*,
and model_profile_overrides.<runtime>.<tier> are also accepted.
- Add tests/config-schema-sdk-parity.test.cjs (node:test) as the
CI drift guard: asserts CJS VALID_CONFIG_KEYS set-equals the
literal set parsed from config-schema.ts, and that every CJS
dynamic pattern source has an identical counterpart in the SDK.
Parallel to the CJS↔docs parity added in #2479.
- Vitest #2653 specs iterate every CJS key through the SDK
validator, spot-check each dynamic pattern, and lock in
planning.sub_repos.
- While here: add workflow.context_coverage_gate to the CJS schema
(already in docs and SDK; CJS previously rejected it) and sync
the missing curated typo-suggestions (review.model, sub_repos,
plan_checker, workflow.review_command) into the SDK.
Fixes#2653.
The /gsd:new-milestone workflow Step 5 rewrote STATE.md's Current Position
body but never touched the YAML frontmatter, so every downstream reader
(state.json, getMilestoneInfo, progress bars) kept reporting the stale
milestone until the first phase advance forced a resync. Asymmetric with
milestone.complete, which uses readModifyWriteStateMdFull.
Add a new `state milestone-switch` handler (both SDK and CJS) that atomically:
- Stomps frontmatter milestone/milestone_name with caller-supplied values
- Resets status to 'planning' and progress counters to zero
- Rewrites the ## Current Position section to the new-milestone template
- Preserves Accumulated Context (decisions, blockers, todos)
Wire the workflow Step 5 to invoke `state.milestone-switch` instead of the
manual body rewrite. Note the flag is `--milestone` not `--version`:
gsd-tools reserves `--version` as a globally-invalid help flag.
Red vitest in sdk/src/query/state-mutation.test.ts asserts the frontmatter
reset. Regression guard via node:test in tests/bug-2630-*.test.cjs runs
through gsd-tools end-to-end.
Fixes#2630
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(workflows): agent-skills query keys must match subagent_type
Eight workflow files called `gsd-sdk query agent-skills <KEY>` with
a key that did not match any `subagent_type` Task() spawns in the
same workflow (or any existing `agents/<KEY>.md`):
- research-phase.md:45 — gsd-researcher → gsd-phase-researcher
- plan-phase.md:36 — gsd-researcher → gsd-phase-researcher
- plan-phase.md:38 — gsd-checker → gsd-plan-checker
- quick.md:145 — gsd-checker → gsd-plan-checker
- verify-work.md:36 — gsd-checker → gsd-plan-checker
- new-milestone.md:207 — gsd-synthesizer → gsd-research-synthesizer
- new-project.md:63 — gsd-synthesizer → gsd-research-synthesizer
- ui-review.md:21 — gsd-ui-reviewer → gsd-ui-auditor
- discuss-phase.md:114 — gsd-advisor → gsd-advisor-researcher
Effect before this fix: users configuring `agent_skills.<correct-type>`
in .planning/config.json got no injection on these paths because the
workflow asked the SDK for a different (non-existent) key. The SDK
correctly returned "" for the unknown key, which then interpolated as
an empty string into the Task() prompt. Silent no-op.
The discuss-phase advisor case is a subtle variant — the spawn site
uses `subagent_type="general-purpose"` and loads the agent role via
`Read(~/.claude/agents/gsd-advisor-researcher.md)`. The injection key
must follow the agent identity (gsd-advisor-researcher), not the
technical spawn type.
This is a follow-up to #2555 — the SDK-side fix in that PR (#2587)
only becomes fully effective once the call sites use the right keys.
Adds `sdk/src/workflow-agent-skills-consistency.test.ts` as a
contract test: every `agent-skills <slug>` invocation in
`get-shit-done/workflows/**/*.md` must reference an existing
`agents/<slug>.md`. Fails loudly on future key typos.
Closes#2615
* test: harden workflow agent-skills regex per review feedback
Review (#2616): CodeRabbit flagged the `agent-skills <slug>` pattern
as too permissive (can match prose mentions of the string) and the
per-line scan as brittle (misses commands wrapped across lines).
- Require full `gsd-sdk query agent-skills` prefix before capture
+ `\b` around the pattern so prose references no longer match.
- Scan each file's full content (not line-by-line) so `\s+` can span
newlines; resolve 1-based line number from match index.
- Add JSDoc on helpers and on QUERY_KEY_PATTERN.
Verified: RED against base (`f30da83`) produces the same 9 violations
as before; GREEN on fixed tree.
---------
Co-authored-by: forfrossen <forfrossensvart@gmail.com>
/gsd:manager's background execute-phase Task fails with
"Stream idle timeout - partial response received" on multi-plan phases
(Claude Code + Opus 4.7 at ~200K+ cache_read) because the long subagent
never emits tokens fast enough between large tool_results — the SSE layer
times out mid-assistant-turn and the harness retries hit the same TTFT
wall after prompt cache TTL expires.
Root cause: no orchestrator-level activity at wave/plan boundaries.
Fix (maintainer-approved A+B):
- A (wave boundary): execute-phase.md now emits a `[checkpoint]`
heartbeat before each wave spawns and after each wave completes.
- B (plan boundary): also emit `[checkpoint]` before each Task()
dispatch and after each executor returns (complete/failed/checkpoint).
Heartbeats are literal assistant-text lines (no tool call) with a
monotonic `{P}/{Q} plans done` counter so partial-transcript recovery
tools can grep progress even when a run dies mid-phase.
Docs: COMMANDS.md /gsd-manager section documents the marker format.
Tests: tests/bug-2410-stream-checkpoint-heartbeats.test.cjs (12 cases)
asserts the heartbeats exist at every boundary and in the right workflow
step. Full suite: 5422 node:test cases pass. Pre-existing vitest
failures on main are unrelated to this change.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2619): prevent extractCurrentMilestone from truncating on phase-vX.Y headings
extractCurrentMilestone sliced ROADMAP.md to the current milestone by
looking for the next milestone heading with a greedy regex:
^#{1,N}\s+(?:.*v\d+\.\d+|✅|📋|🚧)
Any heading that mentioned a version literal matched — including phase
headings like "### Phase 12: v1.0 Tech-Debt Closure". When the current
milestone was at the same heading level as the phases (### 🚧 v1.1 …),
the slice terminated at the first such phase, hiding every phase that
followed from phase.insert, validate.health W007, and other SDK commands.
Fix: add a `(?!Phase\s+\S)` negative lookahead so phase headings can
never be treated as milestone boundaries. Phase headings always start
with the literal `Phase `, so this is a clean exclusion.
Applied to:
- get-shit-done/bin/lib/core.cjs (extractCurrentMilestone)
- sdk/src/query/roadmap.ts (extractCurrentMilestone + extractNextMilestoneSection)
Regression tests:
- tests/roadmap-phase-fallback.test.cjs: extractCurrentMilestone does not
truncate on phase heading containing vX.Y (#2619)
- sdk/src/query/roadmap.test.ts: extractCurrentMilestone bug-2619: does
not truncate at a phase heading containing vX.Y
Closes#2619
* fix(#2619): make milestone-boundary Phase lookahead case-insensitive
CodeRabbit follow-up on #2619: the negative lookahead `(?!Phase\s+\S)`
in the SDK milestone-boundary regex was case-sensitive, so headings like
`### PHASE 12: v1.0 Tech-Debt` or `### phase 12: …` still truncated the
milestone slice. Add the `i` flag (now `gmi`).
The sibling CJS regex in get-shit-done/bin/lib/core.cjs already uses the
`mi` flag, so it is already case-insensitive; added a regression test to
lock that in.
- sdk/src/query/roadmap.ts: change flags from `gm` → `gmi`
- sdk/src/query/roadmap.test.ts: add PHASE/phase regression test
- tests/roadmap-phase-fallback.test.cjs: add PHASE/phase regression test
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#2492): add gates ensuring discuss-phase decisions are translated and verified
Two gates close the loop between CONTEXT.md `<decisions>` and downstream
work, fixing #2492:
- Plan-phase **translation gate** (BLOCKING). After requirements
coverage, refuses to mark a phase planned when a trackable decision
is not cited (by id `D-NN` or by 6+-word phrase) in any plan's
`must_haves`, `truths`, or body. Failure message names each missed
decision with id, category, text, and remediation paths.
- Verify-phase **validation gate** (NON-BLOCKING). Searches plans,
SUMMARY.md, files modified, and recent commit subjects for each
trackable decision. Misses are written to VERIFICATION.md as a
warning section but do not change verification status. Asymmetry is
deliberate — fuzzy-match miss should not fail an otherwise green
phase.
Shared helper `parseDecisions()` lives in `sdk/src/query/decisions.ts`
so #2493 can consume the same parser.
Decisions opt out of both gates via `### Claude's Discretion` heading
or `[informational]` / `[folded]` / `[deferred]` tags.
Both gates skip silently when `workflow.context_coverage_gate=false`
(default `true`).
Closes#2492
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2492): make plan-phase decision gate actually block (review F1, F8, F9, F10, F15)
- F1: replace `${context_path}` with `${CONTEXT_PATH}` in the plan-phase
gate snippet so the BLOCKING gate receives a non-empty path. The
variable was defined in Step 4 (`CONTEXT_PATH=$(_gsd_field "$INIT" ...)`)
and the gate snippet referenced the lowercase form, leaving the gate to
run with an empty path argument and silently skip.
- F15: wrap the SDK call with `jq -e '.data.passed == true' || exit 1` so
failure halts the workflow instead of being printed and ignored. The
verify-phase counterpart deliberately keeps no exit-1 (non-blocking by
design) and now carries an inline note documenting the asymmetry.
- F10: tag the JSON example fence as `json` and the options-list fence as
`text` (MD040).
- F8/F9: anchor the heading-presence test regexes to `^## 13[a-z]?\\.` so
prose substrings like "Requirements Coverage Gate" mentioned in body
text cannot satisfy the assertion. Added two new regression tests
(variable-name match, exit-1 guard) so a future revert is caught.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2492): tighten decision-coverage gates against false positives and config drift (review F3,F4,F5,F6,F7,F16,F18,F19)
- F3: forward `workstream` arg through both gate handlers so workstream-scoped
`workflow.context_coverage_gate=false` actually skips. Added negative test
that creates a workstream config disabling the gate while the root config
has it enabled and asserts the workstream call is skipped.
- F4: restrict the plan-phase haystack to designated sections — front-matter
`must_haves` / `truths` / `objective` plus body sections under headings
matching `must_haves|truths|tasks|objective`. HTML comments and fenced
code blocks are stripped before extraction so a commented-out citation or
a literal example never counts as coverage. Verify-phase keeps the broader
artifact-wide haystack by design (non-blocking).
- F5: reject decisions with fewer than 6 normalized words from soft-matching
(previously only rejected when the resulting phrase was under 12 chars
AFTER slicing — too lenient). Short decisions now require an explicit
`D-NN` citation, with regression tests for the boundary.
- F6: walk every `*-SUMMARY.md` independently and use `matchAll` with the
`/g` flag so multiple `files_modified:` blocks across multiple summaries
are all aggregated. Previously only the first block in the concatenated
string was parsed, silently dropping later plans' files.
- F7: validate every `files_modified` path stays inside `projectDir` after
resolution (rejects absolute paths, `../` traversal). Cap each file read
at 256 KB. Skipped paths emit a stderr warning naming the entry.
- F16: validate `workflow.context_coverage_gate` is boolean in
`loadGateConfig`; warn loudly on numeric or other-shaped values and
default to ON. Mirrors the schema-vs-loadConfig validation gap from
#2609.
- F18: bump verify-phase `git log -n` cap from 50 to 200 so longer-running
phases are not undercounted. Documented as a precision-vs-recall tradeoff
appropriate for a non-blocking gate.
- F19: tighten `QueryResult` / `QueryHandler` to be parameterized
(`<T = unknown>`). Drops the `as unknown as Record<string, unknown>`
casts in the gate handlers and surfaces shape mismatches at compile time
for callers that pass a typed `data` value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2492): harden decisions parser and verify-phase glob (review F11,F12,F13,F14,F17,F20)
- F11: strip fenced code blocks from CONTEXT.md before searching for
`<decisions>` so an example block inside ``` ``` is not mis-parsed.
- F12: accept tab-indented continuation lines (previously required a leading
space) so decisions split with `\t` continue cleanly.
- F13: parse EVERY `<decisions>` block in the file via `matchAll`, not just
the first. CONTEXT.md may legitimately carry more than one block.
- F14: `decisions.parse` handler now resolves a relative path against
`projectDir` — symmetric with the gate handlers — and still accepts
absolute paths.
- F17: replace `ls "${PHASE_DIR}"/*-CONTEXT.md | head -1` in verify-phase.md
with a glob loop (ShellCheck SC2012 fix). Also avoids spawning an extra
subprocess and survives filenames with whitespace.
- F20: extend the unicode quote-stripping in the discretion-heading match
to cover U+2018/2019/201A/201B and the U+201C-F double-quote variants
plus backtick, so any rendering of "Claude's Discretion" collapses to
the same key.
Each fix has a regression test in `decisions.test.ts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add unified post-planning gap checker (closes#2493)
Adds a unified post-planning gap checker as Step 13e of plan-phase.md.
After all plans are generated and committed, scans REQUIREMENTS.md and
CONTEXT.md <decisions> against every PLAN.md in the phase directory and
emits a single Source | Item | Status table.
Why
- The existing Requirements Coverage Gate (§13) blocks/re-plans on REQ
gaps but emits two separate per-source signals. Issue #2493 asks for
one unified report after planning so that requirements AND
discuss-phase decisions slipping through are surfaced in one place
before execution starts.
What
- New workflow.post_planning_gaps boolean config key, default true,
added to VALID_CONFIG_KEYS, CONFIG_DEFAULTS, hardcoded.workflow, and
cmdConfigSet (boolean validation).
- New get-shit-done/bin/lib/decisions.cjs — shared parser for
CONTEXT.md <decisions> blocks (D-NN entries). Designed for reuse by
the related #2492 plan/verify decision gates.
- New get-shit-done/bin/lib/gap-checker.cjs — parses REQUIREMENTS.md
(checkbox + traceability table forms), reads CONTEXT.md decisions,
walks PHASE_DIR/*-PLAN.md, runs word-boundary coverage detection
(REQ-1 must not match REQ-10), formats a sorted report.
- New gsd-tools gap-analysis CLI command wired through gsd-tools.cjs.
- workflows/plan-phase.md gains §13e between §13d (commit plans) and
§14 (Present Final Status). Existing §13 gate preserved — §13e is
additive and non-blocking.
- sdk/prompts/workflows/plan-phase.md gets an equivalent
post_planning_gaps step for headless mode.
- Docs: CONFIGURATION.md, references/planning-config.md, INVENTORY.md,
INVENTORY-MANIFEST.json all updated.
Tests
- tests/post-planning-gaps-2493.test.cjs: 30 test cases covering step
insertion position, decisions parser, gap detector behavior
(covered/not-covered, false-positive guard, missing-file
resilience, malformed-input resilience, gate on/off, deterministic
natural sort), and full config integration.
- Full suite: 5234 / 5234 pass.
Design decisions
- Numbered §13e (sub-step), not §14 — §14 already exists (Present
Final Status); inserting before it preserves downstream auto-advance
step numbers.
- Existing §13 gate kept, not replaced — §13 blocks/re-plans on
REQ gaps; §13e is the unified post-hoc report. Per spec: "default
behavior MUST be backward compatible."
- Word-boundary ID matching avoids REQ-1 matching REQ-10 and avoids
brittle semantic/substring matching.
- Shared decisions.cjs parser so #2492 can reuse the same regex.
- Natural-sort keys (REQ-02 before REQ-10) for deterministic output.
- Boolean validation in cmdConfigSet rejects non-boolean values
matches the precedent set by drift_threshold/drift_action.
Closes#2493
* fix(#2493): expose post_planning_gaps in loadConfig() + sync schema example
Address CodeRabbit review on PR #2610:
- core.cjs loadConfig(): return post_planning_gaps from both the
config.json branch and the global ~/.gsd/defaults.json fallback so
callers can rely on config.post_planning_gaps regardless of whether
the key is present (comment 3127977404, Major).
- docs/CONFIGURATION.md: add workflow.post_planning_gaps to the Full
Schema JSON example so copy/paste users see the new toggle alongside
security_block_on (comment 3127977392, Minor).
- tests/post-planning-gaps-2493.test.cjs: regression coverage for
loadConfig() — default true when key absent, honors explicit
true/false from workflow.post_planning_gaps.
* feat: make model profiles runtime-aware for Codex/non-Claude runtimes (closes#2517)
Adds an optional top-level `runtime` config key plus a
`model_profile_overrides[runtime][tier]` map. When `runtime` is set,
profile tiers (opus/sonnet/haiku) resolve to runtime-native model IDs
(and reasoning_effort where supported) instead of bare Claude aliases.
Codex defaults from the spec:
opus -> gpt-5.4 reasoning_effort: xhigh
sonnet -> gpt-5.3-codex reasoning_effort: medium
haiku -> gpt-5.4-mini reasoning_effort: medium
Claude defaults mirror MODEL_ALIAS_MAP. Unknown runtimes fall back to
the Claude-alias safe default rather than emit IDs the runtime cannot
accept. reasoning_effort is only emitted into Codex install paths;
never returned from resolveModelInternal and never written to Claude
agent frontmatter.
Backwards compatible: any user without `runtime` set sees identical
behavior — the new branch is gated on `config.runtime != null`.
Precedence (highest to lowest):
1. per-agent model_overrides
2. runtime-aware tier resolution (when `runtime` is set)
3. resolve_model_ids: "omit"
4. Claude-native default
5. inherit (literal passthrough)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2517): address adversarial review of #2609 (findings 1-16)
Addresses all 16 findings from the adversarial review of PR #2609.
Each finding is enumerated below with its resolution.
CRITICAL
- F1: readGsdRuntimeProfileResolver(targetDir) now probes per-project
.planning/config.json AND ~/.gsd/defaults.json with per-project winning,
so the PR's headline claim ("set runtime in project config and Codex
TOML emit picks it up") actually holds end-to-end.
- F2: resolveTierEntry field-merges user overrides with built-in defaults.
The CONFIGURATION.md string-shorthand example
`{ codex: { opus: "gpt-5-pro" } }`
now keeps reasoning_effort from the built-in entry. Partial-object
overrides like `{ opus: { reasoning_effort: 'low' } }` keep the
built-in model. Both paths regression-tested.
MAJOR
- F3: resolveReasoningEffortInternal gates strictly on the
RUNTIMES_WITH_REASONING_EFFORT allowlist regardless of override
presence. Override + unknown-runtime no longer leaks reasoning_effort.
- F4: runtime:"claude" is now a no-op for resolution (it is the implicit
default). It no longer hijacks resolve_model_ids:"omit". Existing
tests for `runtime:"claude"` returning Claude IDs were rewritten to
reflect the no-op semantics; new test asserts the omit case returns "".
- F5: _readGsdConfigFile in install.js writes a stderr warning on JSON
parse failure instead of silently returning null. Read failure and
parse failure are warned separately. Library require is hoisted to top
of install.js so it is not co-mingled with config-read failure modes.
- F6: install.js requires for core.cjs / model-profiles.cjs are hoisted
to the top of the file with __dirname-based absolute paths so global
npm install works regardless of cwd. Test asserts both lib paths exist
relative to install.js __dirname.
- F7: docs/CONFIGURATION.md `runtime` row no longer lists `opencode` as
a valid runtime — install-path emission for non-Codex runtimes is
explicitly out of scope per #2517 / #2612, and the doc now points at
#2612 for the follow-on work. resolveModelInternal still accepts any
runtime string (back-compat) and falls back safely for unknown values.
- F8: Tests now isolate HOME (and GSD_HOME) to a per-test tmpdir so the
developer's real ~/.gsd/defaults.json cannot bleed into assertions.
Same pattern CodeRabbit caught on PRs #2603 / #2604.
- F9: `runtime` and `model_profile_overrides` documented as flat-only
in core.cjs comments — not routed through `get()` because they are
top-level keys per docs/CONFIGURATION.md and introducing nested
resolution for two new keys was not worth the edge-case surface.
- F10/F13: loadConfig now invokes _warnUnknownProfileOverrides on the
raw parsed config so direct .planning/config.json edits surface
unknown runtime values (e.g. typo `runtime: "codx"`) and unknown
tier values (e.g. `model_profile_overrides.codex.banana`) at read
time. Warnings only — preserves back-compat for runtimes added
later. Per-process warning cache prevents log spam across repeated
loadConfig calls.
MINOR / NIT
- F11: Removed dead `tier || 'sonnet'` defensive shortcut. The local
is now `const alias = tier;` with a comment explaining why `tier`
is guaranteed truthy at that point (every MODEL_PROFILES entry
defines `balanced`, the fallback profile).
- F12: Extracted resolveTierEntry() in core.cjs as the single source
of truth for runtime-aware tier resolution. core.cjs and bin/install.js
both consume it — no duplicated lookup logic between the two files.
- F14: Added regression tests for findings #1, #2, #3, #4, #6, #10, #13
in tests/issue-2517-runtime-aware-profiles.test.cjs. Each must-fix
path has a corresponding test that fails against the pre-fix code
and passes against the post-fix code.
- F15: docs/CONFIGURATION.md `model_profile` row cross-references
#1713 / #1806 next to the `adaptive` enum value.
- F16: RUNTIME_PROFILE_MAP remains in core.cjs as the single source of
truth; install.js imports it through the exported resolveTierEntry
helper rather than carrying its own copy. Doc files (CONFIGURATION.md,
USER-GUIDE.md, settings.md) intentionally still embed the IDs as text
— code comment in core.cjs flags that those doc files must be updated
whenever the constant changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(workflows): extract discuss-phase modes/templates/advisor for progressive disclosure (closes#2551)
Splits 1,347-line workflows/discuss-phase.md into a 495-line dispatcher plus
per-mode files in workflows/discuss-phase/modes/ and templates in
workflows/discuss-phase/templates/. Mirrors the progressive-disclosure
pattern that #2361 enforced for agents.
- Per-mode files: power, all, auto, chain, text, batch, analyze, default, advisor
- Templates lazy-loaded at the step that produces the artifact (CONTEXT.md
template at write_context, DISCUSSION-LOG.md template at git_commit,
checkpoint.json schema when checkpointing)
- Advisor mode gated behind `[ -f $HOME/.claude/get-shit-done/USER-PROFILE.md ]`
— inverse of #2174's --advisor flag (don't pay the cost when unused)
- scout_codebase phase-type→map selection table extracted to
references/scout-codebase.md
- New tests/workflow-size-budget.test.cjs enforces tiered budgets across
all workflows/*.md (XL=1700 / LARGE=1500 / DEFAULT=1000) plus the
explicit <500 ceiling for discuss-phase.md per #2551
- Existing tests updated to read from the new file locations after the
split (functional equivalence preserved — content moved, not removed)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2607): align modes/auto.md check_existing with parent (Update it, not Skip)
CodeRabbit flagged drift between the parent step (which auto-selects "Update
it") and modes/auto.md (which documented "Skip"). The pre-refactor file had
both — line 182 said "Skip" in the overview, line 250 said "Update it" in the
actual step. The step is authoritative. Fix the new mode file to match.
Refs: PR #2607 review comment 3127783430
* test(#2607): harden discuss-phase regression tests after #2551 split
CodeRabbit identified four test smells where the split weakened coverage:
- workflow-size-budget: assertion was unreachable (entered if-block on match,
then asserted occurrences === 0 — always failed). Now unconditional.
- bug-2549-2550-2552: bounded-read assertion checked concatenated source, so
src.includes('3') was satisfied by unrelated content in scout-codebase.md
(e.g., "3-5 most relevant files"). Now reads parent only with a stricter
regex. Also asserts SCOUT_REF exists.
- chain-flag-plan-phase: filter(existsSync) silently skipped a missing
modes/chain.md. Now fails loudly via explicit asserts.
- discuss-checkpoint: same silent-filter pattern across three sources. Now
asserts each required path before reading.
Refs: PR #2607 review comments 3127783457, 3127783452, plus nitpicks for
chain-flag-plan-phase.test.cjs:21-24 and discuss-checkpoint.test.cjs:22-27
* docs(#2607): fix INVENTORY count, context.md placeholders, scout grep portability
- INVENTORY.md: subdirectory note said "50 top-level references" but the
section header now says 51. Updated to 51.
- templates/context.md: footer hardcoded XX-name instead of declared
placeholders [X]/[Name], which would leak sample text into generated
CONTEXT.md files. Now uses the declared placeholders.
- references/scout-codebase.md: no-maps fallback used grep -rl with
"\\|" alternation (GNU grep only — silent on BSD/macOS grep). Switched
to grep -rlE with extended regex for portability.
Refs: PR #2607 review comments 3127783404, 3127783448, plus nitpick for
scout-codebase.md:32-40
* docs(#2607): label fenced examples + clarify overlay/advisor precedence
- analyze.md / text.md / default.md: add language tags (markdown/text) to
fenced example blocks to silence markdownlint MD040 warnings flagged by
CodeRabbit (one fence in analyze.md, two in text.md, five in default.md).
- discuss-phase.md: document overlay stacking rules in discuss_areas — fixed
outer→inner order --analyze → --batch → --text, with a pointer to each
overlay file for mode-specific precedence.
- advisor.md: add tie-breaker rules for NON_TECHNICAL_OWNER signals — explicit
technical_background overrides inferred signals; otherwise OR-aggregate;
contradictory explanation_depth values resolve by most-recent-wins.
Refs: PR #2607 review comments 3127783415, 3127783437, plus nitpicks for
default.md:24, discuss-phase.md:345-365, and advisor.md:51-56
* fix(#2607): extract codebase_drift_gate body to keep execute-phase under XL budget
PR #2605 added 80 lines to execute-phase.md (1622 -> 1702), pushing it over
the XL_BUDGET=1700 line cap enforced by tests/workflow-size-budget.test.cjs
(introduced by this PR). Per the test's own remediation hint and #2551's
progressive-disclosure pattern, extract the codebase_drift_gate step body to
get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md and leave
a brief pointer in the workflow. execute-phase.md is now 1633 lines.
Budget is NOT relaxed; the offending workflow is tightened.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#2529): /gsd-settings-integrations — third-party integrations command
Adds /gsd-settings-integrations for configuring API keys, code-review CLI
routing, and agent-skill injection. Distinct from /gsd-settings (workflow
toggles) because these are connectivity, not pipeline shape.
Three sections:
- Search Integrations: brave_search / firecrawl / exa_search API keys,
plus search_gitignored toggle.
- Code Review CLI Routing: review.models.{claude,codex,gemini,opencode}
shell-command strings.
- Agent Skills Injection: agent_skills.<agent-type> free-text input,
validated against [a-zA-Z0-9_-]+.
Security:
- New secrets.cjs module with ****<last-4> masking convention.
- cmdConfigSet now masks value/previousValue in CLI output for secret keys.
- Plaintext is written only to .planning/config.json; never echoed to
stdout/stderr, never written to audit/log files by this flow.
- Slug validators reject path separators, whitespace, shell metacharacters.
Tests (tests/settings-integrations.test.cjs — 25 cases):
- Artifact presence / frontmatter.
- Field round-trips via gsd-tools config-set for all four search keys,
review.models.<cli>, agent_skills.<agent-type>.
- Config-merge safety: unrelated keys preserved across writes.
- Masking: config-set output never contains plaintext sentinel.
- Logging containment: plaintext secret sentinel appears only in
config.json under .planning/, nowhere else on disk.
- Negative: path-traversal, shell-metachar, and empty-slug rejected.
- /gsd:settings workflow mentions /gsd:settings-integrations.
Docs:
- docs/COMMANDS.md: new command entry with security note.
- docs/CONFIGURATION.md: integration settings section (keys, routing,
skills injection) with masking documentation.
- docs/CLI-TOOLS.md: reviewer CLI routing and secret-handling sections.
- docs/INVENTORY.md + INVENTORY-MANIFEST.json regenerated.
Closes#2529
* fix(#2529): mask secrets in config-get; address CodeRabbit review
cmdConfigGet was emitting plaintext for brave_search/firecrawl/exa_search.
Apply the same isSecretKey/maskSecret treatment used by config-set so the
CLI surface never echoes raw API keys; plaintext still lives only in
config.json on disk.
Also addresses CodeRabbit review items in the same PR area:
- #3127146188: config-get plaintext leak (root fix above)
- #3127146211: rename test sentinels to concat-built markers so secret
scanners stop flagging the test file. Behavior preserved.
- #3127146207: add explicit 'text' language to fenced code blocks (MD040).
- nitpick: unify masked-value wording in read_current legend
('****<last-4>' instead of '**** already set').
- nitpick: extend round-trip test to cover search_gitignored toggle.
New regression test 'config-get masks secrets and never echoes plaintext'
verifies the fix for all three secret keys.
* docs(#2529): bump INVENTORY counts post-rebase (commands 84→85, workflows 82→83)
* fix(test): bump CLI Modules count 27→28 after rebase onto main (CI #24811455435)
PR #2604 was rebased onto main before #2605 (drift.cjs) merged. The
pull_request CI runs against the merge ref (refs/pull/2604/merge),
which now contains 28 .cjs files in get-shit-done/bin/lib/, but
docs/INVENTORY.md headline still said "(27 shipped)".
inventory-counts.test.cjs failed with:
AssertionError: docs/INVENTORY.md "CLI Modules (27 shipped)" disagrees
with get-shit-done/bin/lib/ file count (28)
Rebased branch onto current origin/main (picks up drift.cjs row, which
was already added by #2605) and bumped the headline to 28.
Full suite: 5200/5200 pass.
* feat: auto-remap codebase after significant phase execution (#2003)
Adds a post-phase structural drift detector that compares the committed tree
against `.planning/codebase/STRUCTURE.md` and either warns or auto-remaps
the affected subtrees when drift exceeds a configurable threshold.
## Summary
- New `bin/lib/drift.cjs` — pure detector covering four drift categories:
new directories outside mapped paths, new barrel exports at
`(packages|apps)/*/src/index.*`, new migration files, and new route
modules. Prioritizes the most-specific category per file.
- New `verify codebase-drift` CLI subcommand + SDK handler, registered as
`gsd-sdk query verify.codebase-drift`.
- New `codebase_drift_gate` step in `execute-phase` between
`schema_drift_gate` and `verify_phase_goal`. Non-blocking by contract —
any error logs and the phase continues.
- Two new config keys: `workflow.drift_threshold` (int, default 3) and
`workflow.drift_action` (`warn` | `auto-remap`, default `warn`), with
enum/integer validation in `config-set`.
- `gsd-codebase-mapper` learns an optional `--paths <p1,p2,...>` scope hint
for incremental remapping; agent/workflow docs updated.
- `last_mapped_commit` lives in YAML frontmatter on each
`.planning/codebase/*.md` file; `readMappedCommit`/`writeMappedCommit`
round-trip helpers ship in `drift.cjs`.
## Tests
- 55 new tests in `tests/drift-detection.test.cjs` covering:
classification, threshold gating at 2/3/4 elements, warn vs. auto-remap
routing, affected-path scoping, `--paths` sanitization (traversal,
absolute, shell metacharacter rejection), frontmatter round-trip,
defensive paths (missing STRUCTURE.md, malformed input, non-git repos),
CLI JSON output, and documentation parity.
- Full suite: 5044 pass / 0 fail.
## Documentation
- `docs/CONFIGURATION.md` — rows for both new keys.
- `docs/ARCHITECTURE.md` — section on the post-execute drift gate.
- `docs/AGENTS.md` — `--paths` flag on `gsd-codebase-mapper`.
- `docs/USER-GUIDE.md` — user-facing behavior note + toggle commands.
- `docs/FEATURES.md` — new 27a section with REQ-DRIFT-01..06.
- `docs/INVENTORY.md` + `docs/INVENTORY-MANIFEST.json` — drift.cjs listed.
- `get-shit-done/workflows/execute-phase.md` — `codebase_drift_gate` step.
- `get-shit-done/workflows/map-codebase.md` — `parse_paths_flag` step.
- `agents/gsd-codebase-mapper.md` — `--paths` directive under parse_focus.
## Design decisions
- **Frontmatter over sidecar JSON** for `last_mapped_commit`: keeps the
baseline attached to the file, survives git moves, survives per-doc
regeneration, no extra file lifecycle.
- **Substring match against STRUCTURE.md** for `isPathMapped`: the map is
free-form markdown, not a structured manifest; any mention of a path
prefix counts as "mapped territory". Cheap, no parser, zero false
negatives on reasonable maps.
- **Category priority migration > route > barrel > new_dir** so a file
matching multiple rules counts exactly once at the most specific level.
- **Empty-tree SHA fallback** (`4b825dc6…`) when `last_mapped_commit` is
absent — semantically correct (no baseline means everything is drift)
and deterministic across repos.
- **Four layers of non-blocking** — detector try/catch, CLI try/catch, SDK
handler try/catch, and workflow `|| echo` shell fallback. Any single
layer failing still returns a valid skipped result.
- **SDK handler delegates to `gsd-tools.cjs`** rather than re-porting the
detector to TypeScript, keeping drift logic in one canonical place.
Closes#2003
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(mapper): tag --paths fenced block as text (CodeRabbit MD040)
Comment 3127255172.
* docs(config): use /gsd- dash command syntax in drift_action row (CodeRabbit)
Comment 3127255180. Matches the convention used by every other command
reference in docs/CONFIGURATION.md.
* fix(execute-phase): initialize AGENT_SKILLS_MAPPER + tag fenced blocks
Two CodeRabbit findings on the auto-remap branch of the drift gate:
- 3127255186 (must-fix): the mapper Task prompt referenced
${AGENT_SKILLS_MAPPER} but only AGENT_SKILLS (for gsd-executor) is
loaded at init_context (line 72). Without this fix the literal
placeholder string would leak into the spawned mapper's prompt.
Add an explicit gsd-sdk query agent-skills gsd-codebase-mapper step
right before the Task spawn.
- 3127255183: tag the warn-message and Task() fenced code blocks as
text to satisfy markdownlint MD040.
* docs(map-codebase): wire PATH_SCOPE_HINT through every mapper prompt
CodeRabbit (review id 4158286952, comment 3127255190) flagged that the
parse_paths_flag step defined incremental-remap semantics but did not
inject a normalized variable into the spawn_agents and sequential_mapping
mapper prompts, so incremental remap could silently regress to a
whole-repo scan.
- Define SCOPED_PATHS / PATH_SCOPE_HINT in parse_paths_flag.
- Inject ${PATH_SCOPE_HINT} into all four spawn_agents Task prompts.
- Document the same scope contract for sequential_mapping mode.
* fix(drift): writeMappedCommit tolerates missing target file
CodeRabbit (review id 4158286952, drift.cjs:349-355 nitpick) noted that
readMappedCommit returns null on ENOENT but writeMappedCommit threw — an
asymmetry that breaks first-time stamping of a freshly produced doc that
the caller has not yet written.
- Catch ENOENT on the read; treat absent file as empty content.
- Add a regression test that calls writeMappedCommit on a non-existent
path and asserts the file is created with correct frontmatter.
Test was authored to fail before the fix (ENOENT) and passes after.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: /gsd-settings-advanced — power-user config tuning command (closes#2528)
Adds a second-tier interactive configuration command covering the power-user
knobs that don't belong in the common-case /gsd-settings prompt. Six sectioned
AskUserQuestion batches cover planning, execution, discussion, cross-AI, git,
and runtime settings (19 config keys total). Current values are pre-selected;
numeric fields reject non-numeric input; writes route through
gsd-sdk query config-set so unrelated keys are preserved.
- commands/gsd/settings-advanced.md — command entry
- get-shit-done/workflows/settings-advanced.md — six-section workflow
- get-shit-done/workflows/settings.md — advertise advanced command
- get-shit-done/bin/lib/config-schema.cjs — add context_window to VALID_CONFIG_KEYS
- docs/COMMANDS.md, docs/CONFIGURATION.md, docs/INVENTORY.md — docs + inventory
- tests/gsd-settings-advanced.test.cjs — 81 tests (files, frontmatter,
field coverage, pre-selection, merge-preserves-siblings, VALID_CONFIG_KEYS
membership, confirmation table, /gsd-settings cross-link, negative scenarios)
All 5073 tests pass; coverage 88.66% (>= 70% threshold).
* docs(settings-advanced): clarify per-field numeric bounds and label fenced blocks
Addresses CodeRabbit review on PR #2603:
- Numeric-input rule now states min is field-specific: plan_bounce_passes
and max_discuss_passes require >= 1; other numeric fields accept >= 0.
Resolves the inconsistency between the global rule and the field-level
prompts (CodeRabbit comment 3127136557).
- Adds 'text' fence language to seven previously unlabeled code blocks in
the workflow (six AskUserQuestion sections plus the confirmation banner)
to satisfy markdownlint MD040 (CodeRabbit comment 3127136561).
* test(settings-advanced): tighten section assertion, fix misleading test name, add executable numeric-input coverage
Addresses CodeRabbit review on PR #2603:
- Required section list now asserts the full 'Runtime / Output' heading
rather than the looser 'Runtime' substring (comment 3127136564).
- Renames the subagent_timeout coercion test to match the actual key
under test (was titled 'context_window' but exercised
workflow.subagent_timeout — comment 3127136573).
- Adds two executable behavioral tests at the config-set boundary
(comment 3127136579):
* Non-numeric input on a numeric key currently lands as a string —
locks in that the workflow's AskUserQuestion re-prompt loop is the
layer responsible for type rejection. If a future change adds CLI-side
numeric validation, the assertion flips and the test surfaces it.
* Numeric string on workflow.max_discuss_passes is coerced to Number —
locks in the parser invariant for a second numeric key.
* feat(#2527): add settings layers to /gsd:settings (Group A toggles)
Expand /gsd:settings from 14 to 22 settings, grouped into six visual
sections: Planning, Execution, Docs & Output, Features, Model & Pipeline,
Misc. Adds 8 new toggles:
workflow.pattern_mapper, workflow.tdd_mode, workflow.code_review,
workflow.code_review_depth (conditional on code_review=on),
workflow.ui_review, commit_docs, intel.enabled, graphify.enabled
All 8 keys already existed in VALID_CONFIG_KEYS and docs/CONFIGURATION.md;
this wires them into the interactive flow, update_config write step,
~/.gsd/defaults.json persistence, and confirmation table.
Closes#2527
* test(#2527): tighten leaf-collision and rename mismatched negative test
Addresses CodeRabbit findings on PR #2602:
- comment 3127100796: leaf-only matching collapsed `intel.enabled` and
`graphify.enabled` to a single `enabled` token, so one occurrence
could satisfy both assertions. Replace with hasPathLike(), which
requires each dotted segment to appear in order within a bounded
window. Applied to both update_config and save_as_defaults blocks.
- comment 3127100798: the negative-test description claimed to verify
invalid `code_review_depth` value rejection but actually exercised an
unknown key path. Split into two suites with accurate names: one
asserts settings.md constrains the depth options, the other asserts
config-set rejects an unknown key path.
* docs(#2527): clarify resolved config path for /gsd-settings
Addresses CodeRabbit comment 3127100790 on PR #2602: the original line
implied a single `.planning/config.json` target, but settings updates
route to `.planning/workstreams/<active>/config.json` when a workstream
is active. Document both resolved paths so the merge target is
unambiguous.
* feat(#2473): ship refuses to open PR when HANDOFF.json declares in-progress work
Add a preflight step to /gsd-ship that parses .planning/HANDOFF.json and
refuses to run git push + gh pr create when any remaining_tasks[].status
is not in the terminal set {done, cancelled, deferred_to_backend, wont_fix}.
Refusal names each blocking task and lists four resolutions (finish, mark
terminal, delete stale file, --force). Missing HANDOFF.json is a no-op so
projects that do not use /gsd-pause-work see no behavior change.
Also documents the terminal-statuses contract in references/artifact-types.md
and adds tests/ship-handoff-preflight.test.cjs to lock in the contract.
Closes#2473
* fix(#2473): capture node exit from $() so malformed HANDOFF.json hard-stops
Command substitution BLOCKING=$(node -e "...") discards the inner process
exit code, so a corrupted HANDOFF.json that fails JSON.parse would yield
empty BLOCKING and fall through silently to push_branch — the opposite of
what preflight is supposed to do.
Capture node's exit into HANDOFF_EXIT via $? immediately after the
assignment and branch on it. A non-zero exit is now a hard refusal with
the parser error printed on the preceding stderr line. --force does not
bypass this branch: if the file exists and can't be parsed, something is
wrong and the user should fix it (option 3 in the refusal message —
"Delete HANDOFF.json if it's stale" — still applies).
Verified with a tmp-dir simulation: captured exit 2, hard-stop fires
correctly on malformed JSON. Added a test case asserting the capture
($?) + branch (-ne 0) + parser exit (process.exit(2)) are all present,
so a future refactor can't silently reintroduce the bug.
Reported by @coderabbitai on PR #2553.
Commands are now installed as commands/gsd/<name>.md and invoked as
/gsd:<name> in Claude Code. The old hyphen form /gsd-<name> was still
hardcoded in hundreds of places across workflows, references, templates,
lib modules, and command files — causing "Unknown command" errors
whenever GSD suggested a command to the user.
Replace all /gsd-<cmd> occurrences where <cmd> is a known command name
(derived at runtime from commands/gsd/*.md) using a targeted Node.js
script. Agent names, tool names (gsd-sdk, gsd-tools), directory names,
and path fragments are not touched.
Adds regression test tests/bug-2543-gsd-slash-namespace.test.cjs that
enforces zero legacy occurrences going forward. Removes inverted
tests/stale-colon-refs.test.cjs (bug #1748) which enforced the now-obsolete
hyphen form; the new bug-2543 test supersedes it. Updates 5 assertion
tests that hardcoded the old hyphen form to accept the new colon form.
Closes#2543
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `statusline.show_last_command` config toggle (default: false) that
appends ` │ last: /<cmd>` to the statusline, showing the most recently
invoked slash command in the current session.
The suffix is derived by tailing the active Claude Code transcript
(provided as transcript_path in the hook input) and extracting the last
<command-name> tag. Reads only the final 256 KiB to stay cheap per render.
Graceful degradation: missing transcript, no recorded command, unreadable
config, or parse errors all silently omit the suffix without breaking the
statusline.
Closes#2538
#2549: load_prior_context was reading every prior *-CONTEXT.md file,
growing linearly with project phase count. Cap to the 3 most recent
phases. If .planning/DECISIONS-INDEX.md exists, read that instead.
#2550: scout_codebase claimed to select maps "based on phase type" but
had no classifier — agents read all 7 maps. Replace with an explicit
phase-type-to-maps table (2–3 maps per phase type) with a Mixed fallback.
#2552: Add explicit instruction not to split-read the same file at two
different offsets. Split reads break prompt cache reuse and cost more
than a single full read.
Closes#2549Closes#2550Closes#2552
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The normalization `replace(/^0+/, '')` over-stripped decimal phase IDs:
`"00.1"` collapsed to `".1"`, while the disk-side extractor yielded
`"0.1"` from `"00.1-<slug>"`. Set membership failed and inserted decimal
phases were silently excluded from every disk scan inside
`buildStateFrontmatter`, causing `state update` to rewind progress
counters.
Strip leading zeros only when followed by a digit
(`replace(/^0+(?=\d)/, '')`), preserving the zero before the decimal
point while keeping existing behavior for zero-padded integer IDs.
Closes#2554
* fix(#2530-2535): correct VALID_CONFIG_KEYS set — remove internal state key, add missing public keys, add migration hints
- Remove workflow._auto_chain_active from VALID_CONFIG_KEYS (internal runtime state, not user-settable) (#2530)
- Add hooks.workflow_guard to VALID_CONFIG_KEYS (read by gsd-workflow-guard.js hook, already documented) (#2531)
- Add workflow.ui_review to VALID_CONFIG_KEYS (read in autonomous.md via config-get) (#2532)
- Add workflow.max_discuss_passes to VALID_CONFIG_KEYS (read in discuss-phase.md via config-get) (#2533)
- Add CONFIG_KEY_SUGGESTIONS entries for sub_repos → planning.sub_repos and plan_checker → workflow.plan_check (#2535)
- Document workflow.ui_review and workflow.max_discuss_passes in docs/CONFIGURATION.md
- Clear INTERNAL_KEYS exemption in parity test (workflow._auto_chain_active removed from schema entirely)
- Add regression test file tests/bug-2530-valid-config-keys.test.cjs covering all 6 bugs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: align SDK VALID_CONFIG_KEYS with CJS — remove internal key, add missing public keys
- Remove workflow._auto_chain_active from SDK (internal runtime state, not user-settable)
- Add workflow.ui_review, workflow.max_discuss_passes, hooks.workflow_guard to SDK
- Add ui_review and max_discuss_passes to Full Schema example in CONFIGURATION.md
Resolves CodeRabbit review on #2561.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(sdk): add queued_phases to init.manager (closes#2497)
Surfaces the milestone immediately AFTER the active one so the
/gsd-manager dashboard can preview upcoming phases without mixing
them into the active phases grid.
Changes:
- roadmap.ts: exports two new helpers
- extractPhasesFromSection(section): parses phase number / name /
goal / depends_on using the same pattern initManager uses for
the active milestone, so queued phases have identical shape.
- extractNextMilestoneSection(content, projectDir): resolves the
current milestone via the STATE-first path (matching upstream
PR #2508) then scans for the next ## milestone heading. Shipped
milestones are stripped first so they can't shadow the real
next. Returns null when the active milestone is the last one.
- init-complex.ts: initManager now exposes
- queued_phases: Array<{ number, name, display_name, goal,
depends_on, dep_phases, deps_display }>
- queued_milestone_version: string | null
- queued_milestone_name: string | null
Existing phases array is unchanged — callers that only care about
the active milestone see no behavior difference.
Scope note: PR #2508 (merged upstream 2026-04-21) superseded the
#2495 + #2496 portions of this branch's original submission. This
commit is the rebased remainder contributing only #2497 on top of
upstream's new helpers.
Test coverage (7 new tests, all passing):
- roadmap.test.ts: +5 tests
- extractPhasesFromSection parses multiple phases with goal + deps
- extractPhasesFromSection returns [] when no phase headings
- extractNextMilestoneSection returns the milestone after the
STATE-resolved active one
- extractNextMilestoneSection returns null when active is last
- extractNextMilestoneSection returns null when no version found
- init-complex.test.ts: +4 tests under `queued_phases (#2497)`
- surfaces next milestone with version + name metadata
- queued entries carry name / deps_display / display_name
- queued phases are NOT mixed into active phases list
- returns [] + nulls when active is the last milestone
All 51 tests in roadmap.test.ts + init-complex.test.ts pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(workflows): render queued_phases section in /gsd-manager dashboard
Surfaces the new `queued_phases` / `queued_milestone_version` /
`queued_milestone_name` fields from init.manager (SDK #2497) in a
compact preview section directly below the main active-milestone
table.
Changes to workflows/manager.md:
- Initialize step: parse the optional trio
(queued_milestone_version, queued_milestone_name, queued_phases)
alongside the existing init.manager fields. Treat missing as
empty for backward compatibility with older SDK versions.
- Dashboard step: new "Queued section (next milestone preview)"
rendered between the main active-milestone grid and the
Recommendations section. Renders only when queued_phases is
non-empty; skipped entirely when absent or empty (e.g. active
milestone is the last one).
- Queued rows render without D/P/E columns since the phases haven't
been discussed yet — just number, display_name, deps_display,
and a fixed "· Queued" status.
- Success criterion added: queued section renders when non-empty
and is skipped when absent.
Queued phases are deliberately NOT eligible for the Continue action
menu; they live in a future milestone. The preview exists for
situational awareness only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When model_profile is "inherit", execute-phase was passing the literal string
"inherit" to Task(model=), causing fallback to the default model. The workflow
now documents that executor_model=="inherit" requires omitting the model= parameter
entirely so Claude Code inherits the orchestrator model automatically.
Closes#2516
Scan REQUIREMENTS.md body for all **REQ-ID** patterns during phase
complete and emit a warning for any IDs absent from the Traceability
table, regardless of whether the roadmap has a Requirements: line.
Closes#2526
Step 8 file list omitted deferred-items.md, leaving executor out-of-scope
findings untracked after final commit even with commit_docs: true.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Flips the bias in step 8b: build a simple HTML page/web UI by default,
fall back to stdout only for pure fact-checking (binary yes/no, benchmarks).
Mirrors upstream spike-idea skill constraint #3 update.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spike workflow:
- Add frontier mode (no-arg or "frontier" proposes integration + frontier spikes)
- Add depth-over-speed principle — follow surprising findings, test edge cases,
document investigation trail not just verdict
- Add CONVENTIONS.md awareness — follow established patterns, update after session
- Add Requirements section in MANIFEST — track design decisions as they emerge
- Add re-ground step before each spike to prevent drift in long sessions
- Add Investigation Trail section to README template
- Restructured prior context loading with priority ordering
- Research step now runs per-spike with briefing and approach comparison table
Sketch workflow:
- Add frontier mode (no-arg or "frontier" proposes consistency + frontier sketches)
- Add spike context loading — ground mockups in real data shapes, requirements,
and conventions from spike findings
Spike wrap-up workflow:
- Add CONVENTIONS.md generation step (recurring stack/structure/pattern choices)
- Reference files now use implementation blueprint format (Requirements, How to
Build It, What to Avoid, Constraints)
- SKILL.md now includes requirements section from MANIFEST
- Next-steps route to /gsd-spike frontier mode instead of inline analysis
Sketch wrap-up workflow:
- Next-steps route to /gsd-sketch frontier mode
Commands updated with frontier mode in descriptions and argument hints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(insert-phase): update STATE.md next-phase recommendation after inserting a phase
Closes#2502
* fix(insert-phase): update all STATE.md pointers; tighten test scope
Two follow-up fixes from CodeRabbit review of #2509:
1. The update_project_state instruction only said to find "the line" for
the next-phase recommendation. STATE.md can have multiple pointers
(structured current_phase: field AND prose recommendation text).
Updated wording to explicitly require updating all of them in the same
edit.
2. The regression test for the next-phase pointer update scanned the
entire file, so a match anywhere would pass even if update_project_state
itself was missing the instruction. Scoped the assertion to only the
content inside <step name="update_project_state"> to prevent false
positives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(detect-custom-files): exclude skills and command dirs not wiped by installer (closes#2505)
GSD_MANAGED_DIRS included 'skills' and 'command' directories, but the
installer never wipes those paths. Users with third-party skills installed
(40+ files, none in GSD's manifest) had every skill flagged as a "custom
file" requiring backup, producing noisy false-positive reports on every
/gsd-update run.
Removes 'skills' and 'command' from both gsd-tools.cjs and the SDK's
detect-custom-files.ts. Adds two regression tests confirming neither
directory is scanned.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(settings): warn that model profiles are no-ops on non-Claude runtimes (closes#2506)
settings.md presented Quality/Balanced/Budget model profiles without any
indication that these tiers map to Claude models (Opus/Sonnet/Haiku) and
have no effect on non-Claude runtimes (Codex, Gemini CLI, OpenRouter).
Users on Codex saw the profile chooser as if it would meaningfully select
models, but all agents silently used the runtime default regardless.
Adds a non-Claude runtime note before the profile question (shown in
TEXT_MODE, the path all non-Claude runtimes take) explaining the profiles
are no-ops and directing users to either choose Inherit or configure
model_overrides manually. Also updates the Inherit option description to
explicitly name the runtimes where it is the correct choice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The guard at the worktree-merge resurrection block was inverting the
intended logic: it deleted any .planning/ file absent from PRE_MERGE_FILES,
which includes brand-new files (e.g. SUMMARY.md just created by the
executor). A genuine resurrection is a file that was previously tracked on
main, deliberately removed, and then re-introduced by the merge. Detecting
that requires a git history check — not just tree membership.
Fix: replace the PRE_MERGE_FILES grep guard with a `git log --follow
--diff-filter=D` check that only removes the file if it has a deletion
event in main's ancestry.
Closes#2501
* feat(plan-phase): chunked mode + filesystem fallback for Windows stdio hang (#2310)
Addresses the 2026-04-16 Windows incident where gsd-planner wrote all 5
PLAN.md files to disk but Task() never returned, hanging the orchestrator
for 30+ minutes. Two mitigations:
1. Filesystem fallback (steps 9a, 11a): when Task() returns with an
empty/truncated response but PLAN.md files exist on disk, surface a
recoverable prompt (Accept plans / Retry planner / Stop) instead of
silently failing. Directly addresses the post-restart recovery path.
2. Chunked mode (--chunked flag / workflow.plan_chunked config): splits the
single long-lived planner Task into a short outline Task (~2 min) followed
by N short per-plan Tasks (~3-5 min each). Each plan is committed
individually for crash resilience. A hang loses one plan, not all of them.
Resume detection skips plans already on disk on re-run.
RCA confirmed: task state mtime 14:29 vs PLAN.md writes 14:32-14:52 =
subagent completed normally, IPC return was dropped by Windows stdio deadlock.
Neither mitigation fixes the root cause (requires upstream Task() timeout
support); both bound damage and enable recovery.
New reference file planner-chunked.md keeps OUTLINE COMPLETE / PLAN COMPLETE
return formats out of gsd-planner.md (which sits at 46K near its size limit).
Closes#2310
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(plan-phase): address CodeRabbit review comments on #2499
- docs/CONFIGURATION.md: add workflow.plan_chunked to full JSON schema example
- plan-phase.md step 8.5.1: validate PLAN-OUTLINE.md with grep for OUTLINE
COMPLETE marker before reusing (not just file existence)
- plan-phase.md step 8.5.2: validate per-plan PLAN.md has YAML frontmatter
(head -1 grep for ---) before skipping in resume path
- plan-phase.md: add language tags (text/javascript/bash) to bare fenced
code blocks in steps 8.5, 9a, 11a (markdownlint MD040)
- Rejected: commit_docs gate on per-plan commits (gsd-sdk query commit
already respects commit_docs internally — comment was a false positive)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(plan-phase): route Accept-plans through step 9 PLANNING COMPLETE handling
Honors --skip-verify / plan_checker_enabled=false in 9a fallback path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(execute-phase): post-merge deletion audit for bulk file deletions (closes#2384)
Two data-loss incidents were caused by worktree merges bringing in bulk
file deletions silently. The pre-merge check (HEAD...WT_BRANCH) catches
deletions on the worktree branch, but files deleted during the merge
itself (e.g., from merge conflict resolution or stale branch state) were
not audited post-merge.
Adds a post-merge audit immediately after git merge --no-ff succeeds:
- Counts files deleted outside .planning/ in the merge commit
- If count > 5 and ALLOW_BULK_DELETE!=1: reverts the merge with
git reset --hard HEAD~1 and continues to the next worktree
- Logs the full file list and an escape-hatch instruction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): tighten post-merge deletion audit assertions (CodeRabbit #2483)
Replace loose substring checks with exact regex assertions:
- assert.match against 'git diff --diff-filter=D --name-only HEAD~1 HEAD'
- assert.match against threshold gate + ALLOW_BULK_DELETE override condition
- assert.match against git reset --hard HEAD~1 revert
- assert.match against MERGE_DEL_COUNT grep -vc for non-.planning count
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(inventory): update workflow count to 81 (graduation.md added in #2490)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(install): remove bare ~/.claude reference in update.md (closes#2470)
The installer's copyWithPathReplacement() replaces ~/\.claude\/ (with
trailing slash) but not ~/\.claude (bare, no trailing slash). A comment
on line 398 of update.md used the bare form, which scanForLeakedPaths()
correctly flagged for every non-Claude runtime install.
Replaced the example in the comment with a non-Claude runtime path so
the file passes the scanner for all runtimes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): align regex with installer's word-boundary semantics (CodeRabbit #2482)
Replace negative lookahead (?!\/) with \b word boundary to match the
installer's scanForLeakedPaths() pattern. The lookahead would incorrectly
flag ~/.claude_suffix whereas \b correctly excludes it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): revert \b regex — (?!\/) was intentionally scoped to bare refs
The installer's scanForLeakedPaths uses \b but the test is specifically
checking for bare ~/.claude without trailing slash that the replacer misses.
~/.claude/ (with slash) at line 359 of update.md is expected and handled.
\b would flag it as a false positive.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(inventory): update workflow count to 81 (graduation.md added in #2490)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>