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>
Wire the --reviews flag through the full stack so plan-phase can
replan incorporating cross-AI review feedback from REVIEWS.md:
- core.cjs: add has_reviews detection in searchPhaseInDir
- init.cjs: wire has_reviews and reviews_path through all init functions
- plan-phase.md command: add --reviews to argument-hint and flags
- plan-phase.md workflow: add step 2.5 validation, skip research,
skip existing plans prompt, pass reviews_path to planner
- gsd-planner.md: add reviews_mode section for consuming review feedback
- COMMANDS.md: add --reviews and missing flags to docs
Closes the gap where --reviews was referenced in 6 places (review
workflow, review command, help workflow, COMMANDS.md, FEATURES.md)
but never implemented.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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
- 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>
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
Phase numbers reset per milestone (v1.0 Phase 1-26, v1.1 Phase 1-6,
v1.2 Phase 1-6). Functions that searched ROADMAP.md for phase headings
or checkboxes would match archived milestone entries inside <details>
blocks before reaching the current milestone's entries.
This caused:
- roadmap_complete: true for phases that aren't complete (checkbox
regex matched the archived [x] Phase N from a previous milestone)
- phase-complete marking the wrong checkbox (archived instead of current)
- phase-add computing wrong maxPhase from archived headings
- progress not showing phases defined in ROADMAP but not yet on disk
Quick task IDs were sequential integers (001, 002...) computed by reading
the local .planning/quick/ directory max value. Two users running /gsd:quick
simultaneously would get the same number, causing directory collisions on git.
Replace with a collision-resistant format: YYMMDD-xxx where xxx is the
number of 2-second blocks elapsed since midnight, encoded as 3 lowercase
Base36 characters (000–xbz). Practical collision window is ~2 seconds per
user — effectively zero for any realistic team workflow.
- init.cjs: remove nextNum scan logic, generate quickId from wall clock
- quick.md: rename all next_num refs to quick_id, update directory patterns
- init.test.cjs: rewrite cmdInitQuick tests for new ID format
Co-authored-by: yanbing <yanbing@corp.netease.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add toPosixPath() helper to normalize output paths to forward slashes
- Use string concatenation for relative base paths instead of path.join()
- Apply toPosixPath() to all user-facing file paths in init.cjs output
- Use array-based execFileSync in test helpers to bypass shell quoting
issues with JSON args and dollar signs on Windows cmd.exe
Fixes 7 test failures on Windows: frontmatter set/merge (3), init
path assertions (2), and state dollar-amount corruption (2).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase number parsing only matched single-decimal (e.g. 03.2) but crashed
on multi-level decimals (e.g. 03.2.1). Requirement IDs with regex
metacharacters (parentheses, dots) were interpolated raw into RegExp
constructors, causing SyntaxError crashes.
- Add escapeRegex() utility for safe regex interpolation
- Update normalizePhaseName/comparePhaseNum for multi-level decimals
- Replace all .replace('.', '\\.') with escapeRegex() across modules
- Escape reqId before regex interpolation in cmdPhaseComplete
- Update all phase dir matching regexes from (?:\.\d+)? to (?:\.\d+)*
- Add regression test for phase complete 03.2.1
Closes#621
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Break the 5324-line monolith into focused modules:
- core.cjs: shared utilities, constants, internal helpers
- frontmatter.cjs: YAML frontmatter parsing/serialization/CRUD
- state.cjs: STATE.md operations + progression engine
- phase.cjs: phase CRUD, query, and lifecycle
- roadmap.cjs: roadmap parsing and updates
- verify.cjs: verification suite + consistency/health validation
- config.cjs: config ensure/set/get
- template.cjs: template selection and fill
- milestone.cjs: milestone + requirements lifecycle
- commands.cjs: standalone utility commands
- init.cjs: compound init commands for workflow bootstrapping
gsd-tools.cjs is now a thin CLI router (~550 lines including
JSDoc) that imports from lib/ modules. All 81 tests pass.
Also updates package.json test script to point to tests/.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>