Commit Graph

30 Commits

Author SHA1 Message Date
Tom Boucher
5f0bd0902c Merge pull request #1561 from Tibsfox/fix/concurrency-safety-1473a
fix(core): concurrency safety — withPlanningLock and atomic STATE.md
2026-04-02 17:50:38 -04:00
Tibsfox
d4859220e2 fix(core): add concurrency safety and atomic state writes
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>
2026-04-02 04:28:06 -07:00
Tibsfox
25891db0b2 fix(health): STATE/ROADMAP cross-validation and config checks
Add W011: detect when STATE.md says current phase is N but ROADMAP.md shows it as [x] complete (can happen after crash mid-phase-complete). Add W012-W015: validate branching_strategy against known values, context_window as positive integer, phase_branch_template has {phase} placeholder, milestone_branch_template has {milestone} placeholder.

Add stateReplaceFieldWithFallback diagnostic: warn when neither primary nor fallback field name matches in STATE.md (surfaces template drift from external edits).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 04:20:02 -07:00
Tom Boucher
43d1787670 fix: preserve Status/LastActivity/Progress in Current Position during begin-phase
cmdStateBeginPhase replaced the entire ## Current Position section with only
Phase and Plan lines, destroying Status, Last activity, and Progress fields.
cmdStateAdvancePlan then failed to update these fields since they no longer
existed.

Now begin-phase updates individual lines within Current Position instead of
replacing the whole section. Also adds updateCurrentPositionFields helper so
advance-plan keeps the Current Position body in sync with bold frontmatter
fields.

Fixes #1365

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 19:25:14 -04:00
SalesTeamToolbox
c83b69bbb5 feat: workstream namespacing for parallel milestone work
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>
2026-03-21 15:21:13 -07:00
j2h4u
f850952332 fix(state): preserve frontmatter status when body Status field is missing
syncStateFrontmatter() rebuilds YAML frontmatter from the body on every
writeStateMd() call. If an agent removes or omits the Status: field from
the body, buildStateFrontmatter() defaults to 'unknown', overwriting a
previously valid status (e.g., 'executing').

Fix: read existing frontmatter before stripping, and preserve its status
value when the body-derived status would be 'unknown'. This makes
frontmatter self-healing — once a status is set, it persists even if the
body loses the field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 00:33:13 +05:00
Tom Boucher
62db008570 security: add prompt injection guards, path traversal prevention, and input validation
Defense-in-depth security hardening for a codebase where markdown files become
LLM system prompts. Adds centralized security module, PreToolUse hook for
injection detection, and CI-ready codebase scan.

New files:
- security.cjs: path traversal prevention, prompt injection scanner/sanitizer,
  safe JSON parsing, field name validation, shell arg validation
- gsd-prompt-guard.js: PreToolUse hook scans .planning/ writes for injection
- security.test.cjs: 62 unit tests for all security functions
- prompt-injection-scan.test.cjs: CI scan of all agent/workflow/command files

Hardened code paths:
- readTextArgOrFile: path traversal guard (--prd, --text-file)
- cmdStateUpdate/Patch: field name validation prevents regex injection
- cmdCommit: sanitizeForPrompt strips invisible chars from commit messages
- gsd-tools --fields: safeJsonParse wraps unprotected JSON.parse
- cmdFrontmatterGet/Set: null byte rejection
- cmdVerifyPathExists: null byte rejection
- install.js: registers prompt guard hook, updates uninstaller

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 11:38:26 -04:00
Tom Boucher
3e2c85e6fd refactor: consolidate STATE.md field helpers, fix command injection in isGitIgnored
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.
2026-03-19 15:05:20 -04:00
j2h4u
85a65fd384 fix(state): parse compound Plan field in advance-plan command
`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>
2026-03-19 21:28:26 +05:00
Tom Boucher
9506b895c1 Merge pull request #1151 from 0Shard/fix/semver-and-frontmatter-parsing
fix: semver 3+ segment parsing and CRLF frontmatter corruption recovery
2026-03-19 12:02:43 -04:00
Tom Boucher
1d4deb0f8b refactor: adopt planningPaths() across 4 modules — eliminate 34 inline path constructions
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.
2026-03-18 23:39:47 -04:00
Tom Boucher
7424b3448d Merge pull request #1117 from trek-e/fix/parallel-commit-no-verify-1116 2026-03-18 18:09:17 -04:00
Tom Boucher
60f38cdb9e fix: remove duplicate stateExtractField, cross-platform code detection, STATE.md file locking
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.
2026-03-18 17:17:34 -04:00
Tom Boucher
c7954d1ad7 refactor: deduplicate code, add planningPaths helper, annotate empty catches
Code quality refactoring across 9 lib modules:

1. state.cjs: Remove duplicate stateExtractField() definition (lines 12
   vs 184). Replace 3 inline regex escape patterns with escapeRegex()
   from core.cjs. Add getStatePath() local helper for the 15 repeated
   statePath declarations. Import planningPaths from core.

2. core.cjs: Add planningPaths(cwd) helper returning an object with all
   common .planning paths (state, roadmap, project, config, phases,
   requirements). Add planningDir(cwd) shorthand. Export both.

3. init.cjs: Replace Unix-only execSync('find . | grep | head') with
   cross-platform fs.readdirSync recursive walk for code file detection.
   Remove unused child_process import. Fixes Windows compatibility.

4. All lib modules: Annotate 48 empty catch blocks with
   /* intentionally empty */ to distinguish deliberate error swallowing
   from accidental missing handlers. Affected: commands.cjs (8),
   config.cjs (1), core.cjs (4), init.cjs (13), milestone.cjs (3),
   phase.cjs (6), roadmap.cjs (1), state.cjs (3), verify.cjs (9).

All 755 tests pass.
2026-03-18 12:31:20 -04:00
Tom Boucher
7dd31e6d20 feat: signal file for decision points — WAITING.json (#1034) (#1133)
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
2026-03-18 09:59:39 -06:00
0Shard
7156f02ed5 fix: semver 3+ segment parsing and CRLF frontmatter corruption recovery
- 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).
2026-03-17 17:18:48 +02:00
Tom Boucher
2842f076ea fix: CRLF frontmatter parsing, duplicate cwd crash, STATE.md phase transitions (#1105)
- 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
2026-03-16 13:40:21 -06:00
Tom Boucher
460f92e727 feat: normalize generated markdown for markdownlint compliance (#1112)
Add normalizeMd() utility that fixes common markdownlint violations
in GSD-generated .planning/ files, improving IDE readability:

  MD022 — Blank lines around headings
  MD031 — Blank lines around fenced code blocks
  MD032 — Blank lines around lists
  MD012 — No multiple consecutive blank lines (collapsed to max 2)
  MD047 — Files end with a single newline

Applied at key write points:
  - state.cjs: writeStateMd() normalizes STATE.md on every write
  - frontmatter.cjs: frontmatter set/merge commands normalize output
  - template.cjs: template fill normalizes generated plan/summary files
  - milestone.cjs: MILESTONES.md archive writes normalized

Includes 14 new tests covering all rules, edge cases (CRLF, frontmatter,
ordered lists, code blocks, real-world STATE.md patterns).

Closes #1040
2026-03-16 13:39:34 -06:00
Shard
ca4ae7b3b8 fix: scope ROADMAP.md searches to current milestone (#1059)
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
2026-03-15 11:39:59 -06:00
Tibsfox
641cdbdae7 fix(state): deduplicate extractField into shared helper with regex escaping
Two defensive hardening changes:

1. state.cjs: Replace two identical inline extractField closures
   (cmdStateSnapshot and buildStateFrontmatter) with a shared
   stateExtractField(content, fieldName) helper at module scope.
   The helper uses escapeRegex on fieldName before interpolation
   into RegExp constructors, preventing breakage if a field name
   ever contains regex metacharacters. Net removal of duplicated
   logic.

2. gsd-plan-checker.md: Bound the "relevant" definition in the
   exhaustive cross-check instruction. A requirement is "relevant"
   only if ROADMAP.md explicitly maps it to this phase or the phase
   goal directly implies it — prevents false blocker flags for
   requirements belonging to other phases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:01:54 -06:00
TÂCHES
43c0bde6d0 Merge pull request #822 from Tibsfox/fix/core-lifecycle-state
fix(core): correct phase lifecycle, milestone detection, state parsing, and CLI commit messages
2026-03-02 16:20:03 -06:00
j2h4u
e1b3277869 fix(state): derive total_phases from ROADMAP when phases lack directories
Phases listed in ROADMAP.md but not yet planned (no directory on disk)
were excluded from total_phases, causing premature milestone completion.
Now uses Math.max(diskDirs, roadmapCount) via filter.phaseCount.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 16:07:53 -06:00
j2h4u
b863ed6de5 fix(state): scope phase counting to current milestone
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>
2026-03-02 16:07:49 -06:00
Tibsfox
8c017034a3 fix(state): support both bold and plain field formats in all state.cjs functions
Expand dual-format parsing to cover all 8 field extraction and
replacement locations in state.cjs. The previous fix only covered
cmdStateSnapshot and buildStateFrontmatter (read path), leaving 6
write-path functions with bold-only regex that silently failed on
plain-format STATE.md files.

Functions fixed:
- stateExtractField: shared read helper (cascades to cmdStateAdvancePlan,
  cmdStateRecordSession)
- stateReplaceField: shared write helper (cascades to all state mutations)
- cmdStateGet: individual field lookup
- cmdStatePatch: batch field updates
- cmdStateUpdate: single field updates
- cmdStateUpdateProgress: progress bar writes
- cmdStateSnapshot session section: Last Date, Stopped At, Resume File

Each function now tries **Field:** bold format first (preserving
existing behavior), then falls back to plain Field: format. This
eliminates the read/write asymmetry where state-snapshot could read
plain-format fields but state-update/state-patch could not modify them.

Closes #730

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 05:34:43 -08:00
Tibsfox
78eaabc3da fix(state): support both bold and plain field formats in state parsing
extractField in state-snapshot and buildStateFrontmatter only matches
the **Field:** bold markdown format, but STATE.md may use plain
Field: format depending on how it was generated. When all fields
return null, progress routing freezes with no matching condition.

Add dual-format parsing: try **Field:** first, fall back to plain
Field: with line-start anchor. Both instances in cmdStateSnapshot
and buildStateFrontmatter are updated consistently.

Closes #730

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 04:48:16 -08:00
Ethan Hurst
ff3e2fd5eb fix: escape regex metacharacters in stateExtractField (#741)
stateReplaceField properly escaped fieldName before building the regex,
but stateExtractField did not. Field names containing regex metacharacters
could cause incorrect matches or regex errors.

Co-authored-by: Ethan Hurst <ethan.hurst@outlook.com.au>
2026-02-27 12:58:55 -06:00
Lex Christopherson
0ca1a59ab3 feat: add YAML frontmatter sync to STATE.md for machine readability 2026-02-25 07:20:14 -06:00
Colin Johnson
77d434709d Fix STATE.md decision corruption and dollar handling (#701) 2026-02-23 09:55:54 -06:00
TÂCHES
3704829aa6 fix: clamp progress bar percent to prevent RangeError crash (#715)
When orphaned SUMMARY.md files cause totalSummaries > totalPlans, the
progress percentage exceeds 100%, making String.repeat() throw RangeError
on negative arguments. Clamp percent to Math.min(100, ...) at all three
computation sites (state, commands, roadmap).

Closes #633

Co-authored-by: vinicius-tersi <vinicius-tersi@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:51:28 -06:00
Tyler Satre
c67ab759a7 refactor: split gsd-tools.cjs into 11 domain modules under bin/lib/
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>
2026-02-20 10:40:45 -05:00