fix(tests): update remediation step assertions and crossval fixture

- auto-recovery, idle-recovery, validate-milestone tests: assert
  gsd recover instead of gsd doctor in remediation steps
- derive-state-crossval test C: add task summary files so migration
  consistency check doesn't downgrade tasks to pending
- md-importer: slice auto-upgrade now requires slice summary to exist
  (all tasks done without slice summary = summarizing, not complete)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson 2026-03-22 17:01:10 -06:00
parent 85f849ab7b
commit 547bffa6d8
5 changed files with 17 additions and 14 deletions

View file

@ -619,28 +619,29 @@ export function migrateHierarchyToDb(basePath: string): {
counts.tasks++;
}
// Pre-migration consistency: if all tasks are done but the roadmap
// checkbox for this slice is unchecked, trust the task-level state
// and mark the slice as complete. This handles the common
// Pre-migration consistency: if all tasks are done and the slice
// summary exists but the roadmap checkbox is unchecked, upgrade the
// slice to complete. This handles the common
// "all_tasks_done_roadmap_not_checked" inconsistency that the old
// doctor would have auto-fixed.
// doctor would have auto-fixed. Without a slice summary, the slice
// is in the "summarizing" phase, not complete.
if (!sliceEntry.done) {
const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, sliceEntry.id, 'SUMMARY');
const hasSliceSummary = sliceSummaryPath !== null && existsSync(sliceSummaryPath);
const allTasksDone = plan.tasks.length > 0 && plan.tasks.every(t => {
// Check actual imported status (may have been downgraded above)
const tDir = resolveTasksDir(basePath, milestoneId, sliceEntry.id);
if (!tDir) return t.done;
const summaryFile = join(tDir, `${t.id}-SUMMARY.md`);
return t.done && existsSync(summaryFile);
});
if (allTasksDone) {
// Update the slice status in-place via DB
if (allTasksDone && hasSliceSummary) {
const adapter = _getAdapter();
if (adapter) {
adapter.prepare(
`UPDATE slices SET status = 'complete' WHERE id = :sid AND milestone_id = :mid`,
).run({ ':sid': sliceEntry.id, ':mid': milestoneId });
process.stderr.write(
`gsd-migrate: ${milestoneId}/${sliceEntry.id} all tasks complete — upgrading slice to complete\n`,
`gsd-migrate: ${milestoneId}/${sliceEntry.id} all tasks + slice summary complete — upgrading slice to complete\n`,
);
}
}

View file

@ -170,7 +170,7 @@ test("buildLoopRemediationSteps returns steps for plan-slice", () => {
const steps = buildLoopRemediationSteps("plan-slice", "M001/S01", base);
assert.ok(steps);
assert.ok(steps!.includes("PLAN"));
assert.ok(steps!.includes("gsd doctor"));
assert.ok(steps!.includes("gsd recover"));
} finally {
cleanup(base);
}

View file

@ -231,7 +231,9 @@ skills_used: []
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
writeFile(base, 'milestones/M001/slices/S01/tasks/T02-PLAN.md', '# T02 Plan');
// No S01-SUMMARY.md — should be summarizing
writeFile(base, 'milestones/M001/slices/S01/tasks/T01-SUMMARY.md', '---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01 Summary\nDone.');
writeFile(base, 'milestones/M001/slices/S01/tasks/T02-SUMMARY.md', '---\nid: T02\nparent: S01\nmilestone: M001\n---\n# T02 Summary\nDone.');
// Tasks have summaries, but no S01-SUMMARY.md — should be summarizing
invalidateStateCache();
const fileState = await _deriveStateImpl(base);

View file

@ -246,7 +246,7 @@ const ROADMAP_COMPLETE = `# M001: Test Milestone
mkdirSync(join(base, ".gsd", "milestones", "M002", "slices", "S03", "tasks"), { recursive: true });
const result = buildLoopRemediationSteps("execute-task", "M002/S03/T01", base);
assertTrue(result !== null, "should return remediation steps");
assertTrue(result!.includes("T01-SUMMARY.md"), "steps mention the summary file");
assertTrue(result!.includes("gsd undo-task"), "steps include undo-task command");
assertTrue(result!.includes("T01"), "steps mention the task ID");
assertTrue(result!.includes("gsd undo-task"), "steps include gsd undo-task command");
} finally {
@ -262,7 +262,7 @@ const ROADMAP_COMPLETE = `# M001: Test Milestone
const result = buildLoopRemediationSteps("plan-slice", "M001/S01", base);
assertTrue(result !== null, "should return remediation steps for plan-slice");
assertTrue(result!.includes("S01-PLAN.md"), "steps mention the slice plan file");
assertTrue(result!.includes("gsd doctor"), "steps include gsd doctor command");
assertTrue(result!.includes("gsd recover"), "steps include gsd recover command");
} finally {
rmSync(base, { recursive: true, force: true });
}
@ -276,7 +276,7 @@ const ROADMAP_COMPLETE = `# M001: Test Milestone
const result = buildLoopRemediationSteps("research-slice", "M001/S01", base);
assertTrue(result !== null, "should return remediation steps for research-slice");
assertTrue(result!.includes("S01-RESEARCH.md"), "steps mention the slice research file");
assertTrue(result!.includes("gsd doctor"), "steps include gsd doctor command");
assertTrue(result!.includes("gsd recover"), "steps include gsd recover command");
} finally {
rmSync(base, { recursive: true, force: true });
}

View file

@ -375,7 +375,7 @@ test("buildLoopRemediationSteps returns steps for validate-milestone", () => {
assert.ok(result);
assert.ok(result!.includes("VALIDATION"));
assert.ok(result!.includes("verdict: pass"));
assert.ok(result!.includes("gsd doctor"));
assert.ok(result!.includes("gsd recover"));
} finally {
cleanup(base);
}