On Windows, os.homedir() reads USERPROFILE, not HOME. Tests pass HOME
override for sandboxing. Use process.env.HOME with os.homedir() fallback
so tests work cross-platform.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three new commands for managing isolated GSD workspaces:
- /gsd:new-workspace — create workspace with repo worktrees/clones
- /gsd:list-workspaces — scan ~/gsd-workspaces/ for active workspaces
- /gsd:remove-workspace — clean up workspace and git worktrees
Supports both multi-repo orchestration (subset of repos from a parent
directory) and feature branch isolation (worktree of current repo with
independent .planning/).
Includes init functions, command routing, workflows, 24 tests, and
user documentation.
Closes#1241
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
core.cjs output() created gsd-*.json files in /tmp for large payloads
but never cleaned them up, causing unbounded disk usage (800+ GB
reported in #1251). Similarly, profile-pipeline.cjs created
gsd-pipeline-* and gsd-profile-* temp directories without cleanup.
Adds reapStaleTempFiles() that removes gsd-prefixed temp files/dirs
older than 5 minutes. Called opportunistically before each new temp
file/dir creation. Non-critical — cleanup failures never break output.
Closes#1251
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ensureConfigFile(): keep our refactored version that delegates to
buildNewProjectConfig({}) instead of upstream's duplicated logic.
buildNewProjectConfig(): add firecrawl and exa_search API key
detection alongside existing brave_search, matching upstream's
new integrations.
loadConfig() defaulted commit_docs to true regardless of whether
.planning/ was gitignored. The documented auto-detection only existed
inside cmdCommit, so init commands returned commit_docs: true even
when .planning/ was in .gitignore. This caused LLM executors to
bypass the cmdCommit gate and re-commit planning files with raw git.
Now loadConfig() checks isGitIgnored(cwd, '.planning/') when no
explicit commit_docs value is set in config.json. If .planning/ is
gitignored, commit_docs defaults to false. An explicit commit_docs
value in config.json is always respected.
Added 5 regression tests covering auto-detection, explicit overrides,
and the no-config-file edge case.
cmdInitPlanPhase, cmdInitExecutePhase, and cmdInitVerifyWork returned
phase_found: false when the phase existed in ROADMAP.md but no phase
directory had been created yet. This caused workflows to fail silently
after /gsd:new-project, producing directories named null-null.
cmdInitPhaseOp (used by discuss-phase) already had a ROADMAP fallback.
Applied the same pattern to the three missing commands: when
findPhaseInternal returns null, fall back to getRoadmapPhaseInternal
and construct phaseInfo from the ROADMAP entry.
Added 5 regression tests covering:
- plan-phase ROADMAP fallback
- execute-phase ROADMAP fallback
- verify-work ROADMAP fallback
- phase_found false when neither directory nor ROADMAP entry exists
- disk directory preferred over ROADMAP fallback
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