* fix(phase): guard backlog dirs and YYYY-MM dates in integer phase removal
Closes#2435Closes#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>
* 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>
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>
* 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>
- 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>
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>
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>
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.
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>
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>
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>
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>
* feat(state): add programmatic gates for STATE.md consistency
Adds four enforcement gates to prevent STATE.md drift:
- `state validate`: detects drift between STATE.md and filesystem
- `state sync`: reconstructs STATE.md from actual project state
- `state planned-phase`: records state after plan-phase completes
- Performance Metrics update in `phase complete`
Also fixes ghost `state update-position` command reference in
execute-phase.md (command didn't exist in CLI dispatcher).
Closes#1627
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(state): By Phase table regex ate next section when table body was empty
The lazy [\s\S]*? with a $ lookahead in byPhaseTablePattern would
match past blank lines and capture the next ## section header as table
body when no data rows existed. Replaced with a precise row-matching
pattern ((?:[ \t]*\|[^\n]*\n)*) that only captures pipe-delimited
lines. Added regression assertion to verify row placement.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace /gsd: command format with /gsd- skill format in all suggestions
All next-step suggestions shown to users were still using the old colon
format (/gsd:xxx) which cannot be copy-pasted as skills. Migrated all
occurrences across agents/, commands/, get-shit-done/, docs/, README files,
bin/install.js (hardcoded defaults for claude runtime), and
get-shit-done/bin/lib/*.cjs (generate-claude-md templates and error messages).
Updated tests to assert new hyphen format instead of old colon format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: migrate remaining /gsd: format to /gsd- in hooks, workflows, and sdk
Addresses remaining user-facing occurrences missed in the initial migration:
- hooks/: fix 4 user-facing messages (pause-work, update, fast, quick)
and 2 comments in gsd-workflow-guard.js
- get-shit-done/workflows/: fix 21 Skill() literal calls that Claude
executes directly (installer does not transform workflow content)
- sdk/prompt-sanitizer.ts: update regex to strip /gsd- format in addition
to legacy /gsd: format; update JSDoc comment
- tests/: update autonomous-ui-steps, prompt-sanitizer to assert new format
Note: commands/gsd/*.md frontmatter (name: gsd:xxx) intentionally unchanged
— installer derives skillName from directory path, not the name field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(plan-phase): preserve --chain flag in auto-advance sync and handle ui-phase gate in chain mode
Bug 1: step 15 sync-flag check only guarded against --auto, causing
_auto_chain_active to be cleared when plan-phase is invoked without
--auto in ARGUMENTS even though a --chain pipeline was active. Added
--chain to the guard condition, matching discuss-phase behaviour.
Bug 2: UI Design Contract gate (step 5.6) always exited the workflow
when UI-SPEC was missing, breaking the discuss --chain pipeline
silently. When _auto_chain_active is true, the gate now auto-invokes
gsd-ui-phase --auto via Skill() and continues to step 6 without
prompting. Manual invocations retain the existing AskUserQuestion flow.
* fix: remove <sub>/clear</sub> pattern and duplicate old-format command in discuss-phase.md
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(phase-resolution): use exact token matching instead of prefix matches
Closes#1635
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(phase-resolution): add case-insensitive flag to project-code strip regex
The strip regex in phaseTokenMatches lacked the `i` flag, so lowercase
project-code prefixes (e.g. `ck-01-name`) were not stripped during the
fallback comparison. This made `phaseTokenMatches('ck-01-name', '01')`
return false when it should return true.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- fix(#1572): phase complete now marks bold-wrapped plan checkboxes in ROADMAP.md
(`- [ ] **01-01**` format) by allowing optional `**` around plan IDs in the
planCheckboxPattern regex in both phase.cjs and roadmap.cjs
- fix(#1569): manager init no longer recommends 999.x (BACKLOG) phases as next
actions; add guard in cmdManagerInit that skips phases matching /^999(?:\.|$)/
- fix(#1568): add regression tests confirming init execute-phase respects
model_overrides for executor_model, including when resolve_model_ids is 'omit'
- fix(#1533): reject session_id values containing path traversal sequences
(../, /, \) in gsd-context-monitor and gsd-statusline before constructing
/tmp file paths; add security tests covering both hooks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire withPlanningLock into all ROADMAP.md write paths (phase add/insert/complete/remove, roadmap update-plan-progress) to prevent concurrent corruption when parallel agents modify planning files.
Extract acquireStateLock/releaseStateLock from writeStateMd and add readModifyWriteStateMd helper that holds the lock across the entire read-modify-write cycle, preventing lost updates.
Replace O(n^2) normalizeMd fence detection with single-pass O(n) pre-computed fence state array.
Warn on must_haves parse failure and stateReplaceFieldWithFallback field miss.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmdPhaseComplete updated Status and Completed columns in the progress
table but skipped the Plans Complete column and plan-level checkboxes.
If update-plan-progress was missed for any plan, the phase completion
safety net didn't catch it, leaving ROADMAP.md inconsistent.
Fixes#1446
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When project_code is set (e.g., "CK"), phase directories are prefixed
with the code: CK-01-foundation, CK-02-api, etc. This disambiguates
phases across multiple GSD projects in the same session.
Changes:
- Add project_code to VALID_CONFIG_KEYS and buildNewProjectConfig defaults
- Add project_code to loadConfig in core.cjs
- Prepend prefix in cmdPhaseAdd and cmdPhaseInsert
- Update searchPhaseInDir, cmdFindPhase, comparePhaseNum, and
normalizePhaseName to strip prefix before matching/sorting
- Support {project} placeholder in git.phase_branch_template
- Add 4 tests covering prefixed add, null code, find, and sort
Closes#1019
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- core.cjs: add filterPlanFiles, filterSummaryFiles, getPhaseFileStats,
readSubdirectories helpers; use them in searchPhaseInDir and getArchivedPhaseDirs
- gsd-tools.cjs: add parseNamedArgs() helper; replace ~50 repetitive indexOf/ternary
patterns in state record-metric, add-decision, add-blocker, record-session,
begin-phase, signal-waiting, template fill, and frontmatter subcommands
- phase.cjs: decompose 250-line cmdPhaseRemove into renameDecimalPhases(),
renameIntegerPhases(), and updateRoadmapAfterPhaseRemoval(); import readSubdirectories
- workstream.cjs: import stateExtractField from state.cjs and shared helpers from
core.cjs; replace all inline regex state parsing and readdirSync+filter+map patterns
All 1062 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enable multiple Claude Code instances to work on the same codebase
simultaneously by scoping .planning/ state into workstreams.
Core changes:
- planningDir(cwd, ws?) and planningPaths(cwd, ws?) are now workstream-aware
via GSD_WORKSTREAM env var (auto-detected from --ws flag or active-workstream file)
- All bin/lib modules use planningDir(cwd) for scoped paths (STATE.md, ROADMAP.md,
phases/, REQUIREMENTS.md) and planningRoot(cwd) for shared paths (milestones/,
PROJECT.md, config.json, codebase/)
- New workstream.cjs module: create, list, status, complete, set, get, progress
- gsd-tools.cjs: --ws flag parsing with priority chain
(--ws > GSD_WORKSTREAM env > active-workstream file > flat mode)
- Collision detection: transition.md checks for other active workstreams before
suggesting next-milestone continuation (prevents WS A from trampling WS B)
- ${GSD_WS} routing propagation across all 9 workflow files ensures workstream
scope chains automatically through the workflow lifecycle
New files:
- get-shit-done/bin/lib/workstream.cjs (CRUD + collision detection)
- get-shit-done/commands/gsd/workstreams.md (slash command)
- get-shit-done/references/workstream-flag.md (documentation)
- tests/workstream.test.cjs (20 tests covering CRUD, env var routing, --ws flag)
All 1062 tests passing (1042 existing + 20 new workstream tests).
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.
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>
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>
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
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
- Scope phase section regex to prevent cross-phase boundary matching
- Handle 'In Progress' status in traceability table (not just 'Pending')
- Add requirements_updated field to phase complete result object
- Add 3 new tests: result field, In Progress status, cross-boundary safety
Co-authored-by: ashanuoc <ashanuoc@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Three files emit path.join or path.relative results directly into
JSON output without normalizing backslashes on Windows. Wrap these
with toPosixPath (already used in core.cjs and init.cjs) so agents
receive consistent forward-slash paths on all platforms.
Files changed:
- phase.cjs: wrap directory path in cmdPhasesFind
- commands.cjs: wrap todo path in cmdListTodos, relPath in cmdScaffold
- template.cjs: wrap relPath in cmdTemplateFill (both exists and
created branches)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract getMilestonePhaseFilter() from milestone.cjs closure into core.cjs
as a shared helper. Apply it in buildStateFrontmatter and cmdPhaseComplete
so multi-milestone projects count only current milestone's phases instead
of all directories on disk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cmdPhaseComplete determines is_last_phase and next_phase by scanning
.planning/phases/ directories on disk. Phases defined in ROADMAP.md
but not yet planned (no directory created) are invisible to this scan,
causing premature is_last_phase:true when only the first phase has
been scaffolded.
Add a fallback that parses ROADMAP.md phase headings when the
filesystem scan finds no next phase. Uses the existing comparePhaseNum
utility for consistent ordering with letter suffixes and decimals.
Closes#709
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cmdPhasePlanIndex had 3 mismatches with the canonical XML plan format
defined in templates/phase-prompt.md:
- files_modified: looked up fm['files-modified'] (hyphen) but plans use
files_modified (underscore). Now checks underscore first, hyphen fallback.
- objective: read from YAML frontmatter but plans put it in <objective>
XML tag. Now extracts first line from the tag, falls back to frontmatter.
- task_count: matched ## Task N markdown headings but plans use <task>
XML tags. Now counts XML tags first, markdown fallback.
All three fixes preserve backward compat with legacy markdown-style plans.
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>