fix: add auto-fix for premature slice completion deadlock in doctor (#1611)

When a slice is marked [x] in ROADMAP but tasks are incomplete and no
summary exists, doctor detects slice_checked_missing_summary (declared
fixable) but had no shouldFix handler — creating an unrecoverable
deadlock. Add handler that unchecks the slice when tasks are incomplete,
and add markSliceUndoneInRoadmap to both doctor.ts and
roadmap-mutations.ts.

Closes #1591

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-20 10:39:24 -06:00 committed by GitHub
parent ec55fe64f1
commit 2f5323ee97
2 changed files with 50 additions and 0 deletions

View file

@ -280,6 +280,21 @@ async function markSliceDoneInRoadmap(basePath: string, milestoneId: string, sli
}
}
async function markSliceUndoneInRoadmap(basePath: string, milestoneId: string, sliceId: string, fixesApplied: string[]): Promise<void> {
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
if (!roadmapPath) return;
const content = await loadFile(roadmapPath);
if (!content) return;
const updated = content.replace(
new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"),
`$1[ ] **${sliceId}:`,
);
if (updated !== content) {
await saveFile(roadmapPath, updated);
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
}
}
function matchesScope(unitId: string, scope?: string): boolean {
if (!scope) return true;
return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
@ -863,6 +878,12 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
fixable: true,
});
if (!allTasksDone) {
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
if (shouldFix("slice_checked_missing_summary")) {
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
}
}
}
if (slice.done && !hasSliceUat) {

View file

@ -39,6 +39,35 @@ export function markSliceDoneInRoadmap(basePath: string, mid: string, sid: strin
return true;
}
/**
* Mark a slice as not done ([ ]) in the milestone roadmap.
* Idempotent no-op if already unchecked or if the slice isn't found.
*
* @returns true if the roadmap was modified, false if no change was needed
*/
export function markSliceUndoneInRoadmap(basePath: string, mid: string, sid: string): boolean {
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
if (!roadmapFile) return false;
let content: string;
try {
content = readFileSync(roadmapFile, "utf-8");
} catch {
return false;
}
const updated = content.replace(
new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sid}:`, "m"),
`$1[ ] **${sid}:`,
);
if (updated === content) return false;
atomicWriteSync(roadmapFile, updated);
clearParseCache();
return true;
}
/**
* Mark a task as done ([x]) in the slice plan.
* Idempotent no-op if already checked or if the task isn't found.