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:
parent
ec55fe64f1
commit
2f5323ee97
2 changed files with 50 additions and 0 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue