Integrate Exa (semantic search) and Firecrawl (deep web scraping) as
MCP-based research tools, following the existing Brave Search pattern.
- Add tool declarations to all 3 researcher agents
- Add tool strategy sections with usage guidance and priority
- Add config detection for FIRECRAWL_API_KEY and EXA_API_KEY env vars
- Add firecrawl/exa_search config keys to core defaults and init output
- Update source priority hierarchy across all researchers
VALID_CONFIG_KEYS: merge our additions (workflow.auto_advance,
workflow.node_repair, workflow.node_repair_budget, hooks.context_warnings)
with upstream's additions (workflow.text_mode, git.quick_branch_template).
ensureConfigFile(): keep our refactored version that delegates to
buildNewProjectConfig({}) instead of upstream's duplicated logic.
buildNewProjectConfig(): add git.quick_branch_template: null and
workflow.text_mode: false to match upstream's new keys.
new-project.md: integrate upstream's Step 5.1 Sub-Repo Detection
after our commit block; drop upstream's duplicate Note (ours at
line 493 is more detailed).
1. Gate findProjectRoot to commands that access .planning/ — skip for
pure-utility commands (generate-slug, current-timestamp, template,
frontmatter, verify-path-exists, verify-summary) to avoid unnecessary
filesystem traversal on every invocation.
2. Warn to stderr when commit-to-subrepo encounters files that don't
match any configured sub-repo prefix.
3. Document that loadConfig auto-syncs sub_repos with the filesystem,
so config.json may be rewritten when repos are added or removed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- findProjectRoot: use isInsideGitRepo() to walk up and find .git in
ancestor dirs, fixing nested paths like backend/src/modules/
- Add sub_repos to cmdInitExecutePhase output so execute-plan.md and
gsd-executor.md can route commits correctly
- Align new-project.md sub-repo detection to maxdepth 1 matching
detectSubRepos() behavior
- Add 3 nested path tests for .git heuristic, sub_repos, and multiRepo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add support for workspaces with multiple independent git repositories.
When configured, GSD routes commits to the correct sub-repo and ensures
.planning/ stays at the project root.
Core features:
- detectSubRepos(): scans child directories for .git to discover repos
- findProjectRoot(): walks up from CWD to find the project root that
owns .planning/, preventing orphaned .planning/ in sub-repos
- loadConfig auto-syncs sub_repos when repos are added or removed
- Migrates legacy "multiRepo: true" to sub_repos array automatically
- All init commands include project_root in output
- cmdCommitToSubrepo: groups files by sub-repo prefix, commits independently
Zero impact on single-repo workflows — sub_repos defaults to empty array.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactoring:
- Extract stateReplaceFieldWithFallback() to state.cjs as single source of truth
for the try-primary-then-fallback pattern that was duplicated inline across
phase.cjs, milestone.cjs, and state.cjs
- Replace all inline bold-only regex patterns in cmdPhaseComplete with shared
stateReplaceField/stateExtractField helpers — now supports both **Bold:**
and plain Field: STATE.md formats (fixes the same bug as #924)
- Replace inline bold-only regex patterns in cmdMilestoneComplete with shared helpers
- Replace inline bold-only regex for Completed Phases/Total Phases/Progress
counters in cmdPhaseComplete with stateExtractField/stateReplaceField
- Replace inline bold-only Total Phases regex in cmdPhaseRemove with shared helpers
Security:
- Fix command injection surface in isGitIgnored (core.cjs): replace execSync with
string concatenation with execFileSync using array arguments — prevents shell
interpretation of special characters in file paths
Tests (7 new):
- 5 tests for stateReplaceFieldWithFallback: primary field, fallback, neither,
preference, and plain format
- 1 regression test: phase complete with plain-format STATE.md fields
- 1 regression test: milestone complete with plain-format STATE.md fields
854 tests pass (was 847). No behavioral regressions.
- Add resolveWorktreeRoot() that detects linked worktrees via
git rev-parse --git-common-dir and resolves to the main worktree
where .planning/ lives
- Add withPlanningLock() file-based locking mechanism to prevent
concurrent worktrees from corrupting shared planning files
- Wire worktree root resolution into gsd-tools.cjs main entry point
- Add regression tests for resolveWorktreeRoot (non-git, normal repo)
and withPlanningLock (normal execution, error cleanup, stale lock
recovery)
When check-todos moves a file from pending/ to done/, the commit only
staged the new destination. The deletion of the source was never staged
because `git add` on a non-existent path is a no-op. Now we detect
missing files and use `git rm --cached` to stage the deletion.
Closes#1228
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The regex-based table parser captured a fixed number of columns,
so 5-column tables (Phase | Milestone | Plans | Status | Completed)
had the Milestone column eaten and Status/Date written to wrong cells.
Replaced regex with cell-based `split('|')` parsing that detects
column count (4 or 5) and updates the correct cells by index.
Affects both `cmdRoadmapUpdatePlanProgress` and `cmdPhaseComplete`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The summary template puts the one-liner as a `**bold**` line after the
`# Phase N` heading, but `cmdSummaryExtract` and `cmdMilestoneComplete`
only checked frontmatter `one-liner` field — which is often empty.
Adds `extractOneLinerFromBody()` to core.cjs as a fallback that parses
the first `**...**` line after the heading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`cmdStateAdvancePlan` expected separate `Current Plan` and
`Total Plans in Phase` fields, but the current STATE.md template
uses a single compound field: `Plan: X of Y in current phase`.
Now tries legacy separate fields first, then falls back to parsing
the compound format. Preserves the compound format when writing back
(replaces only the plan number). Also handles `Last activity`
(lowercase) field name from current template.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add workflow.text_mode config option (default: false) that replaces
AskUserQuestion TUI menus with plain-text numbered lists
- Document --text flag and config-set workflow.text_mode true as the
fix for /rc remote sessions where the Claude App cannot forward TUI
menu selections
- Update discuss-phase.md with text mode parsing and answer_validation
fallback documentation
- Add text_mode to loadConfig defaults and VALID_CONFIG_KEYS
- Add regression tests for config-set and loadConfig
The summary template writes task count as `**Tasks:** N` in the
Performance section, but `cmdMilestoneComplete` only counted
`## Task N` markdown headers — which don't exist in that format.
Now checks three patterns in order: `**Tasks:** N` field (primary),
`<task` XML tags, then `## Task N` headers (legacy fallback).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`cmdRoadmapUpdatePlanProgress` only marked phase-level checkboxes
(e.g. `- [ ] Phase 50: Build`) but skipped plan-level entries
(e.g. `- [ ] 50-01-PLAN.md`). Now iterates phase summaries and
marks matching plan checkboxes as complete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent silent loss of UAT/verification items when projects advance.
Surfaces outstanding items across all prior phases so nothing is forgotten.
New command:
- /gsd:audit-uat — cross-phase audit with categorized report and test plan
New capabilities:
- Cross-phase health check in /gsd:progress (Step 1.6)
- status: partial for incomplete UAT sessions
- result: blocked with blocked_by tag for dependency-gated tests
- human_needed items persisted as trackable HUMAN-UAT.md files
- Phase completion and transition warnings for verification debt
Files: 4 new, 14 modified (9 feature + 5 docs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidates repeated path.join(cwd, '.planning', ...) patterns into
calls to the shared planningPaths() helper from core.cjs.
Modules updated:
- state.cjs: 16 → planningPaths() (2 remain for non-standard paths)
- commands.cjs: 8 → planningPaths() (4 remain for todos dir)
- milestone.cjs: 5 → planningPaths() (3 remain for archive/milestones)
- roadmap.cjs: 4 → planningPaths()
Benefits:
- Single source of truth for .planning/ directory structure
- Easier to change planning dir location in the future
- Consistent path construction across the codebase
- No behavioral changes — pure refactor
797/797 tests pass.
Code review fixes from codebase pattern analysis:
1. state.cjs: Remove duplicate stateExtractField() definition (lines 12 vs 184).
The second definition shadowed the first with identical logic. Keeps the
original that uses escapeRegex() from core.cjs.
2. init.cjs: Replace Unix-only 'find' pipe with cross-platform fs.readdirSync
recursive walk for code file detection. The execSync('find ... | grep ...')
command fails on Windows where these Unix utilities aren't available.
Removes unused child_process import.
3. state.cjs: Add lockfile-based mutual exclusion to writeStateMd() to prevent
parallel executor agents from overwriting each other's STATE.md changes.
Uses O_EXCL atomic file creation for lock acquisition, stale lock detection
(10s timeout), and spin-wait with jitter. Ensures data integrity during
wave-based parallel execution where multiple agents update STATE.md
concurrently.
All 755 existing tests pass.
Add a cross_reference_todos step to discuss-phase that surfaces relevant
backlog items before scope-setting decisions are made.
Implementation:
- New 'todo match-phase <N>' CLI command (commands.cjs) that scores
pending todos against a phase's ROADMAP goal using three heuristics:
keyword overlap, area match, and file path overlap
- New cross_reference_todos step in discuss-phase.md between
load_prior_context and scout_codebase
- CONTEXT.md template gains 'Folded Todos' subsection in <decisions>
and 'Reviewed Todos (not folded)' subsection in <deferred>
Design:
- No AI call for matching — pure keyword/area/file heuristics for speed
- Silent skip when todo_count is 0 or no matches (no workflow slowdown)
- Auto mode folds all todos with score >= 0.4 automatically
- Scoring: keywords (up to 0.6), area match (0.3), file overlap (0.4)
Tests: 5 new tests covering empty state, keyword matching, unrelated
todo exclusion, area matching, and score sorting.
Closes#1111
Two gaps in the standard workflow cycle caused planning document drift:
1. PROJECT.md was never updated during discuss → plan → execute → verify.
Only transition.md (optional) and complete-milestone evolved it.
Added an 'update_project_md' step to execute-phase.md that evolves
PROJECT.md after phase completion: moves requirements to Validated,
updates Current State, bumps Last Updated timestamp.
2. cmdPhaseComplete() in phase.cjs advanced 'Current Phase' but never
incremented 'Completed Phases' counter or recalculated 'percent'.
Added counter increment and percentage recalculation based on
completed/total phases ratio.
Addresses the workflow-level gaps identified in #956:
- PROJECT.md evolution in execute-phase (gap #2)
- completed_phases counter not incremented (gap #1 table row 3)
- percent never recalculated (gap #1 table row 4)
Fixes#956
When GSD hits a blocking decision point (AskUserQuestion, next action prompt),
external watchers have no way to detect it. Users monitoring multiple auto
sessions must visually check each terminal.
Added:
- state signal-waiting: writes .planning/WAITING.json (or .gsd/WAITING.json)
with type, question, options, timestamp, and phase info
- state signal-resume: removes WAITING.json when user answers
Signal file format:
{ status, type, question, options[], since, phase }
External tools can watch for this file via fswatch, polling, or inotify.
Complements the existing remote-questions extension (Slack/Discord).
Fixes#1034
Claude Code's Task tool sometimes doesn't resolve short aliases (opus,
sonnet, haiku) and passes them directly to the API, causing 404s. Tasks
then inherit the parent session's model instead of the configured one.
Added:
- MODEL_ALIAS_MAP in core.cjs mapping aliases to full model IDs
- resolve_model_ids config option (default: false for backward compat)
- resolveModelInternal() maps aliases when resolve_model_ids is true
Usage:
{ "resolve_model_ids": true }
This causes gsd-tools resolve-model to return 'claude-sonnet-4-5' instead
of 'sonnet', which the Task tool passes to the API without needing alias
resolution on Claude Code's side.
The alias map is maintained per release. Users can also use model_overrides
for full control.
All 755 tests pass.
Fixes#991
stripShippedMilestones() uses a negative heuristic: strip all <details>
blocks, assume what remains is the current milestone. This breaks when
agents accidentally wrap the current milestone in <details> for
collapsibility — all downstream consumers then see an empty milestone.
Observed failure: cmdPhaseComplete() returns is_last_phase: true and
next_phase: null for non-final phases because the current milestone's
phases were stripped along with shipped ones.
Added extractCurrentMilestone(content, cwd) — a positive lookup that:
1. Reads the current milestone version from STATE.md frontmatter
2. Falls back to 🚧 in-progress marker in ROADMAP.md
3. Finds the section heading matching that version
4. Returns only that section's content
5. Falls back to stripShippedMilestones() if version can't be determined
Updated 12 call sites across 6 files to use extractCurrentMilestone:
- core.cjs: getRoadmapPhaseInternal(), getMilestonePhaseFilter()
- phase.cjs: cmdPhaseAdd(), cmdPhaseInsert(), cmdPhaseComplete() (2 sites)
- roadmap.cjs: cmdRoadmapGetPhase(), cmdRoadmapAnalyze()
- commands.cjs: stats/progress display
- verify.cjs: phase verification (2 sites)
- init.cjs: project initialization
Kept stripShippedMilestones() for:
- getMilestoneInfo() — determines the version itself, can't use positive lookup
- replaceInCurrentMilestone() — write operations, conservative boundary
- extractCurrentMilestone() fallback — when no version available
All 755 tests pass.
Fixes#1145
Add `config-new-project` CLI command that writes a complete,
fully-materialized `.planning/config.json` with sane defaults
instead of the previous partial template (6-7 user-chosen keys
only). Unset keys are no longer silently resolved at read time —
every key GSD reads is written explicitly at project creation.
Previously, missing keys were resolved silently by loadConfig()
defaults, making the effective config non-discoverable. Now every
key that GSD reads is written explicitly at project creation.
- buildNewProjectConfig() — single source of truth for all
defaults; merges hardcoded ← ~/.gsd/defaults.json ← user choices
- ensureConfigFile() refactored to reuse buildNewProjectConfig({})
instead of duplicating default logic (~40 lines removed)
- new-project.md Steps 2a and 5 updated to call config-new-project
instead of writing a hardcoded partial JSON template
- Test coverage for config.cjs: 78.96% → 93.81% statements,
100% functions; adds config-set-model-profile test suite
FIXES:
- VALID_CONFIG_KEYS extended with workflow.auto_advance,
workflow.node_repair, workflow.node_repair_budget,
hooks.context_warnings — these keys had hardcoded defaults
but were not settable via config-set
- fix(core): getMilestoneInfo() version regex `\d+\.\d+` only matched
2-segment versions (v1.2). Changed to `\d+(?:\.\d+)+` to support
3+ segments (v1.2.1, v2.0.1). Same fix in roadmap.cjs milestone
extraction pattern.
- fix(state): stripFrontmatter() used `^---\n` (LF-only) which failed
to strip CRLF frontmatter blocks. When STATE.md had dual frontmatter
blocks from prior CRLF corruption, each writeStateMd() call preserved
the stale block and prepended a new wrong one. Now handles CRLF and
strips all stacked frontmatter blocks.
- fix(frontmatter): extractFrontmatter() always used the first ---
block. When dual blocks exist from corruption, the first is stale.
Now uses the last block (most recent sync).
- fix(frontmatter): handle CRLF line endings in extractFrontmatter,
spliceFrontmatter, and parseMustHavesBlock — fixes wave parsing on
Windows where all plans reported as wave 1 (#1085)
- fix(hooks): remove duplicate const cwd declaration in
gsd-context-monitor.js that caused SyntaxError on every PostToolUse
invocation (#1091, #1092, #1094)
- feat(state): add 'state begin-phase' command that updates STATUS,
Last Activity, Current focus, Current Position, and plan counts
when a new phase starts executing (#1102, #1103, #1104)
- docs(workflow): add state begin-phase call to execute-phase workflow
validate_phase step so STATE.md is current from the start