fix(#1445,#1446): exclude 999.x backlog from milestone totals; allow total_phases downward correction (#1490)
* fix(#1445,#1446): exclude 999.x backlog phases from milestone totals; allow total_phases downward correction #1445: deriveProgressFromRoadmap (phase-lifecycle.cts), the roadmapPhaseCount loop (state.cts), and getMilestonePhaseFilter (roadmap-parser.cts) all now skip phase tokens matching /^999\b/ — consistent with the existing init.cts filter. 999.x backlog dirs are consequently excluded from phaseDirs too. #1446: shouldPreserveExistingProgress (state-document.cts) no longer includes total_phases in its ratchet check. total_phases always takes the freshly derived value; only completed_phases, total_plans, and completed_plans retain ratchet behaviour. Regression tests added for both bugs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: add changeset for #1445/#1446 progress-backlog-exclusion-and-ratchet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#1445,#1446): rename test files to fix-NNN convention; fix changeset pr: null Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -54,10 +54,16 @@ export function deriveProgressFromRoadmap(roadmapContent: string): RoadmapProgre
|
||||
);
|
||||
if (progressTableMatch) {
|
||||
const tableText = progressTableMatch[0];
|
||||
// Count data rows (rows starting with pipe then a phase number)
|
||||
const dataRowPattern = /^\|\s*\d+/gm;
|
||||
const dataRows = tableText.match(dataRowPattern);
|
||||
totalPhases = dataRows ? dataRows.length : null;
|
||||
// Count data rows (rows starting with pipe then a phase number),
|
||||
// excluding 999.x backlog phases. Mirrors init.cts /^999(?:\.|$)/ filter.
|
||||
const dataRowPattern = /^\|\s*(\d+[^|]*)\|/gm;
|
||||
let dataRowCount = 0;
|
||||
let drm: RegExpExecArray | null;
|
||||
while ((drm = dataRowPattern.exec(tableText)) !== null) {
|
||||
if (/^999\b/.test(drm[1].trim())) continue;
|
||||
dataRowCount++;
|
||||
}
|
||||
totalPhases = dataRowCount > 0 ? dataRowCount : null;
|
||||
}
|
||||
|
||||
// Sum plan counts from M/N columns in progress table
|
||||
|
||||
@@ -408,7 +408,8 @@ function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, p
|
||||
for (const h of tokenizeHeadings(roadmap)) {
|
||||
if (h.level < 2 || h.level > 4) continue;
|
||||
const pm = phaseHeadingPattern.exec(h.text);
|
||||
if (pm) milestonePhaseNums.add(pm[1]);
|
||||
// Exclude 999.x backlog phases from milestone phase set. Mirrors init.cts filter.
|
||||
if (pm && !/^999\b/.test(pm[1])) milestonePhaseNums.add(pm[1]);
|
||||
}
|
||||
} catch { /* intentionally empty */ }
|
||||
|
||||
|
||||
@@ -171,8 +171,10 @@ export function shouldPreserveExistingProgress(existingProgress: unknown, derive
|
||||
return false;
|
||||
const existing = existingProgress as ProgressRecord;
|
||||
const derived = derivedProgress as ProgressRecord;
|
||||
return (existingProgressExceedsDerived(existing, derived, 'total_phases') ||
|
||||
existingProgressExceedsDerived(existing, derived, 'completed_phases') ||
|
||||
// total_phases is intentionally excluded from the ratchet: it must always
|
||||
// take the freshly derived value so it can correct downward (#1446).
|
||||
// Only completed_phases, total_plans, and completed_plans keep ratchet behaviour.
|
||||
return (existingProgressExceedsDerived(existing, derived, 'completed_phases') ||
|
||||
existingProgressExceedsDerived(existing, derived, 'total_plans') ||
|
||||
existingProgressExceedsDerived(existing, derived, 'completed_plans'));
|
||||
}
|
||||
|
||||
@@ -1402,7 +1402,8 @@ function buildStateFrontmatter(bodyContent: string, cwd: string | undefined): Re
|
||||
// Only count tokens that contain at least one digit — excludes
|
||||
// pure-word section headings (Overview, Details) while keeping
|
||||
// numeric phases (01, 05.1) and project-code IDs (PROJ-42).
|
||||
if (/\d/.test(m[1])) roadmapPhaseCount++;
|
||||
// Also exclude 999.x backlog phases. Mirrors init.cts filter.
|
||||
if (/\d/.test(m[1]) && !/^999\b/.test(m[1])) roadmapPhaseCount++;
|
||||
}
|
||||
}
|
||||
} catch { /* fall through: phaseDirs.length used as sole count */ }
|
||||
|
||||
Reference in New Issue
Block a user