* fix(#557): Milestone marked complete while ROADMAP lists unstarted phases
- Fix 1 — Broaden extractCurrentMilestone() (core.cjs):
(a) Extend sectionPattern to also match <summary> tag content: when a
milestone version appears only inside a <summary> tag, locate the
enclosing <details> block and return its content directly instead of
falling through to stripShippedMilestones().
(b) Extend activeMarkerPattern to include 🔄: change
/\b(?:STARTED|ACTIVE|WIP)\b|in\s+progress|🚧/i to also match 🔄.
(c) Extend the Step 2 fallback regex from /🚧\s*\*\*v(\d+\.\d+)\s/ to
/(?:🚧|🔄)\s*\*\*v(\d+\.\d+)\s/ so the emoji-only fallback also
catches 🔄.
- Fix 2 — Add completion guard (milestone.cjs):
Before writing "milestone complete" to STATE.md, check whether any phase
in the current milestone has no directory on disk (disk_status:
no_directory). When STATE.md milestone version matches the version being
completed and unstarted phases are found, emit an error. Re-run with
--force to override.
- Fix 3 — Add W021 health check (verify.cjs):
Check 14 (W021): if STATE.md status contains "milestone complete" or
"archived", scan the current milestone section of ROADMAP.md; if any
phase has no directory on disk, emit W021 warning: "STATE says milestone
complete but ROADMAP lists N unstarted phase(s)".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): replace hand-written changeset with generated fragment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): address Codex review findings
- core.cjs: add (?!Phase\s+\S) to sectionPattern so phase headings that
mention the milestone version (e.g. ### Phase 1: v1.3 migration) cannot
bypass the <summary> fallback path
- milestone.cjs: replace loose numeric-prefix matching with phaseTokenMatches
+ normalizePhaseName so decimal (2.1) and letter-suffix (12A) phase IDs
are handled correctly in the completion guard
- verify.cjs: same correction in W021 check; also add phaseTokenMatches to
core.cjs import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#557): fix getMilestonePhaseFilter version-heading false match
getMilestonePhaseFilter's sectionPattern also lacked the (?!Phase\s+\S)
exclusion that extractCurrentMilestone received in the previous commit.
A phase title like "### Phase 4: v1.3 migration" could match as the
milestone section start, mis-scoping the phase set used for completion
stats and archive.
Also handle the case where the version lives only in a <summary> tag:
when there is no heading match but a <summary> match exists, skip
setting missingExplicitVersion so milestone.complete does not incorrectly
error with "no phases found".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>