Files
msd-core/.changeset/sturdy-yaks-caper.md
Tom Boucher 19fa7364e0 fix(#2199): accept bullet/em-dash phase entries in roadmap lookup + milestone filter (#2228)
* test(#2199): cover bullet/em-dash ROADMAP phase resolution + milestone count

Adds the bullet-only ROADMAP fixture the suite lacked: an all-bullet em-dash
ROADMAP resolves each phase (no Phase null), colon/en-dash/hyphen bullet
separators all resolve, mixed heading + bullet forms coexist, and the milestone
phase-count counts bullet-form phases instead of collapsing to zero.

* fix(#2199): accept bullet/em-dash phase entries in roadmap lookup + milestone filter

Roadmap phase lookup (findRoadmapPhaseInContent) matched only ATX headings
against a colon-required pattern, so a bullet/checkbox entry like
`- [ ] **Phase N — name**` — which the bundled roadmapper emits in bullet-house-
style ROADMAPs — resolved found:false and `Phase null` was written into STATE.md.
The milestone phase-filter built its phase set from headings only, so a bullet-
only ROADMAP collapsed to a zero-count pass-all filter and progress denominators
broke.

- Add a shared bullet-phase-line pattern (separator: em-dash/en-dash/hyphen/colon).
- findRoadmapPhaseInContent: on a heading-match miss, fall back to the bullet line
  for the requested phase; return found:true with the captured name.
- getMilestonePhaseFilter: also scan bullet lines into the milestone phase set.

Closes #2199

* docs(#2199): add changeset fragment

* fix(#2199): bullet phase lookup as last resort + consolidate test (review)

Two corrections to the initial fix:

1. Regression — the bullet fallback inside findRoadmapPhaseInContent was too
   eager: it returned a bullet match from the scoped (current-milestone) content
   before the caller tried the full-content heading path, so a phase whose
   Requirements live in a Phase Details heading (after the active-milestone
   section) got a bullet-line section with no Requirements → phase_req_ids null
   (broke 3 init tests). Restructure: findRoadmapPhaseInContent is heading-only
   again; a separate findRoadmapBulletPhaseInContent runs in getRoadmapPhaseInternal
   ONLY after scoped + full heading lookup fails, so a heading with a Requirements
   section always wins.

2. lint-test-file-count — the standalone fix-2199-roadmap-bullet-phase.test.cjs
   collided with the over-cap 'roadmap' module (FAIL_NOVEL_FILES). Consolidate
   the regression into the existing tests/roadmap-parser.test.cjs (its natural
   home, under the 2-file cap).

* test(#2199): assert heading-in-full beats bullet-in-scoped (review L3)

The exact first-attempt regression: a phase has a bullet in the active-milestone
scope but its heading (carrying Requirements) lives in a Phase Details section
outside that scope. Pin that the heading section wins over the bullet line so
req_ids resolve and the eager-bullet bug cannot return.

* docs(#2199): backfill PR number in changeset fragment
2026-07-12 23:23:45 -04:00

491 B

type, pr
type pr
Fixed 2228

Bullet/em-dash ROADMAP phases no longer resolve to Phase null — the roadmap phase lookup matched only ATX headings with a colon, so a bullet entry like - [ ] **Phase N — Name** (which the roadmapper emits) failed to resolve and Phase null landed in STATE.md; a bullet-only ROADMAP also broke the milestone phase count. Phase lookup and the milestone filter now accept bullet/checkbox entries with an em-dash/en-dash/hyphen/colon separator. (#2199)