Compare commits

...

790 Commits

Author SHA1 Message Date
Tom Boucher
a411e08e88 fix(coderabbit): resolve all 12 findings on PR #3152
MAJOR (security/correctness):
- commands/gsd/debug.md: add Write to allowed-tools (session file creation
  requires it — workflow explicitly says 'use Write tool, never heredoc')
- workflows/debug.md: add SLUG sanitization guard to steps 1b+1c (status/
  continue subcommands used raw user input in file paths — path traversal)
- workflows/thread.md: sanitize $ARGUMENTS in RESUME mode before file path
  construction (was bypassing the sanitization guard in CLOSE/STATUS modes)

MINOR (consistency/correctness):
- docs/INVENTORY-MANIFEST.json: remove stale top-level 'workflows' array
  (duplicate of families.workflows introduced in earlier update)
- commands/gsd/resume-work.md: normalize process to 'Execute end-to-end.'
- commands/gsd/settings.md: normalize process to 'Execute end-to-end.'
- commands/gsd/update.md: normalize otherwise branch to 'execute end-to-end.'
- docs/adr/0002: add Status: Accepted + Date header (ADR convention)
- workflows/extract-learnings.md: rename step extract_learnings → extract-learnings
- tests/extract-learnings.test.cjs: tighten step-name assertion to exact name

ARCHITECTURE:
- scripts/command-contract-helpers.cjs: extract CANONICAL_TOOLS, parseFrontmatter,
  executionContextRefs as shared module — single source of truth consumed by
  both lint script and test suite (prevents silent lint/test disagreement)
- scripts/lint-command-contract.cjs: require() helpers instead of duplicating
- tests/command-contract.test.cjs: require() helpers; move readFileSync calls
  inside test() callbacks (registration-time throws surface as named failures)
2026-05-05 16:06:29 -04:00
Tom Boucher
b752a9aae7 fix(tests): redirect implementation tests to workflow files after extraction
After extracting debug.md and thread.md implementations to workflow files
and renaming extract_learnings.md, existing tests still referenced the
old locations:

- debug-session-management.test.cjs: commands/gsd/debug.md → workflows/debug.md
- thread-session-management.test.cjs: commands/gsd/thread.md → workflows/thread.md
- extract-learnings.test.cjs: extract_learnings.md → extract-learnings.md
- enh-2430-learnings-consumption.test.cjs: extract_learnings.md → extract-learnings.md

Also adds <available_agent_types> block and TEXT_MODE fallback note to
get-shit-done/workflows/debug.md to satisfy the spawn-type-consistency
(#1357) and AskUserQuestion text-mode fallback (#2012) contract tests
that scan all workflow files.
2026-05-05 15:44:59 -04:00
Tom Boucher
ecf3510511 chore(changeset): add changeset for ADR-0002 enhancement (#3151) 2026-05-05 15:36:45 -04:00
Tom Boucher
81f9534b5a feat(adr-0002): command contract validation module + prose @-ref cleanup + workflow extraction
ADR-0002: commands/gsd/*.md contract now enforced at two layers:

LINT (scripts/lint-command-contract.cjs — new CI step):
- name: present, starts with gsd: or gsd-
- description: non-empty
- allowed-tools: non-empty, all entries canonical
- execution_context @-refs: resolve on disk, no trailing prose on same line
- handles both @~/ and $HOME/ path prefixes

TEST (tests/command-contract.test.cjs — 361 assertions):
- Behavioral contract for all 65 command files
- Replaces scattered coverage in enh-2790 + bug-3135
- Per-command per-rule test — one failure names the exact file + rule

CI (.github/workflows/test.yml):
- 'Lint — command contract (ADR-0002)' step added to lint-tests job

PROSE @-REF CLEANUP (39 command files, ~900 tokens/invocation recovered):
- Removed redundant @~/.claude/get-shit-done/... paths from <process> prose
- execution_context block is now the single authoritative load declaration
- Routing commands (sketch, spike, update, pause-work, etc.) keep routing
  instructions; only the inert path token is stripped

WORKFLOW EXTRACTION (debug.md + thread.md, ~15,000 chars / ~3,750 tokens):
- get-shit-done/workflows/debug.md: full process extracted from commands/gsd/debug.md
- get-shit-done/workflows/thread.md: full process extracted from commands/gsd/thread.md
- Command files reduced to frontmatter + objective + execution_context + context
- debug.md: 9,603 → 1,703 chars; thread.md: 7,868 → 585 chars

RENAME:
- get-shit-done/workflows/extract_learnings.md → extract-learnings.md
  (aligns with hyphen convention of all other workflow files)

DOCS:
- docs/INVENTORY.md: count 85→87, new rows, rename row, fix add-todo --backlog attribution
- docs/INVENTORY-MANIFEST.json: +debug.md +thread.md +extract-learnings.md -extract_learnings.md

Closes ADR-0002 implementation.
2026-05-05 15:18:13 -04:00
Tom Boucher
695ad986c0 docs(adr): add ADR-0002 command contract validation module 2026-05-05 15:09:24 -04:00
Tom Boucher
519de8a91d docs(context): add workflow learnings from 2026-05-05 triage + PR cycle
- Skill consolidation gap class: missing workflow files, detection via regression test
- CodeRabbit stale thread resolution pattern after allow-test-rule fixes
- PR discipline: split unrelated changes, one concern per PR
- INVENTORY.md must stay in sync with workflow filesystem on every add/remove
- README: storyline-only target, MD001/MD040 markdownlint rules to watch
- Issue triage: always check local branches for crash-recovery before re-implementing
- SDK-only verbs: golden-policy NO_CJS_SUBPROCESS_REASON exemption required
2026-05-05 15:03:38 -04:00
Tom Boucher
c2b3f02d41 fix(#3135): restore workflows/add-backlog.md — capture --backlog had no workflow to load (#3147)
* fix(#3121): implement commands verb in SDK native registry

- Add commandsList handler — returns sorted JSON array of all registered
  verb strings; satisfies workstream-flag.md + agent tooling discoverability
- Register ['commands', commandsList] in DECISION_ROUTING_STATIC_CATALOG
- Add golden-policy exemption (SDK-only, no CJS mirror needed)
- check.decision-coverage-plan/verify were already registered; commands was the remaining gap

Closes #3121

* fix(#3135): restore workflows/add-backlog.md — capture --backlog had no workflow to load

Root cause: PR #2824 consolidated add-backlog into gsd-capture --backlog and
wired capture.md to delegate to workflows/add-backlog.md via execution_context.
The workflow file was never created (same gap class as reapply-patches.md which
was caught and fixed in the same PR). With no file to load, the agent had no
implementation steps to follow when --backlog was invoked.

Fix:
- Restore get-shit-done/workflows/add-backlog.md with full process from deleted
  commands/gsd/add-backlog.md (phase.next-decimal, ROADMAP write, mkdir, commit)
- Preserve #2280 ordering invariant: ROADMAP entry written before directory
- Fix docs/INVENTORY.md: remove incorrect attribution of --backlog to add-todo.md,
  add add-backlog.md row, bump workflow count 84→85
- Update docs/INVENTORY-MANIFEST.json
- Add regression test: every execution_context @-reference in commands/gsd/*.md
  must resolve to an existing workflow file on disk

Closes #3135
2026-05-05 15:02:38 -04:00
Tom Boucher
9811782e6d fix(#3121): implement commands verb in SDK native registry (#3146)
- Add commandsList handler — returns sorted JSON array of all registered
  verb strings; satisfies workstream-flag.md + agent tooling discoverability
- Register ['commands', commandsList] in DECISION_ROUTING_STATIC_CATALOG
- Add golden-policy exemption (SDK-only, no CJS mirror needed)
- check.decision-coverage-plan/verify were already registered; commands was the remaining gap

Closes #3121
2026-05-05 15:02:34 -04:00
Tom Boucher
669d6a1f32 fix(#3127): make state.begin-phase idempotent on mid-flight phases (#3145)
* fix(#3127): make state.begin-phase idempotent on mid-flight phases

Root cause: cmdStateBeginPhase() unconditionally overwrote execution-
progress fields regardless of current phase status. When execute-phase
called it on a phase already mid-flight (--wave N resume), it regressed:
  - Current Plan to 1 (from e.g. 3)
  - Last Activity Description to 'context gathered; ready for plan-phase'
  - Plan: N of M body line to 'Plan: 1 of M'
  - last_updated timestamp to an older value
  - progress.percent could decrease

Fix: read Status field before writing. If phase is already executing
(Status: Executing Phase N), skip execution-progress fields and only
update fields safe on resume:
  - Last Activity date (always safe)
  - Resume-specific 'execution resumed (wave continue)' activity line

First-time execution (Status != Executing Phase N) writes all fields
as before -- no regression on the normal path.

Regression test: 4 real unit tests using synthetic STATE.md files:
  - mid-flight phase does not reset Current Plan (was the bug)
  - mid-flight phase does not overwrite stopped_at narrative
  - fresh phase sets Current Plan to 1 (normal path, no regression)
  - both paths update Last Activity date (safe field)

Suite: 6990/6990. Closes #3127.

* fix(lint+state): allow-test-rule, escapeRegex phaseNumber in idempotency guard
2026-05-05 15:02:30 -04:00
Tom Boucher
ba0409e04e fix(#3097, #3099): add cwd-drift sentinel + absolute-path guard to executor worktree protocol (#3144)
* fix(#3097, #3099): add cwd-drift + absolute-path guards to executor worktree protocol

#3097 — cwd-drift sentinel (gsd-executor.md task_commit_protocol step 0a):
  A Bash cd out of the worktree makes [ -f .git ] false, silently skipping
  all HEAD/branch safety guards. Commits land on main's branch.
  Fix: on first commit, capture spawn-time toplevel into sentinel file at
  .git/worktrees/<name>/gsd-spawn-toplevel. Before every subsequent commit,
  verify ACTUAL_TL matches EXPECTED_TL. Exits 1 with recovery instructions
  if drift detected.

#3099 — absolute-path guard (gsd-executor.md task_commit_protocol step 0b):
  Absolute paths constructed from the orchestrator's pwd (main repo root)
  resolve to the main repo inside worktrees. Edit/Write lands in wrong dir;
  git commit sees a clean worktree tree; work silently lost or leaks to main.
  Fix: before any absolute-path Edit/Write, verify path starts with
  WT_ROOT=/Users/thbouc/projects/get-shit-done. Prefer relative paths.

Both guards are documented in references/worktree-path-safety.md, which
is now loaded into every executor spawn prompt via <execution_context>.
The <worktree_branch_check> footnote references all three steps (0/0a/0b).

execute-phase.md: extracted worktree bash commands to reference file
(safe embed — @ files are inlined before the executor processes the prompt).
The blank line in <required_reading> was removed to stay at the XL=1700 line
budget after adding the @ reference.

Suite: 6986/6986. Closes #3097. Closes #3099.

* fix(lint+executor+docs): allow-test-rule, fix [ -f .git ] guard, fail-closed abs-path check, fix INVENTORY count
2026-05-05 15:02:26 -04:00
Tom Boucher
d993e71adf fix(#3096): enforce sequential Steps 7+8 + Edit-only tool discipline in ai-integration-phase (#3143)
* fix(#3096): enforce sequential Steps 7+8 + Edit-only discipline in ai-integration-phase

Root cause: Steps 7 (gsd-ai-researcher) and 8 (gsd-domain-researcher)
were listed without an explicit sequential constraint. An orchestrator
optimizing for speed could parallelize them since sections appeared
disjoint. gsd-domain-researcher's Write at finalization replaced the
full AI-SPEC.md with its in-memory copy (pre-researcher state), losing
Sections 3/4. Confirmed at 40% incidence (2/5 agents on a real run).
Recovery cost: one extra ai-researcher dispatch, ~18 min wall.

Fix:
  - Explicit 'MUST run sequentially' note on Step 7 (ordering note)
  - 'Wait for Step 7 to complete before spawning Step 8' on Step 8
  - Edit-only tool discipline injected into both agent prompts:
      'Use Edit exclusively - NEVER use Write on this file'
    prevents the last-writer-wins overwrite regardless of dispatch order

Suite: 7043/7043. Closes #3096.

* fix(lint): allow-test-rule for ai-integration-phase structural contract test
2026-05-05 15:02:23 -04:00
Tom Boucher
47ed26a01b fix(#3120): add register_authored_at_plan_time guard — prevent rubber-stamping legacy phases (#3142)
* fix(#3120): add register_authored_at_plan_time guard to secure-phase

Root cause: Step 3 short-circuit used threats_open: 0 as the sole
condition to skip directly to Step 6 (write clean SECURITY.md). It
did not distinguish empty-by-all-mitigated from empty-by-no-planning.
Legacy phases authored before <threat_model> blocks were canonical
received a rubber-stamped clean SECURITY.md with no audit performed.

Fix:
  Step 2c: track register_authored_at_plan_time (true iff >=1 PLAN
           file contained a parseable <threat_model> block)
  Step 3:  two-condition short-circuit:
           - threats_open:0 AND register_authored_at_plan_time:true
             -> skip to Step 6 (legitimate, all mitigated)
           - threats_open:0 AND register_authored_at_plan_time:false
             -> retroactive-STRIDE mode in Step 5 (build register
                from implementation, then verify)
  Step 5:  auditor constraint varies by mode:
           planned     -> Verify mitigations exist, do not scan
           retroactive -> Build STRIDE register first, then verify

Suite: 7039/7039. Closes #3120.

* fix(lint+changeset): allow-test-rule, drop dead regex branches, fix pr field to 3142
2026-05-05 15:02:19 -04:00
Tom Boucher
7827e1ddee fix(#3129): replace bypassed bash regex with token-walk git-cmd.js classifier (#3141)
* fix(#3129): replace bypassed bash regex with token-walk git-cmd.js classifier

Root cause: gsd-validate-commit.sh used:
  if [[ "$CMD" =~ ^git[[:space:]]+commit ]]
This regex silently bypasses Conventional Commits enforcement for:
  git -C /path commit -m ...     (working-directory prefix)
  GIT_AUTHOR_NAME=x git commit   (env-var prefix)
  /usr/bin/git commit -m ...     (full-path executable)

Fix: introduces hooks/lib/git-cmd.js with isGitSubcommand(cmd, sub) —
a token-walk classifier that handles all four forms by:
  1. Skipping leading VAR=VALUE env assignments
  2. Validating the git executable (basename check for full-path support)
  3. Consuming git global options (-C <path>, --git-dir=, -p, etc.)
  4. Checking the subcommand token

The hook delegates to this classifier via node shell-out. node is
already called twice in this hook (config check + JSON parse), so no
new runtime dependency.

This becomes the single source of truth for all hooks that gate on
git subcommands (pre-commit-review-gate, post-push-verify, etc.).

Regression test: 27 assertions — tokenize correctness, 12 must-match
cases (including all 3 bypass forms), 8 must-not-match cases, 3 source
checks. All are real behavioral tests, not string comparisons.
Suite: 7035/7035. Closes #3129.

* fix(lint+hook+changeset): allow-test-rule, fix HOOK_DIR quote injection, fix changeset pr+typo
2026-05-05 15:02:15 -04:00
Tom Boucher
375bf3abd6 fix(#3126): replace hardcoded globalSkillsBase with first-class runtime-aware mapping (#3140)
* fix(#3126): replace hardcoded globalSkillsBase with runtime-aware mapping

Root cause: buildAgentSkillsBlock() used path.join(os.homedir(), '.claude',
'skills') for globalSkillsBase regardless of config.runtime. Cursor users
(and every non-Claude runtime) saw their global: skill lookups fail with
a warning pointing to the wrong directory.

Fix: introduces get-shit-done/bin/lib/runtime-homes.cjs — a pure, side-
effect-free module covering all 15 GSD runtimes:

  Runtime      Config base              Skills path
  claude        ~/.claude               ~/.claude/skills/
  cursor        ~/.cursor               ~/.cursor/skills/
  gemini        ~/.gemini               ~/.gemini/skills/
  codex         ~/.codex                ~/.codex/skills/
  copilot       ~/.copilot              ~/.copilot/skills/
  antigravity   ~/.gemini/antigravity   ...antigravity/skills/
  windsurf      ~/.codeium/windsurf     ...windsurf/skills/
  augment       ~/.augment              ~/.augment/skills/
  trae          ~/.trae                 ~/.trae/skills/
  qwen          ~/.qwen                 ~/.qwen/skills/
  hermes        ~/.hermes               ~/.hermes/skills/gsd/ (nested #2841)
  codebuddy     ~/.codebuddy            ~/.codebuddy/skills/
  cline         ~/.cline                null (rules-based, no skills dir)
  opencode      ~/.config/opencode      ...opencode/skills/
  kilo          ~/.config/kilo          ...kilo/skills/

Also adds CLAUDE_CONFIG_DIR env var support (was missing).
Warning messages now show the actual runtime-specific path.
Docs: INVENTORY.md CLI Modules 41→42.

Regression test: 30 assertions across all runtimes.
Suite: 7008/7008. Closes #3126.

* fix(lint+init): allow-test-rule, fix display path duplication (skillName appended twice)
2026-05-05 15:02:11 -04:00
Tom Boucher
b0be6755e7 fix(#3128): extend roadmap.cjs plan-count to detect {N}-PLAN-{NN}-{slug}.md layout (#3139)
* fix(#3128): extend roadmap.cjs plan-count to match {N}-PLAN-{NN}-{slug}.md

Root cause: same regex flaw as #2893 (fixed in phase.cjs by #2896).
The manager-dashboard countPhasePlansAndSummaries() in roadmap.cjs was
not updated alongside the phase.cjs fix. Files like 5-PLAN-01-setup.md
end in -setup.md, not -PLAN.md, so plan_count returned 0.

Symptom: init manager returned plan_count=0 / disk_status=discussed for
fully-planned phases, triggering redundant background planner agents that
correctly detected existing plans and declined -- wasted runs.

Fix: apply the same looksLikePlanFile pattern from phase.cjs with
PLAN-OUTLINE and pre-bounce exclusions to countPhasePlansAndSummaries.

Regression test: tests/bug-3128-roadmap-plan-count-slug-layout.test.cjs
Suite: 6985/6985. Closes #3128.

* fix(lint): allow-test-rule for roadmap isPlanFile structural contract test
2026-05-05 15:02:07 -04:00
Tom Boucher
3f57a13ccf fix(#3087): restore 10 demoted directive phrases in gsd-planner.md (#3138)
* fix(#3087): restore 10 demoted directive phrases in gsd-planner.md

CRITICAL/MANDATORY/ALWAYS/MUST emphasis was systematically removed in
v1.38.4 (PR #2489) without documentation. Conflicts with PR #2489's own
stated intent (sycophancy-hardening). Downstream effect: weaker adherence
to user decisions and requirement coverage in v1.38.4-v1.40.x.

Restored:
  CRITICAL: User Decision Fidelity (heading)
  CRITICAL: Never Simplify User Decisions (heading)
  Multi-Source Coverage Audit (MANDATORY in every plan set)
  Audit ALL four source types before finalizing
  Discovery is MANDATORY unless you can prove...
  ALWAYS split if:
  requirements MUST list requirement IDs from ROADMAP
  CRITICAL: Every requirement ID MUST appear in at least one plan
  ALWAYS use the Write tool to create files
  CRITICAL — File naming convention (enforced)

Regression test: tests/bug-3087-planner-directive-language.test.cjs
(10 assertions, one per restored directive — all pass).
Suite: 6983/6983. Closes #3087.

* fix(changeset+test): fix pr field to 3138, wrap readFileSync in try/catch
2026-05-05 15:02:03 -04:00
Tom Boucher
3e2682d3c9 fix(#3130): harden update.md npx invocations against cache-stale and token-routing failures (#3136)
* fix(#3130): harden update.md npx invocations against cache-stale and token-routing

Two failure modes with the old form:
1. Cache-stale: npx serves a cached older version (no --package= flag)
2. Token-routing: Bash-tool wrapper misroutes @ token in package@tag spec

All three sibling invocations (local/global/unknown) now use:
  npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc $ARGS

--package= forces a fresh registry fetch; -- prevents token misrouting.

Also fixes the manual-update hint in the error-exit block.

Regression test: tests/bug-3130-update-npx-robust-invocation.test.cjs
Suite: 6973/6973 pass. Closes #3130.

* fix(lint): allow-test-rule for update.md structural contract test
2026-05-05 15:01:59 -04:00
Tom Boucher
ad8ba840bc Merge pull request #3149 from gsd-build/docs/readme-rewrite-storyline-only
docs(#3148): rewrite root README — storyline + highlights only, link to docs for detail
2026-05-05 14:59:17 -04:00
Tom Boucher
622f3a8ea4 fix(readme): convert admonition heading to bold to fix MD001 heading level skip 2026-05-05 14:46:17 -04:00
Tom Boucher
5d1e485d05 fix(readme): add bash language identifier to all fenced code blocks (MD040) 2026-05-05 14:25:18 -04:00
Tom Boucher
4ab1da354e docs(readme): rewrite root README — storyline + highlights only, link to docs for detail
997 → 272 lines. Remove redundancy with docs/:
- Full 15-runtime install flag matrix → docs/USER-GUIDE.md
- Minimal install deep-dive → docs/USER-GUIDE.md
- Wave execution ASCII diagram → docs/ARCHITECTURE.md
- 12-table command reference → docs/COMMANDS.md
- Full config schema + all settings tables → docs/CONFIGURATION.md
- Security section + full uninstall list → docs/USER-GUIDE.md
- v1.39.0 highlights → CHANGELOG.md

Keep: hero, author note, 6-step loop (condensed), Getting Started,
core command table, why-it-works (3 bullets), config (key dials only),
docs table, troubleshooting (essentials), community, license.
2026-05-05 14:19:06 -04:00
Tom Boucher
48f09d34af docs(context): add recurring PR mistakes distilled from CodeRabbit reviews 2026-05-05 13:59:27 -04:00
Tom Boucher
9de8e24463 Merge pull request #3133 from gsd-build/fix/3131-rewire-orphaned-workflows-missed-consolidation
fix(#3131): re-wire 4 orphaned workflows as flags on parent commands
2026-05-05 11:28:36 -04:00
Tom Boucher
811410be61 fix: address all 13 CodeRabbit comments from second review pass
Duplicate /gsd-help rows (caused by join-discord → help replacement
landing in tables that already had /gsd-help):
- Remove Discord-purpose duplicate row from README.md, README.ja-JP.md,
  README.zh-CN.md, README.ko-KR.md, docs/zh-CN/README.md,
  docs/zh-CN/USER-GUIDE.md, docs/ja-JP/USER-GUIDE.md,
  docs/ko-KR/USER-GUIDE.md
- Remove orphaned Discord-only ### /gsd-help sections from
  docs/ja-JP/COMMANDS.md and docs/ko-KR/COMMANDS.md

Gap-fix command precision (plan-milestone-gaps → audit-milestone --fix):
- README.ja-JP.md, README.ko-KR.md, README.zh-CN.md gap-fix rows
  updated to /gsd-audit-milestone --fix

docs/COMMANDS.md: document --path <dir> for --from-gsd2 in table and
  example block

docs/FEATURES.md:
- Add adaptive to /gsd-config --profile value set
- Add blank line before spike Produces table (MD058)

Suite: 6971/6971 pass
2026-05-05 11:22:37 -04:00
Tom Boucher
891eae1025 fix: short-circuit --assumptions and --from-gsd2 dispatch; add changeset
- discuss-phase --assumptions: add 'Stop here' + convert If→Otherwise
  chain so the flag is an exclusive route (CodeRabbit major)
- import --from-gsd2: add 'Stop here' + convert final 'Execute...'
  to 'Otherwise...' to prevent fall-through to standard import
  (CodeRabbit major, inline comment)
- .changeset/rewire-orphaned-workflows-3131.md: add missing changeset
2026-05-05 11:05:17 -04:00
Tom Boucher
858c821829 docs: sweep stale /gsd-* command references across all user-facing docs
Replace 30 absorbed/deleted standalone command forms with their
consolidated flag-based equivalents across 25 files (English + 4
locales + AGENTS/CLI-TOOLS/CONFIGURATION):

  /gsd-session-report        → /gsd-pause-work --report
  /gsd-list-phase-assumptions → /gsd-discuss-phase --assumptions
  /gsd-analyze-dependencies  → /gsd-manager --analyze-deps
  /gsd-research-phase        → /gsd-plan-phase --research-phase
  /gsd-plan-milestone-gaps   → /gsd-audit-milestone
  /gsd-code-review-fix       → /gsd-code-review --fix
  /gsd-spike-wrap-up         → /gsd-spike --wrap-up
  /gsd-sketch-wrap-up        → /gsd-sketch --wrap-up
  /gsd-set-profile           → /gsd-config --profile
  /gsd-check-todos           → /gsd-capture --list
  /gsd-add-todo              → /gsd-capture
  /gsd-add-backlog           → /gsd-capture --backlog
  /gsd-plant-seed            → /gsd-capture --seed
  /gsd-note                  → /gsd-capture --note
  /gsd-add-phase             → /gsd-phase
  /gsd-insert-phase          → /gsd-phase --insert
  /gsd-edit-phase            → /gsd-phase --edit
  /gsd-remove-phase          → /gsd-phase --remove
  /gsd-new-workspace         → /gsd-workspace --new
  /gsd-list-workspaces       → /gsd-workspace --list
  /gsd-remove-workspace      → /gsd-workspace --remove
  /gsd-sync-skills           → /gsd-update --sync
  /gsd-reapply-patches       → /gsd-update --reapply
  /gsd-scan                  → /gsd-map-codebase --fast
  /gsd-intel                 → /gsd-map-codebase --query
  /gsd-next                  → /gsd-progress --next
  /gsd-do                    → /gsd-progress --do
  /gsd-status                → /gsd-progress
  /gsd-join-discord          → /gsd-help

Skipped: CHANGELOG, RELEASE notes, superpowers/specs (historical)
Suite: 6971/6971 pass
2026-05-05 11:01:15 -04:00
Tom Boucher
851cddcc03 fix(#3131): re-wire 4 orphaned workflows as flags on parent commands
- discuss-phase --assumptions  → list-phase-assumptions.md
- pause-work --report          → session-report.md
- manager --analyze-deps       → analyze-dependencies.md
- import --from-gsd2           → gsd-tools.cjs from-gsd2 CLI

TDD: 8 new assertions in enh-2790-skill-consolidation.test.cjs
(argument-hint presence + body dispatch reference per flag).
Confirmed RED before wiring, GREEN after. Full suite 6971/6971.

help.md updated with all four new flag forms to satisfy
bug-2954-help-md-slash-command-stubs parity test.

Closes #3131
2026-05-05 10:51:10 -04:00
Tom Boucher
61773332d6 Merge pull request #3125 from gsd-build/fix/3098-phase-insert-and-init-phase-op-disagree-
fix: make phase insert placeholder/dry-run preconditions explicit
2026-05-04 23:54:44 -04:00
Tom Boucher
9987792c46 chore(changeset): correct issue reference for PR #3125 fragment 2026-05-04 23:49:00 -04:00
Tom Boucher
aa64638176 Merge pull request #3112 from gsd-build/fix/3101-plan-summary-matcher-in-core-cjs-reports
fix: canonicalize plan-summary matching for suffixless summaries
2026-05-04 23:35:34 -04:00
Tom Boucher
be4a9b3b43 Merge pull request #3114 from gsd-build/fix/3054-gsd-next-command-no-longer-available
fix: remove stale /gsd-next references from user-facing surfaces
2026-05-04 23:35:30 -04:00
Tom Boucher
e7ecd46bbe Merge pull request #3115 from gsd-build/fix/3053-sdk-ignores-multi-plan-phase-layout-plan
fix: count nested plans/ layout in phase status indexing
2026-05-04 23:35:26 -04:00
Tom Boucher
985b736d45 Merge pull request #3124 from gsd-build/fix/3050-update-backup-step-crashes-with-eacces-w
fix: make update custom-file backup resilient to EACCES
2026-05-04 23:35:21 -04:00
Tom Boucher
d3d995cfc4 test(3050): avoid includes-based source-grep assertion 2026-05-04 23:34:57 -04:00
Tom Boucher
43e5fef95e Merge pull request #3113 from gsd-build/fix/3083-resume-project-md-route-to-workflow-emit
fix: remove /clear then from resume route templates
2026-05-04 23:33:31 -04:00
Tom Boucher
083e813aea Merge pull request #3116 from gsd-build/fix/3055-bug-top-level-branching-strategy-in-plan
fix: normalize legacy top-level branching_strategy into git config
2026-05-04 23:33:28 -04:00
Tom Boucher
fe4db16769 Merge pull request #3118 from gsd-build/fix/3063-state-complete-phase-corrupts-state-md-b
fix: prevent state complete-phase from resolving literal 'Phase' token
2026-05-04 23:33:25 -04:00
Tom Boucher
399bb80b40 Merge pull request #3123 from gsd-build/fix/3091-npx-install-gsd-sdk-symlink-never-create
fix: align SDK install/fallback guidance with query-capable CLI
2026-05-04 23:33:22 -04:00
Tom Boucher
d978ad6b2f merge: sync main into PR #3114 and keep canonical next/profile commands 2026-05-04 23:32:42 -04:00
Tom Boucher
0fe88b9e7a chore(changeset): add release fragment for PR #3112 2026-05-04 23:32:15 -04:00
Tom Boucher
baf0d56063 chore(changeset): add release fragment for PR #3113 2026-05-04 23:32:14 -04:00
Tom Boucher
d2d1205691 chore(changeset): add release fragment for PR #3115 2026-05-04 23:32:12 -04:00
Tom Boucher
1c1e3b5de4 chore(changeset): add release fragment for PR #3116 2026-05-04 23:32:11 -04:00
Tom Boucher
a6d4e61606 chore(changeset): add release fragment for PR #3118 2026-05-04 23:32:09 -04:00
Tom Boucher
e2b12bfad2 chore(changeset): add release fragment for PR #3123 2026-05-04 23:32:07 -04:00
Tom Boucher
915e7daced chore(changeset): add release fragment for PR #3124 2026-05-04 23:32:06 -04:00
Tom Boucher
313f170cf0 chore(changeset): add release fragment for PR #3125 2026-05-04 23:32:04 -04:00
Tom Boucher
199083777a Merge pull request #3111 from gsd-build/fix/3094-progress-md-still-recommends-deleted-gsd
fix: remove stale /gsd-list-phase-assumptions guidance from progress routing
2026-05-04 23:31:26 -04:00
Tom Boucher
dbbc7f0942 Merge pull request #3117 from gsd-build/fix/3056-pruneorphanedworktrees-destroys-linked-w
fix: make orphaned worktree prune non-destructive by default
2026-05-04 23:31:13 -04:00
Tom Boucher
2113902daf Merge pull request #3119 from gsd-build/fix/3072-gsd-sdk-query-resolve-model-error-when-i
fix: guard optional sketch-findings probes from non-zero ls exits
2026-05-04 23:31:10 -04:00
Tom Boucher
f01f6b76dd Merge pull request #3122 from gsd-build/fix/3088-gsd-complete-milestone-leaves-state-md-n
fix: normalize stale STATE narrative tails on milestone completion
2026-05-04 23:31:06 -04:00
Tom Boucher
4ee6ce4a01 fix(3054): align docs anchors and structured stale-command checks 2026-05-04 23:30:35 -04:00
Tom Boucher
67684626d8 fix(3088): append missing STATE narrative sections on milestone close 2026-05-04 23:29:45 -04:00
Tom Boucher
b331c48261 test(3072): parse bash blocks for findings probe guard checks 2026-05-04 23:28:52 -04:00
Tom Boucher
3d2f2e85a0 test(3056): canonicalize worktree paths in prune assertions 2026-05-04 23:28:20 -04:00
Tom Boucher
5b63ba6ea9 test(3094): switch stale-progress assertion to structured token check 2026-05-04 23:27:38 -04:00
Tom Boucher
a4d16c3c93 Merge pull request #3109 from gsd-build/fix/3043-milestone-complete-version-scoping
fix: respect explicit milestone version in milestone complete
2026-05-04 23:27:16 -04:00
Tom Boucher
78846b1e6a Merge pull request #3108 from gsd-build/feat/deepen-query-failure-classification
refactor: deepen query architecture seams with compatibility shims
2026-05-04 23:24:03 -04:00
Tom Boucher
59fd17251a fix(phase): clarify insert preconditions and reject unsupported dry-run flag 2026-05-04 23:22:20 -04:00
Tom Boucher
efa642a078 fix(update): skip unreadable custom files during backup 2026-05-04 23:20:25 -04:00
Tom Boucher
120113c42b fix(sdk-guidance): point quick install hint and agent fallbacks to query-capable CLI 2026-05-04 23:18:41 -04:00
coderabbitai[bot]
2d25c97706 fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
2026-05-05 03:17:22 +00:00
Tom Boucher
2dcf374da0 fix(milestone): normalize STATE narrative after milestone completion 2026-05-04 23:17:00 -04:00
Tom Boucher
50f714cdd5 fix(workflows): make optional findings-skill probes non-fatal 2026-05-04 23:13:33 -04:00
Tom Boucher
471df09242 fix(state): harden complete-phase resolution and add explicit override 2026-05-04 23:10:26 -04:00
Tom Boucher
ecd5d11b32 fix(worktree): disable destructive orphaned-worktree removal by default 2026-05-04 23:08:13 -04:00
Tom Boucher
58062a64a0 fix(sdk-config): honor legacy top-level branching_strategy in init 2026-05-04 23:06:54 -04:00
Tom Boucher
65024683fd fix(init): count plans/ summaries from nested plans/ layout 2026-05-04 23:03:10 -04:00
Tom Boucher
72f4c3b362 fix(docs): replace stale /gsd-next references with /gsd-progress --next 2026-05-04 22:54:01 -04:00
Tom Boucher
538ef683be fix(resume): remove clear prefix from resume routing 2026-05-04 22:52:30 -04:00
Tom Boucher
c7886415c3 fix(phase): canonicalize plan-summary matching for suffixless summaries 2026-05-04 22:51:15 -04:00
Tom Boucher
a54dda3837 fix(progress): remove stale list-phase-assumptions routing 2026-05-04 22:47:16 -04:00
Tom Boucher
19e580137d fix: scope milestone complete stats to explicit version 2026-05-04 22:06:22 -04:00
Tom Boucher
78c794c016 test: remove dead registry wiring assertion 2026-05-04 21:49:41 -04:00
Tom Boucher
40acf1f02e fix: address CodeRabbit findings on query/transport error handling 2026-05-04 21:49:41 -04:00
Tom Boucher
1642f47908 test: align registry wiring assertions with declarative assembly 2026-05-04 21:49:41 -04:00
Tom Boucher
38718e9d4b fix: avoid unsafe Promise cast in execRaw 2026-05-04 21:49:40 -04:00
Tom Boucher
a441f96f37 chore: update changeset pr reference 2026-05-04 21:49:40 -04:00
Tom Boucher
0500bdf619 refactor: deepen query architecture seams with compatibility shims 2026-05-04 21:49:40 -04:00
Tom Boucher
c6a35d6398 refactor: deepen transport policy and output projection paths 2026-05-04 21:49:40 -04:00
Tom Boucher
969cfcf998 refactor: split native hotpath fallback and dispatch branches 2026-05-04 21:49:40 -04:00
Tom Boucher
e0c791a5d0 refactor: centralize native dispatch data projection 2026-05-04 21:49:40 -04:00
Tom Boucher
deb4477375 refactor: remove thin runtime and tools error wrappers 2026-05-04 21:49:40 -04:00
Tom Boucher
5aaf0dbea5 refactor: reduce query error factory public surface 2026-05-04 21:49:40 -04:00
Tom Boucher
ace241d0c2 refactor: fold query error seam types into factory module 2026-05-04 21:49:40 -04:00
Tom Boucher
0fffc7c055 refactor: centralize gsd-tools error wrapping path 2026-05-04 21:49:40 -04:00
Tom Boucher
6059a574f2 refactor: remove redundant native dispatch cast in runtime 2026-05-04 21:49:40 -04:00
Tom Boucher
b0e616288b refactor: isolate native dispatch error projection 2026-05-04 21:49:40 -04:00
Tom Boucher
ed9d67c91b refactor: deepen subprocess adapter with shared execution error path 2026-05-04 21:49:40 -04:00
Tom Boucher
97019d274e refactor: keep classification constructors internal to GSDToolsError 2026-05-04 21:49:40 -04:00
Tom Boucher
7311e0a9ab refactor: extract query error seam factory builders 2026-05-04 21:49:39 -04:00
Tom Boucher
c66ff96de8 test: use typed GSDToolsError constructors in cli output tests 2026-05-04 21:49:39 -04:00
Tom Boucher
a24de43f8b test: consolidate tools error mapping coverage in factory tests 2026-05-04 21:49:39 -04:00
Tom Boucher
70faa0ff0f refactor: remove query tools error mapper wrapper 2026-05-04 21:49:39 -04:00
Tom Boucher
b9e3979fc1 refactor: introduce explicit query error seam contracts 2026-05-04 21:49:39 -04:00
Tom Boucher
c7d3f83b8b refactor: reduce failure-classification API surface 2026-05-04 21:49:39 -04:00
Tom Boucher
bc289fad4a refactor: type native adapter error seam to GSDToolsError 2026-05-04 21:49:39 -04:00
Tom Boucher
9bee4dce4a test: adopt typed GSDToolsError constructors across failure tests 2026-05-04 21:49:39 -04:00
Tom Boucher
9a469fa05c refactor: centralize query tools error construction in factory 2026-05-04 21:49:39 -04:00
Tom Boucher
abf7779088 test: cover typed timeout mapping in query dispatch 2026-05-04 21:49:39 -04:00
Tom Boucher
16bf552037 test: lock typed timeout no-fallback transport behavior 2026-05-04 21:49:39 -04:00
Tom Boucher
009cfb1562 refactor: split native adapter timeout and failure seams 2026-05-04 21:49:39 -04:00
Tom Boucher
6fe4af2546 refactor: split subprocess timeout and failure error seams 2026-05-04 21:49:39 -04:00
Tom Boucher
41683b2f53 refactor: centralize typed GSDToolsError construction 2026-05-04 21:49:38 -04:00
Tom Boucher
7dcafbc211 refactor: consolidate failure classification constructors 2026-05-04 21:49:38 -04:00
Tom Boucher
ccda572ade refactor: default typed failure classification across query errors 2026-05-04 21:49:38 -04:00
Tom Boucher
1ca7f58831 test: cover tools error mapping and unify timeout fallback check 2026-05-04 21:49:38 -04:00
Tom Boucher
7298a76b20 refactor: centralize dispatch error projection from failure signals 2026-05-04 21:49:38 -04:00
Tom Boucher
5cfd874058 refactor: add typed query failure signals 2026-05-04 21:49:38 -04:00
Tom Boucher
ba6100c548 refactor: deepen query failure classification module 2026-05-04 21:49:38 -04:00
Tom Boucher
9f5b011b35 refactor: use internal gsdtools error type import 2026-05-04 21:49:38 -04:00
Tom Boucher
1037b82a98 test: address remaining coderabbit findings and notes 2026-05-04 21:49:38 -04:00
Tom Boucher
ac883f8150 fix: address coderabbit query seam findings 2026-05-04 21:49:38 -04:00
Tom Boucher
3e22c70fac docs: fix changeset summary text 2026-05-04 21:49:38 -04:00
Tom Boucher
12fc34689e docs: add changeset for query seam deepening 2026-05-04 21:49:37 -04:00
Tom Boucher
9d096b9925 refactor: deepen gsdtools query execution seams 2026-05-04 21:49:37 -04:00
Tom Boucher
42ed7cee8d refactor: deepen GSDTools query execution seams (#3085)
* refactor: deepen gsdtools query execution seams

* docs: add changeset for query seam deepening

* docs: fix changeset summary text

* fix: address coderabbit query seam findings

* test: address remaining coderabbit findings and notes

* refactor: use internal gsdtools error type import
2026-05-03 18:56:41 -04:00
Tom Boucher
5e21bf7567 Deepen query dispatch seam with Command Topology Module (#3078)
* Deepen query dispatch seam with command topology module

* Stabilize SDK parity defaults and integration test gating

* docs(architecture): record pre-project config policy and e2e gate

* refactor(query): stop injecting native adapter in CLI dispatch path

* fix(config): align workflow auto-chain typing and docs
2026-05-03 18:11:38 -04:00
Tom Boucher
9c92c32f6e refactor(query): deepen runtime context/native adapter/output seams (#3076)
* refactor(query): deepen runtime context, native adapter, and cli output seams

* chore(changeset): add fragment for query seam deepening continuation

* refactor(query): converge internal command-resolution imports on canonical seam

* refactor(query): remove dead seam wrappers and converge on canonical modules

* docs(architecture): update context and adr for query seam completion

* fix(query): preserve gsd-tools stderr in cli output and clarify static ws test scope

* test(query): cover whitespace stderr and null exitCode fallback
2026-05-03 16:31:48 -04:00
Tom Boucher
5c9f34bd31 refactor(cli): extract Query CLI Adapter Module seam (#3074)
* refactor(cli): extract query adapter seam from cli entrypoint

* test: update ws forwarding guard for query-cli-adapter seam

* fix(query): close remaining CodeRabbit findings on cli adapter

* test: address remaining CodeRabbit nitpicks on ws forwarding coverage
2026-05-03 15:57:01 -04:00
Tom Boucher
b6c401dc90 refactor(query): deepen command/dispatch seams and resolve coderabbit findings
* refactor(query): deepen command definition seam and fold fallback mapping cleanup

* refactor(query): add shared dispatch formatting module seam

* fix(query): restore QueryResult type import in dispatch deps

* test/query: align raw-output policy and definition normalization contracts

* refactor(query): deepen diagnosis, invariant report, and error taxonomy seams

* refactor(query): deepen dispatch plan, fallback bridge, policy snapshot, and hints seams

* refactor(query): deepen validation, fallback policy, capability, and result builder seams

* refactor(query): deepen resolution strategy, output classifier, observability, and policy-capability seams

* refactor(query): finalize deep strategy/classifier/observability/capability seams

* test/query: address coderabbit inline and out-of-diff dispatch nits

* fix(query): address remaining coderabbit input-validation and bridge stderr threads

* fix(query): address remaining coderabbit dispatch and strategy/output nits
2026-05-03 15:29:34 -04:00
Tom Boucher
c3f896f311 docs(contributing): codify CONTEXT + ADR contribution and testing standards 2026-05-03 14:54:14 -04:00
Tom Boucher
f104dab332 refactor(query): deepen dispatch policy seam with structured result contract (#3066)
* refactor(query): deepen dispatch policy seam with structured result contract

Closes #3065.

- unify query dispatch outcome as typed success/failure union
- include error kind/details + final exit_code in failure path
- align native and fallback paths under one dispatch policy seam
- make CLI query path consume seam result (thin adapter)
- add ADR + context term for Dispatch Policy Module

* refactor(query): strengthen dispatch seam with shared error mapper and typed details

- add query-dispatch-error-mapper module shared by native/fallback paths
- remove ad-hoc inline mapping in dispatch/fallback executors
- lock error-details schema in mapper + dispatch tests
- document structured dispatch contract in QUERY-HANDLERS.md

* fix(query): return structured fallback failure when path resolution throws

- guard resolveGsdToolsPath in cjs dispatch path
- map thrown resolution errors to fallback_failure result
- add regression test for structured failure contract
2026-05-03 14:30:27 -04:00
Tom Boucher
5975f06b6a refactor(query): extract command catalog seam for registry wiring (#3060)
* refactor(sdk): extract gsdtools transport seam with per-command policy

* refactor(query): centralize registry command catalog wiring

* refactor(query): unify command resolution seam across sdk callers

* fix(sdk): address CodeRabbit transport policy and timeout findings

* refactor(query): extract mutation event mapper seam

* refactor(query): converge mutation and transport policy data

* refactor(query): share fallback orchestration across cli and sdk

* refactor(query): split static registry catalog by domain clusters

* refactor(query): extract mutation event emission decorator seam

* refactor(query): extract alias-family handler catalog module

* refactor(query): extract cjs fallback execution adapter

* refactor(query): deepen command semantics seam

* refactor(query): extract deep dispatch seam

* refactor(query): deepen cjs fallback execution seam

* refactor(query): merge routing plan into dispatch seam

* fix(query): address CodeRabbit review findings on PR #3060

Critical: prevent double-execution race by checking timeout errors
before subprocess fallback (gsd-transport.ts).

Major: fix execRaw() to respect transport policy outputMode instead
of hardcoding 'raw' (gsd-tools.ts).

Major: add explicit 30s timeout to subprocess fallback execution
(query-fallback-executor.ts).

Major: remove raw args from stderr banner to prevent secret leakage
(query-fallback-executor.ts).

Minor: ensure native text output has trailing newline for CLI parity
(query-dispatch.ts).

Update gsd-tools.test.ts to match new execRaw() behavior.

* fix(tests): update CLI integration tests for catalog-based registration

The refactoring moved handler registration from inline registry.register()
calls to catalog-based registration (registerStaticCatalog/registerAliasCatalog).

- gsd-sdk-query-registry-integration.test.cjs: collectRegisteredNames() now
  also scans catalog files for handler names registered via the new system.
- bug-2492-context-coverage-gate.test.cjs: checks for catalog-based
  registration (DECISION_ROUTING_STATIC_CATALOG) instead of inline strings.
- bug-2524-sdk-query-ws-flag.test.cjs: checks for dispatchNative callback
  pattern instead of direct registry.dispatch() call.

* fix(query): address remaining CodeRabbit review findings

- query-command-semantics.ts: guard stats/progress rewrite so option
  tokens (e.g. --pick) are not turned into subcommands, preserving the
  top-level handler dispatch.

- query-dispatch.ts: formatOutput now skips --pick for text-format
  responses (matching CJS fallback behavior) and surfaces a proper error
  when extractField returns undefined instead of silently producing
  'undefined'.

- query-dispatch.ts: fix backwards error message — 'registered' is the
  restrictive policy that disables fallback, not enables it.

- tests/bug-2492-context-coverage-gate.test.cjs: check
  VERIFY_DECISION_STATIC_CATALOG (the correct catalog for plan-gate
  handlers) instead of DECISION_ROUTING_STATIC_CATALOG.

- tests/gsd-sdk-query-registry-integration.test.cjs: resolve catalog
  variable before loading entries so the drift guard checks each
  referenced catalog individually.

* refactor(query): deepen registry assembly module with strict invariants

- extract registry assembly into dedicated module
- split build vs mutation decoration internals
- add strict assembly invariants:
  1) no duplicate keys
  2) alias canonicals must have handlers
  3) mutation commands must be registered
  4) raw-output policy commands must be registered
- slim query index to thin re-export seam
- add focused registry assembly tests
- update drift-guard tests to target new seam

* test(query): add thin-seam coverage for query index re-exports

* fix(query): return structured native dispatch errors + tighten decisions.parse guard

- runQueryDispatch native path now catches adapter errors and returns
  QueryDispatchResult.error instead of throwing.
- preserve legacy CLI exit contract by using code=1 for native dispatch
  failures.
- strengthen bug-2492 guard: decisions.parse assertion now checks
  VERIFY_DECISION_STATIC_CATALOG OR explicit command token.
2026-05-03 13:57:32 -04:00
Tom Boucher
0f98952a3d refactor(sdk): extract GSDTools transport seam + policy (#3058)
* refactor(sdk): extract gsdtools transport seam with per-command policy

* fix(sdk): address CodeRabbit transport policy and timeout findings

* fix(sdk): harden raw transport formatting and raw-path coverage
2026-05-03 08:20:05 -04:00
Tom Boucher
eb365f7336 docs: audit and update docs/ for v1.40.0 release (#3048)
* docs(en): update FEATURES/USER-GUIDE/COMMANDS for v1.40.0 surface

- FEATURES.md: append v1.40.0 section (#122 skill consolidation, #123
  namespace meta-skills, #124 context-window guard, #125 phase-lifecycle
  status-line read-side); add to TOC.
- USER-GUIDE.md: add slash-command form (hyphen vs colon) primer and
  namespace routing primer; replace deleted slash forms in walkthroughs
  (`/gsd-add-backlog`, `/gsd-plant-seed`, `/gsd-add-phase`,
  `/gsd-set-profile`, `/gsd-list-workspaces`, etc.) with consolidated
  forms (`/gsd-capture --backlog`, `/gsd-phase --insert`,
  `/gsd-config --profile`, `/gsd-workspace --list`, etc.); fix
  `/gsd-spike-wrap-up` and `/gsd-sketch-wrap-up` to flag form.
- COMMANDS.md: clarify Command Syntax (Gemini = colon form, others =
  hyphen form); add Namespace Meta-Skills section with all six routers;
  add `--context` to /gsd-health flag table.

Refs #3047

* docs(en): refresh INVENTORY/CLI-TOOLS/STATE-MD-LIFECYCLE for v1.40.0

- INVENTORY.md: workflow-row "Invoked by" column updated to point at
  consolidated commands (`/gsd-phase` family, `/gsd-workspace --list`,
  `/gsd-config --advanced/--integrations/--profile`,
  `/gsd-sketch --wrap-up`, `/gsd-spike --wrap-up`); CLI-modules row for
  `secrets.cjs` updated to `/gsd-config --integrations`. Command count
  and namespace meta-skills section already reflect 65 shipped (= 59
  consolidated sub-skills + 6 ns-* routers).
- CLI-TOOLS.md: add `validate context` row under Validation Commands
  with the 60 %/70 % threshold envelope used by `/gsd-health --context`.
- STATE-MD-LIFECYCLE.md: flip status header from "proposed" to
  "shipped in v1.40.0" since `parseStateMd()` and `formatGsdState()`
  now read and render `active_phase`, `next_action`, `next_phases`,
  and `progress`.

`docs/AGENTS.md` audited and verified clean — `gsd-code-fixer` row
already lists the correct `/gsd-code-review --fix` spawner; no
deleted-skill references found. `docs/INVENTORY-MANIFEST.json`
audited and verified clean — already enumerates the 65 commands
(including six ns-* routers) and contains no deleted slash forms.

Refs #3047

* docs(en): cleanup ARCHITECTURE/CONFIGURATION for v1.40.0

- ARCHITECTURE.md: split Commands install-target list to call out the
  Gemini colon form (`/gsd:command-name`) vs hyphen form for every
  other runtime. Add a new subsection covering two-stage hierarchical
  routing via the six namespace meta-skills (#2792) and a paired note
  on the MCP token-budget interaction so readers see the two big
  per-turn cost levers in one place.
- CONFIGURATION.md: rewrite three references to the deleted
  `/gsd-settings-advanced` and `/gsd-settings-integrations` slash
  forms to use the consolidated `/gsd-config --advanced` /
  `/gsd-config --integrations` invocations. Add a new "STATE.md
  Frontmatter (Phase Lifecycle)" section documenting the four
  optional fields (`active_phase`, `next_action`, `next_phases`,
  `progress`) read by the v1.40 status-line, with a pointer to
  STATE-MD-LIFECYCLE.md for the full reference.

`docs/manual-update.md` audited and verified clean — already documents
`/gsd-update --reapply` (the consolidated form), no reference to the
deleted `/gsd-reapply-patches`.

Refs #3047

* docs(i18n): mirror v1.40.0 slash-command rename into ja-JP/ko-KR/zh-CN/pt-BR

Mechanical token-level renames only — every reference to a deleted
micro-skill slash form is rewritten to the consolidated form on the
matching parent skill. No prose was machine-translated; new prose
sections (slash-form primer, namespace routing primer, v1.40 feature
entries, STATE.md frontmatter) were left for human translator
follow-up.

Renames applied uniformly across all four trees:
  /gsd-add-todo, /gsd-add-note, /gsd-add-backlog,
  /gsd-plant-seed, /gsd-check-todos      → /gsd-capture[ --note|
                                            --backlog|--seed|--list]
  /gsd-add-phase, /gsd-insert-phase,
  /gsd-remove-phase, /gsd-edit-phase     → /gsd-phase[ --insert|
                                            --remove|--edit]
  /gsd-new-workspace, /gsd-list-workspaces,
  /gsd-remove-workspace                  → /gsd-workspace[ --new|
                                            --list|--remove]
  /gsd-settings-advanced,
  /gsd-settings-integrations,
  /gsd-set-profile                       → /gsd-config[ --advanced|
                                            --integrations|--profile]
  /gsd-sketch-wrap-up                    → /gsd-sketch --wrap-up
  /gsd-spike-wrap-up                     → /gsd-spike --wrap-up
  /gsd-reapply-patches                   → /gsd-update --reapply
  /gsd-code-review-fix                   → /gsd-code-review --fix
  /gsd-plan-milestone-gaps               → /gsd-audit-milestone

Refs #3047

* docs(changelog): regroup [Unreleased] under Feature/Enhancement/Fix

Replace the existing Keep-a-Changelog \`Added\` / \`Changed\` /
\`Performance\` / \`Removed\` / \`Fixed\` sub-headers in the [Unreleased]
block with the issue/PR template taxonomy:

  Added                 → Feature
  Changed / Performance → Enhancement
  Removed               → Enhancement
  Fixed                 → Fix

Order within the release: Feature → Enhancement → Fix. Every bullet
preserved verbatim — only headers and grouping changed; the awkward
inline-versioned headers (\`### Added — 1.40.0-rc.1\`,
\`### Changed — 1.40.0-rc.1\`, \`### Fixed — 1.40.0-rc.1\`) folded into
the same buckets with the \`— 1.40.0-rc.1\` suffix dropped, since the
[Unreleased] block IS 1.40.0-rc.1.

The [1.39.2] hotfix block called out in #3047's spec does not yet
exist in CHANGELOG.md (the previously released hotfix is [1.39.1]),
so this commit only regroups [Unreleased]. Older release blocks
([1.39.1] and earlier) are frozen and untouched.

Refs #3047

* docs(changeset): add fragment for v1.40.0 doc audit

Refs #3047

* docs(en): strip leading / from deleted slash-command tokens in FEATURES

REQ-CONSOLIDATE-03 and REQ-CONSOLIDATE-04 listed deleted commands by
their `/gsd-foo` form for the historical record. The docs-parity tests
in bug-3010, bug-3029-3034, and bug-3042-3044 use the regex
`/\/gsd-[a-z0-9][a-z0-9-]*/g` to scan user-facing surfaces for any
remaining mention of removed slash forms — they cannot tell prose
about a deleted command from a live recommendation.

Strip the leading slash from the bare-name references (preserve the
historical text otherwise). Tests now require a `/` prefix to match,
so `gsd-add-todo` reads identically to a human but no longer trips
the parser.

Verified locally: 65/65 tests pass across the three docs-parity
suites that were red on CI run 25270072600.

Refs #3047

* docs(en): fix CR feedback + drop literal /gsd:plan-phase from USER-GUIDE

CI: tests/bug-2543-gsd-slash-namespace.test.cjs flagged
docs/USER-GUIDE.md:35 for embedding the literal `/gsd:plan-phase`
token in the parenthetical Gemini-form example. The test scans every
.md under docs/ for `/gsd:<live-cmd>` because non-Gemini surfaces must
not advertise the colon form. Replaced the literal example with a
prose substitution rule.

CR: docs/ARCHITECTURE.md:125 — the namespace meta-skills were listed
by file-prefix (`gsd-ns-workflow`) but the invocable frontmatter `name:`
is the bare form (`gsd-workflow`). Verified against the six
`commands/gsd/ns-*.md` files. Replaced with the canonical names and
noted the file/name disagreement in-line.

CR: docs/COMMANDS.md:723 — `v1.40` aligned to canonical `v1.40.0`.

CR: docs/FEATURES.md:2679 — REQ-CTX-GUARD-02 advertised the wrong
invocation (`gsd-tools validate context`). The shipped handler is
exposed via `gsd-sdk query validate.context` and requires explicit
`--tokens-used <int>` + `--context-window <int>` flags (verified
against sdk/src/query/validate.ts:849-882 and
get-shit-done/bin/lib/validate-command-router.cjs:19-36).

CR: docs/zh-CN/README.md:533 — added `inherit` to the profile-options
parenthetical to match the canonical set (verified against
model-profiles.cjs:29 `VALID_PROFILES = […MODEL_PROFILES['gsd-planner'], 'inherit']`).

Verified locally: 74/74 tests pass across the four docs-parity suites
that were red on CI runs 25270072600 and 25270182903.

Refs #3047
2026-05-03 07:33:27 -04:00
Tom Boucher
1e6737cd8e feat(plan-phase): --research-phase flag + scrub stale slash-command refs (#3042, #3044) (#3045)
* feat(plan-phase): --research-phase flag absorbs deleted /gsd-research-phase + scrub stale refs (#3042, #3044)

#3042 (orphaned research-phase): /gsd-research-phase had a workflow file
but no slash-command stub. Rather than restore the orphan, the research-
only capability is now a flag on /gsd-plan-phase:

  /gsd-plan-phase --research-phase <N>

When set, the workflow scopes to phase N, runs the research step (Section
5 of the existing plan-phase workflow), then early-exits before the
planner/plan-checker/verifier chain.

Per RCA against the deleted standalone, the flag adds two modifiers to
fully cover the original surface (Option B from the RCA discussion):

- --view : print existing RESEARCH.md to stdout, no spawn. Cheapest mode
  for the correction-without-replanning loop the issue reporter
  explicitly called out. Errors with a clear hint if RESEARCH.md is
  missing.
- --research : reuse the existing "force re-research" semantics. In
  research-only mode this skips the existing-RESEARCH.md prompt and
  re-spawns unconditionally.
- Neither flag, RESEARCH.md exists : prompt update/view/skip. Mirrors
  the deleted standalone's existing-artifact menu (#3042 RCA).

#3044 (stale slash-command refs): scrubbed five deleted commands from
all user-facing surfaces, including English docs, 4 localized doc sets
(ja-JP, ko-KR, zh-CN, pt-BR), workflows, templates, and references.

  /gsd-check-todos          → /gsd-capture --list
  /gsd-new-workspace        → /gsd-workspace --new
  /gsd-status               → /gsd-progress
  /gsd-plan-milestone-gaps  → table rows / orphan sections removed
                              (PR #3038 only scrubbed workflows/agent;
                              missed the docs surfaces this PR covers)
  /gsd-research-phase       → /gsd-plan-phase --research-phase

Includes a fix to docs/issue-driven-orchestration.md (PR #3036)
which itself referenced /gsd-new-workspace 4 times — self-correction.

Removed:
- get-shit-done/workflows/research-phase.md (orphan, capability
  absorbed into --research-phase flag)

Tests:
- tests/bug-3042-3044-research-flag-and-stale-refs.test.cjs — 46
  structural-IR tests across both bugs:
  - argument-hint advertises --research-phase + --view
  - workflow parses --research-phase, sets RESEARCH_ONLY,
    early-exits before planner
  - --view prints RESEARCH.md without spawning
  - --research forces refresh in research-only mode
  - existing-RESEARCH.md prompt path with update/view/skip
  - workflows/research-phase.md is removed
  - 5 deleted slash-commands absent from 17 English user-facing
    surfaces + 16 localized doc surfaces (4 locales × 4 docs each)
  - replacement command tokens present where deleted ones lived

6950/6950 full suite pass. Lints clean.

Closes #3042
Closes #3044

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address all 8 CR findings on PR #3045

Major (3):
- get-shit-done/workflows/plan-phase.md:344 — added explicit early-exit
  guard at Section 5.1: "Skip if RESEARCH_ONLY=true". Without it, an LLM
  could fall through "use existing, skip to step 6" → planner spawn,
  violating the research-only contract. The guard makes the early-exit
  unreachable from any non-research-only branch.
- get-shit-done/references/continuation-format.md (3 examples) +
  zh-CN/.../continuation-format.md (3 examples) — pointed to
  `/gsd-plan-phase --research-phase` but docs/COMMANDS.md didn't
  document the flag. Added a full --research-phase + --view + --research
  modifier section to the /gsd-plan-phase flag table in COMMANDS.md so
  the canonical reference matches the continuation examples.

Minor (5):
- docs/FEATURES.md:1632 — `/gsd-plan-phase --research-phase` →
  `/gsd-plan-phase --research-phase <N>` (include required arg).
- get-shit-done/templates/README.md:46 — NN-VALIDATION.md producer
  reverted from `/gsd-plan-phase --research-phase` (Nyquist) to plain
  `/gsd-plan-phase` (Nyquist). VALIDATION.md is created during normal
  Nyquist flow, not research-only mode — the bulk replacement was
  wrong for that line.
- get-shit-done/workflows/help.md:89 — signature line was missing
  `--research`; added it alongside `--research-phase` and `--view`.
- tests/bug-3042-3044-...:197 — promptHasView/promptHasSkip were
  tautological (matched anywhere in 1700-line workflow). Tightened
  to a proximity check anchored on "RESEARCH.md already exists" prompt
  header within a 600-char window. Updated workflow to emit that
  literal phrase.
- tests/feat-2840-...:95 — workspace assertion used `/gsd-workspace`
  but the documented replacement is `/gsd-workspace --new`. Tightened
  to require both tokens (in 3 places: requiredCommands list, regex
  in conceptPairs, error message).

6950/6950 full suite pass. Lint clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 23:12:50 -04:00
Tom Boucher
dca12242b5 fix(install): skip Gemini local commands/gsd when global GSD present (#3037) (#3041)
* fix(install): skip Gemini local commands/gsd when global GSD present (#3037)

Reporter showed that running `npx get-shit-done-cc --gemini --global`
followed by `--gemini --local` in a project creates the same 65 GSD
command files in both Gemini scopes:
  - ~/.gemini/commands/gsd/ (user scope)
  - <project>/.gemini/commands/gsd/ (workspace scope)

Gemini conflict-detects by command name across scopes and renames every
overlapping /gsd:* command to /workspace.gsd:* and /user.gsd:*, breaking
the documented /gsd:* namespace.

Fix: in bin/install.js, when handling --gemini --local, detect whether
~/.gemini/commands/gsd/ already exists with managed-shape content. If
so, skip the local copy and print a clear three-line warning explaining
the conflict avoidance. The user-scope install already provides the
same /gsd:* commands in this project; the local copy adds zero value.

Sibling fixes (test isolation):
- tests/install-minimal-all-runtimes.test.cjs: pass HOME/USERPROFILE
  through the spawned installer's env so the developer's real
  ~/.gemini/commands/gsd/ doesn't trigger the new skip path during
  test runs that want to assert the local-install populates
  commands/gsd/.
- tests/gemini-namespacing.test.cjs: the "Gemini Install (Behavioral)"
  describe block now creates an isolated tmpHome and points
  process.env.HOME at it before calling install(false, 'gemini'),
  with proper restore in afterEach.

Test:
- tests/bug-3037-gemini-duplicate-commands.test.cjs — 4 structural
  tests:
    1. global install populates HOME/.gemini/commands/gsd
    2. local install AFTER global skips the local copy
    3. local install with NO existing global still populates locally
       (no-regression)
    4. local install when HOME has .gemini/ but no GSD-managed
       commands/gsd/ still populates locally (non-GSD-Gemini-user
       no-regression)

6909/6909 full suite pass. Lints clean.

Closes #3037

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address CR feedback on PR #3041 — narrower detection + USERPROFILE restore

CR findings:

1. **bin/install.js (Major)** — userScopeHasGsd used
   `fs.readdirSync(homeGeminiGsd).length > 0` which would skip the
   local install for any non-empty directory, including a user who
   hand-dropped a single override at ~/.gemini/commands/gsd/<thing>
   .toml without ever running --gemini --global. Narrowed the
   detection to require at least 3 canonical GSD command files
   (help.toml, progress.toml, new-project.toml) — a marker that
   ships in every GSD Gemini install (minimal mode included) and is
   structurally impossible to produce by accident.

2. **tests/bug-3037-...:59 (Minor)** — beforeEach overwrites
   process.env.USERPROFILE but afterEach only restores HOME, leaking
   the temp home into later tests on Windows or any code path that
   reads USERPROFILE. Added save/restore symmetric with HOME.

Plus added a 5th regression test covering the narrowed detection:
"local install when HOME has hand-dropped overrides UNDER commands/gsd/
(but no full GSD) still populates locally" — directly exercises the
edge case CR identified.

5/5 targeted tests pass. 6910/6910 full suite pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 17:44:52 -04:00
Tom Boucher
7714b5244b fix(workflows,docs): scrub stale /gsd-code-review-fix and /gsd-plan-milestone-gaps refs (#3029, #3034) (#3038)
* fix(workflows,docs): scrub stale /gsd-code-review-fix and /gsd-plan-milestone-gaps refs (#3029, #3034)

#2790 consolidated /gsd-code-review-fix into /gsd-code-review --fix and
deleted /gsd-plan-milestone-gaps in favor of inline gap planning as part
of /gsd-audit-milestone's output. The deletion was propagated through
some surfaces (#2950 covered help/do/settings/discuss-phase/etc.) but
several user-facing surfaces still emitted the old forms:

#3029 — /gsd-code-review-fix references in:
- agents/gsd-code-fixer.md (description, "Spawned by", recovery prose)
- get-shit-done/workflows/code-review.md (offer text)
- get-shit-done/workflows/execute-phase.md (offer text)
- get-shit-done/workflows/code-review-fix.md (internal retry hints)
- docs/INVENTORY.md (agent + workflow rows)
- docs/CONFIGURATION.md (workflow.code_review row)
- docs/USER-GUIDE.md (3 occurrences in walkthrough)
- docs/AGENTS.md (gsd-code-fixer agent stub)
- docs/FEATURES.md (commands list + REQ-REVIEW-04)

All replaced with /gsd-code-review --fix. Internal retry hints in the
workflow file itself updated to point at the new form. Release notes
(docs/RELEASE-*.md) and gsd-ns-review's "absorbed by" deletion note
left unchanged — historical/explanatory content.

#3034 — /gsd-plan-milestone-gaps references in:
- get-shit-done/workflows/audit-milestone.md (<offer_next> blocks for
  gaps_found and tech_debt: lines 281, 323)
- commands/gsd/complete-milestone.md (gaps_found pre-flight: lines 46, 57)

Replaced with inline closure path:
  /gsd-phase --insert <N> "Close gap: <REQ-ID> ..."
  /gsd-discuss-phase <N>
  /gsd-plan-phase <N>
  /gsd-execute-phase <N>

Plus a Nyquist-coverage hint pointing at /gsd-validate-phase /
/gsd-secure-phase for retroactive audit-chain hygiene gaps. The
gsd-ns-project SKILL.md "deleted by #2790" note is preserved
(it's the canonical pointer for future readers asking what
happened to the command).

Tests:
- tests/bug-3029-3034-stale-command-routes.test.cjs — parser-based
  assertions per fixed surface, plus a structural cross-check that
  gsd-ns-project keeps the deletion note. 15 tests, all green.
- 6905/6905 full suite passes.

Closes #3029
Closes #3034

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address CR feedback on PR #3038 — argument order, structural tests, agent count

CR findings on PR #3038:

1. **docs/USER-GUIDE.md (Major)** — `--fix` examples used flag-first form
   (`/gsd-code-review --fix 3`), but the supported CLI grammar is
   phase-first (`/gsd-code-review 3 --fix`). The original sed-based
   replacement preserved the position of the `gsd-code-review-fix`
   token, producing the wrong order. Fixed in USER-GUIDE.md (3
   occurrences) and the same drift in the workflow surfaces:
   - get-shit-done/workflows/code-review-fix.md (2 retry hints)
   - get-shit-done/workflows/code-review.md (offer text)
   - get-shit-done/workflows/execute-phase.md (offer text)

2. **docs/AGENTS.md (Minor)** — internal count drift: line 483 said
   "Ten additional agents" but line 725 said "12 advanced/specialized".
   Filesystem reality: 33 agents total, 21 primary, 12 specialized
   (count of `### ` stubs in the Advanced and Specialized section).
   Updated lines 3, 13, 483 to use 12/33 and added the two missing
   names (doc-classifier, doc-synthesizer) to the inline list at
   line 13.

3. **tests:94 (Major refactor suggestion)** — `.includes()` token checks
   were source-grep style. Refactored to a typed-IR pattern: extract
   the SET of slash-command tokens via regex, assert membership on the
   parsed Set instead of substring scanning the raw file text. Added
   the `allow-test-rule` comment explaining the IR-build vs
   IR-assertion split per scripts/lint-no-source-grep.cjs convention.

4. **tests:130 (Major)** — replacement-path assertion was file-wide and
   could false-pass on generic mentions of "inline" elsewhere in the
   file. Refactored: `extractOfferBlocks(content)` returns the typed
   list of `<offer_next>` and "Pre-flight" blocks where the deleted
   command previously lived, and the assertion runs against those
   blocks specifically. Now requires `/gsd-phase --insert` or
   inline-audit prose to appear in the same offer block, not just
   somewhere in the file.

15/15 targeted tests pass. 6905/6905 full suite pass. Lints clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 17:23:44 -04:00
Tom Boucher
117b3ec009 docs: add issue-driven orchestration guide (#2840) (#3036)
* docs: add issue-driven orchestration guide (#2840)

Adds docs/issue-driven-orchestration.md — a recipe for driving GSD from a
GitHub / Linear / Jira issue using existing primitives. Maps Symphony-style
orchestration concepts onto GSD commands without vendoring code, adding a
daemon, or introducing tracker integration.

Concept mapping covers:
- WORKFLOW.md → ROADMAP.md / STATE.md / phase CONTEXT.md / phase PLAN.md
- isolated agent workspace → /gsd-new-workspace --strategy worktree
- agent dispatch → /gsd-manager (interactive), /gsd-autonomous (unattended)
- per-phase steps → /gsd-discuss-phase → /gsd-plan-phase → /gsd-execute-phase
- proof-of-work → /gsd-verify-work (UAT.md persists across /clear)
- adversarial review → /gsd-review (cross-AI peer review)
- human merge gate → /gsd-ship
- follow-up capture → /gsd-note, /gsd-plant-seed, /gsd-new-milestone

End-to-end flow walks through 7 numbered steps from picking the tracker
issue to capturing follow-ups. Safety boundaries (isolated worktrees,
explicit human review, no automatic public posting, verification before
ship) and non-goals (no vendoring, no daemon, no mandatory tracker, no
gate bypass, no command-surface expansion) are spelled out explicitly so
the doc cannot drift into "let's just add one more flag".

Cross-linked from docs/README.md (Documentation Index) and
docs/USER-GUIDE.md (Table of Contents preamble).

Tests: tests/feat-2840-issue-driven-orchestration-guide.test.cjs — 9
structural-IR tests parse the guide into a typed record and assert on
flags (commandsPresent, conceptPairs, nonGoalFlags, safetyFlags,
numberedSteps). Fence-language MD040 check enforced. Cross-link
presence enforced. No raw-text assertions on prose.

6890/6890 tests pass. Lint:tests clean (allow-test-rule comment justifies
the doc-shape parser per scripts/lint-no-source-grep.cjs escape hatch).
Lint:changeset clean.

Closes #2840

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): guard USER-GUIDE.md existsSync before read (CR #3036)

CR Minor: cross-linked-from-USER-GUIDE.md test called fs.readFileSync
directly without first asserting fs.existsSync, asymmetric with the
README.md test above. A missing USER-GUIDE.md would throw ENOENT instead
of producing a meaningful assertion message. Mirror the null-guard
pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 16:57:42 -04:00
Tom Boucher
95d2bc20f8 feat(hooks): opt-in SessionStart update banner for non-statusline users (#2795) (#3035)
* feat(hooks): opt-in SessionStart update banner for non-statusline users (#2795)

When a user declines (or keeps a non-GSD) statusline at install time, the
installer now offers an opt-in SessionStart banner that surfaces GSD update
availability. The banner reads the existing
~/.cache/gsd/gsd-update-check.json cache (written by
gsd-check-update-worker.js) and emits a single systemMessage line only when
update_available is true:

    GSD update available: <installed> → <latest>. Run /gsd-update.

It is silent when up-to-date and rate-limits "check failed" diagnostics to
once per 24h via a sentinel file so a corrupt cache doesn't nag every
session. Removed cleanly by `npx get-shit-done-cc --uninstall` which strips
both the script and the SessionStart entry. The banner is never offered when
GSD's statusline is being installed (statusline already surfaces update
info, so re-prompting would be noise).

Implementation:
- hooks/gsd-update-banner.js — pure functions buildBannerOutput,
  shouldSuppressFailureWarning, readCache; thin main() wires them.
- bin/install.js — handleUpdateBanner() prompt, parseUpdateBannerInput(),
  buildUpdateBannerHookEntry(), buildUpdateBannerPromptText(); chained into
  installAllRuntimes() so finalize() receives both flags. updateBannerCommand
  computed alongside the other JS-hook commands; finishInstall() registers
  the SessionStart entry only when shouldInstallBanner === true and the
  hook file is present at the target.
- Hook ships in scripts/build-hooks.js HOOKS_TO_COPY, listed in
  MANAGED_HOOKS for stale-detection in gsd-check-update-worker.js, in the
  uninstall hook-removal lists in install.js, and in the
  rewriteLegacyManagedNodeHookCommands allowlist.

Tests:
- tests/feat-2795-update-banner.test.cjs — 22 tests, structural-IR
  assertions on parsed JSON envelopes (no raw-text matching). Covers
  pure-function branches (cache present/absent, parseError, rate-limit
  suppression, missing version fields), end-to-end hook invocation against
  fixture cache states, and install.js wiring (prompt text, input parsing,
  hook entry shape).
- tests/trae-install.test.cjs — updated install() return-shape assertion to
  include updateBannerCommand: null for the no-settings runtime.
- 6881/6881 tests pass.

Docs (bundled in same commit per the bundle-docs-with-code skill):
- docs/USER-GUIDE.md — new "Surface GSD Update Notifications Without GSD's
  Statusline" task section with opt-in/opt-out instructions.
- docs/FEATURES.md — REQ-HOOK-08 added; "Update Banner" subsection under
  the Hook System feature with cache flow + removal path.
- docs/INVENTORY.md — hook count 11 → 12, new row for gsd-update-banner.js.
- docs/INVENTORY-MANIFEST.json — regenerated.

Closes #2795

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(install): gate banner prompt on actual installability (CR #3035)

CodeRabbit findings on PR #3035:

- bin/install.js (Major): continueAfterStatusline gated banner prompt on
  the raw `shouldInstallStatusline` flag from handleStatusline. But
  finishInstall later silently skips the statusline write on local
  installs unless --force-statusline is set (#2248). Two consequences:
    1. Interactive local Claude/Gemini installs got neither a statusline
       nor a banner offer.
    2. Codex/Cursor/Copilot/Windsurf/Trae/Cline-only installs (where
       every result.updateBannerCommand is null) still got prompted even
       though the choice was silently ignored.
  Fix: derive willInstallStatusline = shouldInstallStatusline &&
  (isGlobal || forceStatusline), and gate the banner prompt on a
  canInstallBanner precondition computed from results[].updateBannerCommand.
  Pass the raw shouldInstallStatusline through to finalize unchanged so
  per-runtime statusline gating in finishInstall is unaffected.

- tests/feat-2795-update-banner.test.cjs (Minor): rate-limit suppression
  test parsed r1.stdout without first asserting r1.status === 0. Other
  e2e tests in this file (lines 210, 241) do this. A non-zero exit would
  surface as a cryptic SyntaxError instead of a status assertion failure.
  Fix applied verbatim.

6881/6881 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 16:33:16 -04:00
Tom Boucher
35fffe7f31 docs(out-of-scope): record #2758 agent-template-rendering decision
Closed on the technical merits: the determinism claim is theoretical (no
observed misinterpretation), token waste is small and unmeasured, and PR
#2279's orchestrator-embedding path already serves the deterministic-gating
need without a parallel templating subsystem.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 15:56:24 -04:00
Tom Boucher
d137ce86ec docs(out-of-scope): record #2756 temporal-context decision
Reporter did not return to clarify the actual ask after the narrowing-then-
retraction in the comment thread. Closing as wontfix per .out-of-scope/
temporal-context.md with re-open criteria spelled out.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 15:53:08 -04:00
Tom Boucher
8c43ba7301 docs(#3025): MCP tool schema as a context-budget concern (#3032)
* docs(#3025): MCP tool schema as a context-budget concern

Adds documentation covering the largest GSD cost lever that GSD
itself does not own: MCP tool schema injection. Every enabled MCP
server adds its schema to every turn (often 20k+ tokens for
heavyweight servers like browser/playwright, mac-tools, etc.),
which can dwarf whatever `model_profile` tuning saves.

Two doc surfaces (per the bundle-docs-with-code skill depth gradient):

1. get-shit-done/references/context-budget.md
   - New "MCP Tool Schema Cost (Harness Concern)" section.
   - Explains schemas-per-turn cost framing.
   - Names enabledMcpjsonServers / disabledMcpjsonServers and
     .claude/settings.json explicitly.
   - Pre-phase audit checklist: browser/playwright, platform-specific,
     cross-project/stale, duplicate/shadow.
   - Explicit "GSD does not manage MCP enablement — harness concern"
     statement so users don't hunt for a GSD setting.
   - Links to Anthropic Claude Code MCP docs as canonical reference.
   - Notes compounding interaction with model_profile (additive levers).

2. docs/USER-GUIDE.md
   - New task-oriented "Trim MCP servers to reduce per-turn cost"
     section above "Using Non-Claude Runtimes".
   - Same checklist condensed.
   - Cross-link to context-budget.md for the full reference.

Tests:
- tests/feat-3025-mcp-token-budget-docs.test.cjs (12 cases) parses
  both docs into typed semantic-flag records and asserts behavioral
  invariants (mentions key, includes audit, names harness, etc.)
  rather than substring-matching prose. Adheres to CONTRIBUTING.md
  no-source-grep — section can be reworded freely as long as the
  required semantics survive.
- Markdownlint pre-flight tests (MD040 fence language, MD056 table
  column count) per the bundle-docs-with-code skill so CR can't
  ratchet on prose nitpicks across multiple review rounds.

Verification:
- 12/12 pass on regression test
- 6857/6857 full suite (12 net new)
- lint-no-source-grep clean (377 test files)

Companion to #3023 (per-phase-type model map) and #3024 (dynamic
routing). Together they cover the three biggest cost levers users
ask about; this issue covers the one GSD does not own.

Closes #3025

* docs(#3025): batch 3 CR fixes — pr id, relative link, named flag

CodeRabbit on PR #3032 (3 minor — 2 inline + 1 nitpick), all in one
push per the bundle-docs-with-code skill (avoid per-round nitpick
ratchet):

1. Inline (Minor) — .changeset/mcp-token-budget-docs.md:3
   `pr: TBD` → `pr: 3032` so changeset tooling can link the entry.

2. Inline (Minor) — docs/USER-GUIDE.md:1101
   Used a hardcoded `https://github.com/.../blob/main/...` URL for the
   cross-link to `context-budget.md`. Rest of USER-GUIDE.md uses
   relative links. Switched to `../get-shit-done/references/context-
   budget.md#mcp-tool-schema-cost-harness-concern` so feature-branch
   work shows the right content and rename-resilience is preserved.

3. Nitpick — tests/feat-3025-mcp-token-budget-docs.test.cjs:234
   The cross-link assertion used an inline `/context-budget/i.test(...)`
   while every other invariant in the file lived as a named flag in
   `parseMcpBudgetSection`. Per CONTRIBUTING.md no-source-grep, added
   `crossLinksContextBudget` to the parser and asserted on
   `parsed.crossLinksContextBudget` so the cross-link rule sits next
   to its siblings.

Verification:
- 12/12 pass on regression test (no count change; refactor only)
- No source code changes, only docs + tests

* test(#3025): strip inline markdown before phrase-match (CR nitpick)

CodeRabbit caught that the `explainsHarnessNotGsd` primary regex
branch couldn't match "GSD does **not** manage" in
context-budget.md because the markdown bold markers (`**`) sit
between contiguous words. The test passed today only via the
fallback `harness (concern|setting|controlled)` branch — the
primary branch was effectively dead code.

Fix: strip inline markdown emphasis (`**`, `*`, `~~`) and inline-
code backticks before any phrase-matching in `parseMcpBudgetSection`.
All seven flag computations now run against the stripped text so
markdown formatting can't silently invalidate any invariant.

Underscores are intentionally NOT stripped — `model_profile` and
other snake_case identifiers must survive intact for the
mentionsModelProfileInteraction check to find them.

Verification: 12/12 still pass; primary branches now fire on
real markdown content rather than relying on fallbacks.

* test(#3025): guard markdownlint tests against null section (CR nitpick)

CodeRabbit caught that the MD040 and MD056 markdownlint pre-flight
tests called `section.match(...)` and `section.split('\n')`
directly on the value returned by `extractSection`, which returns
null when no matching header is found. If the MCP section is ever
removed (regression), both tests would throw `TypeError: Cannot
read properties of null` instead of producing a clean assertion
failure naming the actual problem.

The semantic tests above are protected because parseMcpBudgetSection
short-circuits to a typed-falsy record on null input. The
markdownlint tests bypassed that guard since they need raw section
text, not parsed flags. Added `assert.ok(section, ...)` preconditions
to both so a missing section produces a meaningful failure message.

No content changes; defensive programming only.

Verification: 12/12 still pass.
2026-05-02 15:24:26 -04:00
Tom Boucher
e1d661ece0 feat(#3024): dynamic routing with failure-tier escalation (#3031)
* feat(#3024): dynamic routing with failure-tier escalation

Adds a `dynamic_routing` block to .planning/config.json that lets
the resolver start agents on a cheap tier and escalate one tier up
when the orchestrator detects a soft failure (verification
inconclusive, plan-check FLAG, etc.). Solves the "pay Opus rates as
insurance" anti-pattern by making escalation observed-quality-driven.

Architecture:
- AGENT_DEFAULT_TIERS map (light/standard/heavy) — every agent in
  MODEL_PROFILES declares a default tier; tests assert coverage
  so adding a new agent without updating the map fails CI.
- nextTier(currentTier) helper — light → standard → heavy → heavy
  (heavy stays at heavy; can't go further).
- resolveModelForTier(cwd, agentType, attempt) — new resolver. The
  orchestrator tracks the attempt counter and passes 0 for the
  first spawn, 1+ on escalation. The resolver caps internally at
  max_escalations so the orchestrator can blindly bump the counter.
- Schema validation: dynamic_routing.enabled / escalate_on_failure /
  max_escalations / tier_models.<light|standard|heavy>. Unknown
  tiers and unknown sub-keys rejected at config-set time.
- SDK schema mirror updated to keep CJS/SDK in lockstep (#2653).

Resolution precedence (highest → lowest):
  1. model_overrides[<agent>]              (full IDs accepted)
  2. dynamic_routing.tier_models[<tier>]   (NEW; escalation-aware)
  3. models[<phase_type>]                  (#3023 phase-type map)
  4. model_profile                         (per-agent column)
  5. Runtime default

Backward compatibility: dynamic_routing is disabled by default
(enabled: false or block omitted). resolveModelForTier short-
circuits to resolveModelInternal in that case, so callers can
adopt unconditionally without breaking existing behavior.

This PR delivers the JS-layer infrastructure: schema + tier map +
resolver. Orchestrator adoption (workflow markdown updates that
detect soft failures and call resolveModelForTier with attempt+1)
is incremental follow-up — verifier / plan-checker / integration-
checker each adopt the protocol when ready.

Tests (23 cases, all structural-IR — no stdout grep):
- Schema invariants: AGENT_DEFAULT_TIERS coverage, VALID_AGENT_TIERS
  exact match, every assignment uses a valid tier
- nextTier helper: light→standard→heavy→heavy, null on invalid input
- Disabled mode: no block + enabled:false both no-op (back-compat)
- Enabled mode: attempt=0 returns default tier model, attempt=1
  escalates, beyond max_escalations caps, heavy agents stay heavy,
  default max_escalations=1 when omitted
- Precedence: per-agent override beats dynamic_routing,
  dynamic_routing beats phase-type models
- Validation: every settings key accepted, unknown tiers/sub-keys
  rejected, bare `dynamic_routing` rejected as config-set target

Documentation:
- get-shit-done/references/model-profiles.md — full reference section
- docs/CONFIGURATION.md — full settings table + escalation flow
- docs/USER-GUIDE.md — task-oriented "Cheap-by-default" section
- docs/FEATURES.md — config row cross-link

Verification:
- 23/23 pass on regression test
- 6843/6843 full suite (23 net new from 6820)
- lint-no-source-grep clean (376 test files)
- SDK schema mirror keeps CJS/SDK in sync per #2653 parity test

Closes #3024

* fix(#3024): honor escalate_on_failure:false + 3 CR follow-ups

CodeRabbit on PR #3031 (4 findings — 1 Major + 2 Minor + 1 Nitpick):

1. **Major (inline)** — get-shit-done/bin/lib/core.cjs:1668
   resolveModelForTier ignored dynamic_routing.escalate_on_failure.
   When the user set it to false, escalation should be disabled, but
   the resolver only checked attempt/max_escalations. An orchestrator
   that always passes attempt+1 on retry would silently escalate
   despite the user opting out.
   Fix: gate effectiveAttempt on `dr.escalate_on_failure !== false`
   so false short-circuits every attempt back to the default tier.

2. **Minor (inline)** — docs/CONFIGURATION.md:123-126
   The dynamic_routing rows in the Core Settings table had 4 cells
   instead of 5 (missing the Options column), breaking the table
   structure. Added explicit Options values for enabled / escalate_on_failure
   / max_escalations rows.

3. **Minor (outside-diff)** — references/model-profiles.md:179-195
   "Resolution Logic" sketch was pre-#3024 and didn't include
   dynamic_routing in the precedence ladder. Updated to a 6-step
   block with dynamic_routing at step 3 (between override and
   phase-type).

4. **Nitpick** — tests/feat-3024-dynamic-routing.test.cjs:189+
   Tests used `if (lightAgent) { ... }` guards that silent-pass
   when AGENT_DEFAULT_TIERS drifts. Replaced all 5 conditional
   skips with `assert.ok(lightAgent, '...')` preconditions so a
   tier-mapping change surfaces as a test failure.

Plus: 2 new regression tests for the Major fix:
- escalate_on_failure:false caps every attempt at default tier
- escalate_on_failure:true (explicit) still escalates normally

Verification:
- 25/25 pass on regression test (23 prior + 2 escalate_on_failure)
- 6845/6845 full suite (2 net new)
- lint-no-source-grep clean

* docs(#3024): align precedence + add fence language tags (CR follow-up)

CodeRabbit (3 minor):

1. docs/CONFIGURATION.md:691 — "Per-Phase-Type Models → Resolution
   precedence" was a 4-step block written pre-#3024; readers got
   contradictory rules between the per-phase-type section and the
   later dynamic_routing section. Updated to the same 5-step ladder
   with dynamic_routing at step 2, and noted that dynamic_routing
   is disabled by default so this section's behavior is unchanged
   when the kill-switch is off.

2. docs/CONFIGURATION.md:770 — escalation-flow code fence missing
   language tag (MD040). Added `text`.

3. references/model-profiles.md:184 — resolution-ladder code fence
   missing language tag (MD040). Added `text`.

No code changes; docs only. Verification: regression test still 25/25.

* docs(#3024): clarify precedence prose — five layers, not four (CR nitpick)

CodeRabbit nitpick: the "Per-Phase-Type Models → Resolution
precedence" prose said "The four layers compose..." but the ladder
above lists five (including Runtime default). Also "dynamic_routing
escalates per-attempt above all of them" misreads as suggesting
dynamic_routing wins over model_overrides — actually overrides still
win at step 1.

Reworded top-down so the precedence direction is unambiguous:
  - model_profile = base
  - models = phase-level override
  - dynamic_routing = per-attempt escalation
  - model_overrides = per-agent exception (top)
  - runtime default = fallback

No code changes; docs only.

* docs(#3024): note escalate_on_failure:false in escalation-flow diagram (CR)

CodeRabbit nitpick: the escalation-flow diagram in
docs/CONFIGURATION.md described the soft-failure → respawn →
tier_models[next_tier_up] path, but didn't surface the
`dynamic_routing.escalate_on_failure: false` kill-switch right next
to it. Users reading the flow diagram (which is the canonical place
to understand attempt behavior) wouldn't see that the kill-switch
overrides the soft-failure branch.

Added a one-paragraph note immediately after the flow listing,
before the tier-sequence example, so the kill-switch is visible
exactly where users decide whether escalation will happen.

No code changes; docs only.
2026-05-02 14:26:35 -04:00
Tom Boucher
d812c66020 feat(#3023): per-phase-type model map in .planning/config.json (#3030)
* feat(#3023): per-phase-type model map in .planning/config.json

Adds a new `models` block to .planning/config.json with six phase-type
slots (planning / discuss / research / execution / verification /
completion). Lets users express coarse tuning ("Opus for planning,
Sonnet for the rest") without learning the agent taxonomy.

Resolution precedence (highest → lowest):
  1. Per-agent `model_overrides[agent]`     (full IDs; targeted exception)
  2. Phase-type `models[phase_type]`         (NEW; tier alias)
  3. Profile table (`model_profile`)          (per-agent column)
  4. Runtime default

The three layers compose: `models` defaults a phase, `model_overrides`
carves an exception. Phase-type values are tier aliases (opus/sonnet/
haiku/inherit) so the runtime-resolution chain (#2517) stays correct
end-to-end without further branching.

Implementation:
- model-profiles.cjs: new AGENT_TO_PHASE_TYPE map + VALID_PHASE_TYPES
  set. Each agent in MODEL_PROFILES gets one phase-type assignment;
  tests assert coverage so adding a new agent without updating the
  table fails CI.
- core.cjs (resolveModelInternal): inserts phase-type tier lookup
  between per-agent override and profile-derived tier. Skips runtime
  resolution when the resolved tier is 'inherit' (was previously gated
  only on profile === 'inherit'; phase-type can now produce inherit
  independently).
- core.cjs (loadConfig): pass `parsed.models` through both code paths
  so resolveModelInternal can read it.
- config-schema.cjs + sdk/src/query/config-schema.ts: dynamic-pattern
  validator accepts only the six known phase-types; unknown slots
  rejected at config-set time.

Backward compat: configs without `models` behave exactly as today.

Tests (15 cases, all structural-IR — no stdout grep):
- Schema: AGENT_TO_PHASE_TYPE coverage, VALID_PHASE_TYPES exact match
- Resolver: phase-type alone; per-agent override beats phase-type;
  phase-type beats profile; issue's full example; "inherit"; empty
  block is no-op; no block is no-op
- Validation: each of the 6 slots accepted; unknown slot rejected;
  bare `models` (no slot) rejected

Verification:
- 15/15 pass on new regression test
- 6808/6808 full suite (5 net new), 0 fail
- lint-no-source-grep clean across 375 test files

Closes #3023

* docs(#3023): document `models` per-phase-type config in user-facing docs

Adds `models` block coverage to the three user-facing docs that ship
with each release:

1. docs/CONFIGURATION.md
   - New "Per-Phase-Type Models" section between "Per-Agent Overrides"
     and "Non-Claude Runtimes" with:
       * full example mixing models + model_overrides
       * phase-type → agent mapping table
       * resolution-precedence pseudocode
       * accepted values (tier alias only)
       * "When to use which" decision matrix
       * validation behavior + example error
   - Added `"models": {}` to the Full Schema snippet
   - Added a row for `models.<phase_type>` to the config keys table
     (next to model_profile_overrides for adjacency)

2. docs/FEATURES.md
   - Added a row for models.<phase_type> in the Configurable Settings
     table (right under model_profile)
   - Cross-link to CONFIGURATION.md for the full surface

3. docs/USER-GUIDE.md
   - New task-oriented "Tuning model cost by phase" section above
     "Using Non-Claude Runtimes" — leads with the concrete config
     and shows the override pattern (one-shot phase + targeted exception)
   - Cross-link to CONFIGURATION.md

Verification:
- 29/29 pass on config-schema-docs-parity + docs-update + new feature
  test (parity-check passes, so the config-schema entry I added in the
  feature commit is now matched by a docs row)
- 6808/6808 full suite pass
- lint-no-source-grep clean

Doc style follows the same pattern used by the existing model_profile,
model_overrides, and model_profile_overrides sections — example-led,
table-backed, cross-referenced. Each doc surfaces the feature at the
right depth (reference / settings table / task guide).

* fix(#3023): mirror phase-type tier in resolveReasoningEffortInternal (CR Major)

CodeRabbit caught a real Codex correctness bug + 3 minor docs/test issues:

1. **Major (outside-diff)** — resolveReasoningEffortInternal in core.cjs
   derived its tier exclusively from the profile table, ignoring the
   models.<phase_type> override added in #3023. Failure mode on Codex:

     Config: model_profile=balanced, models.execution=opus, agent=gsd-executor
     resolveModelInternal:           tier=opus    → gpt-5.4
     resolveReasoningEffortInternal: tier=sonnet  → reasoning_effort=medium
                                                     ↑
                                                  WRONG — should be xhigh
                                                  (opus tier on Codex)

   The runtime received a mismatched (model, effort) pair. Mirrored the
   phase-type lookup from resolveModelInternal so both functions derive
   from the same tier source. 'inherit' phase-type returns null effort
   (no runtime entry maps to 'inherit'; let runtime decide).

2. Minor — .changeset/per-phase-type-models.md `pr: TBD` → `pr: 3030`.

3. Minor (outside-diff) — model-profiles.md "Resolution Logic" section
   omitted the new phase-type tier. Updated the 4-step block to a 5-step
   block including `models[phase_type]` between override and profile,
   plus a paragraph noting that `model` and `reasoning_effort` derive
   from the same tier source.

4. Nitpick — added 2 typo-safety tests:
   - models.research = "haiku3" (typo) → falls through to profile
   - models.research = "openai/gpt-5" (full ID) → falls through to profile
   Plus 5 new reasoning_effort tests covering the Major fix:
   - exported correctly
   - phase-type override flips both model AND effort to same tier
   - inherit phase-type returns null effort
   - per-agent override still bypasses phase-type for effort
   - claude runtime ignores models.* (no effort propagation)

Verification:
- 24/24 pass on regression test (15 original + 2 typo-safety + 5 effort + 2 outside-diff related)
- 6815/6815 full suite (7 net new from 6808)
- lint-no-source-grep clean

The reasoning_effort tests are written semantically (phase-type override
must produce the SAME effort as a profile-only opus config) rather than
hard-coding tier-specific effort strings, so changes to the runtime tier
map don't break them.

* fix(#3023): phase-type override beats profile=inherit (CR Major round 2)

CodeRabbit caught another precedence inversion: when
  { model_profile: 'inherit', models: { execution: 'opus' } }
both resolvers short-circuited on `profile === 'inherit'` BEFORE the
phase-type override could be honored. Result: model returned 'inherit'
and reasoning_effort returned null — both contradicting the documented
precedence where models[phase_type] wins over model_profile.

Fix in resolveModelInternal:
- Compute tier from phase-type FIRST. If phase-type is a valid alias,
  it wins. Otherwise, fall back to profile-derived tier OR 'inherit'
  (when profile === 'inherit').
- Gate the runtime-resolution branch on `tier !== 'inherit'` (was
  `profile !== 'inherit'`) so phase-type=opus can flip runtime mapping
  on even when profile=inherit.
- Gate the inherit-return on `tier === 'inherit'` (was
  `profile === 'inherit'`).

Fix in resolveReasoningEffortInternal:
- Remove the `if (profile === 'inherit') return null;` early-return.
- Compute tier from phase-type first, fall back to profile. If
  phase-type is explicitly 'inherit' OR the resolved tier is 'inherit',
  return null (no runtime entry maps to inherit).

Tests added (5 new):
- model: phase-type wins over profile=inherit (with explicit opus, with
  haiku for one phase + planner-without-slot still inheriting)
- model: profile=inherit + no models block → all agents inherit (no
  regression on existing inherit semantics)
- model: profile=inherit + models block but agent has no slot → that
  agent inherits, agents with slots get phase-type tier
- effort: phase-type opus + profile=inherit → produces opus-tier
  effort, NOT null (the original bug)

Verification:
- 27/27 pass on regression test (24 prior + 3 model + 1 effort)
- 6820/6820 full suite (5 net new)
- lint-no-source-grep clean

The effort test reads the expected value by running a profile-only opus
config and comparing — semantic check, not hard-coded effort string. So
runtime tier map changes don't break the test.
2026-05-02 13:19:15 -04:00
Tom Boucher
c9f5b7daac fix(#3020): probe user shell PATH at install-time, not just process.env.PATH (#3028)
* fix(#3020): probe user shell PATH at install-time, not just process.env.PATH

The installer's "✓ GSD SDK ready" message was a false positive whenever
the install subprocess's process.env.PATH contained the gsd-sdk shim
but the user's later interactive shells did not. Three known sources
of mismatch on POSIX:

- ~/.local/bin: install subprocess inherits npm/npx-injected PATH;
  user's login shell may not add ~/.local/bin if .profile/.bashrc/
  .zshrc don't.
- nvm/fnm/volta: node version managers shim PATH per-shell, so
  `npm prefix -g` from inside the install subprocess can resolve to
  a different bin dir than the user's interactive shell sees.
- npm-prefix tooling: some installers inject extra PATH entries that
  vanish in fresh sessions.

Result reported on #3011 by @x0rk and @stefanoginella: install prints
✓, but every workflow invocation later fails with
"bash: gsd-sdk: command not found".

Fix:

- isGsdSdkOnPath(pathString?) — now accepts an explicit PATH string.
  Zero-arg form preserves existing behavior (reads process.env.PATH).
  Pure walk, no spawn. Lets callers verify against any PATH source.

- getUserShellPath() — new helper. Probes the user's login shell via
  `$SHELL -lc 'printf %s "$PATH"'` (POSIX). 2-second timeout so a
  misconfigured rc file can't hang the install. Returns null on
  Windows (cross-shell PATH probing requires a different strategy
  per Git Bash / PowerShell / cmd.exe — tracked separately) or when
  the probe fails; callers fall back to process.env.PATH in that case.

- installSdkIfNeeded() — after the existing isGsdSdkOnPath() check
  passes, also verify the shim is reachable from getUserShellPath()
  on POSIX. If install-PATH and user-shell-PATH disagree, downgrade
  to the actionable ⚠ diagnostic from PR #3014 (which has the shim
  location, shell-specific PATH-update commands, and an npx fallback
  note). Routing affected users into PR #3014's diagnostic is the
  point — not silently green-then-red.

Tests:
- bug-3020-install-shell-path-probe.test.cjs (10 tests, structural):
  - isGsdSdkOnPath accepts an explicit PATH (true/false on fixture
    PATH dirs with/without an executable shim)
  - zero-arg form returns a boolean
  - empty string PATH → false
  - getUserShellPath returns string-or-null
  - returns null on Windows
  - returns null when $SHELL unset on POSIX
  - cross-shell mismatch detection: install-PATH and user-PATH that
    differ produce different isGsdSdkOnPath results — the invariant
    the install-time check now exploits
- All assertions on structural records, not console output. Adheres
  to typed-IR / CONTRIBUTING.md "Prohibited: Raw Text Matching".

Verification:
- 10/10 pass on new regression test
- 6768/6768 pass on full suite (5 net-new tests)
- lint-no-source-grep clean

Windows cross-shell coverage (gsd-sdk.cmd resolves under PowerShell
but not Git Bash without a no-extension sibling) is tracked separately
— this PR is the POSIX-side fix and the Windows scaffolding (the
optional pathString arg on isGsdSdkOnPath) that a Windows fix can
build on.

Closes #3020

* fix(#3020): type-guard pathString, last-line PATH parse (CR)

CodeRabbit on PR #3028 (4 findings — 3 actionable + 1 nitpick):

1. .changeset/install-shell-path-probe.md (2 findings):
   - `pr: TBD` → `pr: 3028`
   - Doc said `echo $PATH` but impl uses `printf %s "$PATH"` (chosen
     to avoid shell-dependent echo behavior, e.g. interpreting `-n`).
     Aligned changeset prose with implementation.

2. bin/install.js:9176 — isGsdSdkOnPath(pathString) used
   `pathString !== undefined` to gate the explicit-PATH branch, but
   getUserShellPath() can return null and `null.split()` throws.
   Tightened to `typeof pathString === 'string'` so null / number /
   object inputs fall back to process.env.PATH. Added 2 regression
   tests covering the null and non-string cases.

3. bin/install.js:9232 — getUserShellPath trimmed entire stdout. A
   misconfigured rc file that prints a banner / motd / log line
   BEFORE the printf would pollute the result and incorrectly flip
   the cross-shell check to false. Take the LAST non-empty line
   (PATH itself is single-line) so noise can't hijack the probe.

4. Nitpick: the changeset PR placeholder — covered by (1).

Verification: 12/12 pass on regression test (10 original + 2 new
type-guard tests), 6770/6770 full suite, lint clean.

* docs(#3020): JSDoc references printf %s "$PATH", not echo $PATH (CR)

CodeRabbit caught two stale JSDoc references that still said
`$SHELL -lc 'echo $PATH'` while the implementation uses
`$SHELL -lc 'printf %s "$PATH"'`. echo is undesirable here because:

- POSIX echo's behavior with `-n` / backslash escapes varies across
  shells (bash builtin vs /bin/echo vs zsh) and can introduce
  trailing-newline pollution that the per-line trim now papers over.
- printf is portable and emits exactly the bytes given.

Synced both stale doc strings:
  - bin/install.js:9211 (getUserShellPath JSDoc)
  - tests/bug-3020-install-shell-path-probe.test.cjs:27 (header)

No behavior change — implementation already uses printf.
2026-05-02 11:45:39 -04:00
Tom Boucher
6df9b44297 fix(#3018): codex adapter must stop and ask, not silently default decisions (#3027)
* fix(#3018): codex adapter must stop and ask, not silently default decisions

@jon-hendry: running `\$gsd-discuss-phase 81` in Codex Default mode
proceeded toward writing CONTEXT.md / DISCUSSION-LOG.md / checkpoint
artifacts without surfacing the discussion questions to the user. The
generated Codex skill adapter explicitly told it to do that:

  Execute mode fallback:
  - When `request_user_input` is rejected (Execute mode), present a
    plain-text numbered list and pick a reasonable default.

That instruction is wrong for any workflow whose contract is to
discuss with the user (most prominently `$gsd-discuss-phase`). The
fallback now requires the agent to:

1. STOP. Present the questions as a plain-text numbered list, then
   wait for the user's reply.
2. Only proceed without a user answer when one of these is true:
   (a) invocation included --auto / --all,
   (b) user explicitly approved a default for this question, or
   (c) workflow's documented contract permits autonomous defaults.
3. Do NOT write CONTEXT.md, DISCUSSION-LOG.md, PLAN.md, or checkpoint
   files until the user has answered or one of (a)-(c) above applies.

Tests:
- bug-3018-codex-discuss-fallback.test.cjs (5 tests, structural-IR):
  parses the generated header into sections via regex,  asserts on
  the Execute-mode-fallback section's content (must contain stop/
  wait + plain-text directives, must NOT contain "pick a reasonable
  default", must name a permission path, must forbid artifact
  writing). No raw text snapshot — the assertions describe the
  behavioral invariant, so prose can be reworded without test churn.
- codex-config.test.cjs:128 still passes — section still mentions
  "Execute mode" as required.

Verification:
- 5/5 pass on new regression test
- 116/116 pass on bug-3018 + codex-config combined
- 6763/6763 pass on full suite
- lint-no-source-grep clean

Closes #3018

* test(#3018): parse fallback into typed semantic-flag record (CR)

CodeRabbit nitpick on PR #3027: the regression tests grepped the
generated header prose with regex, which is brittle and tests wording
rather than semantics. Per CONTRIBUTING.md "no-source-grep" standard.

Refactored to a structural-IR shape:

- New `parseExecuteModeFallback(section)` walks the section text once
  and returns a typed record:
    {
      ok, sectionLength,
      instructsStop,                          // STOP/HALT/WAIT directive
      presentsPlainTextQuestions,             // plain-text / numbered list
      namesPermissionPath,                    // --auto / --all / explicit approval
      forbidsWritingArtifactsBeforeAnswer,    // write-ban + named artifact class
      silentlyPicksDefaults,                  // anti-pattern guard (must be false)
    }
- Each positive invariant gets its own test asserting on the parsed
  boolean, so a failure points at the exact invariant that broke.
- A final test does a single assert.deepStrictEqual against the full
  expected contract — gives a structured diff when any flag flips.
- The artifact-write ban now requires BOTH a "do not write" intent
  AND a named artifact class (was a single broad regex), so generic
  "do not write" prose elsewhere in the section can't satisfy it.

Verification: 8/8 pass; lint-no-source-grep clean.
2026-05-02 11:45:36 -04:00
Tom Boucher
e3b64b39f8 fix(#3019): query --help reaches handler instead of short-circuiting (#3026)
* fix(#3019): query --help reaches handler instead of short-circuiting to top-level usage

The query argv parser in sdk/src/cli.ts harvested -h/--help as a global
flag and main() short-circuited dispatch when args.help was true. Net
effect: every `gsd-sdk query <anything> --help` printed top-level USAGE
instead of contextual subcommand help. There was no path for users to
discover what arguments a query subcommand accepts — they had to trigger
"required" errors by trial and error.

Two-layer fix:

1. sdk/src/cli.ts (parseCliArgsQueryPermissive)
   - Push -h / --help onto queryArgv instead of consuming them silently,
     so the registered handler / gsd-tools.cjs fallback gets to interpret
     the flag and render contextual help.
   - Only honor the global help flag when there is NO real subcommand to
     dispatch to (i.e. queryArgv contains only help flags). Preserves
     `gsd-sdk query --help` → top-level USAGE while letting
     `gsd-sdk query phase add --help` reach the handler.

2. get-shit-done/bin/gsd-tools.cjs
   - Render top-level usage on --help / -h / -? / --usage instead of
     erroring with "Unknown flag". The discovery hint in the usage text
     points users at the working method (run without args → error names
     required arguments) and references #3019 for tracking subcommand-
     level help printers.
   - --version remains rejected (no discovery use-case).

#1818 anti-hallucination invariant preserved: the destructive command
NEVER executes when --help is present. The new shape returns success:true
+ usage on stdout instead of the old success:false + error on stderr —
both satisfy "destructive command did not run", and the new shape also
restores discoverability.

Tests:
- sdk/src/cli.test.ts: 4 new vitest cases covering #3019 — query argv
  parser keeps --help with subcommand, parses -h short flag, preserves
  bare `query --help` top-level behavior, preserves --help position when
  intermixed with other query flags.
- tests/bug-3019-help-passthrough.test.cjs: 5 node:test cases on the
  fallback — bare gsd-tools (no args) errors with usage; --help renders
  usage on stdout exit 0; -h same; subcommand --help renders usage; usage
  hint mentions discovery method (without prose substring matching —
  parses into typed sections).
- tests/bug-1818-unknown-flags.test.cjs: rewritten to assert the new
  invariant ("destructive command did not run" + "usage was rendered")
  instead of the old shape ("--help is rejected with non-zero exit").
  Each destructive test seeds a sentinel artifact (phase dir, slug
  output) and asserts it survives.

Verification:
- 47/47 vitest pass on sdk/src/cli.test.ts
- 5/5 pass on tests/bug-3019-help-passthrough.test.cjs
- 8/8 pass on tests/bug-1818-unknown-flags.test.cjs (rewritten)
- 6763/6763 pass on full node:test suite
- lint-no-source-grep clean (0 violations)

Closes #3019

* fix(#3019): SDK fallback forwards plain-text help, broader usage list (CR)

CodeRabbit on PR #3026 (4 findings — 1 Major outside-diff, 2 inline,
1 nitpick):

1. **Major outside-diff** — sdk/src/cli.ts:442-454. The fallback path
   that delegates to gsd-tools.cjs called parseCliQueryJsonOutput
   (JSON.parse) on stdout. Now that gsd-tools renders plain-text usage
   on --help, JSON.parse threw "Unexpected token 'U'". Wrapped the
   parse in try/catch — on parse failure, forward the plain stdout
   verbatim so subcommand help reaches the user. Regression test:
   tests/bug-3019-help-passthrough.test.cjs spawns the built SDK and
   asserts `gsd-sdk query phase --help` exits 0, stdout contains the
   gsd-tools usage, and stderr does NOT contain a JSON-parse error.

2. .changeset/help-passthrough.md:3 — `pr: TBD` → `pr: 3026`.

3. gsd-tools.cjs:346 (TOP_LEVEL_USAGE):
   - Removed self-referencing `#3019` link (immediately stale after
     this PR merges).
   - Expanded Commands list from 17 → all 47 dispatcher cases:
     agent-skills, audit-open, audit-uat, check-commit, commit, …
     phase, phases, roadmap, milestone, validate, progress, intel,
     graphify, learnings, etc. — the bulk of the surface that was
     previously unreachable via --help discovery.

4. Nitpick: `isUsageOutput` was duplicated in bug-1818 and
   bug-3019-help-passthrough tests. Moved to tests/helpers.cjs with
   structural-comment, removed both duplicates.

Verification: 47/47 vitest pass, 14/14 regression tests pass,
6764/6764 full suite, lint clean.

* test(#3019): use t.skip() instead of bare return when SDK not built (CR)

CodeRabbit follow-up on PR #3026:

The integration test guarded against missing sdk/dist/cli.js with a
bare `return;` — node:test counts that as a passing test (0 assertions
exercised, 0 failures). On a CI checkout that hasn't run the SDK build,
the #3026 regression test silently green-lit and no signal ever
surfaced that the integration check was skipped.

Switched to `t.skip(...)` via the test context parameter so the
omission shows up in the test report. The unit-level fix
(sdk/src/cli.ts) is still covered by vitest, so the skip only affects
the end-to-end spawn-built-SDK check.

Verification: 6/6 pass when SDK is built; 5 pass + 1 skip when not.
2026-05-02 11:45:33 -04:00
Tom Boucher
8e25eb6546 fix(#3017): codex SessionStart hook uses absolute node, not bare 'node' (#3022)
* fix(#3017): codex SessionStart hook uses absolute node, not bare 'node'

PR #3002 fixed #2979 for settings.json-based managed JS hooks (Claude
Code, Gemini, Antigravity) by routing through buildHookCommand() →
resolveNodeRunner(), emitting the absolute Node binary path so hooks
resolve under GUI/minimal-PATH runtimes (/usr/bin:/bin:/usr/sbin:/sbin)
where nvm/Homebrew/Volta-installed node is not on PATH.

The Codex install path bypassed both helpers — line 7935 of bin/install.js
wrote `command = "node ${path}"` directly into config.toml. So Codex
SessionStart hook still failed with exit 127 ("node: command not found")
under the same minimal-PATH conditions PR #3002 was meant to close.

Fix:
- Add buildCodexHookBlock(targetDir, { absoluteRunner, eol }) — a pure
  helper that emits the toml hook block with the absolute runner. Returns
  null when absoluteRunner is null so the caller skips registration with
  a warning instead of writing a broken bare-node hook.
- Add rewriteLegacyCodexHookBlock(content, absoluteRunner) — mirror of
  rewriteLegacyManagedNodeHookCommands for the toml surface, so
  reinstall migrates a 1.39.x bare-node config.toml to the absolute form.
  Uses basename equality (CODEX_MANAGED_HOOK_BASENAMES set) so user-
  authored bare-node hooks are left alone.
- Replace the inline string-concat at line 7935 with a call to the new
  helper, threaded with the detected line ending so CRLF files stay CRLF.
- On the codex reinstall path, call rewriteLegacyCodexHookBlock first so
  existing bare-node entries get migrated before the new entry is added.

Tests:
- bug-3017-codex-hook-absolute-node.test.cjs (9 tests, all typed-IR):
  - buildCodexHookBlock emits absolute runner, parses to expected fields
  - returns null on missing runner (caller skips)
  - integrates with resolveNodeRunner() in the live process
  - rewriteLegacyCodexHookBlock migrates managed bare-node entries
  - leaves user-authored bare-node hooks alone (basename allowlist)
  - leaves entries with absolute runner unchanged (idempotent)
  - returns content unchanged when absoluteRunner is null
- codex-config.test.cjs e2e expectation updated to match new shape:
  parsed.hooks.SessionStart[0].hooks[0].command now equals
  '"<process.execPath>" "<hookPath>"' instead of 'node <hookPath>'.

Verification:
- 9/9 pass on the new regression test
- 179/179 pass across all codex-touching test files
- 6767/6767 pass on full suite, lint-no-source-grep clean
- Adheres to typed-IR / CONTRIBUTING.md "Prohibited: Raw Text Matching":
  parseCodexHookBlock returns a typed record; assertions are on
  structured fields (runner, hookPath, type, hasMarker), not stdout regex.

Closes #3017

* test(#3017): tighten runner assertions to exact process.execPath (CR)

CodeRabbit on PR #3022 (3 findings, 2 actionable + 1 nitpick):

1. .changeset/codex-bare-node-fix.md:3 — replace `pr: TBD` with
   `pr: 3022` so changeset metadata is traceable.

2. tests/bug-3017-codex-hook-absolute-node.test.cjs:81-146 — the test
   asserted `parsed.runner !== 'node'` and `parsed.runner.includes('/node')`,
   which would false-positive on any absolute path containing '/node'
   (e.g. /Users/x/notnode/foo). Tightened to compare against the EXACT
   absolute path supplied by the caller (after stripping toml + JSON
   escape layers via a new unescapeRunner() helper). The live-process
   integration test now compares against process.execPath exactly. The
   rewriteLegacyCodexHookBlock test also uses exact-equality.

3. Nitpick (skipped): use repository's TOML parser for parsing instead
   of bespoke regex. The hand-rolled parser is small, scoped, and
   fully tested by these structural assertions; pulling in a TOML lib
   for tests would create a circular dependency on the SUT (the
   installer's own parser). Leaving as-is.

Verification: 9/9 pass on regression test, 6767/6767 full suite, lint clean.
2026-05-02 11:45:30 -04:00
Tom Boucher
f2decefede fix(#3010): post-install message and docs use /gsd-update --reapply (#3012)
* fix(#3010): post-install message and docs use /gsd-update --reapply

PR #2824 consolidated 86 skills into ~58, removing the standalone
/gsd-reapply-patches command and folding it into a flag on /gsd-update
(/gsd-update --reapply). The 1.39.1 hotfix (#2954) updated help.md
but missed three other surfaces that still recommended the dead form:

1. bin/install.js reportLocalPatches() — runtime emitter shown after
   every install with backed-up patches. All branches updated:
   - claude/opencode/kilo/copilot: /gsd-update --reapply
   - gemini: /gsd:update --reapply
   - codex: $gsd-update --reapply
   - cursor: gsd-update --reapply (mention the skill name)

2. get-shit-done/workflows/update.md — Step 4 prose and the
   check_local_patches block both referenced /gsd-reapply-patches.
   Replaced with /gsd-update --reapply (with backticks around the
   command per CR feedback for copy/paste UX).

3. Localized docs (en/ja-JP/ko-KR/zh-CN) — 14 files across
   ARCHITECTURE.md / COMMANDS.md / FEATURES.md / INVENTORY.md /
   USER-GUIDE.md / manual-update.md still listed the removed command.

Tests:
- bug-3010-reapply-patches-references.test.cjs (4 tests): scans
  bin/install.js's reportLocalPatches body, every workflow file, and
  every doc (excluding CHANGELOG history and help.md's deprecation
  notice) for the removed command form, and verifies each runtime
  branch emits the consolidated form via captured console output.
- tests/copilot-install.test.cjs:1081-1115 — stale assertions that
  hard-coded the removed string updated to assert /gsd-update --reapply.

Verification: 115/115 pass across both files.

Co-authored-by: Patrick Clery <patrick@patrickclery.com>
Closes #3010

* test(#3010): broaden dead-command scan + tighten runtime exact-match

CodeRabbit follow-up findings on #3012:

1. Workflow + docs scans only matched "/gsd-reapply-patches", missing
   the gemini ("/gsd:reapply-patches") and codex ("$gsd-reapply-patches")
   spellings. A regression that re-introduced either form in localized
   docs would have passed silently. Extracted a DEAD_COMMAND_PATTERNS
   array + findDeadCommands() helper used by both scans, so all three
   removed forms are checked uniformly. Match output also reports which
   spellings hit, for faster diagnosis.

2. reportLocalPatches runtime test asserted output.includes('update --reapply'),
   which is too loose — a malformed prefix like '/gsd:update --reapply' on
   the claude branch would have passed. Replaced with an exact
   {runtime → expected token} map covering all 7 branches:
     claude/opencode/kilo/copilot → /gsd-update --reapply
     gemini → /gsd:update --reapply
     codex → $gsd-update --reapply
     cursor → gsd-update --reapply
   Negative assertion also runs DEAD_COMMAND_PATTERNS against output for
   every runtime, so dead forms can't slip in regardless of branch.

Verification: 4/4 pass on bug-3010-reapply-patches-references.test.cjs.

* test(#3010): add prefix-absence guard for cursor runtime (CR follow-up)

CodeRabbit (Minor): the cursor expected token "gsd-update --reapply" is
a substring of every prefixed form ("/gsd-update --reapply" for claude/
opencode/kilo/copilot, "\$gsd-update --reapply" for codex). The positive
output.includes(expectedToken) check therefore can't distinguish correct
cursor output from a regression where the installer emits a prefixed
form for cursor — both pass the substring check.

Add an explicit prefix-absence assertion for cursor that fails if any
of /, \$, or : appears immediately before "gsd-update --reapply" in
output. The gemini form ("/gsd:update --reapply") doesn't share the
substring (gsd:update vs gsd-update) so it's already caught by the
positive includes failing on cursor's expected bare token.

Verification: 4/4 pass.

---------

Co-authored-by: Patrick Clery <patrick@patrickclery.com>
2026-05-02 09:38:34 -04:00
Tom Boucher
a4e5cc7c24 fix(#3011): actionable SDK-not-on-PATH diagnostic with shim location and shell-specific commands (#3014)
* fix(#3011): actionable SDK-not-on-PATH diagnostic with shim location and shell-specific commands

The previous diagnostic was a generic 'GSD SDK files are present but
gsd-sdk is not on your PATH' message with no concrete path or
shell-specific PATH-export command. Windows users reported that they
couldn't find where the shim was written and didn't know how to add
it to PATH for each shell (PowerShell vs cmd.exe vs Git Bash vs WSL
all read PATH from different sources).

New formatSdkPathDiagnostic({ shimDir, platform, runDir }) helper
returns a typed IR:
- shimLocationLine: explicit 'Shim written to: <path>'
- actionLines: platform-specific PATH-export commands
  - Windows: 3 lines (PowerShell, cmd.exe, Git Bash with backslash->/
    translation for bash compatibility)
  - POSIX: 1 line (export PATH=...)
- npxNoteLines: 'you're running via npx ... npm install -g instead'
  when runDir is under an _npx cache segment (where the shim may be
  written to a temp dir that won't persist for the user's interactive
  shell)
- isNpx, isWin32: structured booleans for assertions

Renderer in install.js just emits each line. Tests assert on the
typed IR fields directly (no source-grep, no console-output parsing).

Tests: 12 cases across 5 suites covering Windows shell flavors
(PowerShell preserves backslashes, Git Bash translates to forward),
POSIX exports, null-shimDir fallback to npm install -g advice, npx
detection on both path-separator conventions, and IR shape contract.

Closes #3011

* fix(#3011): cmd.exe guidance uses powershell -Command, not setx

CodeRabbit flagged the cmd.exe action line as a Major Windows
correctness bug:

  setx PATH "${shimDir}; %PATH%"

Two failure modes:
1. setx silently truncates the registry value above 1024 chars,
   permanently storing the truncated PATH and breaking applications
   until restored from the registry backup or fixed manually.
2. %PATH% expands to its current literal value at the moment setx
   runs, and the result is written as REG_SZ instead of REG_EXPAND_SZ.
   Lazy references like %SystemRoot% are baked in as literals, so
   future changes to those variables stop propagating.

Replace with the same SetEnvironmentVariable call already used for
the PowerShell line, invoked through `powershell -Command` so cmd.exe
users get a safe command without us recommending two different APIs.

* fix(#3011): escape shimDir for PowerShell, bash, and POSIX export

CodeRabbit (Minor): a Windows username with a single quote (e.g.
"C:\Users\O'Neil\AppData\Roaming\npm") would interpolate raw into the
suggested commands, producing unparseable shell input the user can't
fix without understanding the bug.

Each shell context needs a different escape:

- PowerShell single-quoted strings: '' is the literal-quote escape.
  Apply to both the PowerShell line and the cmd.exe line (which
  delegates to PowerShell).

- Git Bash, where the path lives inside an outer single-quoted echo:
  '\'' (close-quote, escaped-quote, reopen-quote) embeds a literal
  single quote. The slash-conversion (\\ → /) still applies first.

- POSIX export (Linux/macOS) inside double quotes: escape \, $, ",
  and backtick so the path is copied verbatim. $PATH lives outside
  the escape and still expands at paste time.

Regression test: bug-3011-sdk-path-diagnostic.test.cjs locks in the
expected escape sequence for all three shell flavors.
2026-05-02 09:30:58 -04:00
Tom Boucher
f55069ecbf test(#2974): migrate 8 test files to typed-IR assertions (#3016)
* test(#2974): migrate 8 test files to typed-IR assertions

Replaces raw stdout/stderr substring matching with structured-field
assertions per CONTRIBUTING.md "Prohibited: Raw Text Matching on Test
Outputs". Adds shared infrastructure for typed error emission so this
pattern is the easy path going forward.

Shared infrastructure:
- core.cjs: ERROR_REASON frozen enum + setJsonErrorMode/getJsonErrorMode
- gsd-tools.cjs: --json-errors CLI flag, parsed before subcommand dispatch
- config.cjs: typed reasons at all 7 error sites
- graphify.cjs: GRAPHIFY_REASON enum + reason/timeout_ms in execGraphify result
- bin/install.js: pure buildSdkFailFastReport() IR builder + renderer
- hooks/gsd-session-state.sh, gsd-phase-boundary.sh: emit Claude Code
  hookSpecificOutput JSON envelope with typed state_present/config_mode/
  planning_modified/file_path fields (no-op when hooks.community is off)

Test migrations (all pass, 171 tests across the 8 files):
- bug-2649-sdk-fail-fast: assert on ir.reason / ir.context / ir.fix_command
- bug-2687-config-read-warning-parity: assert.equal stderr === ''
- bug-2796-arg-parsing-regression: assert on result.json.updated/.phase
- bug-2838-summary-rescue: parse rescue footer, assert mtime invariant
- bug-2943-config-get-context-window: parse JSON, assert ERROR_REASON.CONFIG_KEY_NOT_FOUND
- graphify: assert reason === GRAPHIFY_REASON.ENOENT/TIMEOUT
- hooks-opt-in: parse hookSpecificOutput, assert typed fields
- security-scan: reclassified as source-text-is-the-product (scan label
  output and CI workflow YAML ARE the deployed contract)

Verification: lint-no-source-grep clean (0 violations), full suite
6741/6741 pass.

Closes #2974

* test(#2974): address CR feedback — typed code field, robust idempotency

Two CodeRabbit findings on #3016 addressed:

1. tests/hooks-opt-in.test.cjs:355 (Minor, inline) —
   parsed.reason.includes('Conventional Commits') was still substring
   matching after the typed-IR migration. Fixed at the source: the
   gsd-validate-commit hook now emits a typed `code` field
   ('CONVENTIONAL_COMMITS_VIOLATION', 'COMMIT_SUBJECT_TOO_LONG')
   alongside the human-readable `reason`. Test asserts strictEqual
   on the code; the prose copy is no longer part of the test contract.

2. tests/bug-2838-summary-rescue-gitignored-planning.test.cjs:224-250
   (Outside-diff) — mtimeMs alone can stay unchanged on coarse-grained
   filesystems (HFS+, FAT) when two rewrites land within the same
   timestamp tick, falsely passing the idempotency assertion.
   Replaced with a full snapshot (mtimeMs, ctimeMs, size, ino, sha256
   of contents) compared via assert.deepStrictEqual — the hash
   catches any rewrite the timestamp would miss.

Verification: 30/30 pass on the two affected files; lint-no-source-grep
clean (0 violations across 368 test files).
2026-05-02 09:27:23 -04:00
Tom Boucher
de25400b70 fix(#2979): emit absolute node path in managed hooks for GUI/minimal-PATH runtimes (#3002)
* fix(#2979): emit absolute node path in managed hooks for GUI/minimal-PATH runtimes

Installer-emitted hook commands started with bare 'node' which works
under interactive shells (nvm/Homebrew/Volta on PATH) but fails in
GUI-launched runtimes that start with /usr/bin:/bin:/usr/sbin:/sbin.
Every managed JS hook (gsd-check-update, gsd-statusline, gsd-context-monitor,
gsd-prompt-guard, gsd-read-guard, gsd-read-injection-scanner,
gsd-workflow-guard) failed with /bin/sh: node: command not found —
silently disabling update checks, statusline, and security guards.

Fix: new resolveNodeRunner() helper returns process.execPath (the
absolute path of the Node binary running the installer) forward-slash-
normalized and double-quoted. Used in:
  - buildHookCommand() for global installs (.js runner)
  - local-install code paths for all 7 managed JS hooks

.sh hooks keep bare 'bash' — /bin/bash is in the POSIX standard PATH
and always resolves under minimal-PATH GUI launches.

Tests: bug-2979-hook-absolute-node.test.cjs parses emitted commands
into { runner, hookPath } records and asserts:
  - resolveNodeRunner returns quoted absolute forward-slash node path
  - .js hooks emit absolute runner (default and portableHooks modes)
  - .sh hooks still emit bare 'bash'

Closes #2979

* chore(#2979): add changeset fragment for PR #3002

* chore(#2979): add changeset fragment for PR #3002

* fix(#2979): resolveNodeRunner returns null on missing execPath; rewrite legacy bare-node managed hooks (CR feedback)

CodeRabbit on PR #3002 caught two issues:

1. resolveNodeRunner fell back to bare 'node' when process.execPath was
   empty -- recreating the exact #2979 bug. Now returns null. Callers
   (buildHookCommand and the local-install code paths) check for null
   and skip registration rather than emit a broken command.

2. The original #2979 fix only updated NEWLY registered hooks. Existing
   bare-node managed hook entries from pre-#2979 installs stayed
   broken across reinstalls. New rewriteLegacyManagedNodeHookCommands
   walks settings.hooks and rewrites any managed-hook entry that starts
   with bare 'node ' to use the absolute runner. Filename allowlist
   (gsd-check-update.js, gsd-statusline.js, gsd-context-monitor.js,
   gsd-prompt-guard.js, gsd-read-guard.js, gsd-read-injection-scanner.js,
   gsd-workflow-guard.js) ensures user-authored bare-node hooks are
   left untouched.

Tests: bug-2979-hook-absolute-node.test.cjs grows by 8 cases:
- 5 for the migration walker (rewrites managed entries, leaves quoted-
  runner entries alone, leaves user-authored entries alone, leaves .sh
  entries alone, no-ops on null runner).
- 2 for resolveNodeRunner returning null on empty execPath.
- 1 for buildHookCommand returning null when execPath unavailable.

* chore(#3002): drop direct CHANGELOG.md edit; release entry now lives in .changeset/

The changeset-fragment workflow (#2975) renders fragments into
CHANGELOG.md at release time. Direct edits to [Unreleased] on
each PR caused merge conflicts on every concurrent PR. This commit
restores CHANGELOG.md to match origin/main; the release entry for
this fix is preserved in the .changeset/*.md fragment(s) on this
branch, which the release workflow consolidates.

* fix(#2979): guard hook + statusline pushes against null commands (CR follow-up)

CodeRabbit on PR #3002 found an outside-diff issue: when
resolveNodeRunner() returns null, every dependent *Command becomes
null, but the registration sites still pushed { type: 'command',
command: null } entries onto settings.hooks. The runtime's hook
schema rejects null commands and the failure surfaces as a confusing
parse error.

Fix:
- One unified warning at the top of configureSettings when ANY JS-hook
  command resolves null (operator sees the cause once instead of per-hook).
- Each of the 6 managed JS hook registration if-clauses now guards on
  the *Command variable being truthy: && updateCheckCommand,
  && contextMonitorCommand, && promptGuardCommand, && readGuardCommand,
  && readInjectionScannerCommand, && workflowGuardCommand.
- Statusline registration adds an else-if (!statuslineCommand) clause
  with its own warn before the settings.statusLine write site.

Tests: bug-2979-hook-absolute-node.test.cjs grows by 7 cases
(6 per-hook structural assertions parsing install.js for the
`fs.existsSync(<file>) && <command>` shape, plus 1 statusline
guard-precedes-write test).

* fix(#2979): defense-in-depth validateHookFields before writeSettings (CR)

CodeRabbit on PR #3002 (post-fix-up review): replace source-grep
structural tests with behavioral assertions on the settings object.

The push-site `&& <command>` guards (commit ce696c64) prevent null
commands from being pushed in the first place. As a defense-in-depth
backstop, install.js now runs validateHookFields(settings) right
before writeSettings(); validateHookFields already filters
{type:'command', command: null} entries (line 5884), so even if my
push-site guards ever regress, no null-command entries reach disk.

Tests: replaced the 7 install.js source-grep tests with 8 truly
behavioral tests:
- validateHookFields strips null-command entries for each of the 6
  managed JS hook shapes (parameterized by event + matcher)
- validateHookFields drops the entry entirely when all its hooks are
  null-command
- validateHookFields preserves agent-type hooks while stripping
  null-command sibling hooks in the same entry

These tests exercise the actual function the production code uses,
not its source representation. They survive future refactors of the
registration call sites.

* fix(#2979): tighten managed-hook migration to basename equality (CR)

CodeRabbit on PR #3002 (post-fix-up review): the previous
`trimmed.includes(name)` matcher had a false-positive vector. A
user-authored hook whose path contained a managed filename as a
substring (e.g. /home/me/scripts/wraps-gsd-check-update.js-helper.js)
would be unconditionally rewritten with the GSD runner, replacing
the user's bare `node` with our absolute path -- silently mutating
their hook configuration.

Fix: parse the command into <runner> <script-token> with the
script-token allowed to be quoted (single or double) or bareword.
Extract the path inside quotes, take the basename (handles both
forward and backslash separators on Windows), and match against
MANAGED_HOOK_FILES via Set.has() — exact equality, not substring.

Tests: bug-2979 grows by 4 cases:
- user hook with managed-filename-as-substring is NOT rewritten
- single-quoted path: rewritten correctly
- bareword path: rewritten correctly
- Windows backslash path: basename extraction works
2026-05-02 00:40:09 -04:00
Tom Boucher
ca78b65de7 fix(#2973): /gsd-profile-user writes dev-preferences.md to skills/, not legacy commands/gsd/ (#3003)
* fix(#2973): /gsd-profile-user writes dev-preferences.md to skills/ not legacy commands/gsd/

v1.39.0's install summary claimed the legacy ~/.claude/commands/gsd/
directory had been removed in favor of skills-only architecture, but
the cmdGenerateDevPreferences writer at profile-output.cjs:781 still
defaulted to the legacy path. Every /gsd-profile-user --refresh
deterministically re-created the legacy directory.

Missed in PR #1540's migration because dev-preferences is a
runtime-generated user artifact, not a GSD-shipped command file.

Fix:
- Writer default: ~/.claude/skills/gsd-dev-preferences/SKILL.md
- profile-user.md Display message + artifact list reference new path
- New migrateLegacyDevPreferencesToSkill(targetDir, saved) installer
  helper. Called at all 5 skills-aware install branches. Copies
  preserved legacy dev-preferences.md into skills/gsd-dev-preferences/
  SKILL.md, but ONLY if no SKILL.md already exists -- never clobbers
  user-customized skill content.

Tests: bug-2973-profile-user-skills-path.test.cjs runs the writer in
a subprocess (core.cjs:output uses fs.writeSync(1, ...) which bypasses
in-process stubbing), asserts the writer's command_path field is the
skills location, the file is on disk at that path, the legacy path is
NOT created. Tests for migration helper assert it writes when no skill
exists and skips when one does.

Closes #2973

* chore(#2973): add changeset fragment for PR #3003

* fix(#2973): rephrase comment to avoid cline-install leaked-path lint

The new comment at line 780 of profile-output.cjs literally contained
the string '~/.claude/commands/gsd/' which the cline-install
leaked-path regression test (tests/cline-install.test.cjs:175)
correctly flagged.

Cline transforms .claude/skills/ -> .cline/skills/ in installed .cjs
files but does not transform .claude/commands/. The new comment talks
about the legacy 'commands/gsd' subdirectory without the ~/.claude/
prefix, so the lint passes. The path semantics are unchanged -- the
runtime construction at line 787 still uses path.join(os.homedir(),
'.claude', 'skills', ...) which the lint regex does not match.

* test(#2973): add timeout to spawnSync to prevent CI hangs (CR feedback)

CodeRabbit on PR #3003: without a timeout, a regression that hangs the
writer or dispatcher would block CI indefinitely. Added a 30s timeout
(generous for what should complete in <1s) and an explicit signal
assertion so a timeout trip surfaces as a clear test failure with
context rather than a hung worker.

* test(#2973): add allow-test-rule annotation for legitimate product-text parsing

The new var-binding lint from #2982/#2985 caught readFileSync(...).match()
and readFileSync(...).includes() calls in this test. Both are legitimate
structural assertions against the product workflow markdown, not source-grep:

- match() extracts the path from a structured Display: "..." line and
  asserts on the typed path value (same pattern as bug-2470's installer
  scanForLeakedPaths regex test).
- includes() asserts the absence of a legacy path literal.

profile-user.md IS the shipped workflow artifact, and its Display: line
IS what the user sees. Per the existing test-rigor convention, this is
the source-text-is-the-product justification category.

Annotated with allow-test-rule citing that category.

* chore(#3003): drop direct CHANGELOG.md edit; release entry now lives in .changeset/

The changeset-fragment workflow (#2975) renders fragments into
CHANGELOG.md at release time. Direct edits to [Unreleased] on
each PR caused merge conflicts on every concurrent PR. This commit
restores CHANGELOG.md to match origin/main; the release entry for
this fix is preserved in the .changeset/*.md fragment(s) on this
branch, which the release workflow consolidates.

* fix(#2973): preserve user-owned gsd-dev-preferences skill across wipe (CR)

CodeRabbit on PR #3003 caught a real bug: copyCommandsAsClaudeSkills()
wipes ALL gsd-* skill directories at the top of every install, then
reinstalls from the package source. Since gsd-dev-preferences is
user-generated (written by /gsd-profile-user --refresh) and NOT
shipped by the npm package, the wipe deletes the user's customized
SKILL.md with nothing to restore from.

Fix: USER_OWNED_SKILLS allow-list in copyCommandsAsClaudeSkills.
Snapshot files under skills/gsd-dev-preferences/ before the wipe,
restore after. Same preserve/restore pattern as PR #1924.

Tests: bug-2973 grows by 2 cases:
- user-customized SKILL.md survives the wipe
- non-user-owned gsd-* skills are still wiped (preservation is opt-in)
2026-05-02 00:29:45 -04:00
Tom Boucher
1a51ec5829 fix(#2990): gsd-code-fixer worktree attaches to a new branch, not the user-checked-out one (#3001)
* fix(#2990): gsd-code-fixer worktree attaches to a new branch, not the user-checked-out one

The agent's setup_worktree step ran 'git worktree add "$wt" "$branch"'
where $branch was the user's currently-checked-out branch in the main
repo. Git refuses to check out the same branch in two worktrees by
default, so the call failed before any review fix could be applied.

This is the next-layer failure after #2686 (foreground/background race)
and #2839 (transactional cleanup): the isolation strategy was correct
in design, blocked only by git's same-branch protection.

Fix:
- Create a new branch 'gsd-reviewfix/${padded_phase}-$$' from the
  current branch tip and attach the worktree to it via
  'git worktree add -b "$reviewfix_branch" "$wt" "$branch"'.
- Cleanup tail is now four steps:
  1. 'git -C "$main_repo" merge --ff-only "$reviewfix_branch"'
     -- captures the agent's commits on the user's branch. --ff-only
     fails loudly on divergence (concurrent commits to $branch); the
     temp branch is preserved for manual merge.
  2. 'git worktree remove "$wt" --force'.
  3. 'git -C "$main_repo" branch -D "$reviewfix_branch"' ONLY if
     ff-only succeeded.
  4. 'rm -f "$sentinel"' last (preserves #2839 transactional ordering).
- Recovery sentinel JSON now records reviewfix_branch alongside
  worktree_path so a re-run after interruption cleans both the orphan
  worktree and the orphan temp branch.

Regression test: tests/bug-2990-code-fixer-worktree-branch.test.cjs
parses the agent .md into structured 'git worktree add' invocation
records (skipping occurrences inside markdown inline-code or bash
comments -- those are citations of the OLD pattern, not executable)
and asserts the structural invariants on the new pattern.

Closes #2990

* chore(#2990): add changeset fragment for PR #3001

* chore(#2990): add changeset fragment for PR #3001

* fix(#2990): correct main_repo parsing and ff_status capture (CR feedback)

CodeRabbit on PR #3001 caught two real bugs in the cleanup tail:

1. `awk '/^worktree / { print $2 }'` truncates paths containing
   spaces. /path/with spaces/repo becomes /path/with. Replaced with
   `sub(/^worktree /, ''); print` which strips the prefix and
   preserves the full path.

2. `if ! git merge ...; then ff_status=$?` captures the exit of the
   `!` operator (always 1 on failure), not the merge command's exit
   code. Restructured to `if cmd; then ff_status=0; else ff_status=$?`
   so the else-branch captures the real merge exit code.

Tests still pass: bug-2990 structural assertions on the agent .md
content unchanged.

* fix(#2990): recovery extracts reviewfix_branch and deletes orphan branch (CR)

CodeRabbit on PR #3001 found two issues:

1. (Major) Recovery code only extracted worktree_path from the sentinel.
   If a prior run died after `git worktree remove` but before
   `git branch -D`, the orphan reviewfix branch survived forever. The
   sentinel records reviewfix_branch (line 272) and the docs claim
   recovery deletes it, but the code didn't.
   Fixed: emit BOTH worktree_path and reviewfix_branch from the parser
   (newline-separated), capture each into shell vars, and call
   `git branch -D "$prior_branch" 2>/dev/null || true` after worktree
   removal but before sentinel deletion.

2. (Quick win) The bug-2990 test used regex .test() against the raw
   markdown, which would have been satisfied by prose mentioning the
   token. Restructured to:
   - parseCleanupGitInvocations() returns ordered records with structured
     fields (verb, targetsReviewfixBranch, isMergeFfOnly, isBranchDelete)
   - assert exactly-one merge --ff-only AND exactly-one branch -D
   - assert merge precedes branch-delete in execution order
   - parse the sentinel JSON.stringify call to extract field names and
     assert reviewfix_branch is among them

   Added 2 new tests for the recovery-block invariant: parses the recovery
   node -e block and asserts it extracts parsed.reviewfix_branch alongside
   parsed.worktree_path; and asserts the recovery shell calls
   `git branch -D "$prior_branch"`.

* test(#2990): add allow-test-rule annotation for product-text parsing (CR follow-up)

The lint-tests CI catch flagged md.match() in the new structural-IR
test suite. The .match() calls extract typed fields (cleanup-tail
git invocation records, sentinel JSON field names, recovery-block
node script content) from agents/gsd-code-fixer.md — which IS the
deployed agent product. Asserting on those typed fields tests the
runtime contract, not source code internals.

source-text-is-the-product is the correct classification per the
existing convention (matches thread-session-management.test.cjs and
the others reclassified in PR #2985's CR follow-up).

* chore(#3001): drop direct CHANGELOG.md edit; release entry now lives in .changeset/

The changeset-fragment workflow (#2975) renders fragments into
CHANGELOG.md at release time. Direct edits to [Unreleased] on
each PR caused merge conflicts on every concurrent PR. This commit
restores CHANGELOG.md to match origin/main; the release entry for
this fix is preserved in the .changeset/*.md fragment(s) on this
branch, which the release workflow consolidates.
2026-05-02 00:29:43 -04:00
Tom Boucher
4277f7d7e8 fix(#2994): move verify-reapply-patches.cjs to get-shit-done/bin/ so it ships to user installs (#3000)
* fix(#2994): move verify-reapply-patches.cjs to get-shit-done/bin/ so installer ships it

scripts/verify-reapply-patches.cjs (added in #2972 to close the
verified-yes-without-checking gap from #2969) shipped in the npm tarball
but never reached user installs: bin/install.js copies get-shit-done/
recursively but does not copy the top-level scripts/ directory.

Effect: every fresh install hit `Cannot find module …/scripts/verify-reapply-patches.cjs`
on Step 5 of /gsd-reapply-patches. The whole point of moving
verification out of LLM-driven prose into a deterministic script is
undone if the script does not resolve at runtime.

Fix: move the script to get-shit-done/bin/verify-reapply-patches.cjs
(same pattern as gsd-tools.cjs and other runtime bin scripts that the
installer ships) and update reapply-patches.md Step 5 to invoke
${GSD_HOME}/get-shit-done/bin/verify-reapply-patches.cjs.

Tests:
- bug-2969 SCRIPT path updated to the new location
- New bug-2994-verify-reapply-patches-installed-path.test.cjs parses
  reapply-patches.md into structured invocation records and asserts
  every node ${GSD_HOME}/... reference lives under get-shit-done/
  (the installed tree). Catches future regressions where someone moves
  a runtime-needed script back to scripts/.

Closes #2994

* chore(#2994): add changeset fragment for PR #3000

* chore(#2994): add changeset fragment for PR #3000

* docs(#2994): update verifier-script-location comment to reflect new path (CR)

CodeRabbit on PR #3000: the parenthetical at line 278 still said the
script ships under scripts/, but this PR moved it to get-shit-done/bin/.
Updated the prose to reference the new location and the installer
target path.

* chore(#3000): drop direct CHANGELOG.md edit; release entry now lives in .changeset/

The changeset-fragment workflow (#2975) renders fragments into
CHANGELOG.md at release time. Direct edits to [Unreleased] on
each PR caused merge conflicts on every concurrent PR. This commit
restores CHANGELOG.md to match origin/main; the release entry for
this fix is preserved in the .changeset/*.md fragment(s) on this
branch, which the release workflow consolidates.
2026-05-02 00:29:34 -04:00
Tom Boucher
cde793f1f0 fix(#2992): deterministic latest-version check — package name is a constant, not LLM choice (#2993)
* fix(#2992): deterministic latest-version check — package name is a constant, not LLM choice

The /gsd-update workflow's check_latest_version step was prescribed in
LLM-driven prose: "run `npm view get-shit-done-cc version`". The
executing model could and did shortcut the prescription and invent
npm queries against name-shaped guesses — `@get-shit-done/cli`,
`get-shit-done-cli`, `gsd` — all of which 404 or, worse, return an
unrelated typosquat (the 2016 `get-shit-done` timer package). Same
architectural anti-pattern as #2969 (Hunk Verification Gate where
the LLM filled `verified: yes` without checking).

Implementation built TDD per #2992:

  get-shit-done/bin/check-latest-version.cjs
    - PACKAGE_NAME = 'get-shit-done-cc' as a module constant; not
      parameterised, not exposed for override.
    - checkLatestVersion({ spawn? }) returns
      { ok: bool, version?: string, reason: CHECK_REASON.X, detail? }
      via a frozen enum: OK / FAIL_NPM_FAILED / FAIL_INVALID_OUTPUT.
    - --json mode emits the structured record on stdout for the
      workflow to parse via jq.
    - Windows-aware: uses { shell: process.platform === 'win32' }
      since npm is npm.cmd on Windows (same lesson as #2962).
    - Stored under get-shit-done/bin/ (not top-level scripts/) because
      that path IS in the user's installed config dir; top-level
      scripts/ ships in the npm tarball but is not copied into
      ~/.claude/ at install time.

  tests/bug-2992-check-latest-version.test.cjs
    - 7 tests, all assertions on the typed CHECK_REASON enum + the
      structured record. Injectable spawn function so no real npm
      process is invoked. Covers OK, npm-non-zero, invalid-output,
      empty-output, pre-release semver, PACKAGE_NAME constant lock,
      enum-shape lock.

  get-shit-done/workflows/update.md
    - check_latest_version step rewritten to call the script via
      `node "${GSD_HOME}/get-shit-done/bin/check-latest-version.cjs"
      --json` and parse the structured response with jq. Explicit
      "Do NOT run `npm view` or `npm search` directly" guidance
      cites #2992 so future contributors understand why.

Closes #2992

* fix(#2992): trailing slash on GSD_HOME default to satisfy bare-path lint

The bug-2470 regression test scans update.md for bare `$HOME/.claude`
references (no trailing slash). The PR added one in the new
check_latest_version step. Fix: trailing slash on the default value
(`${GSD_HOME:-$HOME/.claude/}`). Bash POSIX collapses the resulting
double slash; the lint pattern's negative lookahead is now satisfied.

* fix(#2992): emit GSD_DIR from get_installed_version, use it in check_latest_version

Addresses CodeRabbit feedback: the previous `${GSD_HOME:-$HOME/.claude/}`
fallback hardcoded the Claude runtime path, which silently breaks for
non-Claude runtimes (gemini, codex, opencode, kilo).

Fix:
- get_installed_version now emits a 4th line with the resolved config
  dir ($LOCAL_DIR or $GLOBAL_DIR), captured by callers as GSD_DIR.
- check_latest_version uses $GSD_DIR/get-shit-done/bin/check-latest-version.cjs.
  Empty GSD_DIR (UNKNOWN scope) skips the version check and falls
  through to fresh-install path.

This keeps the package name deterministic (#2992) AND respects the
detected runtime, instead of assuming Claude.

* chore(#2992): add changeset fragment for PR #2993

* chore(#2992): add changeset fragment for PR #2993

* fix(#2992): consolidate LATEST_RESULT parsing inside the GSD_DIR guard

CodeRabbit on PR #2993: the previous structure separated the GSD_DIR
guard from the jq parsing, so when GSD_DIR was empty the parsing block
ran against an unset LATEST_RESULT and produced misleading 'couldn't
check for updates' diagnostics instead of clean 'no_install_detected'.

Move all field assignments inside the conditional so the skip path
seeds LATEST_OK=false, LATEST_VERSION='', LATEST_REASON='no_install_detected',
and LATEST_STATUS=0 atomically.

* fix(#2992): emit GSD_DIR in early-return; add code-block lang and spawnSync timeout (CR)

CodeRabbit on PR #2993 caught three issues:

1. (Major) The early-return path in get_installed_version (PREFERRED_CONFIG_DIR
   fast path) only echoed 3 lines, but PR #2993 changed the contract to 4
   (GSD_DIR is now line 4). Downstream check_latest_version misread valid
   installs as UNKNOWN. Added `echo "$PREFERRED_CONFIG_DIR"` before exit 0.

2. (Minor) Markdown MD040: fenced code block at line 310 was missing a
   language identifier. Added ```text.

3. (Quick win) spawnSync('npm view ...') had no timeout, so a hung network
   could block /gsd-update indefinitely. Added 15s timeout; on timeout
   spawnSync returns with signal !== null and the existing failure path
   emits FAIL_NPM_FAILED.

* fix(#3008): kill cross-process race in install-minimal:307 mid-copy test

Old shape compared listTmpStageDirs() snapshots before/after the
mid-copy throw. Under scripts/run-tests.cjs --test-concurrency=4,
tests/install-minimal-all-runtimes.test.cjs runs in a parallel
subprocess and also creates gsd-minimal-skills-* dirs in shared
os.tmpdir(). The parallel process's create/remove activity between
this test's two snapshots caused deterministic failure when timing
aligned -- presented as 'flaky' but is a real race.

CI failure data (PR #2993 run 25238555786):
  expected (before): ['gsd-minimal-skills-km1O1O']
  actual   (after):  []

Both processes behaved correctly in isolation. The test was wrong:
it observed a shared filesystem state across processes.

Fix: stub fs.mkdtempSync inside this test to record THIS call's
stage dir path. After the throw, assert fs.existsSync(stagedDir)
=== false. Direct observation of the function's own behavior; no
global tmpdir scan; no parallel-process interference.

Closes #3008

* fix(#2992): distinguish timeout from npm failure; guard empty LATEST_RESULT (CR)

CodeRabbit on PR #2993 (post-fix-up review) caught two improvements:

1. (Low value) check-latest-version.cjs:55-61 — when spawnSync times
   out, r.status is null and r.signal is set (e.g. 'SIGTERM'), but
   r.stderr is empty. Without the signal-first branch, both timeouts
   and genuine npm failures shaped as 'npm exited non-zero' in detail,
   making logs ambiguous. Added explicit signal-first branch:
   'npm timed out (signal: SIGTERM)'.

2. (Quick win) update.md:284-315 — when node is missing or the script
   doesn't exist, LATEST_RESULT is empty. Piping empty to jq parses
   without error but leaves LATEST_OK / LATEST_REASON as empty
   strings, producing the user-visible diagnostic
   'Couldn\'t check for updates (reason: , exit: N)' with a blank
   reason. Added an explicit guard that sets LATEST_REASON to
   'script_not_found_or_node_unavailable' when LATEST_RESULT is empty,
   so operators see a meaningful failure message.

Tests: bug-2992 grows by 2 cases (timeout signal detail + empty
stderr fallback).
2026-05-02 00:29:31 -04:00
Tom Boucher
ffeeb92c14 fix(#2997): mask SECRET_CONFIG_KEYS in SDK config-set/get and init responses (#2999)
* fix(#2997): mask SECRET_CONFIG_KEYS in SDK config-set/get and init responses

The CJS→TS port at sdk/src/query/config-mutation.ts:240,243 and
config-query.ts:122,128,132 dropped the masking layer that secrets.cjs
spec defines for brave_search/firecrawl/exa_search. Result: the SDK
echoed plaintext API keys into machine-readable JSON output (stdout,
transcripts, CI logs).

Adjacent leak in init.ts:673-675 / init.cjs:728-730: the init bundle
passed config.brave_search through raw, leaking the API key whenever
the user had stored one.

Fix:
- New sdk/src/query/secrets.ts ports SECRET_CONFIG_KEYS, isSecretKey,
  maskSecret, maskIfSecret. Exact CJS parity (verified by 17 tests
  in secrets.test.ts that import secrets.cjs and compare).
- config-set masks value + previousValue in response; on-disk plaintext
  intact (key stays usable).
- config-get masks read response. --default flows through unmasked
  (user's own input, not stored secret).
- init.ts/init.cjs mask string values only; booleans (availability
  flags) pass through unchanged so the typed contract is preserved.

Tests: 17 in secrets.test.ts (including CJS parity), 5 in
config-mutation.test.ts (#2997 block — covers on-disk-preserved,
previousValue masking, short-value, unset, non-secret pass-through),
4 in config-query.test.ts.

Closes #2997

* chore(#2997): add changeset fragment for PR #2999

* chore(#2997): add changeset fragment for PR #2999

* chore(#2999): drop direct CHANGELOG.md edit; release entry now lives in .changeset/

The changeset-fragment workflow (#2975) renders fragments into
CHANGELOG.md at release time. Direct edits to [Unreleased] on
each PR caused merge conflicts on every concurrent PR. This commit
restores CHANGELOG.md to match origin/main; the release entry for
this fix is preserved in the .changeset/*.md fragment(s) on this
branch, which the release workflow consolidates.
2026-05-02 00:17:45 -04:00
Tom Boucher
4e378d37d8 fix(#3008): kill cross-process race in install-minimal:307 mid-copy test (#3009)
Old shape compared listTmpStageDirs() snapshots before/after the
mid-copy throw. Under scripts/run-tests.cjs --test-concurrency=4,
tests/install-minimal-all-runtimes.test.cjs runs in a parallel
subprocess and also creates gsd-minimal-skills-* dirs in shared
os.tmpdir(). The parallel process's create/remove activity between
this test's two snapshots caused deterministic failure when timing
aligned -- presented as 'flaky' but is a real race.

CI failure data (PR #2993 run 25238555786):
  expected (before): ['gsd-minimal-skills-km1O1O']
  actual   (after):  []

Both processes behaved correctly in isolation. The test was wrong:
it observed a shared filesystem state across processes.

Fix: stub fs.mkdtempSync inside this test to record THIS call's
stage dir path. After the throw, assert fs.existsSync(stagedDir)
=== false. Direct observation of the function's own behavior; no
global tmpdir scan; no parallel-process interference.

Closes #3008
2026-05-01 22:37:48 -04:00
Tom Boucher
9f09246f3b fix(#2998): populate gsd-pristine/ from install transform pipeline so verifier has a real baseline (#3004)
* fix(#2998): populate gsd-pristine/ from install transform pipeline so verifier has a real baseline

saveLocalPatches declared a pristineDir variable and JSDoc'd 'saves
pristine copies to gsd-pristine/' but no code ever wrote there. Effect:
/gsd-reapply-patches Step 5 verifier (#2972) silently fell back to its
over-broad heuristic ('every significant backup line') -- exactly the
silent-success-on-lost-content failure mode #2969 was designed to
prevent.

Fix: new populatePristineDir({...}) helper runs copyWithPathReplacement
(the install transform pipeline) into a tmp staging dir, then copies out
only the modified-file paths into gsd-pristine/. saveLocalPatches now
accepts a pristineCtx and calls the helper when local patches are
detected. Soft-fails on transform errors (logs warning, continues with
empty pristine -- no worse than pre-fix).

Pristine reflects the about-to-install version's content, which is the
right baseline for 'what would survive without the user's modifications'.

Tests: bug-2998-pristine-dir-populated.test.cjs asserts the helper is
exported, no-ops on empty input, writes one pristine file per source-
existing path, skips ghost paths, and produces deterministic output
(byte-identical across runs -- the property pristine_hashes depends on).

Closes #2998

* chore(#2998): add changeset fragment for PR #3004

* fix(#2998): expand pristine to all manifest install roots; clear stale pristine on populate (CR)

CodeRabbit on PR #3004 caught two issues:

1. populatePristineDir only staged packageSrc/get-shit-done/ but
   manifest.files records edits under several install roots (commands/,
   agents/, hooks/, skills/, root files like .clinerules). Modified
   paths outside get-shit-done/ were silently skipped, leaving the
   verifier with no baseline for those edits. Fixed by computing the
   set of top-level dirs from the modified set and staging each one
   that exists in source. Root-level files (no slash) bypass the
   transform pipeline and are copied directly.

2. populatePristineDir did not wipe pre-existing gsd-pristine/ before
   populating. A previous run's stale pristine could survive into the
   current run's diff baseline. Now wipe before populate AND in the
   catch path so soft-failures don't leave half-populated data on disk.

Tests: bug-2998-pristine-dir-populated.test.cjs grows by 2 cases:
- agents/ paths are staged and copied (was silently skipped pre-fix)
- mixed get-shit-done/ + agents/ in same modified list both stage
2026-05-01 21:14:14 -04:00
Tom Boucher
c2ada7e799 feat(#2995): post-install path audit for workflow-invoked scripts (#2996)
* feat(#2995): post-install path audit for workflow-invoked scripts

Catches the gap class surfaced by #2994: a workflow references a script
via ${GSD_HOME}/<path> that ships in the npm tarball but is not copied
to the user's config dir at install time. Unit tests don't catch it
because they resolve the script via path.join(__dirname, '..', 'scripts',
…) — the source layout, not the deployed layout.

Implementation built TDD per #2995, vertical slices with structured-IR
assertions:

  scripts/audit-workflow-script-paths.cjs
    - Pure auditWorkflowScriptPaths({ workflowsDir, repoRoot,
      installedPrefixes }) returns { ok, findings: [{ workflow, path,
      kind }] } via the AUDIT_FINDING enum.
    - Two finding kinds: MISSING_FROM_REPO (typo / file deleted) and
      NOT_INSTALLED (#2994 class — first segment outside installed
      prefixes).
    - Tolerates ${GSD_HOME:-...} default-fallback syntax.

  tests/bug-2995-post-install-script-paths.test.cjs
    - 9 tests across 3 suites:
      • Pure-function pass and per-finding-kind detection (5 tests on
        synthetic fixtures).
      • Real workflow audit (2 tests asserting the actual repo's
        get-shit-done/workflows/ has no NEW gaps and KNOWN_GAPS stays
        consistent with audit findings).
      • Enum shape lock + extractReferences edge cases.
    - All assertions on typed AUDIT_FINDING enum / structured records;
      zero raw text matching.
    - KNOWN_GAPS is a Set keyed on `workflow|path|kind` strings;
      currently contains the #2994 entry. The companion test fails if
      a KNOWN_GAPS entry no longer matches a real finding (forces the
      allow-list to shrink as gaps fix).

The audit immediately catches #2994's gap on `reapply-patches.md`. The
allow-list contains exactly that entry; new gaps fail CI; #2994's fix
will remove the entry as part of the same PR.

Closes #2995
Refs #2994

* chore(#2995): add changeset fragment for PR #2996

* chore(#2995): add changeset fragment for PR #2996

* fix(#2995): emit both NOT_INSTALLED + MISSING_FROM_REPO; clean up fixture leak (CR)

CodeRabbit on PR #2996 found two issues:

1. (Low value) auditWorkflowScriptPaths short-circuited on NOT_INSTALLED,
   masking MISSING_FROM_REPO for the same ref. Removed the `continue` so
   both findings emit in one run; added a regression test.

2. (Low value) bug-2995 test created tmpRoot in before() but never wrote
   into it; per-fixture mkdtempSync dirs leaked. Rooted fixture repos
   under tmpRoot so the after() cleanup actually frees them.
2026-05-01 21:13:45 -04:00
Tom Boucher
55ae8e42d2 test(#2986): mutation-killer suite for config-schema.cjs (95 typed assertions) (#3005)
* test(#2986): mutation-killer suite for config-schema.cjs (95 typed assertions)

Stryker measured 4.62% mutation score on config-schema.cjs (6 killed,
124 survived). Surviving mutants documented that existing tests were
exercising paths without verifying outputs.

Adds tests/bug-2986-config-schema-mutation-killers.test.cjs (95 tests,
4 suites) targeting each surviving mutant class:

- M1/M4: parameterized isValidConfigKey(key) === true for every member
  of VALID_CONFIG_KEYS. Kills static-key-fast-path mutations
  (if (VALID_CONFIG_KEYS.has(...)) return true; -> if (false) return true;)
  because no static key matches any DYNAMIC_KEY_PATTERN by design.

- M2: representative dynamic-pattern keys (one per pattern). Each matches
  exactly one pattern. Kills .some -> .every mutation: with .every, no
  single key matches all patterns -> all dynamic keys would be rejected.

- M3: strictEqual against the literal boolean true/false (not assert.ok
  truthy checks). Kills polarity-flip mutations.

- Anchor-tightening: keys that differ from valid by one char beyond the
  documented shape (trailing dot-segment, empty agent name, non-enum tier,
  etc.). Kills regex-loosening mutations on ^, $, charset boundaries.

Tests assert on typed boolean return values from the lib's public surface.
Zero source-grep, zero raw-text matching.

* chore(#2986): add changeset fragment for PR #3005

* test(#2986): use dynamic-only rep key for features pattern (CR feedback)

CodeRabbit on PR #3005: features.thinking_partner is in the static
VALID_CONFIG_KEYS set, so the static fast-path returns true before
DYNAMIC_KEY_PATTERNS.some() is ever called. A Stryker mutant that
removed only the features entry from DYNAMIC_KEY_PATTERNS would
survive because the test only ever exercised the static path for
that key.

Replaced features.thinking_partner with features.some_dynamic_feature
which is NOT in static keys, so isValidConfigKey must reach the
dynamic path to return true. Added a per-rep invariant that asserts
each representative key is NOT a member of VALID_CONFIG_KEYS,
catching this class of mistake at test time on any future
representative-key change.
2026-05-01 21:13:25 -04:00
Tom Boucher
3657c4ea9e fix(#3006): retarget PR-template CHANGELOG checkboxes at the changeset workflow (#3007)
The three PR templates still asked contributors to tick `CHANGELOG.md
updated`, contradicting the post-#2978 rule (documented in
CONTRIBUTING.md and enforced by scripts/changeset/lint.cjs) that
`CHANGELOG.md` must not be edited directly.

Each checkbox now references `npm run changeset` with the appropriate
`--type` (Fixed/Changed/Added) and notes the `no-changelog` opt-out
label where applicable, so `gh pr create` users land in the correct
workflow by copy-paste.

Closes #3006

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 20:01:04 -04:00
Tom Boucher
918f987a19 feat(#2982): extend no-source-grep lint to catch var-binding readFileSync.includes() (#2985)
* feat(#2982): extend no-source-grep lint to catch var-binding readFileSync.includes()

The base lint (scripts/lint-no-source-grep.cjs) only catches
readFileSync(...).<text-method>() chained directly. The much more
common var-binding form escapes it:

  const src = fs.readFileSync(p, 'utf8');
  // 50 lines later
  if (src.includes('foo')) {}        // ← still grep, lint missed it

Scan of the test suite found ~141 files using this pattern.

Implementation built TDD per #2982 with structured-IR assertions:

  scripts/lint-no-source-grep-extras.cjs
    - detectVarBindingViolations(src) — pure detector, two passes:
      pass 1 collects vars bound from readFileSync, pass 2 finds any
      <var>.<includes|startsWith|endsWith|match|search>( on those vars.
    - detectWrappedAssertOkMatch(src) — flags
      assert.ok(<expr>.match(...)) which escapes the assert.match rule.
    - VIOLATION enum exposes stable codes for tests to assert on.

  scripts/lint-no-source-grep.cjs
    - Wires the new detectors into the existing per-file check; one
      additional violation row per file with the first 3 sample tokens.

  tests/bug-2982-lint-var-binding.test.cjs
    - 13 tests, all assertions on typed VIOLATION enum / structured
      records. Covers all 5 text-match methods, multi-var, no-bind,
      string literal (must NOT trigger), wrapped assert.ok(.match),
      and assert.match (must NOT double-flag).

Migration backlog (#2974 expanded scope):

  - 42 files annotated `// allow-test-rule: source-text-is-the-product`
    (legitimate — they read .md/.json/.yml files whose deployed text
    IS the product)
  - 3 files annotated `// allow-test-rule: pending-migration-to-typed-ir [#2974]`
    (read .cjs/.js source — clear migration debt)
  - 95 files annotated `pending-migration-to-typed-ir [#2974]` with
    `Per-file review may reclassify as source-text-is-the-product
    during migration` (mixed — manual review under #2974)

After this lands the lint reports 0 violations on main; new
violations in PRs surface immediately.

Closes #2982
Refs #2974

* test(#2982): fix truncated test name per CR

The label ended with a bare '(' from a copy-paste mishap. Now reads
'does NOT flag .matchAll(...) — matchAll is not match, so
assert.ok(.matchAll(...)) is not flagged'.

* chore(#2982): add changeset fragment for PR #2985

* chore(#2982): add changeset fragment for PR #2985
2026-05-01 19:50:10 -04:00
Tom Boucher
17a4321bf5 docs(#2989): promote v1.39.1 hotfix entries from [Unreleased] to dated section (#2991)
Both v1.39.0 (stable, tagged 2026-05-01T03:05:33Z) and v1.39.1
(hotfix, tagged 2026-05-01T21:03:54Z) shipped to npm but the
CHANGELOG `[Unreleased]` link still pointed at `v1.38.5...HEAD` and
the entries that landed in v1.39.1 were still un-promoted.

Move the five v1.39.1 hotfix entries (#2917, #2949, #2954, #2962,
#2969) into a new `## [1.39.1] - 2026-05-01` section above
`## [1.38.5]`, with a one-line intro and install snippet matching
the conventions used in earlier dated sections.

Update the `[Unreleased]` link to point at `v1.39.1...HEAD`.

Out of scope (separate cleanup):
  - Backfilling a `## [1.39.0]` section. The CHANGELOG never had one;
    this PR doesn't make that worse but also doesn't try to invent
    release-note text from commit messages.
  - The eight v1.39.1 commits without `[Unreleased]` entries
    (#2942, #2944, #2924/#2941, #2940, #2947, #2950, #2948, #2957).
    These weren't in `[Unreleased]` to begin with; faithful
    promotion only moves what was already documented.
  - Adding a `docs/RELEASE-v1.39.1.md` file. The `docs/RELEASE-*.md`
    pattern in this repo is RC-only; stable patches historically
    don't have a counterpart.

The post-v1.39.1 hardening entries (#2980, #2983, #2987 from this
session, plus #2976 which was pre-skipped from the v1.39.1
cherry-pick set after #2980 landed) remain in the new
`[Unreleased]` section — they ship in the next release.

Closes #2989
2026-05-01 18:21:09 -04:00
Tom Boucher
9d5db87249 feat(#2975): adopt changeset-fragment workflow to eliminate CHANGELOG conflicts (#2978)
* feat(#2975): adopt changeset-fragment workflow to eliminate CHANGELOG conflicts

Two PRs that both edit `### Fixed` in CHANGELOG.md always conflict on merge.
Recently bit on #2960/#2972 in the same session — fix-the-conflict-and-rebase
tax. Replace the shared-file model with per-PR fragment files that never
share lines.

Implementation built TDD per #2975, vertical slices with structured-IR
assertions throughout:

  scripts/changeset/parse.cjs       - fragment text → typed record + frozen
                                      FRAGMENT_ERROR enum (8 tests)
  scripts/changeset/render.cjs      - fragments → structured IR with
                                      Keep-a-Changelog section ordering
                                      (2 tests)
  scripts/changeset/serialize.cjs   - IR ↔ markdown round-trip pair
                                      (parse(serialize(ir)) === ir,
                                      3 tests)
  scripts/changeset/cli.cjs         - file-I/O wrapper with --json mode;
                                      reads .changeset/, folds into
                                      CHANGELOG.md, deletes consumed
                                      fragments. Idempotent. (1 test)
  scripts/changeset/lint.cjs        - pure verdict (changedFiles, labels)
                                      → { ok, reason } via LINT_REASON
                                      enum. Honors `no-changelog` label.
                                      (5 tests)
  scripts/changeset/new.cjs         - fragment scaffolder with random
                                      adjective-noun-noun filename. Tests
                                      assert via parseFragment round-trip.
                                      (3 tests)

Total: 22 tests, all assertions on typed structured fields. No regex on
text, no String#includes on file content. Lint clean across 356 test files.

Supporting:

  .changeset/README.md              - format spec + workflow docs
  .changeset/eager-hawks-rally.md   - dogfood fragment for THIS PR (will
                                      be the first thing the new release
                                      tool consumes)
  .github/workflows/changeset-required.yml
                                    - CI: every PR runs lint.cjs
  package.json                      - npm run changeset, changelog:render,
                                      lint:changeset
  CONTRIBUTING.md                   - new "CHANGELOG Entries — Drop a
                                      Fragment" section between PR
                                      Guidelines and Testing Standards

Closes #2975

* fix(#2975): address CodeRabbit findings on changeset workflow

7 valid findings (4 Major, 3 Minor); all addressed:

scripts/changeset/parse.cjs
  - Preserve fragment body verbatim. Previously body.trim() ate
    intentional leading whitespace (code blocks, etc.); now trim() is
    used only for the emptiness check, and a single trailing newline
    is stripped (the editor-added one) so well-formed fragments
    round-trip byte-for-byte. Added a regression test asserting a
    code-block-leading body is preserved.

scripts/changeset/cli.cjs
  - Validate flag values during argument parsing. parseArgs now returns
    { ok, opts | error }; rejects `--repo` etc. with no following value
    or with another flag as the value. main() surfaces the error
    message before exiting 2.
  - Handle post-write fragment-deletion failures. After CHANGELOG.md
    is written, any unlink failure is captured into a structured
    deleteFailures list with reason 'fail_fragment_delete'; cmdRender
    returns exitCode=1 with the partial-failure detail instead of
    leaving the changelog updated and fragments behind (which would
    cause double-consumption on rerun).

scripts/changeset/lint.cjs
  - Treat CHANGELOG.md as a linted user-facing path. Direct edits to
    CHANGELOG.md (the bypass route around the new workflow) now fail
    the lint with FAIL_MISSING_FRAGMENT. Added a regression test for
    that case.
  - Use cp.execFileSync instead of cp.execSync for the git diff call.
    Eliminates the shell-interpolation surface on GITHUB_BASE_REF;
    git's own arg parser remains the validator.

scripts/changeset/new.cjs
  - Atomic fragment creation. existsSync() + writeFileSync was racy
    under concurrent invocations. Now writeFileSync uses { flag: 'wx' }
    which fails EEXIST on collision; the random-name retry loop
    catches EEXIST and re-rolls. Throws explicitly after 16 attempts
    rather than silently overwriting.

.changeset/README.md
  - Add language tag `md` to the format example fence (markdownlint
    MD040).

All 25 changeset tests pass; lint clean (356 test files, 0 violations).

* fix(#2975): sanitize --type and validate flag values in new.cjs (CR fixes)

Two CR findings on scripts/changeset/new.cjs:

1. (Minor) `type` was embedded in frontmatter without sanitization. A
   newline in the value (e.g. `--type 'Fixed\ntype: Added'`) would
   corrupt the fragment. scaffoldFragment now validates `type` against
   the Keep-a-Changelog ALLOWED_TYPES set BEFORE writing — same set
   parse.cjs uses on consume. Throws with a typed error referencing
   the allowed values; tests cover the newline case + 4 other
   non-allowed values.

2. (Minor) `--repo` (and other value-taking flags) without a value
   silently set opts.repo to undefined, which produced a cryptic
   ERR_INVALID_ARG_TYPE deep inside path.join. parseArgs now mirrors
   the cli.cjs convention: returns { ok, opts | error }, validates
   that the next token exists and is not itself another flag, and
   surfaces a precise "missing value for --repo" message before exit.
   Added 3 tests: missing-trailing-value, flag-as-value, well-formed.

29 tests pass across the changeset suite (4 new regression tests).
2026-05-01 18:12:20 -04:00
Tom Boucher
cb98a88139 fix(#2987): skip dry-run publish validation when version is already on npm (#2988)
The `Dry-run publish validation` step ran `npm publish --dry-run` with
no `if:` guard. `npm publish --dry-run` contacts the registry and
exits 1 with "You cannot publish over the previously published
versions" when the target version exists.

The earlier `Detect prior publish (reconciliation mode)` step already
discovers this case and sets steps.prior_publish.outputs.skip_publish=true.
The actual publish step (further down) is gated on that. The
rehearsal step was missing the gate, so any re-run of an
already-published hotfix blew up at the rehearsal before reaching
the reconciliation logic — exactly when an operator is trying to
recover from a later-step failure (merge-back, summary, etc.).

Add `if: ${{ steps.prior_publish.outputs.skip_publish != 'true' }}`
matching the publish step's gate. The rehearsal still runs on first
publishes where it has value.

Trigger: run 25233855236.

Closes #2987
2026-05-01 17:39:35 -04:00
Tom Boucher
fb92d1e596 fix(#2983): classifier exit-code discipline, base-tag staging, drop vestigial merge-back (#2984)
* fix(#2983): classifier exit-code discipline, base-tag staging, drop vestigial merge-back

Three issues surfaced by CodeRabbit's post-merge review of #2981 plus
a production failure on the v1.39.1 release run.

(1) Overloaded classifier exit code

scripts/diff-touches-shipped-paths.cjs reused exit 1 for both the
legitimate "no shipped paths" result and Node's default exit on
uncaught throw, so any classifier failure (corrupt package.json,
EPERM, etc.) was indistinguishable from a normal skip — the workflow's
`if ! ... ; then skip` idiom would silently drop the commit.

Distinct exit codes now:
  0  shipped       — at least one path is in the npm `files` whitelist
  1  not shipped   — CI / test / docs / planning only
  2  classifier error — workflow MUST fail-fast

uncaughtException + unhandledRejection + try/catch around fs/JSON
parsing all route to exit 2 with stderr context.

(2) Classifier missing at the base tag (CRITICAL)

`Prepare hotfix branch` runs `git checkout -b "$BRANCH" "$BASE_TAG"`
BEFORE the cherry-pick loop, replacing the working tree with the base
tag's contents. Base tags predating #2980 (notably v1.39.0, the most
likely next hotfix base) don't have scripts/diff-touches-shipped-paths.cjs
at all — `node <missing>` exits non-zero — `if !` skips every commit —
empty hotfix branch published. Strictly worse than the original #2980
push-rejection, which at least failed loudly.

Stage the classifier from the dispatched ref's working tree into
$RUNNER_TEMP at the top of the run script (before any working-tree-
mutating git command). The cherry-pick loop now references $CLASSIFIER
(staged) instead of the in-tree path. Sanity guards: refuse to start
if scripts/diff-touches-shipped-paths.cjs is missing in the dispatched
ref, refuse to proceed if cp didn't materialize $CLASSIFIER.

The cherry-pick loop captures node's exit via ${PIPESTATUS[1]} and
dispatches via explicit case:
  0  proceed with cherry-pick
  1  skip into NON_SHIPPED_SKIPPED
  *  emit ::error:: + exit "$CLASSIFIER_RC"

(3) Drop the merge-back PR step

Auto-cherry-pick only picks commits already on main (`git cherry HEAD
origin/main` outputs the unmerged ones; we filter fix:/chore: from
main). By construction every code commit on the hotfix branch is
already on main. The only hotfix-branch-only commit is `chore: bump
version to X.Y.Z for hotfix`, which either no-ops against main or
rewinds main's in-progress version. The merge-back PR was vestigial.

It also failed in production on run 25232968975 with `GitHub Actions
is not permitted to create or approve pull requests (createPullRequest)`
— org policy blocks PR creation from the workflow's GH_TOKEN. Even
without that block, the PR would have nothing useful to merge.

Step removed. The `pull-requests: write` permission granted solely
for the merge-back step has been dropped from the release job
(least-privilege).

Regression coverage

tests/bug-2983-classifier-exit-codes-and-base-tag-staging.test.cjs
adds 12 assertions across two describe blocks:

  - 5 classifier behavioral: exit 0/1 preserved, exit 2 on missing
    package.json, exit 2 on malformed JSON, exit-code constants
    exported.
  - 7 workflow contract: classifier staged before checkout, target
    is $RUNNER_TEMP, missing-source guard, missing-staged guard,
    PIPESTATUS-based dispatch, error branch fails workflow, loop uses
    staged path (not in-tree).

tests/bug-2980-hotfix-only-picks-shipping-changes.test.cjs updated
where it asserted the pre-#2983 `if ! ... ; then` shape: now accepts
the post-#2983 case-dispatch form. The test still proves the
classifier participates; bug-2983 enforces the specific shape.

Run summary references for the curious reviewer:

  - Run 25232010071 — original #2980 trigger (workflow-file push
    rejection)
  - Run 25232968975 — failed merge-back step that prompted the
    "is this even useful?" question that drove the removal

Closes #2983

* fix(#2983): address CodeRabbit findings on PR #2984

Two findings, both real, both fixed.

(1) [Critical] PIPESTATUS capture clobbered by `|| true`

Pre-fix shape:
  git diff-tree ... | node "$CLASSIFIER" || true
  CLASSIFIER_RC="${PIPESTATUS[1]}"

When the classifier exits 1 ("not shipped" — common case) or 2
(error), `|| true` triggers the right-hand side. `true` is a
one-command "pipeline" that overwrites PIPESTATUS to (0).
${PIPESTATUS[1]} on the next line is therefore unset (or stale
under set -u). The case dispatch then matched the empty string —
falling into `*)` and failing the workflow on every non-shipped
commit, OR matching `0)` after some shells default-init unset
to 0 and silently picking commits that don't ship.

Local repro confirms the issue:

  $ bash -c 'set -euo pipefail; false | sh -c "exit 7" || true; \
       echo "PIPESTATUS: ${PIPESTATUS[*]}"; \
       echo "[1]: ${PIPESTATUS[1]:-<unset>}"'
  PIPESTATUS: 0
  [1]: <unset>

Fix: bracket the pipeline in `set +e`/`set -e`, snapshot
PIPESTATUS into a local array on the very next line, then
dispatch on the snapshot:

  set +e
  git diff-tree ... | node "$CLASSIFIER"
  PIPE_RC=("${PIPESTATUS[@]}")
  set -e
  DIFFTREE_RC="${PIPE_RC[0]}"
  CLASSIFIER_RC="${PIPE_RC[1]}"

The snapshot must happen on the first line after the pipeline;
any intervening simple command resets PIPESTATUS. The array form
is invariant against that.

Bonus from the new shape: $DIFFTREE_RC is now also captured.
git diff-tree is unlikely to fail on a known-good $SHA, but if
it does, we no longer feed partial/empty input to the classifier
and call it "not shipped." A non-zero DIFFTREE_RC emits
::error::git diff-tree failed and exits.

(2) [Minor] Stale "Merge-back PR opened against main" summary line

The hotfix run summary still printed:
  echo "- Merge-back PR opened against main"

But the merge-back step itself was removed in the previous commit
on this branch. Operators reading the summary would expect a PR
that doesn't exist. Replaced with explicit non-action text:

  echo "- No merge-back PR (auto-picked commits are already on main)"

Test coverage

bug-2983 test file gains 3 assertions:
  - PIPE_RC array-snapshot pattern is required (regex matches the
    exact `PIPE_RC=("${PIPESTATUS[@]}")` form).
  - The `pipeline || true; ${PIPESTATUS[1]}` antipattern is
    explicitly forbidden via assert.doesNotMatch.
  - DIFFTREE_RC is captured from PIPE_RC[0] and a non-zero value
    triggers ::error::git diff-tree failed.
  - Run summary forbids `Merge-back PR opened against main` and
    requires the new non-action sentence.

bug-2964 test's loop-anchor window bumped 6 KB → 8 KB to
accommodate the additional pre-pick scaffolding (the test's own
comment had already anticipated this kind of growth, citing prior
precedents from #2970 and #2980).

Mark CodeRabbit comments resolved post-commit.

Refs CR finding ids 3175253571, 3175253578 on PR #2984.
2026-05-01 17:25:20 -04:00
Tom Boucher
7424271aa0 fix(#2980): hotfix cherry-pick only picks commits that change what ships (#2981)
* fix(#2980): pre-skip workflow-file cherry-picks in release-sdk hotfix loop

The default GITHUB_TOKEN issued to the release-sdk run lacks the
`workflow` scope, so the prepare job's `git push origin "$BRANCH"` is
rejected by GitHub when any cherry-picked commit modifies a file under
`.github/workflows/`:

  ! [remote rejected] hotfix/X.YY.Z -> hotfix/X.YY.Z
    (refusing to allow a GitHub App to create or update workflow ...
     without `workflows` permission)

Pre-#2980 behavior: the auto_cherry_pick loop happily picked
workflow-file commits, then the trailing push exploded with no clear
signal which commit was the culprit. v1.39.1 hit this on PR #2977
(run 25232010071) — earlier release-sdk fixes (#2965, #2967, #2970)
had been skipped on conflict so their workflow-file changes never
reached the push step, masking the bug; #2977 was the first
workflow-file commit to apply cleanly and the push immediately
exploded.

Fix: pre-pick guard in the cherry-pick loop. Inspect each candidate
commit's file list via `git diff-tree --no-commit-id --name-only -r`
BEFORE attempting the pick. If any path matches `^\.github/workflows/`,
skip the commit, emit a `::warning::` annotation naming the dropped
commit, and append to a new `WORKFLOW_SKIPPED` bucket. The run summary
surfaces this bucket in its own section, distinct from `CONFLICT_SKIPPED`
(real merge conflicts) and `POLICY_SKIPPED` (feat/refactor exclusions),
so operators reviewing the run never confuse the remediation paths.

The loud-warning piece is non-negotiable: silent drops were explicitly
rejected as a failure mode during the option-1/2/3 tradeoff discussion.
If a workflow-file fix genuinely needs to ship in a hotfix, the
operator applies it manually on the hotfix branch using a token with
`workflow` scope, or lands it on main and re-cuts the release.

Regression covered by tests/bug-2980-skip-workflow-file-cherrypicks.test.cjs
(5 assertions: pre-pick guard exists, uses `git diff-tree`, emits
`::warning::`, lands in dedicated bucket, surfaces in summary).

The bug-2964 test's 4 KB window after the cherry-pick-loop anchor was
nudged to 6 KB to accommodate the new pre-pick scaffolding — the test's
own comment had already anticipated this kind of growth (citing #2970's
merge-commit pre-skip as prior precedent).

Closes #2980

* refactor(#2980): replace workflow-file pre-skip with shipped-paths filter

The previous commit on this branch caught only the .github/workflows/*
subset of the bug, treating the symptom (push rejection on workflow-file
changes) rather than the root cause (the fix:/chore: filter is too broad
— it picks any commit with that conventional-commit type even when the
diff cannot affect the published npm package).

CI-only fixes (release-sdk.yml itself, hotfix tooling, test-only
commits) shouldn't flow through hotfix runs at all — they cannot change
what `npm install get-shit-done-cc@X.YY.Z` produces. The
.github/workflows/* push rejection is just the loudest of these
"shouldn't have been picked" cases; tests/, docs/, .planning/ commits
get picked silently with the same lack of effect on consumers.

Replace the workflow-file pre-skip with a shipped-paths filter:

- New scripts/diff-touches-shipped-paths.cjs reads package.json `files`,
  plus package.json itself (always-shipped per `npm pack` semantics),
  and exits 0 iff any input path is in the shipped set. Lockfile is
  not shipped (npm pack excludes it unless explicitly in `files`).
- Workflow loop now pipes `git diff-tree --no-commit-id --name-only -r`
  through the classifier; on exit 1 the commit is skipped and
  appended to a new NON_SHIPPED_SKIPPED bucket (replaces
  WORKFLOW_SKIPPED).
- Run summary surfaces NON_SHIPPED_SKIPPED as informational — no
  ::warning:: annotation. A non-shipping commit cannot affect the
  package, so a yellow alert would imply remediation is possible
  and would mislead operators.

The classifier in a separate .cjs file (rather than inline bash
heredoc) is so its rules — directory-prefix vs exact-match,
package.json-always-shipped, lockfile-not-shipped — are unit-testable
in tests/bug-2980-hotfix-only-picks-shipping-changes.test.cjs (11 new
assertions: 4 static workflow + 6 classifier behavioral + 1 mixed-
diff edge case).

Why this dissolves the original push-rejection bug: workflow files
aren't in `files`, so workflow-only commits are skipped pre-pick.
The push step never sees them.

If a workflow-file fix genuinely needs to ship in a hotfix release
(extremely rare — the hotfix workflow is read from main's ref, not
the hotfix branch's), the operator applies it manually using a token
with `workflow` scope. The pre-skip puts that requirement in the run
summary explicitly.

Closes #2980
2026-05-01 16:59:49 -04:00
Tom Boucher
7a416b10d4 fix(#2976): allow same-version bump in release-sdk hotfix release job (#2977)
The release job's "Bump in-tree version (not committed)" step ran
`npm version "$VERSION" --no-git-tag-version` without --allow-same-version,
so on real hotfix runs it failed with `npm error Version not changed` —
because the prepare job had already committed the bump on the hotfix
branch (the release job checks out BRANCH on real runs vs BASE_TAG on
dry-runs, which is why dry-run never caught it).

Pass --allow-same-version to both bumps, matching release.yml:326.

Closes #2976
2026-05-01 16:32:18 -04:00
Tom Boucher
ef43f5161f fix(#2969): deterministic Step 5 verification gate for /gsd-reapply-patches (#2972)
* fix(#2969): deterministic Step 5 verification gate for /gsd-reapply-patches

The prior Step 5 "Hunk Verification Gate" was prescribed correctly in the
workflow text — but executed laxly by the LLM, which filled in `verified: yes`
without actually checking content presence. The reporter observed three
distinct files (skills/gsd-discuss-phase/SKILL.md, skills/gsd-autonomous/
SKILL.md, get-shit-done/workflows/new-project.md) where archives contained
substantive user-added blocks that did not survive into the merged result, yet
the gate reported clean.

Move verification from LLM-driven prose into a deterministic Node script the
workflow calls. The script can't be shortcut.

Changes:

- scripts/verify-reapply-patches.cjs (new): pure Node, no external deps.
  For each file in the patches dir, computes user-added significant lines as
  the line-set diff between backup and pristine baseline (when available;
  falls back to "every significant backup line" when no pristine — over-broad
  but the safe direction for this bug class). Asserts each line appears
  literally in the merged installed file via String.prototype.includes.
  Filters trivial lines (length < 12 chars, pure punctuation, decorative
  comments) so harmless drift doesn't trigger false failures. Exits 0 on
  pass, 1 on any miss with per-file diagnostic, 2 on usage error.
  Supports --json for workflow consumption.

- get-shit-done/workflows/reapply-patches.md: rewrite Step 5 to call the
  script and parse its JSON output. The Step 4 Hunk Verification Table
  remains as advisory Claude-readable summary, but the gate is now the
  script's exit code.

- tests/bug-2969-verify-reapply-patches.test.cjs (new): 6 tests covering
  (a) pass when every line survives, (b) fail when a line is missing,
  (c) fail when the merged file is deleted entirely, (d) --json structured
  report shape, (e) backup-meta.json is correctly skipped as metadata,
  (f) no-pristine-dir fallback exercises the safe over-broad path. All pass.

Out of scope: the manifest-baseline tightening described in #2969 Failure 1
(saveLocalPatches comparing against the wrong baseline so prior silent wipes
poison subsequent updates). That's a separate, bigger architectural change
involving pristine-content infrastructure; this PR addresses the gate fidelity
half so users at least see the diagnostic when content goes missing.

Closes #2969 (partial — Failure 2 only)

* fix(#2969): preserve #1999 Hunk Verification Table assertions alongside new script gate

CI failure on PR #2972 surfaced that tests/reapply-patches.test.cjs (the
#1999 contract) asserts Step 5 references:
  - "Hunk Verification Table"
  - `verified: no` failure condition
  - explicit STOP/halt/abort directive
  - "table absent / missing" halt path

My initial Step 5 rewrite for #2969 substituted the deterministic script
for the table-based gate entirely, stripping those references. The script
is the strictly stronger gate, but the existing #1999 test enforces the
table-based safety net as a defense-in-depth contract.

Restore both gates as a layered Step 5:

  - 5a (binding): deterministic verifier script — script gate, exits
    non-zero on any miss, cannot be shortcut by the LLM
  - 5b (advisory): Hunk Verification Table review — preserved as
    redundant safety net for the case where the script has a bug or the
    pristine baseline is unavailable

Both gates must pass. Verified: tests/reapply-patches.test.cjs (5 tests
in the #1999 suite) and tests/bug-2969-verify-reapply-patches.test.cjs
(6 tests in the #2969 suite) all pass — 21/21 total in this fixture.

* fix(#2969): address CodeRabbit findings on workflow + script

Five CR findings on PR #2972, all valid; addressed in this commit:

1. (Major) Stderr was merged into VERIFY_OUTPUT via `2>&1`, so any Node
   warning, deprecation notice, or stack trace would corrupt the JSON
   parse downstream. Capture stdout only; stderr remains on the
   controlling terminal for operator visibility.

2. (Major) verifyFile() crashed with EISDIR/EACCES instead of producing
   a structured diagnostic when the installed path was a directory or
   unreadable. Wrap statSync/readFileSync in try/catch and emit a
   per-file fail row; the whole-run gate continues with structured
   output. Added test case asserting the directory-at-installed-path
   case fails with `not a regular file` diagnostic instead of crashing.

3. (Minor) PRISTINE_FLAG built as a single string + unquoted expansion
   would split paths with spaces. Switched to a bash array (VERIFY_ARGS)
   that preserves whitespace through expansion.

4. (Minor) Fenced code block missing language tag (markdownlint MD040).
   Added `text` tag to the error message block.

5. (Minor) Usage comment said pristine fallback was "backup-meta lookup"
   but the actual code path falls back to significant-line checks from
   backup content. Corrected the comment to match implementation.

Verified all 21 tests in tests/reapply-patches.test.cjs (#1999 contract)
+ tests/bug-2969-verify-reapply-patches.test.cjs (now 7 tests with the
new directory case) pass.

* test(#2969): structured JSON assertions, no substring matching on script output

Replace every assert.match(r.stdout, /pattern/) call with structured
assertions on the parsed JSON report from the script's own --json mode.
The script's --json contract IS the structured shape we test against —
the test author should never depend on the human-readable formatter
output, just as no test should depend on substring presence in source.

Changes:

  - All 7 tests now run the verifier with --json (via a runVerifier()
    helper) and parse the resulting JSON document into { status, report,
    stderr }. Diagnostic stderr is preserved as a separate channel for
    debug output but is not used for assertions.
  - Each previously substring-matched diagnostic ("Failures: 1",
    "not a regular file", "installed file missing after merge",
    file path, dropped line) is now a deepEqual / equal / Array.includes
    against typed report fields: report.failures, report.results[i].status,
    report.results[i].reason, report.results[i].file,
    report.results[i].missing[].
  - Added an explicit "documented shape" test asserting the JSON output
    has exactly the keys { file, missing, reason, status } per result —
    locks the public contract of the --json mode.
  - DRY'd up fixture reset into a resetFixture() helper since every test
    starts with a fresh patches/installed/pristine triple.

Linter: scripts/lint-no-source-grep.cjs reports 0 violations across 348
test files. Combined run of bug-2969-...test.cjs (7 tests) +
reapply-patches.test.cjs (5 tests in the #1999 suite) all pass —
22/22 in the relevant fixture.

* fix(#2969): typed REASON enum + raw-text-matching rule shipped repo-wide

This commit closes the loop on the no-source-grep discipline:

1. scripts/verify-reapply-patches.cjs:
   - Frozen REASON enum exposes the diagnostic surface as stable codes:
     OK_NO_USER_LINES_VS_PRISTINE, OK_NO_SIGNIFICANT_BACKUP_LINES,
     FAIL_INSTALLED_MISSING, FAIL_INSTALLED_NOT_REGULAR_FILE,
     FAIL_READ_ERROR, FAIL_USER_LINES_MISSING.
   - Each result.reason is now a code from this enum, not free text.
     Tests assert via REASON.X equality, not regex on prose.
   - REASON exported from module.exports.

2. tests/bug-2969-verify-reapply-patches.test.cjs:
   - Full rewrite. Every assertion on typed structured fields:
     report.results[0].status === 'fail',
     report.results[0].reason === REASON.FAIL_INSTALLED_NOT_REGULAR_FILE,
     report.results[0].missing.includes(droppedLine) (Array set membership,
     not String substring).
   - Locks the REASON enum surface via Object.keys(REASON).sort() deepEqual.
   - Locks the JSON report shape via Object.keys(report).sort() deepEqual.
   - Zero regex, zero String#includes, zero startsWith/endsWith on text.

3. CONTRIBUTING.md:
   - New section "Prohibited: Raw Text Matching on Test Outputs" with
     concrete BAD/GOOD examples (substring on file content; assert.match
     on stdout; "structured parser" hiding string ops; regex on free-form
     reason fields).
   - The rule statement: "Tests assert on typed structured values. If
     the code under test produces text, the code under test must also
     expose a structured intermediate representation, and the test must
     assert on that IR — never on the rendered text."
   - Required structured-surface table: file IR, --json mode, frozen
     enum, fs facts.
   - "Hiding grep behind a function is still grep" callout — the
     parser-wrapper anti-pattern.
   - New `pre-existing-text-matching` exemption category for the 8
     grandfathered files. Marked Transitional; new tests cannot use it.

4. scripts/lint-no-source-grep.cjs:
   - Three new patterns enforced (in addition to the existing .cjs-source
     readFileSync rule):
     - assert.match/doesNotMatch on .stdout/.stderr
     - .stdout/.stderr.<includes|startsWith|endsWith>(
     - readFileSync(...).<includes|startsWith|endsWith>(
   - Aggregated violations per file (multiple findings now report together).
   - Updated diagnostic message references both CONTRIBUTING.md sections.

5. 8 pre-existing tests annotated with `// allow-test-rule:
   pre-existing-text-matching` so the lint passes on this commit; each
   carries the prose "Tracked for migration to typed-IR assertions; do
   not copy this pattern." Files: bug-2649, bug-2687, bug-2796, bug-2838,
   bug-2943, graphify, hooks-opt-in, security-scan.

Verification: lint 0 violations across 348 test files; full suite passes.

* fix(#2969): rename exemption category to pending-migration-to-typed-ir + cite tracking issue

Per maintainer feedback:
1. "Grandfathered" / "legacy" framing is wrong — both terms imply
   permanent or condoned exemption. The 8 files are tracked for
   correction, not exempted.
2. Each annotated file must cite the tracking issue so the migration
   work is auditable.

Changes:
- CONTRIBUTING.md: rename exemption category from
  `pre-existing-text-matching` to `pending-migration-to-typed-ir`. Update
  prose to "Tracked for correction, not exempted" and require each
  annotation to cite the open migration issue (e.g.
  `// allow-test-rule: pending-migration-to-typed-ir [#NNNN]`).
- 8 test files: update annotation to cite #2974 (the tracking issue
  opened for migrating these files to typed-IR assertions).
2026-05-01 16:14:39 -04:00
Tom Boucher
e9a66da1e7 fix(#2962): write npm-style gsd-sdk shim on Windows under --sdk install (#2971)
* fix(#2962): write npm-style gsd-sdk shim on Windows under --sdk install

trySelfLinkGsdSdk previously contained `if (process.platform === 'win32')
return null;` — a missed gap from #2775's POSIX self-link rather than an
intentional design choice. As a result, `npx get-shit-done-cc@latest
--claude --global --sdk` on Windows left `gsd-sdk` off PATH despite the
installer reporting success, and the obvious recovery (`npm i -g
@gsd-build/sdk`) lands the stale 0.1.0 publication that lacks the `query`
subcommand the agents call ~40 times.

This PR addresses the shim half. The npm-publish half (publishing
@gsd-build/sdk at parity with the get-shit-done-cc version) requires
maintainer credentials and is left for separate action.

Changes:

- bin/install.js: replace the unconditional Windows return-null with
  dispatch to a new trySelfLinkGsdSdkWindows() that:
  * resolves npm's global bin via `execFileSync('npm', ['prefix', '-g'])`
    (no shell interpolation; npm is the only PATH-resolved binary)
  * verifies write access with a probe before producing partial state
  * writes the standard npm shim triple to npm's global bin:
    - gsd-sdk.cmd (cmd.exe; CRLF line endings)
    - gsd-sdk.ps1 (PowerShell)
    - gsd-sdk    (Bash wrapper for Cygwin/MSYS/Git-Bash)
  * each shim invokes `node "<absolute path to bin/gsd-sdk.js>"` with the
    passed args, decoupling shim location from SDK location — same logical
    structure as the POSIX wrapper-via-require() fallback above
  * unlinks any stale shims before writing so prior installs don't pin
    callers to a now-absent path
  * returns the .cmd path on success (handle the existing onPath check
    looks for) or null on any failure, falling through to the existing
    "gsd-sdk is not on your PATH" warning at line 8704

- tests/bug-2962-windows-sdk-shim.test.cjs (new): 5 tests exercising
  trySelfLinkGsdSdkWindows directly with cp.execFileSync mocked to redirect
  npm prefix to a temp dir. Asserts shim contents reference the absolute
  path, .cmd uses CRLF, stale shims are replaced not appended, and null is
  returned when `npm prefix -g` fails.

- tests/no-unconditional-win32-skip.test.cjs (new): regression guard
  that fails CI if any future commit re-introduces
  `if (process.platform === 'win32') return null;` (or similar
  skip-only branches) in bin/install.js. Negative test verified by
  transiently re-introducing the bad pattern → guard fired → restored
  → passes.

Out of scope: publishing @gsd-build/sdk@<current> to npm so the natural
`npm i -g @gsd-build/sdk` recovery also lands a usable SDK. That requires
maintainer credentials and is the second half of the issue.

Closes #2962

* fix(#2962): address CodeRabbit findings — execSync for npm.cmd, behavior-based regression guard

CR finding 1 (🟠 Major): Node's child_process docs explicitly call out that
.cmd/.bat files cannot be spawned via execFile/execFileSync without a shell
("Spawning .bat and .cmd files on Windows" section). Since `npm` on Windows
is `npm.cmd`, my use of execFileSync('npm', ['prefix', '-g'], { shell: false })
would have failed on the very platform this PR is meant to fix.

Switched to cp.execSync('npm prefix -g', ...) — matching the existing
convention at line ~8718 which makes the same lookup. Args are static literals
so shell interpolation is not an injection vector.

CR finding 2 (🟠 Major): the source-grep regression test in
tests/no-unconditional-win32-skip.test.cjs violated the repo's no-source-grep
testing standard (CONTRIBUTING.md). Replaced with a behavior-based test that:

  - overrides process.platform to 'win32' via Object.defineProperty
  - mocks cp.execSync to return a temp-dir as npm prefix
  - calls trySelfLinkGsdSdk(shimSrc) and asserts it returns non-null AND
    materializes gsd-sdk.cmd on disk

The behavior guard is strictly stronger than the regex version: it would
catch any equivalent skip pattern (e.g. os.platform() === 'win32', a
typeof-based guard, etc.), not just literal `if (process.platform === 'win32')`
text. Negative-tested by re-introducing the `return null` skip → test fails
with maintainer-quoted diagnostic "trySelfLinkGsdSdk must not silently
return null on Windows; a no-op skip is a missed-parity regression"; restored
→ passes.

Test for Windows shim materialization (bug-2962-windows-sdk-shim.test.cjs)
also updated to mock cp.execSync (matching the new production code path)
instead of cp.execFileSync.

Full suite: 6480/6480 pass.

* test(#2962): make Windows shim tests self-contained per CR

Each test now invokes trySelfLinkGsdSdkWindows() itself before reading
the shim files, so they don't implicitly depend on the earlier test's
side effects. Addresses CR's order-dependence finding.

* test(#2962): structured shim parsing — eliminate substring source-grep

CR found that even after the prior refactor, three tests in the suite
still used .includes()/.startsWith() against shim file content
(cmdContent.includes(\`@node ${jsonQuoted} %*\`) etc.). Substring matching
on file text is the same anti-pattern the no-source-grep standard
forbids — even when the file is one this test wrote — because it asserts
a literal exists rather than that the structured shape is correct.

Replace with three small parsers (parseCmdShim, parsePs1Invocation,
parseBashInvocation) that split each shim into header + invocation
tokens and assert via deepEqual on structured records. The assertions
now check that the .cmd has @ECHO OFF / @SETLOCAL / @node <abs> %* in
that order with exactly 3 meaningful lines, and that the .ps1 and bash
wrappers split into the expected (call, nodeCmd, target, argToken)
tuples.

The stale-shim replacement test was hardened the same way: instead of
proving the absence of a sentinel substring, it now proves the parsed
target equals the new shimSrc and != the old path.

Verified: scripts/lint-no-source-grep.cjs reports 0 violations across
348 test files. The 6-test windows-sdk-shim + win32-skip-guard suite
all pass.

* fix(#2962): expose pure shim IR + tests assert on typed fields, not rendered text

Earlier "structured parser" approach (parseCmdShim / parsePs1Invocation /
parseBashInvocation) was still raw-text manipulation behind a function
wrapper — split('\\r\\n'), trim().split(/\\s+/), content.includes('\\r\\n').
Maintainer was right: hiding grep behind a parser is still grep.

Real fix: refactor production code to expose the structured intermediate
representation, and have tests assert on the IR fields directly.

Production:
- New buildWindowsShimTriple(shimSrc) — pure function, no fs/spawn.
  Returns { invocation: { interpreter, target }, eol: { cmd, ps1, sh },
  fileNames: { cmd, ps1, sh }, render: { cmd: () => string, ... } }.
  The IR is the contract; rendered text is an implementation detail of
  the renderers.
- trySelfLinkGsdSdkWindows now calls buildWindowsShimTriple, looks up
  filenames from triple.fileNames, and writes triple.render[kind]() to
  each target. Same observable behavior, structurally separated.
- buildWindowsShimTriple added to test-mode exports.

Tests (full rewrite — no shim file content is read at any point):
- Layer 1: pure-IR tests assert on triple.invocation.target,
  triple.eol === { cmd: '\\r\\n', ps1: '\\n', sh: '\\n' },
  triple.fileNames === { cmd: 'gsd-sdk.cmd', ... }, and the
  documented IR shape via Object.keys().sort() deepEqual.
- Layer 2: fs/spawn driver tests assert filesystem FACTS:
  - return value equals expected path
  - all three target files exist as regular non-empty files
  - rendered file byte length === Buffer.byteLength of triple.render(kind)
    output (proves the writer writes what the renderer produces, no
    mutation, no truncation, no double-write — without comparing content)
  - mtime advances on rewrite (proves stale-replace behavior)
  - returns null when npm prefix -g throws

No more split, .includes, .startsWith, .endsWith, or substring matching
anywhere in the test suite. Lint clean. 10/10 tests pass.
2026-05-01 16:10:30 -04:00
Tom Boucher
b8d9bd69b2 fix(release-sdk): skip all cherry-pick conflicts in hotfix loop (full automation) (#2970)
* fix(release-sdk): skip all cherry-pick conflicts in hotfix loop

Full-automation policy: any conflict the cherry-pick can't auto-resolve
— context-missing (#2966) or real merge conflict — is now skipped, not
aborted. The hotfix run completes with whatever applies cleanly; the
SKIPPED list in the run summary becomes the operator's post-hoc review
queue.

Surfaced in run 25227493387 (1.39.1 dry-run): commit 0fb992d
("fix(git): add git.base_branch config") produced real conflicts in
config.cjs / ship.md / complete-milestone.md / tests/config.test.cjs.
v1.39.0 was tagged on the feat/hermes-runtime-2841 branch (#2920),
which restructured those files. 0fb992d was authored against the
pre-restructure shape, so cherry-pick can't auto-resolve.

Pre-#2968 behavior: the workflow distinguished context-missing (skip)
from real (abort + push partial + exit 1). Real conflicts blocked every
hotfix from a base tag whose lineage diverged from main — exactly the
v1.39.x situation. The user has called explicitly for full automation:
"this needs to be fully automated, no one is going to sit there and
tag fixes."

Behavior change:
  - Both classification branches now `git cherry-pick --skip` and
    append to SKIPPED with a reason category:
      * "context absent at base" — empty-HEAD markers (#2966)
      * "merge conflict — manual review" — non-empty HEAD (#2968)
  - Removed: `git cherry-pick --abort`, partial-state push,
    "Cherry-pick conflict" GITHUB_STEP_SUMMARY block, `exit 1`.
  - Operator's manual recovery path via `auto_cherry_pick=false`
    remains intact.

Trade-off (acknowledged in #2968): a critical fix can be silently
dropped if no one reviews the SKIPPED list. The release job's
install-smoke + full test suite still runs and would catch any
test-covered regression. Fixes that aren't test-covered could ship
missing — accepted cost of full automation per the issue.

Tests:
  - tests/bug-2968-cherry-pick-skip-on-any-conflict.test.cjs (new) —
    extracts the cherry-pick failure block via bash if/fi nesting walk
    (no raw-text grep) and asserts the abort path is removed, --skip
    is unconditional, and "merge conflict" + "context absent at base"
    annotations both exist.
  - tests/bug-2966-cherry-pick-context-missing.test.cjs (renamed
    describe + first test name) — assertions still valid since the
    classifier survives for skip-reason annotation.
  - tests/bug-2964-release-sdk-empty-cherry-pick.test.cjs — unchanged
    and still green.

Local: `node --test tests/bug-2964-...test.cjs tests/bug-2966-...test.cjs
tests/bug-2968-...test.cjs` → 8/8 pass.
Local: `npm run lint:tests` → 0 violations.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

* fix(release-sdk): split cherry-pick conflict skips from policy skips

CodeRabbit flagged on PR #2970 that conflict skips and policy skips
share the SKIPPED bucket. The run summary heading
"Skipped (feat/refactor/etc — not auto-included)" buries manual-review
conflicts (which the operator must triage) under the same list as
intentional policy exclusions (commits that don't match fix/chore by
design and need no action). Operators reviewing the summary can't
distinguish the two without reading every entry.

Split into two variables:
  - POLICY_SKIPPED — feat/refactor/docs/etc filtered out by the
    fix/chore regex (informational, no action needed)
  - CONFLICT_SKIPPED — fix/chore commits whose cherry-pick failed and
    were skipped per the full-automation policy (#2968) (manual
    review queue)

Run summary now emits two sections with distinct headings:
  - "Skipped — cherry-pick conflict (manual review)"
  - "Not auto-included (feat/refactor/docs/etc)"

The new bug-2968 test asserts both buckets are populated correctly:
  - failure path appends to CONFLICT_SKIPPED, not SKIPPED
  - both bucket variables are echoed in the summary
  - both section headings are present

Local: `node --test tests/bug-2964-...test.cjs tests/bug-2966-...test.cjs
tests/bug-2968-...test.cjs` → 9/9 pass.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

* fix(release-sdk): handle merge commits and guard cherry-pick --skip

CodeRabbit flagged a real major issue on PR #2970: merge commits with
fix:/chore: titles fail BEFORE entering cherry-pick state because they
need `-m <parent>` to specify the diff base. Without it, the cherry-pick
errors out and CHERRY_PICK_HEAD is never created. The unconditional
`git cherry-pick --skip` call that follows then fails too (no in-progress
cherry-pick to skip), bricking the loop — defeating the full-automation
policy this PR set out to deliver.

Two guards added:

1. Pre-skip merge commits before invoking cherry-pick. The loop checks
   parent count via `git rev-list --parents -n 1 "$SHA"`; if > 1, the
   commit goes straight to CONFLICT_SKIPPED with reason "merge commit —
   manual -m parent selection required". Operator decides which parent
   to keep when reviewing the run summary.

2. Guard `git cherry-pick --skip` with a CHERRY_PICK_HEAD existence
   check. Catches any other failure mode where the cherry-pick aborts
   before entering conflict state (unreadable commit, ref problems,
   etc.) so the loop still continues cleanly.

Also bumped the bug-2964 test's regex slice window from 2000 to 4000
chars so the merge-commit pre-skip block doesn't push the cherry-pick
line out of the test's match range.

Tests added in tests/bug-2968-cherry-pick-skip-on-any-conflict.test.cjs:
  - merge-commit detection: workflow must call
    `git rev-list --parents -n 1 "$SHA"` before cherry-pick and annotate
    skips with the distinct "manual -m parent selection required"
    reason.
  - guard: failure block must check CHERRY_PICK_HEAD before --skip.

Local: `node --test tests/bug-2964-...test.cjs tests/bug-2966-...test.cjs
tests/bug-2968-...test.cjs` → 11/11 pass.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

* fix(release-sdk): guard awk classifier against degenerate unmerged paths

CodeRabbit raised two issues on PR #2970:

1. Major (workflow): the `awk` classifier runs under `set -euo pipefail`.
   If a CONFLICTED path is missing/unreadable, awk exits non-zero and
   terminates the entire step — bricking the loop on a degenerate file.
   Also, an unmerged path with no `<<<<<<< ` markers (path-level conflict
   or anomalous git state) was misclassified as "context absent at base"
   (the auto-skip path), letting potentially-real conflicts skip silently.

   Fix: before invoking awk, check `[ ! -r "$CONFLICTED" ]` and
   `grep -q '^<<<<<<< ' "$CONFLICTED"`. Either failure marks
   ALL_EMPTY_HEAD=false → REASON falls through to "merge conflict —
   manual review", landing the pick in the operator review queue.
   Also added `2>/dev/null || echo "real"` on the awk call so a
   transient awk failure can't slip into the auto-skip bucket.

2. Nitpick (tests): regex assertions on `failureBlock` could match
   commented lines (e.g. comment text mentioning "CONFLICT_SKIPPED"
   or "git cherry-pick --skip" satisfied the assertions without the
   real command being present).

   Fix: anchor with `^\s*...` + `m` flag so only executable shell lines
   count.

Plus a new test asserting all three workflow guards
(`[ ! -r "$CONFLICTED" ]`, `grep -q '^<<<<<<< '`, `awk ... || echo
"real"`) are present in the failure block.

Local: `node --test tests/bug-2964-...test.cjs tests/bug-2966-...test.cjs
tests/bug-2968-...test.cjs` → 12/12 pass.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-01 15:15:20 -04:00
Tom Boucher
0d25ef0c47 fix(release-sdk): skip cherry-picks whose target context is absent at base (#2967)
* fix(release-sdk): skip cherry-picks whose target context is absent at base

When auto_cherry_pick processed a fix:/chore: commit whose patch modified
code that didn't exist at the hotfix base tag — typically because the
surrounding infrastructure was added later in a feat/refactor commit
excluded by the filter — `git cherry-pick` failed with a conflict that
no operator could meaningfully resolve, and the loop bricked the run.

Discovered re-running the 1.39.1 dry-run after #2965 merged: cherry-pick
of `a3467792` (the #2965 merge itself) failed because the auto_cherry_pick
block it modifies was added in #2956 ("Add automated cherry-pick + SDK-
bundle parity to hotfix flow") — an Add/feat commit, so the fix/chore
filter excludes it. v1.39.0 has no such block, so the patch had no
anchor.

The conflict is unmistakably distinguishable from a real content conflict:
git emits marker blocks where every `<<<<<<< HEAD ... =======` HEAD
section is empty (no anchor lines to reconcile against), while real
conflicts have content on both sides.

After cherry-pick fails:
  1. List unmerged paths via `git diff --diff-filter=U`.
  2. For each, scan conflict markers with awk. If every HEAD section is
     blank/whitespace-only across every block, classify as
     context-missing.
  3. Context-missing → `git cherry-pick --skip` and append to SKIPPED
     list with reason "(context absent at base)".
  4. Otherwise fall through to the existing abort/push-partial/error
     path that surfaces the conflict for operator resolution.

Real conflicts still surface with the same workflow as before.

Tests in tests/bug-2966-cherry-pick-context-missing.test.cjs cover:
  - Static — extracts the "Prepare hotfix branch" run block via
    indentation-aware YAML parsing (no raw-text grep) and asserts the
    classification predicate, --skip call, and skipped-reason annotation
    are present.
  - Behavioral — synthetic repo reproducing the real shape of the
    failure, asserts cherry-pick exits non-zero and produces the
    empty-HEAD marker shape.
  - Predicate — pulls the awk script out of the deployed workflow and
    feeds it sample conflict shapes (empty-HEAD, real, mixed,
    whitespace-only); asserts each is classified as the workflow will
    behave.

Local: `node --test tests/bug-2966-...test.cjs` → 3/3 pass.
Local: `npm run lint:tests` → 0 violations.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

* fix(release-sdk): pin merge.conflictStyle=merge on hotfix cherry-pick

CodeRabbit flagged on #2967 that the awk classifier introduced for #2966
assumes default conflict-marker style (plain `<<<<<<< HEAD ... ======= ...
>>>>>>>`). If a runner has merge.conflictStyle=diff3 or zdiff3 set
(globally, repo-config, or via git defaults shift), the marker emits an
extra `||||||| ancestor` section between HEAD and =======. The awk's
`in_head` mode would accumulate that ancestor content into the HEAD
buffer, and a context-missing conflict would misclassify as real —
sending the workflow into the abort path on a pick that should be
silently skipped.

Pass `-c merge.conflictStyle=merge` on the cherry-pick command itself
(scoped to that one git invocation; doesn't leak to other commands).
This guarantees marker shape regardless of the runner's git config.

Updated the existing static assertion in
tests/bug-2966-cherry-pick-context-missing.test.cjs to require the pin —
a future edit dropping it fails the test.

Local: `node --test tests/bug-2966-...test.cjs` → 3/3 pass.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

* test(#2964): allow git options between `git` and `cherry-pick`

The previous commit on this branch (d6530190) added
`git -c merge.conflictStyle=merge cherry-pick ...` to release-sdk.yml.
The bug-2964 static test's regex `/git cherry-pick[^\n]*"\$SHA"/`
required `cherry-pick` to be the literal next token after `git`, so it
no longer matched the line and CI failed on Node 22 / Node 24 / macOS.

Loosen to `/git\b[^\n]*?cherry-pick[^\n]*"\$SHA"/` so any options
between `git` and `cherry-pick` (e.g. `-c key=value`) are tolerated.
The flag assertions on the matched line still verify --allow-empty and
--keep-redundant-commits are present, which is what bug-2964 actually
guards.

Local: `node --test tests/bug-2964-...test.cjs tests/bug-2966-...test.cjs`
→ 5/5 pass.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

* test(#2966): pin merge.conflictStyle in test git wrapper, assert awk status

CodeRabbit raised two issues on PR #2967:

1. The synthetic-repo cherry-pick reproducer asserted `<<<<<<< HEAD ...`
   blocks have empty HEAD sections, but the cherry-pick itself didn't
   pin `merge.conflictStyle`. A developer or CI runner with global
   diff3/zdiff3 config would inject `||||||| ancestor` lines into the
   HEAD scan and the test would fail for environment reasons rather
   than the bug premise. Pin the style on the test's `git()` wrapper
   so every git operation in the test is deterministic regardless of
   user config.

2. `classify()` ran awk and consumed `r.stdout.trim()` without checking
   `r.status` or `r.error`. A failed awk invocation (missing binary,
   syntax error, signal) returns empty stdout, which would falsely
   classify as "context-missing" and the test would silently pass on
   broken predicates. Add `assert.ok(!r.error, ...)` and
   `assert.equal(r.status, 0, ...)` before reading stdout.

Local: `node --test tests/bug-2966-...test.cjs tests/bug-2964-...test.cjs`
→ 5/5 pass.

https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-01 14:35:18 -04:00
Tom Boucher
a346779213 fix(release-sdk): allow empty/redundant commits during hotfix cherry-pick (#2965) 2026-05-01 13:56:24 -04:00
Tom Boucher
0d6abb87ac fix(#2954): align help.md with post-#2824 skill consolidation (#2959) 2026-05-01 13:36:44 -04:00
Tom Boucher
c5dfdbe42e fix(#2957): claude+global post-install instructs restart and skill fallback (#2960)
* fix(#2957): claude+global post-install instructs restart and skill fallback

`npx get-shit-done-cc --claude --global` writes skills to
`~/.claude/skills/gsd-*/SKILL.md` (CC 2.1.88+ format) and removes the
legacy `~/.claude/commands/gsd/`. The post-install message still told
users to type `/gsd-new-project` without mentioning the required Claude
Code restart or the skill-name fallback. On configurations where CC
does not auto-surface skills in the slash menu, users hit "no commands
appear" and assumed the install failed.

Split the post-install message: the existing single-line instruction
stays for every non-Claude runtime and for `--claude --local`. For
`--claude --global` it now reads:

  Restart Claude Code, then in any directory either type
  /gsd-new-project or ask Claude to run the gsd-new-project skill.

This covers both invocation paths and surfaces the restart requirement.

Add tests/bug-2957-claude-global-postinstall-message.test.cjs as a
regression guard: captures the printed message for claude+global,
claude+local, and opencode+global; asserts content for each. Verified
the test fails on main (pre-fix) and passes after the fix.

Closes #2957

* test(#2957): assert legacy generic instruction is replaced not extended

CodeRabbit flagged that the test would still pass if the new restart/
fallback copy were printed *alongside* the old 'open a blank directory'
instruction. Adding a doesNotMatch assertion proves the claude+global
branch replaces the legacy line rather than appending to it.
2026-05-01 13:04:39 -04:00
javeroff
9d0d085a17 fix(query/agent-skills): emit raw <agent_skills> block instead of JSON-wrapped string (#2917)
* fix(query/agent-skills): emit raw <agent_skills> block instead of JSON-wrapped string

The CLI dispatcher (`cli.ts`) JSON-stringifies all query handler results via
`console.log(JSON.stringify(result.data, null, 2))`.  For the `agent-skills`
handler this produced a JSON-quoted string literal — e.g.
`"<agent_skills>\n…</agent_skills>"` — which workflows embedded verbatim via
`$(gsd-sdk query agent-skills gsd-planner)`, breaking all `<agent_skills>`
injection into spawned subagent prompts.

Fix: add an optional `format: 'json' | 'text'` field to `QueryResult`.  When a
handler returns `format: 'text'` and `--pick` is not active, the CLI writes the
string directly via `process.stdout.write` instead of JSON-stringifying it.
`agentSkills` sets `format: 'text'` for non-empty blocks.

Regression guard: two new CLI integration tests in `skills.test.ts` spawn the
CLI as a child process and assert that (a) a mapped agent type receives the raw
XML block on stdout and (b) an unmapped agent type produces the existing JSON
empty-string output.

Fixes #2914.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(changelog): add #2917 entry under Unreleased Fixed

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 12:21:06 -04:00
Tom Boucher
53cda93a01 Add automated cherry-pick + SDK-bundle parity to hotfix flow (#2956)
* feat(workflows): hotfix auto-cherry-pick + SDK-bundle parity (#2955)

hotfix.yml:
- create: auto-cherry-picks fix:/chore: commits from origin/main since
  BASE_TAG, oldest-first. Patch-equivalents skipped via git cherry.
  feat:/refactor: never auto-included. Conflicts halt with offending SHA.
- finalize: install-smoke gate, sdk-bundle/gsd-sdk.tgz parity with
  release-sdk.yml, tightened next dist-tag re-point, --latest on gh
  release create. SDK package.json bumped in lockstep.

release-sdk.yml:
- New action input (publish | hotfix) and auto_cherry_pick boolean.
- New prepare job branches hotfix/X.YY.Z from highest vX.YY.* tag,
  cherry-picks same logic as hotfix.yml, outputs effective ref.
- install-smoke and release consume prepare.outputs.ref.
- Hotfix mode forces tag=latest, opens merge-back PR. Idempotent if
  branch already exists.

VERSIONING.md: documents the cumulative-tag invariant
(vX.YY.Z anchors vX.YY.{Z+1}) and both workflow paths.

Closes #2955

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(code-review): wire --fix dispatch and update stale command references (#2947)

* fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans

Reporter saw `plan_count: 0` from `/gsd:execute-phase` even though five
plan files existed on disk. Investigation showed the planner had written
files like `01-PLAN-01-foundation.md`, while `phase-plan-index`'s strict
filter (`f.endsWith('-PLAN.md') || f === 'PLAN.md'`) rejected them
silently — collapsing two distinct states into the same `plans: []`
return:

  - directory truly has no plans (legit empty)
  - directory has plans but the filter rejected them (user/agent error)

The canonical contract is documented in three places:
  - `agents/gsd-planner.md` write_phase_prompt step (lines 1063-1080)
  - `commands/gsd/plan-phase.md`
  - `references/universal-anti-patterns.md` (rule 26)

It mandates `{padded_phase}-{NN}-PLAN.md` and explicitly forbids
`PLAN-NN.md` / `01-PLAN-01.md` / `plan-NN.md` etc. The strict filter is
correct per that contract. The bug is that the executor never tells the
user when the contract was violated — they just see `plan_count: 0`
with no signal.

Fix: add a diagnostic helper `describeNonCanonicalPlans()` that scans
the phase directory for files matching `*PLAN*.md` (the diagnostic net)
that the canonical filter rejected, excluding legit derivatives like
`*-PLAN-OUTLINE.md` and `*-PLAN.pre-bounce.md`. When offenders exist,
return a `warning` field naming each one and citing the canonical
pattern so the user knows what to rename to.

Wired into the three filter sites:
  - `phase-plan-index` (the executor's main entry point)
  - `phases list --type plans`
  - `find-phase`

The strict filter itself is unchanged — existing canonical plans behave
identically. This is purely a diagnostic that converts silent-empty
into loud-with-actionable-error.

Tests:
  - `phase-plan-index returns warning for reporter's exact filename
    pattern (`01-PLAN-01-foundation.md`)`
  - `truly empty dir does not emit a warning`
  - `canonical plans + outline + pre-bounce files do not emit a warning`

Closes #2893

* test(#2893): add parity tests for find-phase and phases list --type plans warnings

CodeRabbit's only finding on the prior commit: I wired the warning into
three filter sites (`phase-plan-index`, `find-phase`,
`phases list --type plans`) but only `phase-plan-index` had test
coverage for the warning shape. The other two paths could silently
diverge during future refactors — exactly the silent-drift class of bug
this fix exists to prevent.

Add four parity tests mirroring the existing two:

  - find-phase: non-canonical filenames produce a warning naming each
    offender + citing the canonical pattern.
  - find-phase: canonical plan + derivative files (PLAN-OUTLINE,
    pre-bounce) produce no warning.
  - phases list --type plans: same non-canonical case, but assert the
    warning is prefixed with `${dir}: ` (this path aggregates across
    phase directories so each offender is tagged with its dir).
  - phases list --type plans: canonical case, no warning.

`node --test tests/phase.test.cjs`: 98/98 pass (was 94, +4 new).

* docs(changelog): hotfix flow auto-cherry-pick + SDK bundle parity (#2955)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(workflows): address CodeRabbit findings on hotfix flow (#2955)

5 findings, all real:

1. BASE_TAG selection used lexicographic awk compare, breaking on
   multi-digit patches (v1.27.10 wrongly < v1.27.2). Fixed in both
   hotfix.yml and release-sdk.yml: append TARGET_TAG to candidate list,
   sort -V, take preceding entry. Semver-correct.

2,4. Cherry-pick conflict aborted locally with no remote branch to
   resolve from. Now the skeleton branch is pushed up-front (real runs);
   on conflict we abort, push the partial-pick state with
   --force-with-lease, and emit operator instructions in the run summary.

3. release-sdk.yml dry_run exited before cherry-pick, defeating the
   purpose. Now dry_run still applies cherry-picks locally (catches
   conflicts), just skips push. Downstream install-smoke runs against
   BASE_TAG; the cherry-pick verification itself is the dry-run signal.

5. release-sdk.yml release job missing pull-requests: write — gh pr
   create for the merge-back PR would have failed under restricted
   token defaults. Permission added.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(workflows): CR round 2 — dry-run signal + post-publish reconciliation (#2955)

3 findings, all real:

6. hotfix.yml create dry_run skipped every step (branch creation,
   cherry-pick, version bump) — a green dry-run gave no signal at all.
   Now the local checkout/cherry-pick/bump always runs; only the git
   push calls are gated on dry_run. Conflicts surface in dry-run too.

7,8. "Refuse if version already on npm" preflight hard-failed reruns,
   so a transient failure between npm publish and a later step (tag
   push, GH release, merge-back PR, dist-tag re-point) left the release
   half-shipped with no path to reconcile. Replaced with a
   prior_publish detect step that warns and sets skip_publish=true; the
   publish step is gated on that flag, but tag/release/PR/dist-tag
   continue. GitHub Release create is now idempotent (edit --latest if
   already exists).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(workflows): CR round 3 — preserve dry-run cherry-pick history in conflict guidance (#2955)

Dry-run conflict path discarded successful picks with the runner, but
the message told operators to rerun with auto_cherry_pick=false — which
recreates the branch from BASE_TAG and silently loses every pick that
had succeeded before the conflict.

Updated both hotfix.yml and release-sdk.yml: dry-run conflict summary
now lists the lost SHAs and recommends re-running with
auto_cherry_pick=true (real, not dry-run) to materialize the partial
branch on origin. Real-run guidance unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 11:51:45 -04:00
Tom Boucher
ec07861228 fix(#2948): wire spike --wrap-up flag dispatch (#2951)
* fix(#2948): wire spike --wrap-up flag dispatch

Add dispatch block to commands/gsd/spike.md so that /gsd-spike --wrap-up
routes to the spike-wrap-up workflow instead of silently no-oping. Also
add spike-wrap-up.md to execution_context so the runtime can load it, and
update both companion references in workflows/spike.md from the deleted
/gsd-spike-wrap-up entry-point to /gsd-spike --wrap-up.

Fixes #2948

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#2948): rewrite dispatch test using parseFrontmatter + section extraction

Replace raw fs.readFileSync + text.includes() / regex assertions with structural
parsing: parseFrontmatter extracts the YAML frontmatter fields and _body,
extractSection pulls named XML blocks, and parseExecutionContextRefs resolves
the @-prefixed workflow references. Assertions now target the argument-hint
frontmatter field, the execution_context @-ref list, and the routing text within
<context>/<process> sections — not arbitrary substrings in the raw file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#2948): tighten dispatch assertion to line-level rule check

Replace the co-occurrence check (dispatchText.includes('--wrap-up') &&
dispatchText.includes('spike-wrap-up')) with line-level assertions that parse
the <process> section's rules array, find the exact '- If it is `--wrap-up`:'
line, verify it includes 'strip the flag' and 'spike-wrap-up', and assert the
'- Otherwise:' fallback still routes to the spike workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#2948): anchor parseFrontmatter to line 0 to avoid mid-file --- delimiters

parseFrontmatter was scanning the whole file for the first two '---' lines,
which can match a mid-document horizontal rule as the opening delimiter.
Now requires lines[0].trim() === '---'; returns { _body: content } for files
with no frontmatter, and searches for the closing '---' from line 1 onward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 11:25:26 -04:00
Tom Boucher
3ba17e872e fix(#2950): update stale deleted-command references in workflow files (#2952)
* fix(#2950): update stale deleted-command references in workflow files

Eight workflow files (help.md, do.md, settings.md, discuss-phase.md,
new-project.md, plan-phase.md, spike.md, sketch.md) referenced command
names removed in #2790. Updated all occurrences to canonical new forms:
  /gsd-phase (--insert / --remove), /gsd-capture, /gsd-config (--profile
  / --integrations / --advanced), /gsd-spike --wrap-up,
  /gsd-sketch --wrap-up, /gsd-code-review --fix.
Adds regression test (124 assertions) in tests/bug-2950-stale-command-refs.test.cjs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#2950): update pre-existing assertions to accept new consolidated command forms

gsd-settings-advanced.test.cjs and settings-integrations.test.cjs were checking
settings.md for the old micro-skill names (/gsd-settings-advanced,
/gsd-settings-integrations). Now that #2950 updates settings.md to use the
consolidated equivalents, broaden the assertions to accept both old and new forms.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#2950): require canonical command forms and forbid legacy variants

The broadened OR assertions added to unblock CI were too permissive — they
could pass with legacy names still present. Now assert the canonical form is
present (gsd-config --advanced / gsd-config --integrations) AND the legacy
forms are absent (gsd-settings-advanced, gsd:settings-advanced,
/gsd-settings-integrations).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 11:25:10 -04:00
Tom Boucher
4d628b306a fix(#2949): wire sketch --wrap-up flag dispatch (#2953)
* fix(#2949): wire sketch --wrap-up flag dispatch

Add dispatch logic to commands/gsd/sketch.md so --wrap-up routes to the
sketch-wrap-up workflow instead of silently falling through to the normal
sketch workflow. Also adds sketch-wrap-up.md to execution_context and
updates companion references in workflows/sketch.md from the deleted
/gsd-sketch-wrap-up command to /gsd-sketch --wrap-up.

Fixes #2949

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2949): use exact-match "If it is" instead of "If it contains" for --wrap-up dispatch

Aligns with the established pattern across all consolidated commands
(workspace.md, update.md, progress.md) where the first-token check uses
"If it is `--flag`" for exact equality, not substring matching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 11:06:24 -04:00
Tom Boucher
b328f3269f fix(code-review): wire --fix dispatch and update stale command references (#2947)
* fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans

Reporter saw `plan_count: 0` from `/gsd:execute-phase` even though five
plan files existed on disk. Investigation showed the planner had written
files like `01-PLAN-01-foundation.md`, while `phase-plan-index`'s strict
filter (`f.endsWith('-PLAN.md') || f === 'PLAN.md'`) rejected them
silently — collapsing two distinct states into the same `plans: []`
return:

  - directory truly has no plans (legit empty)
  - directory has plans but the filter rejected them (user/agent error)

The canonical contract is documented in three places:
  - `agents/gsd-planner.md` write_phase_prompt step (lines 1063-1080)
  - `commands/gsd/plan-phase.md`
  - `references/universal-anti-patterns.md` (rule 26)

It mandates `{padded_phase}-{NN}-PLAN.md` and explicitly forbids
`PLAN-NN.md` / `01-PLAN-01.md` / `plan-NN.md` etc. The strict filter is
correct per that contract. The bug is that the executor never tells the
user when the contract was violated — they just see `plan_count: 0`
with no signal.

Fix: add a diagnostic helper `describeNonCanonicalPlans()` that scans
the phase directory for files matching `*PLAN*.md` (the diagnostic net)
that the canonical filter rejected, excluding legit derivatives like
`*-PLAN-OUTLINE.md` and `*-PLAN.pre-bounce.md`. When offenders exist,
return a `warning` field naming each one and citing the canonical
pattern so the user knows what to rename to.

Wired into the three filter sites:
  - `phase-plan-index` (the executor's main entry point)
  - `phases list --type plans`
  - `find-phase`

The strict filter itself is unchanged — existing canonical plans behave
identically. This is purely a diagnostic that converts silent-empty
into loud-with-actionable-error.

Tests:
  - `phase-plan-index returns warning for reporter's exact filename
    pattern (`01-PLAN-01-foundation.md`)`
  - `truly empty dir does not emit a warning`
  - `canonical plans + outline + pre-bounce files do not emit a warning`

Closes #2893

* test(#2893): add parity tests for find-phase and phases list --type plans warnings

CodeRabbit's only finding on the prior commit: I wired the warning into
three filter sites (`phase-plan-index`, `find-phase`,
`phases list --type plans`) but only `phase-plan-index` had test
coverage for the warning shape. The other two paths could silently
diverge during future refactors — exactly the silent-drift class of bug
this fix exists to prevent.

Add four parity tests mirroring the existing two:

  - find-phase: non-canonical filenames produce a warning naming each
    offender + citing the canonical pattern.
  - find-phase: canonical plan + derivative files (PLAN-OUTLINE,
    pre-bounce) produce no warning.
  - phases list --type plans: same non-canonical case, but assert the
    warning is prefixed with `${dir}: ` (this path aggregates across
    phase directories so each offender is tagged with its dir).
  - phases list --type plans: canonical case, no warning.

`node --test tests/phase.test.cjs`: 98/98 pass (was 94, +4 new).
2026-05-01 10:28:05 -04:00
Tom Boucher
e2792536d9 feat(workflows): atomic Write+commit ordering for SUMMARY.md (#2806) (#2939)
* feat(workflows): add atomic Write+commit ordering directive for SUMMARY.md

Adds explicit prompt-ordering language to executor spawn prompts and
plan-execution steps so agents commit SUMMARY.md before emitting any
concluding narrative. Mitigates the truncation-between-Write-and-commit
failure mode that has made the #2070 rescue net load-bearing.

Refs #2806

* fix(workflows): condense REQUIRED ORDER blocks to fit XL budget

The two REQUIRED ORDER directives added in bd1956df pushed
execute-phase.md to 1712 lines, exceeding the 1700-line XL budget.

Collapse each 6-line block into a single line that preserves the
semantic intent (Write SUMMARY.md → commit → narration; no text
between Write and commit; #2070 rescue is not primary defense).

File is now exactly 1700 lines; workflow-size-budget test passes.

* fix(execute-plan): move self-check before commit to preserve atomic Write+commit (#2939)
2026-05-01 09:32:21 -04:00
Tom Boucher
7cc6358f91 fix(install): honour --minimal across every runtime + manifest fix for Claude local (#2940)
* fix(install): record commands/gsd in manifest for Claude local + per-runtime --minimal coverage

writeManifest gated commands/gsd/ recording to Gemini, leaving Claude
Code local installs with an incomplete manifest. Audit during #2923
investigation showed every runtime adapter correctly honours --minimal
on disk (6 skills, 0 agents) — but Claude local manifest reported 0
skills, breaking saveLocalPatches() drift detection and any downstream
tooling that reads manifest.files for the installed surface.

Drop the isGemini gate so any runtime that writes commands/gsd/ has
those files hashed into the manifest.

Adds tests/install-minimal-all-runtimes.test.cjs: spawns the installer
end-to-end for all 14 supported runtimes in both --global and --local
modes, parses the manifest JSON, and asserts mode === 'minimal',
skill set equals MINIMAL_SKILL_ALLOWLIST, and zero gsd-* agents are
recorded. Cross-checks the manifest against on-disk skill files.

Closes #2923

* test(install): address CR feedback on bug-2923 minimal-runtime tests

- Assert installer exit status in runInstall() so failing installs do not
  produce misleading downstream artifact assertions; include stderr in the
  failure message for debuggability.
- Guard the on-disk vs manifest parity loop with assert.ok(manifest, ...)
  so the equality check cannot pass accidentally when the manifest is
  missing.
2026-05-01 09:23:20 -04:00
Tom Boucher
8de8acee46 fix(workflows): assert HEAD on per-agent branch before worktree commits (#2924) (#2941)
* fix(workflows): assert HEAD on per-agent branch before worktree commits

Worktree-mode setup could leave HEAD attached to a protected branch (master),
causing agent commits to land there. The previous response was a destructive
self-recovery via 'git update-ref refs/heads/master <sha>', which silently
rewinds the protected branch and destroys concurrent commits in multi-active
scenarios (parallel agents, user committing while agent runs).

- Reorder <worktree_branch_check> in execute-phase.md and quick.md to assert
  HEAD via 'git symbolic-ref' BEFORE any 'git reset --hard'. HALT with a
  blocker if HEAD is on main/master/develop/trunk/release/* or detached.
- Add a per-commit HEAD assertion (step 0) to gsd-executor.md
  <task_commit_protocol>; HEAD attachment can drift after 'git checkout <sha>'.
- Forbid 'git update-ref refs/heads/<protected>' in
  <destructive_git_prohibition>; surface the blocker rather than self-heal.
- Remove '--no-verify' as the worktree-mode default in execute-phase.md,
  execute-plan.md, quick.md, and references/git-integration.md. Hooks now
  run on every executor commit; opt out only via workflow.worktree_skip_hooks.
- Add regression test that parses the worktree_branch_check blocks structurally
  and asserts the symbolic-ref check precedes the reset --hard, no workflow
  performs update-ref on a protected ref, and --no-verify is no longer the
  default in any parallel-execution prompt.

* fix(#2924): address CodeRabbit review findings on worktree HEAD PR

- Add positive worktree-agent-* allow-list to <task_commit_protocol> step 0
  in gsd-executor.md and to <worktree_branch_check> in execute-phase.md and
  quick.md. The deny-list (main|master|develop|trunk|release/*) silently
  allowed feature/* and other arbitrary branches outside the agent namespace.
- Register workflow.worktree_skip_hooks in both config schemas
  (sdk/src/query/config-schema.ts and get-shit-done/bin/lib/config-schema.cjs)
  and document it in docs/CONFIGURATION.md so config-set accepts it.
- Fix stash lifecycle in execute-phase.md post-wave hook validation: stash
  under a named ref and pop after the hook run; warn on pop failure.
- Pre-dispatch PLAN.md commit in quick.md: gate on git diff --cached --quiet
  for idempotency and exit 1 with a clear error on commit failure (both the
  --no-verify and the normal branches) — no more swallowing real errors.
- Test fixes (tests/bug-2924-worktree-head-attachment.test.cjs):
  - Parse the protected-branch alternation structurally and require
    main, master, develop, trunk, release/.* (release/* was previously
    skipped by the \\b...\\b regex).
  - Use fs.readdirSync(dir, { recursive: true }) so workflows in nested
    subdirectories are also asserted against the update-ref ban.
  - Add allow-list assertions for execute-phase.md, quick.md, and
    gsd-executor.md to lock in the new positive namespace check.

* test(#2924): assert sub-section end marker exists before slicing

* test(#2924): use section boundary instead of fixed window for parallel-agents slice
2026-05-01 09:23:02 -04:00
Tom Boucher
2cc8796265 fix(config-get): return schema default for context_window when absent (#2944)
* fix(config-get): return schema default for context_window when absent (#2943)

cmdConfigGet in bin/lib/config.cjs now consults a SCHEMA_DEFAULTS map before
emitting "Key not found", so context_window (and any future schema-defaulted
keys) return their default value (exit 0) when not set in config.json.

Also updates the stale subagent-timeout.test.cjs assertion that expected the
old broken behavior (exit 1 / "Key not found") to match the corrected behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: use distinct sentinel to prove --default wins over schema default (#2943)

* docs: update CHANGELOG.md for #2943 fix

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 09:22:45 -04:00
Tom Boucher
faee0287a0 fix(detect-custom-files): add skills/ to GSD_MANAGED_DIRS (#2942) (#2945)
After v1.39.0 skill consolidation (#2790), skills/ became a GSD-managed
root that the installer wipes on update. GSD_MANAGED_DIRS in gsd-tools.cjs
was missing 'skills', so user-added skill directories (e.g.
skills/custom-skill/SKILL.md) were never walked and silently destroyed
during /gsd-update.

- Add 'skills' to GSD_MANAGED_DIRS so the directory is walked
- Add tests/bug-2942-detect-custom-skills.test.cjs with 5 targeted tests
- Update tests/update-custom-backup.test.cjs: replace the now-incorrect
  "skills/ must NOT be scanned" assertion (written pre-#2790) with a test
  that verifies custom skills ARE detected and GSD-owned skills are not
  falsely flagged

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 09:22:13 -04:00
Tom Boucher
7e9477bb30 docs(#2935): refresh README highlights for v1.39.0 across all languages (#2936)
Replaces stale v1.32/v1.37 highlight blocks with v1.39.0 highlights in
README.md and four translations, adds /gsd-edit-phase to phase-management
tables, documents workstream config inheritance, the post-merge build gate,
and per-runtime review.models.<cli> selection.

Closes #2935
2026-04-30 23:21:31 -04:00
Tom Boucher
5abf46ac1c Merge pull request #2920 from gsd-build/feat/hermes-runtime-2841
feat(install): add Hermes Agent runtime support
2026-04-30 23:02:15 -04:00
Tom Boucher
372d3453f5 fix(install): tokenize before ALL_RUNTIMES_OPTION check + isolate HERMES_HOME in test
Two CodeRabbit findings on PR #2920:

1. parseRuntimeInput previously only matched the bare "16" exactly for
   the all-runtimes shortcut. Inputs the prompt explicitly encourages —
   "16,", "16 1", "1,16" — fell through to per-token parsing and
   silently installed only Claude or a partial subset. Move the
   ALL_RUNTIMES_OPTION check after tokenization so any token equal to
   "16" expands. Added regression coverage in
   tests/multi-runtime-select.test.cjs for the four mixed-input forms.

2. The "maps Hermes to ~/.hermes for global installs" test invoked
   getGlobalDir('hermes') without isolating HERMES_HOME. On a developer
   machine that exports HERMES_HOME the assertion would fail even
   though getGlobalDir was behaving correctly. Save/clear/restore the
   env var around the assertion, mirroring the pattern the later
   describe block already uses.

Full suite: 6128/6128 pass.
2026-04-30 22:48:08 -04:00
Tom Boucher
c9d6306981 fix(hermes): rewrite CLAUDE.md → HERMES.md (revert from .hermes.md per spec)
Per the issue spec for #2841 and CodeRabbit feedback on PR #2920, the
project-context filename rewrite should produce HERMES.md, not
.hermes.md. Reverts the earlier .hermes.md change at all 5 substitution
sites in bin/install.js and updates the corresponding regression test
in tests/hermes-install.test.cjs to assert HERMES.md.

Full suite: 6127/6127 pass.
2026-04-30 22:30:16 -04:00
Tom Boucher
1168e9f59a Merge pull request #2921 from gsd-build/fix/2916-handle-branching-default-base
fix(#2916): branch new phases off origin/HEAD instead of current HEAD
2026-04-30 22:25:03 -04:00
Tom Boucher
3ed8980519 fix(#2916): drop unreachable post-creation merge-base guard
CodeRabbit pointed out the post-creation guard is structurally
unreachable: immediately after `git checkout -b X origin/$DEFAULT_BRANCH`,
HEAD == origin/$DEFAULT_BRANCH, so both the merge-base form (`MB == DT`)
and the alternative "ahead-of" count form (`AHEAD == 0`) are sentinels
that always pass on a successful fresh checkout. With the explicit base
arg + fail-fast on the checkout, the guard cannot catch anything new.

Removing it (rather than swapping in another no-op that satisfies the
linter but adds no actual coverage) is the honest fix. Comment retained
to explain why no post-creation guard is needed: the explicit base
argument to `git checkout -b` is the single source of correctness for
#2916.

Same simplification mirrored in get-shit-done/workflows/quick.md.

Full suite: 6102/6102.
2026-04-30 22:18:34 -04:00
Tom Boucher
c3aef27aa6 fix(#2916): fail-fast on switch/checkout, gate fork-point warning to fresh branches
Two CodeRabbit findings on PR #2921 (review 4209533909 + comment
3171721073, both still unresolved):

A. Branch switch and create steps now abort on non-zero exit. Previously
   `git switch "$BRANCH_NAME"` and `git checkout -b "$BRANCH_NAME"
   "origin/$DEFAULT_BRANCH"` could fail (locked worktree, dirty tree
   refusing the checkout, etc.) and the workflow would silently continue
   on the wrong branch — sending the phase's later commits to the wrong
   place. Both calls now `|| { echo "ERROR: …" >&2; exit 1; }`.

B. The fork-point base-warning is now scoped to the creation arm of
   the if/else. Previously it ran for the resume path too, so a
   legitimate resumed branch where origin/$DEFAULT_BRANCH had advanced
   since first creation would falsely warn ("does not fork from
   origin/<DEFAULT_BRANCH>"). Moving the check inside the else arm
   means it only runs immediately after a fresh `git checkout -b`, when
   the merge-base check is meaningful.

Same fix mirrored in get-shit-done/workflows/quick.md.

execute-phase.md stays at the 1700-line XL budget. Full suite: 6102/6102.
2026-04-30 22:07:46 -04:00
Tom Boucher
ace61869d0 test(#2916): parameterize fixtures so both main and trunk are exercised
Two follow-ups on commit 80f14cac (which hardened quick-branching with a
trunk fixture):

1. quick-branching.test.cjs: add a `defaultBranch` parameter to
   setupFixture and run the "branches off origin/HEAD" assertion against
   both `main` and `trunk`. The wholesale switch to trunk in 80f14cac
   removed coverage of the conventional `main` path; parameterizing
   restores it without giving up the symbolic-ref guarantee.

2. bug-2916-handle-branching-default-base.test.cjs: apply the same
   parameterization here. handle_branching has the same default-branch
   detection logic as Step 2.5, so it deserves the same trunk regression
   guard. Previously this file only exercised `main`.

A regression that silently defaults to `main` instead of consulting
`git symbolic-ref refs/remotes/origin/HEAD` now fails the `trunk`
variant in both files.

Tests: 10/10 in the touched suites.
2026-04-30 21:57:27 -04:00
Tom Boucher
80f14cac1f test(#2916): scope branch_name scan to init step and harden fixture
- Restrict the "init parse list includes branch_name" assertion to
  the bash blocks inside Step 2 (Initialize) so an unrelated step
  that mentions branch_name cannot mask the contract.
- Switch the fixture's default branch from main to trunk so the
  symbolic-ref code path is locked in: a regression that silently
  defaults to "main" instead of consulting origin/HEAD now fails.

Addresses CodeRabbit review on PR #2921.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 21:48:43 -04:00
Tom Boucher
2256e4c9a3 fix(#2916): use fork-point detection for non-default-base warning
Replace the "ahead-of" heuristic with a structural check that compares
the HEAD↔origin/$DEFAULT_BRANCH merge-base to origin/$DEFAULT_BRANCH
itself. The previous count-based warning fired on legitimate WIP that
was simply ahead of the default branch — the correct signal is that
the branch did not fork from the default branch in the first place.

Addresses CodeRabbit review on PR #2921.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 21:48:36 -04:00
Tom Boucher
e5cd523e7b test(hermes): use parseFrontmatter for agent assertion (CR #2920) 2026-04-30 21:44:12 -04:00
Tom Boucher
b5777572f7 docs(readme): add Hermes uninstall examples (CR #2920) 2026-04-30 21:44:12 -04:00
Tom Boucher
861a7d972b test(install): replace source-grep prompt assertions with structured checks
Two test files were asserting installer prompt behavior by regex/.includes()
against bin/install.js source. Per CONTRIBUTING.md "no-source-grep"
testing standard, replace with structured assertions:

- tests/kilo-install.test.cjs: import runtimeMap and buildRuntimePromptText
  from the install module; assert runtimeMap['11'] === 'kilo' and that the
  rendered prompt lists Kilo above OpenCode without marketing copy.

- tests/multi-runtime-select.test.cjs: import runtimeMap, allRuntimes,
  parseRuntimeInput, buildRuntimePromptText. Assert exported runtimeMap
  matches the canonical option list, allRuntimes contains every runtime
  exactly once, prompt text lists Hermes (10), Qwen Code (13), Trae (14),
  All (16), and parser splits/dedupes by exercising parseRuntimeInput
  rather than regexing source code.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 21:30:48 -04:00
Tom Boucher
bd0511988b fix(hermes): nest GSD skills under skills/gsd/ category (#2841)
Per spec in #2841, all 86 GSD skills must collapse into a single "gsd"
category in Hermes' system prompt. Previous code passed skills/ as the
install root, producing a flat skills/gsd-*/ layout that inflated
Hermes' loader output to 86 top-level entries.

Changes:
- Install path now writes to skills/gsd/{DESCRIPTION.md, gsd-*/SKILL.md}
- Uninstall removes the entire skills/gsd/ category dir plus any leftover
  flat-layout gsd-*/ from older installs (graceful migration)
- writeManifest emits skills/gsd/<skill>/<file> paths for Hermes
- --skills-root hermes returns the nested category path so /gsd-sync-skills
  syncs into the right directory
- DESCRIPTION.md at category root carries name/version/description so
  Hermes' skill loader surfaces the GSD category in the system prompt

Also extracts promptRuntime's runtimeMap, allRuntimes, parseRuntimeInput,
and buildRuntimePromptText to module scope and exports them so tests can
assert structurally instead of grepping bin/install.js source.

Existing hermes-install tests updated to expect the nested layout and
to verify the category DESCRIPTION.md frontmatter (name, version,
description) using the shared parseFrontmatter helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 21:30:48 -04:00
Tom Boucher
4a5f36df5e Merge pull request #2919 from gsd-build/fix/2911-audit-open-output-references
fix(#2911): audit-open emits raw human report and parseable JSON
2026-04-30 21:23:30 -04:00
Tom Boucher
840f2b349e Merge pull request #2918 from gsd-build/worktree-agent-a4db9db3f3106d4d7
fix(progress): explicit context-authority directive in report step
2026-04-30 21:23:12 -04:00
Tom Boucher
140d334dab test(#2916): replace string-grep assertions with behavioral fixture test
CodeRabbit nitpick (per project policy `feedback_no_source_grep_tests`):
the prior `tests/quick-branching.test.cjs` asserted branching correctness
by `.includes()`-grepping the raw markdown content for literal command
substrings. Those assertions stayed green even when the underlying
behavior regressed (e.g. when `git checkout -b` was unconditionally run
from the wrong HEAD).

Replace with the same pattern as `bug-2916-handle-branching-default-base
.test.cjs`:
  - Structurally extract the Step 2.5 bash block from quick.md by
    walking the markdown for fenced ```bash blocks under the heading
    (no regex on prose).
  - Spin up a fixture git repo with a bare origin, a clone whose
    `origin/HEAD` points at `main`, and a checked-out previous-task
    branch carrying its own unmerged commit.
  - Execute the extracted bash block via `bash -c` and assert that
    the new branch's tip equals `origin/main` (0 commits inherited
    from the previous-task HEAD).
  - Add a reuse test that pre-creates the target branch with its own
    commit and verifies the script switches back to it without a
    rebase or reset.

The two informational tests (workflow file exists, branching runs
before task-directory creation) are retained, plus the `branch_name`
parsing assertion is rewritten to walk fenced bash blocks rather than
substring-grep arbitrary content.
2026-04-30 21:22:56 -04:00
Tom Boucher
6e4fad7acc Merge pull request #2933 from gsd-build/chore/2932-coderabbit-docstring-off
chore(ci): disable CodeRabbit docstring coverage check
2026-04-30 21:22:55 -04:00
Tom Boucher
4e2f1105d9 fix(#2916): pin new-branch base to origin/$DEFAULT_BRANCH explicitly
Address CodeRabbit HIGH findings on PR #2921. The previous fix had three
unconditional code paths where `git checkout -b "$BRANCH_NAME"` would run
from the *current* HEAD when the upstream sync failed silently:
  - the dirty-tree warn-and-continue path,
  - the clean path where `git switch` / `git merge --ff-only` errors were
    swallowed by `2>/dev/null` (still falling through to checkout -b),
  - any case where `git fetch` failed but the script continued.

This rewrites both `execute-phase.md` (handle_branching) and `quick.md`
(Step 2.5) to:
  1. Fetch origin/$DEFAULT_BRANCH; if fetch fails AND no local copy of
     origin/$DEFAULT_BRANCH exists, abort with a clear ERROR (exit 1)
     rather than create the branch off arbitrary HEAD.
  2. Always create the new branch with an explicit start point:
     `git checkout -b "$BRANCH_NAME" "origin/$DEFAULT_BRANCH"`. The base
     is now deterministic regardless of which branch is currently
     checked out, regardless of whether the optional local fast-forward
     succeeded, and regardless of dirty-tree state.
  3. Carry uncommitted changes onto the new (origin-pinned) branch
     instead of inheriting the previous-phase HEAD as a fallback base.

The post-creation INHERITED check now references origin/$DEFAULT_BRANCH
rather than the (possibly-stale) local default branch, so the warning
fires accurately even when the local fast-forward was skipped.
2026-04-30 21:22:44 -04:00
Tom Boucher
4ce72cdee7 fix(hermes): align with Hermes Agent conventions per docs review
Four fixes from review of hermes-agent.nousresearch.com docs:

1. SKILL.md frontmatter now declares `version` (required field per
   Hermes spec). Plumbed through `convertClaudeCommandToClaudeSkill`
   gated on runtime='hermes' so other runtimes' frontmatter is unchanged.

2. Project-context filename rewrite changed from `HERMES.md` (not
   discovered by Hermes) to `.hermes.md` (top of Hermes' discovery list:
   .hermes.md → AGENTS.md → CLAUDE.md → .cursorrules).

3. README + finishInstall now show `/gsd-help` and `/gsd-new-project`
   for Hermes; per docs, Hermes auto-exposes skills as slash commands.

4. Hermes tests now parse SKILL.md frontmatter structurally via the
   shared parseFrontmatter helper instead of substring-matching source
   text, and assert the version/name/description shape required by
   Hermes' skill_view().

Full suite: 6128/6128 pass (3 new structural assertions).
2026-04-30 21:22:36 -04:00
Tom Boucher
198022f58d chore(ci): disable CodeRabbit docstring coverage check (#2932)
The docstring coverage pre-merge check (default: warning at 80% threshold)
produces false-positive warnings on PRs whose new code is entirely test
files: it counts test(...) / beforeEach / afterEach arrow-function
callbacks as functions and reports 0% coverage because nothing has JSDoc.

CR's documented schema for reviews.pre_merge_checks.docstrings only
accepts `mode` and `threshold` — there is no per-check path filter that
would let us exclude tests/** while keeping the check active elsewhere.
The top-level path_filters approach would silence ALL CR review on test
files (security scans, out-of-scope checks, the substantive line-level
findings) which we want to keep.

Disabling the check entirely is the right call for this repo because:
  - GSD ships a CLI + agent runtime, not a documented public library
  - The internal helpers that warrant JSDoc already have it
  - The other CR pre-merge checks (out-of-scope, security, title) are
    meaningful for this codebase and stay enabled

Closes #2932
2026-04-30 21:13:55 -04:00
Tom Boucher
ac100ae17b test: assert reportStep present before extractBlockquotes (CR #2918)
Two existing tests called extractBlockquotes(reportStep) without first
asserting reportStep was non-null. If the workflow file ever loses its
`<step name="report">` block, the test would fail with a confusing
TypeError on the destructuring inside extractBlockquotes instead of a
clear "report step must exist" assertion.

Add assert.ok(reportStep, ...) guards at the two missing call sites
(lines 100 and 130). The other two call sites (lines 75-83) already
had guards.

Addresses CodeRabbit comment on PR #2918.
2026-04-30 21:08:26 -04:00
Tom Boucher
002db4dd2b Merge pull request #2931 from gsd-build/feat/2929-release-sdk-parity
ci(release-sdk): bring CI gates to parity with release.yml
2026-04-30 21:04:12 -04:00
Tom Boucher
0e0f6952c5 ci(release-sdk): bring CI gates to parity with release.yml (#2929)
Ports the pre-publish CI gates that release.yml applies into release-sdk.yml,
so the stopgap workflow ships releases at the same quality bar as the
canonical workflow (minus the @gsd-build/sdk publish, still intentionally
omitted, and the release-branch ceremony, intentionally omitted).

Changes (all mechanical copies of release.yml patterns):

  - install-smoke as needs: dependency. The reusable workflow at
    .github/workflows/install-smoke.yml runs the cross-platform install
    matrix (Ubuntu 22/24, macOS 24, packed-vs-unpacked). Publish job
    won't start until install-smoke passes for the dispatched ref.

  - npm test → npm run test:coverage. Full coverage gate, matching
    release.yml's pre-publish test step.

  - Tolerant tag-existence check. The previous upfront "refuse if tag
    exists" was too strict — operators re-running after a mid-flight
    publish-step failure would be blocked by the tag they successfully
    pushed last time. New behavior matches release.yml: skip the tag
    step if the tag points at HEAD; error only if it points elsewhere.

  - Tag-and-push step gets the same skip-if-at-HEAD pattern.

  - New "Re-point next dist-tag at the new latest" step, gated on
    tag=latest. Matches release.yml#finalize "Clean up next dist-tag" —
    keeps @next from going stale relative to @latest.

  - New "Create GitHub Release" step. Per-tag flag selection:
      tag=dev, tag=next  → --prerelease (won't be highlighted on repo home)
      tag=latest         → --latest (becomes the highlighted release)
    All use --generate-notes so the release body auto-fills from commits.

  - Summary updated to mention the GitHub Release and dist-tag re-point.

Out of scope per #2929:
  - canary.yml, release.yml unchanged (verified by file diff)
  - bin/install.js unchanged (install path already uses bundled SDK)
  - No @gsd-build/sdk publish anywhere
  - No release/X.Y.Z branch ceremony (this stopgap targets dispatched
    ref directly)
2026-04-30 20:59:37 -04:00
Tom Boucher
bdead2ee6a Merge pull request #2927 from gsd-build/feat/2925-release-sdk-main
feat(ci): release-sdk.yml stopgap workflow for dev/next/latest CC publishes
2026-04-30 20:51:11 -04:00
Tom Boucher
e107bb35d4 feat(ci): add release-sdk.yml stopgap workflow for dev/next/latest CC publishes (#2925)
Adds a workflow_dispatch-only release path that publishes get-shit-done-cc
to ONE chosen dist-tag per run (dev | next | latest), with the SDK
bundled inside the CC tarball both as the existing loose sdk/dist/ tree
and as a fresh sdk-bundle/gsd-sdk.tgz npm-installable artifact.

Why: @gsd-build/sdk publishes from canary.yml and release.yml fail because
the @gsd-build npm token is currently unavailable. CC users don't consume
@gsd-build/sdk directly — bin/gsd-sdk.js resolves sdk/dist/cli.js from
inside the installed CC package. This workflow ships only get-shit-done-cc
(which we hold the token for) and bundles the SDK two ways so any future
install path can pick whichever shape it needs.

The new sdk-bundle/ directory is added to the CC files whitelist in-tree
at build time only — never committed. Existing canary.yml and release.yml
are intentionally untouched; restore them to primary use once the
@gsd-build/sdk token is recovered.

Per-tag version derivation when the version input is empty:
  - dev    → <base>-dev.N (next sequential, scanning v<base>-dev.* tags)
  - next   → <base>-rc.N (matches release.yml convention)
  - latest → <base> (clean, no suffix)

Refuses to publish when the version already exists on npm or has an
existing git tag (no accidental overwrites). Verifies the publish landed
on the registry and the dist-tag resolves correctly before marking the
run successful.
2026-04-30 20:46:31 -04:00
Tom Boucher
294564b951 fix(#2916): branch new phases off origin/HEAD instead of current HEAD
handle_branching in execute-phase.md (and the equivalent step in quick.md)
created the per-phase branch from whatever branch happened to be checked
out — typically the previous phase's still-unmerged feature branch — so
consecutive phases compounded on top of each other and stayed unpushed.

Detect the default branch via git symbolic-ref refs/remotes/origin/HEAD,
fast-forward it from origin, and fork the new phase branch off that tip.
Existing branches are still reused as-is. Dirty working trees fall back
to current HEAD with a loud warning, and a post-creation guard reports
any inherited commits.

Regression test extracts the bash from the <step name="handle_branching">
block structurally and runs it against a fixture repo where HEAD sits on
a previous-phase branch with extra commits.
2026-04-30 17:30:52 -04:00
Tom Boucher
9a13d2fc0b fix(#2911): audit-open emits raw human report and parseable JSON
Two bugs in the audit-open dispatch case in bin/gsd-tools.cjs:

  1. Bare output(...) calls (only core.output is in scope) threw
     ReferenceError: output is not defined on every invocation,
     blocking the first step of /gsd-complete-milestone.
  2. Even after switching to core.output(formattedReport, raw), the
     human-readable branch JSON-stringified the formatted text because
     core.output only bypasses JSON encoding when called as
     core.output(null, true, rawValue).

Fix:
  - --json path:  core.output(result, raw)   — pass the object,
    let core.output JSON-stringify (don't pre-stringify).
  - text path:    core.output(null, true, formatAuditReport(result))
    — use the rawValue form to emit verbatim section dividers and
    item lists.

Adds tests/bug-2911-audit-open-output-shape.test.cjs which parses
both modes structurally — line-by-line for text mode (asserting the
report headers exist as standalone lines, not as escaped \n inside a
JSON quoted string), and JSON.parse + key-by-key shape assertions for
--json mode (matching the contract returned by auditOpenArtifacts).
2026-04-30 17:30:19 -04:00
Tom Boucher
d29822c1da fix(progress): add explicit context-authority directive to report step
The report step in workflows/progress.md had no directive establishing
PROJECT.md/STATE.md/ROADMAP.md as the authoritative sources for the
progress report. When init.progress returned project_exists: false (e.g.
invoked from a subdirectory without .planning/), the model fell back to
whatever was in its session context — including stale CLAUDE.md
## Project blocks — and produced routing output citing the wrong
milestone/phase.

Add a blockquote directive at the top of the report step that names
PROJECT.md, STATE.md, and ROADMAP.md as authoritative and forbids using
the CLAUDE.md ## Project block as a source for any progress report field.

Fixes #2912
2026-04-30 17:27:37 -04:00
teknium1
b126c0579a feat(install): add Hermes Agent runtime support (#2841)
Adds Hermes Agent as a supported installation target. Users can run
\`npx get-shit-done-cc --hermes\` to install all 86 GSD commands as
skills under \`~/.hermes/skills/gsd-*/SKILL.md\`, following the same
open skill standard as Claude Code 2.1.88+, Qwen Code, Antigravity,
Trae, Augment, and Codebuddy.

Hermes Agent is an open-source AI agent framework by Nous Research
(NousResearch/hermes-agent, MIT). Its skill loader accepts the Claude
skill format as-is: frontmatter parsed with PyYAML SafeLoader (unknown
keys like \`allowed-tools\` / \`argument-hint\` ignored), body XML tags
(\`<objective>\`, \`<execution_context>\`, \`<process>\`) passed directly
to the model. Compatibility proven end-to-end with all 86 GSD skills
loading cleanly, \`skill_view()\` returning full bodies, and
\`build_skills_system_prompt()\` emitting them into the agent system
prompt — zero Hermes code changes required.

Changes:
- \`bin/install.js\`: --hermes flag, getDirName/getGlobalDir/getConfigDirFromHome
  support, HERMES_HOME env var (native to Hermes — used for profile
  mode / Docker deploys), install/uninstall pipelines, interactive
  picker option 10 (alphabetical: between Gemini and Kilo), .hermes
  path replacements in copyCommandsAsClaudeSkills and
  copyWithPathReplacement, legacy commands/gsd cleanup, CLAUDE.md ->
  HERMES.md and "Claude Code" -> "Hermes Agent" content rewrites in
  skills/agents/hooks, runtime-appropriate finish message.
- \`get-shit-done/bin/lib/core.cjs\`: add hermes to KNOWN_RUNTIMES;
  add RUNTIME_PROFILE_MAP.hermes with OpenRouter-slug defaults
  (Hermes is provider-agnostic; these defaults resolve across
  OpenRouter, native Anthropic, and Copilot via Hermes' aggregator-
  aware resolver, and are overridable per-tier via
  model_profile_overrides.hermes.{opus,sonnet,haiku}).
- \`README.md\`: Hermes Agent in tagline, runtime list, verification
  command, install/uninstall examples, \`--hermes\` flag reference.
- \`tests/hermes-install.test.cjs\`: new, 14 tests covering directory
  mapping, HERMES_HOME env var precedence, install/uninstall
  lifecycle, user-skill preservation, engine cleanup.
- \`tests/hermes-skills-migration.test.cjs\`: new, 11 tests covering
  frontmatter conversion, path replacement (~/.claude/ ->
  \$HERMES_HOME/skills/), CLAUDE.md -> HERMES.md, "Claude Code" ->
  "Hermes Agent", stale skill cleanup, SKILL.md format validation.
- \`tests/multi-runtime-select.test.cjs\`: updated for new option
  numbering (hermes=10, kilo=11, opencode=12, qwen=13, trae=14,
  windsurf=15, all=16).
- \`tests/kilo-install.test.cjs\`: updated assertions for Kilo having
  moved from option 10 to option 11.

Closes #2841

Implementation notes:
- Zero custom code paths: Hermes reuses copyCommandsAsClaudeSkills()
  identical to Qwen Code / Antigravity pattern.
- Path replacement: ~/.claude/, \$HOME/.claude/, ./.claude/ ->
  .hermes equivalents in skill/agent/hook content.
- Config precedence: --config-dir > HERMES_HOME > ~/.hermes (matches
  how Hermes itself resolves its home directory).
- Legacy cleanup: removes commands/gsd/ if present from a prior
  install, preserving dev-preferences.md (same as Qwen).
- No external dependencies added.

Testing: 5841 / 5841 tests pass (0 failures, 0 regressions)
- 14 new tests in hermes-install.test.cjs
- 11 new tests in hermes-skills-migration.test.cjs
- multi-runtime-select.test.cjs renumbered + 1 new test (single choice for hermes)
2026-04-30 17:24:53 -04:00
Tom Boucher
006cdafe8f ci(drift): enforce alias freshness checks in CI and contributor flow (#2910)
Merging alias-drift guardrails and local hook hardening.
2026-04-30 14:19:46 -04:00
Tom Boucher
8051bc4fd8 test(golden): expand phases/validate/roadmap parity matrix (#2909)
Merging parity-matrix expansion after stack foundation.
2026-04-30 14:10:28 -04:00
Tom Boucher
444db1714b refactor(query): manifest-backed routing seam + family adapters (#2908)
Merging validated command-seam foundation.
2026-04-30 14:04:50 -04:00
Tom Boucher
6dce1de4a7 fix: gap-analysis parses mixed requirement prefixes and skips table headers (#2902)
* fix: parse non-REQ IDs in gap-analysis and ignore table headers

* fix: parse requirement IDs from first traceability column only

---------

Co-authored-by: Tom Boucher <thomas.boucher@sas.com>
2026-04-30 12:13:55 -04:00
Tom Boucher
abb2cb63f6 refactor: extract planning-workspace seam from core.cjs (#2901)
* refactor: extract planning workspace seam from core

* docs: document planning-workspace module and inventory updates

* fix: harden planning lock timeout and preserve workstream set contract

---------

Co-authored-by: Tom Boucher <thomas.boucher@sas.com>
2026-04-30 11:38:13 -04:00
TÂCHES
8cbdbdd2de feat(sdk): add durable planning runtime (#2898) 2026-04-30 09:03:06 -06:00
Tom Boucher
951d5bf7c0 fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans (#2896)
* fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans

Reporter saw `plan_count: 0` from `/gsd:execute-phase` even though five
plan files existed on disk. Investigation showed the planner had written
files like `01-PLAN-01-foundation.md`, while `phase-plan-index`'s strict
filter (`f.endsWith('-PLAN.md') || f === 'PLAN.md'`) rejected them
silently — collapsing two distinct states into the same `plans: []`
return:

  - directory truly has no plans (legit empty)
  - directory has plans but the filter rejected them (user/agent error)

The canonical contract is documented in three places:
  - `agents/gsd-planner.md` write_phase_prompt step (lines 1063-1080)
  - `commands/gsd/plan-phase.md`
  - `references/universal-anti-patterns.md` (rule 26)

It mandates `{padded_phase}-{NN}-PLAN.md` and explicitly forbids
`PLAN-NN.md` / `01-PLAN-01.md` / `plan-NN.md` etc. The strict filter is
correct per that contract. The bug is that the executor never tells the
user when the contract was violated — they just see `plan_count: 0`
with no signal.

Fix: add a diagnostic helper `describeNonCanonicalPlans()` that scans
the phase directory for files matching `*PLAN*.md` (the diagnostic net)
that the canonical filter rejected, excluding legit derivatives like
`*-PLAN-OUTLINE.md` and `*-PLAN.pre-bounce.md`. When offenders exist,
return a `warning` field naming each one and citing the canonical
pattern so the user knows what to rename to.

Wired into the three filter sites:
  - `phase-plan-index` (the executor's main entry point)
  - `phases list --type plans`
  - `find-phase`

The strict filter itself is unchanged — existing canonical plans behave
identically. This is purely a diagnostic that converts silent-empty
into loud-with-actionable-error.

Tests:
  - `phase-plan-index returns warning for reporter's exact filename
    pattern (`01-PLAN-01-foundation.md`)`
  - `truly empty dir does not emit a warning`
  - `canonical plans + outline + pre-bounce files do not emit a warning`

Closes #2893

* test(#2893): add parity tests for find-phase and phases list --type plans warnings

CodeRabbit's only finding on the prior commit: I wired the warning into
three filter sites (`phase-plan-index`, `find-phase`,
`phases list --type plans`) but only `phase-plan-index` had test
coverage for the warning shape. The other two paths could silently
diverge during future refactors — exactly the silent-drift class of bug
this fix exists to prevent.

Add four parity tests mirroring the existing two:

  - find-phase: non-canonical filenames produce a warning naming each
    offender + citing the canonical pattern.
  - find-phase: canonical plan + derivative files (PLAN-OUTLINE,
    pre-bounce) produce no warning.
  - phases list --type plans: same non-canonical case, but assert the
    warning is prefixed with `${dir}: ` (this path aggregates across
    phase directories so each offender is tagged with its dir).
  - phases list --type plans: canonical case, no warning.

`node --test tests/phase.test.cjs`: 98/98 pass (was 94, +4 new).
2026-04-30 10:49:13 -04:00
Tom Boucher
ca88429bf8 docs(#2888): release notes for 1.40.0-rc.1 (#2889)
Add docs/RELEASE-v1.40.0-rc.1.md following the rc.7 format. Cover the
11 commits on main since v1.39.0-rc.7's release notes landed:

- #2790 — skill surface consolidated 86 → 59
- #2792 — namespace meta-skills + keyword-tag descriptions + context guard
- #2833 — phase-lifecycle status-line read-side
- #2876 — yamlQuote SKILL.md description (Copilot/Antigravity/Trae/CodeBuddy)
- #2768 — Gemini slash command namespace
- #2858 — gsd slash namespace drift cleanup
- #2851 — bare gsd-tools → absolute path
- #2866 — Codex installer trailing-newline preservation
- #2868 — canary publish moved from main to dev
- #2872 — auto-close PRs without issue link

Update CHANGELOG.md [Unreleased] with the same 1.40.0-rc.1 entries.

Closes #2888
2026-04-30 01:13:43 -04:00
Tom Boucher
5fdc950eb7 feat(#2792): namespace meta-skills + keyword-tag descriptions + context utilization guard (#2825)
* feat(#2792): namespace meta-skills retargeted at the post-#2790 surface

This branch is now based on #2790's HEAD (the consolidation PR) instead
of main, and every routing table targets the consolidated surface so a
user routed by a namespace meta-skill never lands at a deleted /
folded sub-skill.

Cross-PR inconsistencies the original PR #2825 carried (vs #2790):

  - ns-ideate routed to gsd-note / gsd-add-todo / gsd-add-backlog /
    gsd-plant-seed → all folded into gsd-capture by #2790. Now routes
    to gsd-capture (the parent picks the mode from the user's intent).
  - ns-context routed to gsd-scan and gsd-intel → folded into
    gsd-map-codebase --fast / --query by #2790. Now routes to those
    flag forms.
  - ns-manage routed all workspace intent to gsd-list-workspaces (a
    list-only entry) → CR also flagged the over-narrow target. #2790
    folds into gsd-workspace; routing now points there.
  - ns-workflow routed to gsd-research-phase → deleted outright by
    #2790. Removed.
  - ns-project routed to gsd-plan-milestone-gaps → deleted outright by
    #2790. Removed.
  - None of the namespaces previously surfaced #2790's new consolidated
    skills (gsd-capture, gsd-phase, gsd-config, gsd-workspace,
    gsd-progress). All five are now reachable through the routers.
  - extract_learnings → extract-learnings (canonicalized by #2858).

Defect fixes within the namespace skills:

  - Hyphen-form `name:` (gsd-workflow, …) per the canonical naming
    contract — the colon-form addressed CR's drift complaint.
  - `Skill` added to allowed-tools on every router. The body instructs
    "Invoke the matched skill directly using the Skill tool" — without
    Skill in the permission list the meta-skill cannot route at all.

New regression guard in tests/enh-2792-namespace-skills.test.cjs: every
gsd-* token in any namespace router's table column resolves to a
surviving commands/gsd/*.md file (or to a known consolidated parent for
flag-form targets like gsd-map-codebase --fast). This single test would
have caught every dead-end route the original PR shipped with.

Skill-count cap in tests/enh-2790-skill-consolidation.test.cjs now
filters out ns-*.md from its <= 63 cap. Namespace routers are
descriptor-only entries, not part of the consolidation surface that cap
is policing — they have their own contract in
tests/enh-2792-namespace-skills.test.cjs.

INVENTORY.md gains a "Namespace Meta-Skills" section with the 6 router
rows; INVENTORY-MANIFEST.json gains 6 entries; the headline count moves
59 → 65 to match.

Out of scope for this rebase: the gsd-health --context flag (PR #2825
advertised the contract but didn't implement it). That's a separate
feature concern and is left untouched here.

5908/5908 on `npm test`.

* feat(#2792): implement gsd-health --context utilization guard

The original PR #2825 advertised a `--context` flag on gsd-health with a
60%/70% utilization threshold table but never implemented the workflow
logic — CR caught it as a contract leak, the rebase deferred it. This
commit closes the gap with TDD red/green/refactor.

Math layer (pure):
  - get-shit-done/bin/lib/context-utilization.cjs
    classifyContextUtilization(tokensUsed, contextWindow) →
      { percent, state }
    State boundaries use the exact ratio:
      < 60% healthy / 60–70% warning / ≥ 70% critical (fracture point)
    Display percent rounded for humans. Throws TypeError on non-integer
    or out-of-range inputs.
  - STATES = Object.freeze({ HEALTHY, WARNING, CRITICAL }) exported
    so callers reference the names by symbol, not by literal string.

SDK CLI integration:
  - get-shit-done/bin/gsd-tools.cjs
    `validate context --tokens-used N --context-window M [--json]`
    routes to the classifier, owns the recommendation copy (the
    classifier intentionally does not — keeps the renderer free to
    evolve without touching the math layer or its tests), and uses
    core.output's rawValue path for the sync-flush guarantee.
  - sdk/src/query/validate.ts + sdk/src/query/index.ts
    TypeScript validateContext handler registered at 'validate.context'
    and 'validate context'. Mirrors the CJS classifier inline (15 lines
    of arithmetic; not worth a shared cross-language module).

User-facing wiring:
  - commands/gsd/health.md frontmatter advertises --context, body
    documents the three-state threshold table.
  - get-shit-done/workflows/health.md adds a `context_check` step
    that's reached only when --context is set. Step calls
    `gsd-sdk query validate.context` with self-reported tokensUsed and
    contextWindow, prints the SDK output verbatim, and ends. Includes
    a TEXT_MODE plain-text fallback for non-Claude runtimes per #2012.

Tests:
  - tests/context-utilization.test.cjs (17 tests) — pure-function
    contract: state thresholds at every boundary, percent rounding,
    input validation, return-shape (no recommendation field — that's
    the renderer's job).
  - tests/validate-context.test.cjs (9 tests) — SDK CLI plumbing:
    arg parsing errors, JSON vs human rendering, recommendation copy
    pinned per state.
  - tests/enh-2792-namespace-skills.test.cjs (4 new tests) — markdown
    contract: --context advertised in argument-hint, threshold table
    in command body, context_check step exists in workflow, step
    invokes gsd-sdk query validate.context with both flags.

Inventory bookkeeping:
  - docs/INVENTORY.md "CLI Modules" 31 → 32; new row for
    context-utilization.cjs.
  - docs/INVENTORY-MANIFEST.json mirror.

5939/5939 on `npm test`.
2026-04-30 01:04:41 -04:00
Tom Boucher
c72b893916 fix(test): unbreak gemini-namespacing test after #2790 skill consolidation (#2886)
Closes #2876 follow-up — CI on main fails because the punctuation test
in tests/gemini-namespacing.test.cjs hardcoded `/gsd-scan` as a known
command, but #2824 (consolidate 86 → 59 skills) removed scan.md from
commands/gsd/. The roster now correctly returns "scan is unknown, leave
unchanged" — the conversion is right, the test fixture is stale.

Swap `scan` for `health` in the punctuation test. Both are bedrock
commands; the test still exercises the original intent (period vs
exclamation handling on adjacent slash commands).

Note added so the next consolidation reviewer knows the swap pattern.

`npm test`: 5936/5936 pass.
2026-04-30 00:57:17 -04:00
hoptop
8fc1fa263c feat(#2833): phase-lifecycle status-line — read-side (parseStateMd + formatGsdState scenes + tests + docs) (#2884)
* feat(#2833): parseStateMd reads phase-lifecycle frontmatter fields

Extend parseStateMd() to parse 4 new STATE.md frontmatter fields that drive
the phase-lifecycle status-line proposed in #2833:

- active_phase   : phase number when orchestrator is in-flight, null when idle
- next_action    : recommended next command when idle
- next_phases    : YAML flow array of phase numbers for next_action
- progress       : nested block with completed_phases / total_phases / percent

All fields default to undefined when absent — formatGsdState() (next commit)
degrades gracefully so existing STATE.md files keep rendering as before.

YAML scope intentionally narrow:
- Only top-level scalar keys (status, milestone, active_phase, next_action)
- Only single-line flow array for next_phases ([...])
- progress block requires 2-space indent for nested keys

Block sequences (- item over multiple lines) and inline comments inside
nested blocks are NOT parsed — keeping the regex-based parser predictable.
Comments outside frontmatter or after the closing --- still work.

Tests: all 27 existing tests still pass (no behavior change for STATE.md
files that don't carry the new fields).

Refs #2833

* feat(#2833): formatGsdState renders phase-lifecycle scenes + opt-in progress bar

Extend formatGsdState() with three lifecycle scenes that activate when the
new STATE.md frontmatter fields (added in the previous commit) are present.
Also append an opt-in progress bar to the milestone segment when
progress.percent is available.

Scenes (first match wins; falls through to the existing path otherwise):

  1. active_phase set     → 'v2.0 [██░] X% · Phase 4.5 executing'
                             (status field carries the lifecycle stage:
                              discussing / planning / executing / verifying)

  2. active_phase null +  → 'v2.0 [██░] X% · next execute-phase 4.5'
     next_action set         (idle state — surfaces what the user should
                              run next without opening STATE.md)

  3. percent=100 (or       → 'v2.0 [██████████] 100% · milestone complete'
     completed=total)

  4. (default fallback)    → 'v1.9 Code Quality · executing · ph (1/5)'
                             (existing rendering, byte-for-byte preserved
                              when none of the new fields are populated)

Backward compat is the design priority:
- STATE.md files without the new fields render identically to v1.38.x
- progress bar is opt-in (empty string when percent absent)
- Each new scene only activates when its specific fields are populated

A new helper renderProgressBar() generates the 10-segment bar that matches
the existing context meter style (so the two bars on the status-line are
visually consistent).

Tests: 27/27 existing tests still pass.

Refs #2833

* test(#2833): cover parseStateMd lifecycle fields + formatGsdState scenes

26 new tests organized in 5 describe blocks, modeled after the existing
enh-2538-statusline-last-command.test.cjs convention:

  parseStateMd #2833 lifecycle fields (7 tests)
    - reads active_phase / next_action / next_phases / progress.percent
    - 'null' literal handled correctly
    - YAML flow array parsing (1 item, multiple items)
    - progress nested block (3 fields)
    - absent fields return undefined

  formatGsdState #2833 lifecycle scenes (6 tests)
    - Scene 1: active_phase set → 'Phase X.Y <stage>'
    - Scene 2: idle + next_action → 'next <action> <phases>' (1+ phases)
    - Scene 3: percent=100 OR completed=total → 'milestone complete'

  formatGsdState #2833 backward compatibility (4 tests) — CRITICAL
    - Legacy STATE.md (no new fields) renders byte-for-byte unchanged
    - Empty state, partial state, progress-bar-opt-in all preserved

  progress bar rendering (6 tests)
    - 0% / 50% / 100% / clamping / opt-in absence

  formatGsdState #2833 scene priority (3 tests)
    - active_phase wins over next_action when both populated
    - next_action wins over fallback when active_phase null
    - percent=100 wins over fallback even with phase set

Combined run: 53/53 tests pass (existing 27 + new 26).

Refs #2833

* docs(#2833): describe phase-lifecycle frontmatter fields and rendering scenes

Add docs/STATE-MD-LIFECYCLE.md as the canonical reference for the four new
STATE.md frontmatter fields and the four status-line rendering scenes
introduced by this proposal:

- Frontmatter field reference (active_phase / next_action / next_phases /
  progress.percent) with type and population semantics
- Why progress.percent is intentionally the phase dimension and not the
  plans dimension (plans dimension trends optimistic when future phases
  are unplanned)
- The four rendering scenes including their priority order
- Stage-label convention for Scene 1 (discussing / planning / executing /
  verifying matching the four phase orchestrators)
- Frontmatter parsing constraints — frontmatter must start at file head,
  no comments inside nested blocks, next_phases is single-line flow only
- Backward-compatibility guarantee (locked in by the test suite)
- Cross-links to the foundation issue #1989 and the read-side issues
  this proposal helps close

The document deliberately scopes itself to the read-side (what the hook
parses, what it renders). Write-side SDK and workflow changes that
auto-maintain the fields are out of scope for this PR so each piece can
be reviewed independently — see the issue thread for the full proposal.

Refs #2833

* test(#2833): simplify '0% renders 10 empty segments' assertion

Address CodeRabbit nitpick — drop the convoluted assert.equal that built
the expected value via .replace() and rely on the existing assert.ok
includes-check. The behavior under test is unchanged; the assertion is
just easier to read.

Refs #2884 review comment
2026-04-30 00:48:49 -04:00
Tom Boucher
87917131f2 refactor(#2790): consolidate 86 gsd-* skills to 59 — fold flags, delete dead skills (#2824)
* feat(#2790): consolidate 86 gsd-* skills to 59 — zero functional loss

Closes #2790

- `capture.md` — absorbs add-todo (default), note (--note), add-backlog
  (--backlog), plant-seed (--seed), check-todos (--list)
- `phase.md` — absorbs add-phase (default), insert-phase (--insert),
  remove-phase (--remove), edit-phase (--edit)
- `config.md` — absorbs settings-advanced (--advanced),
  settings-integrations (--integrations), set-profile (--profile);
  settings.md retained as-is
- `workspace.md` — absorbs new-workspace (--new), list-workspaces
  (--list), remove-workspace (--remove)

- `update.md` — adds --sync (absorbs sync-skills) and --reapply
  (absorbs reapply-patches)
- `sketch.md` — adds --wrap-up (absorbs sketch-wrap-up)
- `spike.md` — adds --wrap-up (absorbs spike-wrap-up)
- `map-codebase.md` — adds --fast (absorbs scan) and --query (absorbs
  intel)
- `code-review.md` — adds --fix (absorbs code-review-fix)
- `progress.md` — adds --next (absorbs next) and --do (absorbs do)

join-discord, research-phase, session-report, from-gsd2,
analyze-dependencies, list-phase-assumptions, plan-milestone-gaps

autonomous.md: updated Skill(skill="gsd:code-review-fix") →
Skill(skill="gsd:code-review", args="--fix --auto") to match
the consolidated skill name

- New: tests/enh-2790-skill-consolidation.test.cjs (48 tests)
- Updated: 14 existing test files redirected from deleted command paths
  to their consolidated equivalents
- docs/INVENTORY.md: Commands count 86→59, ghost rows removed, new
  consolidated rows added
- docs/INVENTORY-MANIFEST.json: regenerated to match filesystem

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#2790): add CHANGELOG entry for skill consolidation

* docs(#2790): update COMMANDS.md for 86→59 skill consolidation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2790): address CodeRabbit review findings

- CHANGELOG.md: add --next alongside --do in progress flag list
- config.md: remove trailing space from --profile code span (MD038)
- COMMANDS.md: add required descriptions to /gsd-phase examples;
  /gsd-phase without args errors, not interactive
- COMMANDS.md: add --next and --do to /gsd-progress flags table + examples
- test: convert content.includes('--reapply') to structural frontmatter
  parse; add allow-test-rule comment for workflow content assertions
- test: replace redundant existsSync duplicate with assertion that verifies
  the full consolidated flag surface (--sync | --reapply) in argument-hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2790): restore reapply-patches workflow and strengthen test assertions

- Create get-shit-done/workflows/reapply-patches.md: the #2790 consolidation
  deleted the 14K combined command+workflow file (reapply-patches.md) but
  update.md already referenced the workflow via execution_context_extended.
  Restoring it fixes a silent behavioral gap where --reapply had no workflow
  to load. Includes full three-way merge logic, hunk verification table
  (Step 4), and the Hunk Verification Gate (Step 5) that blocks cleanup
  until all user-added hunks are confirmed present in the merged output.

- Fix update.md: /gsd-reapply-patches → /gsd-update --reapply (stale ref)

- Fix reapply-verify-hunks.test.cjs: was checking existsSync(update.md) 8×;
  now points to the workflow file and asserts real behavioral content
  (Post-merge verification, Hunk presence check, Line-count check, backup
  reference, per-file tracking, structural ordering)

- Fix reapply-patches.test.cjs: replace content.includes() stubs with
  frontmatter-parsed argument-hint assertions; replace 4 existsSync(update.md)
  no-ops with real assertions against the workflow content

- Fix edit-phase.test.cjs: /gsd-edit-phase → /gsd-phase (COMMANDS.md now
  documents the consolidated command with --edit flag)

- Fix next-safety-gates.test.cjs: split OR predicates into independent
  assertions — --next in progress.md and --force in next.md workflow

- Fix workspace.test.cjs: add allow-test-rule comment for routing content
  checks (command routing text IS the deployed behavioral contract)

- Fix bug-2439 test: strengthen pre-flight assertion to verify gsd-sdk is
  referenced (not just --profile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address CodeRabbit review findings (CR round 2)

- INVENTORY.md: update sync-skills.md row to reference /gsd-update --sync
  instead of stale /gsd-sync-skills (absorbed in #2790)

- enh-2380-sync-skills.test.cjs: align INVENTORY.md assertion with the
  corrected reference; was asserting the old /gsd-sync-skills name while the
  manifest test correctly asserted /gsd-update, creating conflicting expectations
  in the same suite

- reapply-verify-hunks.test.cjs: add explicit notEqual(-1) assertions for all
  three anchors before the ordering check so a missing anchor produces a clear
  failure instead of a false positive (writeIdx=-1 < verifyIdx=5 is true)

- bug-2439-set-profile-gsd-sdk-preflight.test.cjs: defer fs.readFileSync until
  after the existence assertion; eager describe-level read caused the suite to
  crash before the existence test could run, making it effectively dead code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2790): address CR — INVENTORY routing + reapply test contract wording

Two unresolved CodeRabbit findings (Major):

- docs/INVENTORY.md: workflow-file table still pointed at obsolete
  /gsd-do, /gsd-next, /gsd-note, /gsd-add-todo, /gsd-add-backlog,
  /gsd-check-todos, /gsd-plant-seed slash commands. Re-route to the
  consolidated /gsd-progress (--next, --do) and /gsd-capture (--note,
  --backlog, --seed, --list) so the inventory is internally consistent.

- tests/reapply-verify-hunks.test.cjs: 'verification tracks per-file
  status' asserted on phrasing that doesn't appear in reapply-patches.md
  (the 'per-file' substring only matched accidentally via 'sequential
  integer per file'). Switch to the actual contract text — Hunk
  Verification Table, one row per hunk per file, verified column.

* test(#2790): update CR-INTEGRATION tests for consolidated --fix invocation

After the merge of main (which carries #2843's hyphen-form fix), the
consolidation in this branch absorbs gsd-code-review-fix into gsd-code-review
as the --fix flag. Update the two CR-INTEGRATION tests that previously
asserted on the standalone gsd-code-review-fix skill name to instead assert
on a gsd-code-review invocation carrying --fix in its arg tokens.

Tests still parse Skill() invocations structurally; only the asserted
skill-name + arg-token shape changed.

* test(#2790): scope success_criteria check to the <success_criteria> block

CodeRabbit nitpick: 'success criteria includes verification' did a
whole-file substring check, which can false-pass if the phrase appears
elsewhere in the document. Extract the <success_criteria>...</success_criteria>
block first via extractTagBlock() and assert against that scope only.

* fix(#2790): post-rebase reconciliation with main

- INVENTORY.md/JSON: add reapply-patches workflow row + bump count to 85
- autonomous.md: switch consolidated --fix invocation to hyphen Skill name
- analyze-dependencies test: assert COMMANDS.md does NOT document the
  consolidated-away /gsd-analyze-dependencies entry (was: bare .includes())

* fix(#2790): address remaining CR findings — strengthen contract tests

Doc-fixes:
- INVENTORY.md: route transition.md & edit-phase.md rows to consolidated
  /gsd-progress --next and /gsd-phase --edit (was: deleted /gsd-next, /gsd-edit-phase)
- config.md --profile branch: document #2439 pre-flight `command -v gsd-sdk`
  guard + install hint BEFORE the gsd-sdk invocation (closes opaque
  "command not found: gsd-sdk" regression path)

Test discipline (no-source-grep contract):
- bug-2439: replace bare `content.includes('gsd-sdk')` with structured
  parse of <context> block + --profile branch; assert pre-flight token,
  install hint, #2439 citation, and ordering vs gsd-sdk invocation
- edit-phase: parse INVENTORY.md edit-phase.md row's "Invoked by" column
  and assert `/gsd-phase --edit` (not the deleted /gsd-edit-phase)
- next-safety-gates: tighten `--next` documentation contract — require
  --next AND --force AND completeness routing (was OR-based, passed when
  only --next present)
- reapply-patches: parse argument-hint flag list structurally; scan ALL
  <execution_context*> blocks for the @-include of reapply-patches.md;
  parse Hunk Verification Table header columns directly; locate Step 5
  via heading parsing then assert (i) table reference, (ii) verified=no
  gate, (iii) STOP/halt directive, (iv) explicit absent-table halt path
- workspace: parse frontmatter, tokenize argument-hint across multiple
  bracketed segments, parse @-include targets from <execution_context>
  rather than substring-matching the file body

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 00:43:47 -04:00
Tom Boucher
55298b2f70 fix(#2876): yamlQuote SKILL.md description for Copilot/Antigravity/Trae/CodeBuddy (#2881)
* fix(#2876): yamlQuote description in Copilot/Antigravity/Trae/CodeBuddy SKILL.md

A description starting with `[BETA]` (or any YAML flow indicator —
`{`, `*`, `&`, `!`, `|`, `>`, `%`, `@`, backtick) is parsed as a flow
sequence/mapping by YAML 1.2-strict loaders. gh-copilot's frontmatter
loader fails closed:

  ✖ ~/.copilot/skills/gsd-ultraplan-phase/SKILL.md: failed to parse YAML
    frontmatter: Unexpected scalar at node end at line 2, column 21:
    description: [BETA] Offload plan phase to Claude Code's ultraplan…

Six emission sites in `bin/install.js` re-wrote the description without
quoting, while the Claude variant (`convertClaudeCommandToClaudeSkill`)
already routed it through `yamlQuote`. Brought all six in line:

- convertClaudeCommandToCopilotSkill
- convertClaudeAgentToCopilotAgent
- convertClaudeCommandToAntigravitySkill
- convertClaudeAgentToAntigravityAgent
- convertClaudeCommandToTraeSkill
- convertClaudeCommandToCodebuddySkill

Each now wraps the value in `yamlQuote(...)` so any leading character is
parser-safe.

Regression test (tests/bug-2876-skill-frontmatter-quote.test.cjs) drives
the four command converters and two agent converters through the
reporter's exact "[BETA] …" description plus a grab-bag of YAML flow
indicators, asserting the emitted `description:` value is a quoted YAML
scalar. Also round-trips the value through `JSON.parse` for converters
that don't apply runtime-name substitution to confirm fidelity.

Updated 7 pre-existing substring assertions in copilot-install.test.cjs
and antigravity-install.test.cjs that hard-coded the unquoted form.
Round trip: 5893/5893 pass on `npm test`.

Closes #2876

* test(#2876): structurally parse frontmatter instead of substring-grep

Addresses CodeRabbit's two nitpicks on PR #2881: the pre-existing
substring assertions in copilot-install.test.cjs (4 sites) and
antigravity-install.test.cjs (3 sites) only got bumped from the unquoted
form (`description: Diagnose...`) to the quoted-prefix form
(`description: "Diagnose...`). Both are still raw-string checks against
rendered YAML and drift on any quoting/order change — exactly what the
project's CONTRIBUTING.md "no-source-grep" testing standard exists to
prevent.

Add `parseFrontmatter()` to tests/helpers.cjs — a small parser that
handles the YAML scalar forms the install converters emit (double-quoted
JSON, single-quoted with `''` escape, bare). Throws if the content has
no closed `---` block so a regression in the emitter shape fails loudly
rather than silently returning {}.

Refactor the 7 description-substring sites to compare on parsed values:
the assertion now reads as `fm.description === 'Diagnose planning
directory health'` rather than `result.includes('description: "Diagnose
planning directory health')`. Same coverage of the #2876 quoting
behavior, no coupling to byte-level quote style.

`npm test`: 5893/5893 pass.

Closes #2876

* test(#2876): make parseFrontmatter delimiter check CRLF/whitespace tolerant

CR nitpick on PR #2881 (review at 03:08:08Z): parseFrontmatter()
splits on '\n' and compares each line strictly to '---'. A
Windows-authored skill file (CRLF endings) leaves a trailing '\r'
on every line, so '---\r' fails the equality check, and the helper
throws "no closed --- block" on perfectly valid input. Same problem
with whitespace-padded delimiter lines.

Switch to splitting on /\r?\n/ and comparing the trimmed line.
Helper is used by tests/copilot-install.test.cjs and
tests/antigravity-install.test.cjs, so this also de-flakes those
suites on Windows runners.

5893/5893 on `npm test`.
2026-04-29 23:27:27 -04:00
Jeremy McSpadden
4d394a249d fix(commands): normalize gsd slash namespace drift (#2858)
* fix(commands): normalize gsd slash namespace drift

* fix(#2855): address CodeRabbit findings on namespace drift PR

Three CR findings, all valid:

1. autonomous.md line 783 still had `gsd:discuss-phase` (the PR's own
   normalization missed this line). Switched to `gsd-discuss-phase` and
   updated the matching test in autonomous-interactive.test.cjs that was
   asserting the now-retired colon form.

2. tests/bug-2543-gsd-slash-namespace.test.cjs source-grepped the
   fix-slash-commands.cjs script with .includes() rather than driving
   its transform behaviour. Refactored fix-slash-commands.cjs to export
   a pure transformContent(src, cmdNames) function, kept the CLI behaviour
   unchanged via require.main, and replaced the source-grep block with
   five behavioural cases: rewrite, multi-occurrence, idempotence on
   canonical input, no-op on gsd-sdk/gsd-tools, and word-boundary safety.

3. tests/bug-2808-skill-hyphen-name.test.cjs matched `name:` anywhere
   in SKILL.md; a stray name: in the body could satisfy the assertion.
   Scoped the lookup to the YAML frontmatter block via the suggested
   diff (parse the leading --- ... --- region first, then find name:
   inside it).

Full suite: 5854/5854 passing.

* fix(#2855): address remaining CodeRabbit findings on PR #2858

Three structural concerns flagged on the namespace-drift fix PR:

1. scripts/fix-slash-commands.cjs:24 — `buildPattern([])` compiled
   `/gsd:()(?=[^a-zA-Z0-9_-]|$)/g`. The empty capture group still
   matches any `/gsd:` token followed by a non-word boundary
   (whitespace, EOL, punctuation), rewriting it to a stray `/gsd-`.
   Verified live: `transformContent("/gsd:", [])` → `"/gsd-"`. Added
   a guard returning null from `buildPattern` on empty input and
   updated `transformContent` and `processDir` to no-op when the
   pattern is null.

2. tests/autonomous-interactive.test.cjs:44-47 — assertion was
   `content.includes('gsd-discuss-phase') && content.includes('INTERACTIVE')`,
   which would false-pass on any unrelated co-occurrence (e.g.
   `INTERACTIVE=""` initialization plus a stray `gsd-discuss-phase`
   prose mention). Replaced with a structural extraction: locate the
   `**If \`INTERACTIVE\` is set:**` branch, bound it by the next
   `**If` / `<step>` boundary, and assert the
   `Skill(skill="gsd-discuss-phase", ...)` invocation lives inside
   that region. Tolerates whitespace around `(`, `skill`, and `=`.

3. tests/bug-2808-skill-hyphen-name.test.cjs:104 — colon-call regex
   was `Skill\(skill=...` and missed valid formatting like
   `Skill(skill = "gsd:cmd")` or `Skill( skill = ...)`. Loosened to
   `Skill\(\s*skill\s*=\s*...` so reformatting drift can't slip past
   the namespace guard.

Verification: 5854/5854 pass on `npm test` from the rebased branch.

* fix(#2855): drop pre-validation filter that hid namespace drift

CR finding on tests/bug-2808-skill-hyphen-name.test.cjs:128: the test
collected generated skill directories with
`.filter(entry => entry.isDirectory() && entry.name.startsWith('gsd-'))`,
then validated namespace invariants over that filtered list. Anything
that violated the prefix invariant — `gsd:extract-learnings` (colon
form), `extract_learnings` without prefix, `Gsd-foo` mis-cased — would
silently disappear from the iteration and the test would falsely pass.

Drop the `startsWith('gsd-')` filter so every generated directory shows
up. Add explicit assertions before the existing per-skill loop:
  - directory list is non-empty (catches a broken converter that
    produces nothing)
  - every directory begins with `gsd-`
  - every directory contains no `:`
  - every directory contains no `_`

Re-audited the full PR diff for the same anti-pattern: only this one
site filtered before validating the namespace; bug-2643 and
commands-doc-parity also use `readdirSync().filter()` but only by file
extension, which is correct.

5854/5854 on `npm test`.

* fix(#2855): address remaining CR findings (1 active + 2 nitpicks)

Three findings on PR #2858, all the same root cause: input narrowing
before validation lets drift slip past the guards.

1. tests/bug-2808-...:104 (active) — `colonCallRe` captured local names
   with `[a-z0-9-]+`, which excluded the underscore. A drift like
   `Skill(skill="gsd:extract_learnings")` (deprecated colon syntax with
   the old underscore filename) silently slid through. Broadened the
   capture to `[^'"\s)]+` so any malformed local name is surfaced; surrounding pattern (whitespace tolerance, escape support, flags)
   unchanged.

2. tests/bug-2643-...:43 (nitpick) — `extractSkillNamesHyphen` and
   `extractSkillNamesColon` had the same over-strict capture plus
   relied on a single regex over raw bytes, which the project test-
   rigor memory bans (`feedback_no_source_grep_tests.md`). Replaced
   with `extractSkillCalls(content)` — a small structural extractor
   that walks `Skill(` openers, locates each call's matching `)`,
   parses the body's `skill = "..."` keyword argument with permissive
   whitespace + quoting + escape handling, and returns
   `{ name, raw }` records. The two namespace-form helpers become
   thin filters over the structured output. Tightened the body class
   to `[^'"\\]+` so a trailing escape `\` before the closing quote
   (as in `Skill(skill=\"gsd-foo\", …)` written inside another string
   context) doesn't get included in the captured name.

3. tests/bug-2543-...:44 (nitpick) — `DOC_SEARCH_FILES` was a hand-
   curated 7-entry array. Every doc added in the future would silently
   weaken drift detection until someone remembered to extend the list.
   Replaced with `discoverDocSearchFiles(ROOT)`: globs every `.md`
   under `docs/` and adds `README.md` if present. New docs are picked
   up automatically.

Re-audited the diff surface for similar narrowings; no other sites
filter or constrain before validating namespace invariants.

5854/5854 on `npm test`.

* fix(#2855): recurse docs/ tree so localized translations are scanned too

CR finding: discoverDocSearchFiles() stopped at docs/*.md, leaving
localized translation trees (docs/ja-JP/, docs/zh-CN/, docs/ko-KR/,
docs/pt-BR/) and other nested doc collections (docs/skills/,
docs/superpowers/) invisible to the namespace-drift invariant.

Verified the gap: docs/ has 6 nested directories with ~30 .md files
that the previous top-level-only scan was skipping. None contain
/gsd: references today, but a future translation update or new
doc subdir could leak drift.

Switch to an iterative stack walk so every .md under docs/ is scanned
regardless of depth. Stack form (rather than recursion) avoids the
risk of running into the call-stack limit on deep doc trees.

5854/5854 on `npm test`.

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-04-29 22:56:59 -04:00
Oleksander Palian
73b9d1dac0 fix(install): use colon namespace for Gemini slash commands (#2768)
* fix(install): use colon namespace for Gemini slash commands and help reference

This fixes unexecutable command recommendations in Gemini CLI by correctly
namespacing slash commands (/gsd: instead of /gsd-) in all installed
artifacts (agents, commands, workflows).

- Implements a lazy command roster discovery to ensure 100% accurate
  conversion and protect file paths, URLs, and agent names.
- Adds isolated behavioral and unit tests covering all boundary cases.
- Fixes hardcoded command strings in banners and help output.

Closes #2783

* fix(install): close roster gaps in Gemini /gsd- → /gsd: conversion (#2783)

Addresses adversarial review findings on PR #2768:

- Restore regex boundaries (lookbehind + extension lookahead). Roster-only
  matching was insufficient: a URL like `https://example.com/gsd-plan-phase`
  ends in a known command and would be incorrectly converted. Boundaries +
  roster now agree before any conversion fires.
- Smarter trailing lookahead `(?!\.[a-z])` distinguishes file extensions
  (`.cjs`, `.md`) from sentence-ending punctuation (`.` at end of input or
  before whitespace), so `/gsd-help.` correctly converts.
- Fail loud on missing roster. `commands/gsd/` not found previously fell
  through to an empty Set, silently no-op'ing every conversion — exactly the
  bug this code exists to prevent. Now emits a one-shot console.warn (gated
  on GSD_TEST_MODE) before returning the empty set.
- Drop unnecessary `i` flag — GSD commands are always lowercase; matching
  uppercase tokens against a lowercase roster always misses anyway.
- Export `_resetGsdCommandRoster` for test isolation against the module-level
  cache.

Test additions pin the actual safety property of the roster check by using
KNOWN command names embedded in URLs and sub-paths — the cases the prior
tests didn't reach because they used `gsd-tools` (not in roster). Added a
roster-load assertion that fails loudly if the empty-Set fallback path
silently neutralises conversions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(install): centralize <sub> stripping and add structural test assertions

CodeRabbit findings on the prior commit:

- (actionable) Centralizing the Gemini conversion through
  convertClaudeToGeminiMarkdown dropped the stripSubTags() call that the
  inline command path used to make before TOML conversion. Move stripSubTags
  inside convertClaudeToGeminiMarkdown so command/agent/non-command Gemini
  outputs all have <sub> consistently stripped. Remove the now-redundant
  stripSubTags call in convertClaudeToGeminiAgent (single source of truth).
- (nitpick) Replace `.includes()` checks in the TOML test with structured
  parsing — JSON-decode each TOML value and assert on parsed fields, per
  the project's "tests parse, never grep" convention.
- (nitpick) Strengthen the install behavioral test to read a real installed
  artifact (.gemini/commands/gsd/plan-phase.toml), parse it, and assert the
  prompt body actually contains a /gsd: reference and no unconverted
  /gsd-plan-phase. A directory-only check would have passed even if every
  conversion silently no-op'd.
- Add a regression test that <sub> tags are stripped through the
  convertClaudeToGeminiMarkdown pipeline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 22:37:57 -04:00
Tom Boucher
99af76b3ba fix(#2851): replace bare gsd-tools invocations with absolute path (#2869)
* fix(#2851): replace bare gsd-tools invocations with absolute path

`gsd-tools` is not a published bin entry — package.json declares only
get-shit-done-cc and gsd-sdk. The shipped invocation pattern is
`node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" <subcommand>`,
used by every other workflow file.

Two leaked bare invocations:

- get-shit-done/workflows/plan-phase.md §13e (gap-analysis)
  — reported in #2851; gap-analysis silently skipped on every plan-phase run
- get-shit-done/workflows/ingest-docs.md §finalize (commit)
  — caught by the new structural test; ingest-docs commit step was broken

Both updated to canonical absolute-path form.

Adds tests/bug-2851-workflow-bare-gsd-tools.test.cjs which parses every
markdown file under get-shit-done/workflows/, extracts shell-fenced code
blocks, tokenizes each line, and asserts no token in command position is
the bare string `gsd-tools` (the trailing `.cjs` is a different token).
The test also asserts plan-phase.md's gap-analysis call uses the canonical
`node …/gsd-tools.cjs` form.

Closes #2851

* fix(#2851): catch third bare gsd-tools call in ingest-docs.md init

After the first commit, the structural test was strengthened to detect
bare `gsd-tools` inside `$(...)` and backtick command-substitution forms.
The improved test surfaced a third leak:

  ingest-docs.md:55: INIT=$(gsd-tools init ingest-docs)

Fixed to canonical form
  INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init ingest-docs)
plus the standard `@file:` handoff line that every other workflow uses
when capturing INIT (required by tests/windows-robustness.test.cjs).

Updated tests/bug-2801-ingest-docs-handler.test.cjs to match either
the bare `gsd-tools init ingest-docs` or canonical
`gsd-tools.cjs" init ingest-docs` form — the test's intent is to verify
the dispatch handler is wired, not to lock the bare-bin form that #2851
removes.

Closes #2851

* test(#2851): tighten ingest-docs and gap-analysis assertions to canonical form

CodeRabbit caught two soft assertions in the regression tests:

1. tests/bug-2801: the init-ingest-docs assertion accepted both the
   legacy bare `gsd-tools` form and the canonical node-path form.
   Since #2851 is the fix that removes the bare form, the test should
   only accept the canonical absolute-path invocation. Switched to
   parsed-bash-block extraction with an anchored regex on the full
   `node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs"` path.

2. tests/bug-2851: the gap-analysis assertion used two loose
   .includes()/word-boundary checks. Replaced with a single
   assert.match() against the full canonical path so non-canonical
   forms fail.

* test(#2851): env-assignment skip accepts lowercase identifiers too

CodeRabbit caught: the cmdIdx-skip regex /^[A-Z_][A-Z0-9_]*=/ only
matched uppercase variable names, so a line like `tmp=1 gsd-tools init`
would tokenize to ['tmp=1','gsd-tools','init'], the regex would fail on
'tmp=1', cmdIdx would stay at 0, and the command-position check would
compare 'tmp=1' against 'gsd-tools' — false negative.

POSIX shell variable names are [A-Za-z_][A-Za-z0-9_]*. Widen the regex
to match the actual lexical rule. Existing uppercase forms still work
(FOO=bar gsd-tools); now lowercase forms (tmp=1 gsd-tools) and mixed
case forms are also detected.
2026-04-29 21:52:20 -04:00
Tom Boucher
ef08a89241 fix(#2866): Codex installer strips legacy hooks at EOF without trailing newline (#2870)
* fix(#2866): Codex installer strips legacy hooks at end-of-file without trailing newline

The four shape-strip regexes in `bin/install.js` (Codex install path)
required `\r?\n` at end. A stale GSD hook block sitting at end-of-file
without a trailing newline (common — many editors strip them, and the
legacy installer never wrote one) failed every shape, the installer
saw `gsd-check-update` already present, skipped writing the new
Nested-AoT block, and Codex 0.125+ refused to load with
  invalid type: map, expected a sequence in `hooks`

Root cause + fix
================

Each shape's terminator changed from `\r?\n` to `(?:\r?\n|$)`, so
end-of-file is also a valid terminator.

Strip logic was lifted into a new pure helper
`stripStaleGsdHookBlocks(configContent)` that the install pipeline now
calls in place of the inline replace chain. The helper is exported via
the GSD_TEST_MODE module.exports for direct unit-test coverage.

Regression test
===============

`tests/bug-2866-codex-strip-no-trailing-newline.test.cjs` exercises
all four historical shapes (Shape 1 — pre-#1755 gsd-update-check;
Shape 2 — flat [[hooks]]+gsd-check-update; Shape 3 — single
[[hooks.SessionStart]] without nested .hooks; Shape 4 — correct
two-block nested) twice each: once with a trailing newline (regression
guard against the existing behavior) and once at end-of-file without a
trailing newline (the reporter's exact repro).

It also asserts:
- the helper is a no-op when no GSD reference is present, and
- Shape 4 strip does not leave an orphaned [[hooks.SessionStart]]
  header behind (the same ordering invariant the inline code relied on).

The helper is loaded via `package.json` `bin` field, not a hardcoded
path — `tests/bug-2866-codex-strip-no-trailing-newline.test.cjs`
parses package.json and resolves `pkg.bin['get-shit-done-cc']` to
require the installer.

Closes #2866

* test(#2866): assert TOML structure, not raw-text substrings

CodeRabbit caught the strip assertions using `.includes()` against
raw TOML output. Added a small line-structural parseTomlShape() helper
(table headers + dotted-path key/value map, comments stripped) and
rewrote the assertions to:

- Verify no [[hooks.* table header survives the strip
- Verify no key carries a stale gsd-(update|check)-(check|update) value
- Verify history.persistence is preserved as the parsed string "save-all"

Behaviour is unchanged (the strip function under test is not modified).
The assertions now check structural shape rather than substring presence,
which catches re-shaping regressions that text matching would miss.

No new dependencies — the parser is local to the test and handles only
the small well-formed TOML these tests construct.

* refactor(#2866): replace regex hook strip with TOML AST removal

Per CR feedback on PR #2870: the regex-driven `stripStaleGsdHookBlocks`
implementation was fragile to whitespace, indentation, and key-ordering
variations the regression test never exercised. Variations the regex
silently leaked (verified before the rewrite):

- Shape 4 with an extra blank line between parent/child tables
- Shape 2/3 with `command` ordered before `event`
- Shape 3 with an extra `timeout = 5000` key — worse than a leak: the
  regex matched only the command line, leaving `timeout = 5000`
  orphaned outside any TOML table (invalid TOML)
- Tight whitespace `event="SessionStart"` (no spaces around `=`)

The structural rewrite uses the TOML parser already present in this file
(`getTomlTableSections` + `getTomlLineRecords` + `parseTomlValue` +
`removeContentRanges` + `collapseTomlBlankLines`):

1. Find every section whose path is `hooks` or starts with `hooks.`.
2. For each, walk the section's line records and parse `command` values
   structurally — match by basename equality (`gsd-update-check.js` or
   `gsd-check-update.js`), never by regex on raw bytes.
3. Detect orphaned `[[hooks.SessionStart]]` parents: empty body and a
   stale child immediately follows → mark for removal.
4. Extend each removal range backward through any preceding
   `# GSD Hooks` marker line (detected via line records, not text scan).
5. Remove ranges atomically and collapse resulting blank-line runs.

Legacy hook basenames are hoisted to template-literal constants so the
existing `install-hooks-copy.test.cjs` quoted-literal guard continues to
catch accidental *registration* of the inverted filename, while strip
detection (which legitimately needs both names) bypasses it.

Test coverage added: 8 new sub-tests exercising the four whitespace/
ordering variations (with and without trailing newline) plus a
`[[hooks.UserPromptSubmit]]` user-authored hook to guarantee the strip
only touches GSD-managed sections. 20/20 in the file, 5867/5867 in the
full suite.
2026-04-29 21:51:58 -04:00
Tom Boucher
f2ada8500c chore(#2868): switch canary publish from main to dev branch (#2871)
* chore(#2868): switch canary publish from main to dev branch

Swaps the four `if:` guards in `.github/workflows/canary.yml` from
`refs/heads/main` to `refs/heads/dev` so the canary stream is owned
by the new long-lived integration branch. Adds a policy comment at
the top of the workflow documenting the branch->dist-tag mapping
(dev=@canary, main=@next/@latest, no overlap).

Closes #2868

* fix(#2868): summary block matches publish-step gate

CodeRabbit caught: the Summary step keyed off DRY_RUN only, so a
non-dry-run on main would falsely report "Published"/"Tagged" even
though all four publish steps were skipped by the new dev-only gate.

Add PUBLISH_ELIGIBLE env mirroring the publish-step `if:` expression
and a VALIDATION ONLY branch in the summary so non-dev runs report
honestly.
2026-04-29 17:43:30 -04:00
Tom Boucher
f6a6e43226 fix(#2872): auto-close PRs that omit the issue-link keyword (#2873)
The Require Issue Link workflow was posting a comment and failing the
status check, but never transitioning the PR to closed. PR templates
promise auto-close behavior; PR #2863 demonstrated the gap (opened
without a Closes #N, sat open until manually closed).

Adds a `pulls.update({state: 'closed'})` call after the existing
comment, updates the comment heading to 'PR auto-closed', and tells
the author how to reopen after fixing the body.

Closes #2872
2026-04-29 17:40:18 -04:00
Tom Boucher
107a83ebf7 docs(#2859): add release notes for 1.39.0-rc.7 (#2860)
rc.7 will be the first RC in the 1.39.0 train that actually rolls in
the post-rc.5 fixes from main (rc.6 was content-identical to rc.5 — see
#2856). Notes enumerate each fix with PR/issue link, recap rc.6 / rc.5 /
rc.4, and follow the established docs/RELEASE-v1.39.0-rc.X.md format.

No SDK-version pinning advice (consistent with the rc.6 doc cleanup).
Markdownlint-clean fenced code blocks.

Closes #2859
2026-04-29 08:58:16 -04:00
Tom Boucher
43a13217b7 docs(#2856): add docs/RELEASE-v1.39.0-rc.6.md (#2857)
* docs(#2856): add release notes for 1.39.0-rc.6

Documents what's actually in rc.6 (= rc.5 content + version-bump only —
release/1.39.0 was not synced with main before the bump) plus the known
SDK publish failure (@gsd-build/sdk@1.39.0-rc.6 is missing from npm with
404 PUT error). Format mirrors RELEASE-v1.39.0-rc.5.md.

Closes #2856

* docs(#2856): drop SDK refs from rc.6 notes; tag git log fence

Per maintainer + CodeRabbit review:
- Strip the 'Known issue: split publish' section, the SDK pin Note, and
  the @gsd-build/sdk follow-up bullet. SDK publish failure is a known
  separate issue and shouldn't block the rc.6 docs.
- Add bash language tag to the git log fence (markdownlint MD040).
2026-04-29 08:43:39 -04:00
Tom Boucher
2498f5649d docs(release): backfill CHANGELOG with 17 RC-train entries before v1.39.0 final cut (#2854)
Adds [Unreleased] entries for PRs that landed between v1.39.0-rc.4 and
v1.39.0-rc.6 but were missing from CHANGELOG.md. One bullet per PR,
grouped Added (#2828) and Fixed (16 entries: #2788, #2791, #2794, #2796,
#2798, #2801, #2803, #2805, #2808, #2829, #2831, #2832, #2835, #2836,
#2838, #2839).

Closes #2853
2026-04-29 08:29:47 -04:00
Tom Boucher
e81592878e feat(#2789): trim skill description anti-patterns; enforce 100-char budget (#2823)
* feat(#2789): trim skill description anti-patterns; enforce 100-char budget

- Trim descriptions in all commands/gsd/*.md files over 100 chars
- Remove flag documentation from descriptions (belongs in argument-hint)
- Remove Triggers: keyword stuffing
- Add scripts/lint-descriptions.cjs — fails on descriptions > 100 chars
- Add npm script: lint:descriptions
- Add tests/enh-2789-description-budget.test.cjs

Closes #2789

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#2789): add CHANGELOG entry for description budget lint

* docs(#2789): update COMMANDS.md descriptions; add skill description standards note

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 08:14:11 -04:00
Tom Boucher
4815b3c972 fix(#2838): SUMMARY rescue handles gitignored .planning (#2850)
* fix(#2838): SUMMARY rescue handles gitignored .planning explicitly

The pre-fix rescue used `git ls-files --modified --others --exclude-standard`
to detect uncommitted SUMMARY.md before worktree removal. When projects
gitignore .planning/, --exclude-standard filters out the very files the
rescue is meant to save, the rescue branch is skipped, and `git worktree
remove --force` permanently deletes the SUMMARY.

Replace both rescue blocks (quick.md, execute-phase.md) with a
filesystem-level find + cp rescue that bypasses gitignore entirely and
avoids the worktree↔main commit/merge cascade. cmp -s makes it idempotent.

Adds tests/bug-2838-summary-rescue-gitignored-planning.test.cjs which
extracts each rescue block, runs it against a real temp repo with a
gitignored .planning/, and asserts the SUMMARY survives worktree removal.

* test(#2838): assert rescue block exits 0 in idempotency test

CodeRabbit (Minor): the idempotency test pre-creates the destination
SUMMARY.md, so even a syntax/runtime error in the rescue block would
silently false-pass. Add an explicit r.status === 0 assertion.
2026-04-29 08:07:12 -04:00
Tom Boucher
f9ed47ac8b fix(#2832): gsd-sdk auto detects Codex runtime correctly (#2844)
* fix(#2832): gsd-sdk auto detects Codex runtime correctly

Two-part fix for #2832 (gsd-sdk auto silently routing non-Claude runtime
projects through the Claude Agent SDK):

1. Runtime gate at the `auto` entry point. New `runtime-gate.ts` exports
   `assertRuntimeSupportsAutoMode(config)` which throws an actionable error
   when `GSD_RUNTIME` / `config.runtime` resolves to a non-Claude runtime
   (codex, gemini, opencode, etc.). The autonomous orchestrator only knows
   how to drive `@anthropic-ai/claude-agent-sdk` today; failing fast with a
   clear pointer at the in-session slash commands beats the previous instant
   `[FAILED] $0.00 0.1s` flake. Wired into `cli.ts` before the GSD/InitRunner
   construction.

2. Runtime-aware `resolveModel()` in `session-runner.ts`. The profile -> id
   map (`balanced -> claude-sonnet-4-6`, etc.) was applied unconditionally,
   so even with `runtime: codex` and `resolve_model_ids: omit` the SDK
   forced a Claude id into `query()`. Now the profile map only fires when
   the runtime is Claude and the explicit `resolve_model_ids: "omit"` knob
   short-circuits to undefined, mirroring `query/config-query.ts`.

Tests (vitest, sdk/src):
- runtime-gate.test.ts (8 cases): claude / unset / unknown pass; codex,
  gemini, opencode throw; GSD_RUNTIME wins over config.runtime; error
  message references #2832 and the slash-command workaround.
- session-runner.test.ts (4 new cases under "resolveModel runtime
  awareness (#2832)"): codex runtime + balanced profile -> no model
  injected; resolve_model_ids: omit -> no model; claude runtime still
  resolves to claude-sonnet-4-6 (no regression); explicit options.model
  wins on any runtime.

* fix(#2832): address CR — env-precedence in resolveModel + accurate source attribution

Two CodeRabbit findings on PR #2844:

1. session-runner.ts:resolveModel() (Major) — read runtime via detectRuntime()
   so GSD_RUNTIME env precedence is honored. Without this, a Codex run with
   a Claude-shaped config still fell into the Claude-only profile-id branch.

2. runtime-gate.ts:assertRuntimeSupportsAutoMode() (Minor) — when GSD_RUNTIME
   holds an unsupported value, detectRuntime() falls through to config but
   the source label still reported the discarded env value. Fix: validate
   env against SUPPORTED_RUNTIMES before attributing the source.

Tests added for both: env-precedence in session-runner, source attribution
in runtime-gate. 17/17 pass.
2026-04-29 08:03:32 -04:00
Tom Boucher
91194cdbff chore(#2828): add canary release workflow (#2830)
* chore(#2828): add canary release workflow (dev builds on push to main)

Publishes get-shit-done-cc@canary and @gsd-build/sdk@canary on every
push to main. Version format: {base}-canary.{N} where base strips any
pre-release suffix from package.json (1.39.0-rc.4 → 1.39.0-canary.1).

Sequential canary number is auto-detected from existing git tags so
reruns never collide. Concurrency group cancels stale in-flight canary
runs when commits land quickly.

Mirrors the structure and steps of release.yml: same checkout pins,
Node 24, npm-publish environment, build:sdk, tarball verification,
dry-run publish gate, and publish verification with sleep 10.

Closes #2828

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2828): address CodeRabbit review findings on canary.yml

- cancel-in-progress: false — was true, allowing a newer push to cancel a
  run mid-publish (after tag push but before SDK publish), leaving a partial
  release state that's unrecoverable since npm versions are immutable

- Guard tag/publish/verify steps with github.ref == 'refs/heads/main' so
  a manual workflow_dispatch from a feature branch (dry_run defaults false)
  cannot accidentally publish unmerged code under the shared canary dist-tag

- Replace fixed sleep 10 with exponential backoff retry loop (delays: 5 10
  20 30 45s); fixed sleep is flaky against normal npm CDN replication lag
  and a false failure forces a new canary number since the tag already exists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plan-phase): expose --mvp flag in command frontmatter

Adds --mvp to argument-hint and Flags doc. Workflow handler in next commit.

* chore(#2828): remove push:main trigger from canary workflow

Submission rate to main is too high to auto-publish a canary on every
merge. Restrict the workflow to manual workflow_dispatch only.

Closes #2828

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 08:02:59 -04:00
Tom Boucher
74b81379cf fix(#2836): audit-open quick SUMMARY filename + UAT terminal-status drift (#2847)
* fix(#2836): audit-open quick SUMMARY filename + UAT terminal-status drift

Fixes two convention drifts in bin/lib/audit.cjs that produced false-positive
"open" items at every milestone close:

1. scanQuickTasks: looked for bare `SUMMARY.md`, but workflows/quick.md
   mandates `${quick_id}-SUMMARY.md`. Now matches either filename so quick
   tasks created via the documented workflow are recognized.

2. scanUatGaps: only treated `status: complete` as terminal, but
   workflows/execute-phase.md uses `status: resolved` post-gap-closure.
   Now treats both `complete` and `resolved` as terminal, with `result:
   all_pass` as a fallback when status is absent.

Also reconciles workflows/help.md one-liner that referenced bare
`SUMMARY.md` so docs match the authoritative quick.md workflow.

Adds tests/bug-2836-audit-open-summary-uat-drift.test.cjs with 6
structural regression tests covering both fixes plus no-regression cases.

* refactor(#2836): hoist TERMINAL_UAT_STATUSES outside scanUatGaps loop

Address CodeRabbit nitpick: the Set was being recreated on each UAT file
iteration. Hoist to module scope so it is constructed once.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 08:00:17 -04:00
Tom Boucher
12b6ba4e34 fix(#2829): gsd-sdk resolvable in local-mode installs (#2848)
* fix(#2829): gsd-sdk resolvable in local-mode installs

Local-mode installs previously short-circuited installSdkIfNeeded() the
moment opts.isLocal was true, leaving every `gsd-sdk query …` call site
unable to resolve the binary on PATH. The published tarball ships
sdk/dist/cli.js and bin/gsd-sdk.js regardless of mode, and the shim
resolves the CLI relative to its own __dirname — so the same self-link
strategy that powers npx-cache global installs (#2775) also works for
local installs. We now run the shared self-link path whenever the dist
is present, and only fall back to a non-fatal warning + early return
when the dist is genuinely missing (preserving the #2678 contract).

* test(#2829): correct precondition comment about ~/.local/bin

Address CodeRabbit feedback — the test does not create ~/.local/bin,
so reword the inline precondition to "any HOME bin candidate remains
off-PATH" to match what the test actually sets up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 07:59:30 -04:00
Tom Boucher
f4412349f0 fix(#2835): align CR-INTEGRATION tests with hyphen-form skill names (#2843)
* fix(#2835): align CR-INTEGRATION tests with hyphen namespace

PR #2819 changed autonomous.md skill invocations from `gsd:code-review`
(colon) to `gsd-code-review` (hyphen). Tests still asserted the legacy
colon form against the user-installed plugin dir (which lags the repo).

Switch tests to:
- Read autonomous.md from the canonical repo WORKFLOWS_DIR (not the
  plugin install location, which can be stale)
- Parse `Skill(skill="...")` invocations structurally instead of
  substring matching, and assert the canonical hyphen form is present
  while explicitly rejecting the legacy colon form.

Closes #2835

* test(#2835): parse Skill() invocations structurally in CR-INTEGRATION tests

Replace raw-text regex/.includes() assertions with a proper parser that
walks autonomous.md, skips escaped string contexts, and yields
[{ skill, args }] objects. The three CR-INTEGRATION tests now assert
against parsed fields and tokenized args (not substring matches),
addressing CodeRabbit feedback on PR #2843.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 07:57:30 -04:00
Tom Boucher
a7f83ee663 fix(#2831): expand HOME in OpenCode @file references on all platforms (#2842)
* fix(#2831): expand HOME in OpenCode skill/template paths

OpenCode does not shell-expand $HOME in @file references on any platform —
the literal `@$HOME/...` path is resolved relative to the config command/
dir, producing `command/$HOME/...` (file not found). The previous fix for
#2376 only guarded Windows; extend to all platforms.

Closes #2831

* test(#2831): assert behavior via exported computePathPrefix, not source grep

Addresses CodeRabbit review on PR #2842:
- Extracts pathPrefix logic into a named, test-exported computePathPrefix
  helper in bin/install.js (no behavior change at the call site).
- Rewrites bug-2376 and bug-2831 regression tests to call the exported
  function directly instead of regex-matching install.js source text,
  per the repo's no-source-grep testing standard.
- Wraps temp-dir test setup in try/finally so cleanup runs on assertion
  failures (no leaked tmp dirs).
2026-04-29 07:56:51 -04:00
Tom Boucher
7fae804296 fix(#2839): transactional cleanup tail for /gsd-code-review-fix (#2846)
* fix(#2839): make /gsd-code-review-fix cleanup transactional

Cleanup tail in agents/gsd-code-fixer.md previously did 'git worktree
remove' without any recovery marker. If the process was killed between
fix commits and worktree removal, the orphan worktree + branch survived
with no resume path — the next run had no way to discover or finish
the cleanup.

Introduce a recovery sentinel at ${phase_dir}/.review-fix-recovery-pending.json
with strict ordering:
- Sentinel written AFTER 'git worktree add' succeeds (never points at a
  worktree that does not exist).
- Sentinel removed ONLY AFTER 'git worktree remove' returns successfully
  (interruption between commits and removal leaves a sentinel behind).
- New runs detect a pre-existing sentinel, force-remove the recorded
  orphan worktree, then drop the stale sentinel before continuing —
  making the agent self-healing after a crash.

Closes #2839

* fix(#2839): harden sentinel JSON parse and scope ordering assertion

Address CodeRabbit review feedback on PR #2846:

- agents/gsd-code-fixer.md: Guard the recovery-sentinel JSON parse with
  try/catch so a corrupted/truncated sentinel (a realistic crash artifact)
  emits a warning and yields an empty prior_wt instead of aborting setup.
  This preserves the self-healing recovery path even when the sentinel
  itself is the casualty of the original crash.

- tests/bug-2839-review-fix-transactional-cleanup.test.cjs: Scope the
  cleanup-ordering assertion to the cleanup-tail section of the
  setup_worktree step rather than first global occurrences. Previously
  the assertion could pass on pre-recovery references even if cleanup-tail
  ordering regressed. The regex also now accepts the shell-variable form
  (\`rm -f \"\$sentinel\"\`) used in the cleanup tail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 07:56:32 -04:00
Tom Boucher
c3a42d66f9 Revert "feat(install): add Hermes Agent runtime support" (#2849) 2026-04-29 07:44:49 -04:00
Jeremy McSpadden
0acf1de88c Merge pull request #2845 from teknium1/feat/hermes-runtime
feat(install): add Hermes Agent runtime support
2026-04-29 06:38:13 -05:00
teknium1
5a636bc90a feat(install): add Hermes Agent runtime support (#2841)
Adds Hermes Agent as a supported installation target. Users can run
\`npx get-shit-done-cc --hermes\` to install all 86 GSD commands as
skills under \`~/.hermes/skills/gsd-*/SKILL.md\`, following the same
open skill standard as Claude Code 2.1.88+, Qwen Code, Antigravity,
Trae, Augment, and Codebuddy.

Hermes Agent is an open-source AI agent framework by Nous Research
(NousResearch/hermes-agent, MIT). Its skill loader accepts the Claude
skill format as-is: frontmatter parsed with PyYAML SafeLoader (unknown
keys like \`allowed-tools\` / \`argument-hint\` ignored), body XML tags
(\`<objective>\`, \`<execution_context>\`, \`<process>\`) passed directly
to the model. Compatibility proven end-to-end with all 86 GSD skills
loading cleanly, \`skill_view()\` returning full bodies, and
\`build_skills_system_prompt()\` emitting them into the agent system
prompt — zero Hermes code changes required.

Changes:
- \`bin/install.js\`: --hermes flag, getDirName/getGlobalDir/getConfigDirFromHome
  support, HERMES_HOME env var (native to Hermes — used for profile
  mode / Docker deploys), install/uninstall pipelines, interactive
  picker option 10 (alphabetical: between Gemini and Kilo), .hermes
  path replacements in copyCommandsAsClaudeSkills and
  copyWithPathReplacement, legacy commands/gsd cleanup, CLAUDE.md ->
  HERMES.md and "Claude Code" -> "Hermes Agent" content rewrites in
  skills/agents/hooks, runtime-appropriate finish message.
- \`get-shit-done/bin/lib/core.cjs\`: add hermes to KNOWN_RUNTIMES;
  add RUNTIME_PROFILE_MAP.hermes with OpenRouter-slug defaults
  (Hermes is provider-agnostic; these defaults resolve across
  OpenRouter, native Anthropic, and Copilot via Hermes' aggregator-
  aware resolver, and are overridable per-tier via
  model_profile_overrides.hermes.{opus,sonnet,haiku}).
- \`README.md\`: Hermes Agent in tagline, runtime list, verification
  command, install/uninstall examples, \`--hermes\` flag reference.
- \`tests/hermes-install.test.cjs\`: new, 14 tests covering directory
  mapping, HERMES_HOME env var precedence, install/uninstall
  lifecycle, user-skill preservation, engine cleanup.
- \`tests/hermes-skills-migration.test.cjs\`: new, 11 tests covering
  frontmatter conversion, path replacement (~/.claude/ ->
  \$HERMES_HOME/skills/), CLAUDE.md -> HERMES.md, "Claude Code" ->
  "Hermes Agent", stale skill cleanup, SKILL.md format validation.
- \`tests/multi-runtime-select.test.cjs\`: updated for new option
  numbering (hermes=10, kilo=11, opencode=12, qwen=13, trae=14,
  windsurf=15, all=16).
- \`tests/kilo-install.test.cjs\`: updated assertions for Kilo having
  moved from option 10 to option 11.

Closes #2841

Implementation notes:
- Zero custom code paths: Hermes reuses copyCommandsAsClaudeSkills()
  identical to Qwen Code / Antigravity pattern.
- Path replacement: ~/.claude/, \$HOME/.claude/, ./.claude/ ->
  .hermes equivalents in skill/agent/hook content.
- Config precedence: --config-dir > HERMES_HOME > ~/.hermes (matches
  how Hermes itself resolves its home directory).
- Legacy cleanup: removes commands/gsd/ if present from a prior
  install, preserving dev-preferences.md (same as Qwen).
- No external dependencies added.

Testing: 5841 / 5841 tests pass (0 failures, 0 regressions)
- 14 new tests in hermes-install.test.cjs
- 11 new tests in hermes-skills-migration.test.cjs
- multi-runtime-select.test.cjs renumbered + 1 new test (single choice for hermes)
2026-04-29 04:27:46 -07:00
Tom Boucher
eeaf9c556f fix(#2787): track fenced code blocks in extractCurrentMilestone (#2812)
* fix(#2787): track fenced code blocks in extractCurrentMilestone

The milestone-end search used a multiline regex against the raw
restContent string. Lines inside fenced code blocks (``` or ~~~)
that matched the milestone-heading pattern (e.g. `# note v1.0`)
prematurely set sectionEnd, hiding all phases after the block from
roadmap analyze, roadmap get-phase, and every downstream command.

Replace the regex match with a line-by-line scan that tracks fence
state. Lines inside an open fence are skipped regardless of content.
Adds three regression tests covering backtick fences, tilde fences,
and the roadmap get-phase code path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2787): track fence delimiter instead of toggling bare boolean

Replace the inFence boolean with fenceChar/fenceLen tracking so that
indented fences (up to 3 leading spaces) and mixed-delimiter content
(~~~ inside a backtick fence) are parsed correctly. A closing fence
is only recognised when it uses the same character as the opening
delimiter and has at least the same run length, matching the CommonMark
spec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2787): require fence-only closing line — reject info-string lines as closers

A closing fence delimiter must contain only optional trailing whitespace.
A line like \`\`\`js inside an open fence has an info string and must not
close it. The previous regex /^\s{0,3}([`~]{3,})/ matched the opening of any
such line, so the closing check could toggle fenceChar off on an info-string
line and expose subsequent heading-like content to the milestone-end detector.

Fix: capture the trailing portion of every fence-candidate line and only clear
fenceChar when trailing matches /^\s*$/ (per CommonMark §4.5).

Adds a regression test covering the ```text / ```js nesting scenario.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:37:47 -04:00
Tom Boucher
9e58c45ea1 fix(#2791): GSD_WORKSTREAM env var respected by gsd-sdk query + gsd-tools bin alias (#2821)
* fix(#2791): GSD_WORKSTREAM env var respected by gsd-sdk query + gsd-tools bin alias

Two fixes for gsd-sdk binary issues:

**Issue 1 — Binary name collision:**
Both `get-shit-done-cc` and `@gsd-build/sdk` declare `bin: { "gsd-sdk": ... }`.
Added `"gsd-tools": "bin/gsd-sdk.js"` to `package.json` bin so users with the
collision can invoke `gsd-tools query <cmd>` as a conflict-free alternative.

**Issue 2 — Query registry not workstream-aware:**
`gsd-sdk query` commands ignored `GSD_WORKSTREAM` env var, always reading from
the root `.planning/` even when a workstream was active. `gsd-tools.cjs` reads
`GSD_WORKSTREAM` via `planningDir()`, so all ~35 `gsd-sdk query` call sites in
workflow files were broken in workstream-scoped projects.

Fix: added env var fallback in `sdk/src/cli.ts` — when `--ws` is not provided,
`GSD_WORKSTREAM` is used (with name validation; invalid values are silently
ignored, matching CJS behaviour).

Regression test: `tests/bug-2791-sdk-workstream-env.test.cjs`

Closes #2791

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2791): address CodeRabbit — precedence test, invalid env fallback assertion, bash fence

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:23:32 -04:00
Tom Boucher
897cff6051 fix(#2805): find-phase returns null phase_dir for archived phases (not archive path) (#2818)
* fix(#2805): add regression test — archived phase fallback already fixed in source

getPhaseInfoWithFallback already discards archived disk matches when the
current ROADMAP lists the phase (line 133: phaseInfo?.archived &&
roadmapPhase?.found). The regression test confirms this behavior and
prevents the bug from being reintroduced by future refactors.

Regression test: tests/bug-2805-archived-phase-fallback.test.cjs
(3 tests: phase_dir null, phase_found true, phase_name from ROADMAP)

* fix(#2805): address CodeRabbit — exact phase_name assertion, bash fence
2026-04-28 20:23:29 -04:00
Tom Boucher
a4e15d5616 fix(#2788): audit-uat reads human_verification items from frontmatter (#2814)
* fix(#2788): audit-uat reads frontmatter human_verification array

parseVerificationItems only searched the body for a '## Human Verification'
section. gsd-verifier writes items to the frontmatter human_verification:
YAML array, so audit-uat returned total_items: 0 for all such files.

Two fixes:
1. Read frontmatter human_verification: array first (via extractFrontmatter);
   return those items if present (primary path for gsd-verifier output).
2. Relax the body-section heading regex to accept underscore separators and
   parenthetical suffixes (e.g. '## human_verification (action required)').

Regression test: tests/bug-2788-audit-uat-frontmatter.test.cjs

* fix(#2788): address CodeRabbit — trim whitespace entries, support hyphenated headings, bash fence
2026-04-28 20:22:59 -04:00
Tom Boucher
eddb2a205b fix(#2801): add ingest-docs handler to gsd-tools init dispatch (#2820)
* fix(#2801): add ingest-docs handler to gsd-tools init dispatch

The `/gsd-ingest-docs` workflow was broken because `workflows/ingest-docs.md`
called `gsd-sdk query init.ingest-docs` but the installed binary is `gsd-tools`,
and `gsd-tools init` had no `ingest-docs` case in its dispatch switch.

- Added `cmdInitIngestDocs` function to `init.cjs` and exported it; returns
  `project_exists`, `planning_exists`, `has_git`, `project_path`, `commit_docs`
- Added `case 'ingest-docs'` to the `init` switch in `gsd-tools.cjs`
- Updated `workflows/ingest-docs.md` to call `gsd-tools init ingest-docs`
  (line 55) and `gsd-tools commit` (line 292) instead of `gsd-sdk query ...`
- Regression test: `tests/bug-2801-ingest-docs-handler.test.cjs`

Closes #2801

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2801): address CodeRabbit — commit_docs assertion, broader gsd-sdk detection, bash fence

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:22:40 -04:00
Tom Boucher
5fe1f00a0d fix(#2808): SKILL.md files use hyphen name form (gsd-cmd not gsd:cmd) (#2819)
* fix(#2808): SKILL.md name uses hyphen form for Claude Code autocomplete

skillFrontmatterName() was converting gsd-<cmd> to gsd:<cmd> (colon) so
installed SKILL.md files had name: gsd:add-phase etc. Claude Code surfaces
this name in autocomplete, showing the deprecated colon form to users even
though the hyphen form is canonical everywhere else.

Root cause: the colon form was needed because workflows called
Skill(skill="gsd:<cmd>"). All 4 remaining colon-form Skill() calls in
autonomous.md and execute-phase.md are updated to hyphen form.

skillFrontmatterName() now returns the hyphen dir name unchanged.
Updated 4 existing tests that asserted colon form.

Regression test: tests/bug-2808-skill-hyphen-name.test.cjs

* fix(#2808): address CodeRabbit — bash/text fences, structured test assertions, fail-loud on errors
2026-04-28 20:22:37 -04:00
Tom Boucher
fa78692167 fix(#2796): roadmap-update-plan-progress accepts --phase flag form (#2815)
* fix(#2796): roadmap update-plan-progress accepts --phase flag form

roadmap-update-plan-progress used positional-only arg parsing: args[0].
When execute-phase.md:228 calls it with --phase <N>, args[0] was the
literal string "--phase", which findPhase received as the phase number.
findPhase returned found:false, causing updated:false with no write.
ROADMAP.md plan checkboxes silently never advanced.

Fix: check for --phase <value> first; fall back to the first non-flag
positional argument for backward-compatible direct calls.

Regression test: tests/bug-2796-arg-parsing-regression.test.cjs

* fix(#2796): address CodeRabbit — guard --phase against flag-like values, bash fence
2026-04-28 20:22:30 -04:00
Tom Boucher
b959b1844f fix(#2803): config-get supports --default <value> fallback for missing keys (#2817)
* fix(#2803): honor --default flag in SDK config-get handler

The gsd-sdk query config-get handler ignored the --default <value> flag.
Missing keys always threw 'Key not found' (exit 1), making 8 workflow
sites that rely on config-get --default fall through to error paths.

The CJS path (gsd-tools.cjs) honored --default since #1893; this ports
that behavior to the SDK configGet handler.

Regression test: tests/bug-2803-config-get-default-flag.test.cjs

* fix(#2803): address CodeRabbit — require --default value, keep missing config.json as error, bash fence
2026-04-28 20:21:48 -04:00
Tom Boucher
7616309a32 fix(#2798): add context_window to VALID_CONFIG_KEYS allowlist (#2816)
* fix(#2798): add regression test — context_window key already in VALID_CONFIG_KEYS

context_window was already added to both VALID_CONFIG_KEYS allowlists
(CJS and SDK) in a prior fix. The regression test confirms it stays there
and that config-set context_window succeeds end-to-end.

Regression test: tests/bug-2798-context-window-config-key.test.cjs

* fix(#2798): address CodeRabbit — add bash language to release notes fence
2026-04-28 20:21:44 -04:00
Tom Boucher
d46efb4790 fix(#2784): clear shared ~/.cache/gsd/ update-check cache in update workflow (#2813)
* fix(#2784): clear shared ~/.cache/gsd/ cache in update workflow

The SessionStart hook (hooks/gsd-check-update.js) writes update-check
results to $HOME/.cache/gsd/gsd-update-check.json (shared, tool-agnostic).
The update.md run_update step only cleared per-runtime paths like
~/.claude/cache/gsd-update-check.json, so the statusline kept showing the
stale upgrade indicator after a successful update.

Fix: add rm -f "$HOME/.cache/gsd/gsd-update-check.json" to the
cache-clear block in the run_update step.

Regression test: tests/bug-2784-update-cache-clear-path.test.cjs

* fix(#2784): address CodeRabbit review — four edge-cases count, bash fence, structured test assertions
2026-04-28 20:21:41 -04:00
Tom Boucher
055b43054f fix(#2794): embed model_profile_overrides.opencode.<tier> into generated OpenCode agents (#2822)
* docs: add CHANGELOG entry and rc.5 release notes for #2809 Codex hooks migrator fixes

Covers the five correctness findings addressed in the round-5 CR of PR #2809:
parseHooksBody key parser (hyphenated/quoted keys), buildNestedBlock empty-handler
guard, legacyMapSections segment-count filter, quoted-dot regression test, and
strengthened command path assertion.

Closes #2810

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2794): embed model_profile_overrides.opencode.<tier> into generated OpenCode agents

OpenCode agent files were missing `model:` frontmatter when the user configured
tier-based model resolution via `model_profile_overrides.opencode.*`. Only
explicit `model_overrides[agent]` was consulted; the runtime profile resolver
(used by the Codex path since #2517) was never called for OpenCode agents.

Added a tier-resolver fallback in the OpenCode agent conversion block in
`bin/install.js`. Precedence (matching Codex behavior):
  model_overrides[agent] > model_profile_overrides.opencode.<tier> > omit

Regression test: `tests/bug-2794-opencode-model-profile-overrides.test.cjs`

Closes #2794

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 20:16:27 -04:00
Tom Boucher
06de427b09 docs: add CHANGELOG entry and rc.5 release notes for #2809 Codex hooks migrator fixes (#2811)
Covers the five correctness findings addressed in the round-5 CR of PR #2809:
parseHooksBody key parser (hyphenated/quoted keys), buildNestedBlock empty-handler
guard, legacyMapSections segment-count filter, quoted-dot regression test, and
strengthened command path assertion.

Closes #2810

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 18:35:59 -04:00
Tom Boucher
3c03a153a5 fix(#2773): emit correct Codex 0.124.0+ two-level nested hooks schema (#2809)
* fix(#2773): emit correct Codex 0.124.0+ two-level nested hooks schema

Codex 0.124.0's stable spec requires:

  [[hooks.SessionStart]]          ← event entry (optional matcher)

  [[hooks.SessionStart.hooks]]    ← handler sub-table
  type = "command"
  command = "node ..."

Previous GSD versions wrote the flat [[hooks]] + event = "SessionStart"
form (#2637) or a single-block [[hooks.SessionStart]] without the nested
.hooks sub-table (#2760). Both are rejected by Codex 0.124.0+ at launch.

Changes:

bin/install.js
  - Hook block emission now always writes the two-level nested AoT form.
  - migrateCodexHooksMapFormat extended to also migrate flat [[hooks]]
    array-of-tables entries (event = "..." key → [[hooks.<EVENT>]] form).
    Flat [[hooks]] and [[hooks.<EVENT>]] are mutually exclusive TOML types;
    any pre-existing flat entries must be promoted before GSD appends its
    own namespaced hooks.
  - Migrated flat AoT blocks are inserted BEFORE the GSD marker so they
    stay in the "user" portion of the file and survive stripGsdFromCodexConfig.
  - stripCodexGsd* regexes cover all four historical block shapes.
  - validateCodexConfigSchema no longer rejects flat [[hooks]] at the root
    level (removing the false-positive that blocked install when users had
    their own AfterCommand hooks). The validator still enforces the nested
    [[hooks.<EVENT>.hooks]] shape for entries that have a .hooks sub-table.

tests/
  - bug-2760-codex-install-defensive.test.cjs: 29/29 passing.
    Added 5 new regression cases for fresh install, upgrade from each
    legacy shape, idempotent reinstall, and user hook preservation.
  - codex-config.test.cjs: 106/106 passing.
    All migration tests updated to assert [[hooks.<TYPE>.hooks]] sub-table
    (command now in handler level, not event-entry level).
    New tests: flat [[hooks]] migration (SessionStart, AfterCommand),
    install+uninstall preserves non-GSD AfterCommand hook.

Closes #2773

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address CodeRabbit review + CI regression in bug-2698-crlf-install

CI regression (#2698 tests):
  Strip GSD-managed hook blocks BEFORE running migrateCodexHooksMapFormat.
  The previous order let migration convert the stale [[hooks]] + event =
  "SessionStart" + gsd-update-check.js block to [[hooks.SessionStart]] form
  before Shape 1 strip regex could match it; Shape 1 only matches the flat
  [[hooks]] form, so the stale block survived reinstall. Swapping to
  strip-then-migrate ensures only user-authored hooks reach the migration step.
  Shape 3/4 regexes also extended to match both gsd-check-update.js and the
  legacy gsd-update-check.js filename so no variant slips through.

CodeRabbit actionable (major):
  migrateCodexHooksMapFormat now accepts single-quoted TOML event values
  (event = 'SessionStart') in the flat [[hooks]] filter and event-name
  extractor. TOML spec allows single-quoted literal strings; double-quote-only
  regexes silently skipped them, leaving the block unmigrated and triggering
  the hard-fail validator.

CodeRabbit nitpicks:
  tests/codex-config.test.cjs: replace indexOf('[[hooks.AfterCommand]]')
  ordering check with parseTomlToObject structural assertions (no-source-grep
  rule).
  tests/bug-2760-codex-install-defensive.test.cjs: replace three
  content.match(/…/g).length raw-text counts with parseTomlToObject structural
  assertions for single-handler and single-event-entry invariants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address CodeRabbit review #2 — extractFlatHookEventName helper + type assertions

- bin/install.js: consolidate TOML_QUOTED_STRING + TOML_EVENT_CAPTURE into a
  single extractFlatHookEventName() helper that rejects empty-string event values
  (event = "" or event = ''); previously two independent regexes had to be kept
  in sync and neither guarded against a blank event name producing a [[hooks.]]
  header

- tests/bug-2760-codex-install-defensive.test.cjs: add comments explaining why
  the e.command fallback is retained in both allSessionStartCommands and
  afterToolCommands collectors — migration only upgrades [hooks.TYPE] map-format
  sections, not existing [[hooks.TYPE]] namespaced AoT entries authored with
  command at event-entry level; removing the fallback causes false failures for
  preserved user entries

- tests/codex-config.test.cjs: add type = "command" assertions to all migration
  tests that verify .command but were missing .type checks; buildNestedBlock
  injects type = "command" when the source body has no explicit type key, so
  every migrated handler must carry it per the Codex 0.124.0+ schema

138 tests pass, 0 fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: CR round 3 + proactive audit — TOML quoting, stale AoT migration, strict validator

Three real issues from CodeRabbit round 3, plus the collateral improvements they
enable:

bin/install.js — tomlBareKey() helper (#2773 CR6a)
  buildNestedBlock interpolated the raw event name into [[hooks.${type}]] and
  [[hooks.${type}.hooks]] headers without TOML escaping. An event name containing
  spaces or punctuation (e.g. "Before Tool") would produce invalid TOML that
  parseTomlToObject would subsequently reject. Added tomlBareKey() — wraps the
  key in double-quoted TOML strings when it contains non-bare-key characters
  ([A-Za-z0-9_-]).

bin/install.js — staleNamespacedAotSections migration path (#2773 CR6b)
  migrateCodexHooksMapFormat handled [hooks.TYPE] (map-format) and flat [[hooks]]
  with event = "..." but ignored [[hooks.TYPE]] AoT entries that carried handler
  fields (command, type, timeout, statusMessage) at event-entry level without a
  nested [[hooks.TYPE.hooks]] sub-table. This is the pre-#2773 single-block shape
  that Codex 0.124.0+ rejects. Added staleNamespacedAotSections as the third
  migration category: detected by STALE_HANDLER_FIELD_PATTERN + absence of a
  [[hooks.TYPE.hooks]] sub-table in the same file; promoted to the two-level
  nested form by buildNestedBlock. Matcher-only entries (no handler fields) are
  intentionally skipped.

bin/install.js — validator now rejects event-level handler fields (#2773 CR6c)
  With migration covering the stale AoT shape, validateCodexConfigSchema can be
  strict: entries that have handler fields at event-entry level but no .hooks
  sub-array return ok: false instead of silently passing. Matcher-only entries
  (no handler fields and no .hooks) remain valid as event filters.

tests/codex-config.test.cjs — four new migration tests + missing type assertion
  Four tests cover the new stale AoT migration path: single-entry promotion,
  already-nested entry is left untouched (no double-wrap), multiple event types,
  and matcher-only entry is skipped. Added the missing type = "command" assertion
  to the CRLF migration test (the one miss from CR round 2).

tests/bug-2760-codex-install-defensive.test.cjs — strict .hooks-only collectors
  With stale AoT entries now migrated, the entry.command fallbacks in
  allSessionStartCommands and afterToolCommands are dead code. Replaced with
  strict entry.hooks-only collection guarded by an every(Array.isArray(e.hooks))
  pre-assertion, so any future regression that leaves handler fields at event
  level produces an explicit test failure rather than silently collecting them.

142 tests pass, 0 fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: CR round 4 — segment-safe quoted-key detection + structural test assertions

bin/install.js — getTomlTableSections now exposes segments (#2773 CR7a)
  The staleNamespacedAotSections filter used section.path.split('.').length > 2
  to skip [[hooks.TYPE.hooks]] sub-table entries. That check misclassifies quoted
  event names containing dots: [[hooks."before.tool"]] has path hooks.before.tool
  (3 dot-parts) but only 2 true parsed segments, so it was incorrectly excluded
  from migration. Fixed by adding segments to the getTomlTableSections return
  shape (already available on record.tableHeader.segments) and replacing the
  split-based check with section.segments.length !== 2, which uses the true
  parsed key count regardless of dots inside quoted names.

tests/codex-config.test.cjs — replace raw-equality assertions (#2773 CR7b)
  The two new no-op migration tests (already-nested and matcher-only) used
  assert.strictEqual(result, content) — raw string equality that conflicts with
  the repo no-source-grep testing standard. Replaced with structural assertions
  using parseTomlToObject: the already-nested test verifies the handler stays
  under .hooks[0] and no double-wrap occurs; the matcher-only test verifies the
  matcher key is preserved and no .hooks sub-array is added.

142 tests pass, 0 fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: CR round 5 — parseHooksBody key parser, empty-handler guard, segment-safe legacyMap filter, stronger test assertions

- parseHooksBody: replace /^([\w.]+)\s*=/ regex with parseTomlKey() so
  hyphenated keys (status-message) and quoted keys are not silently dropped
- buildNestedBlock: guard against handlerEntries.length === 0 — do not
  synthesise [[hooks.TYPE.hooks]] with type="command" but no command for
  matcher-only or otherwise handler-empty stale sections
- legacyMapSections filter: use section.segments.length === 2 (same fix
  applied to staleNamespacedAotSections in round 4) to prevent [hooks.X.Y]
  3-segment tables from being misclassified as event entries
- tests: add regression test for [[hooks."before.tool"]] quoted-dot event
  names; strengthen command path assertion to exact absolute path comparison

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 18:28:53 -04:00
Tom Boucher
c0730fffde docs(changelog): expand [Unreleased] with all 1.39.0-rc.4 changes since v1.38.5 (#2799)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 22:56:03 -04:00
Tom Boucher
f983c95ffc Release/1.39.0-rc.4 (#2797)
* chore: bump version to 1.39.0 for release

* chore: bump to 1.39.0-rc.1

* chore: bump to 1.39.0-rc.2

* chore: bump to 1.39.0-rc.3

* chore: bump to 1.39.0-rc.4

* docs: add v1.39.0-rc.4 release notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 22:32:14 -04:00
Tom Boucher
b44482cf03 fix(#2760): defensive Codex install — strip legacy agents blocks, default hooks to AoT, validate post-write schema (#2785)
* fix(#2760): defensive Codex install — strip legacy agents blocks, default hooks to AoT, validate post-write schema

Three defects, three defensive fixes shipped together. Issue reporter
never returned with the requested diagnostic backup, but four additional
users have since confirmed the same Codex breakage and ZakAnun confirmed
manual cleanup is the only working workaround — defensive triple ships
without the original backup grep, justified by the corroborating reports.

Fix 1 (defect 3 — confirmed real). The Codex hooks emit path always
appended a top-level `[[hooks]]` AoT block, which collides with users
who already use the namespaced AoT form `[[hooks.SessionStart]]`. New
helper `hasUserNamespacedAotHooks()` detects the user's preferred shape
on parse and the install emits the GSD-managed hook in that same shape
when present. Default for fresh configs stays at top-level `[[hooks]]`
so status-quo behavior is preserved.

Fix 2 (defects 1+2 — defensive). `stripLeakedGsdCodexSections()` (the
install-time stripper) now always purges bare `[agents]` single-bracket
tables and `[[agents]]` sequence tables regardless of GSD marker
presence — both forms are invalid in current Codex schema and produce
"invalid type: ..., expected struct AgentsToml". Previously gated on
GSD-name lookup which missed marker-stripped configs and third-party
authored entries. The uninstall-time stripper (`stripCodexGsdAgentSections`)
keeps its old conservative behavior so user-authored entries survive
uninstall.

Fix 3 (defensive). Post-write schema validation parses the bytes about
to be committed and asserts no bare `[agents]`, no `[[agents]]`, and no
bare `[hooks.<Event>]` tables remain. On failure the install restores
the pre-install backup of config.toml and aborts loudly so the user is
never left with a Codex CLI that refuses to load. Pre-install snapshot
is captured before installCodexConfig runs (not after) so restore
returns the file to its true pre-GSD state.

Tests added (10 new, 1 updated):
  - bug-2760-codex-install-defensive.test.cjs (10 new tests across 4
    describes: hooks AoT preservation, strip robustness for both
    [agents] and [[agents]] without marker, schema validator behavior,
    abort+restore via test seam)
  - codex-config.test.cjs "case 2 ..." updated to reflect new defensive
    bare-[agents] purge

Full suite: 5747 pass / 0 fail.

Closes #2760

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#2760): normalize Codex hooks emit field name across migration and managed paths

The migrateCodexHooksMapFormat path emitted `type = "<TYPE>"` for legacy
[hooks.TYPE] sections, while the GSD-managed Codex install emitted
`event = "SessionStart"` — same target [[hooks]] schema, two different
field names. Codex currently tolerates both via permissive parsing, but
the moment one path tightens this becomes a silent #2760-class regression.

Normalize both call sites on `event` (the existing GSD-managed
convention). Update migration emit, docstring, and existing migration
assertions to match. Add a parity regression test that drives both code
paths and asserts the [[hooks]] field key is identical.

* test(#2153): fix test isolation by building hooks/dist on demand

The "Codex install copies hook file (#2153)" regression depends on
hooks/dist/ being populated, but that directory is gitignored and only
built by `npm run build:hooks`. The npm pretest chain runs `build:sdk`
but not `build:hooks`, so when this file is run in isolation
(`node --test tests/codex-config.test.cjs`) the hook copy step skips
silently and the regression test fails on a stale-environment artifact
rather than a real bug.

Add a top-level before() hook that runs scripts/build-hooks.js when
hooks/dist/ is missing or empty. Matches the pattern already used by
bug-1834-sh-hooks-installed and other install integration tests, so the
suite passes regardless of runner ordering or which tests are targeted.

* fix(#2760): structural TOML validation, atomic writes, and behavioral test rewrites

Addresses CodeRabbit review on PR #2785 plus source-grep violations the
maintainer flagged in the regression test.

Fix 1 (CR 3149606220) — validateCodexConfigSchema now parses the TOML into
a structured object first via the new parseTomlToObject helper, then
runs schema-shape checks against both the parsed structure and the table
section headers. Malformed TOML with valid-looking headers no longer
slips past validation.

Fix 2 (CR 3149606224) — Replaced the four source-grep assertions in
tests/bug-2760-codex-install-defensive.test.cjs (lines 109, 125, 169,
201) with structural assertions against the parsed TOML object via the
exported parseTomlToObject helper. Tests now verify behavior (the file
parses and contains the expected structure) instead of literal byte
patterns. Robust to formatting changes — exactly what the regex-loosening
suggestion was reaching for, done correctly. Confirmed clean by
`npm run lint:tests` (0 violations).

Fix 3 (CR 3149606234) — The describe block that mutates
installModule.__codexSchemaValidator now runs with concurrency: false
so the test seam mutation cannot leak into sibling suites that also
call runCodexInstall.

Fix 4 (CR outside-diff) — Approach (b): atomic temp-file + renameSync.
Added atomicWriteFileSync helper used by mergeCodexConfig and the final
hooks-write. A mid-write failure leaves the .tmp-<pid>-<n> sibling
behind (cleaned up immediately) and never truncates the original
config.toml. Paired with try/catch wrapping around the entire
post-snapshot mutation sequence so any unexpected throw also triggers
restoreCodexSnapshot. Two layers of defense: atomic write prevents the
corruption window, snapshot restore handles non-atomic write paths.

Added behavioral test for fix 4: stubs fs.renameSync to throw on the
configPath rename, asserts the on-disk bytes match the pre-install
snapshot byte-for-byte, asserts the parsed structure is still the
user's [model] section (no half-written GSD agents block), and asserts
no stray .tmp-* files remain. Marked concurrency: false because it
monkey-patches a global.

Test results: 5749/5749 pass, 0 fail. lint:tests clean.

* test(#2760): TOML-parse based assertions for bare-agents purge and hook-field parity (CodeRabbit follow-up)

* fix(#2760): treat write failures as fatal, strip legacy hooks before guard, tighten TOML parser (CR4)

CR4 finding 1 (MAJOR) — Write failures silently succeeded. The inner catch
around atomicWriteFileSync restored the snapshot then re-threw, but the outer
catch only matched 'post-write Codex schema validation failed' and downgraded
everything else to a warn-and-continue. Install finished with "Done!" while
Codex had no GSD agents configured. Fix: wrap writeErr with a `post-write
Codex install failed:` prefix and broaden the outer guard to `.startsWith(
'post-write')` so both schema-validation and write failures abort install.

CR4 finding 2 (MAJOR) — Legacy flat [[hooks]] block prevented namespaced AoT
upgrade. The `!configContent.includes('gsd-check-update')` guard short-
circuited the new namespaced emit when an existing install had the legacy
flat [[hooks]] block, leaving users stuck in the mixed layout this fix is
designed to eliminate. Fix: strip ALL existing managed gsd-check-update
hook blocks (top-level [[hooks]] AND namespaced [[hooks.SessionStart]])
BEFORE evaluating the includes guard, so every install converges on the
right shape regardless of prior state.

CR4 finding 3 (MAJOR) — Homegrown TOML parser silently accepted malformed
input. parseTomlValue happily consumed the `0` prefix of `timeout = 0.5`
and parseTomlToObject did not verify the full RHS was consumed, so
`key = "x" junk` and date/time literals slipped through. Per CONTRIBUTING
("No external dependencies in core"), option (b) was chosen over adding
@iarna/toml: (a) parseTomlValue rejects any integer immediately followed
by `.`, `e`, `E`, `:`, `-`, `T`, or `Z` (floats / dates / times); (b)
parseTomlToObject scans from parsed.end to the next newline and throws
`trailing bytes after value` if anything other than whitespace + optional
`# comment` is present.

* test(#2760): add CR4 regression tests + scope GSD_TEST_MODE + rename rename-fault test

CR4 finding 5 (NIT) — GSD_TEST_MODE leak. Saved previous value, set '1' for
the require, then restored (delete if undefined). No more test-only env var
leaking to siblings in the same node process.

CR4 finding 4 (NIT) — Renamed the existing fix-4 test from 'fs.writeFileSync'
to 'fs.renameSync' (the only call actually faulted) and added a sibling test
that stubs fs.writeFileSync to throw on the .tmp- target — exercising the
pre-rename branch of atomicWriteFileSync that was previously untested. Both
serialize via concurrency: false on the existing describe block.

CR4 finding 1 (MAJOR test) — New behavioral test asserts install throws with
a `post-write Codex install failed` message AND never prints "Done!" when
the hook-block atomic rename fails. Captures stdout via console.log stub,
asserts byte equality of restored snapshot. Faults only the rename whose
temp source contains gsd-check-update so earlier mergeCodexConfig writes
are not collateral damage.

CR4 finding 2 (MAJOR test) — New TOML-parsed behavioral test for the
legacy-hook upgrade path: pre-install has [[hooks.SessionStart]] (user) +
legacy flat [[hooks]] managed gsd-check-update entry; post-install must
have hooks.SessionStart as Array-of-tables with both user hook and GSD
entry, and no top-level [[hooks]] AoT remaining. Also asserts exactly one
gsd-check-update entry (no duplicates).

CR4 finding 3 (MAJOR test) — parseTomlToObject regression suite: rejects
floats (timeout = 0.5), dates (created = 1979-05-27), trailing garbage
(key = "x" junk), and accepts trailing whitespace + # comment.

* fix(#2760): CR5 — pre-write fatal, TOML duplicate-key/header rejection, namespaced AoT migration

Address all five CodeRabbit round-5 findings on PR #2785:

Finding 1 (MAJOR) — Pre-write failures in the Codex hook configuration
catch (around bin/install.js:7002) used to fall through to console.warn
even though restoreCodexSnapshot() had already run. This produced "Done!"
output with no Codex hooks configured. Now wraps the original error with
a "(pre-write)" prefix and rethrows so install aborts loudly. Same defect
class as CR4 finding 1, different layer.

Finding 2 (MAJOR) — parseTomlToObject silently reused existing tables and
overwrote duplicate keys. Real TOML 1.0 rejects:
  - duplicate scalar key in same table ([a]\nx=1\nx=2)
  - re-declared [a] header (two [a] sections)
  - [[arr]] then [arr] for same path (shape mismatch)
Tracks pathShape, declaredHeaders, and per-table-instance key sets;
throws "duplicate or shape-mismatched table header at <path>" or
"duplicate key <name> in <path>".

Finding 3 (MAJOR) — migrateCodexHooksMapFormat used to emit flat
[[hooks]]\nevent="<TYPE>", which produced mixed flat+namespaced layouts
when the user already had [[hooks.<OTHER>]] entries. Now emits
[[hooks.<TYPE>]] directly (the namespace IS the event); managed-emit
detector hasUserNamespacedAotHooks fires correctly so the install
converges on a single namespaced layout regardless of pre-existing state.

Finding 4 (NIT) — tests/bug-2760-codex-install-defensive.test.cjs
rename-failure test tightened from "throw OR warn acceptable" to
assert.equal(threw, true), locking the contract Finding 1 establishes.

Finding 5 (NIT) — bug-2760 test suite snapshots and restores fs.renameSync
defensively in beforeEach/afterEach (symmetric with fs.writeFileSync),
removing the fragile per-test try/finally. Second test in the same
suite cleaned up to drop its try/finally.

Updates tests/codex-config.test.cjs to assert the new namespaced AoT
migration shape via parseTomlToObject (no source-grep). Existing field-
parity test reframed as shape-parity since both paths now emit
namespaced.

Tests: 5764 pass (+8 new). lint:tests: 0 violations.

* docs(#2760): add CHANGELOG entry for Codex install defensive triple

Adds the [Unreleased] Fixed entry for the Codex install fix landed in this
PR — defensive strip of legacy [agents]/[[agents]] blocks, namespaced AoT
hook detection across all events, atomic write + rollback, strict TOML
validation rejecting duplicate keys/repeated headers/trailing bytes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:11:59 -04:00
Tom Boucher
936cf26706 fix(#2769): tolerate Requirements header with colon inside bold delimiters (#2782)
* fix(#2769): tolerate Requirements header with colon inside bold delimiters

extractReqIds in sdk/src/query/init.ts and the legacy init.cjs port only
matched `**Requirements**:` (colon outside bold), so phases declared with
the equally-valid markdown form `**Requirements:**` (colon inside bold,
which is what the project's own templates emit) returned phase_req_ids:
null for both `init plan-phase` and `init execute-phase`.

The mirror-image bug in `phase complete`'s REQUIREMENTS.md traceability
sweep at get-shit-done/bin/lib/phase.cjs:871 only matched the inside-bold
form, silently skipping the REQ-ID checkbox flips for any roadmap that
used the outside-bold form. Both parsers now share the same canonical
regex that accepts all three rendered-identical variants:

  **Requirements:**     (colon inside bold)
  **Requirements**:     (colon outside bold)
  **Requirements** :    (space before outside colon)

Tests:
- tests/init.test.cjs — parameterized over the three header variants for
  both init plan-phase and init execute-phase (6 new behavioral cases).
- sdk/src/query/init.test.ts — describe.each over the same variants
  exercising initPlanPhase through the SDK.
- tests/bug-2769-requirements-header-variants.test.cjs — phase complete
  flips REQ-001 in REQUIREMENTS.md across all three header variants.

Closes #2769

* refactor(#2769): centralize REQUIREMENTS_HEADER_RE constant per CodeRabbit
2026-04-27 12:31:49 -04:00
Tom Boucher
54e6da3126 fix(#2767): pass paths via --files to gsd-sdk query commit + lint guard (#2781)
* fix(#2767): pass paths via --files to gsd-sdk query commit + lint guard

Workflows, agents, commands, and references passed file paths positionally
to `gsd-sdk query commit`, which silently appended them to the commit
subject and triggered the `.planning/` wholesale-stage fallback in
sdk/src/query/commit.ts:136. Regression of #733/#798.

Inserted `--files` before the path list at every site (81 invocations
across 50 files). Added tests/bug-2767-gsd-sdk-commit-files-flag.test.cjs
as a permanent lint that scans every shipped .md file and asserts each
`gsd-sdk query commit[-to-subrepo]` invocation either uses `--files` or
carries no path arguments.

Closes #2767

* test(#2767): replace source-grep with behavioral SDK test

The original test walked every shipped .md file and regex-tokenized
`gsd-sdk query commit` invocations to assert `--files` was present.
CONTRIBUTING.md prohibits this source-grep pattern.

Rewrite as behavioral SDK tests against `sdk/dist/cli.js` over a real
tmp git project (createTempGitProject helper). Cover both the
well-formed (`--files <paths>`) form — clean subject, exactly-staged
files, .planning/ left untouched — and the buggy positional form,
asserting the documented misbehavior (paths leak into subject + the
`.planning/` wholesale-stage fallback at commit.ts:136). Also asserts
`commit-to-subrepo` rejects when `--files` is omitted (commit.ts:258).

The doc-lint is retained as a supplementary defense-in-depth guard
since agent-prompt markdown invocations cannot be exercised end-to-end
— but it is no longer the primary contract.

* docs(#2767): correct contradictory --files guidance in zh-CN/en docs + fix test docstring
2026-04-27 12:31:43 -04:00
Tom Boucher
3ac3a2ae70 fix(#2770): coerce non-string depends_on YAML values to preserve dependencies (#2780)
* fix(#2770): coerce non-string truths to preserve cross-cutting constraints

`cmdRoadmapAnnotateDependencies` skipped non-string truth entries via
`if (typeof t !== 'string') continue`. That avoided the TypeError reported
in #2770 but silently dropped legitimate constraints — numeric YAML scalars
(`- 3`) and kv-shaped truths from parseMustHavesBlock's continuation-kv
path (#2757) — from the cross-cutting analysis, leaving ROADMAP.md
under-annotated.

Replace the skip-guard with a `coerceTruthToString` helper that:
  * passes strings through
  * `String()`-coerces numbers, booleans, bigints
  * extracts a string field (title, text, name, rule, path, provides) from
    object-shaped items

Composes cleanly with #2757 (objects from kv continuation lines now
contribute their title rather than being dropped) and the existing
`splitInlineArray` quote-aware parser.

Tests: tests/bug-2770-annotate-deps-int-coerce.test.cjs
  - numeric scalar truth shared across plans surfaces as constraint
  - kv-shaped truth surfaces via title field
  - bare-int depends_on regression guards on extractFrontmatter

Full suite: 5678 pass, 0 fail.

Closes #2770

* test(#2770): use array join() for multi-line fixtures per CONTRIBUTING

* refactor(#2770): cache trim() and avoid no-op truthCounts.set in aggregation
2026-04-27 12:31:38 -04:00
Tom Boucher
8b6c44433f fix(#2772): only disable worktree isolation when planned paths touch submodules (#2779)
* fix(#2772): only disable worktree isolation when planned paths touch submodules

The previous guard in execute-phase.md and quick.md unconditionally set
USE_WORKTREES=false whenever .gitmodules existed, penalising every plan in
a submodule project even when no plan touched a submodule path.

Replace with submodule-path parsing + per-plan path intersection:

- Parse SUBMODULE_PATHS once from .gitmodules via
  `git config --file .gitmodules --get-regexp '^submodule\..*\.path$'`.
- In execute-phase.md, intersect SUBMODULE_PATHS with each plan's
  files_modified frontmatter; disable worktree isolation only for plans
  with non-empty intersection. Fall back to safe-disable for that plan
  when files_modified is missing/unparseable, with a log line explaining
  why.
- In quick.md (no pre-declared paths), keep submodule-path parsing and
  document a fail-loud commit-time guard so the executor aborts only when
  it actually stages a submodule path.

Add tests/bug-2772-gitmodules-path-intersection.test.cjs covering both
files: no unconditional disable, submodule paths are parsed, intersection
logic exists in execute-phase, fallback path is documented.

Full suite: 5680 / 5680 pass.

Closes #2772

* test(#2772): replace source-grep with behavioral test of submodule path intersection

* fix(#2772): wire USE_WORKTREES_FOR_PLAN into dispatch + fix glob matcher + add quick.md commit guard

Address CodeRabbit review on PR #2779 — the original fix computed
USE_WORKTREES_FOR_PLAN but never read it, so the per-plan submodule
intersection was dead code. Dispatch sites still branched on the
project-level USE_WORKTREES.

Changes:

1. execute-phase.md (CRITICAL — dispatch wiring): Move per-plan
   computation into execute_waves as sub-step 2.5, run it for each plan
   before its dispatch, and gate all four dispatch sites on
   USE_WORKTREES_FOR_PLAN: worktree-mode header, sequential-mode header,
   "worktrees disabled" sequential rule, and post-wave cleanup. Document
   PLAN_FILES extraction via jq from the phase-plan-index JSON. Track
   WAVE_WORKTREE_PLANS so post-wave cleanup only runs when at least one
   plan in the wave actually used worktrees.

2. Per-plan gate matcher (MAJOR — glob safety): Strip leading "./" and
   trailing "/" from both submodule and planned paths. Match
   bidirectionally (pf inside sm AND sm inside pf). Handle globby
   planned paths like "vendor/**/*.c" by extracting the literal prefix
   before the first glob metachar and re-checking. Wrap the iteration
   in set -f / set +f so glob expansion does not corrupt patterns.
   Extracted the gate (~92 lines) into
   workflows/execute-phase/steps/per-plan-worktree-gate.md to keep
   execute-phase.md under the 1700-line XL budget.

3. quick.md (CRITICAL — fail-loud guard): Inject SUBMODULE_PATHS into
   the executor Task prompt and add a <submodule_commit_guard> bash
   block the executor must run before every git commit. The guard
   inspects staged paths via `git diff --cached --name-only`, normalizes
   paths, and aborts with a clear ABORT message + recovery instruction
   ("re-run with workflow.use_worktrees=false") when any staged path
   falls inside a submodule.

4. tests/bug-2772-gitmodules-path-intersection.test.cjs: 25 tests total.
   Updated GATE_SNIPPET to match the new bash matcher. Added
   normalization tests (./ prefix, trailing /, glob "vendor/**/*.c",
   parent directory, ./ in .gitmodules). Added workflow-markdown
   wiring assertions for all 4 dispatch sites + per-plan gate file
   extraction. Added quick.md guard tests: prompt injection assertion +
   behavioral fixture-repo tests that stage a submodule path and assert
   the guard exits non-zero with the ABORT message.

Test count: 5701 pass / 0 fail (was 5698/1 before).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:31:32 -04:00
Tom Boucher
77d929429f fix(#2774): inclusion-based worktree cleanup to protect workspace .git (#2778)
* fix(#2774): inclusion-based worktree cleanup to protect workspace .git

The cleanup blocks in execute-phase.md and quick.md used an exclusion
filter (`grep -v "$(pwd)$"`) to skip the current worktree before calling
`git worktree remove --force` on everything else. The exclusion fails
whenever the current workspace is itself a worktree of an upstream repo:

- multi-workspace setups where `git worktree list` reports the registry
  path as a different absolute path than `$(pwd)`
- the cross-drive Windows case where the registry reports `E:/...` while
  `$(pwd)` resolves to `C:/...` — the equality test never holds, every
  other worktree (including the workspace itself) is removed, and the
  workspace's `.git` pointer file is destroyed.

Switches both cleanup blocks to an inclusion-based filter that targets
only agent-spawned worktrees under `.claude/worktrees/agent-`, the
namespace Claude Code's `isolation="worktree"` always uses for executor
worktrees. The workspace path can never collide with that prefix.

Adds tests/bug-2774-worktree-cleanup-workspace-safety.test.cjs covering:
- both workflow files use the inclusion filter
- neither falls back to the broken `grep -v "$(pwd)$"` guard
- end-to-end simulation of porcelain output with workspace + agent
  worktrees yields only the agent worktree

Closes #2774

* test(#2774): replace source-grep with behavioral test of cleanup pipeline

* fix(#2774): whitespace-safe worktree iteration with while/read

CodeRabbit review on PR #2778 flagged that `for WT in $WORKTREES` splits
on whitespace. Any agent worktree path containing a space (e.g. a workspace
under '/Users/dev/My Workspace/') would be torn into broken half-paths,
`git -C` would fail on each fragment, and the executor branch would never
be deleted.

Switch both cleanup blocks (quick.md and execute-phase.md) to:

  while IFS= read -r WT; do
    [ -z "$WT" ] && continue
    ...
  done < <(git worktree list --porcelain | grep ... | sed ...)

Process substitution feeds the pipeline output line-by-line — IFS= and -r
preserve every byte of the path including embedded spaces.

Also rename the misleading `makeBareTempGitRepo` helper to
`makeTempUpstreamRepo` (it does not pass --bare; it inits a normal repo
with an initial commit so worktree-add works).

Add two new behavioral tests:
- discovery pipeline yields whitespace paths intact on a single line
- the actual while/read loop iterates each whitespace-bearing path
  exactly once (would fail with the previous `for WT in` form)

Tests: 5681 pass, 0 fail.
2026-04-27 12:31:26 -04:00
Tom Boucher
6a293cfc2a fix(#2775): verify gsd-sdk on PATH before reporting SDK ready (#2777)
* fix(#2775): verify gsd-sdk on PATH before reporting SDK ready

`npx get-shit-done-cc@latest` printed `✓ GSD SDK ready` even though
`gsd-sdk` was not callable. Root cause: npx only links the package's
primary bin (`get-shit-done-cc`); secondary bins like `gsd-sdk` are not
materialized into a PATH directory. The installer asserted the weaker
invariant "sdk/dist/cli.js exists on disk" and treated it as proof of
the stronger invariant "command -v gsd-sdk resolves" — they aren't the
same.

Fix tightens the gate in installSdkIfNeeded:

  1. After confirming the dist is present, walk PATH for an executable
     `gsd-sdk` shim (isGsdSdkOnPath, no spawn).
  2. If absent, attempt to materialize the shim via symlink at
     `~/.local/bin/gsd-sdk` (or the first HOME-rooted PATH dir we can
     write to), falling back to a copy on filesystems that reject
     symlinks (trySelfLinkGsdSdk).
  3. Re-probe PATH after linking. Only print `✓ GSD SDK ready` when the
     probe succeeds; otherwise emit a clear ⚠ + remediation.

Also strips the misleading "or `npx get-shit-done-cc`" clause from the
shim header (it never linked the secondary bin).

Closes #2775

* test(#2775): use centralized helpers from helpers.cjs per CONTRIBUTING

* fix(#2775): wrapper script in symlink fallback to preserve __dirname resolution

CodeRabbit follow-up on PR #2777. The previous symlink-fallback in
trySelfLinkGsdSdk used fs.copyFileSync(shimSrc, target), but
bin/gsd-sdk.js resolves the CLI via path.resolve(__dirname, '..',
'sdk', 'dist', 'cli.js'). After a copy, __dirname becomes the link
directory (e.g. ~/.local/bin), so the resolved CLI path was broken
(~/.local/sdk/dist/cli.js) — and isGsdSdkOnPath() only checked file
existence + execute bit, so the success line still printed over a
broken install.

Replace the copy with a tiny wrapper script that require()s the real
shim by absolute path. This preserves __dirname inside bin/gsd-sdk.js
because the require runs against shimSrc's own location.

Also fixes the PATH restoration nit in the regression test (was
coercing undefined to the string "undefined" if PATH was unset).

Adds a behavioral fallback test that mocks fs.symlinkSync to throw,
exercises the fallback path, and asserts the resulting target is a
require()-wrapper (not a verbatim copy) and is executable.

* fix(#2775): PATH-backed dir ordering + tighten captureConsole + drop tautological assertion (CodeRabbit follow-up)
2026-04-27 12:31:21 -04:00
Tom Boucher
290c8b2909 fix(#2771): unify user-owned-artifacts list to suppress false patches warning (#2776)
* fix(#2771): unify user-owned-artifacts list to suppress false patches warning

USER-PROFILE.md was both preserved across reinstalls (correctly) AND tracked in
gsd-file-manifest.json (incorrectly). On the next install, saveLocalPatches()
hashed the on-disk file, found it differed from the stale manifest hash (because
/gsd-profile-user --refresh regenerated it), and reported it as a "locally
modified GSD file" — a spurious warning every time the profile refreshed.

A file is either distribution (manifest-tracked, diff'd against manifest) or
user artifact (preserved across installs, never diff'd). Never both. This
extracts USER_OWNED_ARTIFACTS as a single source of truth, referenced by both
the preserveUserArtifacts call site and writeManifest, so the invariant cannot
drift again.

Adds a regression test that exercises the full reproduction path: install,
create USER-PROFILE.md, reinstall, refresh USER-PROFILE.md, reinstall, assert
no patch backup and no warning text.

Closes #2771

* test(#2771): use centralized helpers from helpers.cjs per CONTRIBUTING

* fix(#2771): normalize legacy USER_OWNED_ARTIFACTS entries from manifest + tighten test
2026-04-27 12:31:15 -04:00
Tom Boucher
dc9b712967 refactor(state): drop unused args + lift currentPhase in cmdStateCompletePhase (#2761)
* refactor(state): drop unused args param and lift currentPhase in cmdStateCompletePhase

Two cleanup items surfaced by CodeRabbit review of PR #2759:

1. cmdStateCompletePhase(cwd, args, raw) — args is never read inside the
   function. All sibling state subcommands use the leaner (cwd, raw) shape.
   Remove the unused parameter and update the dispatch call in gsd-tools.cjs.

2. output() at line 1754 called fs.readFileSync(statePath) after
   readModifyWriteStateMd had already released the lock, re-extracting
   Current Phase via an extra fs read. The closure already computed
   currentPhase at line 1704; lifting resolvedPhase into outer scope and
   capturing it in the callback eliminates the post-lock read and closes the
   small race window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(#2761): apply CodeRabbit nitpicks with regression tests

Two CodeRabbit nitpicks from PR #2761 review, each landed with a
regression test so a future refactor can't unwind them.

1. tests/dispatcher.test.cjs — pin the enumerated subcommand list:
   the 'state unknown subcommand errors' test now also asserts that
   the dispatcher's error string includes 'complete-phase'. Without
   this, a future reformat of the available-subcommands enumeration
   could silently drop entries and the existing
   'Unknown state subcommand' substring check would still pass.

2. get-shit-done/bin/lib/state.cjs — tighten the Phase fallback in
   cmdStateCompletePhase: when STATE.md is missing the canonical
   '**Current Phase:**' field and the only phase signal is the
   decorated body line under '## Current Position' (e.g.
   'Phase: 01 (Foo) — EXECUTING'), the previous fallback returned
   the entire decorated string, producing messy downstream output:
     Status: Phase 01 (Foo) — EXECUTING complete
     Phase: 01 (Foo) — EXECUTING — COMPLETE
   The fallback now strips everything past the leading
   numeric/decimal token via /^\\s*([\\w.-]+)/ so degraded inputs
   produce clean output identical to the canonical path.

3. tests/state.test.cjs — two new tests in a dedicated describe block:
   - decorated Phase line writes clean Phase identifier
   - canonical Current Phase wins over Current Position decoration
   Both run real `gsd state complete-phase` against synthetic
   STATE.md fixtures and assert on the rendered Status field.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 09:03:36 -04:00
Tom Boucher
9472f343db feat(#2762): --minimal install profile (≥94% cold-start token reduction) (#2764)
* feat(#2762): add --minimal install profile to cut cold-start token cost

Eager system-prompt load from 86 gsd-* skill descriptions plus 33
subagent descriptions costs ~12k tokens per turn even in directories
with no .planning/. Frontier models (Sonnet 4.6 / Opus 4.7) with 200K-1M
context don't feel it; local LLMs with 32K-128K do.

--minimal (alias --core-only) installs only the main GSD loop:
new-project, discuss-phase, plan-phase, execute-phase, plus help/update.
Zero gsd-* subagents are written. Re-running gsd update without
--minimal expands to the full surface. Default install behavior is
unchanged.

DRY: a single stageSkillsForMode() helper filters the source dir; all
13 runtime-specific copy fns are unchanged because they recurse the
staged dir. Allowlist + helpers live in get-shit-done/bin/lib/install-
profiles.cjs as the single source of truth.

Manifest now records mode: 'minimal' | 'full' so future commands can
detect install profile.

Tested end-to-end: --minimal yields 6 skill folders + 0 agents; default
yields 86 + 33 (unchanged).

* docs(#2762): document --minimal install in README

Adds a collapsible 'Minimal Install' section under Getting Started
covering: who it's for (local LLMs, token-billed APIs), what you get
(6 skills, 0 subagents, ~700 token floor vs ~12k), and the critical
caveat that re-installing without --minimal restores the full surface
and erases the savings. Includes a comparison table, the manifest
inspection one-liner, and the use-case decision matrix.

* fix(#2762): address CodeRabbit review + CI failures

CodeRabbit findings:
1. Temp dir leak (Minor): stageSkillsForMode created tmp dirs that were
   never cleaned up. Added a module-level Set tracking every staged dir
   plus a process.on('exit') handler that rm -rf's them. Also wrap the
   copy loop in try/catch to remove a partially-populated tmp dir on
   mid-flight failure. Verified end-to-end: 0 leaked dirs in /tmp after
   a real install.

2. Codex full -> minimal stale state (Major): a previous full Codex
   install left agents/gsd-*.toml files plus [agents.gsd-*] sections in
   config.toml. The original cleanup only removed .md files, so a switch
   to --minimal would leave Codex still advertising the full agent
   surface. Cleanup now also handles .toml under isCodex, and minimal
   mode strips GSD sections from config.toml via the existing
   stripGsdFromCodexConfig helper (same path used by --uninstall).

3. Nitpick — Codex downgrade regression test: added a spawnSync-based
   end-to-end test that fakes a previous full install (stale gsd-*.md +
   gsd-*.toml + GSD-marked config.toml + a user-owned agent/setting),
   runs install.js --codex --minimal, and asserts stale GSD files +
   sections are gone while user content is preserved.

CI failures (inventory parity):
- docs/INVENTORY.md CLI Modules table now lists install-profiles.cjs
  with the correct headline count (30 -> 31).
- docs/INVENTORY-MANIFEST.json regenerated via gen-inventory-manifest.cjs.

Test count: 149 pass (was 116 in last commit; +14 new install-minimal +
all previously-failing inventory tests now green).

* test(#2762): expand install-minimal test coverage for future-proofing

Each new test pins a specific guarantee that closes off a future
regression class — turning every CodeRabbit finding (including the
nitpicky one) into a permanent guard.

cleanupStagedSkills suite (+3 tests):
- 'full mode does not register a staged dir' — catches a future
  regression where someone forgets the early-return in stageSkillsForMode
  and starts polluting STAGED_DIRS in default installs.
- 'exit handler registers exactly once across many calls' — catches
  removal of the exitHandlerRegistered guard. install.js has 13
  dispatch sites, so a missing guard would attach 13 listeners.
- 'mid-copy failure removes partial staged dir and re-throws' —
  intercepts fs.copyFileSync to throw mid-loop and asserts the staged
  dir count in /tmp is unchanged after the throw. Pins the exact
  CodeRabbit-flagged leak.

Claude full -> minimal downgrade (+1 test):
- Mirrors the Codex downgrade test for the .md-only path that the
  other 12 runtimes share. Asserts user-owned agents are preserved.

Manifest mode round-trip (+3 tests):
- Default install -> mode: 'full' with >6 skills and >0 agents
- --minimal -> mode: 'minimal' with exactly 6 skills and 0 agents
- --core-only alias produces identical manifest to --minimal

Allowlist scope guards (+3 tests):
- Every main-loop command IS in allowlist (positive)
- Off-loop commands (autonomous, ship, do, progress, next, fast,
  quick, debug, code-review, verify-work) are NOT (guards against
  silent scope creep — future contributor adds 'autonomous' to core
  and the floor erodes)
- Unknown mode strings fall through to full behavior — pre-emptive
  guard for future 'compact'/'tier2' modes that might forget to
  update the predicate.

Total: 25 tests in this file (was 15), 159/159 passing across the
install + inventory suites.

* fix(#2762): clean up staged tmp dirs on SIGINT/SIGTERM/SIGHUP

CodeRabbit follow-up review on c727bf5f flagged that process.on('exit')
does not fire on signal-driven termination. An installer is exactly
the kind of process users abort mid-run with Ctrl+C, so without
explicit signal handlers the staged tmp dirs in STAGED_DIRS would be
left behind until the OS reaps tmpdir.

Fix: ensureExitCleanup now also registers process.once handlers for
SIGINT, SIGTERM, SIGHUP. Each handler runs cleanupStagedSkills then
re-raises the same signal via process.kill(pid, sig) so the OS-default
handler takes over and the parent shell sees the correct exit code
(130 for SIGINT, etc.) — CI scripts and interactive users see the
abort the way they expect.

Test: spawns a child that stages a tmp dir then blocks; parent
captures the staged path from stdout, sends SIGINT, asserts (a) the
staged dir is gone after child exit, (b) child exits via the signal
not via code 0. Skipped on Windows (signal semantics differ; the
natural-exit cleanup test covers the Windows CI matrix).

Total: 26 tests in install-minimal.test.cjs (was 25).
2026-04-27 00:13:20 -04:00
Tom Boucher
ab5ad6c8bc fix(#2757): unquoted truths with colons crash annotate-dependencies (#2759)
parseMustHavesBlock dispatched on `includes(':')` to detect key-value pairs,
but unquoted YAML strings like `GET /foo/:id resolves...` and
`Class::Method is idempotent` also contain colons. When the KV regex failed
to match, `current` was left as `{}` (the empty object initialized before
the branch), which then caused `t.trim()` in roadmap.cjs to throw
`TypeError: t.trim is not a function`.

Two fixes:
- frontmatter.cjs: tighten the KV regex to require at least one space after
  the colon (`\s+` instead of `\s*`), matching YAML convention. When the
  regex still fails to match, fall back to treating the item as a plain
  string instead of leaving `current` as `{}`.
- roadmap.cjs: add `typeof t !== 'string'` guard before `.trim()` as a
  cheap safety net against any future parser anomaly.

Closes #2757

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 21:40:38 -04:00
Tom Boucher
1a230e69aa perf: convert discuss-phase SKILL.md @file imports to lazy per-branch reads (#2752)
* perf: convert discuss-phase @file imports to lazy per-branch reads

Replace eager @file directives in <execution_context> with on-demand
Read calls gated behind mode routing. discuss-phase-assumptions.md is
now only read when DISCUSS_MODE=assumptions; discuss-phase.md is only
read for the default discuss mode; discuss-phase-power.md and
templates/context.md are removed from the entry point entirely
(power mode is handled inside discuss-phase.md's lazy mode dispatch;
context.md is loaded at the write_context step).

Reduces tokens loaded at skill entry from ~13k to near zero.

Closes #2606
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(discuss-phase): use contiguous 'Read and execute' phrase in process block

The test at tests/discuss-mode.test.cjs:45 asserts that the <process>
block contains 'Read and execute' as a literal substring. The prior
wording split the instruction across two lines (Read(...) / Then execute),
so the substring match failed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(discuss-phase): restore discuss-phase-power reference in process block

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:37:45 -04:00
Tom Boucher
1405728292 fix: parseMustHavesBlock quoted strings + gsd state complete-phase (#2744)
* fix: parseMustHavesBlock quoted strings + gsd state complete-phase

Bug #2734: parseMustHavesBlock dropped quoted truths containing ':' because
fully-quoted strings like `"App-side UUIDv4: generated locally"` fell into
the kv-parse branch, the regex failed (value starts with '"'), and current
stayed as empty {}. Fix: detect fully-quoted strings before the ':' check
and extract them directly. Two regression tests added to frontmatter.test.cjs.

Bug #2735: `gsd state complete-phase` subcommand was missing — unknown
subcommands fell through to cmdStateLoad. Added cmdStateCompletePhase to
state.cjs (updates Status, Last Activity, and Current Position to COMPLETE),
exported it, and wired it into the case 'state': dispatch in gsd-tools.cjs.

Closes #2734
Closes #2735

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(state): unknown subcommand returns explicit error instead of silent fallthrough

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:37:43 -04:00
Tom Boucher
3246810876 feat: extend RUNTIME_PROFILE_MAP to gemini/qwen/opencode/copilot + settings-advanced UI (#2754)
Closes #2612

- Add gemini, qwen, opencode, and copilot entries to RUNTIME_PROFILE_MAP in core.cjs
- Group B runtimes (kilo, cline, cursor, windsurf, augment, trae, codebuddy, antigravity) intentionally have no built-in map and fall through to the existing unknown-runtime fallback
- Add 40 new tests to tests/issue-2517-runtime-aware-profiles.test.cjs covering each new runtime's three tiers, Group B fall-through, and partial override merge semantics
- Add Section 7 "Runtime Model Tiers" to settings-advanced.md with interactive UI to view and override built-in tier defaults per runtime
- Update docs/CONFIGURATION.md built-in tier table to include all four new runtimes

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:54 -04:00
Tom Boucher
e0b4561fa9 feat: add /gsd-edit-phase command to modify roadmap phases in place (#2753)
Adds a new slash command that lets developers modify any field of an
existing phase in ROADMAP.md without affecting phase number or position.

- commands/gsd/edit-phase.md: command file with --force flag support
- get-shit-done/workflows/edit-phase.md: full workflow with status guard,
  depends_on validation, diff+confirmation, and STATE.md update
- tests/edit-phase.test.cjs: 32 tests covering all acceptance criteria
- docs/INVENTORY.md, INVENTORY-MANIFEST.json, COMMANDS.md: registered

Closes #2617

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:52 -04:00
Tom Boucher
8788ab2381 feat: post-merge build & test gate — Build step, iOS/Xcode, serial mode (#2751)
* feat: post-merge build & test gate — Build step, iOS/Xcode, serial mode

Step 5.6 of execute-phase is extended per #2720:
- Renamed from "Post-merge test gate (parallel mode only)" to "Post-merge build & test gate"
- Gate now runs in both parallel mode (after worktree merge) and serial mode (after last plan)
- Added Step A: Build gate resolving BUILD_CMD from workflow.build_command config key, then auto-detecting via priority: config override → Xcode (.xcodeproj) → Makefile build: → Justfile → Cargo/Go/Python/npm. Xcode uses xcodebuild -list -json to get first scheme, then xcodebuild build -scheme ... -destination 'platform=iOS Simulator,name=iPhone 16'. Build failure increments WAVE_FAILURE_COUNT.
- Added Xcode/iOS detection to Step B (Test gate): when *.xcodeproj present and no workflow.test_command configured, uses xcodebuild test instead of the previous "no test runner detected" skip. Scheme reused from Step A when available.
- Documented workflow.build_command and workflow.test_command in docs/CONFIGURATION.md (table + JSON schema)

Closes #2720

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(execute-phase): extract Step 5.6 body to post-merge-gate.md sub-file

Moves the build-detection logic and xcodebuild commands from the inline
Step 5.6 body into execute-phase/steps/post-merge-gate.md, replacing it
with a single Read() reference. Reduces execute-phase.md from 1755 to
1647 lines, satisfying the ≤1700 XL-tier budget enforced by
tests/workflow-size-budget.test.cjs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:50 -04:00
Tom Boucher
71a3f86fbe docs: add end-to-end workflow walkthrough to USER-GUIDE.md (#2749)
Adds a concrete single-phase walkthrough (webhook validator project)
showing ROADMAP.md, CONTEXT.md, PLAN.md, SUMMARY.md, and STATE.md
excerpts and how each command consumes the previous step's output.
Also adds links to the walkthrough from README.md's nav bar and
How It Works section.

Closes #2359

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:47 -04:00
Tom Boucher
bf73cbe1a4 test(#2688): add review.models.claude tests for per-runtime review model config (#2748)
Adds two tests to review-model-config.test.cjs:
- isValidConfigKey accepts review.models.claude (schema validation)
- round-trip: config-set then config-get for review.models.claude

The dynamic key pattern (^review\.models\.[a-zA-Z0-9_-]+$), the workflow
model-read logic in review.md, and the CONFIGURATION.md docs were already
in place. Only the claude-specific test coverage was missing.

Closes #2688

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:45 -04:00
Tom Boucher
d5cd64dde5 fix(#2637): migrate legacy Codex [hooks] map format to [[hooks]] array on install (#2747)
Codex 0.124.0 changed the required config.toml hooks format from the old
map-style ([hooks.shell]) to array-of-tables ([[hooks]]). Old GSD installs
that wrote the legacy format now cause a startup parse error on upgrade.

Add migrateCodexHooksMapFormat() which detects non-array [hooks] and
[hooks.TYPE] sections and rewrites them to [[hooks]] entries with an
injected type = "TYPE" key. The migration runs at the start of every Codex
install so affected configs self-heal on the next `gsd install --codex`.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:43 -04:00
Tom Boucher
8f2ec0e8f7 fix: add explicit wait-for-subagent instructions in orchestration workflows (#2755)
Adds ORCHESTRATOR RULE blockquotes immediately after every Task() spawn
in 26 GSD workflow files, instructing the parent orchestrator to stop
working on the task while the subagent is active. This prevents the
parallel-work anti-pattern on Codex runtime where the parent continues
reading files and producing duplicate/conflicting output after spawning.

Rules are placed inline at each spawn point (not as generic headers)
so they are adjacent to and unambiguously associated with each Task()
call. Background Task() spawns get a variant noting not to return to
the spawning context until the subagent reports back.

Closes #2729

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:35 -04:00
Tom Boucher
7255539ff9 fix: validate LM Studio model identity in review workflow (#2746)
* fix: validate LM Studio model identity in review workflow

Capture the full API response before extracting content, then compare
the top-level `.model` field against the configured LM_STUDIO_MODEL.
Emits a warning to stderr if LM Studio served a different model than
requested, while still proceeding with the review response.

Closes #2721

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(review): skip LM Studio review file when content is empty instead of writing error text

Also applies the same fix to llama.cpp which had the identical pattern of writing
a literal error string into the review temp file when content was empty/null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:28 -04:00
Tom Boucher
b8bbc74192 fix(sdk): preserve nested keys from globalDefaults in configNewProject (#2745)
When building nested config sections (workflow, git, hooks, agent_skills,
features), the deep merge was missing globalDefaults for those sections,
causing user values from ~/.gsd/defaults.json to be silently dropped.

Added globalDefaults spread at the correct precedence level (hardcoded <
globalDefaults < userChoices) for all five nested keys, and added three
test cases verifying the merge works end-to-end via HOME env var override.

Closes #2673

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:33:26 -04:00
Tom Boucher
2b95ccbddd Merge pull request #2743 from gsd-build/fix/2714-workstream-config-inheritance
feat(config): workstream config.json inherits from root .planning/config.json
2026-04-26 11:49:30 -04:00
Tom Boucher
4a05283bc8 Merge pull request #2742 from gsd-build/fix/2731-workstream-query-handler-threadthrough
fix(query): thread workstream through all query handlers
2026-04-26 11:49:25 -04:00
Tom Boucher
80f4c9063f Merge pull request #2741 from gsd-build/fix/2727-codex-agents-toml-format
fix(installer): revert Codex agents section to [agents.<name>] struct format
2026-04-26 11:49:19 -04:00
Tom Boucher
41787e361f Merge pull request #2740 from gsd-build/fix/2641-details-summary-milestone-detection
fix(phase-lifecycle): skip <details>-wrapped sections in milestone detection
2026-04-26 11:49:14 -04:00
Tom Boucher
0eef943f0a Merge pull request #2739 from gsd-build/fix/2732-graphify-cli-invocation
fix(graphify): update CLI invocation from legacy flag form to subcommand
2026-04-26 11:49:09 -04:00
Tom Boucher
bbf33b608e Merge pull request #2738 from gsd-build/fix/2644-milestone-complete-version-arg
test(query): Vitest regression guard for milestone.complete version arg (fix #2644)
2026-04-26 11:49:03 -04:00
Tom Boucher
9e63d14709 Merge pull request #2737 from gsd-build/fix/2726-phase-add-bullet-bold-roadmap
fix(phase-lifecycle): detect phases in bullet/bold ROADMAP formats for phase.add
2026-04-26 11:48:58 -04:00
Tom Boucher
5b4a239ead Merge pull request #2736 from gsd-build/fix/2728-phase-complete-roadmap-corruption
fix(phase-lifecycle): prevent plan-line corruption when **Plans:** has no inline value
2026-04-26 11:48:52 -04:00
Tom Boucher
cb149383c1 fix(config): bump MODEL_ALIAS_MAP to claude-opus-4-7 (#2733)
* 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>
2026-04-26 11:48:47 -04:00
Tom Boucher
274fc524cd fix(tests): derive STATE.md phase number from phaseDir in setupProject fixture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 11:46:57 -04:00
Tom Boucher
b7b6f89776 fix(phase-lifecycle): fast-path replaceInCurrentMilestone only when pattern matches after
The previous guard `if (after.trim().length > 0)` incorrectly triggered
when `after` contained only footer text (e.g. `---\n*Last updated*`).
In that case `after.replace(pattern, replacement)` is a no-op and the
function returned unchanged content instead of falling through to the
slow path that searches inside the last `<details>` block.

Fix: capture the replaced string first, then only take the fast path
when the replacement actually changed `after`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 11:46:40 -04:00
Tom Boucher
76f1d20d80 test(phase-lifecycle): add regression for active <details> with trailing footer
Adds a case where the active milestone is the last <details> block but
footer text (--- / *Last updated*) follows </details>, triggering the
fast-path to replace in the footer instead of inside the block.

Closes #2743
2026-04-26 11:36:46 -04:00
Tom Boucher
022b577922 test(graphify): tighten version-check assertions per CodeRabbit nitpick
- Success path: add explicit python3Calls.length === 0 assertion so "no
  fallback" is stated directly rather than implied by calls.length === 1
- Fallback path: add explicit calls[0].cmd === 'graphify' assertion so
  "graphify precedes python3" is verified by name, not just argument
2026-04-26 11:36:14 -04:00
Tom Boucher
493e251bab refactor(tests): extract assertMilestoneSuccess helper in decomposed-handlers.test.ts
Centralizes repeated result.data cast + common success-field checks
per coderabbit review suggestion on PR #2738.
2026-04-26 11:24:31 -04:00
Tom Boucher
cfc79d211f fix(config-query): thread workstream through resolveModel handler
resolveModel ignored _workstream, unlike configGet/configPath which both
forward it to planningPaths/loadConfig. Different workstreams may have
different model_profile settings.

Addresses coderabbit finding on PR #2742.
2026-04-26 11:23:51 -04:00
Tom Boucher
7c08a155ea test(graphify): tighten call-sequence assertions per coderabbit review
Adds explicit call-count and ordering assertions to version-check tests:
- Success path: exactly 1 spawnSync (graphify --version only, no python fallback)
- Failure path: graphify --version attempted first, python3 fallback second

Addresses coderabbit nitpick on PR #2739.
2026-04-26 11:21:54 -04:00
Tom Boucher
8270f17773 fix(phase-lifecycle): handle project-code-prefixed dirs in phaseAdd fallback scan
Filesystem fallback regex /^(\d+)-/ missed directories like CK-45-foundation
when project_code is configured. Updated to /^(?:[A-Z][A-Z0-9]*-)?(\d+)-/i.

Addresses coderabbit finding on PR #2737.
2026-04-26 11:21:33 -04:00
Tom Boucher
c85e65ec03 fix(roadmap-update-plan-progress): propagate Plans** regex hardening from phase-lifecycle
Applies the same [ \t]* + section-boundary lookahead fix that was applied
to planCountPattern in phase-lifecycle.ts. roadmap.update-plan-progress
shared the same corruption vector via \s* crossing newlines.

Addresses coderabbit finding on PR #2736.
2026-04-26 11:19:34 -04:00
Tom Boucher
1cb4bebcf5 feat(config): workstream config.json inherits from root .planning/config.json
- 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
2026-04-26 11:15:16 -04:00
Tom Boucher
3a623b1117 fix(query): thread workstream through all query handlers
All 18+ query handlers accepted _workstream but never forwarded it to
planningPaths/loadConfig/getMilestoneInfo. Remove _ prefix and pass
workstream to all internal helper calls so --ws flag actually scopes
path resolution.

Affected handlers: initNewProject, initProgress, initManager, configGet,
configPath, configSet, configSetModelProfile, configNewProject,
configEnsureSection, validateHealth, commit, checkCommit, commitToSubrepo.

Also fixes validate.ts to use paths.* fields from planningPaths instead
of hardcoded join(projectDir, '.planning') paths.

Closes #2731
2026-04-26 10:53:17 -04:00
Tom Boucher
f6cddc5b2f test(query): add failing tests for workstream path threading in init-complex
Demonstrates that initProgress and initManager ignore the workstream
parameter, reading from root .planning/ instead of the workstream
subdirectory.

Closes #2731
2026-04-26 10:38:10 -04:00
Tom Boucher
7924abec0c fix(installer): revert Codex agents section to [agents.<name>] struct format
[[agents]] sequence format (introduced in #2645) is rejected by
codex-cli 0.124.0 with "invalid type: sequence, expected struct AgentsToml".
Revert to [agents.<name>] struct format which is correct for 0.120.0+.

stripCodexGsdAgentSections already handles both formats for self-healing
configs written by previous GSD versions using [[agents]].

Closes #2727
2026-04-26 10:35:30 -04:00
Tom Boucher
8f7f43abaa test(installer): add failing test for [agents.<name>] struct format (Codex 0.124.0+)
Adds test asserting generateCodexConfigBlock emits [agents.<name>] struct
format, not [[agents]] sequence format. Confirms RED phase for #2727 fix.
2026-04-26 10:35:23 -04:00
Tom Boucher
0f17cfc71d fix(phase-lifecycle): skip <details>-wrapped sections in milestone detection
replaceInCurrentMilestone's lastIndexOf('</details>') heuristic fails
when the active milestone itself is wrapped in a <details> block — the
after-slice is empty so the replacement is silently dropped.

Fix detects this case (after.trim().length === 0) and falls back to
locating the last complete <details>…</details> span and applying the
replacement only inside it, leaving all earlier archived-milestone
blocks untouched.

Closes #2641
2026-04-26 10:28:48 -04:00
Tom Boucher
a7d3bb948b test(phase-lifecycle): add failing test for active milestone in <details> block
Closes #2641
2026-04-26 10:23:41 -04:00
Tom Boucher
f89a56eb55 fix(graphify): update CLI invocation from legacy flag form to subcommand
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
2026-04-26 10:20:41 -04:00
Tom Boucher
f8a0e6f145 test(query): add Vitest regression tests for milestone.complete version arg (fix #2644)
milestoneComplete was imported in decomposed-handlers.test.ts but had zero
test coverage. The original defect (6f79b1d) called phasesArchive([], ...)
instead of forwarding the positional version arg; the wrapping try/catch
swallowed the GSDError into { completed: false, reason: String(err) },
masking a programming error as a legitimate negative answer.

Add five Vitest tests that lock in the correct contract:
- positional version arg is extracted from args[0] and echoed in response
- missing version throws GSDError (not masked as completed: false)
- --archive-phases flag is processed
- --name flag sets milestone name
- response shape has version/date/phases/milestones_updated fields

Closes #2644

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 10:18:08 -04:00
Tom Boucher
54cbc2ad96 fix(phase-lifecycle): detect phases in bullet/bold ROADMAP formats
phaseAdd's phase-number regex only matched heading format (## Phase N:),
missing bullet checklist (- [x] Phase N:) and bold (**Phase N:**) entries.
When zero regex matches, newPhaseId defaulted to 1.

Fix: broaden regex to match all three formats, and add filesystem fallback
scanning .planning/phases/ when ROADMAP scan finds nothing.

Closes #2726
2026-04-26 10:00:39 -04:00
Tom Boucher
a9f49f8f9d fix(phase-lifecycle): prevent plan-line overwrite when **Plans:** is bare
\s* after **Plans:** matches newlines, causing [^\n]+ to consume the first
plan checkbox when the **Plans:** field has no value on the same line.
Additionally, the lazy [\s\S]*? could cross section boundaries when the
current section had no **Plans:** value, corrupting a later section.

Fix 1: replace \s* with [ \t]* to restrict post-colon match to horizontal
whitespace only.
Fix 2: replace [\s\S]*? with (?:(?!\n#{2,4})[\s\S])*? to prevent the
pattern from crossing into a new section heading.

Closes #2728
2026-04-26 09:46:57 -04:00
Tom Boucher
394403ae06 test(phase-lifecycle): add failing regression test for #2728
When **Plans:** appears on its own line with no inline value, the
planCountPattern regex crosses the newline and destroys the first
plan checkbox line by replacing it with the literal "N/N plans complete"
string.

This test documents the expected correct behavior and will fail until
the planCountPattern regex is fixed.
2026-04-26 09:46:51 -04:00
Lex Christopherson
f3685d9173 1.38.5 2026-04-25 17:56:06 -06:00
Lex Christopherson
22b73f548d docs: update changelog for v1.38.5 2026-04-25 17:56:06 -06:00
Lex Christopherson
2fafbd2753 fix(sdk): pass phaseDir to executor prompt so SUMMARY.md lands in .planning/
The SDK's buildExecutorPrompt told executors to "Create a SUMMARY.md file"
with no directory path, causing them to write it in cwd (project root)
instead of .planning/phases/{phase}/. Thread phaseDir from PhaseRunner
through PromptFactory and into the completion instructions so the executor
gets an explicit path like `.planning/phases/01-auth/01-01-SUMMARY.md`.

Backward compatible — buildExecutorPrompt still accepts a plain string
(agentDef) for existing callers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 17:56:06 -06:00
Tom Boucher
470c1a0bff fix(#2722): forensics gh commands pin --repo gsd-build/get-shit-done (#2723)
* 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>
2026-04-25 16:47:55 -04:00
Tom Boucher
caf6974bbf fix(ci): remove stale SDK-variant tests for files deleted in 377a6d2 (#2725)
377a6d2 deleted sdk/prompts/agents/ and sdk/prompts/workflows/ (13 files)
but did not update 3 test files that reference them, causing ENOENT
failures on every CI run (main and all PRs) since that commit.

Removed:
- sdk/prompts/agents variants describe block (enh-2427-sycophancy-hardening)
- PLAN_PHASE_SDK_PATH constant and headless plan-phase test (post-planning-gaps-2493)
- sdk/prompts/workflows/verify-phase.md describe block (verifier-deferred-items)

The underlying behaviour is covered by the existing main agent/workflow
tests; the SDK variant tests are moot now that the SDK loads installed
files instead of bundled stripped-down copies.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:26:15 -04:00
Lex Christopherson
a5a2d44121 1.38.4 2026-04-25 14:13:49 -06:00
Lex Christopherson
73abae60f0 1.38.3 2026-04-25 14:11:46 -06:00
Lex Christopherson
efab0545c7 docs: update changelog for v1.38.3 2026-04-25 14:11:33 -06:00
Lex Christopherson
f0953dec0c fix(sdk): prevent interactive tool calls in headless self-discuss mode
The discuss step loaded the full interactive workflow prompt which
instructs the agent to use AskUserQuestion, Skill(), and area selection
UIs. In headless auto mode, the agent followed these instructions and
tried to interact with a non-existent user.

Fix: prepend a mandatory headless override BEFORE the workflow prompt
that explicitly forbids interactive tools and instructs the agent to
make all decisions autonomously. Prepending (not appending) ensures
the override takes priority over conflicting instructions later in
the prompt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 14:08:05 -06:00
Lex Christopherson
25d9763878 fix(sdk): fix executor plan loading, plan ID derivation, and verification outcome parsing
Bug 1: phasePlanIndex derived empty planId for bare PLAN.md files.
Fixed to use 'PLAN' as the ID, with matching SUMMARY.md detection.

Bug 2: executeSinglePlan passed null to buildPrompt instead of the
actual parsed plan. The executor needs the plan content (tasks,
objectives) to know what to build. Now loads and parses the plan
file before building the prompt.

Bug 3: parseVerificationOutcome checked session exit code, not what
the verifier wrote. A session that runs without errors but writes
status: gaps_found to VERIFICATION.md was treated as 'passed'. Now
queries check.verification-status to read the actual VERIFICATION.md
frontmatter status field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 14:08:05 -06:00
Lex Christopherson
377a6d2c6e fix(sdk): use installed agent/workflow prompts instead of stripped-down bundled copies
The SDK bundled its own agents and workflows at ~17% the size of the real
ones, missing critical instructions like file naming conventions, scope
reduction rules, discovery protocols, and TDD integration. This caused
the planner to create a single PLAN.md instead of properly named
per-plan files (01-01-PLAN.md, 01-02-PLAN.md), breaking wave-based
parallel execution.

- Invert load priority: installed GSD agents/workflows first, SDK
  bundled as last-resort fallback
- Replace @-reference stripping with resolution (read + inline content)
- Use full agent definitions instead of extracting only the <role> block
- Delete sdk/prompts/agents/ and sdk/prompts/workflows/ (13 files)
- Delete headless-prompts.test.ts (validated deleted files)
- Thread projectDir through sanitizePrompt for @-reference resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 14:08:05 -06:00
Tom Boucher
1068223439 feat(#2500): enrich gsd-codebase-mapper arch-focus ARCHITECTURE.md with ASCII diagrams, data flow traces, and constraints (#2715)
* feat(#2500): enrich gsd-codebase-mapper arch-focus ARCHITECTURE.md template

The codebase mapper's arch-focus template was a sparse structural inventory.
After major refactors, the research/ARCHITECTURE.md (created at /gsd-new-project
and never refreshable) went stale while the refreshable codebase version lacked
the visual richness that makes architecture docs useful for planning.

Add to the ARCHITECTURE.md template:
- <!-- refreshed: {date} --> marker at the top (maintainer request)
- ASCII system overview diagram with component boxes and flow arrows
- Component responsibility table (Component / Responsibility / File)
- Primary request path traces with numbered steps and code references
- Architectural constraints section (threading, global state, circular imports)
- Anti-patterns section with codebase-specific patterns and correct alternatives

All existing sections (Pattern Overview, Layers, Key Abstractions, Entry Points,
Error Handling, Cross-Cutting Concerns) are preserved.

7 new tests in tests/enh-2500-codebase-mapper-arch-rich-format.test.cjs verify
each required section is present in the deployed template.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2500): resolve CodeRabbit review findings

- Add 'text' language tag to bare ASCII diagram fenced block (markdownlint MD040)
- Tighten data flow test: require '### Primary Request Path' heading, 3+
  numbered steps, and file:line reference pattern — prevents loose-match
  false positives
- Tighten constraints test: require '## Architectural Constraints' heading
  AND Threading / Global state / Circular imports tokens — prevents broad
  keyword matches masking regressions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 14:23:40 -04:00
Tom Boucher
b40110111d feat(#2306): plan-review-convergence v2 — CYCLE_SUMMARY contract, config gate, local model reviewers (#2718)
* 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 #2306
Closes #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>
2026-04-25 14:18:34 -04:00
Tom Boucher
3da9420a38 fix(#2698,#2678): CRLF agent-block strip regex + local install skips SDK check (#2710)
Fixes #2698 — The two separate LF/CRLF .replace() calls in the Codex hooks
migration could not handle mixed line endings (e.g. header in LF, body in
CRLF), leaving stale gsd-update-check blocks after reinstall. Consolidated to
a single \r?\n-aware regex with gm flags that handles LF, CRLF, and mixed
content in one pass.

Fixes #2678 — installSdkIfNeeded() called process.exit(1) unconditionally when
sdk/dist/cli.js was missing, even during --local installs where users cannot
write to global node_modules. Added isLocal option: when true, prints a warning
and returns instead of exiting.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:16:04 -04:00
Tom Boucher
3fe5759d7c fix(#2686): review-fix agent uses isolated git worktree, prevents main-tree race (#2705)
* fix(#2686): review-fix agent now uses git worktree for isolation

The gsd-code-fixer agent operated directly against the main working tree,
racing any concurrent foreground session for HEAD, the index, and on-disk
files. Added a setup_worktree step (git worktree add /tmp/sv-N-reviewfix
HEAD) as the first action before any file operations, with unconditional
git worktree remove cleanup on exit. Mirrors the pattern used by all other
GSD per-issue agents.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2686): address CodeRabbit review — mktemp unique path, branch-aware worktree, tighten test assertions

- Use mktemp -d for unique worktree path (prevents concurrent-run collision)
- Resolve branch via git branch --show-current before worktree add (prevents detached HEAD)
- Error-and-exit on worktree add failure instead of force-removing shared path
- Test: use .exec().index for checkout position (not indexOf on match string)
- Test: match gsd-sdk query commit as well as git commit for ordering assertion
- Test: tighten /tmp path assertion to require actual /tmp/sv- assignment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:15:39 -04:00
Tom Boucher
8e21c9b1b7 fix(#2684,#2676): milestone.complete arg forwarding + parallel milestone phase routing (#2708)
* test(#2692): add behavioral --wave N test, annotate source-text assertions

Adds two behavioral tests for wave filtering via phase-plan-index:
- Verifies plans with wave frontmatter are correctly grouped by wave number
- Verifies plans with no wave field default to wave 1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2684,#2676): milestone.complete version validation + parallel milestone phase routing

#2684: Confirms milestone.complete correctly validates and uses its version
argument end-to-end. The inline archive path in milestoneComplete already
forwarded version correctly; regression tests lock in that contract.

#2676: phase.complete applied getMilestonePhaseFilter unconditionally, using
STATE.md's primary milestone to scope the candidate set. When the completed
phase belongs to a parallel (secondary) milestone, the filter excluded all
phases from that milestone, leaving an empty candidate set and incorrectly
returning is_last_phase: true / next_phase: null.

Fix: before applying the milestone filter in Step E, check whether the
completed phase itself appears in the filtered set. If not, skip the filter
for both the directory scan and the ROADMAP.md fallback so phases from the
secondary milestone remain visible for next-phase detection.

Closes #2684
Closes #2676

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:10:01 -04:00
Tom Boucher
8393f4b355 fix(#2601): config-set-model-profile now accepts 'inherit' value (#2707)
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>
2026-04-25 12:09:27 -04:00
Tom Boucher
b7ff14fe51 fix(#2687): derive KNOWN_TOP_LEVEL from DYNAMIC_KEY_PATTERNS to eliminate read-side drift (#2706)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:08:59 -04:00
Tom Boucher
94f8e895c0 test(#2692): add behavioral --wave N test, annotate source-text assertions (#2704)
Adds two behavioral tests for wave filtering via phase-plan-index:
- Verifies plans with wave frontmatter are correctly grouped by wave number
- Verifies plans with no wave field default to wave 1

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:04:16 -04:00
Tom Boucher
70f01e0c57 test(#2695): replace CR-CONFIG source-grep + config bypass tests with behavioral config-set assertions (#2702)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:03:39 -04:00
Tom Boucher
56ae7a73f5 test(#2694): eliminate shared mutable content state — move readFileSync to describe scope (#2703)
Fixes #2694

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:02:58 -04:00
Tom Boucher
aeef87de7f docs(test-standards): enforce no-source-grep rule with CI linter + CONTRIBUTING.md (#2700)
* docs(test-standards): enforce no-source-grep rule with CI linter + update CONTRIBUTING.md

Adds scripts/lint-no-source-grep.cjs — a static linter that detects readFileSync
on .cjs source files in tests without an allow-test-rule annotation. Wires it
into CI as a new lint-tests job in test.yml and as npm run lint:tests.

Resolves all 9 existing violations across the test suite:
- Rewrites workspace routing tests (3) as behavioral runGsdTools calls that
  verify each command is router-recognized (exit != "Unknown init workflow")
- Adds allow-test-rule annotations with explanatory comments to 7 legitimate
  structural tests: architectural invariants (locking, orphan-worktree),
  structural regression guards (milestone-regex-global), docs-parity
  (config-field-docs), integration-test-input (copilot-install), and
  structural-implementation-guards (bug-1891, discuss-mode)

Updates CONTRIBUTING.md Testing Standards section with:
- "Prohibited: Source-Grep Tests" section with the before/after pattern,
  root cause analysis of why it breaks (commit 990c3e64), and CI reference
- allow-test-rule exemption table (6 recognized categories with when-to-use)
- "CI Test Quality Checks" table showing lint-tests job and local run command

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve CodeRabbit findings on PR #2700

- CONTRIBUTING.md: "four recognized categories" → "six" (table has 6 rows)
- workspace.test.cjs: use positional args in routing tests (no --name flag)
- lint-no-source-grep.cjs: add source-dir guard to READ_WITH_INLINE_CJS_RE
  (mirrors CJS_PATH_CONST_RE's protection against false positives on temp files)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(lint): tighten allow-test-rule and add recursive test discovery

- ALLOW_ANNOTATION now requires at least one non-whitespace char after the
  colon so bare '// allow-test-rule:' cannot bypass the lint gate
- findTestFiles() recurses into subdirectories so nested *.test.cjs files
  are covered if the tests/ tree ever grows subdirs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 11:34:55 -04:00
Tom Boucher
b1a670e662 fix(#2697): replace retired /gsd: prefix with /gsd- in all user-facing text (#2699)
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>
2026-04-25 10:59:33 -04:00
Tom Boucher
7c6f8005f3 test: destroy 9 config-schema.cjs/core.cjs source-grep tests, replace with behavioral config-set (#2696)
* test: destroy 9 config-schema.cjs/core.cjs source-grep tests, add behavioral config-set tests (#2691, #2693)

Replace source-grep theater with config-set behavioral tests:
- execute-phase-wave: config-set workflow.use_worktrees replaces VALID_CONFIG_KEYS grep
- inline-plan-threshold: delete redundant source-grep (behavioral test at L36 already covered it)
- plan-bounce: config-set for plan_bounce / plan_bounce_script / plan_bounce_passes replaces 3 key-presence greps
- code-review: config-set for code_review / code_review_depth replaces 2 greps; removes CONFIG_PATH constant
- thinking-partner: config-set features.thinking_partner replaces two greps (config-schema.cjs AND core.cjs)

Behavioral tests survive refactors (no path constants, no file reads). The config-schema.cjs →
core.cjs migration commit 990c3e64 happened because these tests groped source paths.

Add allow-test-rule: source-text-is-the-product annotations to legitimate product-content tests:
autonomous-allowed-tools, agent-frontmatter, agent-skills-awareness, bug-2334, bug-2346,
execute-phase-wave (MD reads), plan-bounce (workflow reads). Annotations explain WHY text
inspection is the right level of testing for AI instruction files.

Closes #2691
Closes #2693

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: address CodeRabbit findings on #2696

- agent-frontmatter.test.cjs: move allow-test-rule annotation from block comment
  to standalone // line comment so rule scanners can detect it
- thinking-partner.test.cjs: strengthen config-set test with config-get read-back
  assertion to verify the value was persisted, not just accepted (exit 0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: tighten thinking_partner config assertion per CodeRabbit (#2696)

Replace config-get output substring check (includes('true') false-positive
risk) with a direct JSON read of .planning/config.json, asserting the
exact persisted value via strictEqual. This also validates the config file
was created, catching silent key-acceptance without persistence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 10:50:54 -04:00
Tom Boucher
cd05725576 fix(#2661): unconditional plan-checkbox sync in execute-plan (#2682)
* 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
2026-04-24 20:27:59 -04:00
Tom Boucher
c811792967 fix(#2660): capture prose after labeled bold in extractOneLinerFromBody (#2679)
* 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.
2026-04-24 20:22:29 -04:00
Tom Boucher
34b39f0a37 test(#2659): regression guard against bare output() in audit-open handler (#2680)
* fix(#2659): qualify bare output() calls in audit-open handler

The audit-open dispatch case in bin/gsd-tools.cjs previously called bare
output() on both --json and text branches, which crashed with
ReferenceError: output is not defined. The core module is imported as
`const core`, so every other case uses core.output(). HEAD already
qualifies the calls correctly; this commit adds a regression test that
invokes `audit-open` and `audit-open --json` through runGsdTools and
asserts a clean exit plus non-empty stdout (and an explicit check that
the failure mode is not ReferenceError). The test fails on any revision
where either call reverts to bare output().

Closes #2659

* test(#2659): assert valid JSON output in --json mode

CodeRabbit nit: tighten --json regression coverage by parsing stdout
and asserting the result is a JSON object/array, not just non-empty.
2026-04-24 20:22:17 -04:00
Tom Boucher
b1278f6fc3 fix(#2674): align initProgress with initManager ROADMAP [x] precedence (#2681)
initProgress computed phase status purely from disk (PLAN/SUMMARY counts),
consulting the ROADMAP `- [x] Phase N` checkbox only for phases with no
directory. initManager, by contrast, applied an explicit override: a
ROADMAP `[x]` forces status to `complete` regardless of disk state.

Result: a phase with a stub directory (no SUMMARY.md) and a ticked
ROADMAP checkbox reported `complete` from /gsd-manager and `pending`
from /gsd-progress — same data, different answer.

Apply ROADMAP-[x]-wins as the unified policy inside initProgress, mirroring
initManager's override. A user who typed `- [x] Phase 3` has made an
explicit assertion; a leftover stub dir is the weaker signal.

Adds sdk/src/query/init-progress-precedence.test.ts covering six cases
(stub dir + [x], full dir + [x], full dir + [ ], stub dir + [ ],
ROADMAP-only + [x], and completed_count parity). Pre-fix: cases 1 and 6
failed. Post-fix: all six pass. No existing tests were modified.

Closes #2674
2026-04-24 20:20:11 -04:00
Tom Boucher
303fd26b45 fix(#2662): add state.add-roadmap-evolution SDK handler; insert-phase uses it (#2683)
/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
2026-04-24 20:20:02 -04:00
Tom Boucher
7b470f2625 fix(#2633): ROADMAP.md is the authority for current-milestone phase counts (#2665)
* 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.
2026-04-24 18:11:12 -04:00
Tom Boucher
c8ae6b3b4f fix(#2636): surface gsd-sdk query failures and add workflow↔handler parity check (#2656)
* 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.
2026-04-24 18:10:45 -04:00
Tom Boucher
7ed05c8811 fix(#2645): emit [[agents]] array-of-tables in Codex config.toml (#2664)
* fix(#2645): emit [[agents]] array-of-tables in Codex config.toml

Codex ≥0.116 rejects `[agents.<name>]` map tables with `invalid type:
map, expected a sequence`. Switch generateCodexConfigBlock to emit
`[[agents]]` array-of-tables with an explicit `name` field per entry.

Strip + merge paths now self-heal on reinstall — both the legacy
`[agents.gsd-*]` map shape (pre-#2645 configs) and the new
`[[agents]]` with `name = "gsd-*"` shape are recognized and replaced,
while user-authored `[[agents]]` entries are preserved.

Fixes #2645

* fix(#2645): use TOML-aware parser to strip managed [[agents]] sections

CodeRabbit flagged that the prior regex-based stripper for [[agents]]
array-of-tables only matched headers at column 0 and stopped at any line
beginning with `[`. An indented [[agents]] header would not terminate the
preceding match, so a managed `gsd-*` block could absorb a following
user-authored agent and silently delete it.

Replace the ad-hoc regex with the existing TOML-aware section parser
(getTomlTableSections + removeContentRanges) so section boundaries are
authoritative regardless of indentation. Same logic applies to legacy
[agents.gsd-*] map sections.

Add a comprehensive mixed-shape test covering multiple GSD entries (both
legacy map and new array-of-tables, double- and single-quoted names)
interleaved with multiple user-authored agents in both shapes — verifies
all GSD entries are stripped and every user entry is preserved.
2026-04-24 18:09:01 -04:00
Tom Boucher
0f8f7537da fix(#2652): layer ~/.gsd/defaults.json over built-ins in SDK loadConfig (#2663)
* fix(#2652): layer ~/.gsd/defaults.json over built-ins in SDK loadConfig

SDK loadConfig only merged built-in CONFIG_DEFAULTS, so pre-project init
queries (e.g. resolveModel in Codex installs) ignored user-level knobs like
resolve_model_ids: "omit" and emitted Claude model aliases from MODEL_PROFILES.

Port the user-defaults layer from get-shit-done/bin/lib/config.cjs:65 to the
TS loader. CJS parity: user defaults only apply when no .planning/config.json
exists (buildNewProjectConfig already bakes them in at /gsd:new-project time).

Fixes #2652

* fix(#2652): isolate GSD_HOME in test, refresh loadConfig JSDoc (CodeRabbit)
2026-04-24 18:08:07 -04:00
Tom Boucher
709f0382bf fix(#2639): route Codex TOML emit through full Claude→Codex neutralization pipeline (#2657)
installCodexConfig() applied a narrow path-only regex pass before
generateCodexAgentToml(), skipping the convertClaudeToCodexMarkdown() +
neutralizeAgentReferences(..., 'AGENTS.md') pipeline used on the .md emit
path. Result: emitted Codex agent TOMLs carried stale Claude-specific
references (CLAUDE.md, .claude/skills/, .claude/commands/, .claude/agents/,
.claudeignore, bare "Claude" agent-name mentions).

Route the TOML path through convertClaudeToCodexMarkdown and extend that
pipeline to cover bare .claude/<subdir>/ references and .claudeignore
(both previously unhandled on the .md path too). The $HOME/.claude/
get-shit-done prefix substitution still runs first so the absolute Codex
install path is preserved before the generic .claude → .codex rewrite.

Regression test: tests/issue-2639-codex-toml-neutralization.test.cjs —
drives installCodexConfig against a fixture containing every flagged
marker and asserts the emitted TOML contains zero CLAUDE.md / .claude/
/ .claudeignore occurrences and that Claude Code / Claude Opus product
names survive.

Fixes #2639
2026-04-24 18:06:13 -04:00
Tom Boucher
a6e692f789 fix(#2646): honor ROADMAP [x] checkboxes when no phases/ directory exists (#2669)
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
2026-04-24 18:05:41 -04:00
Tom Boucher
b67ab38098 fix(#2643): align skill frontmatter name with workflow gsd: emission (#2672)
Flat-skills installs write SKILL.md files under gsd-<cmd>/ dirs, but
Claude Code resolves skills by their frontmatter `name:`, not directory
name. PR #2595 normalized every `/gsd-<cmd>` to `/gsd:<cmd>` across
workflows — including inside `Skill(skill="...")` args — but the
installer still emitted `name: gsd-<cmd>`, so every Skill() call on a
flat-skills install resolved to nothing.

Fix: emit `name: gsd:<cmd>` (colon form) in
`convertClaudeCommandToClaudeSkill`. Keep the hyphen-form directory
name for Windows path safety.

Codex stays on hyphen form: its adapter invokes skills as `$gsd-<cmd>`
(shell-var syntax) and a colon would terminate the variable name.
`convertClaudeCommandToCodexSkill` uses `yamlQuote(skillName)` directly
and is untouched.

- Extract `skillFrontmatterName(dirName)` helper (exported for tests).
- Update claude-skills-migration and qwen-skills-migration assertions
  that encoded the old hyphen emission.
- Add `tests/bug-2643-skill-frontmatter-name.test.cjs` asserting every
  `Skill(skill="gsd:<cmd>")` reference in workflows resolves to an
  emitted frontmatter name.

Full suite: 5452/5452 passing.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:05:40 -04:00
Tom Boucher
06463860e4 fix(#2638): write sub_repos to canonical planning.sub_repos (#2668)
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
2026-04-24 18:05:33 -04:00
Tom Boucher
259c1d07d3 fix(#2647): guard tarball ships sdk/dist so gsd-sdk query works (#2671)
v1.38.3 shipped without sdk/dist/ because the outer `files` whitelist
and `prepublishOnly` chain had drifted. The `gsd-sdk` bin shim then
fell through to a stale @gsd-build/sdk@0.1.0 (pre-`query`), breaking
every workflow that called `gsd-sdk query <noun>` on fresh installs.

Current package.json already restores `sdk/dist` + `build:sdk`
prepublish; this PR locks the fix in with:

- tests/bug-2647-outer-tarball-sdk-dist.test.cjs — asserts `files`
  includes `sdk/dist`, `prepublishOnly` invokes `build:sdk`, the
  shim resolves sdk/dist/cli.js, `npm pack --dry-run` lists
  sdk/dist/cli.js, and the built CLI exposes a `query` subcommand.
- scripts/verify-tarball-sdk-dist.sh — packs, extracts, installs
  prod deps, and runs `node sdk/dist/cli.js query --help` against
  the real tarball output.
- .github/workflows/release.yml — runs the verify script in both
  next and stable release jobs before `npm publish`.

Partial fix for #2649 (same root cause on the sibling sdk package).

Fixes #2647
2026-04-24 18:05:18 -04:00
Tom Boucher
387c8a1f9c fix(#2653): eliminate SDK↔CJS config-schema drift (#2670)
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.
2026-04-24 18:05:16 -04:00
Tom Boucher
e973ff4cb6 fix(#2630): reset STATE.md frontmatter atomically on milestone switch (#2666)
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>
2026-04-24 18:05:10 -04:00
Tom Boucher
8caa7d4c3a fix(#2649): installer fail-fast when sdk/dist missing in npx cache (#2667)
Root cause shared with #2647: a broken 1.38.3 tarball shipped without
sdk/dist/. The pre-#2441-decouple installer reacted by running
spawnSync('npm.cmd', ['install'], { cwd: sdkDir }) inside the npx cache
on Windows, where the cache is read-only, producing the misleading
"Failed to npm install in sdk/" error.

Defensive changes here (user-facing behavior only; packaging fix lives
in the sibling PR for #2647):

- Classify the install context (classifySdkInstall): detect npx cache
  paths, node_modules-based installs, and dev clones via path heuristics
  plus a side-effect-free write probe. Exported for test.
- Rewrite the dist-missing error to branch on context:
    tarball + npxCache -> "don't touch npx cache; npm i -g ...@latest"
    tarball (other)    -> upgrade path + clone-build escape hatch
    dev-clone          -> keep existing cd sdk && npm install && npm run build
- Preserve the invariant that the installer never shells out to
  npm install itself — users always drive that.
- Add tests/bug-2649-sdk-fail-fast.test.cjs covering the classifier and
  both failure messages, with spawnSync/execSync interceptors that
  assert no nested npm install is attempted.

Cross-ref: #2647 (packaging).

Fixes #2649

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:05:04 -04:00
forfrossen
a72bebb379 fix(workflows): agent-skills query keys must match subagent_type (follow-up to #2555) (#2616)
* 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>
2026-04-23 12:40:56 -04:00
Tom Boucher
31569c8cc8 ci: explicit rebase check + fail-fast SDK typecheck in install-smoke (#2631)
* ci: explicit rebase check + fail-fast SDK typecheck in install-smoke

Stale-base regression guard. Root cause: GitHub's `refs/pull/N/merge`
is cached against the PR's recorded merge-base, not current main. When
main advances after a PR is opened, the cache stays stale and CI runs
against the pre-advance tree. PRs hit this whenever a type error lands
on main and gets patched shortly after (e.g. #2611 + #2622) — stale
branches replay the broken intermediate state and report confusing
downstream failures for hours.

Observed failure mode: install-smoke's "Assert gsd-sdk resolves on PATH"
step fires with "installSdkIfNeeded() regression" even when the real
cause is `npm run build` failing in sdk/ due to a TypeScript cast
mismatch already fixed on main.

Fix:
- Explicit `git merge origin/main` step in both `install-smoke.yml` and
  `test.yml`. If the merge conflicts, emit a clear "rebase onto main"
  diagnostic and fail early, rather than let conflicts produce unrelated
  downstream errors.
- Dedicated `npm run build:sdk` typecheck step in install-smoke with a
  remediation hint ("rebase onto main — the error may already be fixed
  on trunk"). Fails fast with the actual tsc output instead of masking
  it behind a PATH assertion.
- Drop the `|| true` on `get-shit-done-cc --claude --local` so installer
  failures surface at the install step with install.js's own error
  message, not at the downstream PATH assertion where the message
  misleadingly blames "shim regression".
- `fetch-depth: 0` on checkout so the merge-base check has history.

* ci: address CodeRabbit — add rebase check to smoke-unpacked, fix fetch flag

Two findings from CodeRabbit's review on #2631:

1. `smoke-unpacked` job was missing the same rebase check applied to the
   `smoke` job. It ran on the cached `refs/pull/N/merge` and could hit
   the same stale-base failure mode the PR was designed to prevent. Added
   the identical rebase-check step.

2. `git fetch origin main --depth=0` is an invalid flag — git rejects it
   with "depth 0 is not a positive number". The intent was "fetch with
   full depth", but the right way is just `git fetch origin main` (no
   --depth). Removed the invalid flag and the `||` fallback that was
   papering over the error.
2026-04-23 12:40:16 -04:00
Tom Boucher
eba0c99698 fix(#2623): resolve parent .planning root for sub_repos workspaces in SDK query dispatch (#2629)
* fix(#2623): resolve parent .planning root for sub_repos workspaces in SDK query dispatch

When `gsd-sdk query` is invoked from inside a `sub_repos`-listed child repo,
`projectDir` defaulted to `process.cwd()` which pointed at the child repo,
not the parent workspace that owns `.planning/`. Handlers then directly
checked `${projectDir}/.planning` and reported `project_exists: false`.

The legacy `gsd-tools.cjs` CLI does not have this gap — it calls
`findProjectRoot(cwd)` from `bin/lib/core.cjs`, which walks up from the
starting directory checking each ancestor's `.planning/config.json` for a
`sub_repos` entry that lists the starting directory's top-level segment.

This change ports that walk-up as a new `findProjectRoot` helper in
`sdk/src/query/helpers.ts` and applies it once in `cli.ts:main()` before
dispatching `query`, `run`, `init`, or `auto`. Resolution is idempotent:
if `projectDir` already owns `.planning/` (including an explicit
`--project-dir` pointing at the workspace root), the helper returns it
unchanged. The walk is capped at 10 parent levels and never crosses
`$HOME`. All filesystem errors are swallowed.

Regression coverage:
- `helpers.test.ts` — 8 unit tests covering own-`.planning` guard (#1362),
  sub_repos match, nested-path match, `planning.sub_repos` shape,
  heuristic fallback, unparseable config, legacy `multiRepo: true`.
- `sub-repos-root.integration.test.ts` — end-to-end baseline (reproduces
  the bug without the walk-up) and fixed behavior (walk-up + dispatch of
  `init.new-milestone` reports `project_exists: true` with the parent
  workspace as `project_root`).

sdk vitest: 1511 pass / 24 fail (all 24 failures pre-existing on main,
baseline is 26 failing — `comm -23` against baseline produces zero new
failures). CJS: 5410 pass / 0 fail.

Closes #2623

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#2623): remove stray .planing typo from integration test setup

Address CodeRabbit nitpick: the mkdir('.planing') call on line 23 was
dead code from a typo, with errors silently swallowed via .catch(() => {}).
The test already creates '.planning' correctly on the next line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:58:23 -04:00
Tom Boucher
5a8a6fb511 fix(#2256): pass per-agent model overrides through Codex/OpenCode transport (#2628)
The Codex and OpenCode install paths read `model_overrides` only from
`~/.gsd/defaults.json` (global). A per-project override set in
`.planning/config.json` — the reporter's exact setup for
`gsd-codebase-mapper` — was silently dropped, so the child agent inherited
the runtime's default model regardless of `model_overrides`.

Neither runtime has an inline `model` parameter on its spawn API
(Codex `spawn_agent(agent_type, message)`, OpenCode `task(description,
prompt, subagent_type, task_id, command)`), so the per-agent model must
reach the child via the static config GSD writes at install time. That
config was being populated from the wrong source.

Fix: add `readGsdEffectiveModelOverrides(targetDir)` which merges
`~/.gsd/defaults.json` with per-project `.planning/config.json`, with
per-project keys winning on conflict. Both install sites now call it and
walk up from the install root to locate `.planning/` — matching the
precedence `readGsdRuntimeProfileResolver` already uses for #2517.

Also update the Codex Task()->spawn_agent mapping block so it no longer
says "omit" without context: it now documents that per-agent overrides
are embedded in the agent TOML and notes the restriction that Codex
only permits `spawn_agent` when the user explicitly requested sub-agents
(do the work inline otherwise).

Regression tests (`tests/bug-2256-model-overrides-transport.test.cjs`)
cover: global-only, project-only, project-wins-on-conflict, walking up
from a nested `targetDir`, Codex TOML `model =` emission, and OpenCode
frontmatter `model:` emission.

Closes #2256

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:58:06 -04:00
Tom Boucher
bdba40cc3d fix(#2618): thread --ws through query dispatch and sync root STATE.md on workstream.set (#2627)
* fix(#2618): thread --ws through query dispatch for state and init handlers

Gap 1 of #2618: the query dispatcher already accepts a workstream via
registry.dispatch(cmd, args, projectDir, ws), but several handlers drop it
before reaching planningPaths() / getMilestoneInfo() / findPhase() — so
stateJson and the init.* handlers return root-scoped results even when --ws
is provided.

Changes:

- sdk/src/query/state.ts: forward workstream into getMilestoneInfo() and
  extractCurrentMilestone() so buildStateFrontmatter resolves milestone data
  from the workstream ROADMAP/STATE instead of the root mirror.
- sdk/src/query/init.ts: thread workstream through initExecutePhase,
  initPlanPhase, initPhaseOp, and getPhaseInfoWithFallback (which fans out
  to findPhase() and roadmapGetPhase()). Also switch hardcoded
  join(projectDir, '.planning') to relPlanningPath(workstream) so returned
  state_path/roadmap_path/config_path reflect the workstream layout.

Regression test: stateJson with --ws workstream reads STATE.md from
.planning/workstreams/<name>/ when workstream is provided.

Closes #2618 (gap 1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#2618): sync root .planning/STATE.md mirror on workstream.set

Gap 2 of #2618: setActiveWorkstream only flips the active-workstream
pointer file; the root .planning/STATE.md mirror stays stale. Downstream
consumers (statusline, gsd-sdk query progress, any tool that reads the
root STATE.md) continue to see the previous workstream's state.

After setActiveWorkstream(), copy .planning/workstreams/<name>/STATE.md
verbatim to .planning/STATE.md via writeFileSync. The workstream STATE.md
is authoritative; the root file is a pass-through mirror. Missing source
STATE.md is a no-op rather than an error — a freshly created workstream
with no STATE.md yet should still activate cleanly.

The response now includes `mirror_synced: boolean` so callers can
observe whether the root mirror was updated.

Regression test: workstreamSet root STATE.md mirror sync — switches
from a stale root mirror to a workstream STATE.md with different
frontmatter and asserts the root file now matches.

Closes #2618 (gap 2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:54:34 -04:00
Tom Boucher
df0ab0c0c9 fix(#2410): emit wave + plan checkpoint heartbeats to prevent stream idle timeout (#2626)
/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>
2026-04-23 11:54:11 -04:00
Tom Boucher
807db75d55 fix(#2620): detect HOME-relative PATH entries before suggesting absolute export (#2625)
* fix(#2620): detect HOME-relative PATH entries before suggesting absolute export

When the installer reported `gsd-sdk` not on PATH and suggested
appending an absolute `export PATH="/home/user/.npm-global/bin:$PATH"`
line to the user's rc file, a user who had the equivalent
`export PATH="$HOME/.npm-global/bin:$PATH"` already in their shell
profile would get a duplicate entry — the installer only compared the
absolute form.

Add `homePathCoveredByRc(globalBin, homeDir, rcFileNames?)` to
`bin/install.js` and export it for test-mode callers. The helper scans
`~/.zshrc`, `~/.bashrc`, `~/.bash_profile`, `~/.profile`, grepping each
file for `export PATH=` / bare `PATH=` lines and substituting the
common HOME forms (\$HOME, \${HOME}, leading ~/) with the real home
directory before comparing each resolved PATH segment against
globalBin. Trailing slashes are normalised so `.npm-global/bin/`
matches `.npm-global/bin`. Missing / unreadable / malformed rc files
are swallowed — the caller falls back to the existing absolute
suggestion.

Tests cover $HOME, \${HOME}, and ~/ forms, absolute match,
trailing-slash match, commented-out lines, missing rc files, and
unreadable rc files (directory where a file is expected).

Closes #2620

* fix(#2620): skip relative PATH segments in homePathCoveredByRc

CodeRabbit flagged that the helper unconditionally resolved every
non-$-containing segment against homeAbs via path.resolve(homeAbs, …),
which silently turns a bare relative segment like `bin` or
`node_modules/.bin` into `$HOME/bin` / `$HOME/node_modules/.bin`. That
is wrong: bare PATH segments depend on the shell's cwd at lookup time,
not on $HOME — so the helper was returning true for rc files that do
not actually cover globalBin.

Guard the compare with path.isAbsolute(expanded) after HOME expansion.
Only segments that are absolute on their own (or that became absolute
via $HOME / \${HOME} / ~ substitution) are compared against targetAbs.
Relative segments are skipped.

Add two regression tests covering a bare `bin` segment and a nested
`node_modules/.bin` segment; both previously returned true when home
happened to contain a matching subdirectory and now correctly return
false.

Closes #2620 (CodeRabbit follow-up)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#2620): wire homePathCoveredByRc into installer suggestion path

CodeRabbit flagged that homePathCoveredByRc was added in the previous
commit but never called from the installer, so the user-facing PATH
warning stayed unchanged — users with `export PATH="$HOME/.npm-global/bin:$PATH"`
in their rc would still get a duplicate absolute-path suggestion.

Add `maybeSuggestPathExport(globalBin, homeDir)` that:
- skips silently when globalBin is already on process.env.PATH;
- prints a "try reopening your shell" diagnostic when homePathCoveredByRc
  returns true (the directory IS on PATH via an rc entry — just not in
  the current shell);
- otherwise falls through to the absolute-path
  `echo 'export PATH="…:$PATH"' >> ~/.zshrc` suggestion.

Call it from installSdkIfNeeded after the sdk/dist check succeeds,
resolving globalBin via `npm prefix -g` (plus `/bin` on POSIX). Swallow
any exec failure so the installer keeps working when npm is weird.

Export maybeSuggestPathExport for tests. Add three new regression tests
(installer-flow coverage per CodeRabbit nitpick):
- rc covers globalBin via $HOME form → no absolute suggestion emitted
- rc covers only an unrelated directory → absolute suggestion emitted
- globalBin already on process.env.PATH → no output at all

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:53:51 -04:00
Tom Boucher
74da61fb4a fix(#2619): prevent extractCurrentMilestone from truncating on phase-vX.Y headings (#2624)
* 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>
2026-04-23 11:53:20 -04:00
Jeremy McSpadden
0a049149e1 fix(sdk): decouple from build-from-source install, close #2441 #2453 (#2457)
* fix(sdk): decouple SDK from build-from-source install path, close #2441 and #2453

Ship sdk/dist prebuilt in the tarball and replace the npm-install-g
sub-install with a parent-package bin shim (bin/gsd-sdk.js). npm chmods
bin entries from a packed tarball correctly, eliminating the mode-644
failure (#2453) and the full class of NPM_CONFIG_PREFIX/ignore-scripts/
corepack/air-gapped failure modes that caused #2439 and #2441.

Changes:
- sdk/package.json: prepublishOnly runs `rm -rf dist && tsc && chmod +x
  dist/cli.js` (stale-build guard + execute-bit fix at publish time)
- package.json: add "gsd-sdk": "bin/gsd-sdk.js" bin entry; add sdk/dist
  to files so the prebuilt CLI ships in the tarball
- bin/gsd-sdk.js: new back-compat shim — resolves sdk/dist/cli.js relative
  to the package root and delegates via `node`, so all existing PATH call
  sites (slash commands, agents, hooks) continue to work unchanged (S1 shim)
- bin/install.js: replace installSdkIfNeeded() build-from-source + global-
  install dance with a dist-verify + chmod-in-place guard; delete
  resolveGsdSdk(), detectShellRc(), emitSdkFatal() helpers now unused
- .github/workflows/install-smoke.yml: add smoke-unpacked job that strips
  execute bit from sdk/dist/cli.js before install to reproduce the exact
  #2453 failure mode
- tests/bug-2441-sdk-decouple.test.cjs: new regression tests asserting all
  invariants (no npm install -g from sdk/, shim exists, sdk/dist in files,
  prepublishOnly has rm -rf + chmod)
- tests/bugs-1656-1657.test.cjs: update stale assertions that required
  build-from-source behavior (now asserts new prebuilt-dist invariants)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(release): bump to 1.38.2, wire release.yml to build SDK dist

- Bump version 1.38.1 -> 1.38.2 for the #2441/#2453 fix shipped in 0f6903d.
- Add `build:sdk` script (`cd sdk && npm ci && npm run build`).
- `prepublishOnly` now runs hooks + SDK builds as a safety net.
- release.yml (rc + finalize): build SDK dist before `npm publish` so the
  published tarball always ships fresh `sdk/dist/` (kept gitignored).
- CHANGELOG: document 1.38.2 entry and `--sdk` flag semantics change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: build SDK dist before tests and smoke jobs

sdk/dist/ is gitignored (built fresh at publish time via release.yml),
but both the test suite and install-smoke jobs run `bin/install.js`
or `npm pack` against the checked-out tree where dist doesn't exist yet.

- test.yml: `npm run build:sdk` before `npm run test:coverage`, so tests
  that spawn `bin/install.js` don't hit `installSdkIfNeeded()`'s fatal
  missing-dist check.
- install-smoke.yml (both smoke and smoke-unpacked): build SDK before
  pack/chmod so the published tarball contains dist and the unpacked
  install has a file to strip exec-bit from.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sdk): lift SDK runtime deps to parent so tarball install can resolve them

The SDK's runtime deps (ws, @anthropic-ai/claude-agent-sdk) live in
sdk/package.json, but sdk/node_modules is NOT shipped in the parent
tarball — only sdk/dist, sdk/src, sdk/prompts, and sdk/package.json are.
When a user runs `npm install -g get-shit-done-cc`, npm installs the
parent's node_modules but never runs `npm install` inside the nested
sdk/ directory.

Result: `node sdk/dist/cli.js` fails with ERR_MODULE_NOT_FOUND for 'ws'.
The smoke tarball job caught this; the unpacked variant masked it
because `npm install -g <dir>` copies the entire workspace including
sdk/node_modules (left over from `npm run build:sdk`).

Fix: declare the same deps in the parent package.json so they land in
<pkg>/node_modules, which Node's resolution walks up to from
<pkg>/sdk/dist/cli.js. Keep them declared in sdk/package.json too so
the SDK remains a self-contained package for standalone dev.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lockfile): regenerate package-lock.json cleanly

The previous `npm install` run left the lockfile internally inconsistent
(resolved esbuild@0.27.7 referenced but not fully written), causing
`npm ci` to fail in CI with "Missing from lock file" errors.

Clean regen via rm + npm install fixes all three failed jobs
(test, smoke, smoke-unpacked), which were all hitting the same
`npm ci` sync check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(deps): remove unused esbuild + vitest from root devDependencies

Both were declared but never imported anywhere in the root package
(confirmed via grep of bin/, scripts/, tests/). They lived in sdk/
already, which is the only place they're actually used.

The transitive tree they pulled in (vitest → vite → esbuild 0.28 →
@esbuild/openharmony-arm64) was the root of the CI npm ci failures:
the openharmony platform package's `optional: true` flag was not being
applied correctly by npm 10 on Linux runners, causing EBADPLATFORM.

After removal: 800+ transitive packages → 155. Lockfile regenerated
cleanly. All 4170 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sdk): pretest:coverage builds sdk; tighten shim test assertions

Add "pretest:coverage": "npm run build:sdk" so npm run test:coverage
works in clean checkouts where sdk/dist/ hasn't been built yet.

Tighten the two loose shim assertions in bug-2441-sdk-decouple.test.cjs:
- forwards-to test now asserts path.resolve() is called with the
  'sdk','dist','cli.js' path segments, not just substring presence
- node-invocation test now asserts spawnSync(process.execPath, [...])
  pattern, ruling out matches in comments or the shebang line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address PR review — pretest:coverage + tighten shim tests

Review feedback from trek-e on PR 2457:

1. pretest:coverage + pretest hooks now run `npm run build:sdk` so
   `npm run test[:coverage]` in a clean checkout produces the required
   sdk/dist/ artifacts before running the installer-dependent tests.
   CI already does this explicitly; local contributors benefit.

2. Shim tests in bug-2441-sdk-decouple.test.cjs tightened from loose
   substring matches (which would pass on comments/shebangs alone) to
   regex assertions on the actual path.resolve call, spawnSync with
   process.execPath, process.argv.slice(2), and process.exit pattern.
   These now provide real regression protection for #2453-class bugs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: correct CHANGELOG entry and add [1.38.2] reference link

Two issues in the 1.38.2 CHANGELOG entry:
- installSdkIfNeeded() was described as deleted but it still exists in
  bin/install.js (repurposed to verify sdk/dist/cli.js and fix execute bit).
  Corrected the description to say 'repurposes' rather than 'deletes'.
- The reference-link block at the bottom of the file was missing a [1.38.2]
  compare URL and [Unreleased] still pointed to v1.37.1...HEAD. Added the
  [1.38.2] link and updated [Unreleased] to compare/v1.38.2...HEAD.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(sdk): double-cast WorkflowConfig to Record for strict tsc build

TypeScript error on main (introduced in #2611) blocks `npm run build`
in sdk/, which now runs as part of this PR's tarball build path. Apply
the double-cast via `unknown` as the compiler suggests.

Same fix as #2622; can be dropped if that lands first.

* test: remove bug-2598 test obsoleted by SDK decoupling

The bug-2598 test guards the Windows CVE-2024-27980 fix in the old
build-from-source path (npm spawnSync with shell:true + formatSpawnFailure
diagnostics). This PR removes that entire code path — installSdkIfNeeded
no longer spawns npm, it just verifies the prebuilt sdk/dist/cli.js
shipped in the tarball.

The test asserts `installSdkIfNeeded.toString()` contains a
formatSpawnFailure helper. After decoupling, no such helper exists
(nothing to format — there's no spawn). Keeping the test would assert
invariants of the rejected architecture.

The original #2598 defect (silent failure of npm spawn on Windows) is
structurally impossible in the shim path: bin/gsd-sdk.js invokes
`node sdk/dist/cli.js` directly via child_process.spawn with an
explicit argv array. No .cmd wrapper, no shell delegation.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-04-23 08:36:03 -04:00
Tom Boucher
a56707a07b fix(#2613): preserve STATE.md frontmatter on write path (option 2) (#2622)
* fix(#2613): preserve STATE.md frontmatter on write path (option 2)

`readModifyWriteStateMd` strips frontmatter before invoking the modifier,
so `syncStateFrontmatter` received body-only content and `existingFm`
was always `{}`. The preservation branch never fired, and every mutation
re-derived `status` (to `'unknown'` when body had no `Status:` line) and
`progress.*` (to 0/0 when the shipped milestone's phase directories were
archived), silently overwriting authoritative frontmatter values.

Option 2 — write-side analogue of #2495 READ fix: `buildStateFrontmatter`
reads the current STATE.md frontmatter from disk as a preservation
backstop. Status preserved when derived is `'unknown'` and existing is
non-unknown. Progress preserved when disk scan returns all zeros AND
existing has non-zero counts. Legitimate body-driven status changes and
non-zero disk counts still win.

Milestone/milestone_name already preserved via `getMilestoneInfo`'s
#2495 fix — regression test added to lock that in.

Adds 5 regression tests covering status preservation, progress
preservation, milestone preservation, legitimate status updates, and
disk-scan-wins-when-non-zero.

Closes #2613

* fix(sdk): double-cast WorkflowConfig to Record in loadGateConfig

TypeScript error on main (introduced in #2611) blocks the install-smoke
CI job: `WorkflowConfig` has no string index signature, so the direct
cast to `Record<string, unknown>` fails type-check. The SDK build fails,
`installSdkIfNeeded()` cannot install `gsd-sdk` from source, and the
smoke job reports a false-positive installer regression.

  src/query/check-decision-coverage.ts(236,16): error TS2352:
  Conversion of type 'WorkflowConfig' to type 'Record<string, unknown>'
  may be a mistake because neither type sufficiently overlaps with the
  other.

Apply the double-cast via `unknown` as the compiler suggests. Behavior
is unchanged — this was already a cast.
2026-04-23 08:22:42 -04:00
Tom Boucher
f30da8326a feat: add gates ensuring discuss-phase decisions are translated to plans and verified (closes #2492) (#2611)
* 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>
2026-04-23 00:26:53 -04:00
Tom Boucher
1a3d953767 feat: add unified post-planning gap checker (closes #2493) (#2610)
* 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.
2026-04-22 23:03:59 -04:00
Tom Boucher
cc17886c51 feat: make model profiles runtime-aware for Codex/non-Claude runtimes (closes #2517) (#2609)
* 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>
2026-04-22 23:00:37 -04:00
Tom Boucher
41dc475c46 refactor(workflows): extract discuss-phase modes/templates/advisor for progressive disclosure (closes #2551) (#2607)
* 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>
2026-04-22 21:57:24 -04:00
Tom Boucher
220da8e487 feat: /gsd-settings-integrations — configure third-party search and review integrations (closes #2529) (#2604)
* 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.
2026-04-22 21:41:00 -04:00
Tom Boucher
c90081176d fix(#2598): pass shell: true to npm spawnSync on Windows (#2600)
* fix(#2598): pass shell: true to npm spawnSync on Windows

Since Node's CVE-2024-27980 fix (>= 18.20.2 / >= 20.12.2 / >= 21.7.3),
spawnSync refuses to launch .cmd/.bat files on Windows without
`shell: true`. installSdkIfNeeded picks npmCmd='npm.cmd' on win32 and
then calls spawnSync five times — every one returns
{ status: null, error: EINVAL } before npm ever runs. The installer
checks `status !== 0`, trips the failure path, and emits a bare
"Failed to `npm install` in sdk/." with zero diagnostic output because
`stdio: 'inherit'` never had a child to stream.

Every fresh install on Windows has failed at the SDK build step on any
supported Node version for the life of the post-CVE bin/install.js.

Introduce a local `spawnNpm(args, opts)` helper inside
installSdkIfNeeded that injects `shell: process.platform === 'win32'`
when the caller doesn't override it. Route all five npm invocations
through it: `npm install`, `npm run build`, `npm install -g .`, and
both `npm config get prefix` calls.

Adds a static regression test that parses installSdkIfNeeded and
asserts no bare `spawnSync(npmCmd, ...)` remains, a shell-aware
wrapper exists, and at least five invocations go through it.

Closes #2598

* fix(#2598): surface spawnSync diagnostics in SDK install fatal paths

Thread result.error / result.signal / result.status into emitSdkFatal for
the three npm failure branches (install, run build, install -g .) via a
formatSpawnFailure helper. The root cause of #2598 went silent precisely
because `{ status: null, error: EINVAL }` was reduced to a generic
"Failed to `npm install` in sdk/." with no diagnostic — stdio: 'inherit'
had no child process to stream and result.error was swallowed. Any future
regression in the same area (EINVAL, ENOENT, signal termination) now
prints its real cause in the red fatal banner.

Also strengthen the regression test so it cannot pass with only four
real npm call sites: the previous `spawnSync(npmCmd, ..., shell)` regex
double-counted the spawnNpm helper's own body when a helper existed.
Separate arrow-form vs function-form helper detection and exclude the
wrapper body from explicitShellNpm so the `>= 5` assertion reflects real
invocations only. Add a new test that asserts all three fatal branches
now reference formatSpawnFailure / result.error / signal / status.

Addresses CodeRabbit review comments on PR #2600:
- r3126987409 (bin/install.js): surface underlying spawnSync failure
- r3126987419 (test): explicitShellNpm overcounts by one via helper def
2026-04-22 21:23:44 -04:00
Tom Boucher
1a694fcac3 feat: auto-remap codebase after significant phase execution (closes #2003) (#2605)
* 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>
2026-04-22 21:21:44 -04:00
Tom Boucher
9c0a153a5f feat: /gsd-settings-advanced — power-user config tuning command (closes #2528) (#2603)
* 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.
2026-04-22 20:50:15 -04:00
Tom Boucher
86c5863afb feat: add settings layers to /gsd-settings (Group A toggles) (closes #2527) (#2602)
* 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.
2026-04-22 20:49:52 -04:00
Tom Boucher
1f2850c1a8 fix(#2597): expand dotted query tokens with trailing args (#2599)
resolveQueryArgv only expanded `init.execute-phase` → `init execute-phase`
when the tokens array had length 1. Argv like `init.execute-phase 1` has
length 2, skipped the expansion, and resolved to no registered handler.

All 50+ workflow files use the dotted form with arguments, so this broke
every non-argless query route (`init.execute-phase`, `state.update`,
`phase.add`, `milestone.complete`, etc.) at runtime.

Rename `expandSingleDottedToken` → `expandFirstDottedToken`: split only
the first token on its dots (guarding against `--` flags) and preserve
the tail as positional args. Identity comparison at the call site still
detects "no expansion" since we return the input array unchanged.

Adds regression tests for the three failure patterns reported:
`init.execute-phase 1`, `state.update status X`, `phase.add desc`.

Closes #2597
2026-04-22 17:30:08 -04:00
Tom Boucher
b35fdd51f3 Revert "feat(#2473): ship refuses to open PR when HANDOFF.json declares in-pr…" (#2596)
This reverts commit 7212cfd4de.
2026-04-22 12:57:12 -04:00
Fernando Castillo
7212cfd4de feat(#2473): ship refuses to open PR when HANDOFF.json declares in-progress work (#2553)
* 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.
2026-04-22 12:11:31 -04:00
Tom Boucher
2b5c35cdb1 test(#2519): add regression test for sdk tarball dist inclusion (#2586)
* test(#2519): add regression test verifying sdk/package.json has files + prepublishOnly

Guards the sdk/package.json fix for #2519 (tarball shipped without dist/)
so future edits can't silently drop either the `files` whitelist or the
`prepublishOnly` build hook. Asserts:

- `files` is a non-empty array
- `files` includes "dist" (so compiled CLI ships in tarball)
- `scripts.prepublishOnly` runs a build (npm run build / tsc)
- `bin` target lives under dist/ (sanity tie-in)

Closes #2519

* test(#2519): accept valid npm glob variants for dist in files matcher

Addresses CodeRabbit nitpick: the previous equality check on 'dist' / 'dist/' /
'dist/**' would false-fail on other valid npm packaging forms like './dist',
'dist/**/*', or backslash-separated paths. Normalize each entry and use a
regex that accepts all common dist path variants.
2026-04-22 12:09:12 -04:00
Tom Boucher
73c1af5168 fix(#2543): replace legacy /gsd-<cmd> syntax with /gsd:<cmd> across all source files (#2595)
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>
2026-04-22 12:04:25 -04:00
Tom Boucher
533973700c feat(#2538): add last: /cmd suffix to statusline (opt-in) (#2594)
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
2026-04-22 12:04:21 -04:00
Tom Boucher
349daf7e6a fix(#2545): use word boundary in path replacement to catch ~/.claude without trailing slash (#2592)
The Copilot content converter only replaced `~/.claude/` and
`$HOME/.claude/` when followed by a literal `/`. Bare references
(e.g. `configDir = ~/.claude` at end of line) slipped through and
triggered the post-install "Found N unreplaced .claude path reference(s)"
warning, since the leak scanner uses `(?:~|$HOME)/\.claude\b`.

Switched both replacements to a `(\/|\b)` capture group so trailing-slash
and bare forms are handled in a single pass — matching the pattern
already used by Antigravity, OpenCode, Kilo, and Codex converters.

Closes #2545
2026-04-22 12:04:17 -04:00
Tom Boucher
6b7b5c15a5 fix(#2559): remove stale year injection from research agent web search instructions (#2591)
The gsd-phase-researcher and gsd-project-researcher agents instructed
WebSearch queries to always include 'current year' (e.g., 2024). As
time passes, a hardcoded year biases search results toward stale
dated content — users saw 2024-tagged queries producing stale blog
references in 2026.

Remove the year-injection guidance. Instead, rely on checking
publication dates on the returned sources. Query templates and
success criteria updated accordingly.

Closes #2559
2026-04-22 12:04:13 -04:00
Tom Boucher
67a9550720 fix(#2549,#2550,#2552): bound discuss-phase context reads, add phase-type map selection, prohibit split reads (#2590)
#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 #2549
Closes #2550
Closes #2552

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 12:04:10 -04:00
Tom Boucher
fba040c72c fix(#2557): Gemini/Antigravity local hook commands use relative paths, not \$CLAUDE_PROJECT_DIR (#2589)
\$CLAUDE_PROJECT_DIR is Claude Code-specific. Gemini CLI doesn't set it, and on
Windows its path-join logic doubled the value producing unresolvable paths like
D:\Projects\GSD\'D:\Projects\GSD'. Gemini runs project hooks with project root
as cwd, so bare relative paths (e.g. node .gemini/hooks/gsd-check-update.js)
are cross-platform and correct. Claude Code and others still use the env var.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 12:04:06 -04:00
Tom Boucher
7032f44633 fix(#2544): exit 1 on missing key in config-get (#2588)
The configGet query handler previously threw GSDError with
ErrorClassification.Validation, which maps to exit code 10. Callers
using `if ! gsd-sdk query config-get key; then fallback; fi` could
not detect missing keys through the exit code alone, because exit 10
is still truthy-failure but the intent (and documented UNIX
convention — cf. `git config --get`) is exit 1 for absent key.

Change the classification for the two 'Key not found' throw sites to
ErrorClassification.Execution so the CLI exits 1 on missing key.
Usage/schema errors (no key argument, malformed JSON, missing
config.json) remain Validation.

Closes #2544
2026-04-22 12:04:03 -04:00
Tom Boucher
2404b40a15 fix(#2555): SDK agent-skills reads config.agent_skills and returns <agent_skills> block (#2587)
The SDK query handler `agent-skills` previously scanned every skill
directory on the filesystem and returned a flat JSON list, ignoring
`config.agent_skills[agentType]` entirely. Workflows that interpolate
$(gsd-sdk query agent-skills <type>) into Task() prompts got a JSON
dump of all skills instead of the documented <agent_skills> block.

Port `buildAgentSkillsBlock` semantics from
get-shit-done/bin/lib/init.cjs into the SDK handler:

- Read config.agent_skills[agentType] via loadConfig()
- Support single-string and array forms
- Validate each project-relative path stays inside the project root
  (symlink-aware, mirrors security.cjs#validatePath)
- Support `global:<name>` prefix for ~/.claude/skills/<name>/
- Skip entries whose SKILL.md is missing, with a stderr warning
- Return the exact string block workflows embed:
  <agent_skills>\nRead these user-configured skills:\n- @.../SKILL.md\n</agent_skills>
- Empty string when no agent type, no config, or nothing valid — matches
  gsd-tools.cjs cmdAgentSkills output.
2026-04-22 12:03:59 -04:00
Tom Boucher
0d6349a6c1 fix(#2554): preserve leading zero in getMilestonePhaseFilter (#2585)
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
2026-04-22 12:03:56 -04:00
Tom Boucher
c47a6a2164 fix: correct VALID_CONFIG_KEYS — remove internal state key, add missing public keys, migration hints (#2561)
* 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>
2026-04-22 11:28:25 -04:00
forfrossen
af2dba2328 fix(hooks): detect Claude Code via stdin session_id (closes #2520) (#2521)
* fix(hooks): detect Claude Code via stdin session_id, not filtered env (#2520)

The #2344 fix assumed `CLAUDECODE` would propagate to hook subprocesses.
On Claude Code v2.1.116 it doesn't — Claude Code applies a separate env
filter to PreToolUse hook commands that drops bare CLAUDECODE and
CLAUDE_SESSION_ID, keeping only CLAUDE_CODE_*-prefixed vars plus
CLAUDE_PROJECT_DIR. As a result every Edit/Write on an existing file
produced a redundant READ-BEFORE-EDIT advisory inside Claude Code.

Use `data.session_id` from the hook's stdin JSON as the primary Claude
Code signal (it's part of Claude Code's documented PreToolUse hook-input
schema). Keep CLAUDE_CODE_ENTRYPOINT / CLAUDE_CODE_SSE_PORT env checks
as propagation-verified fallbacks, and keep the legacy
CLAUDE_SESSION_ID / CLAUDECODE checks for back-compat and
future-proofing.

Add tests/bug-2520-read-guard-hook-subprocess-env.test.cjs, which spawns
the hook with an env mirroring the actual Claude Code hook-subprocess
filter. Extend the legacy test harnesses to also strip the
propagation-verified CLAUDE_CODE_* vars so positive-path tests keep
passing when the suite itself runs inside a Claude Code session (same
class of leak as #2370 / PR #2375, now covering the new detection
signals).

Non-Claude-host behavior (OpenCode / MiniMax) is unchanged: with no
`session_id` on stdin and no CLAUDE_CODE_* env var, the advisory still
fires.

Closes #2520

* test(2520): isolate session_id signal from env fallbacks in regression test

Per reviewer feedback (Copilot + CodeRabbit on #2521): the session_id
isolation test used the helper's default CLAUDE_CODE_ENTRYPOINT /
CLAUDE_CODE_SSE_PORT values, so the env fallback would rescue the skip
even if the primary `data.session_id` check regressed. Pass an explicit
env override that clears those fallbacks, so only the stdin `session_id`
signal can trigger the skip.

Other cases (env-only fallback, negative / non-Claude host) already
override env appropriately.

---------

Co-authored-by: forfrossen <forfrossensvart@gmail.com>
2026-04-22 10:41:58 -04:00
elfstrob
9b5397a30f feat(sdk): add queued_phases to init.manager (closes #2497) (#2514)
* 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>
2026-04-22 10:41:37 -04:00
Tom Boucher
7397f580a5 fix(#2516): resolve executor_model inherit literal passthrough; add regression test (#2537)
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
2026-04-21 21:35:22 -04:00
Tom Boucher
9a67e350b3 fix(#2504): auto-pass UAT for infrastructure/foundation phases with no user-facing elements (#2541)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:20:27 -04:00
Tom Boucher
98d92d7570 fix(#2526): warn about REQ-IDs in body missing from Traceability table (#2539)
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
2026-04-21 21:18:58 -04:00
Tom Boucher
8eeaa20791 fix(install): chmod dist/cli.js 0o755 after npm install -g; add regression test (closes #2525) (#2536)
Use process.platform !== 'win32' guard in catch instead of a comment, and add
regression test for bug #2525 (gsd-sdk bin symlink points at non-executable file).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:18:34 -04:00
Tom Boucher
f32ffc9fb8 fix(quick): include deferred-items.md in final commit file list (closes #2523) (#2542)
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>
2026-04-21 20:33:43 -04:00
Tom Boucher
5676e2e4ef fix(sdk): forward --ws workstream flag through query dispatch (#2546)
* fix(sdk): forward --ws workstream flag through query dispatch (closes #2524)

- cli.ts: pass args.ws as workstream to registry.dispatch()
- registry.ts: add workstream? param to dispatch(), thread to handler
- utils.ts: add optional workstream? to QueryHandler type signature
- helpers.ts: planningPaths() accepts workstream? and uses relPlanningPath()
- All ~26 query handlers updated to receive and pass workstream to planningPaths()
- Config/commit/intel handlers use _workstream (project-global, not scoped)
- Add failing-then-passing test: tests/bug-2524-sdk-query-ws-flag.test.cjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(sdk): forward workstream to all downstream query helpers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): rewrite #2524 test as static source assertions — no sdk/dist build in CI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 20:33:24 -04:00
Lex Christopherson
7bb6b6452a fix: spike workflow defaults to interactive UI demos, not stdout
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>
2026-04-21 09:19:04 -06:00
Lex Christopherson
43ea92578b Merge remote-tracking branch 'origin/main' into hotfix/1.38.2
# Conflicts:
#	CHANGELOG.md
#	bin/install.js
#	sdk/src/query/init.ts
2026-04-21 09:16:24 -06:00
Lex Christopherson
a42d5db742 1.38.2 2026-04-21 09:14:52 -06:00
Lex Christopherson
c86ca1b3eb fix: sync spike/sketch workflows with upstream skill v2 improvements
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>
2026-04-21 09:14:32 -06:00
github-actions[bot]
337e052aa9 chore: bump version to 1.38.2 for hotfix 2026-04-21 15:13:56 +00:00
Lex Christopherson
969ee38ee5 fix: sync spike/sketch workflows with upstream skill v2 improvements
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>
2026-04-21 09:05:47 -06:00
Tom Boucher
2980f0ec48 fix(sdk): stripShippedMilestones handles inline SHIPPED headings; getMilestoneInfo prefers STATE.md (#2508)
* fix(sdk): stripShippedMilestones handles inline SHIPPED headings; getMilestoneInfo prefers STATE.md

Fixes two compounding bugs:

- #2496: stripShippedMilestones only stripped <details> blocks, ignoring
  '## Heading —  SHIPPED ...' inline markers. Shipped milestone sections
  were leaking into downstream parsers.

- #2495: getMilestoneInfo checked STATE.md frontmatter only as a last-resort
  fallback, so it returned the first heading match (often a leaked shipped
  milestone) rather than the current milestone. Moved STATE.md check to
  priority 1, consistent with extractCurrentMilestone.

Closes #2495
Closes #2496

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(roadmap): handle ### SHIPPED headings and STATE.md version-only case

Two follow-up fixes from CodeRabbit review of #2508:

1. stripShippedMilestones only split on ## boundaries; ### headings marked
    SHIPPED were not stripped, leaking into fallback parsers. Expanded
   the split/filter regex to #{2,3} to align with extractCurrentMilestone.

2. getMilestoneInfo's early-return on parseMilestoneFromState discarded the
   real milestone name from ROADMAP.md when STATE.md had only `milestone:`
   (no `milestone_name:`), returning the placeholder name 'milestone'.
   Now only short-circuits when STATE.md provides a real name; otherwise
   falls through to ROADMAP for the name while using stateVersion to
   override the version in every ROADMAP-derived return path.

Tests: +2 new cases (### SHIPPED heading, version-only STATE.md).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:41:35 -04:00
Tom Boucher
8789211038 fix(insert-phase): update STATE.md next-phase recommendation after phase insertion (#2509)
* 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>
2026-04-21 10:10:45 -04:00
Tom Boucher
57bbfe652b fix: exclude non-wiped dirs from custom-file scan; warn on non-Claude model profiles (#2511)
* 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>
2026-04-21 10:10:10 -04:00
Tom Boucher
a4764c5611 fix(execute-phase): resurrection-detection must check git history before deleting new .planning/ files (#2510)
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
2026-04-21 09:46:01 -04:00
Tom Boucher
b2534e8a05 feat(plan-phase): chunked mode + filesystem fallback for Windows stdio hang (#2499)
* 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>
2026-04-21 08:40:39 -04:00
Tom Boucher
d1b56febcb fix(execute-phase): post-merge deletion audit for bulk file deletions (closes #2384) (#2483)
* 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>
2026-04-20 18:37:42 -04:00
Tom Boucher
1657321eb0 fix(install): remove bare ~/.claude reference in update.md (closes #2470) (#2482)
* 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>
2026-04-20 18:37:32 -04:00
Tom Boucher
2b494407e5 feat(assembly): add link mode for CLAUDE.md @-reference sections (#2484)
* feat(assembly): add link mode for CLAUDE.md @-reference sections (#2415)

Adds `claude_md_assembly.mode: "link"` config option that writes
`@.planning/<source>` instead of inlining content between GSD markers,
reducing typical CLAUDE.md size by ~65%. Per-block overrides available
via `claude_md_assembly.blocks.<section>`. Falls back to embed for
sections without a real source file (workflow, fallbacks).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): add positive assertion for embedded workflow content (CodeRabbit #2484)

The negative assertion only confirmed @GSD defaults wasn't written.
Add assert.ok(content.includes('GSD Workflow Enforcement')) to verify
the workflow section is actually embedded inline when link mode falls back.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:27:55 -04:00
Tom Boucher
d0f4340807 feat(workflows): link pending todos to roadmap phases in new-milestone (#2433) (#2485)
Adds step 10.5 to gsd-new-milestone that scans pending todos against the
approved roadmap and tags matches with `resolves_phase: N` in their YAML
frontmatter. Adds a `close_phase_todos` step to execute-phase that moves
tagged todos to `completed/` when the phase completes — closing the loop
automatically with no manual cleanup.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:25:24 -04:00
Tom Boucher
280eed93bc feat(cli): add /gsd-sync-skills for cross-runtime managed skill sync (#2491)
* fix(tests): update 5 source-text tests to read config-schema.cjs

VALID_CONFIG_KEYS moved from config.cjs to config-schema.cjs in the
drift-prevention companion PR. Tests that read config.cjs source text
and checked for key literal includes() now point to the correct file.

Closes #2480

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(cli): add /gsd-sync-skills for cross-runtime managed skill sync (#2380)

Adds /gsd-sync-skills command so multi-runtime users can keep gsd-* skill
directories aligned across runtime roots after updating one runtime with gsd-update.

Changes:
- bin/install.js: add --skills-root <runtime> flag that prints the skills root
  path for any supported runtime, reusing the existing getGlobalDir() table.
  Banner is suppressed when --skills-root is used (machine-readable output).
- commands/gsd/sync-skills.md: slash command definition
- get-shit-done/workflows/sync-skills.md: full workflow spec covering argument
  parsing, path resolution via --skills-root, diff computation (CREATE/UPDATE/
  REMOVE/SKIP), dry-run report (default), apply execution, idempotency guarantee,
  and safety rules (only gsd-* touched, dry-run performs no writes).

Safety rules: only gsd-* directories are ever created/updated/removed; non-GSD
skills in destination roots are never touched; --dry-run is the default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:21:43 -04:00
Tom Boucher
b432d4a726 feat(workflows): close LEARNINGS.md consumption-and-graduation loop (#2490)
* fix(tests): update 5 source-text tests to read config-schema.cjs

VALID_CONFIG_KEYS moved from config.cjs to config-schema.cjs in the
drift-prevention companion PR. Tests that read config.cjs source text
and checked for key literal includes() now point to the correct file.

Closes #2480

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(workflows): close LEARNINGS.md consumption-and-graduation loop (#2430)

Part A — Consumption: extend plan-phase.md cross-phase context load to include
LEARNINGS.md files from the 3 most recent prior phases (same recency gate as
CONTEXT.md + SUMMARY.md: CONTEXT_WINDOW >= 500000 only). Also loads LEARNINGS.md
from any phases in the Depends-on chain. Silent skip if absent; 15% context
budget cap with oldest-first truncation; [from Phase N LEARNINGS] attribution.

Part B — Graduation: add graduation_scan step to transition.md (after
evolve_project) that delegates to new graduation.md helper workflow. The helper
clusters recurring items across the last N phases (default window=5, threshold=3)
using Jaccard lexical similarity, surfaces HITL Promote/Defer/Dismiss prompts,
routes promotions to PROJECT.md or PATTERNS.md by category, annotates graduated
items with `graduated:` field, and persists dismissed/deferred clusters in
STATE.md graduation_backlog. Always non-blocking; silently no-ops on first phase
or when data is insufficient.

Also: adds optional `graduated:` annotation docs to extract_learnings.md schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(graduation): address CodeRabbit review findings on PR #2490

- graduation.md: unify insufficient-data guard to silent-skip (remove
  contradictory [no-op] print path)
- graduation.md: add TEXT_MODE fallback for HITL cluster prompts
- graduation.md: add A (defer-all) to accepted actions [P/D/X/A]
- graduation.md: tag untyped code fences with text language (MD040)
- transition.md: tag untyped graduation.md fence with text language

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(graduation): rephrase TEXT_MODE line to avoid prompt-injection scanner false positive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:21:35 -04:00
Tom Boucher
cfe4dc76fd feat(health): canonical artifact registry and W019 unrecognized-file lint (#2448) (#2488)
Adds artifacts.cjs with canonical .planning/ root file names, W019 warning
in gsd-health that flags unrecognized .md files at the .planning/ root, and
templates/README.md as the authoritative artifact index for agents and humans.

Closes #2448
2026-04-20 18:21:23 -04:00
Tom Boucher
f19d0327b2 feat(agents): sycophancy hardening for 9 audit-class agents (#2489)
* fix(tests): update 5 source-text tests to read config-schema.cjs

VALID_CONFIG_KEYS moved from config.cjs to config-schema.cjs in the
drift-prevention companion PR. Tests that read config.cjs source text
and checked for key literal includes() now point to the correct file.

Closes #2480

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(agents): sycophancy hardening for 9 audit-class agents (#2427)

Add adversarial reviewer posture to gsd-plan-checker, gsd-code-reviewer,
gsd-security-auditor, gsd-verifier, gsd-eval-auditor, gsd-nyquist-auditor,
gsd-ui-auditor, gsd-integration-checker, and gsd-doc-verifier.

Four changes per agent:
- Third-person framing: <role> opens with submission framing, not "You are a GSD X"
- FORCE stance: explicit starting hypothesis that the submission is flawed
- Failure modes: agent-specific list of how each reviewer type goes soft
- BLOCKER/WARNING classification: every finding must carry an explicit severity

Also applies to sdk/prompts/agents variants of gsd-plan-checker and gsd-verifier.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:20:08 -04:00
Tom Boucher
bd27d4fabe feat(roadmap): surface wave dependencies and cross-cutting constraints (#2487)
* feat(roadmap): surface wave dependencies and cross-cutting constraints (#2447)

Adds roadmap.annotate-dependencies command that post-processes a phase's
ROADMAP plan list to insert wave dependency notes and surface must_haves.truths
entries shared across 2+ plans as cross-cutting constraints. Operation is
idempotent and purely derived from existing PLAN frontmatter.

Closes #2447

* fix(roadmap): address CodeRabbit review findings on PR #2487

- roadmap.cjs: expand idempotency guard to also check for existing
  cross-cutting constraints header, preventing duplicate injection on
  re-runs; add content equality check before writing to preserve
  true idempotency for single-wave phases
- plan-phase.md: move ROADMAP annotation (13d) before docs commit (13c)
  so annotated ROADMAP.md is included in the commit rather than left dirty;
  include .planning/ROADMAP.md in committed files list
- sdk/src/query/index.ts: add annotate-dependencies aliases to
  QUERY_MUTATION_COMMANDS so the mutation is properly event-wired
- sdk/src/query/roadmap.ts: add timeout (15s) and maxBuffer to spawnSync;
  check result.error before result.status to handle spawn/timeout failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:19:21 -04:00
Tom Boucher
e8ec42082d feat(health): detect MILESTONES.md drift from archived snapshots (#2446) (#2486)
Adds W018 warning when .planning/milestones/vX.Y-ROADMAP.md snapshots
exist without a corresponding entry in MILESTONES.md. Introduces
--backfill flag to synthesize missing entries from snapshot titles.

Closes #2446
2026-04-20 18:19:14 -04:00
Rezolv
86fb9c85c3 docs(sdk): registry docs and gsd-sdk query call sites (#2302 Track B) (#2340)
* feat(sdk): golden parity harness and query handler CJS alignment (#2302 Track A)

Golden/read-only parity tests and registry alignment, query handler fixes
(check-completion, state-mutation, commit, validate, summary, etc.), and
WAITING.json dual-write for .gsd/.planning readers.

Refs gsd-build/get-shit-done#2341

* fix(sdk): getMilestoneInfo matches GSD ROADMAP (🟡, last bold, STATE fallback)

- Recognize in-flight 🟡 milestone bullets like 🚧.
- Derive from last **vX.Y Title** before ## Phases when emoji absent.
- Fall back to STATE.md milestone when ROADMAP is missing; use last bare vX.Y
  in cleaned text instead of first (avoids v1.0 from shipped list).
- Fixes init.execute-phase milestone_version and buildStateFrontmatter after
  state.begin-phase (syncStateFrontmatter).

* feat(sdk): phase list, plan task structure, requirements extract handlers

- Register phase.list-plans, phase.list-artifacts, plan.task-structure,
  requirements.extract-from-plans (SDK-only; golden-policy exceptions).
- Add unit tests; document in QUERY-HANDLERS.md.
- writeProfile: honor --output, render dimensions, return profile_path and dimensions_scored.

* feat(sdk): centralize getGsdAgentsDir in query helpers

Extract agent directory resolution to helpers (GSD_AGENTS_DIR, primary
~/.claude/agents, legacy path). Use from init and docs-init init bundles.

docs(15): add 15-CONTEXT for autonomous phase-15 run.

* feat(sdk): query CLI CJS fallback and session correlation

- createRegistry(eventStream, sessionId) threads correlation into mutation events
- gsd-sdk query falls back to gsd-tools.cjs when no native handler matches
  (disable with GSD_QUERY_FALLBACK=off); stderr bridge warnings
- Export createRegistry from @gsd-build/sdk; add sdk/README.md
- Update QUERY-HANDLERS.md and registry module docs for fallback + sessionId
- Agents: prefer node dist/cli.js query over cat/grep for STATE and plans

* fix(sdk): init phase_found parity, docs-init agents path, state field extract

- Normalize findPhase not-found to null before roadmap fallback (matches findPhaseInternal)

- docs-init: use detectRuntime + resolveAgentsDir for checkAgentsInstalled

- state.cjs stateExtractField: horizontal whitespace only after colon (YAML progress guard)

- Tests: commit_docs default true; config-get golden uses temp config; golden integration green

Refs: #2302

* refactor(sdk): share SessionJsonlRecord in profile-extract-messages

CodeRabbit nit: dedupe JSONL record shape for isGenuineUserMessage and streamExtractMessages.

* fix(sdk): address CodeRabbit major threads (paths, gates, audit, verify)

- Resolve @file: and CLI JSON indirection relative to projectDir; guard empty normalized query command

- plan.task-structure + intel extract/patch-meta: resolvePathUnderProject containment

- check.config-gates: safe string booleans; plan_checker alias precedence over plan_check default

- state.validate/sync: phaseTokenMatches + comparePhaseNum ordering

- verify.schema-drift: token match phase dirs; files_modified from parsed frontmatter

- audit-open: has_scan_errors, unreadable rows, human report when scans fail

- requirements PLANNED key PLAN for root PLAN.md; gsd-tools timeout note

- ingest-docs: repo-root path containment; classifier output slug-hash

Golden parity test strips has_scan_errors until CJS adds field.

* fix: Resolve CodeRabbit security and quality findings
- Secure intel.ts and cli.ts against path traversal
- Catch and validate git add status in commit.ts
- Expand roadmap milestone marker extraction
- Fix parsing array-of-objects in frontmatter YAML
- Fix unhandled config evaluations
- Improve coverage test parity mapping

* docs(sdk): registry docs and gsd-sdk query call sites (#2302 Track B)

Update CHANGELOG, architecture and user guides, workflow call sites, and read-guard tests for gsd-sdk query; sync ARCHITECTURE.md command/workflow counts and directory-tree totals with the repo (80 commands, 77 workflows).

Address CodeRabbit: fix markdown tables and emphasis; align CLI-TOOLS GSDTools and state.read docs with implementation; correct roadmap handler name in universal-anti-patterns; resolve settings workflow config path without relying on config_path from state.load.

Refs gsd-build/get-shit-done#2340

* test: raise planner character extraction limit to 48K

* fix(sdk): resolve build TS error and doc conflict markers
2026-04-20 18:09:21 -04:00
Rezolv
c5b1445529 feat(sdk): golden parity harness and query handler CJS alignment (#2302 Track A) (#2341)
* feat(sdk): golden parity harness and query handler CJS alignment (#2302 Track A)

Golden/read-only parity tests and registry alignment, query handler fixes
(check-completion, state-mutation, commit, validate, summary, etc.), and
WAITING.json dual-write for .gsd/.planning readers.

Refs gsd-build/get-shit-done#2341

* fix(sdk): getMilestoneInfo matches GSD ROADMAP (🟡, last bold, STATE fallback)

- Recognize in-flight 🟡 milestone bullets like 🚧.
- Derive from last **vX.Y Title** before ## Phases when emoji absent.
- Fall back to STATE.md milestone when ROADMAP is missing; use last bare vX.Y
  in cleaned text instead of first (avoids v1.0 from shipped list).
- Fixes init.execute-phase milestone_version and buildStateFrontmatter after
  state.begin-phase (syncStateFrontmatter).

* feat(sdk): phase list, plan task structure, requirements extract handlers

- Register phase.list-plans, phase.list-artifacts, plan.task-structure,
  requirements.extract-from-plans (SDK-only; golden-policy exceptions).
- Add unit tests; document in QUERY-HANDLERS.md.
- writeProfile: honor --output, render dimensions, return profile_path and dimensions_scored.

* feat(sdk): centralize getGsdAgentsDir in query helpers

Extract agent directory resolution to helpers (GSD_AGENTS_DIR, primary
~/.claude/agents, legacy path). Use from init and docs-init init bundles.

docs(15): add 15-CONTEXT for autonomous phase-15 run.

* feat(sdk): query CLI CJS fallback and session correlation

- createRegistry(eventStream, sessionId) threads correlation into mutation events
- gsd-sdk query falls back to gsd-tools.cjs when no native handler matches
  (disable with GSD_QUERY_FALLBACK=off); stderr bridge warnings
- Export createRegistry from @gsd-build/sdk; add sdk/README.md
- Update QUERY-HANDLERS.md and registry module docs for fallback + sessionId
- Agents: prefer node dist/cli.js query over cat/grep for STATE and plans

* fix(sdk): init phase_found parity, docs-init agents path, state field extract

- Normalize findPhase not-found to null before roadmap fallback (matches findPhaseInternal)

- docs-init: use detectRuntime + resolveAgentsDir for checkAgentsInstalled

- state.cjs stateExtractField: horizontal whitespace only after colon (YAML progress guard)

- Tests: commit_docs default true; config-get golden uses temp config; golden integration green

Refs: #2302

* refactor(sdk): share SessionJsonlRecord in profile-extract-messages

CodeRabbit nit: dedupe JSONL record shape for isGenuineUserMessage and streamExtractMessages.

* fix(sdk): address CodeRabbit major threads (paths, gates, audit, verify)

- Resolve @file: and CLI JSON indirection relative to projectDir; guard empty normalized query command

- plan.task-structure + intel extract/patch-meta: resolvePathUnderProject containment

- check.config-gates: safe string booleans; plan_checker alias precedence over plan_check default

- state.validate/sync: phaseTokenMatches + comparePhaseNum ordering

- verify.schema-drift: token match phase dirs; files_modified from parsed frontmatter

- audit-open: has_scan_errors, unreadable rows, human report when scans fail

- requirements PLANNED key PLAN for root PLAN.md; gsd-tools timeout note

- ingest-docs: repo-root path containment; classifier output slug-hash

Golden parity test strips has_scan_errors until CJS adds field.

* fix: Resolve CodeRabbit security and quality findings
- Secure intel.ts and cli.ts against path traversal
- Catch and validate git add status in commit.ts
- Expand roadmap milestone marker extraction
- Fix parsing array-of-objects in frontmatter YAML
- Fix unhandled config evaluations
- Improve coverage test parity mapping

* test: raise planner character extraction limit to 48K

* fix(sdk): resolve TS build error in docs-init passing config
2026-04-20 18:09:02 -04:00
TÂCHES
c8807e38d7 Merge pull request #2481 from gsd-build/hotfix/1.37.1
chore: merge hotfix v1.37.1 back to main
2026-04-20 14:23:58 -06:00
Lex Christopherson
2b4446e2f9 chore: resolve merge conflict — take main's INVENTORY.md references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:23:49 -06:00
Lex Christopherson
ef4ce7d6f9 1.37.1 2026-04-20 14:16:09 -06:00
Tom Boucher
12d38b2da0 fix(ci): update ARCHITECTURE.md counts and add TEXT_MODE fallback to sketch workflow (#2377)
* fix(tests): clear CLAUDECODE env var in read-guard test runner

The hook skips its advisory on two env vars: CLAUDE_SESSION_ID and
CLAUDECODE. runHook() cleared CLAUDE_SESSION_ID but inherited CLAUDECODE
from process.env, so tests run inside a Claude Code session silently
no-oped and produced no stdout, causing JSON.parse to throw.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): update ARCHITECTURE.md counts and add TEXT_MODE fallback to sketch workflow

Four new spike/sketch files were added in 1.37.0 but two housekeeping
items were missed: ARCHITECTURE.md component counts (75→79 commands,
72→76 workflows) and the required TEXT_MODE fallback in sketch.md for
non-Claude runtimes (#2012).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): update directory-tree slash command count in ARCHITECTURE.md

Missed the second count in the directory tree (# 75 slash commands → 79).
The prose "Total commands" was updated but the tree annotation was not,
causing command-count-sync.test.cjs to fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 14:12:21 -06:00
Lex Christopherson
e7a6d9ef2e fix: sync spike/sketch workflows with upstream skill improvements
Spike workflow:
- Add prior spike check — skips already-validated questions
- Add comparison spikes (NNN-a/NNN-b) for head-to-head evaluation
- Add research-before-building step (context7 + web search)
- Add forensic logging/observability for runtime-interactive spikes
- Add Type column to MANIFEST, type/Research/Observability to README

Sketch workflow:
- Add research-the-target-stack step — check component availability,
  framework constraints, and idiomatic patterns before building

Spike wrap-up workflow:
- Replace per-spike curation with auto-include-all (every spike carries
  signal: VALIDATED=patterns, PARTIAL=constraints, INVALIDATED=landmines)
- Add Step 10 intelligent routing — integration spike candidates,
  frontier spike candidates, and standard next-step options

Commands updated with context7/WebSearch tools and --text flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:05:29 -06:00
github-actions[bot]
beb3ac247b chore: bump version to 1.37.1 for hotfix 2026-04-20 20:05:07 +00:00
Lex Christopherson
a95cabaedb fix: sync spike/sketch workflows with upstream skill improvements
Spike workflow:
- Add prior spike check — skips already-validated questions
- Add comparison spikes (NNN-a/NNN-b) for head-to-head evaluation
- Add research-before-building step (context7 + web search)
- Add forensic logging/observability for runtime-interactive spikes
- Add Type column to MANIFEST, type/Research/Observability to README

Sketch workflow:
- Add research-the-target-stack step — check component availability,
  framework constraints, and idiomatic patterns before building

Spike wrap-up workflow:
- Replace per-spike curation with auto-include-all (every spike carries
  signal: VALIDATED=patterns, PARTIAL=constraints, INVALIDATED=landmines)
- Add Step 10 intelligent routing — integration spike candidates,
  frontier spike candidates, and standard next-step options

Commands updated with context7/WebSearch tools and --text flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:04:31 -06:00
Tom Boucher
9d55d531a4 fix(#2432,#2424): pre-dispatch PLAN.md commit + reapply-patches baseline detection; docs(#2397): config schema drift (#2469)
- quick.md Step 5.6: commit PLAN.md to base branch before worktree executor
  spawn when USE_WORKTREES is active, preventing CC #36182 path-resolution
  drift that caused silent writes to main repo instead of worktree
- reapply-patches.md Option A: replace first-add commit heuristic with
  pristine_hashes SHA-256 matching from backup-meta.json so baseline detection
  works correctly on multi-cycle repos; first-add fallback kept for older
  installers without pristine_hashes
- CONFIGURATION.md: move security_enforcement/security_asvs_level/security_block_on
  to workflow.* (matches templates/config.json and workflow readers); rename
  context_profile → context (matches VALID_CONFIG_KEYS in config.cjs); add
  planning.sub_repos to schema example
- universal-anti-patterns.md + context-budget.md: fix context_window_tokens →
  context_window (the actual key name in config.cjs)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:11:00 -04:00
Tom Boucher
5f419c0238 fix(bugs): resolve issues #2388, #2431, #2396, #2376 (#2467)
#2388 (plan-phase silently renames feature branch): add explicit Git
Branch Invariant section to plan-phase.md prohibiting branch
creation/rename/switch during planning; phase slug changes are
plan-level only and must not affect the git branch.

#2431 (worktree teardown silently swallows errors): replace
`git worktree remove --force 2>/dev/null || true` with a lock-aware
block in quick.md and execute-phase.md that detects locked worktrees,
attempts unlock+retry, and surfaces a user-visible recovery message
when removal still fails.

#2396 (hardcoded test commands bypass Makefile): add a three-tier
test command resolver (project config → Makefile/Justfile → language
sniff) in execute-phase.md, verify-phase.md, and audit-fix.md.
Makefile with a `test:` target now takes priority over npm/cargo/go.

#2376 (OpenCode @$HOME not mapped on Windows): add platform guard in
bin/install.js so OpenCode on win32 uses the absolute path instead of
`$HOME/...`, which OpenCode does not expand in @file references on
Windows.

Tests: 29 new assertions across 4 regression test files (all passing).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:10:16 -04:00
Tom Boucher
dfa1ecce99 fix(#2418,#2399,#2419,#2421): four workflow and installer bug fixes (#2462)
- #2418: convertClaudeToAntigravityContent now replaces bare ~/.claude and
  $HOME/.claude (no trailing slash) for both global and local installs,
  eliminating the "unreplaced .claude path reference" warnings in
  gsd-debugger.md and update.md during Antigravity installs.

- #2399: plan-phase workflow gains step 13c that commits PLAN.md files
  and STATE.md via gsd-sdk query commit when commit_docs is true.
  Previously commit_docs:true was read but never acted on in plan-phase.

- #2419: new-project.md and new-milestone.md now parse agents_installed
  and missing_agents from the init JSON and warn users clearly when GSD
  agents are not installed, rather than silently failing with "agent type
  not found" when trying to spawn gsd-project-researcher subagents.

- #2421: gsd-planner.md gains a "Grep gate hygiene" rule immediately after
  the Nyquist Rule explaining the self-invalidating grep gate anti-pattern
  and providing comment-stripping alternatives (grep -v, ast-grep).

Tests: 4 new test files (30 tests) all passing.

Closes #2418
Closes #2399
Closes #2419
Closes #2421

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:09:33 -04:00
Tom Boucher
4cd890b252 fix(phase): guard backlog dirs and YYYY-MM dates in integer phase removal (#2466)
* fix(phase): guard backlog dirs and YYYY-MM dates in integer phase removal

Closes #2435
Closes #2434

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(phase): extend date-collision guard to hyphen-adjacent context

The lookbehind `(?<!\d)` in renameIntegerPhases only excluded
digit-prefixed matches; a YYYY-MM-DD date like 2026-05-14 has a hyphen
before the month digits, which passed the original guard and caused
date corruption when renumbering a phase whose zero-padded number
matched the month. Replace with `(?<![0-9-])` lookbehind and
`(?![0-9-])` lookahead to exclude both digit- and hyphen-adjacent
contexts. Adds a regression test for the hyphen-adjacent case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:52 -04:00
Tom Boucher
d117c1045a test: add --no-sdk to copilot-install E2E runners + static guard (#2461) (#2463)
Four execFileSync installer calls in copilot-install.test.cjs deleted
GSD_TEST_MODE but omitted --no-sdk, triggering the fatal installSdkIfNeeded()
path in test.yml CI where npm global bin is not on PATH.

Partial fix in e213ce0 patched three hook-deployment tests but missed
runCopilotInstall, runCopilotUninstall, runClaudeInstall, runClaudeUninstall.

Also adds tests/sdk-no-sdk-guard.test.cjs: a static analysis guard that
scans test files for subprocess installer calls missing --no-sdk, so this
class of regression is caught automatically in future.

Closes #2461

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:49 -04:00
Tom Boucher
0ea443cbcf fix(install): chmod sdk dist/cli.js executable; fix context monitor over-reporting (#2460)
Bug #2453: After tsc builds sdk/dist/cli.js, npm install -g from a local
directory does not chmod the bin-script target (unlike tarball extraction).
The file lands at mode 644, the gsd-sdk symlink points at a non-executable
file, and command -v gsd-sdk fails on every first install. Fix: explicitly
chmodSync(cliPath, 0o755) immediately after npm install -g completes,
mirroring the pattern used for hook files throughout the installer.

Bug #2451: gsd-context-monitor warning messages over-reported usage by ~13
percentage points vs CC native /context. Root cause: gsd-statusline.js
wrote a buffer-normalized used_pct (accounting for the 16.5% autocompact
reserve) to the bridge file, inflating values. The bridge used_pct is now
raw (Math.round(100 - remaining_percentage)), consistent with what CC's
native /context command reports. The statusline progress bar continues to
display the normalized value; only the bridge value changes. Updated the
existing #2219 tests to check the normalized display via hook stdout rather
than bridge.used_pct, and added a new assertion that bridge.used_pct is raw.

Closes #2453
Closes #2451

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:46 -04:00
Tom Boucher
53b9fba324 fix: stale phase dirs corrupt phase counts; stopped_at overwritten by historical prose (#2459)
* fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing

Closes #2422
Closes #2420

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#2444,#2445): scope stopped_at extraction to Session section; filter stale phase dirs

- buildStateFrontmatter now extracts stopped_at only from the ## Session
  section when one exists, preventing historical prose elsewhere in the
  body (e.g. "Stopped at: Phase 5 complete" in old notes) from overwriting
  the current value in frontmatter (bug #2444)
- buildStateFrontmatter de-duplicates phase dirs by normalized phase number
  before computing plan/phase counts, so stale phase dirs from a prior
  milestone with the same phase numbers as the new milestone don't inflate
  totals (bug #2445)
- cmdInitNewMilestone now filters phase dirs through getMilestonePhaseFilter
  so phase_dir_count excludes stale prior-milestone dirs (bug #2445)
- Tests: 4 tests in state.test.cjs covering both bugs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:43 -04:00
Tom Boucher
5afcd5577e fix: zero-padded phase numbers bypass archived-phase guard; stale current_milestone (#2458)
* fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing

Closes #2422
Closes #2420

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(sdk): skip stateVersion early-return for shipped milestones

When STATE.md has a stale `milestone: v1.0` entry but v1.0 is already
shipped (heading contains  in ROADMAP.md), the stateVersion early-return
path in getMilestoneInfo was returning v1.0 instead of detecting the new
active milestone.

Two-part fix:
1. In the stateVersion block: skip the early-return when the matched
   heading line includes  (shipped marker). Fall through to normal
   detection instead.
2. In the heading-format fallback regex: add a negative lookahead
   `(?!.*)` so the regex never matches a  heading regardless of
   whether stateVersion was present. This handles the no-STATE.md case
   and ensures fallthrough from part 1 actually finds the next milestone.

Adds two regression tests covering both -suffix (`## v1.0  Name`)
and -prefix (`##  v1.0 Name`) heading formats.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(core): allow padded-and-unpadded phase headings in getRoadmapPhaseInternal

The zero-strip normalization (01→1) fixed the archived-phase guard but
broke lookup against ROADMAP headings that still use zero-padded numbers
like "Phase 01:". Change the regex to use 0*<normalized> so both formats
match, making the fix robust regardless of ROADMAP heading style.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:40 -04:00
Tom Boucher
9f79cdc40a fix(security): neutralize spaced+closing injection markers; fix audit-uat resolved status (#2456)
* fix(security): neutralize spaced+closing injection markers; fix audit-uat resolved status

scanForInjection recognizes — adds <user> tags, whitespace-padded tags
(e.g. <user >), closing [/SYSTEM]/[/INST] markers, and closing <</SYS>>
markers. Five new regression tests confirm each gap is closed.

whose result column reads PASS or resolved, so items that were already
confirmed do not appear as outstanding in audit-uat --raw. Two new
regression tests cover item-level PASS and file-level status: passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add closing-tag assertion for spaced <user > sanitization

The test for 'neutralizes spaced tags like <user >' only asserted that the
opening token '<user' was removed. A spaced closing tag '</user >' could
survive sanitization undetected. Added assert.ok(!result.includes('</user'))
to the same test block so both sides of the tag are verified.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:18 -04:00
Tom Boucher
59cfbbba6a fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing (#2455)
* fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing

Closes #2422
Closes #2420

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: patch-version semver in milestone boundary regex + flag-parser validation

Two follow-on correctness issues identified in code review:

1. roadmap.ts: currentVersionMatch and nextMilestoneRegex only captured
   major.minor (v(\d+\.\d+)), collapsing v2.0.1 to "2.0". A sub-heading
   "## v2.0.2 Phase Details" would match the same prefix and be incorrectly
   skipped. Both patterns updated to v(\d+(?:\.\d+)+) to capture full semver.

2. state-mutation.ts: pair-wise flag parsing loop advanced i by 2 unconditionally,
   so a missing flag value caused the next flag token to be assigned as the value
   (e.g. flags['phase'] = '--name'). Fix: iterate with i++ and validate that the
   candidate value exists and does not start with '--' before assigning; throw
   GSDError('missing value for --<key>') on invalid input. Added regression test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:08:14 -04:00
Tom Boucher
990c3e648d fix(tests): update 5 source-text tests to read config-schema.cjs (#2480)
VALID_CONFIG_KEYS moved from config.cjs to config-schema.cjs in the
drift-prevention companion PR. Tests that read config.cjs source text
and checked for key literal includes() now point to the correct file.

Closes #2480

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 09:54:35 -04:00
Tom Boucher
62eaa8dd7b docs: close doc drift vectors — bidirectional parity, manifest, schema-driven config (#2479)
Option A — ghost-entry guard (INVENTORY ⊆ actual):
  tests/inventory-source-parity.test.cjs parses every declared row in
  INVENTORY.md and asserts the source file exists. Catches deletions and
  renames that leave ghost entries behind.

Option B — auto-generated structural manifest:
  scripts/gen-inventory-manifest.cjs walks all six family dirs and emits
  docs/INVENTORY-MANIFEST.json. tests/inventory-manifest-sync.test.cjs
  fails CI when a new surface ships without a manifest update, surfacing
  exactly which entries are missing.

Option C — schema-driven config validation + docs parity:
  get-shit-done/bin/lib/config-schema.cjs extracted from config.cjs as
  the single source of truth for VALID_CONFIG_KEYS and dynamic patterns.
  config.cjs now imports from it. tests/config-schema-docs-parity.test.cjs
  asserts every exact-match key appears in docs/CONFIGURATION.md, surfacing
  14 previously undocumented keys (planning.sub_repos, workflow.ai_integration_phase,
  git.base_branch, learnings.max_inject, and 10 others) — all now documented
  in their appropriate sections.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 09:39:05 -04:00
Logan
fbf30792f3 docs: authoritative shipped-surface inventory with filesystem-backed parity tests (#2390)
* docs: finish trust-bug fixes in user guide and commands

Correct load-bearing defects in the v1.36.0 docs corpus so readers stop
acting on wrong defaults and stale exhaustiveness claims.

- README.md: drop "Complete feature"/"Every command"/"All 18 agents"
  exhaustiveness claims; replace version-pinned "What's new in v1.32"
  bullet with a CHANGELOG pointer.
- CONFIGURATION.md: fix `claude_md_path` default (null/none -> `./CLAUDE.md`)
  in both Full Schema and core settings table; correct `workflow.tdd_mode`
  provenance from "Added in v1.37" to "Added in v1.36".
- USER-GUIDE.md: fix `workflow.discuss_mode` default (`standard` ->
  `discuss`) in the workflow-toggles table AND in the abbreviated Full
  Schema JSON block above it; align the Options cell with the shipped
  enum.
- COMMANDS.md: drop "Complete command syntax" subtitle overclaim to
  match the README posture.
- AGENTS.md: weaken "All 21 specialized agents" header to reflect that
  the `agents/` filesystem is authoritative (shipped roster is 31).

Part 1 of a stacked docs refresh series (PR 1/4).

* docs: refresh shipped surface coverage for v1.36

Close the v1.36.0 shipped-surface gaps in the docs corpus.

- COMMANDS.md: add /gsd-graphify section (build/query/status/diff) and
  its config gate; expand /gsd-quick with --validate flag and list/
  status/resume subcommands; expand /gsd-thread with list --open, list
  --resolved, close <slug>, status <slug>.
- CLI-TOOLS.md: replace the hardcoded "15 domain modules" count with a
  pointer to the Module Architecture table; add a graphify verb-family
  section (build/query/status/diff/snapshot); add Graphify and Learnings
  rows to the Module Architecture table.
- FEATURES.md: add TOC entries for #116 TDD Pipeline Mode and #117
  Knowledge Graph Integration; add the #117 body with REQ-GRAPH-01..05.
- CONFIGURATION.md: move security_enforcement / security_asvs_level /
  security_block_on from root into `workflow.*` in Full Schema to match
  templates/config.json and the gsd-sdk runtime reads; update Security
  Settings table to use the workflow.* prefix; add planning.sub_repos
  to Full Schema and description table; add a Graphify Settings section
  documenting graphify.enabled and graphify.build_timeout.

Note: VALID_CONFIG_KEYS in bin/lib/config.cjs does not yet include
workflow.security_* or planning.sub_repos, so config-set currently
rejects them. That is a pre-existing validator gap that this PR does
not attempt to fix; the docs now correctly describe where these keys
live per the shipped template and runtime reads.

Part 2 of a stacked docs refresh series (PR 2/5), based on PR 1.

* docs: make inventory authoritative and reconcile architecture

Upgrade docs/INVENTORY.md from "complete for agents, selective for others"
to authoritative across all six shipped-surface families, and reconcile
docs/ARCHITECTURE.md against the new inventory so the PR that introduces
INVENTORY does not also introduce an INVENTORY/ARCHITECTURE contradiction.

- docs/AGENTS.md: weaken "21 specialized agents" header to 21 primary +
  10 advanced (31 shipped); add new "Advanced and Specialized Agents"
  section with concise role cards for the 10 previously-omitted shipped
  agents (pattern-mapper, debug-session-manager, code-reviewer,
  code-fixer, ai-researcher, domain-researcher, eval-planner,
  eval-auditor, framework-selector, intel-updater); footnote the Agent
  Tool Permissions Summary as primary-agents-only so it no longer
  misleads.

- docs/INVENTORY.md (rewritten to be authoritative):
  * Full 31-agent roster with one-line role + spawner + primary-doc
    status per agent (unchanged from prior partial work).
  * Commands: full 75-row enumeration grouped by Core Workflow, Phase &
    Milestone Management, Session & Navigation, Codebase Intelligence,
    Review/Debug/Recovery, and Docs/Profile/Utilities — each row
    carries a one-line role derived from the command's frontmatter and
    a link to the source file.
  * Workflows: full 72-row enumeration covering every
    get-shit-done/workflows/*.md, with a one-line role per workflow and
    a column naming the user-facing command (or internal orchestrator)
    that invokes it.
  * References: full 41-row enumeration grouped by Core, Workflow,
    Thinking-Model clusters, and the Modular Planner decomposition,
    matching the groupings docs/ARCHITECTURE.md already uses; notes
    the few-shot-examples subdirectory separately.
  * CLI Modules and Hooks: unchanged — already full rosters.
  * Maintenance section rewritten to describe the drift-guard test
    suite that will land in PR4 (inventory-counts, commands-doc-parity,
    agents-doc-parity, cli-modules-doc-parity, hooks-doc-parity).

- docs/ARCHITECTURE.md reconciled against INVENTORY:
  * References block: drop the stale "(35 total)" count; point at
    INVENTORY.md#references-41-shipped for the authoritative count.
  * CLI Tools block: drop the stale "19 domain modules" count; point
    at INVENTORY.md#cli-modules-24-shipped for the authoritative roster.
  * Agent Spawn Categories: relabel as "Primary Agent Spawn Categories"
    and add a footer naming the 10 advanced agents and pointing at
    INVENTORY.md#agents-31-shipped for the full 31-agent roster.

- docs/CONFIGURATION.md: preserve the six model-profile rows added in
  the prior partial work, and tighten the fallback note so it names the
  13 shipped agents without an explicit profile row, documents
  model_overrides as the escape hatch, and points at INVENTORY.md for
  the authoritative 31-agent roster.

Part 3 of a stacked docs refresh series (PR 3/4). Remaining consistency
work (USER-GUIDE config-section delete-and-link, FEATURES.md TOC
reorder, ARCHITECTURE.md Hook-table expansion + installation-layout
collapse, CLI-TOOLS.md module-row additions, workflow-discuss-mode
invocation normalization, and the five doc-parity tests) lands in PR4.

* test(docs): add consistency guards and remove duplicate refs

Consolidates USER-GUIDE.md's command/config duplicates into pointers to
COMMANDS.md and CONFIGURATION.md (kills a ghost `resolve_model_ids` key
and a stale `discuss_mode: standard` default); reorders FEATURES.md TOC
chronologically so v1.32 precedes v1.34/1.35/1.36; expands
ARCHITECTURE.md's Hook table to the 11 shipped hooks
(gsd-read-injection-scanner, gsd-check-update-worker) and collapses
the installation-layout hook enumeration to the *.js/*.sh pattern form;
adds audit/gsd2-import/intel rows and state signal-*, audit-open,
from-gsd2 verbs to CLI-TOOLS.md; normalizes workflow-discuss-mode.md
invocations to `node gsd-tools.cjs config-set`.

Adds five drift guards anchored on docs/INVENTORY.md as the
authoritative roster: inventory-counts (all six families),
commands/agents/cli-modules/hooks parity checks that every shipped
surface has a row somewhere.

* fix(convergence): thread --ws to review agent; add stall and max-cycles behavioral tests

- Thread GSD_WS through to review agent spawn in plan-review-convergence
  workflow (step 5a) so --ws scoping is symmetric with planning step
- Add behavioral stall detection test: asserts workflow compares
  HIGH_COUNT >= prev_high_count and emits a stall warning
- Add behavioral --max-cycles 1 test: asserts workflow reaches escalation
  gate when cycle >= MAX_CYCLES with HIGH > 0 after a single cycle
- Include original PR files (commands, workflow, tests) as the branch
  predated the PR commits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(docs,config): PR #2390 review — security_* config keys and REQ-GRAPH-02 scope

Addresses trek-e's review items that don't require rebase:

- config.cjs: add workflow.security_enforcement, workflow.security_asvs_level,
  workflow.security_block_on to VALID_CONFIG_KEYS so gsd-sdk config-set accepts
  them (closed the gap where docs/CONFIGURATION.md listed keys the validator
  rejected).
- core.cjs: add matching CONFIG_DEFAULTS entries (true / 1 / 'high') so the
  canonical defaults table matches the documented values.
- config.cjs: wire the three keys into the new-project workflow defaults so
  fresh configs inherit them.
- planning-config.md: document the three keys in the Workflow Fields table,
  keeping the CONFIG_DEFAULTS ↔ doc parity test happy.
- config-field-docs.test.cjs: extend NAMESPACE_MAP so the flat keys in
  CONFIG_DEFAULTS resolve to their workflow.* doc rows.
- FEATURES.md REQ-GRAPH-02: split the slash-command surface (build|query|
  status|diff) from the CLI surface which additionally exposes `snapshot`
  (invoked automatically at the tail of `graphify build`). The prior text
  overstated the slash-command surface.

* docs(inventory): refresh rosters and counts for post-rebase drift

origin/main accumulated surfaces since this PR was authored:

- Agents: 31 → 33 (+ gsd-doc-classifier, gsd-doc-synthesizer)
- Commands: 76 → 82 (+ ingest-docs, ultraplan-phase, spike, spike-wrap-up,
  sketch, sketch-wrap-up)
- Workflows: 73 → 79 (same 6 names)
- References: 41 → 49 (+ debugger-philosophy, doc-conflict-engine,
  mandatory-initial-read, project-skills-discovery, sketch-interactivity,
  sketch-theme-system, sketch-tooling, sketch-variant-patterns)

Adds rows in the existing sub-groupings, introduces a Sketch References
subsection, and bumps all four headline counts. Roles are pulled from
source frontmatter / purpose blocks for each file. All 5 parity tests
(inventory-counts, agents-doc-parity, commands-doc-parity,
cli-modules-doc-parity, hooks-doc-parity) pass against this state —
156 assertions, 0 failures.

Also updates the 'Coverage note' advanced-agent count 10 → 12 and the
few-shot-examples footnote "41 top-level references" → "49" to keep the
file internally consistent.

* docs(agents): add advanced stubs for gsd-doc-classifier and gsd-doc-synthesizer

Both agents ship on main (spawned by /gsd-ingest-docs) but had no
coverage in docs/AGENTS.md. Adds the "advanced stub" entries (Role,
property table, Key behaviors) following the template used by the other
10 advanced/specialized agents in the same section.

Also updates the Agent Tool Permissions Summary scope note from
"10 advanced/specialized agents" to 12 to reflect the two new stubs.

* docs(commands): add entries for ingest-docs, ultraplan-phase, plan-review-convergence

These three commands ship on main (plan-review-convergence via trek-e's
4b452d29 commit on this branch) but had no user-facing section in
docs/COMMANDS.md — they lived only in INVENTORY.md. The commands-doc-parity
test already passes via INVENTORY, but the user-facing doc was missing
canonical explanations, argument tables, and examples.

- /gsd-plan-review-convergence → Core Workflow (after /gsd-plan-phase)
- /gsd-ultraplan-phase → Core Workflow (after plan-review-convergence)
- /gsd-ingest-docs → Brownfield (after /gsd-import, since both consume
  the references/doc-conflict-engine.md contract)

Content pulled from each command's frontmatter and workflow purpose block.

* test: remove redundant ARCHITECTURE.md count tests

tests/architecture-counts.test.cjs and tests/command-count-sync.test.cjs
were added when docs/ARCHITECTURE.md carried hardcoded counts for commands/
workflows/agents. With the PR #2390 cleanup, ARCHITECTURE.md no longer
owns those numbers — docs/INVENTORY.md does, enforced by
tests/inventory-counts.test.cjs (scans the same filesystem directories
with the same readdirSync filter).

Keeping these ARCHITECTURE-specific tests would re-introduce the hardcoded
counts they guard, defeating trek-e's review point. The single-source-of-
truth parity tests already catch the same drift scenarios.

Related: #2257 (the regression this replaced).

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 09:31:34 -04:00
alanshurafa
3d6c2bea4b docs: clarify capture_thought is an optional convention (#1873) (#2379)
* docs: clarify capture_thought is an optional convention (#1873)

Issue #1873 merged /gsd:extract-learnings with an optional
capture_thought hook, but the docs never explained what the tool is
or where it comes from — readers couldn't tell whether it was a
bundled GSD tool, a required dependency, or something they had to
install. This surfaced in a user question on that issue's thread.

Clarify in docs/FEATURES.md §112 and the workflow file that
capture_thought is a convention — any MCP server exposing a tool
with that name will be used; if none is present, LEARNINGS.md
remains the primary output and the step is a silent no-op.

No behavioral change. All 23 extract-learnings tests still pass.

* fix(security): add human to detection message; test [/INST] closing form neutralization

- Detection message now lists <human> alongside <system>/<assistant>/<user>
- Sanitizer regex extended to cover [/INST] closing form (was only [INST])
- Detection pattern extended to cover [/INST] closing form
- New sanitizeForPrompt test asserts [/INST] is neutralized

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(config): add workflow.security_* keys to VALID_CONFIG_KEYS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add language tag to fenced code block in FEATURES.md

Fixes MD040 lint finding in PR #2379 — the capture_thought tool
signature example was missing a javascript language identifier.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 09:04:21 -04:00
Tom Boucher
ebbe74de72 feat(release): publish @gsd-build/sdk alongside get-shit-done-cc in release pipeline (#2468)
* fix(sdk): bump engines.node from >=20 to >=22.0.0

Node 20 reaches EOL April 30 2026. The root package already declares
>=22.0.0 and CI only runs Node 22 and 24. Align sdk/package.json so
`npm install` on Node 20 fails with a clear engines mismatch rather
than a silent install that breaks at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(release): publish @gsd-build/sdk alongside get-shit-done-cc in release pipeline

Closes #2309

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 23:13:14 -04:00
Tom Boucher
2bb1f1ebaf fix(debug): read tdd_mode via workflow.tdd_mode key (closes #2398) (#2454)
debug.md was calling `config-get tdd_mode` (top-level key) while every
other consumer (execute-phase, verify-phase, audit-fix) uses
`config-get workflow.tdd_mode`. This caused /gsd-debug to silently
ignore the tdd_mode setting even when explicitly set in config.json.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 23:12:23 -04:00
Rezolv
39623fd5b8 docs(cli): deprecate gsd-tools.cjs header in favor of gsd-sdk (#2343) (#2343)
Single-file change: JSDoc @deprecated notice pointing to SDK query registry.
No .planning or unrelated merges.
2026-04-19 23:10:32 -04:00
Tom Boucher
e3f40201dd fix(sdk): bump engines.node from >=20 to >=22.0.0 (#2465)
Node 20 reaches EOL April 30 2026. The root package already declares
>=22.0.0 and CI only runs Node 22 and 24. Align sdk/package.json so
`npm install` on Node 20 fails with a clear engines mismatch rather
than a silent install that breaks at runtime.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 23:02:57 -04:00
Jeremy McSpadden
2bb274930b Merge pull request #2452 from gsd-build/codex/merge-hotfix-1.38.1-qwen-path
fix(install): template bare .claude hook paths for non-Claude runtimes
2026-04-19 19:00:18 -05:00
Jeremy McSpadden
f874313807 fix(install): template bare .claude hook paths for non-Claude runtimes 2026-04-19 18:47:59 -05:00
Jeremy McSpadden
30433368a0 fix(install): template bare .claude hook paths for non-Claude runtimes 2026-04-19 18:42:30 -05:00
Jeremy McSpadden
04fab926b5 test: add --no-sdk to hook-deployment installer tests
Tests #1834, #1924, #2136 exercise hook/artifact deployment and don't
care about SDK install. Now that installSdkIfNeeded() failures are
fatal, these tests fail on any CI runner without gsd-sdk pre-built
because the sdk/ tsc build path runs and can fail in CI env.

Pass --no-sdk so each test focuses on its actual subject. SDK install
path has dedicated end-to-end coverage in install-smoke.yml.
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
f98ef1e460 fix(install): fatal SDK install failures + CI smoke gate (#2439)
## Why
#2386 added `installSdkIfNeeded()` to build @gsd-build/sdk from bundled
source and `npm install -g .`, because the npm-published @gsd-build/sdk
is intentionally frozen and version-mismatched with get-shit-done-cc.

But every failure path in that function was warning-only — including
the final `which gsd-sdk` verification. When npm's global bin is off a
user's PATH (common on macOS), the installer printed a yellow warning
then exited 0. Users saw "install complete" and then every `/gsd-*`
command crashed with `command not found: gsd-sdk` (the #2439 symptom).

No CI job executed the install path, so this class of regression could
ship undetected — existing "install" tests only read bin/install.js as
a string.

## What changed

**bin/install.js — installSdkIfNeeded() is now transactional**
- All build/install failures exit non-zero (not just warn).
- Post-install `which gsd-sdk` check is fatal: if the binary landed
  globally but is off PATH, we exit 1 with a red banner showing the
  resolved npm bin dir, the user's shell, the target rc file, and the
  exact `export PATH=…` line to add.
- Escape hatch: `GSD_ALLOW_OFF_PATH=1` downgrades off-PATH to exit 2
  for users with intentionally restricted PATH who will wire up the
  binary manually.
- Resolver uses POSIX `command -v` via `sh -c` (replaces `which`) so
  behavior is consistent across sh/bash/zsh/fish.
- Factored `resolveGsdSdk()`, `detectShellRc()`, `emitSdkFatal()`.

**.github/workflows/install-smoke.yml (new)**
- Executes the real install path: `npm pack` → `npm install -g <tgz>`
  → run installer non-interactively → `command -v gsd-sdk` → run
  `gsd-sdk --version`.
- PRs: path-filtered to installer-adjacent files, ubuntu + Node 22 only.
- main/release branches: full matrix (ubuntu+macos × Node 22+24).
- Reusable via workflow_call with `ref` input for release gating.

**.github/workflows/release.yml — pre-publish gate**
- New `install-smoke-rc` and `install-smoke-finalize` jobs invoke the
  reusable workflow against the release branch. `rc` and `finalize`
  now `needs: [validate-version, install-smoke-*]`, so a broken SDK
  install blocks `npm publish`.

## Test plan
- Local full suite: 4154/4154 pass
- install-smoke.yml will self-validate on this PR (ubuntu+Node22 only)

Addresses root cause of #2439 (the per-command pre-flight in #2440 is
the complementary defensive layer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
d0565e95c1 fix(set-profile): use hyphenated /gsd-set-profile in pre-flight message
Project convention (#1748) requires /gsd-<cmd> hyphen form everywhere
except designated test inputs. Fix the colon references in the
pre-flight error and its regression test to satisfy stale-colon-refs.
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
4ef6275e86 fix(set-profile): guard gsd-sdk invocation with command -v pre-flight (#2439)
/gsd:set-profile crashed with `command not found: gsd-sdk` when gsd-sdk
was not on PATH. The command invoked `gsd-sdk query` directly in a `!`
backtick with no guard, so a missing binary produced an opaque shell
error with exit 127.

Add a `command -v gsd-sdk` pre-flight that prints the install/update
hint and exits 1 when absent, mirroring the #2334 fix on /gsd-quick.
The auto-install in #2386 still runs at install time; this guard is the
defensive layer for users whose npm global bin is off-PATH (install.js
warns but does not fail in that case).

Closes #2439
2026-04-19 18:39:32 -05:00
Jeremy McSpadden
6c50490766 fix(sdk): register init.ingest-docs handler and add registry drift guard (#2442)
The ingest-docs workflow called `gsd-sdk query init.ingest-docs` with a
fallback to `init.default` — neither was registered in createRegistry(),
so the workflow proceeded with `{}` and tried to parse project_exists,
planning_exists, has_git, and project_path from empty.

- Add initIngestDocs handler; register dotted + space aliases
- Simplify workflow call; drop broken fallback
- Repo-wide drift guard scans commands/, agents/, get-shit-done/,
  hooks/, bin/, scripts/, docs/ for `gsd-sdk query <cmd>` and fails
  on any reference with no registered handler (file:line citations)
- Unit tests for the new handler

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:20 -05:00
Jeremy McSpadden
4cbebfe78c docs(readme): add /gsd-ingest-docs to Brownfield commands
Surfaces the new ingest-docs command from the Unreleased changelog in
the README Commands section so users discover it without digging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:20 -05:00
Jeremy McSpadden
9e87d43831 fix(build): include gsd-read-injection-scanner in hooks/dist (#2406)
The scanner was added in #2201 but never added to the HOOKS_TO_COPY
allowlist in scripts/build-hooks.js, so it never landed in hooks/dist/.
install.js reads from hooks/dist/, so every install on 1.37.0/1.37.1
emitted "Skipped read injection scanner hook — not found at target"
and the read-time prompt-injection scanner was silently disabled.

- Add gsd-read-injection-scanner.js to HOOKS_TO_COPY
- Add it to EXPECTED_ALL_HOOKS regression test in install-hooks-copy

Fixes #2406

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:39:20 -05:00
github-actions[bot]
29ea90bc83 chore: bump version to 1.38.1 for hotfix 2026-04-19 23:37:15 +00:00
Jeremy McSpadden
278082a51d Merge pull request #2440 from gsd-build/fix/2439-command-not-found-gsd-sdk
fix(set-profile): guard gsd-sdk invocation with command -v pre-flight (#2439)
2026-04-19 18:26:22 -05:00
Jeremy McSpadden
de59b14dde Merge pull request #2449 from gsd-build/fix/2439-installer-sdk-hardening
fix(install): fatal SDK install failures + CI smoke gate (#2439)
2026-04-19 18:24:21 -05:00
Jeremy McSpadden
e213ce0292 test: add --no-sdk to hook-deployment installer tests
Tests #1834, #1924, #2136 exercise hook/artifact deployment and don't
care about SDK install. Now that installSdkIfNeeded() failures are
fatal, these tests fail on any CI runner without gsd-sdk pre-built
because the sdk/ tsc build path runs and can fail in CI env.

Pass --no-sdk so each test focuses on its actual subject. SDK install
path has dedicated end-to-end coverage in install-smoke.yml.
2026-04-19 16:35:32 -05:00
Jeremy McSpadden
af66cd89ca fix(install): fatal SDK install failures + CI smoke gate (#2439)
## Why
#2386 added `installSdkIfNeeded()` to build @gsd-build/sdk from bundled
source and `npm install -g .`, because the npm-published @gsd-build/sdk
is intentionally frozen and version-mismatched with get-shit-done-cc.

But every failure path in that function was warning-only — including
the final `which gsd-sdk` verification. When npm's global bin is off a
user's PATH (common on macOS), the installer printed a yellow warning
then exited 0. Users saw "install complete" and then every `/gsd-*`
command crashed with `command not found: gsd-sdk` (the #2439 symptom).

No CI job executed the install path, so this class of regression could
ship undetected — existing "install" tests only read bin/install.js as
a string.

## What changed

**bin/install.js — installSdkIfNeeded() is now transactional**
- All build/install failures exit non-zero (not just warn).
- Post-install `which gsd-sdk` check is fatal: if the binary landed
  globally but is off PATH, we exit 1 with a red banner showing the
  resolved npm bin dir, the user's shell, the target rc file, and the
  exact `export PATH=…` line to add.
- Escape hatch: `GSD_ALLOW_OFF_PATH=1` downgrades off-PATH to exit 2
  for users with intentionally restricted PATH who will wire up the
  binary manually.
- Resolver uses POSIX `command -v` via `sh -c` (replaces `which`) so
  behavior is consistent across sh/bash/zsh/fish.
- Factored `resolveGsdSdk()`, `detectShellRc()`, `emitSdkFatal()`.

**.github/workflows/install-smoke.yml (new)**
- Executes the real install path: `npm pack` → `npm install -g <tgz>`
  → run installer non-interactively → `command -v gsd-sdk` → run
  `gsd-sdk --version`.
- PRs: path-filtered to installer-adjacent files, ubuntu + Node 22 only.
- main/release branches: full matrix (ubuntu+macos × Node 22+24).
- Reusable via workflow_call with `ref` input for release gating.

**.github/workflows/release.yml — pre-publish gate**
- New `install-smoke-rc` and `install-smoke-finalize` jobs invoke the
  reusable workflow against the release branch. `rc` and `finalize`
  now `needs: [validate-version, install-smoke-*]`, so a broken SDK
  install blocks `npm publish`.

## Test plan
- Local full suite: 4154/4154 pass
- install-smoke.yml will self-validate on this PR (ubuntu+Node22 only)

Addresses root cause of #2439 (the per-command pre-flight in #2440 is
the complementary defensive layer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:31:15 -05:00
Jeremy McSpadden
48a354663e Merge pull request #2443 from gsd-build/fix/2442-gsd-sdk-query-registry-integration
fix(sdk): register init.ingest-docs handler and add registry drift guard
2026-04-19 15:58:17 -05:00
Jeremy McSpadden
0a62e5223e fix(sdk): register init.ingest-docs handler and add registry drift guard (#2442)
The ingest-docs workflow called `gsd-sdk query init.ingest-docs` with a
fallback to `init.default` — neither was registered in createRegistry(),
so the workflow proceeded with `{}` and tried to parse project_exists,
planning_exists, has_git, and project_path from empty.

- Add initIngestDocs handler; register dotted + space aliases
- Simplify workflow call; drop broken fallback
- Repo-wide drift guard scans commands/, agents/, get-shit-done/,
  hooks/, bin/, scripts/, docs/ for `gsd-sdk query <cmd>` and fails
  on any reference with no registered handler (file:line citations)
- Unit tests for the new handler

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:55:37 -05:00
Jeremy McSpadden
708f60874e fix(set-profile): use hyphenated /gsd-set-profile in pre-flight message
Project convention (#1748) requires /gsd-<cmd> hyphen form everywhere
except designated test inputs. Fix the colon references in the
pre-flight error and its regression test to satisfy stale-colon-refs.
2026-04-19 15:40:50 -05:00
Jeremy McSpadden
a20aa81a0e Merge pull request #2437 from gsd-build/docs/readme-ingest-docs
docs(readme): add /gsd-ingest-docs to Brownfield commands
2026-04-19 15:39:30 -05:00
Jeremy McSpadden
d8aaeb6717 fix(set-profile): guard gsd-sdk invocation with command -v pre-flight (#2439)
/gsd:set-profile crashed with `command not found: gsd-sdk` when gsd-sdk
was not on PATH. The command invoked `gsd-sdk query` directly in a `!`
backtick with no guard, so a missing binary produced an opaque shell
error with exit 127.

Add a `command -v gsd-sdk` pre-flight that prints the install/update
hint and exits 1 when absent, mirroring the #2334 fix on /gsd-quick.
The auto-install in #2386 still runs at install time; this guard is the
defensive layer for users whose npm global bin is off-PATH (install.js
warns but does not fail in that case).

Closes #2439
2026-04-19 15:34:44 -05:00
Jeremy McSpadden
6727a0c929 docs(readme): add /gsd-ingest-docs to Brownfield commands
Surfaces the new ingest-docs command from the Unreleased changelog in
the README Commands section so users discover it without digging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:06:16 -05:00
Jeremy McSpadden
f330ab5c9f Merge pull request #2407 from gsd-build/fix/2406-ship-read-injection-scanner
fix(build): ship gsd-read-injection-scanner hook to users
2026-04-19 14:58:44 -05:00
Jeremy McSpadden
3856b53098 Merge remote-tracking branch 'origin/main' into fix/2406-ship-read-injection-scanner
# Conflicts:
#	CHANGELOG.md
2026-04-18 11:37:47 -05:00
Rezolv
0171f70553 feat(sdk): GSDTools native dispatch and CJS fallback routing (#2302 Track C) (#2342) 2026-04-18 12:35:23 -04:00
Tom Boucher
381c138534 feat(sdk): make checkAgentsInstalled runtime-aware (#2402) (#2413) 2026-04-18 12:22:36 -04:00
Jeremy McSpadden
8ac02084be fix(sdk): point checkAgentsInstalled at ~/.claude/agents (#2401) 2026-04-18 12:11:07 -04:00
Tom Boucher
e208e9757c refactor(agents): consolidate emphasis-marker density in top 4 agents (#2368) (#2412) 2026-04-18 12:10:22 -04:00
Jeremy McSpadden
13a96ee994 fix(build): include gsd-read-injection-scanner in hooks/dist (#2406)
The scanner was added in #2201 but never added to the HOOKS_TO_COPY
allowlist in scripts/build-hooks.js, so it never landed in hooks/dist/.
install.js reads from hooks/dist/, so every install on 1.37.0/1.37.1
emitted "Skipped read injection scanner hook — not found at target"
and the read-time prompt-injection scanner was silently disabled.

- Add gsd-read-injection-scanner.js to HOOKS_TO_COPY
- Add it to EXPECTED_ALL_HOOKS regression test in install-hooks-copy

Fixes #2406

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 08:42:36 -05:00
github-actions[bot]
0c6172bfad chore: finalize v1.38.0 2026-04-18 03:45:59 +00:00
Jeremy McSpadden
e3bd06c9fd fix(release): make merge-back PR step non-fatal
Repos that disable "Allow GitHub Actions to create and approve pull
requests" (org-level policy or repo-level setting) cause the "Create PR
to merge release back to main" step to fail with a GraphQL 403. That
failure cascades: Tag and push, npm publish, GitHub Release creation
are all skipped, and the entire release aborts.

The merge-back PR is a convenience — it's re-openable manually after
the release. Making it non-fatal with continue-on-error lets the rest
of the release complete. The step now emits ::warning:: annotations
pointing at the manual-recovery command when it fails.

Shell pipelines also fall through with `|| echo "::warning::..."` so
transient gh CLI failures don't mask the underlying policy issue.

Covers the failure mode seen on run 24596079637 where dry-run publish
validation passed but the release halted at the PR-creation step.
2026-04-17 22:45:22 -05:00
github-actions[bot]
c69ecd975a chore: bump to 1.38.0-rc.1 2026-04-18 03:05:35 +00:00
Jeremy McSpadden
06c4ded4ec docs(changelog): promote Unreleased to [1.38.0] + add ultraplan entry 2026-04-17 22:03:26 -05:00
github-actions[bot]
341bb941c6 chore: bump version to 1.38.0 for release 2026-04-18 03:02:41 +00:00
Jeremy McSpadden
28d6649f0b Merge pull request #2389 from gsd-build/feat/2387-ingest-docs-clean
feat(ingest-docs): /gsd-ingest-docs — bootstrap or merge .planning/ from repo docs
2026-04-17 21:49:57 -05:00
Jeremy McSpadden
d5f849955b Merge remote-tracking branch 'origin/main' into feat/2387-ingest-docs-clean
# Conflicts:
#	CHANGELOG.md
2026-04-17 21:46:39 -05:00
Jeremy McSpadden
0f7bcabd78 Merge pull request #2386 from gsd-build/fix/2385-sdk-install-flag
fix(install): auto-install @gsd-build/sdk so gsd-sdk is on PATH
2026-04-17 21:45:17 -05:00
Jeremy McSpadden
fc1fa9172b fix(install): build gsd-sdk from in-repo sdk/ source, not stale npm package
PR #2386 v1 installed the published @gsd-build/sdk from npm, which ships an
older version that lacks query handlers needed by current workflows. Every
GSD release would drift further from what the installer put on PATH.

This commit rewires installSdkIfNeeded() to build from the in-repo sdk/
source tree instead:

  1. cd sdk && npm install     (build-time deps incl. tsc)
  2. npm run build             (tsc → sdk/dist/)
  3. npm install -g .          (global install; gsd-sdk on PATH)

Each step is a hard gate — failures warn loudly and point users at the
manual equivalent command. No more silent drift between installed SDK and
the rest of the GSD system.

Root package.json `files` now ships sdk/src, sdk/prompts, sdk/package.json,
sdk/package-lock.json, and sdk/tsconfig.json so npm-registry installs also
carry the source tree needed to build gsd-sdk locally.

Also fixes a blocking tsc error in sdk/src/event-stream.ts:313 — the cast
to `Array<{ type: string; [key: string]: unknown }>` needed a double-cast
via `unknown` because BetaContentBlock's variants don't carry an index
signature. Runtime-neutral type-widening; sdk vitest suite unchanged
(1256 passing; the lone failure is a pre-existing integration test that
requires external API access).

Updates the #1657/#2385 regression test to assert the new build-from-source
path (path.resolve(__dirname, '..', 'sdk') + `npm run build` + `npm install
-g .`) plus a new assertion that root package.json files array ships sdk
source.

Refs #2385

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 19:53:16 -05:00
Jeremy McSpadden
b96255cf0c test(ingest-docs): add structural tests and CHANGELOG entry
- tests/ingest-docs.test.cjs — 40 structural assertions guarding the
  contract: command/workflow/agent/reference files exist; frontmatter
  shape; --mode/--manifest/--resolve/path parsing; path traversal
  guard; 50-doc cap; auto mode-detect via planning_exists; directory
  conventions for ADR/PRD/SPEC; parallel classifier + synthesizer
  spawns; BLOCKER/WARNING/INFO severity and the no-write safety gate;
  gsd-roadmapper routing; --resolve interactive reserved-for-future;
  INGEST-CONFLICTS.md writing. Classifier covers 5 types, JSON schema,
  Accepted-only locking. Synthesizer covers precedence ordering,
  LOCKED-vs-LOCKED block in both modes, three-bucket report, cycle
  detection, variant preservation, SYNTHESIS.md entry point. Plus a
  regression guard that /gsd-import still consumes the shared
  doc-conflict-engine reference (refactor drift check).

- CHANGELOG.md — Unreleased "Added" entry for /gsd-ingest-docs (#2387).

Full suite: 4151/4151 passing.

Refs #2387

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:12:34 -05:00
Jeremy McSpadden
bfdf3c3065 feat(ingest-docs): add /gsd-ingest-docs workflow and command
Orchestrator for the ingest pipeline (#2387):

- commands/gsd/ingest-docs.md — /gsd-ingest-docs command with
  [path] [--mode] [--manifest] [--resolve] args; @-references the
  shared doc-conflict-engine so the BLOCKER gate semantics are
  inherited from the same contract /gsd-import consumes.

- get-shit-done/workflows/ingest-docs.md — end-to-end flow:
    1. parse + validate args (traversal guard on path + manifest)
    2. init query + runtime detect + auto mode-detect (.planning/ presence)
    3. discover docs via directory convention OR manifest YAML
    4. 50-doc cap — forces --manifest for larger sets in v1
    5. discovery approval gate
    6. parallel spawn of gsd-doc-classifier per doc (fallback to
       sequential on non-Claude runtimes)
    7. single gsd-doc-synthesizer spawn
    8. conflict gate honoring doc-conflict-engine safety rule —
       BLOCKER count > 0 aborts without writing PROJECT/REQUIREMENTS/
       ROADMAP/STATE
    9. route to gsd-roadmapper (new) or append-to-milestone (merge),
       audits roadmapper's required PROJECT.md fields and only prompts
       for gaps
   10. commit via gsd-sdk

Updates ARCHITECTURE.md counts (commands 80→81, workflows 77→78,
agents tree-count 31→33).

--resolve interactive is reserved (explicit future-release reject).

Refs #2387

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:12:02 -05:00
Jeremy McSpadden
523a13f1e8 feat(agents): add gsd-doc-classifier and gsd-doc-synthesizer
Two new specialist agents for /gsd-ingest-docs (#2387):

- gsd-doc-classifier: reads one doc, writes JSON classification
  ({ADR|PRD|SPEC|DOC|UNKNOWN} + title + scope + cross-refs + locked).
  Heuristic-first, LLM on ambiguous. Designed for parallel fan-out per doc.

- gsd-doc-synthesizer: consumes all classifications + sources, applies
  precedence rules (ADR>SPEC>PRD>DOC, manifest-overridable), runs cycle
  detection on cross-ref graph, enforces LOCKED-vs-LOCKED hard-blocks
  in both modes, writes INGEST-CONFLICTS.md with three buckets
  (auto-resolved, competing-variants, unresolved-blockers) and
  per-type intel staging files for gsd-roadmapper.

Also updates docs/ARCHITECTURE.md total-agents count (31 → 33) and the
copilot-install expected agent list.

Refs #2387

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:12:02 -05:00
Jeremy McSpadden
0b90150ebf refactor(conflict-engine): extract shared doc-conflict-engine reference
Move the BLOCKER/WARNING/INFO conflict report format, severity semantics,
and safety-gate behavior from workflows/import.md into a new shared
reference file. /gsd-import consumes the reference; behavior is unchanged
(all 13 import-command tests + full 4091-test suite pass).

Prepares for /gsd-ingest-docs (#2387) which will consume the same contract
with its own domain-specific check list. Prevents drift between the two
implementations.

Refs #2387

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:12:02 -05:00
Jeremy McSpadden
819af761a0 fix(install): verify gsd-sdk resolves on PATH after npm install
`npm install -g` can succeed while the binary lands in a prefix that
isn't on the current shell's PATH (common with Homebrew, nvm, or an
unconfigured npm prefix). Re-probe via `which gsd-sdk` (or `where` on
Windows) after install; if it doesn't resolve, downgrade the success
message to a warning with a shell-restart hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 16:09:19 -05:00
Jeremy McSpadden
08b1d8377d fix(install): error on mutually exclusive --sdk and --no-sdk flags
Previously passing both silently had --no-sdk win. Exit non-zero with
a clear error to match how other exclusive flag pairs (--global/--local,
--config-dir/--local) are handled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 16:03:25 -05:00
Jeremy McSpadden
53b49dfe20 test: update #1657 regression guard for #2385 SDK install restoration
The guard was added when @gsd-build/sdk did not yet exist on npm. The
package is now published at v0.1.0 and every /gsd-* command depends
on the `gsd-sdk` binary. Invert the assertions: --sdk/--no-sdk must be
wired up and the installer must reference @gsd-build/sdk. Keep the
promptSdk() ban to prevent reintroducing the old broken prompt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:59:05 -05:00
Jeremy McSpadden
b2fcacda1b fix(install): auto-install @gsd-build/sdk so gsd-sdk is on PATH (#2385)
Every /gsd-* command shells out to `gsd-sdk query …`, but the SDK was
never installed by bin/install.js — the `--sdk` flag documented in
README was never implemented. Users upgrading to 1.36+ hit
"command not found: gsd-sdk" on every command.

- Implement SDK install in finishInstall's finalize path
- Default on; --no-sdk to skip; --sdk to force when already present
- Idempotent probe via `which gsd-sdk` before reinstalling
- Failures are warnings, not fatal — install hint printed

Closes #2385

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:56:35 -05:00
Tom Boucher
794f7e1b0b feat: /gsd-ultraplan-phase [BETA] — offload plan phase to Claude Code ultraplan (#2378)
* docs: add design spec for /gsd-ultraplan-phase beta command

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add /gsd-ultraplan-phase [BETA] command

Offloads GSD plan phase to Claude Code's ultraplan cloud infrastructure.
Plan drafts remotely while terminal stays free; browser UI for inline
comments and revisions; imports back via existing /gsd-import --from.

Intentionally isolated from /gsd-plan-phase so upstream ultraplan changes
cannot break the core planning pipeline.

Closes #2374

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve 5 pre-existing test failures before PR

- ARCHITECTURE.md: update command count 75→80 and workflow count 72→77
  (stale doc counts; also incremented by new ultraplan-phase files)
- sketch.md: add TEXT_MODE plain-text fallback for AskUserQuestion (#2012)
- read-guard.test.cjs: clear CLAUDECODE env var alongside CLAUDE_SESSION_ID
  so positive-path hook tests pass when run inside a Claude Code session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add BETA.md with /gsd-ultraplan-phase user documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address CodeRabbit review — MD040 fence labels and sketch.md TEXT_MODE duplicate

- Add language identifiers to all unlabeled fenced blocks in
  ultraplan-phase.md and design spec (resolves MD040)
- Remove duplicate TEXT_MODE explanation from sketch.md mood_intake step
  (was identical to the banner step definition)
- Make AskUserQuestion conditional explicit in mood_intake prose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 14:45:03 -04:00
Tom Boucher
2e97dee0d0 docs: update release notes and command reference for v1.37.0 (#2382)
* fix(tests): clear CLAUDECODE env var in read-guard test runner

The hook skips its advisory on two env vars: CLAUDE_SESSION_ID and
CLAUDECODE. runHook() cleared CLAUDE_SESSION_ID but inherited CLAUDECODE
from process.env, so tests run inside a Claude Code session silently
no-oped and produced no stdout, causing JSON.parse to throw.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): update ARCHITECTURE.md counts and add TEXT_MODE fallback to sketch workflow

Four new spike/sketch files were added in 1.37.0 but two housekeeping
items were missed: ARCHITECTURE.md component counts (75→79 commands,
72→76 workflows) and the required TEXT_MODE fallback in sketch.md for
non-Claude runtimes (#2012).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): update directory-tree slash command count in ARCHITECTURE.md

Missed the second count in the directory tree (# 75 slash commands → 79).
The prose "Total commands" was updated but the tree annotation was not,
causing command-count-sync.test.cjs to fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update release notes and command reference for v1.37.0

Covers spike/sketch commands, agent size-budget enforcement, and shared
boilerplate extraction across README, COMMANDS, FEATURES, and USER-GUIDE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 13:45:30 -04:00
Lex Christopherson
4cbe0b6d56 1.37.1 2026-04-17 10:38:47 -06:00
Lex Christopherson
d32e5bd461 docs: update changelog for v1.37.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 10:38:47 -06:00
Lex Christopherson
b13eb88ae2 fix: load sketch findings into ui-phase researcher
The UI researcher creates UI-SPEC.md but wasn't checking for
sketch-findings skills. Validated design decisions from /gsd-sketch
were being ignored, causing the researcher to re-ask questions
already answered during sketching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 10:38:47 -06:00
Tom Boucher
8798e68721 fix(ci): update ARCHITECTURE.md counts and add TEXT_MODE fallback to sketch workflow (#2377)
* fix(tests): clear CLAUDECODE env var in read-guard test runner

The hook skips its advisory on two env vars: CLAUDE_SESSION_ID and
CLAUDECODE. runHook() cleared CLAUDE_SESSION_ID but inherited CLAUDECODE
from process.env, so tests run inside a Claude Code session silently
no-oped and produced no stdout, causing JSON.parse to throw.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): update ARCHITECTURE.md counts and add TEXT_MODE fallback to sketch workflow

Four new spike/sketch files were added in 1.37.0 but two housekeeping
items were missed: ARCHITECTURE.md component counts (75→79 commands,
72→76 workflows) and the required TEXT_MODE fallback in sketch.md for
non-Claude runtimes (#2012).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): update directory-tree slash command count in ARCHITECTURE.md

Missed the second count in the directory tree (# 75 slash commands → 79).
The prose "Total commands" was updated but the tree annotation was not,
causing command-count-sync.test.cjs to fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:31:14 -04:00
Tom Boucher
71af170a08 fix(tests): clear CLAUDECODE env var in read-guard test runner (#2375)
The hook skips its advisory on two env vars: CLAUDE_SESSION_ID and
CLAUDECODE. runHook() cleared CLAUDE_SESSION_ID but inherited CLAUDECODE
from process.env, so tests run inside a Claude Code session silently
no-oped and produced no stdout, causing JSON.parse to throw.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:22:09 -04:00
Lex Christopherson
9e8257a3b1 1.37.0 2026-04-17 09:53:04 -06:00
Lex Christopherson
bbcec632b6 docs: update changelog for v1.37.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 09:52:59 -06:00
Lex Christopherson
9ef8f9ba2a docs: add spike/sketch commands to README command tables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 09:50:32 -06:00
Lex Christopherson
f983925eca feat: add /gsd-spike, /gsd-sketch, /gsd-spike-wrap-up, /gsd-sketch-wrap-up commands
First-class GSD commands for rapid feasibility spiking and UI design sketching,
ported from personal skills into the framework with full GSD integration:

- Spikes save to .planning/spikes/, sketches to .planning/sketches/
- GSD banners, checkpoint boxes, Next Up blocks, gsd-sdk query commits
- --quick flag skips intake/decomposition for both commands
- Wrap-up commands package findings into project-local .claude/skills/
  and write WRAP-UP-SUMMARY.md to .planning/ for project history
- Neither requires /gsd-new-project — auto-creates .planning/ subdirs

Pipeline integration:
- new-project.md detects prior spike/sketch work on init
- discuss-phase.md loads spike/sketch findings into prior context
- plan-phase.md includes findings in planner <files_to_read>
- do.md routes spike/sketch intent to new commands
- explore.md offers spike/sketch as output routes
- next.md surfaces pending spike/sketch work as notices
- pause-work.md detects active sketch context for handoff
- help.md documents all 4 commands with usage examples
- artifact-types.md registers spike/sketch artifact taxonomy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 09:47:15 -06:00
Tom Boucher
c5e77c8809 feat(agents): enforce size budget + extract duplicated boilerplate (#2361) (#2362)
Adds tiered agent-size-budget test to prevent unbounded growth in agent
definitions, which are loaded verbatim into context on every subagent
dispatch. Extracts two duplicated blocks (mandatory-initial-read,
project-skills-discovery) to shared references under
get-shit-done/references/ and migrates the 5 top agents (planner,
executor, debugger, verifier, phase-researcher) to @file includes.

Also fixes two broken relative @planner-source-audit.md references in
gsd-planner.md that silently disabled the planner's source audit
discipline.

Closes #2361

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 10:47:08 -04:00
Tom Boucher
4a912e2e45 feat(debugger): extract philosophy block to shared reference (#2363) (#2364)
The gsd-debugger philosophy block contains 76 lines of evergreen
debugging disciplines (user-as-reporter, meta-debugging, cognitive
biases, restart protocol) that are not debugger-specific workflow
and are paid in context on every debugger dispatch.

Extracts to get-shit-done/references/debugger-philosophy.md, replaces
the inline block with a single @file include. Behavior-preserving.

Closes #2363

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 10:23:18 -04:00
Tom Boucher
c2158b9690 docs(contributing): clarify agents/ source of truth vs install-sync targets (#2365) (#2366)
Documents that only agents/ at the repo root is tracked by git.
.claude/agents/, .cursor/agents/, and .github/agents/ are gitignored
install-sync outputs and must not be edited — they will be overwritten.

Closes #2365

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 10:15:47 -04:00
Tom Boucher
3589f7b256 fix(worktrees): prune orphaned worktrees in code, not prose (#2367)
* feat: add /gsd-spec-phase — Socratic spec refinement with ambiguity scoring (#2213)

Introduces `/gsd-spec-phase <phase>` as an optional pre-step before discuss-phase.
Clarifies WHAT a phase delivers (requirements, boundaries, acceptance criteria) with
quantitative ambiguity scoring before discuss-phase handles HOW to implement.

- `commands/gsd/spec-phase.md` — slash command routing to workflow
- `get-shit-done/workflows/spec-phase.md` — full Socratic interview loop (up to 6
  rounds, 5 rotating perspectives: Researcher, Simplifier, Boundary Keeper, Failure
  Analyst, Seed Closer) with weighted 4-dimension ambiguity gate (≤ 0.20 to write SPEC.md)
- `get-shit-done/templates/spec.md` — SPEC.md template with falsifiable requirements
  (Current/Target/Acceptance per requirement), Boundaries, Acceptance Criteria,
  Ambiguity Report, and Interview Log; includes two full worked examples
- `get-shit-done/workflows/discuss-phase.md` — new `check_spec` step detects
  `{padded_phase}-SPEC.md` at startup; displays "Found SPEC.md — N requirements
  locked. Focusing on implementation decisions."; `analyze_phase` respects `spec_loaded`
  flag to skip "what/why" gray areas; `write_context` emits `<spec_lock>` section
  with boundary summary and canonical ref to SPEC.md
- `docs/ARCHITECTURE.md` — update command/workflow counts (74→75, 71→72)

Closes #2213

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(worktrees): auto-prune merged worktrees in code, not prose

Adds pruneOrphanedWorktrees(repoRoot) to core.cjs. It runs on every
cmdInitProgress call (the entry point for most GSD commands) and removes
linked worktrees whose branch is fully merged into main, then runs
git worktree prune to clear stale references. Guards prevent removal of
the main worktree, the current process.cwd(), or any unmerged branch.

Covered by 4 new real-git integration tests in
tests/prune-orphaned-worktrees.test.cjs (TDD red→green).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:11:08 -04:00
Tom Boucher
d7b613d147 fix(hooks): check CLAUDECODE env var in read-guard skip (#2344) (#2352)
Closes #2344

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:30:20 -04:00
Tom Boucher
f8448a337b fix(quick): add gsd-sdk pre-flight check with install hint (#2334) (#2354)
Closes #2334

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:29:59 -04:00
Tom Boucher
d8b851346e fix(agents): add no-re-read critical rules to ui-checker and planner (#2346) (#2355)
* fix(agents): add no-re-read critical rules to ui-checker and planner (#2346)

Closes #2346

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(agents): correct contradictory heredoc rule in read-only ui-checker

The critical_rules block instructed the agent to "use the Write tool"
for any output, but gsd-ui-checker has no Write tool and is explicitly
read-only. Replaced with a simple no-file-creation rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(planner): trim verbose prose to satisfy 46KB size constraint

Condenses documentation_lookup, philosophy, project_context, and
context_fidelity sections — removing redundant examples while
preserving all semantic content. Fixes CI failure on planner
decomposition size test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:26:49 -04:00
Tom Boucher
fb7856f9d2 fix(intel): detect .kilo runtime layout for canonical scope resolution (#2351) (#2356)
Under a .kilo install the runtime root is .kilo/ and the command
directory is command/ (not commands/gsd/). Hardcoded paths produced
semantically empty intel files. Add runtime layout detection and a
mapping table so paths are resolved against the correct root.

Closes #2351

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:20:42 -04:00
Tom Boucher
6deef7e7ed feat(init): allow parallel discuss across independent phases (#2268) (#2357)
The sliding-window pattern serialized discuss to one phase at a time
even when phases had no dependency relationship. Replaced it with a
simple predicate: every undiscussed phase whose dependencies are
satisfied is marked is_next_to_discuss, letting the user pick any of
them from the manager's recommended_actions list.

Closes #2268

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:20:26 -04:00
Tom Boucher
06c528be44 fix(new-project): display saved defaults before prompting to use them (#2333)
* fix(new-project): display saved defaults before prompting to use them

Replaces the blind Yes/No "Use saved defaults?" gate with a flow that
reads ~/.gsd/defaults.json first, displays all values in human-readable
form, then offers three options: use as-is, modify some settings, or
configure fresh.

The "modify some settings" path presents a multiSelect of only the
setting names (with current values shown), asks questions only for the
selected ones, and merges answers over the saved defaults — avoiding a
full re-walk when the user just wants to change one or two things.

Closes #2332

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(new-project): address CodeRabbit review comments

- Use canonical setting names (Research, Plan Check, Verifier) instead of
  "agent" suffix variants, matching Round 2 headers for clean mapping
- Add `text` language tag to fenced display blocks (MD040)
- Add TEXT_MODE fallback for multiSelect in "Modify some settings" path
  so non-Claude runtimes (Codex, Gemini) can use numbered list input

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 20:50:31 -04:00
Tom Boucher
c35997fb0b feat(hooks): add gsd-read-injection-scanner PostToolUse hook (#2201) (#2328)
* feat: add /gsd-spec-phase — Socratic spec refinement with ambiguity scoring (#2213)

Introduces `/gsd-spec-phase <phase>` as an optional pre-step before discuss-phase.
Clarifies WHAT a phase delivers (requirements, boundaries, acceptance criteria) with
quantitative ambiguity scoring before discuss-phase handles HOW to implement.

- `commands/gsd/spec-phase.md` — slash command routing to workflow
- `get-shit-done/workflows/spec-phase.md` — full Socratic interview loop (up to 6
  rounds, 5 rotating perspectives: Researcher, Simplifier, Boundary Keeper, Failure
  Analyst, Seed Closer) with weighted 4-dimension ambiguity gate (≤ 0.20 to write SPEC.md)
- `get-shit-done/templates/spec.md` — SPEC.md template with falsifiable requirements
  (Current/Target/Acceptance per requirement), Boundaries, Acceptance Criteria,
  Ambiguity Report, and Interview Log; includes two full worked examples
- `get-shit-done/workflows/discuss-phase.md` — new `check_spec` step detects
  `{padded_phase}-SPEC.md` at startup; displays "Found SPEC.md — N requirements
  locked. Focusing on implementation decisions."; `analyze_phase` respects `spec_loaded`
  flag to skip "what/why" gray areas; `write_context` emits `<spec_lock>` section
  with boundary summary and canonical ref to SPEC.md
- `docs/ARCHITECTURE.md` — update command/workflow counts (74→75, 71→72)

Closes #2213

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(hooks): add gsd-read-injection-scanner PostToolUse hook (#2201)

Adds a new PostToolUse hook that scans content returned by the Read tool
for prompt injection patterns, including four summarisation-specific patterns
(retention-directive, permanence-claim, etc.) that survive context compression.

Defense-in-depth for long GSD sessions where the context summariser cannot
distinguish user instructions from content read from external files.

- Advisory-only (warns without blocking), consistent with gsd-prompt-guard.js
- LOW severity for 1-2 patterns, HIGH for 3+
- Inlined pattern library (hook independence)
- Exclusion list: .planning/, REVIEW.md, CHECKPOINT, security docs, hook sources
- Wired in install.js as PostToolUse matcher: Read, timeout: 5s
- Added to MANAGED_HOOKS for staleness detection
- 19 tests covering all 13 acceptance criteria (SCAN-01–07, EXCL-01–06, EDGE-01–06)

Closes #2201

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add read-injection-scanner files to prompt-injection-scan allowlist

Test payloads in tests/read-injection-scanner.test.cjs and inlined patterns
in hooks/gsd-read-injection-scanner.js legitimately contain injection strings.
Add both to the CI script allowlist to prevent false-positive failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): assert exitCode, stdout, and signal explicitly in EDGE-05

Addresses CodeRabbit feedback: the success path discarded the return
value so a malformed-JSON input that produced stdout would still pass.
Now captures and asserts all three observable properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:22:31 -04:00
Tom Boucher
2acb38c918 fix(pattern-mapper): prevent redundant file reads and add early-stop rule (#2312) (#2327)
* feat: add /gsd-spec-phase — Socratic spec refinement with ambiguity scoring (#2213)

Introduces `/gsd-spec-phase <phase>` as an optional pre-step before discuss-phase.
Clarifies WHAT a phase delivers (requirements, boundaries, acceptance criteria) with
quantitative ambiguity scoring before discuss-phase handles HOW to implement.

- `commands/gsd/spec-phase.md` — slash command routing to workflow
- `get-shit-done/workflows/spec-phase.md` — full Socratic interview loop (up to 6
  rounds, 5 rotating perspectives: Researcher, Simplifier, Boundary Keeper, Failure
  Analyst, Seed Closer) with weighted 4-dimension ambiguity gate (≤ 0.20 to write SPEC.md)
- `get-shit-done/templates/spec.md` — SPEC.md template with falsifiable requirements
  (Current/Target/Acceptance per requirement), Boundaries, Acceptance Criteria,
  Ambiguity Report, and Interview Log; includes two full worked examples
- `get-shit-done/workflows/discuss-phase.md` — new `check_spec` step detects
  `{padded_phase}-SPEC.md` at startup; displays "Found SPEC.md — N requirements
  locked. Focusing on implementation decisions."; `analyze_phase` respects `spec_loaded`
  flag to skip "what/why" gray areas; `write_context` emits `<spec_lock>` section
  with boundary summary and canonical ref to SPEC.md
- `docs/ARCHITECTURE.md` — update command/workflow counts (74→75, 71→72)

Closes #2213

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(pattern-mapper): prevent redundant file reads and add early-stop rule (#2312)

Adds three explicit constraints to the agent prompt:
1. Read each analog file EXACTLY ONCE (no re-reads from context)
2. For files > 2,000 lines, use Grep + Read with offset/limit instead of full load
3. Stop analog search after 3–5 strong matches

Also adds <critical_rules> block to surface these constraints at high salience.
Adds regression tests READS-01, READS-02, READS-03.

Closes #2312

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(pattern-mapper): clarify re-read rule allows non-overlapping targeted reads (CR feedback)

"Read each file EXACTLY ONCE" conflicted with the large-file targeted-read
strategy. Rewrites both the Step 4 guidance and the <critical_rules> block to
make the rule precise: re-reading the same range is forbidden; multiple
non-overlapping targeted reads for large files are permitted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:15:29 -04:00
Tom Boucher
0da696eb6c fix(install): replace all ~/.claude/ paths in Codex .toml files (#2320) (#2325)
* fix(install): replace all ~/.claude/ paths in generated Codex .toml files (#2320)

installCodexConfig() only rewrote get-shit-done/-scoped paths; all other
~/.claude/ references (hooks, skills, configDir) leaked into generated .toml
files unchanged. Add three additional regex replacements to catch $HOME/.claude/,
~/.claude/, and ./.claude/ patterns and rewrite them to .codex equivalents.

Adds regression test PATHS-01.

Closes #2320

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(install): handle bare .claude end-of-string and scan all .toml files (CR feedback)

- Use capture group (\/|$) so replacements handle both ~/.claude/ and bare
  ~/.claude at end of string, not just the trailing-slash form
- Expand PATHS-01 test to scan agents/*.toml + top-level config.toml
- Broaden leak pattern to match ./.claude, ~, and $HOME variants with or
  without trailing slash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:13:44 -04:00
Tom Boucher
dd8b24a16e fix(quick): rescue uncommitted SUMMARY.md before worktree removal (#2296) (#2326)
Mirrors the safety net from execute-phase.md (#2070): checks for any
uncommitted SUMMARY.md files in the executor worktree before force-removing it,
commits them to the branch, then merges the branch to preserve the data.

Closes #2296

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:11:30 -04:00
Tom Boucher
77a7fbd6be fix(graphify): fall back to graph.links when graph.edges is absent (#2323)
Closes #2301

## Root cause
graphify's JSON output uses the key `links` for edges, but graphify.cjs
reads `graph.edges` at four sites (buildAdjacencyMap, status edge_count,
diff currentEdgeMap/snapshotEdgeMap, snapshot writer). Any graph produced
by graphify itself therefore reported edge_count: 0 and adjacency maps
with no entries.

## Fix
Added `|| graph.links` fallback at all four read sites so both key names
are accepted. The snapshot writer now also normalises to `edges` when
saving, ensuring round-trips through the snapshot path use a consistent key.

## Test
Added LINKS-01/02/03 regression tests covering buildAdjacencyMap,
graphifyStatus edge_count, and graphifyDiff edge change detection with
links-keyed input graphs.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:08:44 -04:00
Tom Boucher
2df700eb81 feat: add /gsd-spec-phase — Socratic spec refinement with ambiguity scoring (#2213) (#2322)
Introduces `/gsd-spec-phase <phase>` as an optional pre-step before discuss-phase.
Clarifies WHAT a phase delivers (requirements, boundaries, acceptance criteria) with
quantitative ambiguity scoring before discuss-phase handles HOW to implement.

- `commands/gsd/spec-phase.md` — slash command routing to workflow
- `get-shit-done/workflows/spec-phase.md` — full Socratic interview loop (up to 6
  rounds, 5 rotating perspectives: Researcher, Simplifier, Boundary Keeper, Failure
  Analyst, Seed Closer) with weighted 4-dimension ambiguity gate (≤ 0.20 to write SPEC.md)
- `get-shit-done/templates/spec.md` — SPEC.md template with falsifiable requirements
  (Current/Target/Acceptance per requirement), Boundaries, Acceptance Criteria,
  Ambiguity Report, and Interview Log; includes two full worked examples
- `get-shit-done/workflows/discuss-phase.md` — new `check_spec` step detects
  `{padded_phase}-SPEC.md` at startup; displays "Found SPEC.md — N requirements
  locked. Focusing on implementation decisions."; `analyze_phase` respects `spec_loaded`
  flag to skip "what/why" gray areas; `write_context` emits `<spec_lock>` section
  with boundary summary and canonical ref to SPEC.md
- `docs/ARCHITECTURE.md` — update command/workflow counts (74→75, 71→72)

Closes #2213

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:08:30 -04:00
Devin
f101a5025e fix(map-codebase): pass current date to mapper agents to fix wrong Analysis Date (#2298)
The `cmdInitMapCodebase` / `initMapCodebase` init handlers did not
include `date` or `timestamp` fields in their JSON output, unlike
`init quick` and `init todo` which both provide them.

Because the mapper agents had no reliable date source, they were forced
to guess the date from model training data, producing incorrect
Analysis Date values (e.g. 2025-07-15 instead of the actual date) in
all seven `.planning/codebase/*.md` documents.

Changes:
- Add `date` and `timestamp` to `cmdInitMapCodebase` (init.cjs) and
  `initMapCodebase` (init.ts)
- Pass `{date}` into each mapper agent prompt via the workflow
- Update agent definition to use the prompt-provided date instead of
  guessing
- Cover sequential_mapping fallback path as well
2026-04-16 17:08:13 -04:00
Tom Boucher
53078d3f85 fix: scale context meter to usable window respecting CLAUDE_CODE_AUTO_COMPACT_WINDOW (#2219)
The autocompact buffer percentage was hardcoded to 16.5%. Users who set
CLAUDE_CODE_AUTO_COMPACT_WINDOW to a custom token count (e.g. 400000 on
a 1M-context model) saw a miscalibrated context meter and incorrect
warning thresholds in the context-monitor hook (which reads used_pct from
the bridge file the statusline writes).

Now reads CLAUDE_CODE_AUTO_COMPACT_WINDOW from the hook env and computes:
  buffer_pct = acw_tokens / total_tokens * 100
Defaults to 16.5% when the var is absent or zero, preserving existing
behavior.

Also applies the renameDecimalPhases zero-padding fix for clean CI.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:40:15 -04:00
Tom Boucher
712e381f13 docs: document required Bash permission patterns for executor subagents (#2071) (#2288)
* docs: document required Bash permission patterns for gsd-executor subagents (Closes #2071)

Adds a new "Executor Subagent Gets Permission denied on Bash Commands"
section to USER-GUIDE.md Troubleshooting. Documents the wildcard Bash
patterns that must be added to ~/.claude/settings.json (or per-project
.claude/settings.local.json) for each supported stack so fresh installs
aren't blocked mid-execution.

Covers: git write commands, gh, Rails/Ruby, Python/uv, Node/npm/pnpm/bun,
and Rust/Cargo. Includes a complete example settings.json snippet for Rails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(phase): preserve zero-padded prefix in renameDecimalPhases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 22:47:12 -04:00
Tom Boucher
09e471188d fix(uat): accept bracketed result values and fix decimal phase renumber padding (#2283)
- uat.cjs: change result capture from \w+ to \[?(\w+)\]? so result: [pending],
  [blocked], [skipped] are parsed correctly (Closes #2273)
- phase.cjs: capture zero-padded prefix in renameDecimalPhases so renamed dirs
  preserve original format (e.g. 06.3-slug → 06.2-slug, not 6.2-slug)
- tests/uat.test.cjs: add regression test for bracketed result values (#2273)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 22:46:57 -04:00
Rezolv
d3a79917fa feat: Phase 2 caller migration — gsd-sdk query in workflows, agents, commands (#2179)
* feat: Phase 2 caller migration — gsd-sdk query in workflows (#2122)

Cherry-picked orchestration rewrites from feat/sdk-foundation (#2008, 4018fee) onto current main, resolving conflicts to keep upstream worktree guards and post-merge test gate. SDK stub registry omitted (out of Phase 2 scope per #2122).

Refs: #2122 #2008
Made-with: Cursor

* docs: add gsd-sdk query migration blurb

Made-with: Cursor

* docs(workflows): extend Phase 2 gsd-sdk query caller migration

- Swap node gsd-tools.cjs for gsd-sdk query in review, plan-phase, execute-plan,
  ship, extract_learnings, ai-integration-phase, eval-review, next, thread
- Document graphify CJS-only in gsd-planner; dual-path in CLI-TOOLS and ARCHITECTURE
- Update tests: workstreams gsd-sdk path, thread frontmatter.get, workspace init.*,
  CRLF-safe autonomous frontmatter parse
- CHANGELOG: Phase 2 caller migration scope

Made-with: Cursor

* docs(phase2): USER-GUIDE + remaining gsd-sdk query call sites

- USER-GUIDE: dual-path CLI section; state validate/sync use full CJS path
- Commands: debug (config-get+tdd), quick (security note), intel Task prompt
- Agent: gsd-debug-session-manager resolve-model via jq
- Workflows: milestone-summary, forensics, next, complete-milestone/verify-work
  (audit-open CJS notes), discuss-phase, progress, verify-phase, add/insert/remove
  phase, transition, manager, quick workflow; remove-phase commit without --files
- Test: quick-session-management accepts frontmatter.get
- CHANGELOG: Phase 2 follow-up bullet

Made-with: Cursor

* docs(phase2): align gsd-sdk query examples in commands and agents

- init.* query names; frontmatter.get uses positional field name
- state.* handlers use positional args; commit uses positional paths
- CJS-only notes for from-gsd2 and graphify; learnings.query wording
- CHANGELOG: Phase 2 orchestration doc pass

Made-with: Cursor

* docs(phase2): normalize gsd-sdk query commit to positional file paths

- Strip --files from commit examples in workflows, references, commands
- Keep commit-to-subrepo ... --files (separate handler)
- git-planning-commit.md: document positional args
- Tests: new-project commit line, state.record-session, gates CRLF, roadmap.analyze
- CHANGELOG [Unreleased]

Made-with: Cursor

* feat(sdk): gsd-sdk query parity with gsd-tools and PR 2179 registry fixes

- Route query via longest-prefix match and dotted single-token expansion; fall back
  to runGsdToolsQuery (same argv as node gsd-tools.cjs) for full CLI coverage.
- Parse gsd-sdk query permissively so gsd-tools flags (--json, --verify, etc.) are
  not rejected by strict parseArgs.
- resolveGsdToolsPath: honor GSD_TOOLS_PATH; prefer bundled get-shit-done copy
  over project .claude installs; export runGsdToolsQuery from the SDK.
- Fix gsd-tools audit-open (core.output; pass object for --json JSON).
- Register summary-extract as alias of summary.extract; fix audit-fix workflow to
  call audit-uat instead of invalid init.audit-uat (PR review).

Updates QUERY-HANDLERS.md and CHANGELOG [Unreleased].

Made-with: Cursor

* fix(sdk): Phase 2 scope — Trek-e review (#2179, #2122)

- Remove gsd-sdk query passthrough to gsd-tools.cjs; drop GSD_TOOLS_PATH
- Consolidate argv routing in resolveQueryArgv(); update USAGE and QUERY-HANDLERS
- Surface @file: read failures in GSDTools.parseOutput
- execute-plan: defer Task Commit Protocol to gsd-executor
- stale-colon-refs: skip .planning/ and root CLAUDE.md (gitignored overlays)
- CHANGELOG [Unreleased]: maintainer review and routing notes

Made-with: Cursor
2026-04-15 22:46:31 -04:00
Tom Boucher
762b8ed25b fix(add-backlog): write ROADMAP entry before directory creation to prevent false duplicate detection (#2286)
Swaps steps 3 and 4 in add-backlog.md so ROADMAP.md is updated before
the phase directory is created. Directory existence is now a reliable
indicator that a phase is already registered, preventing false duplicate
detection in hooks that check for existing 999.x directories (Closes #2280).

Also fixes renameDecimalPhases to preserve zero-padded directory prefixes
(e.g. "06.3-slug" → "06.2-slug" instead of "6.2-slug").

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:46:13 -04:00
Tom Boucher
5f521e0867 fix(settings): route /gsd-settings reads/writes through workstream-aware config path (#2285)
settings.md was reading and writing .planning/config.json directly while
gsd-tools config-get/config-set route to .planning/workstreams/<slug>/config.json
when GSD_WORKSTREAM is active, causing silent write-read drift (Closes #2282).

- config.cjs: add cmdConfigPath() — emits the planningDir-resolved config path as
  plain text (always raw, no JSON wrapping) so shell substitution works correctly
- gsd-tools.cjs: wire config-path subcommand
- settings.md: resolve GSD_CONFIG_PATH via config-path in ensure_and_load_config;
  replace hardcoded cat .planning/config.json and Write to .planning/config.json
  with $GSD_CONFIG_PATH throughout
- phase.cjs: fix renameDecimalPhases to preserve zero-padded prefix (06.3 → 06.2
  not 6.2) — pre-existing test failure on main
- tests/config.test.cjs: add config-path command tests (#2282)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:46:10 -04:00
Tom Boucher
55877d372f feat(handoffs): include project identity in all Next Up blocks (#1948) (#2287)
* feat(handoffs): include project identity in all Next Up blocks

Adds project_code and project_title to withProjectRoot() and updates
all 30 Next Up headings across 18 workflow files to include
[PROJECT_CODE] PROJECT_TITLE suffix for multi-project clarity.

Closes #1948

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(review): add withProjectRoot tests and fix placeholder syntax (#1951)

Address code review feedback:
- Add 4 tests for project_code/project_title injection in withProjectRoot()
- Fix inconsistent placeholder syntax in continuation-format.md canonical
  template (bare-brace → shell-dollar to match variant examples)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(phase): preserve zero-padded prefix in renameDecimalPhases

Captures the zero-padded prefix (e.g. "06" from "06.3-slug") with
(0*${baseInt}) so renamed directories keep their original format
(06.2-slug) instead of stripping padding (6.2-slug).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Higgins <brandonscotthiggins@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:43:02 -04:00
Tom Boucher
779bd1a383 feat(progress): add --forensic flag for 6-check integrity audit after standard report (#2231)
Extends /gsd-progress with opt-in --forensic mode that appends a
6-check integrity audit after the standard routing report. Default
behavior is byte-for-byte unchanged — the audit only runs when
--forensic is explicitly passed.

Checks: (1) STATE vs artifact consistency, (2) orphaned handoff files,
(3) deferred scope drift, (4) memory-flagged pending work, (5) blocking
operational todos, (6) uncommitted source code. Emits CLEAN or
N INTEGRITY ISSUE(S) FOUND verdict with concrete next actions.

Closes #2189

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:23:18 -04:00
Tom Boucher
509a431438 feat(discuss-phase): add --all flag to skip area selection and discuss everything (#2230)
Adds --all to /gsd-discuss-phase so users can skip the AskUserQuestion
area-selection step and jump straight into discussing all gray areas
interactively. Unlike --auto, --all does NOT auto-advance to plan-phase —
it only eliminates the selection friction while keeping full interactive
control over each discussion.

Closes #2188

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:23:09 -04:00
Tom Boucher
a13c4cee3e fix(quick): normalize --discuss --research --validate combo to FULL_MODE (#2274)
After #1518 redefined --full as all three granular flags combined, passing
--discuss --research --validate individually bypassed $FULL_MODE and showed
a "DISCUSS + RESEARCH + VALIDATE" banner instead of "FULL".

Fix: add a normalization step in flag parsing — if all three granular flags
are set, promote to $FULL_MODE=true. Remove the now-unreachable banner case.

Closes #2181

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:00:47 -04:00
Tom Boucher
6ef3255f78 fix: normalize Windows paths in update scope detection (#2278)
* docs: sync ARCHITECTURE.md command count to 74

commands/gsd/ has 74 .md files; the two count references in
ARCHITECTURE.md still said 73. Fixes the command-count-sync
regression test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: normalize Windows paths in update scope detection (#2232)

On Windows with Git Bash, `pwd` returns POSIX-style /c/Users/... paths
while execution_context carries Windows-style C:/Users/... paths. The
string equality check for LOCAL vs GLOBAL install scope never matched,
so every local install on Windows was misdetected as GLOBAL and the
wrong (global) install was updated.

Fix: normalize both paths to POSIX drive-letter form before comparing,
using portable POSIX shell (case+printf+tr, no GNU extensions).

Closes #2232

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(commands): add gsd:inbox command for GitHub issue/PR triage

inbox.md was created but not committed, causing the command count
to read 73 in git while ARCHITECTURE.md correctly stated 74.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:00:26 -04:00
Tom Boucher
ef5b0c187f docs: sync ARCHITECTURE.md command count to 74 (#2270)
* docs: sync ARCHITECTURE.md command count to 74

commands/gsd/ has 74 .md files; the two count references in
ARCHITECTURE.md still said 73. Fixes the command-count-sync
regression test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(commands): add gsd:inbox command for GitHub issue/PR triage

inbox.md was created but not committed, causing the command count
to read 73 in git while ARCHITECTURE.md correctly stated 74.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:00:08 -04:00
Tom Boucher
262b395879 fix: embed model_overrides in Codex TOML and OpenCode agent files (#2279)
* docs: sync ARCHITECTURE.md command count to 74

commands/gsd/ has 74 .md files; the two count references in
ARCHITECTURE.md still said 73. Fixes the command-count-sync
regression test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: embed model_overrides in Codex TOML and OpenCode agent files (#2256)

Codex and OpenCode use static agent files (TOML / markdown frontmatter)
rather than inline Task(model=...) parameters, so model_overrides set in
~/.gsd/defaults.json was silently ignored — all subagents fell through to
the runtime's default model.

Fix: at install time, read model_overrides from ~/.gsd/defaults.json and
embed the matching model ID into each agent file:
  - Codex: model = "..." field in the agent TOML (generateCodexAgentToml)
  - OpenCode: model: ... field in agent frontmatter (convertClaudeToOpencodeFrontmatter)

Also adds readGsdGlobalModelOverrides() helper and passes the result
through installCodexConfig() and the OpenCode agent install loop.

Closes #2256

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(commands): add gsd:inbox command for GitHub issue/PR triage

inbox.md was created but not committed, causing the command count
to read 73 in git while ARCHITECTURE.md correctly stated 74.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:56 -04:00
Cocoon-Break
d9a4e5bf40 fix: parse baseInt to integer in renameDecimalPhases call to fix regex mismatch (Closes #2197) (#2198) 2026-04-15 14:59:38 -04:00
Tom Boucher
7b0a8b6237 fix: normalize phase numbers in stats Map to prevent duplicate rows (#2220)
When ROADMAP.md uses unpadded phase numbers (e.g. "Phase 1:") and
the phases/ directory uses zero-padded names (e.g. "01-auth"), the
phasesByNumber Map held two separate entries — one keyed "1" from the
ROADMAP heading scan and one keyed "01" from the directory scan —
doubling phases_total in /gsd-stats output.

Apply normalizePhaseName() to all Map keys in both the ROADMAP heading
scan and the directory scan so the two code paths always produce the
same canonical key and merge into a single entry.

Closes #2195

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:35 -04:00
Tom Boucher
899419ebec fix: pipe review prompts via stdin to prevent shell expansion (#2222)
When prompt files contain shell metacharacters (\$VAR, backticks,
\$(...)), passing them as -p "\$(cat file)" causes the shell to expand
those sequences before the CLI tool ever receives the text. This
silently corrupts prompts built from user-authored PLAN.md content.

Replace all -p "\$(cat /tmp/gsd-review-prompt-{phase}.md)" patterns
with cat file | cli -p - so the prompt bytes are passed verbatim via
stdin. Affected CLIs: gemini, claude, codex, qwen. OpenCode and cursor
already used the pipe-to-stdin pattern.

Closes #2200

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:31 -04:00
Tom Boucher
1005f02db2 fix(sdk): stop duplicating prompt in runPhaseStepSession user message (#2223)
runPhaseStepSession was passing the full prompt string as both the
user-visible prompt: argument and as systemPrompt.append, sending
the same (potentially large) text twice per invocation and doubling
the token cost for every phase step session.

runPlanSession correctly uses a short directive as the user message
and reserves the full content for systemPrompt.append only. Apply
the same pattern to runPhaseStepSession: use a brief
"Execute this phase step: <step>" directive as the user message.

Closes #2194

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:27 -04:00
Tom Boucher
4f5ffccec7 fix: align INTEL_FILES constant with actual agent output filenames (#2225)
The gsd-intel-updater agent writes file-roles.json, api-map.json,
dependency-graph.json, arch-decisions.json, and stack.json. But
INTEL_FILES in intel.cjs declared files.json, apis.json, deps.json,
arch.md, and stack.json. Only stack.json matched. Every query/status/
diff/validate call iterated INTEL_FILES and found nothing, reporting
all intel files as missing even after a successful refresh.

Update INTEL_FILES to use the agent's actual filenames. Remove the
arch.md special-case code paths (mtime-based staleness, text search,
.md skip in validate) since arch-decisions.json is JSON like the rest.
Update all intel tests to use the new canonical filenames.

Closes #2205

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:24 -04:00
Tom Boucher
62261a3166 fix: add --portable-hooks flag for WSL/Docker $HOME-relative settings.json paths (#2226)
Absolute hook paths in settings.json break when ~/.claude is bind-mounted
into a container at a different path, or when running under WSL with a
Windows Node.js that resolves a different home directory.

Add `--portable-hooks` CLI flag and `GSD_PORTABLE_HOOKS=1` env var opt-in.
When set, buildHookCommand() emits `$HOME`-relative paths instead of resolved
absolute paths, making the generated hook commands portable across bind mounts.

Fixes #2190

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:21 -04:00
Tom Boucher
8f1dd94495 fix: extract smart_discuss step to reference to reduce autonomous.md token count (#2227)
autonomous.md reported 11,748 tokens (over the Claude Code Read tool's 10K
limit), causing it to be read in 150-line chunks and generating a warning
on every /gsd-autonomous invocation.

Extract the 280-line smart_discuss step into a new reference file
(get-shit-done/references/autonomous-smart-discuss.md) and replace the
step body with a lean stub that directs the agent to read the reference.
This follows the established planner decomposition pattern.

autonomous.md: 38,750 → 29,411 chars (~7,350 tokens, well under 10K limit)

Fixes #2196

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:18 -04:00
Tom Boucher
875b257c18 fix(init): embed auto_advance/auto_chain_active/mode in init plan-phase output (#2228)
Prevents infinite config-get loops on Kimi K2.5 and other models that
re-execute bash tool calls when they encounter config-get subshell patterns.
Values are now bundled into the init plan-phase JSON so step 15 of
plan-phase.md can read them directly without separate shell calls.

Closes #2192

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:15 -04:00
Tom Boucher
7b85d9e689 fix(cli): audit-open crashes with ReferenceError: output is not defined (#2236) (#2238)
The audit-open case in gsd-tools.cjs called bare output() on both the --json
and text paths. output is never in scope at the call site — the entire core
module is imported as `const core`, so every other command uses core.output().

Two-part fix:
- Replace output(...) with core.output(...) on both branches
- Pass result (the raw object) on the --json path, not JSON.stringify(result)
  — core.output always calls JSON.stringify internally, so pre-serialising
  caused double-encoding and agents received a string instead of an object

Adds three CLI-level regression tests to milestone-audit.test.cjs that invoke
audit-open through runGsdTools (the same path the agent uses), so a recurrence
at the dispatch layer is caught even if lib-level tests continue to pass.

Closes #2236

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:12 -04:00
Tom Boucher
fa02cd2279 docs(references): use \$GSD_TOOLS variable in workstream-flag.md CLI examples (#2245) (#2255)
Replace bare `node gsd-tools.cjs` invocations with `node "\$GSD_TOOLS"` throughout
the CLI Usage section, and add a comment explaining that \$GSD_TOOLS resolves to the
full installed bin path (global or local). Bare relative paths only work from the
install directory and silently fail when run from a project root.

Closes #2245

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:59:09 -04:00
Tom Boucher
2f28c99db4 fix(init): include shipped-milestone phases in deps_satisfied check (#2269)
- Build completedNums from current milestone phases as before
- Also scan full rawContent for [x]-checked Phase lines across all
  milestone sections (including <details>-wrapped shipped milestones)
- Phases from prior milestones are complete by definition, so any
  dep on them should always resolve to deps_satisfied: true
- Add regression tests in tests/init-manager-deps.test.cjs

Closes #2267
2026-04-15 14:59:07 -04:00
Tom Boucher
e1fe12322c fix(worktree): add pre-merge deletion guard to quick.md; fix backup handling on conflict (#2275)
Three gaps in the orchestrator file-protection block (#1756, #2040):

1. quick.md never received the pre-merge deletion guard added to
   execute-phase.md in #2040. Added the same DELETIONS check: if the
   worktree branch deletes any tracked .planning/ files, block the merge
   with a clear message rather than silently losing those files.

2. Both workflows deleted STATE_BACKUP and ROADMAP_BACKUP on merge
   conflict — destroying the recovery files at exactly the moment they
   were needed. Changed conflict handler to: preserve both backup paths,
   print restore instructions, and break (halt) instead of continue
   (silently advancing to the next worktree).

3. Neither workflow used --no-ff. Without it a fast-forward merge
   produces no merge commit, so HEAD~1 in the resurrection check points
   to the worktree's parent rather than main's pre-merge HEAD. Added
   --no-ff to both git merge calls so HEAD~1 is always reliable.

Closes #2208

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:58:43 -04:00
Tom Boucher
32ab8ac77e fix: skip statusLine in repo settings.json on local install (#2248) (#2277)
Local installs write to .claude/settings.json inside the project, which
takes precedence over the user's global ~/.claude/settings.json. Writing
statusLine here silently clobbers any profile-level statusLine the user
configured. Guard the write with !isGlobal && !forceStatusline; pass
--force-statusline to override.

Closes #2248

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:58:41 -04:00
Tom Boucher
8b94f0370d test: guard ARCHITECTURE.md component counts against drift (#2260)
* test: guard ARCHITECTURE.md component counts against drift (#2258)

Add tests/architecture-counts.test.cjs — 3 tests that dynamically
verify the "Total commands/workflows/agents" counts in
docs/ARCHITECTURE.md match the actual *.md file counts on disk.
Both sides computed at runtime; zero hardcoded numbers.

Also corrects the stale counts in ARCHITECTURE.md:
- commands: 69 → 74
- workflows: 68 → 71
- agents: 24 → 31

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(init): remove literal ~/.claude/ from deprecated root identifiers to pass Cline path-leak test

The cline-install.test.cjs scans installed engine files for literal
~/.claude/(get-shit-done|commands|...) strings that should have been
substituted during install. Two deprecated-legacy entries added by #2261
used tilde-notation string literals for their root identifier, which
triggered this scan.

root is only a display/sort key — filesystem scanning always uses the
path property (already dynamic via path.join). Switching root to the
relative form '.claude/get-shit-done/skills' and '.claude/commands/gsd'
satisfies the Cline path-leak guard without changing runtime behaviour.

Update skill-manifest.test.cjs assertion to match the new root format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 10:35:29 -04:00
TÂCHES
4a34745950 feat(skills): normalize skill discovery contract across runtimes (#2261) 2026-04-15 07:39:48 -06:00
Tom Boucher
c051e71851 test(docs): add command-count sync test; fix ARCHITECTURE.md drift (#2257) (#2259)
Add tests/command-count-sync.test.cjs which programmatically counts
.md files in commands/gsd/ and compares against the two count
occurrences in docs/ARCHITECTURE.md ("Total commands: N" prose line and
"# N slash commands" directory-tree comment). Counts are extracted from
the doc at runtime — never hardcoded — so future drift is caught
immediately in CI regardless of whether the doc or the filesystem moves.

Fix the current drift: ARCHITECTURE.md said 69 commands; the actual
committed count is 73. Both occurrences updated.

Closes #2257

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 08:58:13 -04:00
Tom Boucher
62b5278040 fix(installer): restore detect-custom-files and backup_custom_files lost in release drift (#1997) (#2233)
PR #2038 added detect-custom-files to gsd-tools.cjs and the backup_custom_files
step to update.md, but commit 7bfb11b6 is not an ancestor of v1.36.0: main was
rebuilt after the merge, orphaning the change. Users on 1.36.0 running /gsd-update
silently lose any locally-authored files inside GSD-managed directories.

Root cause: git merge-base 7bfb11b6 HEAD returns aa3e9cf (Cline runtime, PR #2032),
117 commits before the release tag. The "merged" GitHub state reflects the PR merge
event, not reachability from the default branch.

Fix: re-apply the three changes from 7bfb11b6 onto current main:
- Add detect-custom-files subcommand to gsd-tools.cjs (walk managed dirs, compare
  against gsd-file-manifest.json keys via path.relative(), return JSON list)
- Add 'detect-custom-files' to SKIP_ROOT_RESOLUTION set
- Restore backup_custom_files step in update.md before run_update
- Restore tests/update-custom-backup.test.cjs (7 tests, all passing)

Closes #2229
Closes #1997

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:50:53 -04:00
Tom Boucher
50f61bfd9a fix(hooks): complete stale-hooks false-positive fix — stamp .sh version headers + fix detector regex (#2224)
* fix(hooks): stamp gsd-hook-version in .sh hooks and fix stale detection regex (#2136, #2206)

Three-part fix for the persistent "⚠ stale hooks — run /gsd-update" false
positive that appeared on every session after a fresh install.

Root cause: the stale-hook detector (gsd-check-update.js) could only match
the JS comment syntax // in its version regex — never the bash # syntax used
in .sh hooks. And the bash hooks had no version header at all, so they always
landed in the "unknown / stale" branch regardless.

Neither partial fix (PR #2207 regex only, PR #2215 install stamping only) was
sufficient alone:
  - Regex fix without install stamping: hooks install with literal
    "{{GSD_VERSION}}", the {{-guard silently skips them, bash hook staleness
    permanently undetectable after future updates.
  - Install stamping without regex fix: hooks are stamped correctly with
    "# gsd-hook-version: 1.36.0" but the detector's // regex can't read it;
    still falls to the unknown/stale branch on every session.

Fix:
  1. Add "# gsd-hook-version: {{GSD_VERSION}}" header to
     gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh
  2. Extend install.js (both bundled and Codex paths) to substitute
     {{GSD_VERSION}} in .sh files at install time (same as .js hooks)
  3. Extend gsd-check-update.js versionMatch regex to handle bash "#"
     comment syntax: /(?:\/\/|#) gsd-hook-version:\s*(.+)/

Tests: 11 new assertions across 5 describe blocks covering all three fix
parts independently plus an E2E install+detect round-trip. 3885/3885 pass.

Approach credit: PR #2207 (j2h4u / Maxim Brashenko) for the regex fix;
PR #2215 (nitsan2dots) for the install.js substitution approach.

Closes #2136, #2206, #2209, #2210, #2212

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(hooks): extract check-update worker to dedicated file, eliminating template-literal regex escaping

Move stale-hook detection logic from inline `node -e '<template literal>'` subprocess
to a standalone gsd-check-update-worker.js. Benefits:
- Regex is plain JS with no double-escaping (root cause of the (?:\\/\\/|#) confusion)
- Worker is independently testable and can be read directly by tests
- Uses execFileSync (array args) to satisfy security hook that blocks execSync
- MANAGED_HOOKS now includes gsd-check-update-worker.js itself

Update tests to read worker file instead of main hook for regex/configDir assertions.
All 3886 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:57:38 -04:00
Lex Christopherson
201b8f1a05 1.36.0 2026-04-14 08:26:26 -06:00
Lex Christopherson
73c7281a36 docs: update changelog and README for v1.36.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:26:17 -06:00
Gabriel Rodrigues Garcia
e6e33602c3 fix(init): ignore archived phases from prior milestones sharing a phase number (#2186)
When a new milestone reuses a phase number that exists in an archived
milestone (e.g., v2.0 Phase 2 while v1.0-phases/02-old-feature exists),
findPhaseInternal falls through to the archive and returns the old
phase. init plan-phase and init execute-phase then emitted archived
values for phase_dir, phase_slug, has_context, has_research, and
*_path fields, while phase_req_ids came from the current ROADMAP —
producing a silent inconsistency that pointed downstream agents at a
shipped phase from a previous milestone.

cmdInitPhaseOp already guarded against this (see lines 617-642);
apply the same guard in cmdInitPlanPhase, cmdInitExecutePhase, and
cmdInitVerifyWork: if findPhaseInternal returns an archived match
and the current ROADMAP.md has the phase, discard the archived
phaseInfo so the ROADMAP fallback path produces clean values.

Adds three regression tests covering plan-phase, execute-phase, and
verify-work under the shared-number scenario.
2026-04-13 10:59:11 -04:00
pingchesu
c11ec05554 feat: /gsd-graphify integration — knowledge graph for planning agents (#2164)
* feat(01-01): create graphify.cjs library module with config gate, subprocess helper, presence detection, and version check

- isGraphifyEnabled() gates on config.graphify.enabled in .planning/config.json
- disabledResponse() returns structured disabled message with enable instructions
- execGraphify() wraps spawnSync with PYTHONUNBUFFERED=1, 30s timeout, ENOENT/SIGTERM handling
- checkGraphifyInstalled() detects missing binary via --help probe
- checkGraphifyVersion() uses python3 importlib.metadata, validates >=0.4.0,<1.0 range

* feat(01-01): register graphify.enabled in VALID_CONFIG_KEYS

- Added graphify.enabled after intel.enabled in config.cjs VALID_CONFIG_KEYS Set
- Enables gsd-tools config-set graphify.enabled true without key rejection

* test(01-02): add comprehensive unit tests for graphify.cjs module

- 23 tests covering all 5 exported functions across 5 describe blocks
- Config gate tests: enabled/disabled/missing/malformed scenarios (TEST-03, FOUND-01)
- Subprocess tests: success, ENOENT, timeout, env vars, timeout override (FOUND-04)
- Presence tests: --help detection, install instructions (FOUND-02, TEST-04)
- Version tests: compatible/incompatible/unparseable/missing (FOUND-03, TEST-04)
- Fix graphify.cjs to use childProcess.spawnSync (not destructured) for testability

* feat(02-01): add graphifyQuery, graphifyStatus, graphifyDiff to graphify.cjs

- safeReadJson wraps JSON.parse in try/catch, returns null on failure
- buildAdjacencyMap creates bidirectional adjacency map from graph nodes/edges
- seedAndExpand matches on label+description (case-insensitive), BFS-expands up to maxHops
- applyBudget uses chars/4 token estimation, drops AMBIGUOUS then INFERRED edges
- graphifyQuery gates on config, reads graph.json, supports --budget option
- graphifyStatus returns exists/last_build/counts/staleness or no-graph message
- graphifyDiff compares current graph.json against .last-build-snapshot.json

* feat(02-01): add case 'graphify' routing block to gsd-tools.cjs

- Routes query/status/diff/build subcommands to graphify.cjs handlers
- Query supports --budget flag via args.indexOf parsing
- Build returns Phase 3 placeholder error message
- Unknown subcommand lists all 4 available options

* feat(02-01): create commands/gsd/graphify.md command definition

- YAML frontmatter with name, description, argument-hint, allowed-tools
- Config gate reads .planning/config.json directly (not gsd-tools config get-value)
- Inline CLI calls for query/status/diff subcommands
- Agent spawn placeholder for build subcommand
- Anti-read warning and anti-patterns section

* test(02-02): add Phase 2 test scaffolding with fixture helpers and describe blocks

- Import 7 Phase 2 exports (graphifyQuery, graphifyStatus, graphifyDiff, safeReadJson, buildAdjacencyMap, seedAndExpand, applyBudget)
- Add writeGraphJson and writeSnapshotJson fixture helpers
- Add SAMPLE_GRAPH constant with 5 nodes, 5 edges across all confidence tiers
- Scaffold 7 new describe blocks for Phase 2 functions

* test(02-02): add comprehensive unit tests for all Phase 2 graphify.cjs functions

- safeReadJson: valid JSON, malformed JSON, missing file (3 tests)
- buildAdjacencyMap: bidirectional entries, orphan nodes, edge objects (3 tests)
- seedAndExpand: label match, description match, BFS depth, empty results, maxHops (5 tests)
- applyBudget: no budget passthrough, AMBIGUOUS drop, INFERRED drop, trimmed footer (4 tests)
- graphifyQuery: disabled gate, no graph, valid query, confidence tiers, budget, counts (6 tests)
- graphifyStatus: disabled gate, no graph, counts with graph, hyperedge count (4 tests)
- graphifyDiff: disabled gate, no baseline, no graph, added/removed, changed (5 tests)
- Requirements: TEST-01, QUERY-01..03, STAT-01..02, DIFF-01..02
- Full suite: 53 graphify tests pass, 3666 total tests pass (0 regressions)

* feat(03-01): add graphifyBuild() pre-flight, writeSnapshot(), and build_timeout config key

- Add graphifyBuild(cwd) returning spawn_agent JSON with graphs_dir, timeout, version
- Add writeSnapshot(cwd) reading graph.json and writing atomic .last-build-snapshot.json
- Register graphify.build_timeout in VALID_CONFIG_KEYS
- Import atomicWriteFileSync from core.cjs for crash-safe snapshot writes

* feat(03-01): wire build routing in gsd-tools and flesh out builder agent prompt

- Replace Phase 3 placeholder with graphifyBuild() and writeSnapshot() dispatch
- Route 'graphify build snapshot' to writeSnapshot(), 'graphify build' to graphifyBuild()
- Expand Step 3 builder agent prompt with 5-step workflow: invoke, validate, copy, snapshot, summary
- Include error handling guidance: non-zero exit preserves prior .planning/graphs/

* test(03-02): add graphifyBuild test suite with 6 tests

- Disabled config returns disabled response
- Missing CLI returns error with install instructions
- Successful pre-flight returns spawn_agent action with correct shape
- Creates .planning/graphs/ directory if missing
- Reads graphify.build_timeout from config (custom 600s)
- Version warning included when outside tested range

* test(03-02): add writeSnapshot test suite with 6 tests

- Writes snapshot from existing graph.json with correct structure
- Returns error when graph.json does not exist
- Returns error when graph.json is invalid JSON
- Handles empty nodes and edges arrays
- Handles missing nodes/edges keys gracefully
- Overwrites existing snapshot on incremental rebuild

* feat(04-01): add load_graph_context step to gsd-planner agent

- Detects .planning/graphs/graph.json via ls check
- Checks graph staleness via graphify status CLI call
- Queries phase-relevant context with single --budget 2000 query
- Silent no-op when graph.json absent (AGENT-01)

* feat(04-01): add Step 1.3 Load Graph Context to gsd-phase-researcher agent

- Detects .planning/graphs/graph.json via ls check
- Checks graph staleness via graphify status CLI call
- Queries 2-3 capability keywords with --budget 1500 each
- Silent no-op when graph.json absent (AGENT-02)

* test(04-01): add AGENT-03 graceful degradation tests

- 3 AGENT-03 tests: absent-graph query, status, multi-term handling
- 2 D-12 integration tests: known-graph query and status structure
- All 5 tests pass with existing helpers and imports
2026-04-12 18:17:18 -04:00
Rezolv
6f79b1dd5e feat(sdk): Phase 1 typed query foundation (gsd-sdk query) (#2118)
* feat(sdk): add typed query foundation and gsd-sdk query (Phase 1)

Add sdk/src/query registry and handlers with tests, GSDQueryError, CLI query wiring, and supporting type/tool-scoping hooks. Update CHANGELOG. Vitest 4 constructor mock fixes in milestone-runner tests.

Made-with: Cursor

* chore: gitignore .cursor for local-only Cursor assets

Made-with: Cursor

* fix(sdk): harden query layer for PR review (paths, locks, CLI, ReDoS)

- resolvePathUnderProject: realpath + relative containment for frontmatter and key_links

- commitToSubrepo: path checks + sanitizeCommitMessage

- statePlannedPhase: readModifyWriteStateMd (lock); MUTATION_COMMANDS + events

- key_links: regexForKeyLinkPattern length/ReDoS guard; phase dirs: reject .. and separators

- gsd-sdk: strip --pick before parseArgs; strict parser; QueryRegistry.commands()

- progress: static GSDError import; tests updated

Made-with: Cursor

* feat(sdk): query follow-up — tests, QUERY-HANDLERS, registry, locks, intel depth

Made-with: Cursor

* docs(sdk): use ASCII punctuation in QUERY-HANDLERS.md

Made-with: Cursor
2026-04-12 18:15:04 -04:00
Tibsfox
66a5f939b0 feat(health): detect stale and orphan worktrees in validate-health (W017) (#2175)
Add W017 warning to cmdValidateHealth that detects linked git worktrees that are stale (older than 1 hour, likely from crashed agents) or orphaned (path no longer exists on disk). Parses git worktree list --porcelain output, skips the main worktree, and provides actionable fix suggestions. Gracefully degrades if git worktree is unavailable.

Closes #2167

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:56:39 -04:00
Tibsfox
67f5c6fd1d docs(agents): standardize required_reading patterns across agent specs (#2176)
Closes #2168

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:56:19 -04:00
Tibsfox
b2febdec2f feat(workflow): scan planted seeds during new-milestone step 2.5 (#2177)
Closes #2169

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:56:00 -04:00
Tom Boucher
990b87abd4 feat(discuss-phase): adapt gray area language for non-technical owners via USER-PROFILE.md (#2125) (#2173)
When USER-PROFILE.md signals a non-technical product owner (learning_style: guided,
jargon in frustration_triggers, or high-level explanation_depth), discuss-phase now
reframes gray area labels and advisor_research rationale paragraphs in product-outcome
language. Same technical decisions, translated framing so product owners can participate
meaningfully without needing implementation vocabulary.

Closes #2125

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 16:45:29 -04:00
Tom Boucher
6d50974943 fix: remove head -5 truncation from UAT file listing in verify-work (#2172)
Projects with more than 5 phases had active UAT sessions silently
dropped from the verify-work listing. Only the first 5 *-UAT.md files
were shown, causing /gsd-verify-work to report incomplete results.

Remove the | head -5 pipe so all UAT files are listed regardless of
phase count.

Closes #2171

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 16:06:17 -04:00
Bhaskoro Muthohar
5a802e4fd2 feat: add flow diagram directive to phase researcher agent (#2139) (#2147)
Architecture diagrams generated by gsd-phase-researcher now enforce
data-flow style (conceptual components with arrows) instead of
file-listing style. The directive is language-agnostic and applies
to all project types.

Changes:
- agents/gsd-phase-researcher.md: add System Architecture Diagram
  subsection in Architecture Patterns output template
- get-shit-done/templates/research.md: add matching directive in
  both architecture_patterns template sections
- tests/phase-researcher-flow-diagram.test.cjs: 8 tests validating
  directive presence, content, and ordering in agent and template

Closes #2139
2026-04-12 15:56:20 -04:00
Andreas Brauchli
72af8cd0f7 fix: display relative time in intel status output (#2132)
* fix: display relative time instead of UTC in intel status output

The `updated_at` timestamps in `gsd-tools intel status` were displayed
as raw ISO/UTC strings, making them appear to show the wrong time in
non-UTC timezones. Replace with fuzzy relative times ("5 minutes ago",
"1 day ago") which are timezone-agnostic and more useful for freshness.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add regression tests for timeAgo utility

Covers boundary values (seconds/minutes/hours/days/months/years),
singular vs plural formatting, and future-date edge case.

Addresses review feedback on #2132.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:54:17 -04:00
Tom Boucher
b896db6f91 fix: copy hook files to Codex install target (#2153) (#2166)
Codex install registered gsd-check-update.js in config.toml but never
copied the hook file to ~/.codex/hooks/. The hook-copy block in install()
was gated by !isCodex, leaving a broken reference on every fresh Codex
global install.

Adds a dedicated hook-copy step inside the isCodex branch that mirrors
the existing copy logic (template substitution, chmod). Adds a regression
test that verifies the hook file physically exists after install.

Closes #2153

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 15:52:57 -04:00
Tom Boucher
4bf3b02bec fix: add phase add-batch command to prevent duplicate phase numbers on parallel invocations (#2165) (#2170)
Parallel `phase add` invocations each read disk state before any write
completes, causing all processes to calculate the same next phase number
and produce duplicate directories and ROADMAP entries.

The new `add-batch` subcommand accepts a JSON array of phase descriptions
and performs all directory creation and ROADMAP appends within a single
`withPlanningLock()` call, incrementing `maxPhase` within the lock for
each entry. This guarantees sequential numbering regardless of call
concurrency patterns.

Closes #2165

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 15:52:33 -04:00
Tom Boucher
c5801e1613 fix: show contextual warning for dev installs with stale hooks (#2162)
When a user manually installs a dev branch where VERSION > npm latest,
gsd-check-update detects hooks as "stale" and the statusline showed
the red "⚠ stale hooks — run /gsd-update" message. Running /gsd-update
would incorrectly downgrade the dev install to the npm release.

Fix: detect dev install (cache.installed > cache.latest) in the
statusline and show an amber "⚠ dev install — re-run installer to sync
hooks" message instead, with /gsd-update reserved for normal upgrades.

Also expand the update.md workflow's installed > latest branch to
explain the situation and give the correct remediation command
(node bin/install.js --global --claude, not /gsd-update).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 11:52:21 -04:00
Tom Boucher
f0a20e4dd7 feat: open artifact audit gate for milestone close and phase verify (#2157, #2158) (#2160)
* feat(2158): add audit.cjs open artifact scanner with security-hardened path handling

- Scans 8 .planning/ artifact categories for unresolved state
- Debug sessions, quick tasks, threads, todos, seeds, UAT gaps, verification gaps, CONTEXT open questions
- requireSafePath with allowAbsolute:true on all file reads
- sanitizeForDisplay on all output strings
- Graceful per-category error handling, never throws
- formatAuditReport returns human-readable report with emoji indicators

* feat(2158): add audit-open CLI command to gsd-tools.cjs + Deferred Items to state template

- Add audit-open [--json] case to switch router
- Add audit-open entry to header comment block
- Add Deferred Items section to state.md template for milestone carry-forward

* feat(2157): add phase artifact scan step to verify-work workflow

- scan_phase_artifacts step runs audit-open --json after UAT completion
- Surfaces UAT gaps, VERIFICATION gaps, and CONTEXT open questions for current phase
- Prompts user to confirm or decline before marking phase verified
- Records acknowledged gaps in VERIFICATION.md Acknowledged Gaps section
- SECURITY note: file paths validated, content truncated and sanitized before display

* feat(2158): add pre-close artifact audit gate to complete-milestone workflow

- pre_close_artifact_audit step runs before verify_readiness
- Displays full audit report when open items exist
- Three-way choice: Resolve, Acknowledge all, or Cancel
- Acknowledge path writes deferred items table to STATE.md
- Records deferred count in MILESTONES.md entry
- Adds three new success criteria checklist items
- SECURITY note on sanitizing all STATE.md writes

* test(2157,2158): add milestone audit gate tests

- 6 tests for audit.cjs: structured result, graceful missing dirs, open debug detection,
  resolved session exclusion, formatAuditReport header, all-clear message
- 3 tests for complete-milestone.md: pre_close_artifact_audit step, Deferred Items,
  security note presence
- 2 tests for verify-work.md: scan_phase_artifacts step, user prompt for gaps
- 1 test for state.md template: Deferred Items section
2026-04-12 10:06:42 -04:00
Tom Boucher
7b07dde150 feat: add list/status/resume/close subcommands to /gsd-quick and /gsd-thread (#2159)
* feat(2155): add list/status/resume subcommands and security hardening to /gsd-quick

- Add SUBCMD routing (list/status/resume/run) before quick workflow delegation
- LIST subcommand scans .planning/quick/ dirs, reads SUMMARY.md frontmatter status
- STATUS subcommand shows plan description and current status for a slug
- RESUME subcommand finds task by slug, prints context, then resumes quick workflow
- Slug sanitization: only [a-z0-9-], max 60 chars, reject ".." and "/"
- Directory name sanitization for display (strip non-printable + ANSI sequences)
- Add security_notes section documenting all input handling guarantees

* feat(2156): formalize thread status frontmatter, add list/close/status subcommands, remove heredoc injection risk

- Replace heredoc (cat << 'EOF') with Write tool instruction — eliminates shell injection risk
- Thread template now uses YAML frontmatter (slug, title, status, created, updated fields)
- Add subcommand routing: list / list --open / list --resolved / close <slug> / status <slug>
- LIST mode reads status from frontmatter, falls back to ## Status heading
- CLOSE mode updates frontmatter status to resolved via frontmatter set, then commits
- STATUS mode displays thread summary (title, status, goal, next steps) without spawning
- RESUME mode updates status from open → in_progress via frontmatter set
- Slug sanitization for close/status: only [a-z0-9-], max 60 chars, reject ".." and "/"
- Add security_notes section documenting all input handling guarantees

* test(2155,2156): add quick and thread session management tests

- quick-session-management.test.cjs: verifies list/status/resume routing,
  slug sanitization, directory sanitization, frontmatter get usage, security_notes
- thread-session-management.test.cjs: verifies list filters (--open/--resolved),
  close/status subcommands, no heredoc, frontmatter fields, Write tool usage,
  slug sanitization, security_notes
2026-04-12 10:05:17 -04:00
Tom Boucher
1aa89b8ae2 feat: debug skill dispatch and session manager sub-orchestrator (#2154)
* feat(2148): add specialist_hint to ROOT CAUSE FOUND and skill dispatch to /gsd-debug

- Add specialist_hint field to ROOT CAUSE FOUND return format in gsd-debugger structured_returns section
- Add derivation guidance in return_diagnosis step (file extensions → hint mapping)
- Add Step 4.5 specialist skill dispatch block to debug.md with security-hardened DATA_START/DATA_END prompt
- Map specialist_hint values to skills: typescript-expert, swift-concurrency, python-expert-best-practices-code-review, ios-debugger-agent, engineering:debug
- Session manager now handles specialist dispatch internally; debug.md documents delegation intent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(2151): add gsd-debug-session-manager agent and refactor debug command as thin bootstrap

- Create agents/gsd-debug-session-manager.md: handles full checkpoint/continuation loop in isolated context
- Agent spawns gsd-debugger, handles ROOT CAUSE FOUND/TDD CHECKPOINT/DEBUG COMPLETE/CHECKPOINT REACHED/INVESTIGATION INCONCLUSIVE returns
- Specialist dispatch via AskUserQuestion before fix options; user responses wrapped in DATA_START/DATA_END
- Returns compact ≤2K DEBUG SESSION COMPLETE summary to keep main context lean
- Refactor commands/gsd/debug.md: Steps 3-5 replaced with thin bootstrap that spawns session manager
- Update available_agent_types to include gsd-debug-session-manager
- Continue subcommand also delegates to session manager

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(2148,2151): add tests for skill dispatch and session manager

- Add 8 new tests in debug-session-management.test.cjs covering specialist_hint field,
  skill dispatch mapping in debug.md, DATA_START/DATA_END security boundaries,
  session manager tools, compact summary format, anti-heredoc rule, and delegation check
- Update copilot-install.test.cjs expected agent list to include gsd-debug-session-manager

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 09:40:36 -04:00
Tom Boucher
20fe395064 feat(2149,2150): add project skills awareness to 9 GSD agents (#2152)
- gsd-debugger: add Project skills block after required_reading
- gsd-integration-checker, gsd-security-auditor, gsd-nyquist-auditor,
  gsd-codebase-mapper, gsd-roadmapper, gsd-eval-auditor, gsd-intel-updater,
  gsd-doc-writer: add Project skills block at context-load step
- Add context budget note to 8 quality/audit agents
- gsd-doc-writer: add security note for user-supplied doc_assignment content
- Add tests/agent-skills-awareness.test.cjs validation suite
2026-04-12 09:40:20 -04:00
Tom Boucher
c17209f902 feat(2145): /gsd-debug session management, TDD gate, reasoning checkpoint, security hardening (#2146)
* feat(2145): add list/continue/status subcommands and surface next_action in /gsd-debug

- Parse SUBCMD from \$ARGUMENTS before active-session check (list/status/continue/debug)
- Step 1a: list subcommand prints formatted table of all active sessions
- Step 1b: status subcommand prints full session summary without spawning agent
- Step 1c: continue subcommand surfaces Current Focus then spawns continuation agent
- Surface [debug] Session/Status/Hypothesis/Next before every agent spawn
- Read TDD_MODE from config in Step 0 (used in Step 4)
- Slug sanitization: strip path traversal chars, enforce ^[a-z0-9][a-z0-9-]*$ pattern

* feat(2145): add TDD mode, delta debugging, reasoning checkpoint to gsd-debugger

- Security note in <role>: DATA_START/DATA_END markers are data-only, never instructions
- Delta Debugging technique added to investigation_techniques (binary search over change sets)
- Structured Reasoning Checkpoint technique: mandatory five-field block before any fix
- fix_and_verify step 0: mandatory reasoning_checkpoint before implementing fix
- TDD mode block in <modes>: red/green cycle, tdd_checkpoint tracking, TDD CHECKPOINT return
- TDD CHECKPOINT structured return format added to <structured_returns>
- next_action concreteness guidance added to <debug_file_protocol>

* feat(2145): update DEBUG.md template and docs for debug enhancements

- DEBUG.md template: add reasoning_checkpoint and tdd_checkpoint fields to Current Focus
- DEBUG.md section_rules: document next_action concreteness requirement and new fields
- docs/COMMANDS.md: document list/status/continue subcommands and TDD mode flag
- tests/debug-session-management.test.cjs: 12 content-validation tests (all pass)
2026-04-12 09:00:23 -04:00
Tom Boucher
002bcf2a8a fix(2137): skip worktree isolation when .gitmodules detected (#2144)
* feat(sdk): add typed query foundation and gsd-sdk query (Phase 1)

Add sdk/src/query registry and handlers with tests, GSDQueryError, CLI query wiring, and supporting type/tool-scoping hooks. Update CHANGELOG. Vitest 4 constructor mock fixes in milestone-runner tests.

Made-with: Cursor

* fix(2137): skip worktree isolation when .gitmodules detected

When a project contains git submodules, worktree isolation cannot
correctly handle submodule commits — three separate gaps exist in
worktree setup, executor commit protocol, and merge-back. Rather
than patch each gap individually, detect .gitmodules at phase start
and fall back to sequential execution, which handles submodules
transparently (Option B).

Affected workflows: execute-phase.md, quick.md

---------

Co-authored-by: David Sienkowski <dave@sienkowski.com>
2026-04-12 08:33:04 -04:00
Tom Boucher
58632e0718 fix(2095): use cp instead of git-show for worktree STATE.md backup (#2143)
Replace `git show HEAD:.planning/STATE.md` with `cp .planning/STATE.md`
in the worktree merge-back protection logic of execute-phase.md and
quick.md. The git show approach exits 128 when STATE.md has uncommitted
changes or is not yet in HEAD's committed tree, leaving an empty backup
and causing the post-merge restore guard to silently skip — zeroing or
staling the file. Using cp reads the actual working-tree file (including
orchestrator updates that haven't been committed yet), which is exactly
what "main always wins" should protect.
2026-04-12 08:26:57 -04:00
Tom Boucher
a91f04bc82 fix(2136): add missing bash hooks to MANAGED_HOOKS staleness check (#2141)
* test(2136): add failing test for MANAGED_HOOKS missing bash hooks

Asserts that every gsd-*.js and gsd-*.sh file shipped in hooks/ appears
in the MANAGED_HOOKS array inside gsd-check-update.js. The three bash
hooks (gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh)
were absent, causing this test to fail before the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(2136): add gsd-phase-boundary.sh, gsd-session-state.sh, gsd-validate-commit.sh to MANAGED_HOOKS

The MANAGED_HOOKS array in gsd-check-update.js only listed the 6 JS hooks.
The 3 bash hooks were never checked for staleness after a GSD update, meaning
users could run stale shell hooks indefinitely without any warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 08:10:56 -04:00
Tom Boucher
86dd9e1b09 fix(2134): fix code-review SUMMARY.md parser section-reset for top-level keys (#2142)
* test(2134): add failing test for code-review SUMMARY.md YAML parser section reset

Demonstrates bug #2134: the section-reset regex in the inline node parser
in get-shit-done/workflows/code-review.md uses \s+ (requires leading whitespace),
so top-level YAML keys at column 0 (decisions:, metrics:, tags:) never reset
inSection, causing their list items to be mis-classified as key_files.modified
entries.

RED test asserts that the buggy parser contaminates the file list with decision
strings. GREEN test and additional tests verify correct behaviour with the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(2134): fix YAML parser section reset to handle top-level keys (\s* not \s+)

The inline node parser in compute_file_scope (Tier 2) used \s+ in the
section-reset regex, requiring leading whitespace. Top-level YAML keys at
column 0 (decisions:, metrics:, tags:) never matched, so inSection was never
cleared and their list items were mis-classified as key_files.modified entries.

Fix: change \s+ to \s* in both the reset check and its dash-guard companion so
any key at any indentation level (including column 0) resets inSection.

  Before: /^\s+\w+:/.test(line) && !/^\s+-/.test(line)
  After:  /^\s*\w+:/.test(line) && !/^\s*-/.test(line)

Closes #2134

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 08:10:30 -04:00
Tibsfox
ae8c0e6b26 docs(sdk): recommend 1-hour cache TTL for system prompts (#2055)
* docs(sdk): recommend 1-hour cache TTL for system prompts (#1980)

Add sdk/docs/caching.md with prompt caching best practices for API
users building on GSD patterns. Recommends 1-hour TTL for executor,
planner, and verifier system prompts which are large and stable across
requests within a session.

The default 5-minute TTL expires during human review pauses between
phases. 1-hour TTL costs 2x on cache miss but pays for itself after
3 hits — GSD phases typically involve dozens of requests per hour.

Closes #1980

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(sdk): fix ttl type to string per Anthropic API spec

The Anthropic extended caching API requires ttl as a string ('1h'),
not an integer (3600). Corrects both code examples in caching.md.

Review feedback on #2055 from @trek-e.

* docs(sdk): fix second ttl value in direct-api example to string '1h'

Follow-up to trek-e's re-review on #2055. The first fix corrected the Agent SDK integration example (line 16) but missed the second code block (line 60) that shows the direct Claude API call. Both now use ttl: '1h' (string) as the Anthropic extended caching API requires — integer forms like ttl: 3600 are silently ignored by the API and the cache never activates.

Closes #1980

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 08:09:44 -04:00
Tom Boucher
eb03ba3dd8 fix(2129): exclude 999.x backlog phases from next-phase and all_complete (#2135)
* test(2129): add failing tests for 999.x backlog phase exclusion

Bug A: phase complete reports 999.1 as next phase instead of 3
Bug B: init manager returns all_complete:false when only 999.x is incomplete

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(2129): exclude 999.x backlog phases from next-phase scan and all_complete check

In cmdPhaseComplete, backlog phases (999.x) on disk were picked as the
next phase when intervening milestone phases had no directory yet. Now
the filesystem scan skips any directory whose phase number starts with 999.

In cmdInitManager, all_complete compared completed count against the full
phase list including 999.x stubs, making it impossible to reach true when
backlog items existed. Now the check uses only non-backlog phases.

Closes #2129

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 23:50:25 -04:00
Tom Boucher
637daa831b fix(2130): anchor extractFrontmatter regex to file start (#2133)
* test(2130): add failing tests for frontmatter body --- sequence mis-parse

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(2130): anchor extractFrontmatter regex to file start, preventing body --- mis-parse

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 23:47:50 -04:00
Tom Boucher
553d9db56e ci: upgrade GitHub Actions to Node 22+ runtimes (#2128)
- actions/checkout v4.2.2 → v6.0.2 (pr-gate, auto-branch)
- actions/github-script v7.0.1/v8 → v9.0.0 (all workflows)
- actions/stale v9.0.0 → v10.2.0

Eliminates Node.js 20 deprecation warnings. Node 20 actions
will be forced to Node 24 on June 2, 2026 and removed Sept 16, 2026.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 16:28:18 -04:00
Tom Boucher
8009b67e3e feat: expose tdd_mode in init JSON and add --tdd flag override (#2124)
* test(2123): add failing tests for TDD init JSON exposure and --tdd flag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(2123): expose tdd_mode in init JSON and add --tdd flag override

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 15:39:50 -04:00
Tom Boucher
6b7b6a0ae8 ci: fix release pipeline — update actions, add GH releases, extend CI triggers (#1956)
- Update actions/checkout and actions/setup-node to v6 in release.yml and
  hotfix.yml (Node.js 24 compat, prevents June 2026 breakage)
- Add GitHub Release creation to release finalize, release RC, and hotfix
  finalize steps (populates Releases page automatically)
- Extend test.yml push triggers to release/** and hotfix/** branches
- Extend security-scan.yml PR triggers to release/** and hotfix/** branches

Closes #1955

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 15:10:12 -04:00
Tom Boucher
177cb544cb chore(ci): add branch-cleanup workflow — auto-delete on merge + weekly sweep (#2051)
Adds .github/workflows/branch-cleanup.yml with two jobs:

- delete-merged-branch: fires on pull_request closed+merged, immediately
  deletes the head branch. Belt-and-suspenders alongside the repo's
  delete_branch_on_merge setting (see issue for the one-line owner action).

- sweep-orphaned-branches: runs weekly (Sunday 4am UTC) and on
  workflow_dispatch. Paginates all branches, deletes any whose only closed
  PRs are merged — cleans up branches that pre-date the setting change.

Both jobs use the pinned actions/github-script hash already used across
the repo. Protected branches (main, develop, release) are never touched.
422 responses (branch already gone) are treated as success.

Closes #2050

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 15:10:09 -04:00
Tom Boucher
3d096cb83c Merge pull request #2078 from gsd-build/release/1.35.0
chore: merge release v1.35.0 to main
2026-04-11 15:10:02 -04:00
Tom Boucher
805696bd03 feat(state): add metrics table pruning and auto-prune on phase complete (#2087) (#2120)
- Extend cmdStatePrune to prune Performance Metrics table rows older than cutoff
- Add workflow.auto_prune_state config key (default: false)
- Call cmdStatePrune automatically in cmdPhaseComplete when enabled
- Document workflow.auto_prune_state in planning-config.md reference
- Add silent option to cmdStatePrune for programmatic use without stdout

Closes #2087

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 15:02:55 -04:00
Tom Boucher
e24cb18b72 feat(workflow): add opt-in TDD pipeline mode (#2119)
* feat(workflow): add opt-in TDD pipeline mode (workflow.tdd_mode)

Add workflow.tdd_mode config key (default: false) that enables
red-green-refactor as a first-class phase execution mode. When
enabled, the planner aggressively applies type: tdd to eligible
tasks and the executor enforces RED/GREEN/REFACTOR gate sequence
with fail-fast on unexpected GREEN before RED. An end-of-phase
collaborative review checkpoint verifies gate compliance.

Closes #1871

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): allowlist plan-phase.md in prompt injection scan

plan-phase.md exceeds 50K chars after TDD mode integration.
This is legitimate orchestration complexity, not prompt stuffing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: trigger CI run

* ci: trigger CI run

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 14:42:01 -04:00
Tom Boucher
d19b61a158 Merge pull request #2121 from gsd-build/feat/1861-pattern-mapper
feat: add gsd-pattern-mapper agent for codebase pattern analysis
2026-04-11 14:37:03 -04:00
Tom Boucher
29f8bfeead fix(test): allowlist plan-phase.md in prompt injection scan
plan-phase.md exceeds 50K chars after pattern mapper step addition.
This is legitimate orchestration complexity, not prompt stuffing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 14:34:13 -04:00
Tom Boucher
d59d635560 feat: add gsd-pattern-mapper agent for codebase pattern analysis (#1861)
Add a new pattern mapper agent that analyzes the codebase for existing
patterns before planning, producing PATTERNS.md with per-file analog
assignments and code excerpts. Integrated into plan-phase workflow as
Step 7.8 (between research and planning), controlled by the
workflow.pattern_mapper config key (default: true).

Changes:
- New agent: agents/gsd-pattern-mapper.md
- New config key: workflow.pattern_mapper in VALID_CONFIG_KEYS and CONFIG_DEFAULTS
- init plan-phase: patterns_path field in JSON output
- plan-phase.md: Step 7.8 spawns pattern mapper, PATTERNS_PATH in planner files_to_read
- gsd-plan-checker.md: Dimension 12 (Pattern Compliance)
- model-profiles.cjs: gsd-pattern-mapper profile entry
- Tests: tests/pattern-mapper.test.cjs (5 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 14:25:02 -04:00
Tom Boucher
ce1bb1f9ca Merge pull request #2062 from Tibsfox/fix/global-skills-1992
feat(config): support global skills from ~/.claude/skills/ in agent_skills
2026-04-11 13:57:08 -04:00
Tom Boucher
121839e039 Merge pull request #2059 from Tibsfox/fix/context-exhaustion-record-1974
feat(hooks): auto-record session state on context exhaustion
2026-04-11 13:56:43 -04:00
Tom Boucher
6b643b37f4 Merge pull request #2061 from Tibsfox/fix/inline-small-plans-1979
perf(workflow): default to inline execution for 1-2 task plans
2026-04-11 13:56:35 -04:00
Tom Boucher
50be9321e3 Merge pull request #2058 from Tibsfox/fix/limit-prior-context-1969
perf(workflow): limit prior-phase context to 3 most recent phases
2026-04-11 13:56:27 -04:00
Tom Boucher
190804fc73 Merge pull request #2063 from Tibsfox/feat/state-prune-1970
feat(state): add state prune command for unbounded section growth
2026-04-11 13:56:19 -04:00
Tom Boucher
0c266958e4 Merge pull request #2054 from Tibsfox/fix/cache-state-frontmatter-1967
perf(state): cache buildStateFrontmatter disk scan per process
2026-04-11 13:55:43 -04:00
Tom Boucher
d8e7a1166b Merge pull request #2053 from Tibsfox/fix/merge-readdir-health-1973
perf(health): merge four readdirSync passes into one in cmdValidateHealth
2026-04-11 13:55:26 -04:00
Tom Boucher
3e14904afe Merge pull request #2056 from Tibsfox/fix/atomic-writes-1972
fix(core): extend atomicWriteFileSync to milestone, phase, and frontmatter
2026-04-11 13:54:55 -04:00
Tom Boucher
6d590dfe19 Merge pull request #2116 from gsd-build/fix/qwen-claude-reference-leaks
fix(install): eliminate Claude reference leaks in Qwen install paths
2026-04-11 11:21:40 -04:00
Tom Boucher
f1960fad67 fix(install): eliminate Claude reference leaks in Qwen install paths (#2112)
Three install code paths were leaking Claude-specific references into
Qwen installs: copyCommandsAsClaudeSkills lacked runtime-aware content
replacement, the agents copy loop had no isQwen branch, and the hooks
template loop only replaced the quoted '.claude' form. Added CLAUDE.md,
Claude Code, and .claude/ replacements across all three paths plus
copyWithPathReplacement's Qwen .md branch. Includes regression test
that walks the full .qwen/ tree after install and asserts zero Claude
references outside CHANGELOG.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 11:19:47 -04:00
Tom Boucher
898dbf03e6 Merge pull request #2113 from gsd-build/docs/undocumented-features-v1.36
docs: add v1.36.0 feature documentation for PRs #2100-#2111
2026-04-11 10:42:28 -04:00
Tom Boucher
362e5ac36c fix(docs): correct plan_bounce_passes default from 1 to 2
The actual code default in config.cjs and config.json template is 2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:39:31 -04:00
Tom Boucher
3865afd254 Merge branch 'main' into docs/undocumented-features-v1.36 2026-04-11 10:39:23 -04:00
Tom Boucher
091793d2c6 Merge pull request #2111 from gsd-build/feat/1978-prompt-thinning
feat(agents): context-window-aware prompt thinning for sub-200K models
2026-04-11 10:38:18 -04:00
Tom Boucher
06daaf4c68 Merge pull request #2110 from gsd-build/feat/1884-sdk-ws-flag
feat(sdk): add --ws flag for workstream-aware execution
2026-04-11 10:38:07 -04:00
Tom Boucher
4ad7ecc6c6 Merge pull request #2109 from gsd-build/feat/1873-extract-learnings
feat(workflow): add extract-learnings command (#1873)
2026-04-11 10:37:57 -04:00
Tom Boucher
9d5d7d76e7 Merge pull request #2108 from gsd-build/fix/1988-phase-researcher-app-aware
feat(agents): add Architectural Responsibility Mapping to phase-researcher pipeline
2026-04-11 10:37:45 -04:00
Tom Boucher
bae220c5ad Merge pull request #2107 from gsd-build/feat/1875-cross-ai-execution
feat(executor): add cross-AI execution hook in execute-phase
2026-04-11 10:37:39 -04:00
Tom Boucher
8961322141 merge: resolve config.json conflict with main (add all new workflow keys)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:36:17 -04:00
Tom Boucher
3c2cc7189a Merge pull request #2106 from gsd-build/feat/1960-cursor-cli-reviewer
feat(review): add Cursor CLI as peer reviewer in /gsd-review
2026-04-11 10:35:20 -04:00
Tom Boucher
9ff6ca20cf Merge pull request #2105 from gsd-build/feat/1876-code-review-command-hook
feat(ship): add external code review command hook
2026-04-11 10:35:05 -04:00
Tom Boucher
73be20215e merge: resolve conflicts with main (plan_bounce + code_review_command)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:32:20 -04:00
Tom Boucher
ae17848ef1 Merge pull request #2104 from gsd-build/feat/1874-plan-bounce
feat(plan-phase): add plan bounce hook (step 12.5)
2026-04-11 10:31:04 -04:00
Tom Boucher
f425bf9142 enhancement(planner): replace time-based reasoning with context-cost sizing and add multi-source coverage audit (#2091) (#2092) (#2114)
Replace minutes-based task sizing with context-window percentage sizing.
Add planner_authority_limits section prohibiting difficulty-based scope
decisions. Expand decision coverage matrix to multi-source audit covering
GOAL, REQ, RESEARCH, and CONTEXT artifacts. Add Source Audit gap handling
to plan-phase orchestrator (step 9c). Update plan-checker to detect
time/complexity language in scope reduction scans. Add 374 CI regression
tests preventing prohibited language from leaking back into artifacts.

Closes #2091
Closes #2092

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:26:27 -04:00
Tom Boucher
4553d356d2 docs: add v1.36.0 feature documentation for PRs #2100-#2111
Document 8 new features (108-115) in FEATURES.md, add --bounce/--cross-ai
flags to COMMANDS.md, new /gsd-extract-learnings command, 8 new config keys
in CONFIGURATION.md, and skill-manifest + --ws flag in CLI-TOOLS.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 09:54:21 -04:00
Tom Boucher
319663deb7 feat(agents): add context-window-aware prompt thinning for sub-200K models (#1978)
When CONTEXT_WINDOW < 200000, executor and planner agent prompts strip
extended examples and anti-pattern lists into reference files for
on-demand @ loading, reducing static overhead by ~40% while preserving
behavioral correctness for standard (200K-500K) and enriched (500K+) tiers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:34:29 -04:00
Tom Boucher
868e3d488f feat(sdk): add --ws flag for workstream-aware execution (#1884)
Add a --ws <name> CLI flag that routes all .planning/ paths to
.planning/workstreams/<name>/, enabling multi-workstream projects
without directory conflicts.

Changes:
- workstream-utils.ts: validateWorkstreamName() and relPlanningPath() helpers
- cli.ts: Parse --ws flag with input validation
- types.ts: Add workstream? to GSDOptions
- gsd-tools.ts: Inject --ws <name> into all gsd-tools.cjs invocations
- config.ts: Resolve workstream-aware config path with root fallback
- context-engine.ts: Constructor accepts workstream via positional param
- index.ts: GSD class propagates workstream to all subsystems
- ws-flag.test.ts: 22 tests covering all workstream functionality

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:33:34 -04:00
Tom Boucher
3f3fd0a723 feat(workflow): add extract-learnings command for phase knowledge capture (#1873)
Add /gsd:extract-learnings command and backing workflow that extracts
decisions, lessons, patterns, and surprises from completed phase artifacts
into a structured LEARNINGS.md file with YAML frontmatter metadata.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:28:16 -04:00
Tom Boucher
21ebeb8713 feat(executor): add cross-AI execution hook (step 2.5) in execute-phase (#1875)
Add optional cross-AI delegation step that lets execute-phase delegate
plans to external AI runtimes via stdin-based prompt delivery. Activated
by --cross-ai flag, plan frontmatter cross_ai: true, or config key
workflow.cross_ai_execution. Adds 3 config keys, template defaults,
and 18 tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:20:27 -04:00
Tom Boucher
53995faa8f feat(ship): add external code review command hook to ship workflow
Adds workflow.code_review_command config key that allows solo devs to
plug external AI review tools into the ship flow. When configured, the
ship workflow generates a diff, builds a review prompt with stats and
phase context, pipes it to the command via stdin, and parses JSON output
with verdict/confidence/issues. Handles timeout (120s) and failures
gracefully by falling through to the existing manual review flow.

Closes #1876

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:19:32 -04:00
Tom Boucher
9ac7b7f579 feat(plan-phase): add optional plan bounce hook for external refinement (step 12.5)
Add plan bounce feature that allows plans to be refined through an external
script between plan-checker approval and requirements coverage gate. Activated
via --bounce flag or workflow.plan_bounce config. Includes backup/restore
safety (pre-bounce.md), YAML frontmatter validation, and checker re-run on
bounced plans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:19:01 -04:00
Tom Boucher
ff0b06b43a feat(review): add Cursor CLI self-detection and complete REVIEWS.md template (#1960)
Add CURSOR_SESSION_ID env var detection in review.md so Cursor skips
itself as a reviewer (matching the CLAUDE_CODE_ENTRYPOINT pattern).
Add Qwen Review and Cursor Review sections to the REVIEWS.md template.
Update ja-JP and ko-KR FEATURES.md to include --opencode, --qwen, and
--cursor flags in the /gsd-review command signature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:18:49 -04:00
Tom Boucher
72e789432e feat(agents): add Architectural Responsibility Mapping to phase-researcher pipeline (#1988) (#2103)
Before framework-specific research, phase-researcher now maps each
capability to its architectural tier owner (browser, frontend server,
API, database, CDN). The planner sanity-checks task assignments against
this map, and plan-checker enforces tier compliance as Dimension 7c.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:16:11 -04:00
Tom Boucher
23763f920b feat(config): add configurable claude_md_path setting (#2010) (#2102)
Allow users to control where GSD writes its managed CLAUDE.md sections
via a `claude_md_path` setting in .planning/config.json, enabling
separation of GSD content from team-shared CLAUDE.md in shared repos.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:15:36 -04:00
Tom Boucher
9435c4dd38 feat(init): add skill-manifest command to pre-compute skill discovery (#2101)
Adds `skill-manifest` command that scans a skills directory, extracts
frontmatter and trigger conditions from each SKILL.md, and outputs a
compact JSON manifest. This reduces per-agent skill discovery from 36
Read operations (~6,000 tokens) to a single manifest read (~1,000 tokens).

Closes #1976

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:15:18 -04:00
Tom Boucher
f34dc66fa9 fix(core): use dedicated temp subdirectory for GSD temp files (#1975) (#2100)
Move GSD temp file writes from os.tmpdir() root to os.tmpdir()/gsd
subdirectory. This limits reapStaleTempFiles() scan to only GSD files
instead of scanning the entire system temp directory.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:15:00 -04:00
Tom Boucher
1f7ca6b9e8 feat(agents): add Architectural Responsibility Mapping to phase-researcher pipeline (#1988)
Before framework-specific research, phase-researcher now maps each
capability to its architectural tier owner (browser, frontend server,
API, database, CDN). The planner sanity-checks task assignments against
this map, and plan-checker enforces tier compliance as Dimension 7c.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:14:28 -04:00
Tom Boucher
6b0e3904c2 enhancement(workflow): replace consecutive-call counter with prior-phase completeness scan in /gsd-next (#2097)
Removes the .next-call-count counter file guard (which fired on clean usage and missed
real incomplete work) and replaces it with a scan of all prior phases for plans without
summaries, unoverridden VERIFICATION.md failures, and phases with CONTEXT.md but no plans.
When gaps are found, shows a structured report with Continue/Stop/Force options; the
Continue path writes a formal 999.x backlog entry and commits it before routing. Clean
projects route silently with no interruption.

Closes #2089

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:02:30 -04:00
Tom Boucher
aa4532b820 fix(workflow): quote path variables in workspace next-step examples (#2096)
Display examples showing 'cd $TARGET_PATH' and 'cd $WORKSPACE_PATH/repo1'
were unquoted, causing path splitting when project paths contain spaces
(e.g. Windows paths like C:\Users\First Last\...).

Quote all path variable references in user-facing guidance blocks so
the examples shown to users are safe to copy-paste directly.

The actual bash execution blocks (git worktree add, rm -rf, etc.) were
already correctly quoted — this fixes only the display examples.

Fixes #2088

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:02:18 -04:00
Tom Boucher
0e1711b460 fix(workflow): carve out Other+empty exception from answer_validation retry loop (#2093)
When a user selects "Other" in AskUserQuestion with no text body, the
answer_validation block was treating the empty result as a generic empty
response and retrying the question — causing 2-3 cascading question rounds
instead of pausing for freeform user input as intended by the Other handling
on line 795.

Add an explicit exception in answer_validation: "Other" + empty text signals
freeform intent, not a missing answer. The workflow must output one prompt line
and stop rather than retry or generate more questions.

Fixes #2085

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:02:01 -04:00
Tom Boucher
b84dfd4c9b fix(tests): add before() hook to bug-1736 test to prevent hooks/dist race condition (#2099)
With --test-concurrency=4, bug-1834 and bug-1924 run build-hooks.js concurrently
with bug-1736. build-hooks.js creates hooks/dist/ empty first then copies files,
creating a window where bug-1736 sees an empty directory, install() fails with
"directory is empty", and process.exit(1) kills the test process.

Added the same before() pattern used by all other install tests.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 08:50:44 -04:00
Carlos Cativo
5a302f477a fix: add Qwen Code dedicated path replacement branches and finishInstall labels (#2082)
- Add isQwen branch in copyWithPathReplacement for .md files converting
  CLAUDE.md to QWEN.md and 'Claude Code' to 'Qwen Code'
- Add isQwen branch in copyWithPathReplacement for .js/.cjs files
  converting .claude paths to .qwen equivalents
- Add Qwen Code program and command labels in finishInstall() so the
  post-install message shows 'Qwen Code' instead of 'Claude Code'

Closes #2081

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-11 08:36:35 -04:00
Tibsfox
01f0b4b540 feat(state): add --dry-run mode and resolved blocker pruning (#1970)
Review feedback from @trek-e — address scope gaps:

1. **--dry-run mode** — New flag that computes what would be pruned
   without modifying STATE.md. Returns structured output showing
   per-section counts so users can verify before committing.

2. **Resolved blocker pruning** — In addition to decisions and
   recently-completed entries, now prunes entries in the Blockers
   section that are marked resolved (~~strikethrough~~ or [RESOLVED]
   prefix) AND reference a phase older than the cutoff. Unresolved
   blockers are preserved regardless of age.

3. **Tests** — Added tests/state-prune.test.cjs (4 cases):
   - Prunes decisions older than cutoff, keeps recent
   - --dry-run reports changes without modifying STATE.md
   - Prunes resolved blockers, keeps unresolved regardless of age
   - Returns pruned:false when nothing exceeds cutoff

Scope items still deferred (to be filed as follow-up):
- Performance Metrics "By Phase" table row pruning — needs different
  regex handling than prose lines
- Auto-prune via workflow.auto_prune_state at phase completion — needs
  integration into cmdPhaseComplete

Also: the pre-existing test failure (2918/2919) is
tests/stale-colon-refs.test.cjs:83:3 "No stale /gsd: colon references
(#1748)". Verified failing on main, not introduced by this PR.
2026-04-11 03:43:46 -07:00
Tibsfox
f1b3702be8 feat(state): add state prune command for unbounded section growth (#1970)
Add `gsd-tools state prune --keep-recent N` that moves old decisions
and recently-completed entries to STATE-ARCHIVE.md. Entries from phases
older than (current - N) are archived; the N most recent are kept.

STATE.md sections grow unboundedly in long-lived projects. A 20+ phase
project accumulates hundreds of historical decisions that every agent
loads into context. Pruning removes stale entries from the hot path
while preserving them in a recoverable archive.

Usage: gsd-tools state prune --keep-recent 3
Default: keeps 3 most recent phases

Closes #1970

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:39:57 -07:00
Tibsfox
0a18fc3464 fix(config): global skill symlink guard, tests, and empty-name handling (#1992)
Review feedback from @trek-e — three blocking issues and one style fix:

1. **Symlink escape guard** — Added validatePath() call on the resolved
   global skill path with allowAbsolute: true. This routes the path
   through the existing symlink-resolution and containment logic in
   security.cjs, preventing a skill directory symlinked to an arbitrary
   location from being injected. The name regex alone prevented
   traversal in the literal name but not in the underlying directory.

2. **5 new tests** covering the global: code path:
   - global:valid-skill resolves and appears in output
   - global:invalid!name rejected by regex, skipped without crash
   - global:missing-skill (directory absent) skipped gracefully
   - Mix of global: and project-relative paths both resolve
   - global: with empty name produces clear warning and skips

3. **Explicit empty-name guard** — Added before the regex check so
   "global:" produces "empty skill name" instead of the confusing
   'Invalid global skill name ""'.

4. **Style fix** — Hoisted require('os') and globalSkillsBase
   calculation out of the loop, alongside the existing validatePath
   import at the top of buildAgentSkillsBlock.

All 16 agent-skills tests pass.
2026-04-11 03:39:29 -07:00
Tibsfox
7752234e75 feat(config): support global skills from ~/.claude/skills/ in agent_skills (#1992)
Add global: prefix for agent_skills config entries that resolve to
~/.claude/skills/<name>/SKILL.md instead of the project root. This
allows injecting globally-installed skills (e.g., shadcn, supabase)
into GSD sub-agents without duplicating them into every project.

Example config:
  "agent_skills": {
    "gsd-executor": ["global:shadcn", "global:supabase-postgres"]
  }

Security: skill names are validated against /^[a-zA-Z0-9_-]+$/ to
prevent path traversal. The ~/.claude/skills/ directory is a trusted
runtime-controlled location. Project-relative paths continue to use
validatePath() containment checks as before.

Closes #1992

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:37:56 -07:00
Tibsfox
7be9affea2 fix(hooks): address three blocking defects in context exhaustion record (#1974)
Review feedback from @trek-e — three blocking fixes:

1. **Sentinel prevents repeated firing**
   Added warnData.criticalRecorded flag persisted to the warn state file.
   Previously the subprocess fired on every DEBOUNCE_CALLS cycle (5 tool
   uses) for the rest of the session, overwriting the "crash moment"
   record with a new timestamp each time. Now fires exactly once per
   CRITICAL session.

2. **Runtime-agnostic path via __dirname**
   Replaced hardcoded `path.join(process.env.HOME, '.claude', ...)` with
   `path.join(__dirname, '..', 'get-shit-done', 'bin', 'gsd-tools.cjs')`.
   The hook lives at <runtime-config>/hooks/ and gsd-tools.cjs at
   <runtime-config>/get-shit-done/bin/ — __dirname resolves correctly on
   all runtimes (Claude Code, OpenCode, Gemini, Kilo) without assuming
   ~/.claude/.

3. **Correct subcommand: state record-session**
   Switched from `state update "Stopped At" ...` to
   `state record-session --stopped-at ...`. The dedicated command
   updates Last session, Last Date, Stopped At, and Resume File
   atomically under the state lock.

Also:
- Hoisted `const { spawn } = require('child_process')` to top of file
  to match existing require() style.
- Coerced usedPct to Number(usedPct) || 0 to sanitize the bridge file
  in case it's malformed or adversarially crafted.

Tests (tests/bug-1974-context-exhaustion-record.test.cjs, 4 cases):
- Subprocess spawns and writes "context exhaustion" on CRITICAL
- Subprocess does NOT spawn when .planning/STATE.md is absent
- Sentinel guard prevents second fire within same session
- Hook source uses __dirname-based path (not hardcoded ~/.claude/)
2026-04-11 03:37:34 -07:00
Tibsfox
42ad3fe853 feat(hooks): auto-record session state on context exhaustion (#1974)
When the context monitor detects CRITICAL threshold (25% remaining)
and a GSD project is active, spawn a fire-and-forget subprocess to
record "Stopped At: context exhaustion at N%" in STATE.md.

This provides automatic breadcrumbs for /gsd-resume-work when sessions
crash from context exhaustion — the most common unrecoverable scenario.
Previously, session state was only saved via voluntary /gsd-pause-work.

The subprocess is detached and unref'd so it doesn't block the hook
or the agent. The advisory warning to the agent is unchanged.

Closes #1974

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:35:20 -07:00
Tibsfox
67aeb049c2 fix(state): invalidate disk scan cache in writeStateMd (#1967)
Add _diskScanCache.delete(cwd) at the start of writeStateMd before
buildStateFrontmatter is called. This prevents stale reads if multiple
state-mutating operations occur within the same Node process — the
write may create new PLAN/SUMMARY files that the next frontmatter
computation must see.

Matters for:
- SDK callers that require() gsd-tools.cjs as a module
- Future dispatcher extensions handling compound operations
- Tests that import state.cjs directly

Adds tests/bug-1967-cache-invalidation.test.cjs which exercises two
sequential writes in the same process with a new phase directory
created between them, asserting the second write sees the new disk
state (total_phases: 2, completed_phases: 1) instead of the cached
pre-write snapshot (total_phases: 1, completed_phases: 0).

Review feedback on #2054 from @trek-e.
2026-04-11 03:35:00 -07:00
Tibsfox
5638448296 perf(state): cache buildStateFrontmatter disk scan per process (#1967)
buildStateFrontmatter performs N+1 readdirSync calls (phases dir + each
phase subdirectory) every time it's called. Multiple state writes within
a single gsd-tools invocation repeat the same scan unnecessarily.

Add a module-level Map cache keyed by cwd that stores the disk scan
results. The cache auto-clears when the process exits since each
gsd-tools CLI invocation is a short-lived process running one command.

Closes #1967

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:33:48 -07:00
Tibsfox
e5cc0bb48b fix(workflow): correct grep anchor and add threshold=0 guard (#1979)
Two correctness bugs from @trek-e review:

1. Grep pattern `^<task` only matched unindented task tags, missing
   indented tasks in PLAN.md templates that use indentation. Fixed to
   `^\s*<task[[:space:]>]` which matches at any indentation level and
   avoids false positives on <tasks> or </task>.

2. Threshold=0 was documented to disable inline routing but the
   condition `TASK_COUNT <= INLINE_THRESHOLD` evaluated 0<=0 as true,
   routing empty plans inline even when the feature was disabled.
   Fixed by guarding with `INLINE_THRESHOLD > 0`.

Added tests/inline-plan-threshold.test.cjs (8 tests) covering:
- config-set accepts the key and threshold=0
- VALID_CONFIG_KEYS and planning-config.md contain the entry
- Routing pattern matches indented tasks and rejects <tasks>/</task>
- Inline routing is guarded by INLINE_THRESHOLD > 0

Review feedback on #2061 from @trek-e.
2026-04-11 03:33:29 -07:00
Tibsfox
bd7048985d perf(workflow): default to inline execution for small plans (#1979)
Plans with 1-2 tasks now execute inline (Pattern C) instead of spawning
a subagent (Pattern A). This avoids ~14K token subagent spawn overhead
and preserves the orchestrator's prompt cache for small plans.

The threshold is configurable via workflow.inline_plan_threshold
(default: 2). Set to 0 to always spawn subagents. Plans above the
threshold continue to use checkpoint-based routing as before.

Closes #1979

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:31:27 -07:00
Tibsfox
e0b766a08b perf(workflow): include Depends on phases in prior-phase context (#1969)
Per approved spec in #1969, the planner must include CONTEXT.md and
SUMMARY.md from any phases listed in the current phase's 'Depends on:'
field in ROADMAP.md, in addition to the 3 most recent completed phases.

This ensures explicit dependencies are always visible to the planner
regardless of recency — e.g., Phase 7 declaring 'Depends on: Phase 2'
always sees Phase 2's context, not just when Phase 2 is among the 3
most recent.

Review feedback on #2058 from @trek-e.
2026-04-11 03:31:09 -07:00
Tibsfox
2efce9fd2a perf(workflow): limit prior-phase context to 3 most recent phases (#1969)
When CONTEXT_WINDOW >= 500000 (1M models), the planner loaded ALL prior
phase CONTEXT.md and SUMMARY.md files for cross-phase consistency. On
projects with 20+ phases, this consumed significant context budget with
diminishing returns — decisions from phase 2 are rarely relevant to
phase 22.

Limit to the 3 most recent completed phases, which provides enough
cross-phase context for consistency while keeping the planner's context
budget focused on the current phase's plans.

Closes #1969

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:30:32 -07:00
Tibsfox
2cd0e0d8f0 test(core): add atomic write coverage structural regression guard (#1972)
Per CONTRIBUTING.md, enhancements require tests covering the enhanced
behavior. This test structurally verifies that milestone.cjs, phase.cjs,
and frontmatter.cjs do not contain bare fs.writeFileSync calls targeting
.planning/ files. All such writes must route through atomicWriteFileSync.

Allowed exceptions: .gitkeep writes (empty files) and archive directory
writes (new files, not read-modify-write).

This complements atomic-write.test.cjs which tests the helper itself.
If someone later adds a bare writeFileSync to these files without using
the atomic helper, this test will catch it.

Review feedback on #2056 from @trek-e.
2026-04-11 03:30:05 -07:00
Tibsfox
cad40fff8b fix(core): extend atomicWriteFileSync to milestone, phase, and frontmatter (#1972)
Replace 11 fs.writeFileSync calls with atomicWriteFileSync in three
files that write to .planning/ artifacts (ROADMAP.md, REQUIREMENTS.md,
MILESTONES.md, and frontmatter updates). This prevents partial writes
from corrupting planning files on crash or power loss.

Skipped low-risk writes: .gitkeep (empty files) and archive directory
writes (new files, not read-modify-write).

Files changed:
- milestone.cjs: 5 sites (REQUIREMENTS.md, MILESTONES.md)
- phase.cjs: 5 sites (ROADMAP.md, REQUIREMENTS.md)
- frontmatter.cjs: 2 sites (arbitrary .planning/ files)

Closes #1972

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:25:06 -07:00
Tibsfox
053269823b test(health): add degradation test for missing phasesDir (#1973)
Covers the behavior change from independent per-check degradation to
coupled degradation when the hoisted readdirSync throws. Asserts that
cmdValidateHealth completes without throwing and emits zero phase
directory warnings (W005, W006, W007, W009, I001) when phasesDir
doesn't exist.

Review feedback on #2053 from @trek-e.
2026-04-11 03:24:49 -07:00
Tibsfox
08d1767a1b perf(health): merge four readdirSync passes into one in cmdValidateHealth (#1973)
cmdValidateHealth read the phases directory four separate times for
checks 6 (naming), 7 (orphaned plans), 7b (validation artifacts), and
8 (roadmap cross-reference). Hoist the directory listing into a single
readdirSync call with a shared Map of per-phase file lists.

Reduces syscalls from ~3N+1 to N+1 where N is the number of phase
directories.

Closes #1973

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:23:56 -07:00
Tom Boucher
6c2795598a docs: release notes and documentation updates for v1.35.0 (#2079)
Closes #2080
2026-04-10 22:29:06 -04:00
github-actions[bot]
1274e0e82c chore: bump version to 1.35.0 for release 2026-04-11 02:12:57 +00:00
Tom Boucher
7a674c81b7 feat(install): add Qwen Code runtime support (#2019) (#2077)
Adds Qwen Code as a supported installation target. Users can now run
`npx get-shit-done-cc --qwen` to install all 68+ GSD commands as skills
to `~/.qwen/skills/gsd-*/SKILL.md`, following the same open standard as
Claude Code 2.1.88+.

Changes:
- `bin/install.js`: --qwen flag, getDirName/getGlobalDir/getConfigDirFromHome
  support, QWEN_CONFIG_DIR env var, install/uninstall pipelines, interactive
  picker option 12 (Trae→13, Windsurf→14, All→15), .qwen path replacements in
  copyCommandsAsClaudeSkills and copyWithPathReplacement, legacy commands/gsd
  cleanup, fix processAttribution hardcoded 'claude' → runtime-aware
- `README.md`: Qwen Code in tagline, runtime list, verification commands,
  skills format NOTE, install/uninstall examples, flag reference, env vars
- `tests/qwen-install.test.cjs`: 13 tests covering directory mapping, env var
  precedence, install/uninstall lifecycle, artifact preservation
- `tests/qwen-skills-migration.test.cjs`: 11 tests covering frontmatter
  conversion, path replacement, stale skill cleanup, SKILL.md format validation
- `tests/multi-runtime-select.test.cjs`: Updated for new option numbering

Closes #2019

Co-authored-by: Muhammad <basirovmb1988@gmail.com>
Co-authored-by: Jonathan Lima <eezyjb@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:55:44 -04:00
Tom Boucher
5c0e801322 fix(executor): prohibit git clean in worktree context to prevent file deletions (#2075) (#2076)
Running git clean inside a worktree treats files committed on the feature
branch as untracked — from the worktree's perspective they were never staged.
The executor deletes them, then commits only its own deliverables; when the
worktree branch merges back the deletions land on the main branch, destroying
prior-wave work (documented across 8 incidents, including commit c6f4753
"Wave 2 executor incorrectly ran git-clean on the worktree").

- Add <destructive_git_prohibition> block to gsd-executor.md explaining
  exactly why git clean is unsafe in worktree context and what to use instead
- Add regression tests (bug-2075-worktree-deletion-safeguards.test.cjs)
  covering Failure Mode B (git clean prohibition), Failure Mode A
  (worktree_branch_check presence audit across all worktree-spawning
  workflows), and both defense-in-depth deletion checks from #1977

Failure Mode A and defense-in-depth checks (post-commit --diff-filter=D in
gsd-executor.md, pre-merge --diff-filter=D in execute-phase.md) were already
implemented — tests confirm they remain in place.

Fixes #2075

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:37:08 -04:00
Tom Boucher
96eef85c40 feat(import): add /gsd-from-gsd2 reverse migration from GSD-2 to v1 (#2072)
Adds a new command and CLI subcommand that converts a GSD-2 `.gsd/`
project back to GSD v1 `.planning/` format — the reverse of the forward
migration GSD-2 ships.

Closes #2069

Maps GSD-2's Milestone → Slice → Task hierarchy to v1's flat
Milestone sections → Phase → Plan structure. Slices are numbered
sequentially across all milestones; tasks become numbered plans within
their phase. Completion state, research files, and summaries are
preserved.

New files:
- `get-shit-done/bin/lib/gsd2-import.cjs` — parser, transformer, writer
- `commands/gsd/from-gsd2.md` — slash command definition
- `tests/gsd2-import.test.cjs` — 41 tests, 99.21% statement coverage

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:30:13 -04:00
Tom Boucher
2b4b48401c fix(workflow): prevent silent SUMMARY.md loss on worktree force-removal (#2073)
Closes #2070

Two-layer fix for the bug where executor agents in worktree isolation mode
could leave SUMMARY.md uncommitted, then have it silently destroyed by
`git worktree remove --force` during post-wave cleanup.

Layer 1 — Clarify executor instruction (execute-phase.md):
Added explicit REQUIRED note to the <parallel_execution> block making
clear that SUMMARY.md MUST be committed before the agent returns,
and that the git_commit_metadata step in execute-plan.md handles the
SUMMARY.md-only commit path automatically in worktree mode.

Layer 2 — Orchestrator safety net (execute-phase.md):
Before force-removing each worktree, check for any uncommitted SUMMARY.md
files. If found, commit them on the worktree branch and re-merge into the
main branch before removal. This prevents data loss even when an executor
skips the commit step due to misinterpreting the "do not modify
orchestrator files" instruction.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:29:56 -04:00
Tom Boucher
f8cf54bd01 fix(agents): add Context7 CLI fallback for MCP tools broken by tools: restriction (#2074)
Closes #1885

The upstream bug anthropics/claude-code#13898 causes Claude Code to strip all
inherited MCP tools from agents that declare a `tools:` frontmatter restriction,
making `mcp__context7__*` declarations in agent frontmatter completely inert.

Implements Fix 2 from issue #1885 (trek-e's chosen approach): replace the
`<mcp_tool_usage>` block in gsd-executor and gsd-planner with a
`<documentation_lookup>` block that checks for MCP availability first, then
falls back to the Context7 CLI via Bash (`npx --yes ctx7@latest`). Adds the
same `<documentation_lookup>` block to the six researcher agents that declare
MCP tools but lacked any fallback instruction.

Agents fixed (8 total):
- gsd-executor (had <mcp_tool_usage>, now <documentation_lookup> with CLI fallback)
- gsd-planner (had <mcp_tool_usage>, now compact <documentation_lookup>; stays under 45K limit)
- gsd-phase-researcher (new <documentation_lookup> block)
- gsd-project-researcher (new <documentation_lookup> block)
- gsd-ui-researcher (new <documentation_lookup> block)
- gsd-advisor-researcher (new <documentation_lookup> block)
- gsd-ai-researcher (new <documentation_lookup> block)
- gsd-domain-researcher (new <documentation_lookup> block)

When the upstream Claude Code bug is fixed, the MCP path in step 1 of the block
will become active automatically — no agent changes needed.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:29:37 -04:00
Maxim Brashenko
cc04baa524 feat(statusline): surface GSD milestone/phase/status when no active todo (#1990)
When no in_progress todo is active, fill the middle slot of
gsd-statusline.js with GSD state read from .planning/STATE.md.

Format: <milestone> · <status> · <phase name> (N/total)

- Add readGsdState() — walks up from workspace dir looking for
  .planning/STATE.md (bounded at 10 levels / home dir)
- Add parseStateMd() — reads YAML frontmatter (status, milestone,
  milestone_name) and Phase line from body; falls back to body Status:
  parsing for older STATE.md files without frontmatter
- Add formatGsdState() — joins available parts with ' · ', degrades
  gracefully when fields are missing
- Wrap stdin handler in runStatusline() and export helpers so unit
  tests can require the file without triggering the script behavior

Strictly additive: active todo wins the slot (unchanged); missing
STATE.md leaves the slot empty (unchanged). Only the "no active todo
AND STATE.md present" path is new.

Uses the YAML frontmatter added for #628, completing the statusline
display that issue originally proposed.

Closes #1989
2026-04-10 15:56:19 -04:00
Tibsfox
46cc28251a feat(review): add Qwen Code and Cursor CLI as peer reviewers (#1966)
* feat(review): add Qwen Code and Cursor CLI as peer reviewers (#1938, #1960)

Add qwen and cursor to the /gsd-review pipeline following the
established pattern from CodeRabbit and OpenCode integrations:
- CLI detection via command -v
- --qwen and --cursor flags
- Invocation blocks with empty-output fallback
- Install help URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(review): correct qwen/cursor invocations and add doc surfaces (#1966)

Address review feedback from trek-e, kturk, and lawsontaylor:

- Use positional form for qwen (qwen "prompt") — -p flag is deprecated
  upstream and will be removed in a future version
- Fix cursor invocation to use cursor agent -p --mode ask --trust
  instead of cursor --prompt which launches the editor GUI
- Add --qwen and --cursor flags to COMMANDS.md, FEATURES.md, help.md,
  commands/gsd/review.md, and localized docs (ja-JP, ko-KR)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:19:56 -04:00
Tibsfox
7857d35dc1 refactor(workflow): deduplicate deviation rules and commit protocol (#1968) (#2057)
The deviation rules and task commit protocol were duplicated between
gsd-executor.md (agent definition) and execute-plan.md (workflow).
The copies had diverged: the agent had scope boundary and fix attempt
limits the workflow lacked; the workflow had 3 extra commit types
(perf, docs, style) the agent lacked.

Consolidate gsd-executor.md as the single source of truth:
- Add missing commit types (perf, docs, style) to gsd-executor.md
- Replace execute-plan.md's ~90 lines of duplicated content with
  concise references to the agent definition

Saves ~1,600 tokens per workflow spawn and eliminates maintenance
drift between the two copies.

Closes #1968

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 15:17:03 -04:00
Andreas Brauchli
2a08f11f46 fix(config): allow intel.enabled in config-set whitelist (#2021)
`intel.enabled` is the documented opt-in for the intel subsystem
(see commands/gsd/intel.md and docs/CONFIGURATION.md), but it was
missing from VALID_CONFIG_KEYS in config.cjs, so the canonical
command failed:

  $ gsd-tools config-set intel.enabled true
  Error: Unknown config key: "intel.enabled"

Add the key to the whitelist, document it under a new "Intel Fields"
section in planning-config.md alongside the other namespaced fields,
and cover it with a config-set test.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:00:38 -04:00
Berkay Karaman
d85a42c7ad fix(install): guard writeSettings against null settingsPath for cline runtime (#2035)
* fix(install): guard writeSettings against null settingsPath for cline runtime

Cline returns settingsPath: null from install() because it uses .clinerules
instead of settings.json. The finishInstall() guard was missing !isCline,
causing a crash with ERR_INVALID_ARG_TYPE when installing with the cline runtime.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test(cline): add regression tests for ERR_INVALID_ARG_TYPE null settingsPath guard

Adds two regression tests to tests/cline-install.test.cjs for gsd-build/get-shit-done#2044:
- Assert install(false, 'cline') does not throw ERR_INVALID_ARG_TYPE
- Assert settings.json is not written for cline runtime

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test(cline): fix regression tests to directly call finishInstall with null settingsPath

The previous regression tests called install() which returns early for cline
before reaching finishInstall(), so the crash was never exercised. Fix by:
- Exporting finishInstall from bin/install.js
- Calling finishInstall(null, null, ..., 'cline') directly so the null
  settingsPath guard is actually tested

Tests now fail (ERR_INVALID_ARG_TYPE) without the fix and pass with it.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:58:16 -04:00
Tom Boucher
50537e5f67 fix(install): extend buildHookCommand to .sh hooks — absolute quoted paths (#2049)
* fix(autonomous): add Agent to allowed-tools in gsd-autonomous skill

Closes #2043

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(install): extend buildHookCommand to .sh hooks — absolute quoted paths

- Extend buildHookCommand() to branch on .sh suffix, using 'bash' runner
  instead of 'node', so all hook paths go through the same quoted-path
  construction: bash "/absolute/path/hooks/gsd-*.sh"
- Replace three manual 'bash ' + targetDir + '...' concatenations for
  gsd-validate-commit.sh, gsd-session-state.sh, gsd-phase-boundary.sh
  with buildHookCommand(targetDir, hookName) for the global-install branch
- Global .sh hook paths are now double-quoted, fixing invocation failure
  when the config dir path contains spaces (Windows usernames, #2045)
- Adds regression tests in tests/sh-hook-paths.test.cjs

Closes #2045
Closes #2046

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:55:27 -04:00
Tom Boucher
6960fd28fe fix(autonomous): add Agent to allowed-tools in gsd-autonomous skill (#2048)
Closes #2043

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:55:13 -04:00
Tom Boucher
fd3a808b7e fix(workflow): offer recommendation instead of hard redirect for missing UI-SPEC.md (#2039)
* fix(workflow): offer recommendation instead of hard redirect when UI-SPEC.md missing

When plan-phase detects frontend indicators but no UI-SPEC.md, replace the
AskUserQuestion hard-exit block with an offer_next-style recommendation that
displays /gsd-ui-phase as the primary next step and /gsd-plan-phase --skip-ui
as the bypass option. Also registers --skip-ui as a parsed flag so it silently
bypasses the UI gate.

Closes #2011

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: retrigger CI — resolve stale macOS check

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:41:59 -04:00
Tom Boucher
47badff2ee fix(workflow): add plain-text fallback for AskUserQuestion on non-Claude runtimes (#2042)
AskUserQuestion is a Claude Code-only tool. When running GSD on OpenAI Codex,
Gemini CLI, or other non-Claude runtimes, the model renders the tool call as a
markdown code block instead of executing it, so the interactive TUI never
appears and the session stalls without collecting user input.

The workflow.text_mode / --text flag mechanism already handles this in 5 of
the 37 affected workflows. This commit adds the same TEXT_MODE fallback
instruction to all remaining 32 workflows so that, when text_mode is enabled,
every AskUserQuestion call is replaced with a plain-text numbered list that
any runtime can handle.

Fixes #2012

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:30:46 -04:00
Tom Boucher
c8ab20b0a6 fix(workflow): use XcodeGen for iOS app scaffold — prevent SPM executable instead of .xcodeproj (#2041)
Adds ios-scaffold.md reference that explicitly prohibits Package.swift +
.executableTarget for iOS apps (produces macOS CLI, not iOS app bundle),
requires project.yml + xcodegen generate to create a proper .xcodeproj,
and documents SwiftUI API availability tiers (iOS 16 vs 17). Adds iOS
anti-patterns 28-29 to universal-anti-patterns.md and wires the reference
into gsd-executor.md so executors see the guidance during iOS plan execution.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:30:24 -04:00
Tom Boucher
083b26550b fix(worktree): executor deletion verification and pre-merge deletion block (#2040)
* fix(worktree): use reset --hard in worktree_branch_check to correctly set base (#2015)

The worktree_branch_check in execute-phase.md and quick.md used
git reset --soft as the fallback when EnterWorktree created a branch
from main/master instead of the current feature branch HEAD. --soft
moves the HEAD pointer but leaves working tree files from main unchanged,
so the executor worked against stale code and produced commits containing
the entire feature branch diff as deletions.

Fix: replace git reset --soft with git reset --hard in both workflow files.
--hard resets both the HEAD pointer and the working tree to the expected
base commit. It is safe in a fresh worktree that has no user changes.

Adds 4 regression tests (2 per workflow) verifying that the check uses
--hard and does not contain --soft.

* fix(worktree): executor deletion verification and pre-merge deletion block (#1977)

- Remove Windows-only qualifier from worktree_branch_check in execute-plan.md
  (the EnterWorktree base-branch bug affects all platforms, not just Windows)
- Add post-commit --diff-filter=D deletion check to gsd-executor.md task_commit_protocol
  so unexpected file deletions are flagged immediately after each task commit
- Add pre-merge --diff-filter=D deletion guard to execute-phase.md worktree cleanup
  so worktree branches containing file deletions are blocked before fast-forward merge
- Add regression test tests/worktree-safety.test.cjs covering all three behaviors

Fixes #1977

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:30:08 -04:00
Tom Boucher
fc4fcab676 fix(workflow): add gated hunk verification table to reapply-patches — structural enforcement of post-merge checks (#2037)
Adds a mandatory Hunk Verification Table output to Step 4 (columns: file,
hunk_id, signature_line, line_count, verified) and a new Step 5 gate that
STOPs with an actionable error if any row shows verified: no or the table
is absent. Prevents the LLM from silently bypassing post-merge checks by
making the next step structurally dependent on the table's presence and
content. Adds four regression tests covering table presence, column
requirements, Step 5 reference, and the gate condition.

Fixes #1999

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:29:25 -04:00
Tom Boucher
0b7dab7394 fix(workflow): auto-transition phase to complete when verify-work UAT passes with 0 issues (#2036)
After complete_session in verify-work.md, when final_status==complete and
issues==0, the workflow now executes transition.md inline (mirroring the
execute-phase pattern) to mark the phase complete in ROADMAP.md and STATE.md.
Security gate still gates the transition: if enforcement is enabled and no
SECURITY.md exists, the workflow suggests /gsd-secure-phase instead.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:29:09 -04:00
Tom Boucher
17bb9f8a25 fix(worktree): use reset --hard in worktree_branch_check to correctly set base (#2015) (#2028)
The worktree_branch_check in execute-phase.md and quick.md used
git reset --soft as the fallback when EnterWorktree created a branch
from main/master instead of the current feature branch HEAD. --soft
moves the HEAD pointer but leaves working tree files from main unchanged,
so the executor worked against stale code and produced commits containing
the entire feature branch diff as deletions.

Fix: replace git reset --soft with git reset --hard in both workflow files.
--hard resets both the HEAD pointer and the working tree to the expected
base commit. It is safe in a fresh worktree that has no user changes.

Adds 4 regression tests (2 per workflow) verifying that the check uses
--hard and does not contain --soft.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:07:13 -04:00
Tom Boucher
7f11362952 fix(phase): scan .planning/phases/ for orphan dirs in phase add (#2034)
cmdPhaseAdd computed maxPhase from ROADMAP.md only, allowing orphan
directories on disk (untracked in roadmap) to silently collide with
newly added phases. The new phase's mkdirSync succeeded against the
existing directory, contaminating it with fresh content.

Fix: take max(roadmapMax, diskMax) where diskMax scans
.planning/phases/ and strips optional project_code prefix before
parsing the leading integer. Backlog orphans (>=999) are skipped.

Adds 3 regression tests covering:
- orphan dir with number higher than roadmap max
- prefixed orphan dirs (project_code-NN-slug)
- no collision when orphan number is lower than roadmap max

Fixes #2026

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:04:33 -04:00
Tom Boucher
aa3e9cfaf4 feat(install): add Cline as a first-class runtime (#1991) (#2032)
Cline was documented as a supported runtime but was absent from
bin/install.js. This adds full Cline support:

- Registers --cline CLI flag and adds 'cline' to --all list
- Adds getDirName/getConfigDirFromHome/getGlobalDir entries (CLINE_CONFIG_DIR env var respected)
- Adds convertClaudeToCliineMarkdown() and convertClaudeAgentToClineAgent()
- Wires Cline into copyWithPathReplacement(), install(), writeManifest(), finishInstall()
- Local install writes to project root (like Claude Code), not .cline/ subdirectory
- Generates .clinerules at install root with GSD integration rules
- Installs get-shit-done engine and agents with path/brand replacement
- Adds Cline as option 4 in interactive menu (13-runtime menu, All = 14)
- Updates banner description to include Cline
- Exports convertClaudeToCliineMarkdown and convertClaudeAgentToClineAgent for testing
- Adds tests/cline-install.test.cjs with 17 regression tests
- Updates multi-runtime-select, copilot-install, kilo-install tests for new option numbers

Fixes #1991

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:47:22 -04:00
Tom Boucher
14c3ef5b1f fix(workflow): preserve structural planning commits in gsd-pr-branch (#2031)
The previous implementation filtered ALL .planning/-only commits, including
milestone archive commits, STATE.md, ROADMAP.md, and PROJECT.md updates.
Merging the PR branch then left the target with inconsistent planning state.

Fixes by distinguishing two categories of .planning/ commits:
- Structural (STATE.md, ROADMAP.md, MILESTONES.md, PROJECT.md,
  REQUIREMENTS.md, milestones/**): INCLUDED in PR branch
- Transient (phases/, quick/, research/, threads/, todos/, debug/,
  seeds/, codebase/, ui-reviews/): EXCLUDED from PR branch

The git rm in create_pr_branch is now scoped to transient subdirectories
only, so structural files survive cherry-pick into the PR branch.

Adds regression test asserting structural file handling is documented.

Closes #2004

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:25:55 -04:00
Tom Boucher
0a4ae79b7b fix(workflow): route offer_next based on CONTEXT.md existence for next phase (#2030)
When a phase completes, the offer_next step now checks whether CONTEXT.md
already exists for the next phase before presenting options.

- If CONTEXT.md is absent: /gsd-discuss-phase is the recommended first step
- If CONTEXT.md exists: /gsd-plan-phase is the recommended first step

Adds regression test asserting conditional routing is present.

Closes #2002

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:19:32 -04:00
Tom Boucher
d858f51a68 fix(phase): update plan count when current milestone is inside <details> (#2005) (#2029)
replaceInCurrentMilestone() locates content by finding the last </details>
in the ROADMAP and only operates on text after that boundary. When the
current (in-progress) milestone section is itself wrapped in a <details>
block (the standard /gsd-new-project layout), the phase section's
**Plans:** counter lives INSIDE that block. The replacement target ends up
in the empty space after the block's closing </details>, so the regex never
matches and the plan count stays at 0/N permanently.

Fix: switch the plan count update to use direct .replace() on the full
roadmapContent, consistent with the checkbox and progress table updates
that already use this pattern. The phase-scoped heading regex
(### Phase N: ...) is specific enough to avoid matching archived phases.

Adds two regression tests covering: (1) plan count updates inside a
<details>-wrapped current milestone, and (2) phase 2 plan count is not
corrupted when completing phase 1.
2026-04-10 11:15:59 -04:00
Tom Boucher
14b8add69e fix(verify): suppress W006 for phases with unchecked ROADMAP summary checkbox (#2009) (#2027)
W006 (Phase in ROADMAP.md but no directory on disk) fired for every phase
listed in ROADMAP.md that lacked a phase directory, including future phases
that haven't been started yet. This produced false DEGRADED health status
on any project with more than one phase planned.

Fix: before emitting W006, check the ROADMAP summary list for a
'- [ ] **Phase N:**' unchecked checkbox. Phases explicitly marked as not
yet started are intentionally absent from disk -- skip W006 for them.
Phases with a checked checkbox ([x]) or with no summary entry still
trigger W006 as before.

Adds two regression tests: one verifying W006 is suppressed for unchecked
phases, and one verifying W006 still fires for checked phases with no disk
directory.
2026-04-10 11:03:10 -04:00
Tom Boucher
0f77681df4 fix(commit): skip staging deletions for missing files when --files is explicit (#2014) (#2025)
When gsd-tools commit is invoked with --files and one of the listed files
does not exist on disk, the previous code called git rm --cached which
staged and committed a deletion. This silently removed tracked planning
files (STATE.md, ROADMAP.md) from the repository whenever they were
temporarily absent on disk.

Fix: when explicit --files are provided, skip files that do not exist
rather than staging their deletion. Only the default (.planning/ staging
path) retains the git rm --cached behavior so genuinely removed planning
files are not left dangling in the index.

Adds regression tests verifying that missing files in an explicit --files
list are never staged as deletions.
2026-04-10 10:56:09 -04:00
Tibsfox
21d2bd039d fix(hooks): skip read-guard advisory on Claude Code runtime (#2001)
* fix(hooks): skip read-guard advisory on Claude Code runtime (#1984)

Claude Code natively enforces read-before-edit at the runtime level,
so the gsd-read-guard.js advisory is redundant — it wastes ~80 tokens
per Write/Edit call and clutters tool flow with system-reminder noise.

Add early exit when CLAUDE_SESSION_ID is set (standard Claude Code
session env var). Non-Claude runtimes (OpenCode, Gemini, etc.) that
lack native read-before-edit enforcement continue to receive the
advisory as before.

Closes #1984

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hooks): sanitize runHook env to prevent test failures in Claude Code

The runHook() test helper now blanks CLAUDE_SESSION_ID so positive-path
tests pass even when the test suite runs inside a Claude Code session.
The new skip test passes the env var explicitly via envOverrides.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 10:50:35 -04:00
Tibsfox
04e9bd5e76 fix(phase): update overview bullet checkbox on phase complete (#1998) (#2000)
cmdPhaseComplete used replaceInCurrentMilestone() to update the overview
bullet checkbox (- [ ] → - [x]), but that function scopes replacements
to content after the last </details> tag. The current milestone's
overview bullets appear before any <details> blocks, so the replacement
never matched.

Switch to direct .replace() which correctly finds and updates the first
matching unchecked checkbox. This is safe because unchecked checkboxes
([ ]) only exist in the current milestone — archived phases have [x].

Closes #1998

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 10:50:17 -04:00
Lakshman Turlapati
d0ab1d8aaa fix(codex): convert /gsd- workflow commands to $gsd- during installation (#1994)
The convertSlashCommandsToCodexSkillMentions function only converted
colon-style skill invocations (/gsd:command) but not hyphen-style
command references (/gsd-command) used in workflow output templates
(Next Up blocks, phase completion messages, etc.). This caused Codex
users to see /gsd- prefixed commands instead of $gsd- in chat output.

- Add regex to convert /gsd-command → $gsd-command with negative
  lookbehind to exclude file paths (e.g. bin/gsd-tools.cjs)
- Strip /clear references in Codex output (no Codex equivalent)
- Add 5 regression tests covering command conversion, path
  preservation, and /clear removal

Co-authored-by: Lakshman <lakshman@lakshman-GG9LQ90J61.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:49:58 -04:00
Ned Malki
f8526b5c01 fix: complete planningDir migration for config CRUD, template fill, and verify (#1986)
* fix(config): route CRUD through planningDir to honor GSD_PROJECT

PR #1484 added planningDir(cwd) and the GSD_PROJECT env var so a workspace
can host multiple projects under .planning/{project}/. loadConfig() in
core.cjs (line 256) was migrated at the time, but the four CRUD entry points
in config.cjs and the planningPaths() helper in core.cjs were left resolving
against planningRoot(cwd).

The result was a silent split-brain in any multi-project workspace:

  - cmdConfigGet, setConfigValue, ensureConfigFile, cmdConfigNewProject
    all wrote to and read from .planning/config.json
  - loadConfig read from .planning/{GSD_PROJECT}/config.json

So `gsd-tools config-get workflow.discuss_mode` returned "unset" even when
the value was correctly stored in the project-routed file, because the
reader and writer pointed at different paths.

planningPaths() carried a comment that "Shared paths (project, config)
always resolve to the root .planning/" which described the original intent,
but loadConfig() already contradicted that intent for config.json. project
and config now both resolve through planningDir() so the contract matches
the only function that successfully read config.json in the multi-project
case.

Single-project users (no GSD_PROJECT set) are unaffected: planningRoot()
and planningDir() return the same path when no project is configured.

Verification: in a workspace with .planning/projectA/config.json and
GSD_PROJECT=projectA, `gsd-tools config-get workflow.discuss_mode` now
returns the value instead of "Error: Key not found". Backward compat
verified by running the same command without GSD_PROJECT in a
single-project layout.

Affected sites:
- get-shit-done/bin/lib/config.cjs cmdConfigNewProject (line 199)
- get-shit-done/bin/lib/config.cjs ensureConfigFile (line 244)
- get-shit-done/bin/lib/config.cjs setConfigValue (line 294)
- get-shit-done/bin/lib/config.cjs cmdConfigGet (line 367)
- get-shit-done/bin/lib/core.cjs planningPaths.config (line 706)
- get-shit-done/bin/lib/core.cjs planningPaths.project (line 705)

* fix(template): emit project-aware references in template fill plan

The template fill plan body hardcoded `@.planning/PROJECT.md`,
`@.planning/ROADMAP.md`, and `@.planning/STATE.md` references. In a
multi-project workspace these resolve to nothing because the actual
project, roadmap, and state files live under .planning/{GSD_PROJECT}/.

`gsd-tools verify references` reports them as missing on every PLAN.md
generated by template fill in any GSD_PROJECT-routed workspace.

Fix: route the references through planningDir(cwd), normalize via the
existing toPosixPath helper for cross-platform path consistency, and
embed them as `@<relative-path>` matching the phase-relative reference
pattern used elsewhere in the file.

Single-project users (no GSD_PROJECT set) get exactly the same output
as before because planningDir() falls back to .planning/ when no project
is active.

Affected site: get-shit-done/bin/lib/template.cjs cmdTemplateFill plan
branch (lines 142-145, the @.planning/ refs in the Context section).

* fix(verify): planningDir for cmdValidateHealth and regenerateState

cmdValidateHealth resolved projectPath and configPath via planningRoot(cwd)
while ROADMAP/STATE/phases/requirements went through planningDir(cwd). The
inconsistency reported "missing PROJECT.md" and "missing config.json" in
multi-project layouts even when the project-routed copies existed and the
config CRUD writers (now also routed by the previous commit in this PR)
were writing to them.

regenerateState (the /gsd:health --repair STATE.md regeneration path)
hardcoded `See: .planning/PROJECT.md` in the generated body, which fails
the same reference check it just regenerated for in any GSD_PROJECT-routed
workspace.

Fix: route both sites through planningDir(cwd). For regenerateState, derive
a POSIX-style relative reference from the resolved path so the reference
matches verify references' resolution rules. Also dropped the planningRoot
import from verify.cjs since it is no longer used after this change.

Single-project users (no GSD_PROJECT set) get the same paths as before:
planningDir() falls back to .planning/ when no project is configured.

Affected sites:
- get-shit-done/bin/lib/verify.cjs cmdValidateHealth (lines 536-541)
- get-shit-done/bin/lib/verify.cjs regenerateState repair (line 865)
- get-shit-done/bin/lib/verify.cjs core.cjs import (line 8, dropped unused
  planningRoot)
2026-04-10 10:49:42 -04:00
Tibsfox
adec4eef48 fix(worktree): use hard reset to correct file tree when branch base is wrong (#1982)
* fix(worktree): use hard reset to correct file tree when branch base is wrong (#1981)

The worktree_branch_check mitigation detects when EnterWorktree creates
branches from main instead of the current feature branch, but used
git reset --soft to correct it. This only fixed the commit pointer —
the working tree still contained main's files, causing silent data loss
on merge-back when the agent's commits overwrote feature branch code.

Changed to git reset --hard which safely corrects both pointer and file
tree (the check runs before any agent work, so no changes to lose).
Also removed the broken rebase --onto attempt in execute-phase.md that
could replay main's commits onto the feature branch, and added post-reset
verification that aborts if the correction fails.

Updated documentation from "Windows" to "all platforms" since the
upstream EnterWorktree bug affects macOS, Linux, and Windows alike.

Closes #1981

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(worktree): update settings.md worktree description to say cross-platform

Aligns with the workflow file updates — the EnterWorktree base-branch
bug affects all platforms, not just Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 10:49:20 -04:00
Fana
33575ba91d feat: /gsd-ai-integration-phase + /gsd-eval-review — AI framework selection and eval coverage layer (#1971)
* feat: /gsd:ai-phase + /gsd:eval-review — AI evals and framework selection layer

Adds a structured AI development layer to GSD with 5 new agents, 2 new
commands, 2 new workflows, 2 reference files, and 1 template.

Commands:
- /gsd:ai-phase [N] — pre-planning AI design contract (inserts between
  discuss-phase and plan-phase). Orchestrates 4 agents in sequence:
  framework-selector → ai-researcher → domain-researcher → eval-planner.
  Output: AI-SPEC.md with framework decision, implementation guidance,
  domain expert context, and evaluation strategy.
- /gsd:eval-review [N] — retroactive eval coverage audit. Scores each
  planned eval dimension as COVERED/PARTIAL/MISSING. Output: EVAL-REVIEW.md
  with 0-100 score, verdict, and remediation plan.

Agents:
- gsd-framework-selector: interactive decision matrix (6 questions) →
  scored framework recommendation for CrewAI, LlamaIndex, LangChain,
  LangGraph, OpenAI Agents SDK, Claude Agent SDK, AutoGen/AG2, Haystack
- gsd-ai-researcher: fetches official framework docs + writes AI systems
  best practices (Pydantic structured outputs, async-first, prompt
  discipline, context window management, cost/latency budget)
- gsd-domain-researcher: researches business domain and use-case context —
  surfaces domain expert evaluation criteria, industry failure modes,
  regulatory constraints, and practitioner rubric ingredients before
  eval-planner writes measurable criteria
- gsd-eval-planner: designs evaluation strategy grounded in domain context;
  defaults to Arize Phoenix (tracing) + RAGAS (RAG eval) with detect-first
  guard for existing tooling
- gsd-eval-auditor: retroactive codebase scan → scores eval coverage

Integration points:
- plan-phase: non-blocking nudge (step 4.5) when AI keywords detected and
  no AI-SPEC.md present
- settings: new workflow.ai_phase toggle (default on)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: refine ai-integration-phase layer — rename, house style, consistency fixes

Amends the ai-evals framework layer (df8cb6c) with post-review improvements
before opening upstream PR.

Rename /gsd:ai-phase → /gsd:ai-integration-phase:
- Renamed commands/gsd/ai-phase.md → ai-integration-phase.md
- Renamed get-shit-done/workflows/ai-phase.md → ai-integration-phase.md
- Updated config key: workflow.ai_phase → workflow.ai_integration_phase
- Updated repair action: addAiPhaseKey → addAiIntegrationPhaseKey
- Updated all 84 cross-references across agents, workflows, templates, tests

Consistency fixes (same class as PR #1380 review):
- commands/gsd: objective described 3-agent chain, missing gsd-domain-researcher
- workflows/ai-integration-phase: purpose tag described 3-agent chain + "locks
  three things" — updated to 4 agents + 4 outputs
- workflows/ai-integration-phase: missing DOMAIN_MODEL resolve-model call in
  step 1 (domain-researcher was spawned in step 7.5 with no model variable)
- workflows/ai-integration-phase: fractional step ## 7.5 renumbered to integers
  (steps 8–12 shifted)

Agent house style (GSD meta-prompting conformance):
- All 5 new agents refactored to execution_flow + step name="" structure
- Role blocks compressed to 2 lines (removed verbose "Core responsibilities")
- Added skills: frontmatter to all 5 agents (agent-frontmatter tests)
- Added # hooks: commented pattern to file-writing agents
- Added ALWAYS use Write tool anti-heredoc instruction to file-writing agents
- Line reductions: ai-researcher −41%, domain-researcher −25%, eval-planner −26%,
  eval-auditor −25%, framework-selector −9%

Test coverage (tests/ai-evals.test.cjs — 48 tests):
- CONFIG: workflow.ai_integration_phase defaults and config-set/get
- HEALTH: W010 warning emission and addAiIntegrationPhaseKey repair
- TEMPLATE: AI-SPEC.md section completeness (10 sections)
- COMMAND: ai-integration-phase + eval-review frontmatter validity
- AGENTS: all 5 new agent files exist
- REFERENCES: ai-evals.md + ai-frameworks.md exist and are non-empty
- WORKFLOW: plan-phase nudge integration, workflow files exist + agent coverage

603/603 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add Google ADK to framework selector and reference matrix

Google ADK (released March 2025) was missing from the framework options.
Adds Python + Java multi-agent framework optimised for Gemini / Vertex AI.

- get-shit-done/references/ai-frameworks.md: add Google ADK profile (type,
  language, model support, best for, avoid if, strengths, weaknesses, eval
  concerns); update Quick Picks, By System Type, and By Model Commitment tables
- agents/gsd-framework-selector.md: add "Google (Gemini)" to model provider
  interview question
- agents/gsd-ai-researcher.md: add Google ADK docs URL to documentation_sources

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: adapt to upstream conventions post-rebase

- Remove skills: frontmatter from all 5 new agents (upstream changed
  convention — skills: breaks Gemini CLI and must not be present)
- Add workflow.ai_integration_phase to VALID_CONFIG_KEYS whitelist in
  config.cjs (config-set blocked unknown keys)
- Add ai_integration_phase: true to CONFIG_DEFAULTS in core.cjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: rephrase 4b.1 line to avoid false-positive in prompt-injection scan

"contract as a Pydantic model" matched the `act as a` pattern case-insensitively.
Rephrased to "output schema using a Pydantic model".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: adapt to upstream conventions (W016, colon refs, config docs)

- Replace verify.cjs from upstream to restore W010-W015 + cmdValidateAgents,
  lost when rebase conflict was resolved with --theirs
- Add W016 (workflow.ai_integration_phase absent) inside the config try block,
  avoids collision with upstream's W010 agent-installation check
- Add addAiIntegrationPhaseKey repair case mirroring addNyquistKey pattern
- Replace /gsd: colon format with /gsd- hyphen format across all new files
  (agents, workflows, templates, verify.cjs) per stale-colon-refs guard (#1748)
- Add workflow.ai_integration_phase to planning-config.md reference table
- Add ai_integration_phase → workflow.ai_integration_phase to NAMESPACE_MAP
  in config-field-docs.test.cjs so CONFIG_DEFAULTS coverage check passes
- Update ai-evals tests to use W016 instead of W010

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add 5 new agents to E2E Copilot install expected list

gsd-ai-researcher, gsd-domain-researcher, gsd-eval-auditor,
gsd-eval-planner, gsd-framework-selector added to the hardcoded
expected agent list in copilot-install.test.cjs (#1890).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 10:49:00 -04:00
Tibsfox
bad9c63fcb ci: update action versions to v6 and extend CI to release/hotfix branches (#1955) (#1965)
- Update actions/checkout from v4.2.2 to v6.0.2 in release.yml and
  hotfix.yml (prevents breakage after June 2026 Node.js 20 deprecation)
- Update actions/setup-node from v4.1.0 to v6.3.0 in both workflows
- Add release/** and hotfix/** to test.yml push triggers
- Add release/** and hotfix/** to security-scan.yml PR triggers

test.yml already used v6 pins — this aligns the release pipelines.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:48:14 -04:00
Tibsfox
cb1eb7745a fix(core): preserve letter suffix case in normalizePhaseName (#1963)
* fix(core): preserve letter suffix case in normalizePhaseName (#1962)

normalizePhaseName uppercased letter suffixes (e.g., "16c" → "16C"),
causing directory/roadmap mismatches on case-sensitive filesystems.
init progress couldn't match directory "16C-name" to roadmap "16c".

Preserve original case — comparePhaseNum still uppercases for sorting
(correct), but normalizePhaseName is used for display and directory
creation where case must match the roadmap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(phase): update existing test to expect preserved letter case

The 'uppercases letters' test asserted the old behavior (3a → 03A).
With normalizePhaseName now preserving case, update expectations to
match (3a → 03a) and rename the test to 'preserves letter case'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:48:00 -04:00
Anshul Vishwakarma
49645b04aa fix(executor): enforce acceptance_criteria as hard gate, not advisory text (#1959)
The existing MANDATORY acceptance_criteria instruction is purely advisory —
executor agents read it and silently skip criteria when they run low on
context or hit complexity. This causes planned work to be dropped without
any signal to the orchestrator or verifier.

Changes:
- Replace advisory text with a structured 5-step verification loop
- Each criterion must be proven via grep/file-check/CLI command
- Agent is BLOCKED from next task until all criteria pass
- Failed criteria after 2 fix attempts logged as deviation (not silent skip)
- Self-check step now re-runs ALL acceptance criteria before SUMMARY
- Self-check also re-runs plan-level verification commands

Closes #1958

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:47:43 -04:00
storyandwine
50cce89a7c feat: support CodeBuddy runtime (#1887)
Add CodeBuddy (Tencent Cloud AI coding IDE/CLI) as a first-class
runtime in the GSD installer.

- Add --codebuddy CLI flag and interactive menu option
- Add directory mapping (.codebuddy/ local, ~/.codebuddy/ global)
- Add CODEBUDDY_CONFIG_DIR env var support
- Add markdown conversion (CLAUDE.md -> CODEBUDDY.md, .claude/ -> .codebuddy/)
- Preserve tool names (CodeBuddy uses same names as Claude Code)
- Configure settings.json hooks (Claude Code compatible hook spec)
- Add copyCommandsAsCodebuddySkills for SKILL.md format
- Add 15 tests (dir mapping, env vars, conversion, E2E install/uninstall)
- Update README.md and README.zh-CN.md
- Update existing tests for new runtime numbering

Co-authored-by: happyu <happyu@tencent.com>
2026-04-10 10:46:21 -04:00
chudeemeke
7e2217186a feat(review): add per-CLI model selection via config (#1859)
* feat(review): add per-CLI model selection via config

- Add review.models.<cli> dynamic config keys to VALID_CONFIG_KEYS
- Update review.md to read model preferences via config-get at runtime
- Null/missing values fall back to CLI defaults (backward compatible)
- Add key suggestion for common typo (review.model)
- Update planning-config reference doc

Closes #1849

* fix(review): handle absent and null model config gracefully

Address PR #1859 review feedback from @trek-e:

1. Add `|| true` to all four config-get subshell invocations in
   review.md so that an absent review.models.<cli> key does not
   produce a non-zero exit from the subshell. cmdConfigGet calls
   error() (process.exit(1)) when the key path is missing; the
   2>/dev/null suppresses the message but the exit code was still
   discarded silently. The || true makes the fall-through explicit
   and survives future set -e adoption.

2. Add `&& [ "$VAR" != "null" ]` to all four if guards. cmdConfigSet
   does not parse the literal 'null' as JSON null — it stores the
   string 'null' — and cmdConfigGet --raw returns the literal text
   'null' for that value. Without the extra guard the workflow would
   pass `-m "null"` to the CLI, which crashes. The issue spec
   documents null as the "fall back to CLI default" sentinel, so this
   restores the contract.

3. Add tests/review-model-config.test.cjs covering all five cases
   trek-e listed:
   - isValidConfigKey accepts review.models.gemini (via config-set)
   - isValidConfigKey accepts review.models.codex (via config-set)
   - review.model is rejected and suggests review.models.<cli-name>
   - config-set then config-get round-trip with a model ID
   - config-set then config-get round-trip with null (returns "null")

   Tests follow the node:test + node:assert/strict pattern from
   tests/agent-skills.test.cjs and use runGsdTools from helpers.cjs.

Closes #1849
2026-04-10 10:44:15 -04:00
yuiooo1102-droid
dcb503961a feat: harness engineering improvements — post-merge test gate, shared file isolation, behavioral verification (#1486)
* feat: harness engineering improvements — post-merge test gate, shared file isolation, behavioral verification

Three improvements inspired by Anthropic's harness engineering research
(March 2026) and real-world pain points from parallel worktree execution:

1. Post-merge test gate (execute-phase.md)
   - Run project test suite after merging each wave's worktrees
   - Catches cross-plan integration failures that individual Self-Checks miss
   - Addresses the Generator self-evaluation blind spot (agents praise own work)

2. Shared file isolation (execute-phase.md)
   - Executors no longer modify STATE.md or ROADMAP.md in parallel mode
   - Orchestrator updates tracking files centrally after merge
   - Eliminates the #1 source of merge conflicts in parallel execution

3. Behavioral verification (verify-phase.md)
   - Verifier runs project test suite and CLI commands, not just grep
   - Follows Anthropic's Generator/Evaluator separation principle
   - Tests actual behavior against success criteria, not just file existence

Real-world evidence: In a session executing 37 plans across 8 phases with
parallel worktrees, we observed:
- 4 test failures after merge that all Self-Checks missed (models.py type loss)
- STATE.md/ROADMAP.md conflicts on every single parallel merge
- Verifier reporting PASSED while merged code had broken imports

References:
- Anthropic Engineering Blog: Harness Design for Long-Running Apps (2026-03-24)
- Issue #1451: Massive git worktree problem
- Issue #1413: Autonomous execution without manual context clearing

* fix: address review feedback — test runner detection, parallel isolation, edge cases

- Replace hardcoded jest/vitest with `npm test` (reads project's scripts.test)
- Add Go detection to post-merge test gate (was only in verify-phase)
- Add 5-minute timeout to post-merge test gate to prevent pipeline stalls
- Track cumulative wave failures via WAVE_FAILURE_COUNT for cross-wave awareness
- Guard orchestrator tracking commit against unchanged files (prevent empty commits)
- Align execute-plan.md with parallel isolation model (skip STATE.md/ROADMAP.md
  updates when running in parallel mode, orchestrator handles centrally)
- Scope behavioral verification CLI checks: skip when no fixtures/test data exist,
  mark as NEEDS HUMAN instead of inventing inputs

* fix: pass PARALLEL_MODE to executor agents to activate shared file isolation

The executor spawn prompt in execute-phase.md instructed agents not to
modify STATE.md/ROADMAP.md, but execute-plan.md gates this behavior on
PARALLEL_MODE which was never defined in the executor context. This adds
the variable to the spawn prompt and wraps all three shared-file steps
(update_current_position, update_roadmap, git_commit_metadata) with
explicit conditional guards.

* fix: replace unreliable PARALLEL_MODE env var with git worktree auto-detection

Address PR #1486 review feedback (trek-e):

1. PARALLEL_MODE was never reliably set — the <env> block instructed the LLM
   to export a bash variable, but each Bash tool call runs in a fresh shell
   so the variable never persisted. Replace with self-contained worktree
   detection: `[ -f .git ]` returns true in worktrees (.git is a file) and
   false in main repos (.git is a directory). Each bash block detects
   independently with no external state dependency.

2. TEST_EXIT only checked for timeout (124) — test failures (non-zero,
   non-124) were silently ignored, making the "If tests fail" prose
   unreachable. Add full if/elif/else handling: 0=pass, 124=timeout,
   else=fail with WAVE_FAILURE_COUNT increment.

3. Add Go detection to regression_gate (was missing go.mod check).
   Replace hardcoded npx jest/vitest with npm test for consistency.

4. Renumber steps from 4/4b/4c/5/5/5b to 4a/4b/4c/4d/5/6/7/8/9.

* fix: address remaining review blockers — timeout, tracking guard, shell safety

- verify-phase.md: wrap behavioral_verification test suite in timeout 300
- execute-phase.md: gate tracking update on TEST_EXIT=0, skip on failure/timeout
- Quote all TEST_EXIT variables, add default initialization
- Add else branch for unrecognized project types
- Renumber steps to align with upstream (5.x series)

* fix: rephrase worktree success_criteria to satisfy substring test guard

The worktree mode success_criteria line literally contained "STATE.md"
and "ROADMAP.md" inside a prohibition ("No modifications to..."), but
the test guard in execute-phase-worktree-artifacts.test.cjs uses a
substring check and cannot distinguish prohibition from requirement.

Rephrase to "shared orchestrator artifacts" so the substring check
passes while preserving the same intent.
2026-04-10 10:42:45 -04:00
Tibsfox
295a5726dc fix(ui-phase): suggest discuss-phase when CONTEXT.md is missing (#1952) (#1964)
The Next Up block always suggested /gsd-plan-phase, but plan-phase
redirects to discuss-phase when CONTEXT.md doesn't exist. This caused
a confusing two-step redirect ~90% of the time since ui-phase doesn't
create CONTEXT.md.

Conditionally suggest discuss-phase or plan-phase based on CONTEXT.md
existence, matching the logic in progress.md Route B.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:02:26 -04:00
Tom Boucher
f7549d437e fix(core): resolve @file: references in gsd-tools stdout (#1891) (#1949)
Workflows used bash-specific `if [[ "$INIT" == @file:* ]]` to detect
when large JSON was written to a temp file. This syntax breaks on
PowerShell and other non-bash shells.

Intercept stdout in gsd-tools.cjs to transparently resolve @file:
references before they reach the caller, matching the existing --pick
path behavior. The bash checks in workflow files become harmless
no-ops and can be removed over time.

Co-authored-by: Tibsfox <tibsfox@tibsfox.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:40:54 -04:00
Tom Boucher
e6d2dc3be6 fix(phase): skip 999.x backlog phases in phase-add numbering (#1950)
Backlog phases use 999.x numbering and should not be counted when
calculating the next sequential phase ID. Without this fix, having
backlog phases causes the next phase to be numbered 1000+.

Co-authored-by: gg <grgbrasil@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:40:47 -04:00
Tom Boucher
4dd35f6b69 fix(state): correct TOCTOU races, busy-wait, lock cleanup, and config locking (#1944)
cmdStateUpdateProgress, cmdStateAddDecision, cmdStateAddBlocker,
cmdStateResolveBlocker, cmdStateRecordSession, and cmdStateBeginPhase from
bare readFileSync+writeStateMd to readModifyWriteStateMd, eliminating the
TOCTOU window where two concurrent callers read the same content and the
second write clobbers the first.

Atomics.wait(), matching the pattern already used in withPlanningLock in
core.cjs.

and core.cjs and register a process.on('exit') handler to unlink them on
process exit. The exit event fires even when process.exit(1) is called
inside a locked region, eliminating stale lock files after errors.

read-modify-write body of setConfigValue in a planning lock, preventing
concurrent config-set calls from losing each other's writes.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 17:39:29 -04:00
Tom Boucher
14fd090e47 docs(config): document missing config keys in planning-config.md (#1947)
* fix(core): resolve @file: references in gsd-tools stdout (#1891)

Workflows used bash-specific `if [[ "$INIT" == @file:* ]]` to detect
when large JSON was written to a temp file. This syntax breaks on
PowerShell and other non-bash shells.

Intercept stdout in gsd-tools.cjs to transparently resolve @file:
references before they reach the caller, matching the existing --pick
path behavior. The bash checks in workflow files become harmless
no-ops and can be removed over time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(config): add missing config fields to planning-config.md (#1880)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Tibsfox <tibsfox@tibsfox.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:36:47 -04:00
Tom Boucher
13faf66132 fix(installer): preserve USER-PROFILE.md and dev-preferences.md on re-install (#1945)
Running gsd-update (re-running the installer) silently deleted two
user-generated files:
- get-shit-done/USER-PROFILE.md (created by /gsd-profile-user)
- commands/gsd/dev-preferences.md (created by /gsd-profile-user)

Root causes:
1. copyWithPathReplacement() calls fs.rmSync(destDir, {recursive:true})
   before copying, wiping USER-PROFILE.md with no preserve allowlist.
2. The legacy commands/gsd/ cleanup at ~line 5211 rmSync'd the entire
   directory, wiping dev-preferences.md.
3. The backup path in profile-user.md pointed to the same directory
   that gets wiped, so the backup was also lost.

Fix:
- Add preserveUserArtifacts(destDir, fileNames) and restoreUserArtifacts()
  helpers that save/restore listed files around destructive wipes.
- Call them in install() before the get-shit-done/ copy (preserves
  USER-PROFILE.md) and before the legacy commands/gsd/ cleanup
  (preserves dev-preferences.md).
- Fix profile-user.md backup path from ~/.claude/get-shit-done/USER-PROFILE.backup.md
  to ~/.claude/USER-PROFILE.backup.md (outside the wiped directory).

Closes #1924

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 17:28:23 -04:00
Tom Boucher
60fa2936dd fix(core): add atomicWriteFileSync to prevent truncated files on kill (#1943)
Replaces direct fs.writeFileSync calls for STATE.md, ROADMAP.md, and
config.json with write-to-temp-then-rename so a process killed mid-write
cannot leave an unparseable truncated file. Falls back to direct write if
rename fails (e.g. cross-device). Adds regression tests for the helper.

Closes #1915

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 17:27:20 -04:00
Tom Boucher
f6a7b9f497 fix(milestone): prevent data loss and Backlog drop on milestone completion (#1940)
- Reorder reorganize_roadmap_and_delete_originals to commit archive files
  as a safety checkpoint BEFORE removing any originals (fixes #1913)
- Use overwrite-in-place for ROADMAP.md instead of delete-then-recreate
- Use git rm for REQUIREMENTS.md to stage deletion atomically with history
- Add 3-step Backlog preservation protocol: extract before rewrite, re-append
  after, skip silently if absent (fixes #1914)
- Update success_criteria and archival_behavior to reflect new ordering
2026-04-07 17:26:33 -04:00
Tibsfox
6d429da660 fix(milestone): replace test()+replace() with compare pattern to avoid global regex lastIndex bug (#1923)
The requirement marking function used test() then replace() on the
same global-flag regex. test() advances lastIndex, so replace() starts
from the wrong position and can miss the first match.

Replace with direct replace() + string comparison to detect changes.
Also drop unnecessary global flag from done-check patterns that only
need existence testing, and eliminate the duplicate regex construction
for the table pattern.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:26:31 -04:00
Tibsfox
8021e86038 fix(install): anchor local hook paths to $CLAUDE_PROJECT_DIR (#1906) (#1917)
Local installs wrote bare relative paths (e.g. `node .claude/hooks/...`)
into settings.json. Claude Code persists the shell's cwd between tool
calls, so a single `cd subdir` broke every hook for the rest of the
session.

Prefix all 9 local hook commands with "$CLAUDE_PROJECT_DIR"/ so path
resolution is always anchored to the project root regardless of cwd.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:26:29 -04:00
Tibsfox
7bc6668504 fix(phase): use readModifyWriteStateMd for atomic STATE.md updates in phase transitions (#1936)
cmdPhaseComplete and cmdPhasesRemove read STATE.md outside the lock
then wrote inside. A crash between the ROADMAP update (locked) and
the STATE write left them inconsistent. Wrap both STATE.md updates in
readModifyWriteStateMd to hold the lock across read-modify-write.

Also exports readModifyWriteStateMd from state.cjs for cross-module use.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:26:26 -04:00
Tibsfox
d12d31f8de perf(hooks): add .planning/ sentinel check before config read in context monitor (#1930)
The context monitor hook read and parsed config.json on every
PostToolUse event. For non-GSD projects (no .planning/ directory),
this was unnecessary I/O. Add a quick existsSync check for the
.planning/ directory before attempting to read config.json.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:25:21 -04:00
Tibsfox
602b34afb7 feat(config): add --default flag to config-get for graceful absent-key handling (#1893) (#1920)
When --default <value> is passed, config-get returns the default value
(exit 0) instead of erroring (exit 1) when the key is absent or
config.json doesn't exist. When the key IS present, --default is
ignored and the real value returned.

This lets workflows express optional config reads without defensive
`2>/dev/null || true` boilerplate that obscures intent and is fragile
under `set -e`.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:25:11 -04:00
Tibsfox
4334e49419 perf(init): hoist readdirSync and regex out of phase loop in manager (#1900)
cmdInitManager called fs.readdirSync(phasesDir) and compiled a new
RegExp inside the per-phase while loop. At 50 phases this produced
50 redundant directory scans and 50 regex compilations with full
ROADMAP content scans.

Move the directory listing before the loop and pre-extract all
checkbox states via a single matchAll pass. This reduces both
patterns from O(N^2) to O(N).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 17:25:09 -04:00
Tibsfox
28517f7b6d perf(roadmap): hoist readdirSync out of phase loop in analyze command (#1899)
cmdRoadmapAnalyze called fs.readdirSync(phasesDir) inside the
per-phase while loop, causing O(N^2) directory reads for N phases.
At 50 phases this produced 100 redundant syscalls; at 100 phases, 200.

Move the directory listing before the loop and build a lookup array
that is reused for each phase match. This reduces the pattern from
O(N^2) to O(N) directory reads.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 17:24:58 -04:00
Tibsfox
9679e18ef4 perf(config): cache isGitIgnored result per process lifetime (#1898)
loadConfig() calls isGitIgnored() which spawns a git check-ignore
subprocess. The result is stable for the process lifetime but was
being recomputed on every call. With 28+ loadConfig call sites, this
could spawn multiple redundant git subprocesses per CLI invocation.

A module-level Map cache keyed on (cwd, targetPath) ensures the
subprocess fires at most once per unique pair per process.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 17:24:54 -04:00
Tom Boucher
3895178c6a fix(uninstall): remove gsd-file-manifest.json on uninstall (#1939)
The installer writes gsd-file-manifest.json to the runtime config root
at install time but uninstall() never removed it, leaving stale metadata
after every uninstall. Add fs.rmSync for MANIFEST_NAME at the end of the
uninstall cleanup sequence.

Regression test: tests/bug-1908-uninstall-manifest.test.cjs covers both
global and local uninstall paths.

Closes #1908

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 17:19:10 -04:00
RodZ
dced50d887 docs: remove duplicate keys in CONFIGURATION.md (#1895)
The Full Schema JSON block had `context_profile` listed twice, and the
"Hook Settings" section was duplicated later in the document.
2026-04-07 08:18:20 -04:00
Tibsfox
820543ee9f feat(references): add common bug patterns checklist for debugger agent (#1780)
* feat(references): add common bug patterns checklist for debugger

Create a technology-agnostic reference of ~80%-coverage bug patterns
ordered by frequency — off-by-one, null access, async timing, state
management, imports, environment, data shape, strings, filesystem,
and error handling. The debugger agent now reads this checklist before
forming hypotheses, reducing the chance of overlooking common causes.

Closes #1746

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(references): use bold bullet format in bug patterns per GSD convention (#1746)

- Convert checklist items from '- [ ]' checkbox format to '- **label** —'
  bold bullet format matching other GSD reference files
- Scope test to <patterns> block only so <usage> section doesn't fail
  the bold-bullet assertion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 08:13:58 -04:00
Tibsfox
5c1f902204 fix(hooks): handle missing reference files gracefully during fresh install (#1878)
Add fs.existsSync() guards to all .js hook registrations in install.js,
matching the pattern already used for .sh hooks (#1817). When hooks/dist/
is missing from the npm package, the copy step produces no files but the
registration step previously ran unconditionally for .js hooks, causing
"PreToolUse:Bash hook error" on every tool invocation.

Each .js hook (check-update, context-monitor, prompt-guard, read-guard,
workflow-guard) now verifies the target file exists before registering
in settings.json, and emits a skip warning when the file is absent.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:13:52 -04:00
Tibsfox
40f8286ee3 fix(docs): correct mode and discuss_mode allowed values in planning-config.md (#1882)
- Fix mode: "code-first"/"plan-first"/"hybrid" → "interactive"/"yolo"
  (verified against templates/config.json and workflows/new-project.md)
- Fix discuss_mode: "auto"/"analyze" → "assumptions"
  (verified against workflows/settings.md line 188)
- Add regression tests asserting correct values and rejecting stale ones

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:13:49 -04:00
Tibsfox
a452c4a03b fix(phase): scan ROADMAP.md entries in next-decimal to prevent collisions (#1877)
next-decimal and insert-phase only scanned directory names in
.planning/phases/ when calculating the next available decimal number.
When agents added backlog items by writing ROADMAP.md entries and
creating directories without calling next-decimal, the function would
not see those entries and return a number that was already in use.

Both functions now union directory names AND ROADMAP.md phase headers
(e.g. ### Phase 999.3: ...) before computing max + 1. This follows the
same pattern already used by cmdPhaseComplete (lines 791-834) which
scans ROADMAP.md as a fallback for phases defined but not yet
scaffolded to disk.

Additional hardening:
- Use escapeRegex() on normalized phase names in regex construction
- Support optional project-code prefix in directory pattern matching
- Handle edge cases: missing ROADMAP.md, empty/missing phases dir,
  leading-zero padded phase numbers in ROADMAP.md

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:13:46 -04:00
Lex Christopherson
caf337508f 1.34.2 2026-04-06 14:54:12 -06:00
Lex Christopherson
c7de05e48f fix(engines): lower Node.js minimum to 22
Node 22 is still in Active LTS until October 2026 and Maintenance LTS
until April 2027. Raising the engines floor to >=24.0.0 unnecessarily
locked out a fully-supported LTS version and produced EBADENGINE
warnings on install. Restore Node 22 support, add Node 22 to the CI
matrix, and update CONTRIBUTING.md to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:54:12 -06:00
Tom Boucher
641ea8ad42 docs: update documentation for v1.34.0 release (#1868) 2026-04-06 16:25:41 -04:00
Lex Christopherson
07b7d40f70 1.34.1 2026-04-06 14:16:52 -06:00
Lex Christopherson
4463ee4f5b docs: update changelog for v1.34.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:16:45 -06:00
Lex Christopherson
cf385579cf docs: remove npm v1.32.0 stuck notice from README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:05:19 -06:00
Tom Boucher
64589be2fc docs: add npm v1.32.0 stuck notice with GitHub install workaround
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:44:49 -04:00
Tom Boucher
d14e336793 chore: bump to 1.34.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:34:34 -04:00
Tibsfox
dd5d54f182 enhance(reapply-patches): post-merge verification to catch dropped hunks (#1775)
* feat(reapply-patches): post-merge verification to catch dropped hunks

Add a post-merge verification step to the reapply-patches workflow that
detects when user-modified content hunks are silently lost during
three-way merge. The verification performs line-count sanity checks and
hunk-presence verification against signature lines from each user
addition.

Warnings are advisory — the merge result is kept and the backup remains
available for manual recovery. This strengthens the never-skip invariant
from PR #1474 by ensuring not just that files are processed, but that
their content survives the merge intact.

Closes #1758

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* enhance(reapply-patches): add structural ordering test and refactor test setup (#1758)

- Add ordering test: verification section appears between merge-write
  and status-report steps (positional constraint, not just substring)
- Move file reads into before() hook per project test conventions
- Update commit prefix from feat: to enhance: per contribution taxonomy
  (addition to existing workflow, not new concept)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 15:20:06 -04:00
Tibsfox
2a3fe4fdb5 feat(references): add gates taxonomy with 4 canonical gate types (#1781)
* feat(references): add gates taxonomy with 4 canonical gate types

Define pre-flight, revision, escalation, and abort gates as the
canonical validation checkpoint types used across GSD workflows.
Includes a gate matrix mapping each workflow phase to its gate type,
checked artifacts, and failure behavior. Cross-referenced from
plan-phase and execute-phase workflows.

Closes #1715

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(agents): add gates.md reference to plan-checker and verifier per approved scope (#1715)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(agents): move gates.md to required_reading blocks and add stall detection (#1715)

- Move gates.md @-reference from <role> prose into <required_reading> blocks
  in gsd-plan-checker.md and gsd-verifier.md so it loads as context
- Add stall-detection to Revision Gate recovery description
- Fix /gsd-next → next for consistent workflow naming in Gate Matrix
- Update tests to verify required_reading placement and stall detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 15:19:46 -04:00
Tom Boucher
e9ede9975c fix(gsd-check-update): prioritize .claude in detectConfigDir search order (#1863)
Move .claude to the front of the detectConfigDir search array so Claude Code
sessions always find their own GSD install first, preventing false "update
available" warnings when an older OpenCode install coexists on the same machine.

Closes #1860

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:14:02 -04:00
Tom Boucher
0e06a44deb fix(package): include hooks/*.sh files in npm package (#1852 #1862) (#1864)
The "files" field in package.json listed "hooks/dist" instead of "hooks",
which excluded gsd-session-state.sh, gsd-validate-commit.sh, and
gsd-phase-boundary.sh from the npm tarball. Any fresh install from the
registry produced broken shell hook registrations.

Fix: replace "hooks/dist" with "hooks" so the full hooks/ directory is
bundled, covering both the compiled .js files (in hooks/dist/) and the
.sh source hooks at the top of hooks/.

Adds regression test in tests/package-manifest.test.cjs.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:13:23 -04:00
Tom Boucher
09e56893c8 fix(milestone): preserve 999.x backlog phases during phases clear (#1858)
* fix(milestone): preserve 999.x backlog phases during phases clear

Fixes #1853

* fix: remove accidentally bundled plan-stall-detection test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 08:54:18 -04:00
Alan
2d80cc3afd fix: use ~/.codeium/windsurf as Windsurf global config dir (#1856) 2026-04-06 08:40:37 -04:00
Tom Boucher
f7d4d60522 fix(ci): drop Node 22 from matrix, require Node 24 minimum (#1848)
Node 20 reached EOL April 30 2026. Node 22 is no longer the LTS
baseline — Node 24 is the current Active LTS. Update CI matrix to
run only Node 24, raise engines floor to >=24.0.0, and update
CONTRIBUTING.md node compatibility table accordingly.

Fixes #1847

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:23:07 -04:00
Tom Boucher
c0145018f6 fix(installer): deploy commands directory in local installs (#1843)
* fix(installer): deploy commands directory in local installs (#1736)

Local Claude installs now populate .claude/commands/gsd/ with command .md
files. Claude Code reads local project commands from .claude/commands/gsd/,
not .claude/skills/ — only the global ~/.claude/skills/ is used for the
skills format. The previous code deployed skills/ for both global and local
installs, causing all /gsd-* commands to return "Unknown skill" after a
local install.

Global installs continue to use skills/gsd-xxx/SKILL.md (Claude Code 2.1.88+
format). Local installs now use commands/gsd/xxx.md (the format Claude Code
reads for local project commands).

Also adds execute-phase.md to the prompt-injection scan allowlist (the
workflow grew past 50K chars, matching the existing discuss-phase.md exemption).

Closes #1736

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(installer): fix test cleanup pattern and uninstall local/global split (#1736)

Replace try/finally with t.after() in all 3 regression tests per CONTRIBUTING.md
conventions. Split the Claude Code uninstall branch on isGlobal: global removes
skills/gsd-*/ directories (with legacy commands/gsd/ cleanup), local removes
commands/gsd/ as the primary install location since #1736.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:11:18 -04:00
Tom Boucher
5884a24d14 fix(installer): deploy missing shell hook scripts to hooks directory (#1844)
Add end-to-end regression tests confirming the installer deploys all three
.sh hooks (gsd-session-state.sh, gsd-validate-commit.sh, gsd-phase-boundary.sh)
to the target hooks/ directory alongside .js hooks.

Root cause: the hook copy loop in install.js only handled entry.endsWith('.js')
files; the else branch for non-.js files (including .sh scripts) was absent,
so .sh hooks were silently skipped. The fix (else + copyFileSync + chmod) is
already present; these tests guard against regression.

Also allowlists execute-phase.md in the prompt-injection scan — it exceeds
the 50K size threshold due to legitimate adaptive context enrichment content
added in recent releases.

Closes #1834

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:11:16 -04:00
Tom Boucher
85316d62d5 feat: 3-tier release strategy with hotfix, release, and CI workflows (#1289)
* feat: 3-tier release strategy with hotfix, release, and CI workflows

Supersedes PRs #1208 and #1210 with a consolidated approach:

- VERSIONING.md: Strategy document with 3 release tiers (patch/minor/major)
- hotfix.yml: Emergency patch releases to latest
- release.yml: Standard release cycle with RC/beta pre-releases to next
- auto-branch.yml: Create branches from issue labels
- branch-naming.yml: Convention validation (advisory)
- pr-gate.yml: PR size analysis and labeling
- stale.yml: Weekly cleanup of inactive issues/PRs
- dependabot.yml: Automated dependency updates

npm dist-tags: latest (stable) and next (pre-release) only,
following Angular/Next.js convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR review findings for release workflow security and correctness

- Move all ${{ }} expression interpolation from run: blocks into env: mappings
  in both hotfix.yml (~12 instances) and release.yml (~16 instances) to prevent
  potential command injection via GitHub Actions expression evaluation
- Reorder rc job in release.yml to run npm ci and test:coverage before pushing
  the git tag, preventing broken tagged commits when tests fail
- Update VERSIONING.md to accurately describe the implementation: major releases
  use beta pre-releases only, minor releases use rc pre-releases only (no
  beta-then-rc progression)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* security: harden release workflows — SHA pinning, provenance, dry-run guards

Addresses deep adversarial review + best practices research:

HIGH:
- Fix release.yml rc/finalize: dry_run now gates tag+push (not just npm publish)
- Fix hotfix.yml finalize: reorder tag-before-publish (was publish-before-tag)

MEDIUM — Security hardening:
- Pin ALL actions to SHA hashes (actions/checkout@11bd7190,
  actions/setup-node@39370e39, actions/github-script@60a0d830)
- Add --provenance --access public to all npm publish commands
- Add id-token: write permission for npm provenance OIDC
- Add concurrency groups (cancel-in-progress: false) on both workflows
- Add branch-naming.yml permissions: {} (deny-all default)
- Scope permissions per-job instead of workflow-level where possible

MEDIUM — Reliability:
- Add post-publish verification (npm view + dist-tag check) after every publish
- Add npm publish --dry-run validation step before actual publish
- Add branch existence pre-flight check in create jobs

LOW:
- Fix VERSIONING.md Semver Rules: MINOR = "enhancements" not "new features"
  (aligns with Release Tiers table)

Tests: 1166/1166 pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* security: pin actions/stale to SHA hash

Last remaining action using a mutable version tag. Now all actions
across all workflow files are pinned to immutable SHA hashes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address all Copilot review findings on release strategy workflows

- Configure git identity in all committing jobs (hotfix + release)
- Base hotfix on latest patch tag instead of vX.Y.0
- Add issues: write permission for PR size labeling
- Remove stale size labels before adding new one
- Make tagging and PR creation idempotent for reruns
- Run dry-run publish validation unconditionally
- Paginate listFiles for large PRs
- Fix VERSIONING.md table formatting and docs accuracy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: clean up next dist-tag after finalize in release and hotfix workflows

After finalizing a release, the next dist-tag was left pointing at the
last RC pre-release. Anyone running npm install @next would get a stale
version older than @latest. Now both workflows point next to the stable
release after finalize, matching Angular/Next.js convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): address blocking issues in 3-tier release workflows

- Move back-merge PR creation before npm publish in hotfix/release finalize
- Move version bump commit after test step in rc workflow
- Gate hotfix create branch push behind dry_run check
- Add confirmed-bug and confirmed to stale.yml exempt labels
- Fix auto-branch priority: critical prefix collision with hotfix/ naming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 23:08:31 -04:00
Jeremy McSpadden
00c6a5ea68 fix(install): preserve non-array hook entries during uninstall (#1824)
* fix(install): preserve non-array hook entries during uninstall

Uninstall filtering returned null for hook entries without a hooks
array, silently deleting user-owned entries with unexpected shapes.
Return the entry unchanged instead so only GSD hooks are removed.

* test(install): add regression test for non-array hook entry preservation (#1825)

Fix mirrored filterGsdHooks helper to match production code and add
test proving non-array hook entries survive uninstall filtering.
2026-04-05 23:07:59 -04:00
Rezolv
d52c880eec feat(agents): auto-inject relevant global learnings into planner context (#1830)
* feat(agents): auto-inject relevant global learnings into planner context

* fix(agents): address review feedback for learnings planner injection

- Add features.global_learnings to VALID_CONFIG_KEYS for explicit validation
- Fix error message in cmdConfigSet to mention features.<feature_name> pattern
- Clarify tag syntax in planner injection step (frontmatter tags or objective keywords)
2026-04-05 23:07:57 -04:00
Tibsfox
a70ac27b24 docs(references): extend planning-config.md with complete field reference (#1786)
* docs(references): extend planning-config.md with complete field reference

Add a comprehensive field table generated from CONFIG_DEFAULTS and
VALID_CONFIG_KEYS covering all config.json fields with types, defaults,
allowed values, and descriptions. Includes field interaction notes
(auto-detection, threshold triggers) and three copy-pasteable example
configurations for common setups.

Closes #1741

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): add missing sub_repos and model_overrides to config reference (#1741)

- Add sub_repos field to planning-config.md field table
- Add model_overrides field to planning-config.md field table
- Fix test namespace map to cover both missing fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(docs): add thinking_partner field and plan_checker alias note (#1741)

- Add features.thinking_partner to config reference documentation
- Document plan_checker as flat-key alias of workflow.plan_check
- Move file reads from describe scope into before() hooks
- Add test coverage for thinking_partner field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 23:07:54 -04:00
Tibsfox
f0f0f685a5 feat(commands): add /gsd-audit-fix for autonomous audit-to-fix pipeline (#1814)
* feat(commands): add /gsd-audit-fix autonomous audit-to-fix pipeline

Chains audit, classify, fix, test, commit into an autonomous pipeline. Runs an audit (currently audit-uat), classifies findings as auto-fixable vs manual-only (erring on manual when uncertain), spawns executor agents for fixable issues, runs tests after each fix, and commits atomically with finding IDs for traceability.

Supports --max N (cap fixes), --severity (filter threshold), --dry-run (classification table only), and --source (audit command). Reverts changes on test failure and continues to the next finding.

Closes #1735

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(commands): address review feedback on audit-fix command (#1735)

- Change --severity default from high to medium per approved spec
- Fix pipeline to stop on first test failure instead of continuing
- Verify gsd-tools.cjs commit usage (confirmed valid — no change needed)
- Add argument-hint for /gsd-help discoverability
- Update tests: severity default, stop-on-failure, argument-hint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(commands): address second-round review feedback on audit-fix (#1735)

- Replace non-existent gsd-tools.cjs commit with direct git add/commit
- Scope revert to changed files only instead of git checkout -- .
- Fix argument-hint to reflect actual supported source values
- Add type: prompt to command frontmatter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 23:07:52 -04:00
Tom Boucher
c0efb7b9f1 fix(workflows): remove deprecated --no-input flag from claude CLI calls (#1759) (#1842)
claude --no-input was removed in Claude Code >= v2.1.81 and causes an
immediate crash ("error: unknown option '--no-input'"). The -p/--print
flag already handles non-interactive output, so --no-input is redundant.

Adds a regression test in tests/workflow-compat.test.cjs that scans all
workflow, command, and agent .md files to ensure --no-input never returns.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 22:54:12 -04:00
Tom Boucher
13c635f795 feat(security): improve prompt injection scanner — invisible Unicode, encoding obfuscation, structural validation, entropy analysis (#1839)
* fix(tests): allowlist execute-phase.md in prompt-injection scan

execute-phase.md grew to ~51K chars after the code-review gate step
was added in #1630, tripping the 50K size heuristic in the injection
scanner. The limit is calibrated for user-supplied input — trusted
workflow source files that legitimately exceed it are allowlisted
individually, following the same pattern as discuss-phase.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(security): improve prompt injection scanner with 4 detection layers (#1838)

- Layer 1: Unicode tag block U+E0000–U+E007F detection in strict mode (2025 supply-chain attack vector)
- Layer 2: Character-spacing obfuscation, delimiter injection (<system>/<assistant>/<user>/<human>), and long hex sequence patterns
- Layer 3: validatePromptStructure() — validates XML tag structure of agent/workflow files against known-valid tag set
- Layer 4: scanEntropyAnomalies() — Shannon entropy analysis flagging high-entropy paragraphs (>5.5 bits/char)

All layers implemented TDD (RED→GREEN): 31 new tests written first, verified failing, then implemented.
Full suite: 2559 tests, 0 failures. security.cjs: 99.6% stmt coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 20:22:52 -04:00
Tom Boucher
95eda5845e fix(tests): allowlist execute-phase.md in prompt-injection scan (#1835)
execute-phase.md grew to ~51K chars after the code-review gate step
was added in #1630, tripping the 50K size heuristic in the injection
scanner. The limit is calibrated for user-supplied input — trusted
workflow source files that legitimately exceed it are allowlisted
individually, following the same pattern as discuss-phase.md.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 20:03:47 -04:00
Bill Huang
99c089bfbf feat: add /gsd:code-review and /gsd:code-review-fix commands (#1630)
* feat: add /gsd:code-review and /gsd:code-review-fix commands

Closes #1636

Add two new slash commands that close the gap between phase execution
and verification. After /gsd:execute-phase completes, /gsd:code-review
reviews produced code for bugs, security issues, and quality problems.
/gsd:code-review-fix then auto-fixes issues found by the review.

## New Files

- agents/gsd-code-reviewer.md — Review agent with 3 depth levels
  (quick/standard/deep) and structured REVIEW.md output
- agents/gsd-code-fixer.md — Fix agent with atomic git rollback,
  3-tier verification, per-finding atomic commits, logic-bug flagging
- commands/gsd/code-review.md — Slash command definition
- commands/gsd/code-review-fix.md — Slash command definition
- get-shit-done/workflows/code-review.md — Review orchestration:
  3-tier file scoping, repo-boundary path validation, config gate
- get-shit-done/workflows/code-review-fix.md — Fix orchestration:
  --all/--auto flags, 3-iteration cap, artifact backup across iterations
- tests/code-review.test.cjs — 35 tests covering agents, commands,
  workflows, config, integration, rollback strategy, and logic-bug flagging

## Modified Files

- get-shit-done/bin/lib/config.cjs — Register workflow.code_review and
  workflow.code_review_depth with defaults and typo suggestions
- get-shit-done/workflows/execute-phase.md — Add code_review_gate step
  (PIPE-01): runs after aggregate_results, advisory only, non-blocking
- get-shit-done/workflows/quick.md — Add Step 6.25 code review (PIPE-03):
  scopes via git diff, uses gsd-code-reviewer, advisory only
- get-shit-done/workflows/autonomous.md — Add Step 3c.5 review+fix chain
  (PIPE-02): auto-chains code-review-fix --auto when issues found

## Design Decisions

- Rollback uses git checkout -- {file} (atomic) not Write tool (partial write risk)
- Logic-bug fixes flagged "requires human verification" (syntax check cannot verify semantics)
- Path traversal guard rejects --files paths outside repo root
- Fail-closed scoping: no HEAD~N heuristics when scope is ambiguous

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add /gsd:code-review and /gsd:code-review-fix commands

Closes #1636

Add two new slash commands that close the gap between phase execution
and verification. After /gsd:execute-phase completes, /gsd:code-review
reviews produced code for bugs, security issues, and quality problems.
/gsd:code-review-fix then auto-fixes issues found by the review.

## New Files

- agents/gsd-code-reviewer.md — Review agent: 3 depth levels, REVIEW.md
- agents/gsd-code-fixer.md — Fix agent: git rollback, 3-tier verification,
  logic-bug flagging, per-finding atomic commits
- commands/gsd/code-review.md, code-review-fix.md — Slash command definitions
- get-shit-done/workflows/code-review.md — Review orchestration: 3-tier
  file scoping, path traversal guard, config gate
- get-shit-done/workflows/code-review-fix.md — Fix orchestration:
  --all/--auto flags, 3-iteration cap, artifact backup
- tests/code-review.test.cjs — 35 tests: agents, commands, workflows,
  config, integration, rollback, logic-bug flagging

## Modified Files

- get-shit-done/bin/lib/config.cjs — Register workflow.code_review and
  workflow.code_review_depth config keys
- get-shit-done/workflows/execute-phase.md — Add code_review_gate step
  (PIPE-01): after aggregate_results, advisory, non-blocking
- get-shit-done/workflows/quick.md — Add Step 6.25 code review (PIPE-03):
  git diff scoping, gsd-code-reviewer, advisory
- get-shit-done/workflows/autonomous.md — Add Step 3c.5 review+fix chain
  (PIPE-02): auto-chains code-review-fix --auto when issues found

## Design decisions

- Rollback uses git checkout -- {file} (atomic) not Write tool
- Logic-bug fixes flagged requires human verification (syntax != semantics)
- --files paths validated within repo root (path traversal guard)
- Fail-closed: no HEAD~N heuristics when scope ambiguous

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve contradictory rollback instructions in gsd-code-fixer

rollback_strategy said git checkout, critical_rules said Write tool.
Align all three sections (rollback_strategy, execution_flow step b,
critical_rules) to use git checkout -- {file} consistently.

Also remove in-memory PRE_FIX_CONTENT capture — no longer needed
since git checkout is the rollback mechanism.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address all review feedback from rounds 3-4

Blocking (bash compatibility):
- Replace mapfile -t with portable while IFS= read -r loops in both
  workflows (mapfile is bash 4+; macOS ships bash 3.2 by default)
- Add macOS bash version note to platform_notes

Blocking (quick.md scope heuristic):
- Replace fragile HEAD~$(wc -l SUMMARY.md) with git log --grep based
  diff, matching the more robust approach in code-review.md

Security (path traversal):
- Document realpath -m macOS behavior in platform_notes; guard remains
  fail-closed on macOS without coreutils

Logic / correctness:
- Fix REVIEW_PATH / FIX_REPORT_PATH interpolation in node -e strings;
  use process.env.REVIEW_PATH via env var prefix to avoid single-quote
  path injection risk
- Add iteration semantics comment clarifying off-by-one behavior
- Remove duplicate "3. Determine changed files" heading in gsd-code-reviewer.md

Agent:
- Add logic-bug limitation section to gsd-code-fixer verification_strategy

Tests (39 total, up from 32):
- Add rollback uses git checkout test
- Add success_criteria consistency test (must not say Write tool)
- Add logic-bug flagging test
- Add files_reviewed_list spec test
- Add path traversal guard structural test
- Add mapfile-in-bash-blocks tests (bash 3.2 compatibility)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add gsd-code-reviewer to quick.md available_agent_types and copilot install test

- quick.md Step 6.25 spawns gsd-code-reviewer but the workflow's
  <available_agent_types> block did not list it, failing the spawn
  consistency CI check (#1357)
- copilot-install.test.cjs hardcoded agent list was missing
  gsd-code-fixer.agent.md and gsd-code-reviewer.agent.md, failing
  the Copilot full install verification test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: replace /gsd: colon refs with /gsd- hyphen format in new files

Fixes stale-colon-refs CI test (#1748). All 19 violations replaced:
- agents/gsd-code-fixer.md (2): description + role spawned-by text
- agents/gsd-code-reviewer.md (4): description + role + fallback note + error msg
- get-shit-done/workflows/code-review-fix.md (7): error msgs + retry suggestions
- get-shit-done/workflows/code-review.md (5): error msgs + retry suggestions
- get-shit-done/workflows/execute-phase.md (1): code_review_gate suggestion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 19:43:45 -04:00
Rezolv
12cdf6090c feat(workflows): auto-copy learnings to global store at phase completion (#1828)
* feat(workflows): add auto-copy learnings to global store at phase completion

* fix(workflows): address review feedback for learnings auto-copy

- Replace shell-interpolated ${phase_dir} with agent context instruction
- Remove unquoted glob pattern in bash snippet
- Use gsd-tools learnings copy instead of manual file detection
- Document features.* dynamic namespace in config.cjs

* docs(config): add features.* namespace to CONFIGURATION.md schema
2026-04-05 19:33:43 -04:00
Rezolv
e107b4e225 feat(config): add execution context profiles for mode-specific agent output (#1827)
* feat(config): add execution context profiles for mode-specific agent output

* fix(config): add enum validation for context config key

Validate context values against allowed enum (dev, research, review)
in cmdConfigSet before writing to config.json, matching the pattern
used for model_profile validation. Add rejection test for invalid
context values.
2026-04-05 19:09:19 -04:00
Rezolv
f25ae33dff feat(tools): add global learnings store with CRUD library and CLI support (#1831)
* feat(tools): add global learnings store with CRUD library and CLI support

* fix(tools): address review feedback for global learnings store

- Validate learning IDs against path traversal in learningsRead, learningsDelete, and cmdLearningsDelete
- Fix total invariant in learningsCopyFromProject (total = created + skipped)
- Wrap cmdLearningsPrune in try/catch to handle invalid duration format
- Rename raw -> content in readLearningFile to avoid variable shadowing
- Add CLI integration tests for list, query, prune error, and unknown subcommand
2026-04-05 19:09:14 -04:00
Tibsfox
790cbbd0d6 feat(commands): add /gsd-explore for Socratic ideation and idea routing (#1813)
* feat(commands): add /gsd-explore for Socratic ideation and idea routing

Open-ended exploration command that guides developers through ideas via
Socratic questioning, optionally spawns research when factual questions
surface, then routes crystallized outputs to appropriate GSD artifacts
(notes, todos, seeds, research questions, requirements, or new phases).

Conversation follows questioning.md principles — one question at a time,
contextual domain probes, natural flow. Outputs require explicit user
selection before writing.

Closes #1729

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(commands): address review feedback on explore command (#1729)

- Change allowed-tools from Agent to Task to match subagent spawn pattern
- Remove unresolved {resolved_model} placeholder from Task spawn

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 18:33:27 -04:00
Rezolv
02d2533eac feat(commands): add external plan import command /gsd-import (#1801)
* feat(commands): add external plan import command /gsd-import

Adds a new /gsd-import command for importing external plan files into
the GSD planning system with conflict detection against PROJECT.md
decisions and CONTEXT.md locked decisions.

Scoped to --from mode only (plan file import). Uses validatePath()
from security.cjs for file path validation. Surfaces all conflicts
before writing and never auto-resolves. Handles missing PROJECT.md
gracefully by skipping constraint checks.

--prd mode (PRD extraction) is noted as future work.

Closes #1731

* fix(commands): address review feedback for /gsd-import

- Add structural tests for command/workflow files (13 assertions)
- Add REQUIREMENTS.md to conflict detection context loading
- Replace security.cjs CLI invocation with inline path validation
- Move PBR naming check from blocker list to conversion step
- Add Edit to allowed-tools for ROADMAP.md/STATE.md patching
- Remove emoji from completion banner and validation message
2026-04-05 18:33:24 -04:00
Rezolv
567736f23d feat(commands): add safe git revert command /gsd-undo (#1800)
* feat(commands): add safe git revert command /gsd-undo

Adds a new /gsd-undo command for safely reverting GSD phase or plan
commits. Uses phase manifest lookup with git log fallback, atomic
single-commit reverts via git revert --no-commit, dependency checking
with user confirmation, and structured revert commit messages including
a user-provided reason.

Three modes: --last N (interactive selection), --phase NN (full phase
revert), --plan NN-MM (single plan revert).

Closes #1730

* fix(commands): address review feedback for /gsd-undo

- Add dirty-tree guard before revert operations (security)
- Fix manifest schema to use manifest.phases[N].commits (critical)
- Extend dependency check to MODE=plan for intra-phase deps
- Handle mid-sequence conflict cleanup with reset HEAD + restore
- Fix unbalanced grep alternation pattern for phase scope matching
- Remove Write from allowed-tools (never needed)
2026-04-05 18:33:21 -04:00
Rezolv
db6f999ee4 feat(workflows): add stall detection to plan-phase revision loop (#1794)
* feat(workflows): add stall detection to plan-phase revision loop

Adds issue count tracking and stall detection to the plan-phase
revision loop (step 12). When issue count stops decreasing across
iterations, the loop escalates to the user instead of burning
remaining iterations. The existing 3-iteration cap remains as a
backstop. Uses normalized issue counting from checker YAML output.

Closes #1716

* fix(workflows): add parsing fallback and re-entry guard to stall detection
2026-04-05 18:33:19 -04:00
Rezolv
3bce941b2a docs(agents): add few-shot calibration examples for plan-checker and verifier (#1792)
* docs(agents): add few-shot calibration examples for plan-checker and verifier

Closes #1723

* test(agents): add structural tests for few-shot calibration examples

Validates reference file existence, frontmatter metadata, example counts,
WHY annotations on every example, agent @reference lines, and content
structure (input/output pairs, calibration gap patterns table).
2026-04-05 18:33:17 -04:00
Rezolv
7b369d2df3 feat(intel): add queryable codebase intelligence system (#1728)
* feat(intel): add queryable codebase intelligence system

Add persistent codebase intelligence that reduces context overhead:

- lib/intel.cjs: 654-line CLI module with 13 exports (query, status,
  diff, snapshot, patch-meta, validate, extract-exports, and more).
  Reads config.json directly (not via config-get which hard-exits on
  missing keys). Default is DISABLED (user must set intel.enabled: true).
- gsd-tools.cjs: intel case routing with 7 subcommand dispatches
- /gsd-intel command: 4 modes (query, status, diff, refresh). Config
  gate uses Read tool. Refresh spawns gsd-intel-updater agent via Task().
- gsd-intel-updater agent: writes 5 artifacts to .planning/intel/
  (files.json, apis.json, deps.json, stack.json, arch.md). Uses
  gsd-tools intel CLI calls. Completion markers registered in
  agent-contracts.md.
- agent-contracts.md: updated with gsd-intel-updater registration

* docs(changelog): add intel system entry for #1688

* test(intel): add comprehensive tests for intel.cjs

Cover disabled gating, query (keys, values, case-insensitive, multi-file,
arch.md text), status (fresh, stale, missing), diff (no baseline, added,
changed), snapshot, validate (missing files, invalid JSON, complete store),
patch-meta, extract-exports (CJS, ESM named, ESM block, missing file),
and gsd-tools CLI routing for intel subcommands.

38 test cases across 10 describe blocks.

* fix(intel): address review feedback — merge markers, redundant requires, gate docs, update route

- Remove merge conflict markers from CHANGELOG.md
- Replace redundant require('path')/require('fs') in isIntelEnabled with top-level bindings
- Add JSDoc notes explaining why intelPatchMeta and intelExtractExports skip isIntelEnabled gate
- Add 'intel update' CLI route in gsd-tools.cjs and update help text
- Fix stale /gsd: colon reference in intelUpdate return message
2026-04-05 18:33:15 -04:00
Tom Boucher
4302d4404e fix(core): treat model_profile "inherit" as pass-through instead of falling back to balanced (#1833)
When model_profile is set to "inherit" in config.json, resolveModelInternal()
now returns "inherit" immediately instead of looking it up in MODEL_PROFILES
(where it has no entry) and silently falling back to balanced.

Also adds "inherit" to the valid profile list in verify.cjs so setting it
doesn't trigger a false validation error.

Closes #1829

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 18:20:11 -04:00
Tom Boucher
2ded61bf45 fix(cli): require --confirm flag before phases clear deletes directories (#1832)
phases clear now checks for phase dirs before deleting. If any exist and
--confirm is absent, the command exits non-zero with a message showing the
count and how to proceed. Empty phases dir (nothing to delete) succeeds
without --confirm unchanged.

Updates new-milestone.md workflow to pass --confirm (intentional programmatic
caller). Updates existing new-milestone-clear-phases tests to match new API.

Closes #1826

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 18:05:32 -04:00
Tom Boucher
b185529f48 fix(installer): guard .sh hook registration with fs.existsSync before writing settings.json (#1823)
Before registering each .sh hook (validate-commit, session-state, phase-boundary),
check that the target file was actually copied. If the .sh file is missing (e.g.
omitted from the npm package as in v1.32.0), skip registration and emit a warning
instead of writing a broken hook entry that errors on every tool invocation.

Closes #1817

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 17:49:20 -04:00
Tom Boucher
e881c91ef1 fix(cli): reject help/version flags instead of silently ignoring them (#1822)
* fix(cli): reject help/version flags instead of silently ignoring them (#1818)

AI agents can hallucinate --help or --version on gsd-tools invocations.
Without a guard, unknown flags were silently ignored and the command
proceeded — including destructive ones like `phases clear`. Add a
pre-dispatch check in main() that errors immediately if any never-valid
flag (-h, --help, -?, --version, -v, --usage) is present in args after
global flags are stripped. Regression test covers phases clear, generate-
slug, state load, and current-timestamp with both --help and -h variants.

Closes #1818

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(agents): convert gsd-verifier required_reading to inline wiring

The thinking-model-guidance test requires inline @-reference wiring at
decision points rather than a <required_reading> block. Convert
verification-overrides.md reference from the <required_reading> block
to an inline reference inside <verification_process> alongside the
existing thinking-models-verification.md reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): resolve conflict between thinking-model and verification-overrides tests

thinking-model-guidance.test prohibited <required_reading> entirely, but
verification-overrides.test requires gsd-verifier.md to have a
<required_reading> block for verification-overrides.md between </role>
and <project_context>. The tests were mutually exclusive.

Fix: narrow the thinking-model assertion to check that the thinking-models
reference is not *inside* a <required_reading> block (using regex extraction),
rather than asserting no <required_reading> block exists at all. Restore the
<required_reading> block in gsd-verifier.md. Both suites now pass (2345/2345).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 17:32:18 -04:00
Tibsfox
3a277f8ba8 feat(next): add hard stop safety gates and consecutive-call guard (#1784)
Add three hard-stop checks to /gsd-next that prevent blind advancement:
1. Unresolved .continue-here.md checkpoint from a previous session
2. Error/failed state in STATE.md
3. Unresolved FAIL items in VERIFICATION.md

Also add a consecutive-call budget guard that prompts after 6
consecutive /gsd-next calls, preventing runaway automation loops.

All gates are bypassed with --force (prints a one-line warning).
Gates run in order and exit on the first hit to give clear,
actionable diagnostics.

Closes #1732

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:05:06 -04:00
Tibsfox
4c8719d84a feat(commands): add /gsd-scan for rapid single-focus codebase assessment (#1808)
Lightweight alternative to /gsd-map-codebase that spawns a single
mapper agent for one focus area instead of four parallel agents.
Supports --focus flag with 5 options: tech, arch, quality, concerns,
and tech+arch (default). Checks for existing documents and prompts
before overwriting.

Closes #1733

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:04:33 -04:00
Tibsfox
383007dca4 feat(workflows): add conditional thinking partner at decision points (#1816)
Integrate lightweight thinking partner analysis at two workflow decision
points, controlled by features.thinking_partner config (default: false):

1. discuss-phase: when developer answers reveal competing priorities
   (detected via keyword/structural signals), offers brief tradeoff
   analysis before locking decisions

2. plan-phase: when plan-checker flags architectural tradeoffs, analyzes
   options and recommends an approach aligned with phase goals before
   entering the revision loop

The thinking partner is opt-in, skippable (No, I have decided),
and brief (3-5 bullets). A third integration point for /gsd-explore
will be added when #1729 lands.

Closes #1726

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:04:08 -04:00
Tibsfox
a2a49ecd14 feat(model-profiles): add adaptive preset with role-based model assignment (#1806)
Add a fourth model profile preset that assigns models by agent role:
opus for planning and debugging (reasoning-critical), sonnet for
execution and research (follows instructions), haiku for mapping and
checking (high volume, structured output).

This gives solo developers on paid API tiers a cost-effective middle
ground — quality where it matters most (planning) without overspending
on mechanical tasks (mapping, checking).

Per-agent overrides via model_overrides continue to take precedence
over any profile preset, including adaptive.

Closes #1713

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:03:17 -04:00
Rezolv
6d5a66f64e docs(references): add common bug patterns reference for debugger (#1797) 2026-04-05 17:02:45 -04:00
Tibsfox
3143edaa36 fix(workflows): respect commit_docs:false in worktree merge and quick task commits (#1802)
Three locations in execute-phase.md and quick.md used raw `git add
.planning/` commands that bypassed the commit_docs config check. When
users set commit_docs: false during project setup, these raw git
commands still staged and committed .planning/ files.

Add commit_docs guards (via gsd-tools.cjs config-get) around all raw
git add .planning/ invocations. The gsd-tools.cjs commit wrapper
already respects this flag — these were the only paths that bypassed it.

Fixes #1783

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:02:20 -04:00
Tom Boucher
aa87993362 feat(agents): add thinking model guidance reference files (#1722) (#1820)
Combines implementation by @davesienkowski (inline @-reference wiring at
decision-point steps, named reasoning models with anti-patterns, sequencing
rules, Gap Closure Mode) and @Tibsfox (test suite covering file existence,
section structure, and agent wiring).

- 5 reference files in get-shit-done/references/ — each with named reasoning
  models, Counters annotations, Conflict Resolution sequencing, and When NOT
  to Think guidance
- Inline @-reference wiring placed inside the specific step/section blocks
  where thinking decisions occur (not at top-of-agent)
- Planning cluster includes Gap Closure Mode root-cause check section
- Test suite: 63 tests covering file existence, named models, Conflict
  Resolution sections, Gap Closure Mode, and inline wiring placement

Closes #1722

Co-authored-by: Tibsfox <tibsfox@users.noreply.github.com>
Co-authored-by: Rezolv <davesienkowski@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 17:01:25 -04:00
Tom Boucher
94a18df5dd feat(references): add verification override mechanism reference (#1747) (#1819)
Combines implementation by @Tibsfox (test suite, 80% fuzzy threshold)
and @davesienkowski (must_have schema, mandatory audit fields, full
lifecycle with re-verification carryforward and overrides_applied counter,
embedded verifier step 3b, When-NOT-to-use guardrails).

- New reference: get-shit-done/references/verification-overrides.md
  with must_have/accepted_by/accepted_at schema, 80% fuzzy match
  threshold, When to Use / When NOT to Use guardrails, full override
  lifecycle (re-verification carryforward, milestone audit surfacing)
- gsd-verifier.md updated with required_reading block, embedded Step 3b
  override check before FAIL marking, and overrides_applied frontmatter
- 27-assertion test suite covering reference structure, field names,
  threshold value, lifecycle fields, and agent cross-reference

Closes #1747

Co-authored-by: Tibsfox <tibsfox@users.noreply.github.com>
Co-authored-by: Rezolv <davesienkowski@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 17:00:30 -04:00
Tom Boucher
b602c1ddc7 fix: remove editorial parenthetical descriptions from product names (#1778)
Community PRs repeatedly add marketing commentary in parentheses next to
product names (licensing model, parent company, architecture). Product
listings should contain only the product name.

Cleaned across 8 files in 5 languages (EN, KO, JA, ZH, PT) plus
install.js code comments and CHANGELOG. Added static analysis guard
test that prevents this pattern from recurring.

Fixes #1777

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 12:41:17 -04:00
Tom Boucher
0b6ef6fa24 fix: register gsd-workflow-guard.js in settings.json during install (#1772)
The hook was built, copied to hooks/dist/, and installed to disk — but
never registered as a PreToolUse entry in settings.json, making the
hooks.workflow_guard config flag permanently inert.

Adds the registration block following the same pattern as the other
community hooks (prompt-guard, read-guard, validate-commit, etc.).

Includes regression test that verifies every JS hook in gsdHooks has a
corresponding command construction and registration block.

Fixes #1767

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 12:30:24 -04:00
Jeremy McSpadden
bdc143aa7f Merge pull request #1771 from gsd-build/fix/uninstall-hook-safety-1755
fix(install): uninstall hook safety — per-hook granularity and legacy migration
2026-04-05 10:49:53 -05:00
Jeremy McSpadden
175d89efa9 fix(install): uninstall hook safety — per-hook granularity, legacy migration, workflow-guard cleanup
Addresses three findings from Codex adversarial review of #1768:

- Uninstall settings cleanup now filters at per-hook granularity instead of
  per-entry. User hooks that share an entry with a GSD hook are preserved
  instead of being removed as collateral damage.
- Add gsd-workflow-guard to PreToolUse/BeforeTool uninstall settings filter
  so opt-in users don't get dangling references after uninstall.
- Codex install now strips legacy gsd-update-check.js hook entries before
  appending the corrected gsd-check-update.js, preventing duplicate hooks
  on upgrade from affected versions.
- 8 new regression tests covering per-hook filtering, legacy migration regex.

Fixes #1755
2026-04-05 10:46:40 -05:00
Jeremy McSpadden
84de0cc760 fix(install): comprehensive audit cleanup of hook copy, uninstall, and manifest (#1755) (#1768)
- Add chmod +x for .sh hooks during install (fixes #1755 permission denied)
- Fix Codex hook: wrong path (get-shit-done/hooks/) and inverted filename (gsd-update-check.js → gsd-check-update.js)
- Fix cache invalidation path from ~/cache/ to ~/.cache/gsd/
- Track .sh hooks in writeManifest so saveLocalPatches detects modifications
- Add gsd-workflow-guard.js to uninstall file cleanup list
- Add community hooks (session-state, validate-commit, phase-boundary) to uninstall settings.json cleanup
- Remove phantom gsd-check-update.sh from uninstall list
- Remove dead isCursor/isWindsurf branches in uninstall (already handled by combined branch)
- Warn when expected .sh hooks are missing after verifyInstalled
- Add 15 regression tests in install-hooks-copy.test.cjs
- Update codex-config.test.cjs assertions for corrected hook filename

Fixes #1755
2026-04-05 11:37:27 -04:00
Tom Boucher
c7d25b183a fix(commands): replace undefined $GSD_TOOLS with resolved path (#1766) (#1769)
workstreams.md referenced $GSD_TOOLS (6 occurrences) which is never
defined anywhere in the system. All other 60+ command files use the
standard $HOME/.claude/get-shit-done/bin/gsd-tools.cjs path. The
undefined variable resolves to empty string, causing all workstream
commands to fail with module not found.

Fixes #1766

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 11:30:38 -04:00
Tom Boucher
cfff82dcd2 fix(workflow): protect orchestrator files during worktree merge (#1756) (#1764)
When a worktree branch outlives a milestone transition, git merge
silently overwrites STATE.md and ROADMAP.md with stale content and
resurrects archived phase directories. Fix by backing up orchestrator
files before merge, restoring after, and detecting resurrected files.

Fixes #1761

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 11:11:38 -04:00
Tom Boucher
17c65424ad ci: auto-close draft PRs with policy message (#1765)
- Add close-draft-prs.yml workflow that auto-closes draft PRs with
  explanatory comment directing contributors to submit completed PRs
- Update CONTRIBUTING.md with "No draft PRs" policy
- Update default PR template with draft PR warning

Closes #1762

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 11:11:16 -04:00
Tom Boucher
6bd786bf88 test: add stale /gsd: colon reference regression guard (#1753)
* test: add stale /gsd: colon reference regression guard

Fixes #1748

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: replace 39 stale /gsd: colon references with /gsd- hyphen format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 10:23:41 -04:00
Tom Boucher
b34da909a3 Revert "test: add stale /gsd: colon reference regression guard"
This reverts commit f2c9b30529.
2026-04-05 10:03:04 -04:00
Tom Boucher
f2c9b30529 test: add stale /gsd: colon reference regression guard
Fixes #1748

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 09:58:02 -04:00
Tom Boucher
6317603d75 docs: add welcome back notice and update highlights to v1.33.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 09:38:57 -04:00
Tom Boucher
949da16dbc chore(release): v1.33.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 09:25:43 -04:00
Tibsfox
89c2469ff2 feat(config): apply ~/.gsd/defaults.json as fallback for pre-project commands (#1738)
* feat(config): apply ~/.gsd/defaults.json as fallback for pre-project commands (#1683)

When .planning/config.json is missing (e.g., running GSD commands outside
a project), loadConfig() now checks ~/.gsd/defaults.json before returning
hardcoded defaults. This lets users set preferred model_profile,
context_window, subagent_timeout, and other settings globally.

Only whitelisted keys are merged — unknown keys in defaults.json are
silently ignored. If defaults.json is missing or contains invalid JSON,
the hardcoded defaults are returned as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(config): scope defaults.json fallback to pre-project context only

Only consult ~/.gsd/defaults.json when .planning/ does not exist (truly
pre-project). When .planning/ exists but config.json is missing, return
hardcoded defaults — avoids interference with tests and initialized
projects. Use GSD_HOME env var for test isolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:15:41 -04:00
Tom Boucher
381b4584f8 fix: stale hooks scanner ignores orphaned files from removed features (#1751)
The stale hooks detector in gsd-check-update.js used a broad
`startsWith('gsd-') && endsWith('.js')` filter that matched every
gsd-*.js file in the hooks directory. Orphaned hooks from removed
features (e.g., gsd-intel-*.js) lacked version headers and were
permanently flagged as stale, with no way to clear the warning.

Replace the broad wildcard with a MANAGED_HOOKS allowlist of the 6
JS hooks GSD currently ships. Orphaned files are now ignored.

Regression test verifies: (1) no broad wildcard filter, (2) managed
list matches build-hooks.js HOOKS_TO_COPY, (3) orphaned filenames
are excluded.

Fixes #1750

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 09:15:19 -04:00
Jeremy McSpadden
931fef5425 fix: add Kilo path replacement in copyFlattenedCommands (#1710)
Fixes #1709

copyFlattenedCommands replaced ~/.opencode/ paths but had no
equivalent ~/.kilo/ replacement. Adds kiloDirRegex for symmetric
path handling between the OpenCode and Kilo install pipelines.
2026-04-04 16:17:11 -04:00
Jeremy McSpadden
771259597b refactor: deduplicate config defaults into CONFIG_DEFAULTS constant (#1708)
Fixes #1707

Extracts config defaults from loadConfig() into an exported
CONFIG_DEFAULTS constant in core.cjs. config.cjs and verify.cjs
now reference CONFIG_DEFAULTS instead of duplicating values,
preventing future divergence.
2026-04-04 16:17:09 -04:00
Jeremy McSpadden
323ba83e2b docs: add /gsd-secure-phase and /gsd-docs-update to COMMANDS.md (#1706)
Fixes #1705

Both commands have command files, workflows, and backing agents but
were missing from the user-facing command reference.
2026-04-04 16:17:07 -04:00
Jeremy McSpadden
30a8777623 docs: add 3 missing agents to AGENTS.md and fix stale counts (#1703)
Fixes #1702

- Title: 18 → 21 agents
- Categories table: added Doc Writers (2), Profilers (1), bumped
  Auditors from 2 → 3 (security-auditor)
- Added full detail sections for gsd-doc-writer, gsd-doc-verifier,
  gsd-security-auditor with roles, tools, spawn patterns, and
  key behaviors
- Added 3 agents to tool permissions summary table
2026-04-04 16:17:05 -04:00
Jeremy McSpadden
4e2682b671 docs: update ARCHITECTURE.md with current component counts and missing entries (#1701)
Fixes #1700

- Commands: 44 → 60, Workflows: 46 → 60, Agents: 16 → 21
- Lib modules: 15 → 19, added docs, workstream, schema-detect,
  profile-pipeline, profile-output to CLI Tools table
- Added missing agent categories: security-auditor, doc-writer,
  doc-verifier, user-profiler, assumptions-analyzer
- Fixed stale hook names (gsd-read-before-edit → gsd-read-guard),
  removed non-existent gsd-commit-docs, added shell hooks
- Expanded references section from 8 to all 25 reference files
- Updated file system layout counts to match actual state
2026-04-04 16:17:02 -04:00
Tom Boucher
24c1949986 test: add MODEL_ALIAS_MAP regression test for #1690 (#1698)
Ensures opus, sonnet, and haiku aliases map to current Claude model
IDs (4-6, 4-6, 4-5). Prevents future regressions where aliases
silently resolve to outdated model versions.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 15:52:13 -04:00
Jeremy McSpadden
8d29ecd02f fix: add missing 'act as' injection pattern to prompt guard hook (#1697)
Fixes #1696

The gsd-prompt-guard.js hook was missing the 'act as a/an/the' prompt
injection pattern that security.cjs includes. Adds the pattern with
the same (?!plan|phase|wave) negative lookahead exception to allow
legitimate GSD workflow references.
2026-04-04 15:50:04 -04:00
Jeremy McSpadden
fa57a14ec7 fix: resolve REG-04 — frontmatter inline array parser now respects quoted commas (#1695)
Fixes #1694

The inline array parser used .split(',') which ignored quote boundaries,
splitting "a, b" into two items. Replaced with a quote-aware splitter
that tracks single/double quote state.

Updated REG-04 test to assert correct behavior and added coverage for
single-quoted and mixed-quote inline arrays.
2026-04-04 15:50:01 -04:00
Jeremy McSpadden
839ea22d06 fix: replace shell sleep with cross-platform Atomics.wait in planning lock (#1693)
Fixes #1692

spawnSync('sleep', ['0.1']) fails silently on Windows (ENOENT),
causing a tight busy-loop during lock contention. Atomics.wait()
provides a cross-platform 100ms blocking wait available in Node 22+.
2026-04-04 15:49:59 -04:00
Jeremy McSpadden
ade67cf9f9 fix: update MODEL_ALIAS_MAP to current Claude model IDs (#1691)
Fixes #1690

- opus: claude-opus-4-0 → claude-opus-4-6
- sonnet: claude-sonnet-4-5 → claude-sonnet-4-6
- haiku: claude-haiku-3-5 → claude-haiku-4-5

Also updates the stale haiku reference in sdk/src/session-runner.ts
and documentation examples in CONFIGURATION.md (en, ja-JP, ko-KR).
2026-04-04 15:49:56 -04:00
Tom Boucher
f6d2cf2a4a docs: add Chore / Maintenance issue template (#1689)
Internal improvements (refactoring, CI/CD, test quality, dependency
updates, tech debt) had no dedicated template, forcing contributors
to misuse Enhancement or Feature Request forms. This adds a focused
template with appropriate fields and auto-labels (type: chore,
needs-triage).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 15:38:21 -04:00
Tom Boucher
7185803543 fix(install): apply path replacement in copyCommandsAsClaudeSkills (#1677)
* ci: drop Windows runner, add static hardcoded-path detection

Replace the Windows CI runner with a static analysis test that catches
the same class of platform-specific path bugs (C:\, /home/, /Users/,
/tmp/) without requiring an actual Windows machine.

- tests/hardcoded-paths.test.cjs: new static scanner that checks string
  literals in all source JS/CJS files for hardcoded platform paths;
  runs on Linux/macOS in <100ms and fires on every PR
- .github/workflows/test.yml: remove windows-latest from matrix; switch
  macOS smoke-test runner from Node 22 → Node 24 (the declared standard)
- package.json: bump engines.node from >=20.0.0 to >=22.0.0 (Node 20
  reached EOL April 2026)

Matrix goes from 4 runners → 3 runners per run:
  ubuntu/22  ubuntu/24  macos/24

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(install): apply path replacement in copyCommandsAsClaudeSkills (#1653)

copyCommandsAsClaudeSkills received pathPrefix as a parameter but never
used it — all 51 SKILL.md files kept hardcoded ~/.claude/ paths even on
local (per-project) installs, causing every skill's @-file references
to resolve to a nonexistent global directory.

Add the same three regex replacements that copyCommandsAsCodexSkills
already applies: ~/.claude/ → pathPrefix, $HOME/.claude/ → pathPrefix,
./.claude/ → ./getDirName(runtime)/.

Closes #1653

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:12:58 -04:00
Tom Boucher
a6457a7688 ci: drop Windows runner, add static hardcoded-path detection (#1676)
Replace the Windows CI runner with a static analysis test that catches
the same class of platform-specific path bugs (C:\, /home/, /Users/,
/tmp/) without requiring an actual Windows machine.

- tests/hardcoded-paths.test.cjs: new static scanner that checks string
  literals in all source JS/CJS files for hardcoded platform paths;
  runs on Linux/macOS in <100ms and fires on every PR
- .github/workflows/test.yml: remove windows-latest from matrix; switch
  macOS smoke-test runner from Node 22 → Node 24 (the declared standard)
- package.json: bump engines.node from >=20.0.0 to >=22.0.0 (Node 20
  reached EOL April 2026)

Matrix goes from 4 runners → 3 runners per run:
  ubuntu/22  ubuntu/24  macos/24

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:37:54 -04:00
Tom Boucher
2703422be8 refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md (#1675)
* refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md

- Replace require('node:assert') with require('node:assert/strict') across
  all 73 test files to enforce strict equality (no type coercion)
- Replace try/finally cleanup blocks with t.after() hooks in core.test.cjs
  and hooks-opt-in.test.cjs per the test lifecycle standards
- Utility functions in codex-config and security-scan retain try/finally
  as that is appropriate for per-function resource guards, not lifecycle hooks

Closes #1674

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf(tests): add --test-concurrency=4 to test runner for parallel file execution

Node.js --test-concurrency controls how many test files run as parallel child
processes. Set to 4 by default, configurable via TEST_CONCURRENCY env var.
Fixes tests at a known level rather than inheriting os.availableParallelism()
which varies across CI environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): allowlist verify.test.cjs in prompt-injection scanner

tests/verify.test.cjs uses <human>...</human> as GSD phase task-type
XML (meaning "a human should verify this step"), which matches the
scanner's fake-message-boundary pattern for LLM APIs. This is a
false positive — add it to the allowlist alongside the other test files
that legitimately contain injection-adjacent patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:29:03 -04:00
Rezolv
9bf9fc295d feat(references): add shared behavioral docs and wire into workflows (#1658)
Add 6 shared reference docs that standardize agent behavior across all
GSD workflows:

- universal-anti-patterns.md: 27 behavioral rules across 8 categories
- context-budget.md: 4-tier system (PEAK/GOOD/DEGRADING/POOR) with
  read-depth rules based on context window size
- gate-prompts.md: 14 named prompt patterns for decision points
- revision-loop.md: Check-Revise-Escalate with stall detection (max 3)
- domain-probes.md: domain-specific follow-up questions for 10 domains
- agent-contracts.md: completion markers for all 21 GSD agents

Wire into existing workflows via @-reference includes:
- plan-phase.md: revision-loop, gate-prompts, agent-contracts
- execute-phase.md: agent-contracts, context-budget
- discuss-phase.md: domain-probes, gate-prompts, universal-anti-patterns
2026-04-04 14:16:27 -04:00
monokoo
840b9981d9 fix: add environment-based runtime detection for /gsd-review (#1463)
Replace AI self-identification with env var checks (ANTIGRAVITY_AGENT,
CLAUDE_CODE_ENTRYPOINT) to correctly determine which review CLI to skip.

Fixes incorrect skip behavior when running non-Claude models inside
the Antigravity client.
2026-04-04 14:15:56 -04:00
Tom Boucher
ca6a273685 fix: remove marketing text from runtime prompt, fix #1656 and #1657 (#1672)
* chore: ignore .worktrees directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(install): remove marketing taglines from runtime selection prompt

Closes #1654

The runtime selection menu had promotional copy appended to some
entries ("open source, the #1 AI coding platform on OpenRouter",
"open source, free models"). Replaced with just the name and path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(kilo): update test to assert marketing tagline is removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): use process.execPath so tests pass in shells without node on PATH

Three test patterns called bare `node` via shell, which fails in Claude Code
sessions where `node` is not on PATH:

- helpers.cjs string branch: execSync(`node ...`) → execFileSync(process.execPath)
  with a shell-style tokenizer that handles quoted args and inner-quote stripping
- hooks-opt-in.test.cjs: spawnSync('bash', ...) for hooks that call `node`
  internally → spawnHook() wrapper that injects process.execPath dir into PATH
- concurrency-safety.test.cjs: exec(`node ...`) for concurrent patch test
  → exec(`"${process.execPath}" ...`)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve #1656 and #1657 — bash hooks missing from dist, SDK install prompt

#1656: Community bash hooks (gsd-session-state.sh, gsd-validate-commit.sh,
gsd-phase-boundary.sh) were never included in HOOKS_TO_COPY in build-hooks.js,
so hooks/dist/ never contained them and the installer could not copy them to
user machines. Fixed by adding the three .sh files to the copy array with
chmod +x preservation and skipping JS syntax validation for shell scripts.

#1657: promptSdk() called installSdk() which ran `npm install -g @gsd-build/sdk`
— a package that does not exist on npm, causing visible errors during interactive
installs. Removed promptSdk(), installSdk(), --sdk flag, and all call sites.

Regression tests in tests/bugs-1656-1657.test.cjs guard both fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: sort runtime list alphabetically after Claude Code

- Claude Code stays pinned at position 1
- Remaining 10 runtimes sorted A-Z: Antigravity(2), Augment(3), Codex(4),
  Copilot(5), Cursor(6), Gemini(7), Kilo(8), OpenCode(9), Trae(10), Windsurf(11)
- Updated runtimeMap, allRuntimes, and prompt display in promptRuntime()
- Updated multi-runtime-select, kilo-install, copilot-install tests to match

Also fix #1656 regression test: run build-hooks.js in before() hook so
hooks/dist/ is populated on CI (directory is gitignored; build runs via
prepublishOnly before publish, not during npm ci).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:15:30 -04:00
Tom Boucher
e66f7e889e docs: add typed contribution templates and tighten contributor guidelines (#1673)
Overhaul CONTRIBUTING.md and all GitHub issue/PR templates to enforce a
structured, approval-gated contribution process that cuts down on drive-by
feature submissions.

Changes:
- CONTRIBUTING.md: add Types of Contributions section defining Fix,
  Enhancement, and Feature with escalating requirements and explicit
  rejection criteria; add Issue-First Rule section making clear that
  enhancements require approved-enhancement and features require
  approved-feature label before any code is written; backport gsd-2
  testing standards (t.after() per-test cleanup, array join() fixture
  pattern, Node 24 as primary CI target, test requirements by change type,
  reviewer standards)

- .github/ISSUE_TEMPLATE/enhancement.yml: new template requiring current
  vs. proposed behavior, reason/benefit narrative, full scope of changes,
  and breaking changes assessment; cannot be clicked through

- .github/ISSUE_TEMPLATE/feature_request.yml: full rewrite requiring solo-
  developer problem statement, what is being added, full file-level scope,
  user stories, acceptance criteria, maintenance burden assessment, and
  alternatives considered; incomplete specs are closed, not revised

- .github/pull_request_template.md: converted from general template to a
  routing page directing contributors to the correct typed template;
  using the default template for a feature or enhancement is a rejection
  reason

- .github/PULL_REQUEST_TEMPLATE/fix.md: new typed template requiring
  confirmed-bug label on linked issue and regression test confirmation

- .github/PULL_REQUEST_TEMPLATE/enhancement.md: new typed template with
  hard gate on approved-enhancement label and scope confirmation section

- .github/PULL_REQUEST_TEMPLATE/feature.md: new typed template requiring
  file inventory, spec compliance checklist from the issue, and scope
  confirmation that nothing beyond the approved spec was added

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:03:56 -04:00
Tom Boucher
085f5b9c5b fix(install): remove marketing taglines from runtime selection prompt (#1655)
* chore: ignore .worktrees directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(install): remove marketing taglines from runtime selection prompt

Closes #1654

The runtime selection menu had promotional copy appended to some
entries ("open source, the #1 AI coding platform on OpenRouter",
"open source, free models"). Replaced with just the name and path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(kilo): update test to assert marketing tagline is removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 13:23:15 -04:00
Lex Christopherson
3d4b660cd1 docs: remove npm publish workaround from README
v1.32.0 is now live on npm, so the manual install warning is no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:24:51 -06:00
Tom Boucher
8d6577d101 fix: update Discord invite link from vanity URL to permanent link (#1648)
The discord.gg/gsd vanity link was lost due to a drop in server boosts.
Updated all references to the permanent invite link discord.gg/mYgfVNfA2r
across READMEs, issue templates, install script, and join-discord command.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:04:13 -04:00
Tom Boucher
05c08fdd79 docs: restore npm install notice on README, update docs/README.md for v1.32.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:55:07 -04:00
Tom Boucher
c8d7ab3501 docs: fill documentation gaps from v1.32.0 audit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:54:14 -04:00
Tom Boucher
2c36244f08 docs: fill localized documentation gaps from v1.32.0 audit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:52:15 -04:00
Tom Boucher
f6eda30b19 docs: update localized documentation for v1.32.0 release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:45:55 -04:00
Tom Boucher
acf82440e5 docs: update English documentation for v1.32.0 release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:28:50 -04:00
Tom Boucher
bfef14bbf7 docs: update all READMEs for v1.32.0 release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 08:28:04 -04:00
1203 changed files with 158413 additions and 13425 deletions

44
.changeset/README.md Normal file
View File

@@ -0,0 +1,44 @@
# Changeset Fragments
This directory holds **per-PR CHANGELOG fragments**. Every PR with user-facing changes drops one (or more) `<random-name>.md` files here describing its CHANGELOG entry. Fragments are consolidated into the top-level `CHANGELOG.md` at release time.
## Why
Two PRs that both edit the `### Fixed` block of `CHANGELOG.md` always conflict on merge — git can't pick a serialization order without human input. Two PRs that each add a fresh `.changeset/<unique-name>.md` never conflict because they don't share lines.
See [#2975](https://github.com/gsd-build/get-shit-done/issues/2975) for the full rationale.
## Adding a fragment
```bash
node scripts/changeset/new.cjs \
--type Fixed \
--pr 1234 \
--body "fix the thing — explain the user-visible change in one sentence"
```
This writes `.changeset/<adjective>-<noun>-<noun>.md` with frontmatter and a body. Three random words → concurrent PRs don't collide.
## Format
```md
---
type: Fixed
pr: 1234
---
**`/gsd-foo` no longer drops trailing slashes** — explain the user-visible change.
```
Allowed `type:` values follow [Keep a Changelog](https://keepachangelog.com/): `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`.
## Opting out
PRs that legitimately have no user-facing impact can add the `no-changelog` label. CI honors it. When unsure, add the fragment.
## At release time
```bash
node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD
```
Reads every fragment, groups bullets by `type:`, replaces `## [Unreleased]` with a new `## [vX.Y.Z] - YYYY-MM-DD` block, opens a fresh `## [Unreleased]` above, deletes consumed fragments. Idempotent.

View File

@@ -0,0 +1,11 @@
---
type: Changed
pr: 3152
---
**Command contract validation now enforced in CI (ADR-0002)** — \`scripts/lint-command-contract.cjs\` runs as a pre-test step and validates every \`commands/gsd/*.md\` file against five rules: \`name:\` present + \`gsd:\` prefix, \`description:\` non-empty, \`allowed-tools:\` entries canonical, \`execution_context\` @-refs resolve on disk, @-refs on their own line. Prevents the \`add-backlog.md\`-class gap from silently reappearing on consolidation PRs.
**~900 tokens/invocation recovered** — prose \`@~/.claude/get-shit-done/...\` path tokens removed from \`<process>\` blocks in 39 command files. The \`<execution_context>\` block is now the single authoritative load declaration; the duplicate prose copies were inert but consumed context on every command invocation.
**~3,750 tokens removed from eager session load** — \`/gsd-debug\` (9,603 → 1,703 chars) and \`/gsd-thread\` (7,868 → 585 chars) now follow the workflow-delegation pattern used by all other commands. Their implementations moved to \`get-shit-done/workflows/debug.md\` and \`get-shit-done/workflows/thread.md\`. Behavior is unchanged.
\`get-shit-done/workflows/extract_learnings.md\` renamed to \`extract-learnings.md\` to match the hyphen convention of all other workflow files. Closes #3151.

View File

@@ -0,0 +1,5 @@
---
type: Changed
---
**Query command dispatch deepened with Command Topology Module** — query dispatch now consumes a single topology seam that resolves command tokens, binds native handler adapters, and returns structured no-match diagnosis, improving locality and reducing dispatch seam drift.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3058
---
**GSD transport raw-mode handling and timeout fallback hardened** — fixes undefined raw formatting edge case and adds raw-path coverage to prevent regressions.

View File

@@ -0,0 +1,8 @@
---
type: Changed
pr: 3069
---
**query command metadata now flows through a canonical Command Definition Module seam** — registry assembly, mutation semantics, and alias generation consume one Interface (`family`, `canonical`, `aliases`, `mutation`, `output_mode`, `handler_key`) to improve locality and reduce drift.
**query fallback error mapping cleanup** — the CJS fallback catch path now passes original `err` to `mapFallbackDispatchError` (follow-up to prior review feedback missed in PR #3066).

View File

@@ -0,0 +1,6 @@
---
type: Changed
pr: 3075
---
**query architecture deepening pass** — extracted Query Runtime Context, Native Dispatch Adapter, and Query CLI Output Modules so dispatch policy, runtime context policy, and CLI projection logic each live behind focused seams with higher locality and leverage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2990
---
gsd-code-fixer worktree no longer fails on the same-branch checkout — the agent now creates a new gsd-reviewfix/ branch via git worktree add -b and fast-forwards the user's branch on cleanup. See #2990.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 2986
---
Test suite for config-schema.cjs is now mutation-resistant — 95 typed assertions kill the 124 surviving Stryker mutants from the 4.62% baseline. Tests target static-key fast path, dynamic-pattern .some semantics, polarity, and regex-anchor tightening. See #2986.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3008
---
**`tests/install-minimal.test.cjs:307` no longer races on shared `os.tmpdir()` under parallel CI** — the previous shape compared `listTmpStageDirs()` snapshots before and after the throw. Under `scripts/run-tests.cjs --test-concurrency=4`, `tests/install-minimal-all-runtimes.test.cjs` runs in a parallel process and creates/removes `gsd-minimal-skills-*` dirs in the shared OS tmpdir between snapshots, so `deepStrictEqual` failed deterministically when the parallel process happened to have a live stage dir during the snapshot window. Fix: stub `fs.mkdtempSync` to record THIS call's stage dir, then assert that exact path no longer exists after the throw — no global filesystem snapshot, no race. (#3008)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3022
---
**Codex SessionStart hook now uses absolute Node binary path** — closes the gap left after #3002. The Codex install path wrote `command = "node ${path}"` directly into config.toml, bypassing `resolveNodeRunner()`. Under GUI/minimal-PATH runtimes (`/usr/bin:/bin:/usr/sbin:/sbin`), bare `node` failed to resolve, exit 127. Now routed through new `buildCodexHookBlock()` helper. Reinstall path migrates legacy bare-node entries via new `rewriteLegacyCodexHookBlock()`. See #3017.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: TBD
---
**Codex skill adapter no longer instructs the agent to silently default discuss-phase decisions.** When `request_user_input` was rejected (Default mode), the generated adapter said "pick a reasonable default" — so `$gsd-discuss-phase` proceeded toward writing CONTEXT.md / DISCUSSION-LOG.md / checkpoints without ever asking the user. Adapter prose now requires the agent to STOP, present plain-text questions, and wait, with explicit named exceptions (`--auto`/`--all`/explicit user approval). See #3018.

View File

@@ -0,0 +1,6 @@
---
type: Changed
pr: 3074
---
**query CLI path extracted into a dedicated Query CLI Adapter Module**`sdk/src/cli.ts` now delegates query-specific dispatch, error mapping, and output/exit handling to `sdk/src/query/query-cli-adapter.ts` for better locality and testability.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3012
---
**Post-install message and update.md no longer recommend the removed `/gsd-reapply-patches` command** — after PR #2824 consolidated 86 skills into ~58, `/gsd-reapply-patches` was folded into a flag (`/gsd-update --reapply`). The 1.39.1 hotfix (#2954) updated `help.md` but missed `bin/install.js`'s `reportLocalPatches` runtime emitter, `get-shit-done/workflows/update.md` Step 4, and the English + zh-CN/ja-JP/ko-KR doc set. Users hit "Unknown command" after every install with backed-up patches. All five runtime branches in `reportLocalPatches` (claude, opencode, kilo, copilot, gemini, codex, cursor) now emit the consolidated form. Regression: `tests/bug-3010-reapply-patches-references.test.cjs` scans `bin/install.js`, every workflow file, and every doc (excluding CHANGELOG history and help.md's deprecation notice) for stale recommendations. See #3010.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 0
---
**Documentation refreshed for v1.40.0** — full audit of `docs/` against the 1.40.0-rc.1 release surface. Updates command lists, walkthroughs, and inventory rows for the 86→59 skill consolidation (#2790), the six namespace meta-skills with two-stage routing (#2792), the `/gsd-health --context` guard, the phase-lifecycle status-line read-side (#2833), and the Gemini colon-form / non-Gemini hyphen-form slash-command split. Translations in ja-JP/ko-KR/zh-CN/pt-BR mirror the structural changes; new English prose is marked with `<!-- TODO i18n -->` for human translator follow-up. CHANGELOG.md `[Unreleased]` section regrouped under Feature/Enhancement/Fix headers.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: TBD
---
**`dynamic_routing` block in `.planning/config.json` for failure-tier escalation (#3024).** Each agent declares a default tier (`light` / `standard` / `heavy`); when `dynamic_routing.enabled: true`, the resolver picks `tier_models[default_tier]` for the first spawn and escalates one tier up on orchestrator-detected soft failure (capped by `max_escalations`). Disabled by default — fully backward compatible. Composes with `model_overrides` (higher precedence) and `models.<phase_type>` (lower) for full cost-control flexibility. Adds new resolver `resolveModelForTier(cwd, agent, attempt)` to `core.cjs` for orchestrator integration.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 2975
---
**Changeset-fragment workflow** — eliminates CHANGELOG.md merge conflicts. Each PR drops `.changeset/<random-name>.md` with frontmatter (`type:`, `pr:`) plus a markdown body; the release-time `npm run changelog:render` consolidates fragments into `CHANGELOG.md` and deletes them. CI lint (`npm run lint:changeset`) requires a fragment on any PR touching user-facing files (`bin/`, `get-shit-done/`, `agents/`, `commands/`, `hooks/`, `sdk/src/`); contributors can opt out via the `no-changelog` label for purely internal changes. See [.changeset/README.md](.changeset/README.md) and CONTRIBUTING.md for the workflow.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3114
---
**`/gsd-progress --next` doc migration is fully consistent** — command docs now use clear `--next` wording, FEATURES TOC anchors match renamed headings, and regression tests enforce stale-command detection via structured slash-command token checks.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3117
---
**Worktree prune regression checks are now path-normalized** — pruning safety tests now parse `git worktree list --porcelain` and assert structured normalized paths, preventing path-separator false negatives across platforms while preserving non-destructive prune guarantees.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3119
---
**Optional findings probe guard checks now use structured parsing** — regression tests now parse fenced bash blocks and validate sketch/spike findings probes as structured command records, ensuring non-fatal `|| true` guards are enforced without raw source grep assertions.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3138
---
**`gsd-planner.md` directive language restored** — 10 instances of `CRITICAL`/`MANDATORY`/`ALWAYS`/`MUST` emphasis were silently removed in v1.38.4 (PR #2489) without documentation, conflicting with that release's stated sycophancy-hardening intent. Downstream effect: planner output in v1.38.4v1.40.x exhibited weaker adherence to user decisions and requirement coverage, as observed in #3087. Restored: `CRITICAL: User Decision Fidelity`, `CRITICAL: Never Simplify User Decisions`, `Multi-Source Coverage Audit (MANDATORY in every plan set)`, `Audit ALL four source types`, `Discovery is MANDATORY`, `ALWAYS split if:`, `requirements MUST list`, `CRITICAL: Every requirement ID MUST appear`, `ALWAYS use the Write tool`, and `CRITICAL — File naming convention`. Closes #3087.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3122
---
**Milestone close now repairs missing STATE narrative sections** — when `## Current Position` or `## Operator Next Steps` headings are absent, milestone completion appends canonical sections so state remains deterministic and consistently points operators to `/gsd-new-milestone`.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3111
---
**Progress routing command guidance remains canonical** — pre-planning assumption checks in progress routing now consistently assert and document `/gsd-discuss-phase` as the replacement path, with tests enforcing structured slash-command token checks.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3096
---
**`ai-integration-phase` Steps 7+8 now enforce sequential execution and Edit-only tool discipline** — when `gsd-ai-researcher` and `gsd-domain-researcher` were dispatched in parallel (an optimization an orchestrator could reasonably make since the sections appeared disjoint), `gsd-domain-researcher`'s `Write` call at finalization silently replaced the entire AI-SPEC.md with its pre-researcher copy, losing Sections 3/4. Confirmed at 40% incidence rate (2 of 5 agents on a real run). Fix adds an explicit sequential ordering note to Steps 7+8 ("MUST run sequentially — wait for Step 7 to complete before spawning Step 8") and injects Edit-only tool discipline into both agent prompts ("Use the Edit tool exclusively — NEVER use Write on this file"). Closes #3096.

View File

@@ -0,0 +1,11 @@
---
type: Fixed
pr: 3097
---
**Executor agents now detect and halt on cwd-drift out of worktrees (#3097)** — when a Bash call `cd`'d out of a worktree, `[ -f .git ]` became false (main repo's `.git` is a directory), silently skipping all HEAD/branch guards and allowing commits to land on the main repo's branch. Adds step 0a (cwd-drift sentinel using `git rev-parse --git-dir` + a per-worktree sentinel file at `.git/worktrees/<name>/gsd-spawn-toplevel`) to `gsd-executor.md`'s `task_commit_protocol`. Closes #3097.
---
type: Fixed
pr: 3099
---
**Executor agents now detect absolute paths that resolve outside the worktree (#3099)** — absolute paths constructed from the orchestrator's `pwd` (main repo root) resolved to the main repo when used in Edit/Write calls from a worktree, silently losing work. Adds step 0b (absolute-path guard using `WT_ROOT=$(git rev-parse --show-toplevel)`) with a clear warning and instructions to prefer relative paths. Both guards are documented in `references/worktree-path-safety.md` (loaded into every executor spawn prompt via `<execution_context>`). Closes #3099.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3142
---
**`secure-phase` no longer rubber-stamps SECURITY.md for legacy phases with no `<threat_model>` blocks** — Step 3's short-circuit previously exited to Step 6 (write clean SECURITY.md) whenever `threats_open: 0`, regardless of whether zero threats meant "all mitigated" or "none were ever written". Legacy phases authored before `<threat_model>` blocks became canonical now trigger **retroactive-STRIDE mode** in Step 5: the auditor builds a register from implementation files before verifying mitigations. Step 2c now tracks `register_authored_at_plan_time` and Step 3 gates the skip on both `threats_open: 0 AND register_authored_at_plan_time: true`. Closes #3120.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3121
---
**`gsd-sdk query commands` no longer returns "Unknown command"** — `commands` was referenced in `references/workstream-flag.md` and by agent tooling for verb discovery but had no SDK handler. A new `commandsList` handler in the native registry returns a sorted JSON array of all registered verb strings. `check.decision-coverage-plan` and `check.decision-coverage-verify` were already registered in the SDK native registry; the remaining gap was the `commands` introspection verb. Closes #3121.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3126
---
**`global:` skill resolution now uses the correct runtime home directory** — `buildAgentSkillsBlock()` hardcoded `globalSkillsBase` to `~/.claude/skills` regardless of the active runtime, causing every `global:` skill lookup to silently fail on non-Claude runtimes (Cursor, Gemini, Codex, Windsurf, etc.). Introduces `get-shit-done/bin/lib/runtime-homes.cjs` — a first-class runtime→directory mapping module covering all 15 supported runtimes with their canonical env-var overrides. Notable specifics: Hermes Agent uses a nested `skills/gsd/<skillName>/` layout (#2841); Cline is rules-based and returns `null` (no skills directory); `CLAUDE_CONFIG_DIR` env var was previously missing for Claude. Warning messages now show the actual runtime-specific path. Closes #3126.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3127
---
**`state.begin-phase` is now idempotent** — when called on a phase already in-flight (e.g. `--wave N` resume), it no longer overwrites `Current Plan`, `stopped_at` narrative, `Plan: N of M` body line, or `Last Activity Description` with stale values from the last `plan-phase` run. An idempotency guard reads the current `Status` field before writing: if it already contains `Executing Phase N`, only the `Last Activity` date and a resume-specific activity line are updated; all execution-progress fields are preserved. First-time execution (Status ≠ Executing) continues to write all fields as before. Closes #3127.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3128
---
**`roadmap.cjs` plan_count now correctly detects `{N}-PLAN-{NN}-{slug}.md` files** — the manager-dashboard plan-count filter matched only `*-PLAN.md` and `PLAN.md`, missing the slug-form layout (`5-PLAN-01-setup.md`) that `gsd-plan-phase` actually writes. `init manager` returned `plan_count: 0` / `disk_status: "discussed"` for fully-planned phases, causing the manager to recommend and dispatch redundant background planner agents. Same regex flaw as #2893 (fixed in `phase.cjs` via PR #2896); `roadmap.cjs` was missed in that sweep. Fix applies the same `looksLikePlanFile` logic (with `PLAN-OUTLINE` and `pre-bounce` exclusions) to `countPhasePlansAndSummaries`. Closes #3128.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3141
---
**`gsd-validate-commit.sh` community hook now catches all git commit forms** — the previous `[[ "$CMD" =~ ^git[[:space:]]+commit ]]` bash regex silently bypassed Conventional Commits enforcement for `git -C /path commit`, `GIT_AUTHOR_NAME=x git commit`, and `/usr/bin/git commit`. Introduces `hooks/lib/git-cmd.js` — a token-walk classifier (`isGitSubcommand(cmd, sub)`) that correctly handles env-prefix assignments, `-C path` working-directory flags, full-path executables, `--git-dir=` options, and all git global boolean flags. The hook now delegates detection to this module — the single source of truth for all hooks that gate on git subcommands. Closes #3129.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3130
---
**`update.md` npx invocations hardened against cache-stale and Bash-tool token-routing failures** — the previous `npx -y get-shit-done-cc@latest` form had two failure modes: (1) npx serving a cached older version instead of `@latest`, and (2) Bash-tool wrappers misrouting the `@` token, producing `Unknown command: "get-shit-done-cc@latest"`. All three sibling invocations (local, global, unknown/fallback) now use `npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc` — the `--package=` flag forces a fresh registry fetch and the `--` separator prevents token misrouting. Closes #3130.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3135
---
**`/gsd-capture --backlog` now has a workflow to load** — PR #2824 consolidated `add-backlog` into the `--backlog` flag on `/gsd-capture` and wired `commands/gsd/capture.md` to delegate to `workflows/add-backlog.md` via `execution_context`. The workflow file was never created, leaving the routing with no implementation to load. Restores `get-shit-done/workflows/add-backlog.md` with the full process from the deleted `commands/gsd/add-backlog.md`: find next 999.x slot via `phase.next-decimal`, write ROADMAP entry before creating the phase directory (preserving the #2280 ordering invariant), create `.planning/phases/{N}-{slug}/`, and commit. Also fixes `docs/INVENTORY.md` which incorrectly attributed `--backlog` routing to `add-todo.md`. Adds a broad regression test that every `execution_context` `@`-reference in any `commands/gsd/*.md` resolves to an existing workflow file, preventing this class of gap from silently re-appearing. Closes #3135.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3037
---
**Gemini local install no longer duplicates `/gsd:*` commands across user and workspace scopes** — when GSD is already installed at the user scope (`~/.gemini/commands/gsd/`) and you run `npx get-shit-done-cc --gemini --local` in a project, the installer now skips writing `commands/gsd/` to `<project>/.gemini/` and prints a one-line warning explaining why. Previously, both scopes received the same 65 command files, and Gemini's conflict detector renamed every `/gsd:*` command to `/workspace.gsd:*` and `/user.gsd:*`, breaking the documented namespace. Closes #3037.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2994
---
/gsd-reapply-patches Step 5 verifier now resolves at runtime — moved scripts/verify-reapply-patches.cjs to get-shit-done/bin/ which is shipped by the installer. The legacy scripts/ directory is not copied to user installs. See #2994.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3060
---
**Query mutation event mapping moved to dedicated module** — preserves event payloads while improving registry locality and test surface.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3026
---
**`gsd-sdk query <subcommand> --help` now reaches the handler instead of returning top-level usage.** The query argv parser harvested `--help` as a global flag and `main()` short-circuited dispatch — there was no path to discover what arguments a query subcommand accepts. The parser now leaves `--help` in `queryArgv` so the handler/fallback can render contextual help. The `gsd-tools.cjs` fallback now renders top-level usage on `--help` (instead of erroring), preserving #1818's anti-hallucination invariant by NOT executing the destructive command. See #3019.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3060
---
**Alias-family handler maps moved to dedicated catalog module** — keeps command keys/order while reducing createRegistry coupling and improving family-level locality.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3028
---
**Installer no longer prints `✓ GSD SDK ready` when the shim is unreachable from the user's runtime shells.** The previous check used `process.env.PATH` from the install subprocess, which often differs from the user's later interactive shells (POSIX `~/.local/bin` not in login shell, node-version-manager PATH shims). Added `getUserShellPath()` helper that probes `$SHELL -lc 'printf %s "$PATH"'` and `isGsdSdkOnPath(pathString?)` overload that accepts an explicit PATH; the install-time check now downgrades to the actionable `⚠` diagnostic from PR #3014 when install-PATH and user-shell-PATH disagree. Windows cross-shell support tracked separately. See #3020.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 2840
---
**`docs/issue-driven-orchestration.md` — recipe for driving GSD from a tracker issue** — new guide that maps Symphony-style orchestration concepts (workflow, isolated agent workspace, proof-of-work, human review gate, follow-up capture) onto existing GSD primitives (`/gsd-new-workspace`, `/gsd-manager`, `/gsd-autonomous`, `/gsd-verify-work`, `/gsd-review`, `/gsd-ship`, `STATE.md`, phase artifacts). Documentation only — no new commands, no daemon, no tracker integration.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2994
---
/gsd-reapply-patches Step 5 verifier now resolves at runtime — moved scripts/verify-reapply-patches.cjs to get-shit-done/bin/ which is shipped by the installer. The legacy scripts/ directory is not copied to user installs. See #2994.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2979
---
Managed JS hooks now resolve under GUI/minimal-PATH runtimes — installer emits process.execPath (absolute, quoted, forward-slash-normalized) as the runner for every .js hook command instead of bare node. See #2979.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 2995
---
Post-install path smoke test for workflow-invoked scripts — audits every node ${GSD_HOME}/...cjs invocation in workflows resolves at the runtime-installed path. See #2995.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3043
---
milestone complete now scopes phase stats to the explicit version argument and errors when that version is missing from a versioned ROADMAP milestone section.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3011
---
**Actionable diagnostic when `gsd-sdk` is not on PATH after install** — Windows users (and others on multi-shell setups) reported that the previous "GSD SDK files are present but `gsd-sdk` is not on your PATH" warning gave them no way to fix it: no path to look at, no shell-specific commands, no mention of the npx-cache caveat. New `formatSdkPathDiagnostic({ shimDir, platform, runDir })` helper returns a typed IR with the resolved shim location, platform-specific PATH-export commands (PowerShell / cmd.exe / Git Bash on Windows; `export PATH` on POSIX), and an npx-specific note when running under an `_npx` cache segment (where the shim may be written to a temp dir that won't persist). The console renderer in `bin/install.js` emits the lines from the IR; tests assert on the typed fields directly. (#3011)

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 3032
---
**Documentation: MCP tool schema as a context-budget concern (#3025).** Adds new sections to `get-shit-done/references/context-budget.md` and `docs/USER-GUIDE.md` explaining that every enabled MCP server injects its tool schema into every turn — heavyweight servers (browser/playwright, Mac-tools, Windows-tools) can cost 20k+ tokens each, often dwarfing what `model_profile` tuning saves. The toggle lives in `.claude/settings.json` (`enabledMcpjsonServers` / `disabledMcpjsonServers`) and is a Claude Code harness concern, not a GSD concern. Includes a pre-phase audit checklist (browser, platform-specific, cross-project, duplicates) and notes the multiplier interaction with `model_profile`. Companion to #3023 (per-phase-type model map) and #3024 (dynamic routing); together they cover the three biggest cost levers.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2997
---
SDK config-set/config-get and init responses no longer echo plaintext API keys. New sdk/src/query/secrets.ts ports SECRET_CONFIG_KEYS masking from CJS; init bundles only mask string values to preserve the boolean availability-flag contract. See #2997.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2992
---
/gsd-update queries wrong npm package names — moved package name into a deterministic check-latest-version.cjs script and updated the workflow to use ${GSD_DIR} from get_installed_version. See #2992.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3007
---
**PR templates now point at the changeset workflow** — the `Fix`, `Enhancement`, and `Feature` PR templates previously asked contributors to tick `CHANGELOG.md updated`, which contradicted the post-#2978 rule that `CHANGELOG.md` must not be edited directly. Each checkbox now references `npm run changeset` (and the `no-changelog` opt-out where applicable).

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3060
---
**CLI query CJS fallback execution extracted to dedicated adapter module** — preserves logs/help passthrough behavior while improving fallback locality and testability.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3060
---
**Query mutation event emission now uses a dedicated decorator seam** — preserves fire-and-forget behavior while reducing registry coupling and improving testability.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 3030
---
**`models` block in `.planning/config.json` for per-phase-type model selection (#3023).** A new resolution layer between per-agent `model_overrides` and the `model_profile` tier table. Six named slots (`planning` / `discuss` / `research` / `execution` / `verification` / `completion`) accept tier aliases (`opus` / `sonnet` / `haiku` / `inherit`). Lets you express "Opus for planning, Sonnet for the rest" in two lines without learning the agent taxonomy. Fully backward compatible — configs without `models` behave exactly as today.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2998
---
gsd-pristine/ is now populated by the installer when local patches are detected — saveLocalPatches calls a new populatePristineDir helper that runs the install transform pipeline into a tmp staging dir and copies modified files into pristineDir. The reapply-patches Step 5 verifier no longer falls back to its over-broad heuristic. See #2998.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2997
---
SDK config-set/config-get and init responses no longer echo plaintext API keys. New sdk/src/query/secrets.ts ports SECRET_CONFIG_KEYS masking from CJS; init bundles only mask string values to preserve the boolean availability-flag contract. See #2997.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 2995
---
Post-install path smoke test for workflow-invoked scripts — audits every node ${GSD_HOME}/...cjs invocation in workflows resolves at the runtime-installed path. See #2995.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3108
---
Query module architecture deepened with compatibility-preserving seams — command policy now derives from command definitions, and dispatch/topology/registry seams are consolidated for better locality while preserving existing query behavior.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3112
---
Fixes for issue #3112 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3113
---
Fixes for issue #3113 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3115
---
Fixes for issue #3115 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3116
---
Fixes for issue #3116 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3118
---
Fixes for issue #3118 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3123
---
Fixes for issue #3123 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3124
---
Fixes for issue #3124 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3125
---
Fixes for issue #3098 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3060
---
**Query fallback orchestration now shared** — CLI and SDK query dispatch now use one planning seam for native vs CJS fallback decisions with behavior parity preserved.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3060
---
**Query/transport policy data now converged in shared module** — mutation and raw-output policy wiring now share one source of truth to reduce drift.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3042
---
**`/gsd-research-phase` consolidated into `/gsd-plan-phase --research-phase <N>`** — the standalone research command's slash-command stub was never registered (#3042). Rather than restore the orphan, the research-only capability now lives as a flag on `/gsd-plan-phase`. New modifiers: `--view` prints existing `RESEARCH.md` to stdout without spawning, `--research` forces refresh, otherwise prompts `update / view / skip` when `RESEARCH.md` already exists. Also scrubs four other stale slash-command references (`/gsd-check-todos`, `/gsd-new-workspace`, `/gsd-status`, residual `/gsd-plan-milestone-gaps`) across English + 4 localized doc sets (#3044). Closes #3042 and #3044.

View File

@@ -0,0 +1,6 @@
---
type: Changed
pr: 3131
---
**Re-wired 4 orphaned workflows as flags on parent commands** — six workflows were mis-categorised as "outright deleted dead skills" during the #2790 consolidation; two were caught by prior PRs (#3045, #3038) and four are fixed here. New flags: `/gsd-discuss-phase --assumptions` (surfaces Claude's implementation assumptions before planning), `/gsd-pause-work --report` (generates a post-session summary in `.planning/reports/`), `/gsd-manager --analyze-deps` (scans ROADMAP phases for dependency relationships before parallel execution), `/gsd-import --from-gsd2` (reverse-migrates a GSD-2 `.gsd/` project back to GSD v1 `.planning/` format). Also sweeps 29 stale `/gsd-*` command references across 27 user-facing files (English + 4 locales). Closes #3131.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3029
---
**`/gsd-code-review-fix` and `/gsd-plan-milestone-gaps` no longer surface as "Unknown command"** — both were consolidated by #2790 (`/gsd-code-review --fix` and inline gap planning in `/gsd-audit-milestone` respectively), but several user-facing surfaces still emitted the old slash forms in their offer text. Fixed audit-milestone offer blocks, gsd-complete-milestone routing, code-review/execute-phase offer text, gsd-code-fixer agent role card, and the doc surfaces (USER-GUIDE, FEATURES, INVENTORY, AGENTS, CONFIGURATION). Closes #3029, closes #3034.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2990
---
gsd-code-fixer worktree no longer fails on the same-branch checkout — the agent now creates a new gsd-reviewfix/ branch via git worktree add -b and fast-forwards the user's branch on cleanup. See #2990.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 2982
---
Extended no-source-grep lint to catch var-binding readFileSync.includes() pattern. Tests now fail when source-grep is hidden behind a parser wrapper. See #2982.

View File

@@ -0,0 +1,6 @@
---
type: Changed
pr: 3065
---
**Dispatch policy seam now returns a structured result contract** across native and fallback query execution paths (`ok`, typed error `kind`, `details`, and final `exit_code`), with CLI consuming the unified result instead of mixed throw/result handling.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3060
---
**Query static command registrations now split into domain catalog modules** — preserves command order/strings while improving registry locality and maintenance.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 3085
---
**`GSDTools` query execution internals now use deep Module seams** — refactors runtime composition, native/subprocess adapters, and output projection behind stable public interfaces for better locality and testability.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 2974
---
Migrated 8 test files from raw text matching (`stdout.includes(...)`, `assert.match(stderr, ...)`) to typed-IR assertions per CONTRIBUTING.md. Adds shared `ERROR_REASON` enum and `--json-errors` flag in `core.cjs`, typed `GRAPHIFY_REASON` in `graphify.cjs`, pure `buildSdkFailFastReport()` IR builder in `bin/install.js`, and Claude Code JSON envelope output (`hookSpecificOutput` with typed fields) for `gsd-session-state.sh` and `gsd-phase-boundary.sh`. Tests now assert on structured fields (`reason`, `context`, `state_present`, `planning_modified`, etc.) instead of substring matching. See #2974.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 2795
---
**Optional update banner for non-GSD statusline users** — when the installer detects you've declined or kept a non-GSD statusline, it now offers an opt-in `SessionStart` banner that surfaces update availability via the existing `~/.cache/gsd/gsd-update-check.json` cache. Silent when up-to-date, rate-limits failure diagnostics to once per 24h, removed cleanly by `npx get-shit-done-cc --uninstall`.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2973
---
/gsd-profile-user --refresh writes dev-preferences.md to ~/.claude/skills/gsd-dev-preferences/SKILL.md instead of the legacy commands/gsd/ directory. Installer migrates any preserved legacy file to the new location. See #2973.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2992
---
/gsd-update queries wrong npm package names — moved package name into a deterministic check-latest-version.cjs script and updated the workflow to use ${GSD_DIR} from get_installed_version. See #2992.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2979
---
Managed JS hooks now resolve under GUI/minimal-PATH runtimes — installer emits process.execPath (absolute, quoted, forward-slash-normalized) as the runner for every .js hook command instead of bare node. See #2979.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 2982
---
Extended no-source-grep lint to catch var-binding readFileSync.includes() pattern. Tests now fail when source-grep is hidden behind a parser wrapper. See #2982.

26
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,26 @@
# CodeRabbit configuration — gsd-build/get-shit-done
#
# Schema: https://docs.coderabbit.ai/reference/yaml-template/
#
# Project context: GSD ships a CLI tool + an agent runtime, not a documented
# public library. We carry rich JSDoc on internal helpers that warrant it
# (see bin/install.js, get-shit-done/bin/lib/*.cjs) but we do not enforce a
# blanket docstring coverage bar — see issue #2932 for rationale.
reviews:
pre_merge_checks:
# Disable docstring coverage check.
#
# The check produces false-positive warnings on PRs whose new code is
# entirely test files: it counts test(...) / beforeEach / afterEach
# arrow-function callbacks as functions and then reports 0% coverage
# because nothing has JSDoc. There is no per-check path filter in CR's
# documented schema that would let us exclude tests/** while keeping
# the check active elsewhere, and the top-level path_filters approach
# would silence ALL CR review on tests (security scans, out-of-scope
# checks, line-level findings) which we want to keep.
#
# All other CR pre-merge checks (out-of-scope, security, title) remain
# at their defaults.
docstrings:
mode: off

6
.githooks/pre-commit Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
npm run check:alias-drift
fi

48
.githooks/pre-push Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
zero_sha='0000000000000000000000000000000000000000'
blocked_regex="${GSD_BLOCKED_AUTHOR_REGEX:-}"
# Local-only guard: no-op unless the developer opts in via env var, e.g.
# export GSD_BLOCKED_AUTHOR_REGEX='@example-corp\.com$'
if [[ -z "$blocked_regex" ]]; then
exit 0
fi
violations=()
while read -r local_ref local_sha remote_ref remote_sha; do
# branch/tag deletion
if [[ "$local_sha" == "$zero_sha" ]]; then
continue
fi
if [[ "$remote_sha" == "$zero_sha" ]]; then
# New remote ref: inspect commits not already on any remote
commit_list=$(git rev-list "$local_sha" --not --remotes)
else
commit_list=$(git rev-list "$remote_sha..$local_sha")
fi
while read -r commit; do
[[ -z "$commit" ]] && continue
author_email=$(git show -s --format='%ae' "$commit")
lower_email=$(printf '%s' "$author_email" | tr '[:upper:]' '[:lower:]')
if printf '%s' "$lower_email" | grep -Eq "$blocked_regex"; then
violations+=("$commit <$author_email>")
fi
done <<< "$commit_list"
done
if [[ ${#violations[@]} -gt 0 ]]; then
{
echo "Push blocked: commit author email matched local blocked regex ($blocked_regex)."
echo "Rewrite author info before pushing these commits:"
for v in "${violations[@]}"; do
echo " - $v"
done
echo "Suggested fix: git rebase -i <base> --exec \"git commit --amend --no-edit --author='Your Name <non-enterprise@email>'\""
} >&2
exit 1
fi

View File

@@ -90,7 +90,7 @@ body:
label: What happened?
description: Describe what went wrong. Be specific about which GSD command you were running.
placeholder: |
When I ran `/gsd:plan`, the system...
When I ran `/gsd-plan`, the system...
validations:
required: true
@@ -111,8 +111,8 @@ body:
placeholder: |
1. Install GSD with `npx get-shit-done-cc@latest`
2. Select runtime: Claude Code
3. Run `/gsd:init` with a new project
4. Run `/gsd:plan`
3. Run `/gsd-init` with a new project
4. Run `/gsd-plan`
5. Error appears at step...
validations:
required: true

118
.github/ISSUE_TEMPLATE/chore.yml vendored Normal file
View File

@@ -0,0 +1,118 @@
---
name: Chore / Maintenance
description: Internal improvements — refactoring, test quality, CI/CD, dependency updates, tech debt.
labels: ["type: chore", "needs-triage"]
body:
- type: markdown
attributes:
value: |
## Internal maintenance work
Use this template for work that improves the **project's health** without changing user-facing behavior. Examples:
- Test suite refactoring or standardization
- CI/CD pipeline improvements
- Dependency updates
- Code quality or linting changes
- Build system or tooling updates
- Documentation infrastructure (not content — use Docs Issue for content)
- Tech debt paydown
If this changes how GSD **works** for users, use [Enhancement](./enhancement.yml) or [Feature Request](./feature_request.yml) instead.
- type: checkboxes
id: preflight
attributes:
label: Pre-submission checklist
options:
- label: This does not change user-facing behavior (commands, output, file formats, config)
required: true
- label: I have searched existing issues — this has not already been filed
required: true
- type: input
id: chore_title
attributes:
label: What is the maintenance task?
description: A short, concrete description of what needs to happen.
placeholder: "e.g., Migrate test suite to node:assert/strict, Update c8 to v12, Add Windows CI matrix entry"
validations:
required: true
- type: dropdown
id: chore_type
attributes:
label: Type of maintenance
options:
- Test quality (coverage, patterns, runner)
- CI/CD pipeline
- Dependency update
- Refactoring / code quality
- Build system / tooling
- Documentation infrastructure
- Tech debt
- Other
validations:
required: true
- type: textarea
id: current_state
attributes:
label: Current state
description: |
Describe the current situation. What is the problem or debt? Include numbers where possible (test count, coverage %, build time, dependency age).
placeholder: |
73 of 89 test files use `require('node:assert')` instead of `require('node:assert/strict')`.
CONTRIBUTING.md requires strict mode. Non-strict assert allows type coercion in `deepEqual`,
masking potential bugs.
validations:
required: true
- type: textarea
id: proposed_work
attributes:
label: Proposed work
description: |
What changes will be made? List files, patterns, or systems affected.
placeholder: |
- Replace `require('node:assert')` with `require('node:assert/strict')` across all 73 test files
- Replace `try/finally` cleanup with `t.after()` hooks per CONTRIBUTING.md standards
- Verify all 2148 tests still pass
validations:
required: true
- type: textarea
id: acceptance_criteria
attributes:
label: Done when
description: |
List the specific conditions that mean this work is complete. These should be verifiable.
placeholder: |
- [ ] All test files use `node:assert/strict`
- [ ] Zero `try/finally` cleanup blocks in test lifecycle code
- [ ] CI green on all matrix entries (Node 22/24, Ubuntu/macOS/Windows)
- [ ] No change to user-facing behavior
validations:
required: true
- type: dropdown
id: area
attributes:
label: Area affected
options:
- Test suite
- CI/CD
- Build system
- Core library code
- Installer
- Documentation tooling
- Multiple areas
validations:
required: true
- type: textarea
id: additional_context
attributes:
label: Additional context
description: Related issues, prior art, or anything else that helps scope this work.
validations:
required: false

View File

@@ -4,7 +4,7 @@ contact_links:
url: https://github.com/gsd-build/get-shit-done/discussions
about: v1.31.0 was not published to npm due to a hardware failure. Read the pinned announcement for the workaround before opening an issue.
- name: Discord Community
url: https://discord.gg/gsd
url: https://discord.gg/mYgfVNfA2r
about: Ask questions and get help from the community
- name: Discussions
url: https://github.com/gsd-build/get-shit-done/discussions

160
.github/ISSUE_TEMPLATE/enhancement.yml vendored Normal file
View File

@@ -0,0 +1,160 @@
---
name: Enhancement Proposal
description: Propose an improvement to an existing feature. Read the full instructions before opening this issue.
labels: ["enhancement", "needs-review"]
body:
- type: markdown
attributes:
value: |
## ⚠️ Read this before you fill anything out
An enhancement improves something that already exists — better output, expanded edge-case handling, improved performance, cleaner UX. It does **not** add new commands, new workflows, or new concepts. If you are proposing something new, use the [Feature Request](./feature_request.yml) template instead.
**Before opening this issue:**
- Confirm the thing you want to improve actually exists and works today.
- Read [CONTRIBUTING.md](../../CONTRIBUTING.md#-enhancement) — understand what `approved-enhancement` means and why you must wait for it before writing any code.
**What happens after you submit:**
A maintainer will review this proposal. If it is incomplete or out of scope, it will be **closed**. If approved, it will be labeled `approved-enhancement` and you may begin coding.
**Do not open a PR until this issue is labeled `approved-enhancement`.**
- type: checkboxes
id: preflight
attributes:
label: Pre-submission checklist
description: You must check every box. Unchecked boxes are an immediate close.
options:
- label: I have confirmed this improves existing behavior — it does not add a new command, workflow, or concept
required: true
- label: I have searched existing issues and this enhancement has not already been proposed
required: true
- label: I have read CONTRIBUTING.md and understand I must wait for `approved-enhancement` before writing any code
required: true
- label: I can clearly describe the concrete benefit — not just "it would be nicer"
required: true
- type: input
id: what_is_being_improved
attributes:
label: What existing feature or behavior does this improve?
description: Name the specific command, workflow, output, or behavior you are enhancing.
placeholder: "e.g., `/gsd-plan` output, phase status display in statusline, context summary format"
validations:
required: true
- type: textarea
id: current_behavior
attributes:
label: Current behavior
description: |
Describe exactly how the thing works today. Be specific. Include example output or commands if helpful.
placeholder: |
Currently, `/gsd-status` shows:
```
Phase 2/5 — In Progress
```
It does not show the phase name, making it hard to know what phase you are actually in without
opening STATE.md.
validations:
required: true
- type: textarea
id: proposed_behavior
attributes:
label: Proposed behavior
description: |
Describe exactly how it should work after the enhancement. Be specific. Include example output or commands.
placeholder: |
After the enhancement, `/gsd-status` would show:
```
Phase 2/5 — In Progress — "Implement core auth module"
```
The phase name is pulled from STATE.md and appended to the existing output.
validations:
required: true
- type: textarea
id: reason_and_benefit
attributes:
label: Reason and benefit
description: |
Answer both of these clearly:
1. **Why is the current behavior a problem?** (Not just inconvenient — what goes wrong, what is harder than it should be, or what is confusing?)
2. **What is the concrete benefit of the proposed behavior?** (What becomes easier, faster, less error-prone, or clearer?)
Vague answers like "it would be better" or "it's more user-friendly" are not sufficient.
placeholder: |
**Why the current behavior is a problem:**
When working in a long session, the AI agent frequently loses track of which phase is active
and must re-read STATE.md. The numeric-only status gives no semantic context.
**Concrete benefit:**
Showing the phase name means the agent can confirm the active phase from the status output
alone, without an extra file read. This reduces context consumption in long sessions.
validations:
required: true
- type: textarea
id: scope
attributes:
label: Scope of changes
description: |
List the files and systems this enhancement would touch. Be complete.
An enhancement should have a narrow, well-defined scope. If your list is long, this might be a feature, not an enhancement.
placeholder: |
Files modified:
- `get-shit-done/commands/gsd/status.md` — update output format description
- `get-shit-done/bin/lib/state.cjs` — expose phase name in status() return value
- `tests/status.test.cjs` — update snapshot and add test for phase name in output
- `CHANGELOG.md` — user-facing change entry
No new files created. No new dependencies.
validations:
required: true
- type: textarea
id: breaking_changes
attributes:
label: Breaking changes
description: |
Does this change existing command output, file formats, or behavior that users or AI agents might depend on?
If yes, describe exactly what changes and how it stays backward compatible (or why it cannot).
Write "None" only if you are certain.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: |
What other ways could this be improved? Why is your proposed approach the right one?
If you haven't considered alternatives, take a moment before submitting.
validations:
required: true
- type: dropdown
id: area
attributes:
label: Area affected
options:
- Core workflow (init, plan, build, verify)
- Planning system (phases, roadmap, state)
- Context management (context engineering, summaries)
- Runtime integration (hooks, statusline, settings)
- Installation / setup
- Output / formatting
- Documentation
- Other
validations:
required: true
- type: textarea
id: additional_context
attributes:
label: Additional context
description: Screenshots, related issues, or anything else that helps explain the proposal.
validations:
required: false

View File

@@ -1,44 +1,165 @@
---
name: Feature Request
description: Suggest a new feature or improvement
labels: ["enhancement"]
description: Propose a new feature. Read the full instructions before opening this issue.
labels: ["feature-request", "needs-review"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a feature! Please describe what you'd like to see.
## ⚠️ Read this before you fill anything out
- type: textarea
id: problem
A feature adds something new to GSD — a new command, workflow, concept, or integration. Features have the **highest bar** for acceptance because every feature adds permanent maintenance burden to a project built for solo developers.
**Before opening this issue:**
- Check [Discussions](https://github.com/gsd-build/get-shit-done/discussions) — has this been proposed and declined before?
- Read [CONTRIBUTING.md](../../CONTRIBUTING.md#-feature) — understand what "approved-feature" means and why you must wait for it before writing code.
- Ask yourself: *does this solve a real problem for a solo developer working with an AI coding tool, or is it a feature I personally want?*
**What happens after you submit:**
A maintainer will review this spec. If it is incomplete, it will be **closed**, not revised. If it conflicts with GSD's design philosophy, it will be declined. If it is approved, it will be labeled `approved-feature` and you may begin coding.
**Do not open a PR until this issue is labeled `approved-feature`.**
- type: checkboxes
id: preflight
attributes:
label: Problem or motivation
description: What problem does this solve? Why do you want this?
placeholder: "I'm frustrated when..."
label: Pre-submission checklist
description: You must check every box. Unchecked boxes are an immediate close.
options:
- label: I have searched existing issues and discussions — this has not been proposed and declined before
required: true
- label: I have read CONTRIBUTING.md and understand that I must wait for `approved-feature` before writing any code
required: true
- label: I have read the existing GSD commands and workflows and confirmed this feature does not duplicate existing behavior
required: true
- label: This feature solves a problem for solo developers using AI coding tools, not a personal preference or workflow I happen to like
required: true
- type: input
id: feature_name
attributes:
label: Feature name
description: A short, concrete name for this feature (not a sales pitch — just what it is).
placeholder: "e.g., Phase rollback command, Auto-archive completed phases, Cross-project state sync"
validations:
required: true
- type: dropdown
id: feature_type
attributes:
label: Type of addition
description: What kind of thing is this feature adding?
options:
- New command (slash command or CLI flag)
- New workflow (multi-step process)
- New runtime integration
- New planning concept (phase type, state, etc.)
- New installation/setup behavior
- New output or reporting format
- Other (describe in spec)
validations:
required: true
- type: textarea
id: solution
id: problem_statement
attributes:
label: Proposed solution
description: How do you think this should work? Include example commands or workflows if possible.
label: The solo developer problem
description: |
Describe the concrete problem this solves for a solo developer using an AI coding tool. Be specific.
Good: "When a phase fails mid-way, there is no way to roll back state without manually editing STATE.md. This causes the AI agent to continue from a corrupted state, producing wrong plans."
Bad: "It would be nice to have a rollback feature." / "Other tools have this." / "I need this for my workflow."
placeholder: |
A new command `/gsd:example` that...
When [specific situation], the developer cannot [specific thing], which causes [specific negative outcome].
validations:
required: true
- type: textarea
id: what_is_added
attributes:
label: What this feature adds
description: |
Describe exactly what is being added. Be specific about commands, output, behavior, and user interaction.
Include example commands or example output where possible.
placeholder: |
A new command `/gsd-rollback` that:
1. Reads the current phase from STATE.md
2. Reverts STATE.md to the previous phase's snapshot
3. Outputs a confirmation with the rolled-back state
Example usage:
```
/gsd-rollback
> Rolled back from Phase 3 (failed) to Phase 2 (completed)
```
validations:
required: true
- type: textarea
id: full_scope
attributes:
label: Full scope of changes
description: |
List every file, system, and area of the codebase this feature would touch. Be exhaustive.
If you cannot fill this out, you do not understand the codebase well enough to propose this feature yet.
placeholder: |
Files that would be created:
- `get-shit-done/commands/gsd/rollback.md` — new slash command definition
Files that would be modified:
- `get-shit-done/bin/lib/state.cjs` — add rollback() function
- `get-shit-done/bin/lib/phases.cjs` — expose phase snapshot API
- `tests/rollback.test.cjs` — new test file
- `docs/COMMANDS.md` — document new command
- `CHANGELOG.md` — entry for this feature
Systems affected:
- STATE.md schema (must remain backward compatible)
- Phase lifecycle state machine
validations:
required: true
- type: textarea
id: user_stories
attributes:
label: User stories
description: Write at least two user stories in the format "As a [user], I want [thing] so that [outcome]."
placeholder: |
1. As a solo developer, I want to roll back a failed phase so that I can re-attempt it without corrupting my project state.
2. As a solo developer, I want rollback to be undoable so that I don't accidentally lose completed work.
validations:
required: true
- type: textarea
id: acceptance_criteria
attributes:
label: Acceptance criteria
description: |
List the specific, testable conditions that must be true for this feature to be considered complete.
These become the basis for reviewer sign-off. Vague criteria ("it works") are not acceptable.
placeholder: |
- [ ] `/gsd-rollback` reverts STATE.md to the previous phase when current phase status is `failed`
- [ ] `/gsd-rollback` exits with an error if there is no previous phase to roll back to
- [ ] `/gsd-rollback` outputs the before/after phase names in its confirmation message
- [ ] Rollback is logged in the phase history so the AI agent can see it happened
- [ ] All existing tests still pass
- [ ] New tests cover the happy path, no-previous-phase case, and STATE.md corruption case
validations:
required: true
- type: dropdown
id: scope
attributes:
label: Which area does this affect?
label: Which area does this primarily affect?
options:
- Core workflow (init, plan, build, verify)
- Planning system (phases, roadmap, state)
- Context management (context engineering, summaries)
- Runtime integration (hooks, statusline, settings)
- Installation / setup
- Documentation
- Other
- Documentation only
- Multiple areas (describe in scope section above)
validations:
required: true
@@ -46,7 +167,7 @@ body:
id: runtimes
attributes:
label: Applicable runtimes
description: Which runtimes should this work with?
description: Which runtimes must this work with? Check all that apply.
options:
- label: Claude Code
- label: Gemini CLI
@@ -58,18 +179,72 @@ body:
- label: Windsurf
- label: All runtimes
- type: textarea
id: breaking_changes
attributes:
label: Breaking changes assessment
description: |
Does this feature change existing behavior, command output, file formats, or APIs?
If yes, describe exactly what breaks and how existing users would migrate.
Write "None" only if you are certain.
placeholder: |
None — this adds a new command and does not modify any existing command behavior or file schemas.
OR:
STATE.md will gain a new `phase_history` array field. Existing STATE.md files without this field
will be treated as having an empty history (backward compatible). The rollback command will
decline gracefully if history is empty.
validations:
required: true
- type: textarea
id: maintenance_burden
attributes:
label: Maintenance burden
description: |
Every feature is code that must be maintained forever. Describe the ongoing cost:
- How does this interact with future changes to phases, state, or commands?
- Does this add external dependencies?
- Does this require documentation updates across multiple files?
- Will this create edge cases or interactions with other features?
placeholder: |
- No new dependencies
- The rollback function must be updated if the STATE.md schema ever changes
- Will need to be tested on each new Node.js LTS release
- The command definition must be kept in sync with any future command format changes
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Have you considered other approaches?
description: |
What other approaches did you consider? Why did you reject them?
If the answer is "I didn't consider any alternatives", this issue will be closed.
placeholder: |
1. Manual STATE.md editing — rejected because it requires the developer to understand the schema
and is error-prone. The AI agent cannot reliably guide this.
2. A `/gsd-reset` command that wipes all state — rejected because it is too destructive and
loses all completed phase history.
validations:
required: true
- type: textarea
id: prior_art
attributes:
label: Prior art and references
description: |
Does any other tool, project, or GSD discussion address this? Link to anything relevant.
If you are aware of a prior declined proposal for this feature, explain why this proposal is different.
validations:
required: false
- type: textarea
id: context
id: additional_context
attributes:
label: Additional context
description: Any other information, screenshots, or examples.
description: Anything else — screenshots, recordings, related issues, or links.
validations:
required: false

View File

@@ -0,0 +1,86 @@
## Enhancement PR
> **Using the wrong template?**
> — Bug fix: use [fix.md](?template=fix.md)
> — New feature: use [feature.md](?template=feature.md)
---
## Linked Issue
> **Required.** This PR will be auto-closed if no valid issue link is found.
> The linked issue **must** have the `approved-enhancement` label. If it does not, this PR will be closed without review.
Closes #
> ⛔ **No `approved-enhancement` label on the issue = immediate close.**
> Do not open this PR if a maintainer has not yet approved the enhancement proposal.
---
## What this enhancement improves
<!-- Name the specific command, workflow, or behavior being improved. -->
## Before / After
**Before:**
<!-- Describe or show the current behavior. Include example output if applicable. -->
**After:**
<!-- Describe or show the behavior after this enhancement. Include example output if applicable. -->
## How it was implemented
<!-- Brief description of the approach. Point to the key files changed. -->
## Testing
### How I verified the enhancement works
<!-- Manual steps or automated tests. -->
### Platforms tested
- [ ] macOS
- [ ] Windows (including backslash path handling)
- [ ] Linux
- [ ] N/A (not platform-specific)
### Runtimes tested
- [ ] Claude Code
- [ ] Gemini CLI
- [ ] OpenCode
- [ ] Other: ___
- [ ] N/A (not runtime-specific)
---
## Scope confirmation
<!-- Confirm the implementation matches the approved proposal. -->
- [ ] The implementation matches the scope approved in the linked issue — no additions or removals
- [ ] If scope changed during implementation, I updated the issue and got re-approval before continuing
---
## Checklist
- [ ] Issue linked above with `Closes #NNN`**PR will be auto-closed if missing**
- [ ] Linked issue has the `approved-enhancement` label — **PR will be closed if missing**
- [ ] Changes are scoped to the approved enhancement — nothing extra included
- [ ] All existing tests pass (`npm test`)
- [ ] New or updated tests cover the enhanced behavior
- [ ] `.changeset/` fragment added (`npm run changeset -- --type Changed --pr <NNN> --body "..."`) — or `no-changelog` label applied if not user-facing
- [ ] Documentation updated if behavior or output changed
- [ ] No unnecessary dependencies added
## Breaking changes
<!-- Does this enhancement change any existing behavior, output format, or API?
If yes, describe exactly what changes and confirm backward compatibility.
Write "None" if not applicable. -->
None

113
.github/PULL_REQUEST_TEMPLATE/feature.md vendored Normal file
View File

@@ -0,0 +1,113 @@
## Feature PR
> **Using the wrong template?**
> — Bug fix: use [fix.md](?template=fix.md)
> — Enhancement to existing behavior: use [enhancement.md](?template=enhancement.md)
---
## Linked Issue
> **Required.** This PR will be auto-closed if no valid issue link is found.
> The linked issue **must** have the `approved-feature` label. If it does not, this PR will be closed without review — no exceptions.
Closes #
> ⛔ **No `approved-feature` label on the issue = immediate close.**
> Do not open this PR if a maintainer has not yet approved the feature spec.
> Do not open this PR if you wrote code before the issue was approved.
---
## Feature summary
<!-- One paragraph. What does this feature add? Assume the reviewer has read the issue spec. -->
## What changed
### New files
<!-- List every new file added and its purpose. -->
| File | Purpose |
|------|---------|
| | |
### Modified files
<!-- List every existing file modified and what changed in it. -->
| File | What changed |
|------|-------------|
| | |
## Implementation notes
<!-- Describe any decisions made during implementation that were not specified in the issue.
If any part of the implementation differs from the approved spec, explain why. -->
## Spec compliance
<!-- For each acceptance criterion in the linked issue, confirm it is met. Copy them here and check them off. -->
- [ ] <!-- Acceptance criterion 1 from issue -->
- [ ] <!-- Acceptance criterion 2 from issue -->
- [ ] <!-- Add all criteria from the issue -->
## Testing
### Test coverage
<!-- Describe what is tested and where. New features require new tests — no exceptions. -->
### Platforms tested
- [ ] macOS
- [ ] Windows (including backslash path handling)
- [ ] Linux
### Runtimes tested
- [ ] Claude Code
- [ ] Gemini CLI
- [ ] OpenCode
- [ ] Codex
- [ ] Copilot
- [ ] Other: ___
- [ ] N/A — specify which runtimes are supported and why others are excluded
---
## Scope confirmation
- [ ] The implementation matches the scope approved in the linked issue exactly
- [ ] No additional features, commands, or behaviors were added beyond what was approved
- [ ] If scope changed during implementation, I updated the issue spec and received re-approval
---
## Checklist
- [ ] Issue linked above with `Closes #NNN`**PR will be auto-closed if missing**
- [ ] Linked issue has the `approved-feature` label — **PR will be closed if missing**
- [ ] All acceptance criteria from the issue are met (listed above)
- [ ] Implementation scope matches the approved spec exactly
- [ ] All existing tests pass (`npm test`)
- [ ] New tests cover the happy path, error cases, and edge cases
- [ ] `.changeset/` fragment added with a user-facing description of the feature (`npm run changeset -- --type Added --pr <NNN> --body "..."`)
- [ ] Documentation updated — commands, workflows, references, README if applicable
- [ ] No unnecessary external dependencies added
- [ ] Works on Windows (backslash paths handled)
## Breaking changes
<!-- Describe any behavior, output format, file schema, or API changes that affect existing users.
For each breaking change, describe the migration path.
Write "None" only if you are certain. -->
None
## Screenshots / recordings
<!-- If this feature has any visual output or changes the user experience, include before/after screenshots
or a short recording. Delete this section if not applicable. -->

74
.github/PULL_REQUEST_TEMPLATE/fix.md vendored Normal file
View File

@@ -0,0 +1,74 @@
## Fix PR
> **Using the wrong template?**
> — Enhancement: use [enhancement.md](?template=enhancement.md)
> — Feature: use [feature.md](?template=feature.md)
---
## Linked Issue
> **Required.** This PR will be auto-closed if no valid issue link is found.
Fixes #
> The linked issue must have the `confirmed-bug` label. If it doesn't, ask a maintainer to confirm the bug before continuing.
---
## What was broken
<!-- One or two sentences. What was the incorrect behavior? -->
## What this fix does
<!-- One or two sentences. How does this fix the broken behavior? -->
## Root cause
<!-- Brief explanation of why the bug existed. Skip for trivial typo/doc fixes. -->
## Testing
### How I verified the fix
<!-- Describe manual steps or point to the automated test that proves this is fixed. -->
### Regression test added?
- [ ] Yes — added a test that would have caught this bug
- [ ] No — explain why: <!-- e.g., environment-specific, non-deterministic -->
### Platforms tested
- [ ] macOS
- [ ] Windows (including backslash path handling)
- [ ] Linux
- [ ] N/A (not platform-specific)
### Runtimes tested
- [ ] Claude Code
- [ ] Gemini CLI
- [ ] OpenCode
- [ ] Other: ___
- [ ] N/A (not runtime-specific)
---
## Checklist
- [ ] Issue linked above with `Fixes #NNN`**PR will be auto-closed if missing**
- [ ] Linked issue has the `confirmed-bug` label
- [ ] Fix is scoped to the reported bug — no unrelated changes included
- [ ] Regression test added (or explained why not)
- [ ] All existing tests pass (`npm test`)
- [ ] `.changeset/` fragment added if this is a user-facing fix (`npm run changeset -- --type Fixed --pr <NNN> --body "..."`) — or `no-changelog` label applied
- [ ] No unnecessary dependencies added
## Breaking changes
<!-- Does this fix change any existing behavior, output format, or API that users might depend on?
If yes, describe. Write "None" if not applicable. -->
None

25
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
labels:
- dependencies
- type: chore
commit-message:
prefix: "chore(deps):"
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
labels:
- dependencies
- type: chore
commit-message:
prefix: "chore(ci):"

View File

@@ -1,59 +1,40 @@
## Linked Issue
## ⚠️ Wrong template — please use the correct one for your PR type
> **Required.** PRs without a linked issue are closed without review.
> Open an issue first if one doesn't exist: https://github.com/gsd-build/get-shit-done/issues/new/choose
Every PR must use a typed template. Using this default template is a reason for rejection.
Closes #
Select the template that matches your PR:
## What
| PR Type | When to use | Template link |
|---------|-------------|---------------|
| **Fix** | Correcting a bug, crash, or behavior that doesn't match documentation | [Use fix template](?template=PULL_REQUEST_TEMPLATE/fix.md) |
| **Enhancement** | Improving an existing feature — better output, expanded edge cases, performance | [Use enhancement template](?template=PULL_REQUEST_TEMPLATE/enhancement.md) |
| **Feature** | Adding something new — new command, workflow, concept, or integration | [Use feature template](?template=PULL_REQUEST_TEMPLATE/feature.md) |
<!-- One sentence: what does this PR do? -->
---
## Why
### Not sure which type applies?
<!-- One sentence: why is this change needed? -->
- If it **corrects broken behavior** → Fix
- If it **improves existing behavior** without adding new commands or concepts → Enhancement
- If it **adds something that doesn't exist today** → Feature
- If you are not sure → open a [Discussion](https://github.com/gsd-build/get-shit-done/discussions) first
## How
---
<!-- Brief description of the approach taken. Skip for trivial changes. -->
### Reminder: Issues must be approved before PRs
## Testing
For **enhancements**: the linked issue must have the `approved-enhancement` label before you open this PR.
### Platforms tested
For **features**: the linked issue must have the `approved-feature` label before you open this PR.
- [ ] macOS
- [ ] Windows (including backslash path handling)
- [ ] Linux
PRs that arrive without a labeled, approved issue are closed without review.
### Runtimes tested
> **No draft PRs.** Draft PRs are automatically closed. Only open a PR when your code is complete, tests pass, and the correct template is used. See [CONTRIBUTING.md](../CONTRIBUTING.md).
- [ ] Claude Code
- [ ] Gemini CLI
- [ ] OpenCode
- [ ] Codex
- [ ] Copilot
- [ ] N/A (not runtime-specific)
See [CONTRIBUTING.md](../CONTRIBUTING.md) for the full process.
### Test details
---
<!-- How did you verify this works? Manual steps, automated tests, etc. -->
## Checklist
- [ ] Issue linked above (`Closes #NNN`) — **PR will be auto-closed if missing**
- [ ] Follows GSD style (no enterprise patterns, no filler)
- [ ] Updates CHANGELOG.md for user-facing changes
- [ ] No unnecessary dependencies added
- [ ] Works on Windows (backslash paths tested)
- [ ] Templates/references updated if behavior changed
- [ ] Existing tests pass (`npm test`)
## Breaking Changes
<!-- List any breaking changes, or write "None" -->
None
## Screenshots / recordings
<!-- If this is a visual change, add before/after screenshots. Delete this section if not applicable. -->
<!-- If you believe your PR genuinely does not fit any of the above categories (e.g., CI/tooling changes,
dependency updates, or doc-only fixes with no linked issue), delete this file and describe your PR below.
Add a note explaining why none of the typed templates apply. -->

85
.github/workflows/auto-branch.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Auto-Branch from Issue Label
on:
issues:
types: [labeled]
permissions:
contents: write
issues: write
jobs:
create-branch:
runs-on: ubuntu-latest
timeout-minutes: 2
if: >-
contains(fromJSON('["bug", "enhancement", "priority: critical", "type: chore", "area: docs"]'),
github.event.label.name)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Create branch
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const label = context.payload.label.name;
const issue = context.payload.issue;
const number = issue.number;
// Generate slug from title
const slug = issue.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.substring(0, 40);
// Map label to branch prefix
const prefixMap = {
'bug': 'fix',
'enhancement': 'feat',
'priority: critical': 'fix',
'type: chore': 'chore',
'area: docs': 'docs',
};
const prefix = prefixMap[label];
if (!prefix) return;
// For priority: critical, use fix/critical-NNN-slug to avoid
// colliding with the hotfix workflow's hotfix/X.Y.Z naming.
const branch = label === 'priority: critical'
? `fix/critical-${number}-${slug}`
: `${prefix}/${number}-${slug}`;
// Check if branch already exists
try {
await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branch}`,
});
core.info(`Branch ${branch} already exists`);
return;
} catch (e) {
if (e.status !== 404) throw e;
}
// Create branch from main HEAD
const mainRef = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'heads/main',
});
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/heads/${branch}`,
sha: mainRef.data.object.sha,
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number,
body: `Branch \`${branch}\` created.\n\n\`\`\`bash\ngit fetch origin && git checkout ${branch}\n\`\`\``,
});

View File

@@ -10,7 +10,7 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/github-script@v8
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
await github.rest.issues.addLabels({

123
.github/workflows/branch-cleanup.yml vendored Normal file
View File

@@ -0,0 +1,123 @@
name: Branch Cleanup
on:
pull_request:
types: [closed]
schedule:
- cron: '0 4 * * 0' # Sunday 4am UTC — weekly orphan sweep
workflow_dispatch:
permissions:
contents: write
pull-requests: read
jobs:
# Runs immediately when a PR is merged — deletes the head branch.
# Belt-and-suspenders alongside the repo's delete_branch_on_merge setting,
# which handles web/API merges but may be bypassed by some CLI paths.
delete-merged-branch:
name: Delete merged PR branch
runs-on: ubuntu-latest
timeout-minutes: 2
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true
steps:
- name: Delete head branch
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const branch = context.payload.pull_request.head.ref;
const protectedBranches = ['main', 'develop', 'release'];
if (protectedBranches.includes(branch)) {
core.info(`Skipping protected branch: ${branch}`);
return;
}
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branch}`,
});
core.info(`Deleted branch: ${branch}`);
} catch (e) {
// 422 = branch already deleted (e.g. by delete_branch_on_merge setting)
if (e.status === 422) {
core.info(`Branch already deleted: ${branch}`);
} else {
throw e;
}
}
# Runs weekly to catch any orphaned branches whose PRs were merged
# before this workflow existed, or that slipped through edge cases.
sweep-orphaned-branches:
name: Weekly orphaned branch sweep
runs-on: ubuntu-latest
timeout-minutes: 10
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- name: Delete branches from merged PRs
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const protectedBranches = new Set(['main', 'develop', 'release']);
const deleted = [];
const skipped = [];
// Paginate through all branches (100 per page)
let page = 1;
let allBranches = [];
while (true) {
const { data } = await github.rest.repos.listBranches({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
page,
});
allBranches = allBranches.concat(data);
if (data.length < 100) break;
page++;
}
core.info(`Scanning ${allBranches.length} branches...`);
for (const branch of allBranches) {
if (protectedBranches.has(branch.name)) continue;
// Find the most recent closed PR for this branch
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${context.repo.owner}:${branch.name}`,
state: 'closed',
per_page: 1,
sort: 'updated',
direction: 'desc',
});
if (prs.length === 0 || !prs[0].merged_at) {
skipped.push(branch.name);
continue;
}
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branch.name}`,
});
deleted.push(branch.name);
} catch (e) {
if (e.status !== 422) {
core.warning(`Failed to delete ${branch.name}: ${e.message}`);
}
}
}
const summary = [
`Deleted ${deleted.length} orphaned branch(es).`,
deleted.length > 0 ? ` Removed: ${deleted.join(', ')}` : '',
skipped.length > 0 ? ` Skipped (no merged PR): ${skipped.length} branch(es)` : '',
].filter(Boolean).join('\n');
core.info(summary);
await core.summary.addRaw(summary).write();

38
.github/workflows/branch-naming.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Validate Branch Name
on:
pull_request:
types: [opened, synchronize]
permissions: {}
jobs:
check-branch:
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Validate branch naming convention
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const branch = context.payload.pull_request.head.ref;
const validPrefixes = [
'feat/', 'fix/', 'hotfix/', 'docs/', 'chore/',
'refactor/', 'test/', 'release/', 'ci/', 'perf/', 'revert/',
];
const alwaysValid = ['main', 'develop'];
if (alwaysValid.includes(branch)) return;
if (branch.startsWith('dependabot/') || branch.startsWith('renovate/')) return;
// GSD auto-created branches
if (branch.startsWith('gsd/') || branch.startsWith('claude/')) return;
const isValid = validPrefixes.some(prefix => branch.startsWith(prefix));
if (!isValid) {
const prefixList = validPrefixes.map(p => `\`${p}\``).join(', ');
core.warning(
`Branch "${branch}" doesn't follow naming convention. ` +
`Expected prefixes: ${prefixList}`
);
}

157
.github/workflows/canary.yml vendored Normal file
View File

@@ -0,0 +1,157 @@
# Release stream policy:
# dev → @canary (this workflow — preview builds for the long-lived integration branch)
# main → @next (RC train, see release.yml)
# main → @latest (stable cuts, see release.yml)
#
# Streams do not mix. The publish/tag steps below gate on `refs/heads/dev` so a
# workflow_dispatch run on any other branch (including main) completes the
# build/test/dry-run validation but does not publish or tag.
name: Canary
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (skip npm publish, tagging, and push)'
required: false
type: boolean
default: false
concurrency:
group: canary
cancel-in-progress: false
env:
NODE_VERSION: 24
jobs:
canary:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
id-token: write
environment: npm-publish
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
- name: Determine canary version
id: canary
run: |
# Strip any pre-release suffix from package.json version to get base (e.g. 1.39.0-rc.4 → 1.39.0)
RAW=$(node -p "require('./package.json').version")
BASE=$(echo "$RAW" | sed 's/-.*//')
# Find next sequential canary number from existing tags
N=1
while git tag -l "v${BASE}-canary.${N}" | grep -q .; do
N=$((N + 1))
done
CANARY_VERSION="${BASE}-canary.${N}"
echo "canary_version=$CANARY_VERSION" >> "$GITHUB_OUTPUT"
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Bump to canary version
env:
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
run: |
npm version "$CANARY_VERSION" --no-git-tag-version
cd sdk && npm version "$CANARY_VERSION" --no-git-tag-version && cd ..
- name: Install and test
run: |
npm ci
npm test
- name: Build SDK dist for tarball
run: npm run build:sdk
- name: Verify tarball ships sdk/dist/cli.js (bug #2647)
run: bash scripts/verify-tarball-sdk-dist.sh
- name: Dry-run publish validation
run: |
npm publish --dry-run --tag canary
cd sdk && npm publish --dry-run --tag canary
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Tag and push
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
env:
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
run: |
git tag "v${CANARY_VERSION}"
git push origin "v${CANARY_VERSION}"
- name: Publish to npm (canary)
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
run: npm publish --provenance --access public --tag canary
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish SDK to npm (canary)
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
run: cd sdk && npm publish --provenance --access public --tag canary
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Verify publish
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
env:
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
run: |
PUBLISHED="NOT_FOUND"
SDK_PUBLISHED="NOT_FOUND"
for delay in 5 10 20 30 45; do
PUBLISHED=$(npm view get-shit-done-cc@"$CANARY_VERSION" version 2>/dev/null || echo "NOT_FOUND")
SDK_PUBLISHED=$(npm view @gsd-build/sdk@"$CANARY_VERSION" version 2>/dev/null || echo "NOT_FOUND")
if [ "$PUBLISHED" = "$CANARY_VERSION" ] && [ "$SDK_PUBLISHED" = "$CANARY_VERSION" ]; then
break
fi
echo "Not yet live (sleeping ${delay}s)..."
sleep "$delay"
done
if [ "$PUBLISHED" != "$CANARY_VERSION" ]; then
echo "::error::Published version verification failed. Expected $CANARY_VERSION, got $PUBLISHED"
exit 1
fi
echo "Verified: get-shit-done-cc@$CANARY_VERSION is live on npm"
if [ "$SDK_PUBLISHED" != "$CANARY_VERSION" ]; then
echo "::error::SDK version verification failed. Expected $CANARY_VERSION, got $SDK_PUBLISHED"
exit 1
fi
echo "Verified: @gsd-build/sdk@$CANARY_VERSION is live on npm"
CANARY_TAG=$(npm dist-tag ls get-shit-done-cc 2>/dev/null | grep "canary:" | awk '{print $2}')
echo "canary dist-tag points to: $CANARY_TAG"
- name: Summary
env:
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
DRY_RUN: ${{ inputs.dry_run }}
PUBLISH_ELIGIBLE: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
BRANCH_REF: ${{ github.ref }}
run: |
echo "## Canary v${CANARY_VERSION}" >> "$GITHUB_STEP_SUMMARY"
if [ "$DRY_RUN" = "true" ]; then
echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY"
elif [ "$PUBLISH_ELIGIBLE" != "true" ]; then
echo "**VALIDATION ONLY** — publish/tag skipped for \`${BRANCH_REF}\`; canary publish is gated to \`refs/heads/dev\`." >> "$GITHUB_STEP_SUMMARY"
else
echo "- Published to npm as \`canary\`" >> "$GITHUB_STEP_SUMMARY"
echo "- SDK also published: \`@gsd-build/sdk@${CANARY_VERSION}\` on \`canary\`" >> "$GITHUB_STEP_SUMMARY"
echo "- Tagged \`v${CANARY_VERSION}\`" >> "$GITHUB_STEP_SUMMARY"
echo "- Install: \`npx get-shit-done-cc@canary\`" >> "$GITHUB_STEP_SUMMARY"
fi

View File

@@ -0,0 +1,24 @@
name: Changeset Required
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
permissions:
contents: read
pull-requests: read
jobs:
changeset-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '24'
- name: Run changeset lint
env:
GITHUB_BASE_REF: ${{ github.base_ref }}
run: node scripts/changeset/lint.cjs

51
.github/workflows/close-draft-prs.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Close Draft PRs
on:
pull_request:
types: [opened, reopened, converted_to_draft]
permissions:
pull-requests: write
jobs:
close-if-draft:
name: Reject draft PRs
if: github.event.pull_request.draft == true
runs-on: ubuntu-latest
steps:
- name: Comment and close draft PR
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const pr = context.payload.pull_request;
const repoUrl = context.repo.owner + '/' + context.repo.repo;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: [
'## Draft PRs are not accepted',
'',
'This project only accepts completed pull requests. Draft PRs are automatically closed.',
'',
'**Why?** GSD requires all PRs to be ready for review when opened \u2014 with tests passing, the correct PR template used, and a linked approved issue. Draft PRs bypass these quality gates and create review overhead.',
'',
'### What to do instead',
'',
'1. Finish your implementation locally',
'2. Run `npm run test:coverage` and confirm all tests pass',
'3. Open a **non-draft** PR using the [correct template](https://github.com/' + repoUrl + '/blob/main/CONTRIBUTING.md#pull-request-guidelines)',
'',
'See [CONTRIBUTING.md](https://github.com/' + repoUrl + '/blob/main/CONTRIBUTING.md) for the full process.',
].join('\n')
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
state: 'closed'
});
core.info('Closed draft PR #' + pr.number + ': ' + pr.title);

495
.github/workflows/hotfix.yml vendored Normal file
View File

@@ -0,0 +1,495 @@
name: Hotfix Release
# Hotfix flow for X.YY.Z patch releases (Z > 0).
#
# create:
# - Branches hotfix/X.YY.Z from the highest existing vX.YY.* tag (1.27.2 from
# v1.27.1, 1.27.1 from v1.27.0). The base IS the cumulative-fix anchor for
# the previous patch.
# - Auto-cherry-picks every fix:/chore: commit on origin/main that isn't
# already in the base, oldest-first. Patch-equivalents (already applied)
# are skipped via `git cherry`. feat:/refactor: are NEVER auto-included.
# - Conflicts fail the workflow with the offending SHA so the operator can
# resolve manually on the branch and re-run finalize with auto_cherry_pick=false.
# - Step summary lists every included SHA so the eventual vX.YY.Z tag
# self-documents what shipped.
#
# finalize:
# - install-smoke gate (cross-platform, parity with release.yml/release-sdk.yml)
# - Bundles SDK as both loose tree (sdk/dist/cli.js) and recoverable tarball
# (sdk-bundle/gsd-sdk.tgz) — parity with release-sdk.yml so a hotfix shipped
# during the @gsd-build-token outage carries the same payload shape.
# - Publishes to @latest, tags vX.YY.Z, re-points @next → vX.YY.Z, opens
# merge-back PR.
on:
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
type: choice
options:
- create
- finalize
version:
description: 'Patch version (e.g., 1.27.1)'
required: true
type: string
auto_cherry_pick:
description: 'Auto-cherry-pick fix:/chore: commits from origin/main since base tag (create only)'
required: false
type: boolean
default: true
dry_run:
description: 'Dry run (skip npm publish, tagging, and push)'
required: false
type: boolean
default: false
concurrency:
group: hotfix-${{ inputs.version }}
cancel-in-progress: false
env:
NODE_VERSION: 24
jobs:
validate-version:
runs-on: ubuntu-latest
timeout-minutes: 2
permissions:
contents: read
outputs:
base_tag: ${{ steps.validate.outputs.base_tag }}
branch: ${{ steps.validate.outputs.branch }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Validate version format
id: validate
env:
VERSION: ${{ inputs.version }}
run: |
# Must be X.Y.Z where Z > 0 (patch release)
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[1-9][0-9]*$'; then
echo "::error::Version must be a patch release (e.g., 1.27.1, not 1.28.0)"
exit 1
fi
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1-2)
TARGET_TAG="v${VERSION}"
BRANCH="hotfix/${VERSION}"
# Append TARGET_TAG to the candidate list, then sort -V, then walk the
# sorted list and print whatever immediately precedes TARGET_TAG. This
# is semver-correct for multi-digit patches (v1.27.10 > v1.27.9) where
# a plain `awk '$1 < target'` lexicographic compare would mis-order.
BASE_TAG=$( ( git tag -l "v${MAJOR_MINOR}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$"; echo "$TARGET_TAG" ) \
| sort -V \
| awk -v target="$TARGET_TAG" '$1 == target { print prev; exit } { prev = $1 }')
if [ -z "$BASE_TAG" ]; then
echo "::error::No prior stable tag found for ${MAJOR_MINOR}.x before $TARGET_TAG"
exit 1
fi
echo "base_tag=$BASE_TAG" >> "$GITHUB_OUTPUT"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
create:
needs: validate-version
if: inputs.action == 'create'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
- name: Check branch doesn't already exist
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
run: |
if git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
echo "::error::Branch $BRANCH already exists. Delete it first or use finalize."
exit 1
fi
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Create hotfix branch from base tag and push (skeleton)
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
git checkout -b "$BRANCH" "$BASE_TAG"
# Push the skeleton branch up-front so any subsequent cherry-pick
# conflict leaves a remote artefact the operator can fetch, resolve,
# and re-push. Skipped on dry-run — local checkout still exercises
# the same cherry-pick + bump flow so conflicts are caught.
if [ "$DRY_RUN" != "true" ]; then
git push -u origin "$BRANCH"
fi
- name: Cherry-pick fix/chore commits from origin/main since base tag
if: ${{ inputs.auto_cherry_pick }}
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
git fetch origin main:refs/remotes/origin/main
# `git cherry $BASE_TAG origin/main` lists every commit on main not
# patch-equivalent in BASE_TAG. + means needs picking, - means
# already applied (skipped silently).
CANDIDATES=$(git cherry "$BASE_TAG" origin/main | awk '/^\+ / {print $2}')
if [ -z "$CANDIDATES" ]; then
echo "No commits on origin/main beyond $BASE_TAG."
echo "## Cherry-pick summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Base: \`$BASE_TAG\` — no commits to consider." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
# Re-order chronologically (oldest first) for predictable application.
ORDERED=$(git log --reverse --format='%H' "$BASE_TAG..origin/main" \
| grep -F -f <(echo "$CANDIDATES") || true)
INCLUDED=""
SKIPPED=""
while IFS= read -r SHA; do
[ -z "$SHA" ] && continue
SUBJECT=$(git log -1 --format='%s' "$SHA")
# fix: or chore:, optional scope, optional ! breaking marker
if echo "$SUBJECT" | grep -qE '^(fix|chore)(\([^)]+\))?!?: '; then
echo "→ cherry-picking $SHA $SUBJECT"
if ! git cherry-pick -x "$SHA"; then
# Abort restores HEAD to the last successful pick. On real
# runs, push that state so the operator can fetch, resolve
# $SHA manually, and finalize with auto_cherry_pick=false.
git cherry-pick --abort || true
if [ "$DRY_RUN" != "true" ]; then
git push --force-with-lease origin "$BRANCH" || git push origin "$BRANCH" || true
fi
{
echo "## Cherry-pick conflict"
echo ""
echo "Failed at: \`${SHA}\` — \`${SUBJECT}\`"
echo ""
if [ "$DRY_RUN" = "true" ]; then
echo "**Dry run:** branch was not pushed, so the picks below were discarded with the runner."
if [ -n "$INCLUDED" ]; then
echo ""
echo "Already-applied picks (lost — must be re-applied before resolving \`${SHA}\`):"
echo ""
echo "$INCLUDED"
fi
echo ""
echo "**To resolve:** re-run \`create\` with \`auto_cherry_pick=true\` (real, not dry-run) to materialize the partial branch on origin, then resolve \`${SHA}\` manually. Re-running with \`auto_cherry_pick=false\` would recreate the branch from \`${BASE_TAG}\` and lose every pick listed above."
else
echo "Branch \`${BRANCH}\` was pushed with picks applied up to (but not including) the conflicting commit."
echo ""
echo "**To resolve:** \`git fetch origin && git checkout ${BRANCH} && git cherry-pick -x ${SHA}\`, fix the conflict, push, then re-run \`finalize\` with \`auto_cherry_pick=false\`."
fi
} >> "$GITHUB_STEP_SUMMARY"
echo "::error::Cherry-pick of $SHA failed. See summary."
exit 1
fi
INCLUDED="${INCLUDED}- \`${SHA}\` ${SUBJECT}"$'\n'
else
echo " skip $SHA $SUBJECT (not fix/chore)"
SKIPPED="${SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
fi
done <<< "$ORDERED"
{
echo "## Cherry-pick summary"
echo ""
echo "Base: \`$BASE_TAG\`"
echo ""
if [ -n "$INCLUDED" ]; then
echo "### Included (fix/chore)"
echo ""
echo "$INCLUDED"
else
echo "_No fix/chore commits to include._"
echo ""
fi
if [ -n "$SKIPPED" ]; then
echo "### Skipped (feat/refactor/etc — not auto-included)"
echo ""
echo "$SKIPPED"
fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Bump version and push
env:
BRANCH: ${{ needs.validate-version.outputs.branch }}
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
VERSION: ${{ inputs.version }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
npm version "$VERSION" --no-git-tag-version
git add package.json package-lock.json
# Keep sdk/package.json in lockstep (parity with release-sdk.yml).
if [ -f sdk/package.json ]; then
(cd sdk && npm version "$VERSION" --no-git-tag-version)
git add sdk/package.json
[ -f sdk/package-lock.json ] && git add sdk/package-lock.json
fi
git commit -m "chore: bump version to $VERSION for hotfix"
if [ "$DRY_RUN" != "true" ]; then
git push origin "$BRANCH"
else
echo "DRY RUN — branch not pushed. Local checkout exercised the cherry-pick and bump flow."
fi
{
echo "## Hotfix branch created"
echo ""
echo "- Branch: \`$BRANCH\`"
echo "- Based on: \`$BASE_TAG\`"
echo "- Apply additional manual fixes if needed, then run \`finalize\`."
} >> "$GITHUB_STEP_SUMMARY"
install-smoke:
needs: validate-version
if: inputs.action == 'finalize'
permissions:
contents: read
uses: ./.github/workflows/install-smoke.yml
with:
ref: ${{ needs.validate-version.outputs.branch }}
finalize:
needs: [validate-version, install-smoke]
if: inputs.action == 'finalize'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
pull-requests: write
id-token: write
environment: npm-publish
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.validate-version.outputs.branch }}
fetch-depth: 0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
cache: 'npm'
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Detect prior publish (reconciliation mode)
id: prior_publish
env:
VERSION: ${{ inputs.version }}
run: |
EXISTING=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || true)
if [ -n "$EXISTING" ]; then
echo "::warning::get-shit-done-cc@${VERSION} is already on the registry — entering reconciliation mode (skip publish, continue with tag/release/PR/dist-tag)."
echo "skip_publish=true" >> "$GITHUB_OUTPUT"
else
echo "skip_publish=false" >> "$GITHUB_OUTPUT"
fi
- name: Install and test
run: |
npm ci
npm run test:coverage
- name: Build SDK dist for tarball
run: npm run build:sdk
- name: Verify CC tarball ships sdk/dist/cli.js (bug #2647 guard)
run: bash scripts/verify-tarball-sdk-dist.sh
- name: Pack SDK as tarball and bundle into CC source tree
env:
VERSION: ${{ inputs.version }}
run: |
set -e
cd sdk
npm pack
TARBALL="gsd-build-sdk-${VERSION}.tgz"
if [ ! -f "$TARBALL" ]; then
echo "::error::Expected $TARBALL but npm pack did not produce it."
ls -la
exit 1
fi
mkdir -p ../sdk-bundle
mv "$TARBALL" ../sdk-bundle/gsd-sdk.tgz
cd ..
ls -la sdk-bundle/
- name: Add sdk-bundle to CC files whitelist (in-tree, not committed)
run: |
node <<'NODE'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
if (!Array.isArray(pkg.files)) {
console.error('::error::package.json files is not an array');
process.exit(1);
}
if (!pkg.files.includes('sdk-bundle')) {
pkg.files.push('sdk-bundle');
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
console.log('Added sdk-bundle/ to package.json files whitelist');
}
NODE
- name: Verify CC tarball will contain sdk-bundle/gsd-sdk.tgz
run: |
set -e
TARBALL=$(npm pack --ignore-scripts 2>/dev/null | tail -1)
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
echo "::error::npm pack produced no tarball"
exit 1
fi
if ! tar -tzf "$TARBALL" | grep -q "package/sdk-bundle/gsd-sdk.tgz"; then
echo "::error::CC tarball is missing package/sdk-bundle/gsd-sdk.tgz"
exit 1
fi
echo "✅ CC tarball contains sdk-bundle/gsd-sdk.tgz"
rm -f "$TARBALL"
- name: Dry-run publish validation
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --dry-run --tag latest
- name: Tag and push
if: ${{ !inputs.dry_run }}
env:
VERSION: ${{ inputs.version }}
run: |
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
EXISTING_SHA=$(git rev-parse "refs/tags/v${VERSION}")
HEAD_SHA=$(git rev-parse HEAD)
if [ "$EXISTING_SHA" != "$HEAD_SHA" ]; then
echo "::error::Tag v${VERSION} already exists pointing to different commit"
exit 1
fi
echo "Tag v${VERSION} already exists on current commit; skipping"
else
git tag "v${VERSION}"
git push origin "v${VERSION}"
fi
- name: Publish to npm (latest)
if: ${{ !inputs.dry_run && steps.prior_publish.outputs.skip_publish != 'true' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --provenance --access public --tag latest
- name: Re-point next dist-tag at this hotfix
if: ${{ !inputs.dry_run }}
env:
VERSION: ${{ inputs.version }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
npm dist-tag add "get-shit-done-cc@${VERSION}" next
echo "✅ next dist-tag re-pointed to v${VERSION} (matches latest)"
- name: Create GitHub Release (idempotent)
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
if gh release view "v${VERSION}" >/dev/null 2>&1; then
echo "GitHub Release v${VERSION} already exists; ensuring --latest flag is set"
gh release edit "v${VERSION}" --latest || true
else
gh release create "v${VERSION}" \
--title "v${VERSION} (hotfix)" \
--generate-notes \
--latest
fi
- name: Create PR to merge hotfix back to main
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ needs.validate-version.outputs.branch }}
VERSION: ${{ inputs.version }}
run: |
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number')
if [ -n "$EXISTING_PR" ]; then
gh pr edit "$EXISTING_PR" \
--title "chore: merge hotfix v${VERSION} back to main" \
--body "Merge hotfix changes back to main after v${VERSION} release."
else
gh pr create \
--base main \
--head "$BRANCH" \
--title "chore: merge hotfix v${VERSION} back to main" \
--body "Merge hotfix changes back to main after v${VERSION} release."
fi
- name: Verify publish landed on registry
if: ${{ !inputs.dry_run }}
env:
VERSION: ${{ inputs.version }}
run: |
PUBLISHED="NOT_FOUND"
for delay in 5 10 20 30 45; do
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
if [ "$PUBLISHED" = "$VERSION" ]; then
break
fi
echo "Waiting ${delay}s for registry to catch up (saw: $PUBLISHED)..."
sleep "$delay"
done
if [ "$PUBLISHED" != "$VERSION" ]; then
echo "::error::Version $VERSION did not appear on the registry within timeout"
exit 1
fi
LATEST_VER=$(npm view get-shit-done-cc dist-tags.latest 2>/dev/null || echo "NOT_FOUND")
if [ "$LATEST_VER" != "$VERSION" ]; then
echo "::error::dist-tag 'latest' resolves to '$LATEST_VER', expected '$VERSION'"
exit 1
fi
echo "✓ Verified: get-shit-done-cc@$VERSION is live on @latest"
- name: Summary
env:
VERSION: ${{ inputs.version }}
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
{
echo "## Hotfix v${VERSION}"
echo ""
echo "- Base (cumulative-fix anchor): \`${BASE_TAG}\`"
if [ "$DRY_RUN" = "true" ]; then
echo "- **DRY RUN** — npm publish, tagging, and push skipped"
else
echo "- Published to npm as \`latest\`"
echo "- \`next\` dist-tag re-pointed to v${VERSION}"
echo "- Tagged \`v${VERSION}\` (anchor for the next hotfix's cherry-pick base)"
echo "- SDK bundled at \`sdk-bundle/gsd-sdk.tgz\` inside CC tarball"
echo "- Merge-back PR opened against main"
fi
} >> "$GITHUB_STEP_SUMMARY"

Some files were not shown because too many files have changed in this diff Show More