/gsd-insert-phase step 4 instructed the agent to directly Edit/Write
.planning/STATE.md to append a Roadmap Evolution entry. Projects that
ship a protect-files.sh PreToolUse hook (a recommended hardening
pattern) blocked the raw write, silently leaving STATE.md out of sync
with ROADMAP.md.
Adds a dedicated SDK handler state.add-roadmap-evolution (plus space
alias) that:
- Reads STATE.md through the shared readModifyWriteStateMd lockfile
path (matches sibling mutation handlers — atomic against
concurrent writers).
- Locates ### Roadmap Evolution under ## Accumulated Context, or
creates both sections as needed.
- Dedupes on exact-line match so idempotent retries are no-ops
({ added: false, reason: "duplicate" }).
- Validates --phase / --action presence and action membership,
throwing GSDError(Validation) for bad input (no silent
{ ok: false } swallow).
Workflow change (insert-phase.md step 4):
- Replaces the raw Edit/Write instructions for STATE.md with
gsd-sdk query state.patch (for the next-phase pointer) and
gsd-sdk query state.add-roadmap-evolution (for the evolution
log).
- Updates success criteria to check handler responses.
- Drops "Write" from commands/gsd/insert-phase.md allowed-tools
(no step in the workflow needs it any more).
Tests (vitest, sdk/src/query/state-mutation.test.ts): subsection
creation when missing; append-preserving-order when present;
duplicate -> reason=duplicate; idempotence over two calls; three
validation cases covering missing --phase, missing --action, and
invalid action.
This is the first SDK handler dedicated to STATE.md Roadmap
Evolution mutations. Other workflows with similar raw STATE.md
edits (/gsd-pause-work, /gsd-resume-work, /gsd-new-project,
/gsd-complete-milestone, /gsd-add-phase) remain on raw Edit/Write
and will need follow-up issues to migrate — out of scope for this
fix.
Closes#2662
The /gsd:new-milestone workflow Step 5 rewrote STATE.md's Current Position
body but never touched the YAML frontmatter, so every downstream reader
(state.json, getMilestoneInfo, progress bars) kept reporting the stale
milestone until the first phase advance forced a resync. Asymmetric with
milestone.complete, which uses readModifyWriteStateMdFull.
Add a new `state milestone-switch` handler (both SDK and CJS) that atomically:
- Stomps frontmatter milestone/milestone_name with caller-supplied values
- Resets status to 'planning' and progress counters to zero
- Rewrites the ## Current Position section to the new-milestone template
- Preserves Accumulated Context (decisions, blockers, todos)
Wire the workflow Step 5 to invoke `state.milestone-switch` instead of the
manual body rewrite. Note the flag is `--milestone` not `--version`:
gsd-tools reserves `--version` as a globally-invalid help flag.
Red vitest in sdk/src/query/state-mutation.test.ts asserts the frontmatter
reset. Regression guard via node:test in tests/bug-2630-*.test.cjs runs
through gsd-tools end-to-end.
Fixes#2630
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(#2613): preserve STATE.md frontmatter on write path (option 2)
`readModifyWriteStateMd` strips frontmatter before invoking the modifier,
so `syncStateFrontmatter` received body-only content and `existingFm`
was always `{}`. The preservation branch never fired, and every mutation
re-derived `status` (to `'unknown'` when body had no `Status:` line) and
`progress.*` (to 0/0 when the shipped milestone's phase directories were
archived), silently overwriting authoritative frontmatter values.
Option 2 — write-side analogue of #2495 READ fix: `buildStateFrontmatter`
reads the current STATE.md frontmatter from disk as a preservation
backstop. Status preserved when derived is `'unknown'` and existing is
non-unknown. Progress preserved when disk scan returns all zeros AND
existing has non-zero counts. Legitimate body-driven status changes and
non-zero disk counts still win.
Milestone/milestone_name already preserved via `getMilestoneInfo`'s
#2495 fix — regression test added to lock that in.
Adds 5 regression tests covering status preservation, progress
preservation, milestone preservation, legitimate status updates, and
disk-scan-wins-when-non-zero.
Closes#2613
* fix(sdk): double-cast WorkflowConfig to Record in loadGateConfig
TypeScript error on main (introduced in #2611) blocks the install-smoke
CI job: `WorkflowConfig` has no string index signature, so the direct
cast to `Record<string, unknown>` fails type-check. The SDK build fails,
`installSdkIfNeeded()` cannot install `gsd-sdk` from source, and the
smoke job reports a false-positive installer regression.
src/query/check-decision-coverage.ts(236,16): error TS2352:
Conversion of type 'WorkflowConfig' to type 'Record<string, unknown>'
may be a mistake because neither type sufficiently overlaps with the
other.
Apply the double-cast via `unknown` as the compiler suggests. Behavior
is unchanged — this was already a cast.
* fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing
Closes#2422Closes#2420
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: patch-version semver in milestone boundary regex + flag-parser validation
Two follow-on correctness issues identified in code review:
1. roadmap.ts: currentVersionMatch and nextMilestoneRegex only captured
major.minor (v(\d+\.\d+)), collapsing v2.0.1 to "2.0". A sub-heading
"## v2.0.2 Phase Details" would match the same prefix and be incorrectly
skipped. Both patterns updated to v(\d+(?:\.\d+)+) to capture full semver.
2. state-mutation.ts: pair-wise flag parsing loop advanced i by 2 unconditionally,
so a missing flag value caused the next flag token to be assigned as the value
(e.g. flags['phase'] = '--name'). Fix: iterate with i++ and validate that the
candidate value exists and does not start with '--' before assigning; throw
GSDError('missing value for --<key>') on invalid input. Added regression test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(sdk): add typed query foundation and gsd-sdk query (Phase 1)
Add sdk/src/query registry and handlers with tests, GSDQueryError, CLI query wiring, and supporting type/tool-scoping hooks. Update CHANGELOG. Vitest 4 constructor mock fixes in milestone-runner tests.
Made-with: Cursor
* fix(2137): skip worktree isolation when .gitmodules detected
When a project contains git submodules, worktree isolation cannot
correctly handle submodule commits — three separate gaps exist in
worktree setup, executor commit protocol, and merge-back. Rather
than patch each gap individually, detect .gitmodules at phase start
and fall back to sequential execution, which handles submodules
transparently (Option B).
Affected workflows: execute-phase.md, quick.md
---------
Co-authored-by: David Sienkowski <dave@sienkowski.com>