fix(#1551): match dash-separated milestone phase IDs in roadmap analyze checklist scan (#1552)

* fix(#1551): match dash-separated milestone phase IDs in roadmap analyze checklist scan

The checklist scanner in cmdRoadmapAnalyze allowed only a dot separator
(?:\.\d+)* while the detail-heading scanner allows [.-], so milestone-prefixed
IDs (1-01) truncated at the dash (-> 1) and reported phantom missing detail
sections on every well-formed milestone roadmap. Widen the char class to
(?:[.-]\d+)* to match the detail scanner and the shared phaseMarkdownRegexSource
helper.

Fixes #1551

Claude-Session: https://claude.ai/code/session_01H96MxPGMJJUiJLV2NgzV16

* chore(changeset): Fixed fragment for #1552 (roadmap milestone-id checklist scan)

Claude-Session: https://claude.ai/code/session_01H96MxPGMJJUiJLV2NgzV16

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
Rezolv
2026-06-21 23:30:27 -04:00
committed by GitHub
parent 9acd0208cd
commit dee40cd392
3 changed files with 44 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1552
---
roadmap analyze no longer reports phantom missing_phase_details for milestone-prefixed (M-NN) phase IDs

View File

@@ -426,8 +426,11 @@ function cmdRoadmapAnalyze(cwd: string, raw: boolean): void {
const totalSummaries = phases.reduce((sum, p) => sum + p.summary_count, 0);
const completedPhases = phases.filter(p => p.disk_status === 'complete').length;
// Detect phases in summary list without detail sections (malformed ROADMAP)
const checklistPattern = /-\s*\[[ x]\]\s*\*\*Phase\s+(\d+[A-Z]?(?:\.\d+)*)/gi;
// Detect phases in summary list without detail sections (malformed ROADMAP).
// The char class must allow `-` (not just `.`) so dash-separated milestone-prefixed
// IDs (e.g. `1-01`) match the detail-heading scanner above; otherwise they truncate
// at the dash (`1-01` -> `1`) and every such phase reports a phantom missing detail.
const checklistPattern = /-\s*\[[ x]\]\s*\*\*Phase\s+(\d+[A-Z]?(?:[.-]\d+)*)/gi;
const checklistPhases = new Set<string>();
let checklistMatch: RegExpExecArray | null;
while ((checklistMatch = checklistPattern.exec(content)) !== null) {

View File

@@ -541,6 +541,40 @@ describe('roadmap analyze missing phase details', () => {
const output = JSON.parse(result.output);
assert.strictEqual(output.missing_phase_details, null, 'missing_phase_details should be null');
});
test('does not report phantom missing details for milestone-prefixed (M-NN) phase IDs', () => {
// The checklist scanner truncated dash-separated IDs at the dash (1-01 -> 1)
// while the detail-heading scanner kept the full ID, so every milestone-prefixed
// ROADMAP spuriously reported the truncated major as a missing detail section.
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
`# Roadmap
- [ ] **Phase 1-01: Foundation** - Set up project
- [ ] **Phase 1-02: API** - Build REST API
- [ ] **Phase 2-01: Ship** - Release
### Phase 1-01: Foundation
**Goal:** Set up project
### Phase 1-02: API
**Goal:** Build REST API
### Phase 2-01: Ship
**Goal:** Release
`
);
const result = runGsdTools('roadmap analyze', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(
output.missing_phase_details,
null,
'milestone-prefixed phases with matching detail sections should report no missing details'
);
});
});
// ─────────────────────────────────────────────────────────────────────────────