Merge pull request #3673 from Tibsfox/fix/auto-remediate-stale-slice-status
fix(gsd): auto-remediate stale slice DB status when SUMMARY exists
This commit is contained in:
commit
0d3789eee5
3 changed files with 69 additions and 7 deletions
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
@ -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 });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue