Files
msd-core/.changeset/1761-state-json-unbounded-milestone-read-path.md
Tom Boucher 38c2c1805e fix(#1761): skip conflated progress in state json read-path when milestone unbounded (#1818)
* fix(#1761): skip conflated progress in state json read-path when milestone unbounded

ADR-1769 Phase 7 (#1794) closed the state sync WRITE path — when a milestone
version is asserted in frontmatter but the ROADMAP has no versioned heading
for it, sync leaves Progress untouched. But the state json READ path rebuilds
progress via buildStateFrontmatter, whose roadmapPhaseCount loop counts phase
headings across the WHOLE document when extractCurrentMilestone can't bound
the milestone. state json therefore reported a conflated total_phases (sum of
sibling milestones) + a derived percent — exactly the value the sync guard
was added to prevent. (Repro from the issue: total_phases 8 = 4+4, percent 13.)

Mirror the cmdStateSync guard inside buildStateFrontmatter: when the asserted
milestone cannot be bounded to a versioned ROADMAP heading (the same
versionedHeading test the sync path uses), fall back to the on-disk
phase-dir count for total_phases and skip percent. Bounded milestones
(versioned ROADMAP, or no milestone asserted) are unchanged. The signal rides
on the existing _diskScanCache (new milestoneBounded field) so neither
extractCurrentMilestone's return contract nor its other callers change.

Regression: extend tests/bug-1761-state-sync-wrong-progress.test.cjs with the
read-path case (unbounded → no percent, no conflated total_phases) and a
bounded control (versioned ROADMAP → unchanged percent + total_phases).

* docs(#1761): add changeset fragment for state json read-path fix
2026-06-28 22:41:38 -04:00

1.0 KiB

type, pr
type pr
Fixed 1818

gsd-tools state json no longer reports conflated progress for an unversioned milestone (#1761) — the ADR-1769 Phase 7 fix (#1794) taught state sync to leave Progress untouched when a milestone version is asserted but the ROADMAP has no versioned heading for it, but the state json read path still rebuilt progress via buildStateFrontmatter, whose phase-heading count fell back to the whole document and summed sibling milestones. state json therefore reported a conflated total_phases (e.g. 8 = 4+4 across two milestones) plus a derived percent, contradicting the sync guard on the very same project. The read path now mirrors the sync guard: when the asserted milestone cannot be bounded to a versioned ROADMAP heading, total_phases falls back to the on-disk phase-dir count and percent is omitted. Bounded milestones (versioned ROADMAP, or no milestone asserted) are unchanged; the signal rides on the existing _diskScanCache so extractCurrentMilestone's return contract and its other callers are untouched.