From 89fe6c3bdb4f8303b45f7f96e3e00dfd583b30f9 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 18:51:49 -0700 Subject: [PATCH 1/3] fix(gsd): auto-remediate stale slice DB status when SUMMARY exists on disk When complete-slice unit fails after writing SUMMARY.md but before calling updateSliceStatus(), the DB stays out of sync. The post-unit check previously reported this as a "rogue" artifact, leading to infinite re-dispatch of the same complete-slice unit. Now auto-remediates by calling updateSliceStatus() to sync the DB when SUMMARY exists on disk but status != "complete". Falls back to rogue detection if the DB update fails. Closes #3633 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-post-unit.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/resources/extensions/gsd/auto-post-unit.ts b/src/resources/extensions/gsd/auto-post-unit.ts index 3bffee4b8..e0a10b4cd 100644 --- a/src/resources/extensions/gsd/auto-post-unit.ts +++ b/src/resources/extensions/gsd/auto-post-unit.ts @@ -39,7 +39,7 @@ import { } from "./auto-recovery.js"; import { regenerateIfMissing } from "./workflow-projections.js"; import { syncStateToProjectRoot } from "./auto-worktree.js"; -import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js"; +import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, updateSliceStatus, _getAdapter } from "./gsd-db.js"; import { renderPlanCheckboxes } from "./markdown-renderer.js"; import { consumeSignal } from "./session-status-io.js"; import { @@ -161,7 +161,14 @@ export function detectRogueFileWrites( const dbRow = getSlice(mid, sid); if (!dbRow || dbRow.status !== "complete") { - rogues.push({ path: summaryPath, unitType, unitId }); + // Auto-remediate: SUMMARY exists on disk but DB is stale — sync DB to + // match filesystem instead of reporting as rogue (#3633). + try { + updateSliceStatus(mid, sid, "complete", new Date().toISOString()); + } catch { + // If DB update fails, fall back to rogue detection so the issue is visible + rogues.push({ path: summaryPath, unitType, unitId }); + } } } else if (unitType === "plan-milestone") { if (!mid) return []; From c9a4d117b05c79b31ddfecbfc0cd9afae88cc5ca Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:24:18 -0700 Subject: [PATCH 2/3] test: add regression test for auto-remediate stale slice status Co-Authored-By: Claude Opus 4.6 (1M context) --- .../tests/auto-remediate-slice-status.test.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts diff --git a/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts b/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts new file mode 100644 index 000000000..552096d00 --- /dev/null +++ b/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts @@ -0,0 +1,56 @@ +/** + * Regression test for #3673 — auto-remediate stale slice DB status + * + * When complete-slice fails after writing SUMMARY.md but before calling + * updateSliceStatus(), the DB stays stale and the post-unit check + * previously reported this as a "rogue" artifact, causing infinite + * re-dispatch. The fix calls updateSliceStatus() to sync the DB. + * + * This structural test verifies updateSliceStatus is imported and called + * in the complete-slice branch of auto-post-unit.ts. + */ + +import { describe, test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const source = readFileSync(join(__dirname, '..', 'auto-post-unit.ts'), 'utf-8'); + +describe('auto-remediate stale slice status (#3673)', () => { + test('updateSliceStatus is imported from gsd-db', () => { + assert.match(source, /import\s*\{[^}]*updateSliceStatus[^}]*\}\s*from\s*["']\.\/gsd-db/, + 'updateSliceStatus should be imported from gsd-db'); + }); + + test('updateSliceStatus is called with "complete" status', () => { + assert.match(source, /updateSliceStatus\(mid,\s*sid,\s*["']complete["']/, + 'updateSliceStatus should be called with "complete" status'); + }); + + test('remediation is wrapped in try-catch for fallback to rogue detection', () => { + // The updateSliceStatus call should be in a try block with a catch + // that falls back to rogues.push + const updateIdx = source.indexOf('updateSliceStatus(mid, sid'); + assert.ok(updateIdx > 0, 'updateSliceStatus call should exist'); + + // Find surrounding try-catch + const before = source.slice(Math.max(0, updateIdx - 200), updateIdx); + assert.match(before, /try\s*\{/, + 'updateSliceStatus should be inside a try block'); + + const after = source.slice(updateIdx, updateIdx + 300); + assert.match(after, /catch/, + 'try block should have a catch for fallback'); + }); + + test('rogue detection still exists as fallback', () => { + // rogues.push should appear in the catch block + assert.match(source, /rogues\.push\(\{.*path:\s*summaryPath/, + 'rogues.push fallback should still exist'); + }); +}); From cff7ded42cd0eabe86ce0a4bfe5e0ec14354e48c Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:43:53 -0700 Subject: [PATCH 3/3] fix(test): update rogue detection test for auto-remediation behavior The fix auto-remediates stale slice DB status via updateSliceStatus() instead of reporting it as a rogue artifact. Update the test to expect 0 rogues since the stale status is now silently fixed. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../extensions/gsd/tests/rogue-file-detection.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts b/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts index e0fd6c00e..09110adf7 100644 --- a/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +++ b/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts @@ -149,7 +149,7 @@ test("rogue detection: DB not available → returns empty array (graceful degrad } }); -test("rogue detection: slice summary on disk, no DB row → detected as rogue", () => { +test("rogue detection: slice summary on disk, no DB row → auto-remediated (not rogue)", () => { const basePath = createTmpBase(); const dbPath = join(basePath, ".gsd", "gsd.db"); mkdirSync(join(basePath, ".gsd"), { recursive: true }); @@ -160,11 +160,10 @@ test("rogue detection: slice summary on disk, no DB row → detected as rogue", const summaryPath = createSliceSummaryOnDisk(basePath, "M001", "S01"); assert.ok(existsSync(summaryPath), "Slice summary file should exist on disk"); + // Fix #3633: stale slice DB status is auto-remediated via updateSliceStatus() + // instead of being reported as rogue, so rogues array should be empty. const rogues = detectRogueFileWrites("complete-slice", "M001/S01", basePath); - assert.equal(rogues.length, 1, "Should detect one rogue slice file"); - assert.equal(rogues[0].path, summaryPath); - assert.equal(rogues[0].unitType, "complete-slice"); - assert.equal(rogues[0].unitId, "M001/S01"); + assert.equal(rogues.length, 0, "Should auto-remediate stale slice, not report as rogue"); } finally { closeDatabase(); rmSync(basePath, { recursive: true, force: true });