From 547bffa6d8b37ad8bc194627bd081ac8bf7aeab3 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Sun, 22 Mar 2026 17:01:10 -0600 Subject: [PATCH] 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) --- src/resources/extensions/gsd/md-importer.ts | 17 +++++++++-------- .../extensions/gsd/tests/auto-recovery.test.ts | 2 +- .../gsd/tests/derive-state-crossval.test.ts | 4 +++- .../extensions/gsd/tests/idle-recovery.test.ts | 6 +++--- .../gsd/tests/validate-milestone.test.ts | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/resources/extensions/gsd/md-importer.ts b/src/resources/extensions/gsd/md-importer.ts index d683e1207..5122d6396 100644 --- a/src/resources/extensions/gsd/md-importer.ts +++ b/src/resources/extensions/gsd/md-importer.ts @@ -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`, ); } } diff --git a/src/resources/extensions/gsd/tests/auto-recovery.test.ts b/src/resources/extensions/gsd/tests/auto-recovery.test.ts index a0e71c179..206658d16 100644 --- a/src/resources/extensions/gsd/tests/auto-recovery.test.ts +++ b/src/resources/extensions/gsd/tests/auto-recovery.test.ts @@ -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); } diff --git a/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts b/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts index eb1b6c427..92bc5dc0d 100644 --- a/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +++ b/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts @@ -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); diff --git a/src/resources/extensions/gsd/tests/idle-recovery.test.ts b/src/resources/extensions/gsd/tests/idle-recovery.test.ts index 1ea94e812..0f500f199 100644 --- a/src/resources/extensions/gsd/tests/idle-recovery.test.ts +++ b/src/resources/extensions/gsd/tests/idle-recovery.test.ts @@ -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 }); } diff --git a/src/resources/extensions/gsd/tests/validate-milestone.test.ts b/src/resources/extensions/gsd/tests/validate-milestone.test.ts index 9a1ed7f25..47372c1ea 100644 --- a/src/resources/extensions/gsd/tests/validate-milestone.test.ts +++ b/src/resources/extensions/gsd/tests/validate-milestone.test.ts @@ -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); }