fix(#2630): reset STATE.md frontmatter atomically on milestone switch (#2666)

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>
This commit is contained in:
Tom Boucher
2026-04-24 18:05:10 -04:00
committed by GitHub
parent 8caa7d4c3a
commit e973ff4cb6
7 changed files with 435 additions and 1 deletions

View File

@@ -32,6 +32,7 @@ import {
stateRecordMetric, stateUpdateProgress, stateAddDecision,
stateAddBlocker, stateResolveBlocker, stateRecordSession,
stateSignalWaiting, stateSignalResume, stateValidate, stateSync, statePrune,
stateMilestoneSwitch,
} from './state-mutation.js';
import {
configSet, configSetModelProfile, configNewProject, configEnsureSection,
@@ -133,6 +134,7 @@ export const QUERY_MUTATION_COMMANDS = new Set<string>([
'state.signal-resume', 'state signal-resume',
'state.sync', 'state sync',
'state.prune', 'state prune',
'state.milestone-switch', 'state milestone-switch',
'frontmatter.set', 'frontmatter.merge', 'frontmatter.validate', 'frontmatter validate',
'config-set', 'config-set-model-profile', 'config-new-project', 'config-ensure-section',
'commit', 'check-commit', 'commit-to-subrepo',
@@ -321,6 +323,8 @@ export function createRegistry(
registry.register('state.validate', stateValidate);
registry.register('state.sync', stateSync);
registry.register('state.prune', statePrune);
registry.register('state.milestone-switch', stateMilestoneSwitch);
registry.register('state milestone-switch', stateMilestoneSwitch);
registry.register('state signal-waiting', stateSignalWaiting);
registry.register('state signal-resume', stateSignalResume);
registry.register('state validate', stateValidate);