fix: treat milestones with summary but no roadmap as complete (#13)
Milestones completed before the roadmap convention (e.g. M001-M003) have SUMMARY files but no ROADMAP. Previously these were treated as incomplete, causing the first one to become the active milestone with a fallback title like 'M001: M001'. Now getActiveMilestoneId(), the completeMilestoneIds pre-computation, and the deriveState() registry loop all check for a SUMMARY file when no roadmap exists and mark the milestone as complete, extracting the title from the summary H1.
This commit is contained in:
parent
729218e58e
commit
424f7edf72
2 changed files with 76 additions and 3 deletions
|
|
@ -79,7 +79,12 @@ export async function getActiveMilestoneId(basePath: string): Promise<string | n
|
|||
for (const mid of milestoneIds) {
|
||||
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
||||
const content = roadmapFile ? await loadFile(roadmapFile) : null;
|
||||
if (!content) return mid; // No roadmap yet — milestone is incomplete
|
||||
if (!content) {
|
||||
// No roadmap — but if a summary exists, the milestone is already complete
|
||||
const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
||||
if (summaryFile) continue; // completed milestone, skip
|
||||
return mid; // No roadmap and no summary — milestone is incomplete
|
||||
}
|
||||
const roadmap = parseRoadmap(content);
|
||||
if (!isMilestoneComplete(roadmap)) return mid;
|
||||
}
|
||||
|
|
@ -117,7 +122,12 @@ export async function deriveState(basePath: string): Promise<GSDState> {
|
|||
for (const mid of milestoneIds) {
|
||||
const rf = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
||||
const rc = rf ? await loadFile(rf) : null;
|
||||
if (!rc) continue;
|
||||
if (!rc) {
|
||||
// No roadmap — milestone is complete if it has a summary
|
||||
const sf = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
||||
if (sf) completeMilestoneIds.add(mid);
|
||||
continue;
|
||||
}
|
||||
const rmap = parseRoadmap(rc);
|
||||
if (!isMilestoneComplete(rmap)) continue;
|
||||
const sf = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
||||
|
|
@ -134,7 +144,18 @@ export async function deriveState(basePath: string): Promise<GSDState> {
|
|||
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
||||
const content = roadmapFile ? await loadFile(roadmapFile) : null;
|
||||
if (!content) {
|
||||
// No roadmap yet — treat as incomplete/active
|
||||
// No roadmap — check if a summary exists (completed milestone without roadmap)
|
||||
const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
||||
if (summaryFile) {
|
||||
const summaryContent = await loadFile(summaryFile);
|
||||
const summaryTitle = summaryContent
|
||||
? (parseSummary(summaryContent).title || mid)
|
||||
: mid;
|
||||
registry.push({ id: mid, title: summaryTitle, status: 'complete' });
|
||||
completeMilestoneIds.add(mid);
|
||||
continue;
|
||||
}
|
||||
// No roadmap and no summary — treat as incomplete/active
|
||||
if (!activeMilestoneFound) {
|
||||
activeMilestone = { id: mid, title: mid };
|
||||
activeMilestoneFound = true;
|
||||
|
|
|
|||
|
|
@ -618,6 +618,58 @@ Continue from step 2.
|
|||
}
|
||||
}
|
||||
|
||||
// ═══ Milestone with summary but no roadmap → complete ═══════════════════
|
||||
{
|
||||
console.log('\n=== milestone with summary and no roadmap → complete ===');
|
||||
const base = createFixtureBase();
|
||||
try {
|
||||
// M001, M002: completed milestones with summaries but no roadmaps
|
||||
const m1dir = join(base, '.gsd', 'milestones', 'M001');
|
||||
mkdirSync(m1dir, { recursive: true });
|
||||
writeFileSync(join(m1dir, 'M001-SUMMARY.md'), '---\nid: M001\n---\n# Bootstrap\nDone.');
|
||||
|
||||
const m2dir = join(base, '.gsd', 'milestones', 'M002');
|
||||
mkdirSync(m2dir, { recursive: true });
|
||||
writeFileSync(join(m2dir, 'M002-SUMMARY.md'), '---\nid: M002\n---\n# Core Features\nDone.');
|
||||
|
||||
// M003: active milestone with a roadmap
|
||||
writeRoadmap(base, 'M003', '# M003: Polish\n## Slices\n- [ ] **S01: Cleanup**');
|
||||
|
||||
const state = await deriveState(base);
|
||||
|
||||
assertEq(state.phase, 'planning', 'summary-no-roadmap: phase is planning (active is M003)');
|
||||
assertEq(state.activeMilestone?.id, 'M003', 'summary-no-roadmap: active milestone is M003');
|
||||
assertEq(state.activeMilestone?.title, 'Polish', 'summary-no-roadmap: active title is Polish');
|
||||
assertEq(state.registry.length, 3, 'summary-no-roadmap: registry has 3 entries');
|
||||
assertEq(state.registry[0]?.status, 'complete', 'summary-no-roadmap: M001 is complete');
|
||||
assertEq(state.registry[0]?.title, 'Bootstrap', 'summary-no-roadmap: M001 title from summary');
|
||||
assertEq(state.registry[1]?.status, 'complete', 'summary-no-roadmap: M002 is complete');
|
||||
assertEq(state.registry[1]?.title, 'Core Features', 'summary-no-roadmap: M002 title from summary');
|
||||
assertEq(state.registry[2]?.status, 'active', 'summary-no-roadmap: M003 is active');
|
||||
assertEq(state.progress?.milestones?.done, 2, 'summary-no-roadmap: milestones done = 2');
|
||||
assertEq(state.progress?.milestones?.total, 3, 'summary-no-roadmap: milestones total = 3');
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══ All milestones have summary but no roadmap → complete ═════════════
|
||||
{
|
||||
console.log('\n=== all milestones summary-only → complete ===');
|
||||
const base = createFixtureBase();
|
||||
try {
|
||||
const m1dir = join(base, '.gsd', 'milestones', 'M001');
|
||||
mkdirSync(m1dir, { recursive: true });
|
||||
writeFileSync(join(m1dir, 'M001-SUMMARY.md'), '---\ntitle: Done\n---\nAll done.');
|
||||
|
||||
const state = await deriveState(base);
|
||||
assertEq(state.phase, 'complete', 'all-summary-only: phase is complete');
|
||||
assertEq(state.registry[0]?.status, 'complete', 'all-summary-only: M001 is complete');
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
// Results
|
||||
// ═════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue