Commit Graph

339 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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