fix(phase): add ROADMAP.md fallback to cmdPhaseComplete next-phase scan

cmdPhaseComplete determines is_last_phase and next_phase by scanning
.planning/phases/ directories on disk. Phases defined in ROADMAP.md
but not yet planned (no directory created) are invisible to this scan,
causing premature is_last_phase:true when only the first phase has
been scaffolded.

Add a fallback that parses ROADMAP.md phase headings when the
filesystem scan finds no next phase. Uses the existing comparePhaseNum
utility for consistent ordering with letter suffixes and decimals.

Closes #709

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tibsfox
2026-02-28 04:48:04 -08:00
parent 1c58e84eb3
commit 4e00c50022

View File

@@ -783,7 +783,9 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
}
}
// Find next phase
// Find next phase — check both filesystem AND roadmap
// Phases may be defined in ROADMAP.md but not yet scaffolded to disk,
// so a filesystem-only scan would incorrectly report is_last_phase:true
let nextPhaseNum = null;
let nextPhaseName = null;
let isLastPhase = true;
@@ -806,6 +808,24 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
}
} catch {}
// Fallback: if filesystem found no next phase, check ROADMAP.md
// for phases that are defined but not yet planned (no directory on disk)
if (isLastPhase && fs.existsSync(roadmapPath)) {
try {
const roadmapForPhases = fs.readFileSync(roadmapPath, 'utf-8');
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
let pm;
while ((pm = phasePattern.exec(roadmapForPhases)) !== null) {
if (comparePhaseNum(pm[1], phaseNum) > 0) {
nextPhaseNum = pm[1];
nextPhaseName = pm[2].replace(/\(INSERTED\)/i, '').trim().toLowerCase().replace(/\s+/g, '-');
isLastPhase = false;
break;
}
}
} catch {}
}
// Update STATE.md
if (fs.existsSync(statePath)) {
let stateContent = fs.readFileSync(statePath, 'utf-8');