From 03ebc022777ea0c62efaa9b21b1848c0126d9ae7 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Thu, 7 May 2026 05:41:08 +0200 Subject: [PATCH] fix: stamp replan triggers in db --- .../sf/tests/triage-resolution-db.test.mjs | 68 +++++++++++++++++++ .../extensions/sf/triage-resolution.js | 18 ++--- 2 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 src/resources/extensions/sf/tests/triage-resolution-db.test.mjs diff --git a/src/resources/extensions/sf/tests/triage-resolution-db.test.mjs b/src/resources/extensions/sf/tests/triage-resolution-db.test.mjs new file mode 100644 index 000000000..a6333cadb --- /dev/null +++ b/src/resources/extensions/sf/tests/triage-resolution-db.test.mjs @@ -0,0 +1,68 @@ +/** + * triage-resolution-db.test.mjs — DB-backed replan trigger coverage. + * + * Purpose: prove triage replan execution stamps structured slice state while + * retaining the markdown marker as compatibility evidence. + */ +import assert from "node:assert/strict"; +import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, test } from "vitest"; +import { + closeDatabase, + getSlice, + insertMilestone, + insertSlice, + openDatabase, +} from "../sf-db.js"; +import { executeReplan } from "../triage-resolution.js"; + +const tmpDirs = []; + +afterEach(() => { + closeDatabase(); + while (tmpDirs.length > 0) { + const dir = tmpDirs.pop(); + if (dir) rmSync(dir, { recursive: true, force: true }); + } +}); + +function makeProject() { + const dir = mkdtempSync(join(tmpdir(), "sf-triage-resolution-")); + mkdirSync(join(dir, ".sf"), { recursive: true }); + tmpDirs.push(dir); + return dir; +} + +test("executeReplan_stamps_db_replan_trigger_and_writes_marker", () => { + const project = makeProject(); + assert.equal(openDatabase(join(project, ".sf", "sf.db")), true); + insertMilestone({ id: "M001", title: "Milestone", status: "active" }); + insertSlice({ milestoneId: "M001", id: "S01", title: "Slice" }); + + const ok = executeReplan(project, "M001", "S01", { + id: "cap-1", + text: "Need to replan the slice", + rationale: "new dependency", + }); + + assert.equal(ok, true); + const slice = getSlice("M001", "S01"); + assert.equal(typeof slice.replan_triggered_at, "string"); + assert.ok(slice.replan_triggered_at.length > 0); + assert.equal( + existsSync( + join( + project, + ".sf", + "milestones", + "M001", + "slices", + "S01", + "S01-REPLAN-TRIGGER.md", + ), + ), + true, + ); +}); diff --git a/src/resources/extensions/sf/triage-resolution.js b/src/resources/extensions/sf/triage-resolution.js index 481614a6c..6a3ea0082 100644 --- a/src/resources/extensions/sf/triage-resolution.js +++ b/src/resources/extensions/sf/triage-resolution.js @@ -10,7 +10,6 @@ * Also provides detectFileOverlap() for surfacing downstream impact on quick tasks. */ import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs"; -import { createRequire } from "node:module"; import { join } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; import { @@ -21,6 +20,7 @@ import { } from "./captures.js"; import { MILESTONE_ID_RE } from "./milestone-ids.js"; import { milestonesDir, sfRoot } from "./paths.js"; +import { isDbAvailable, setSliceReplanTriggeredAt } from "./sf-db.js"; // ─── Resolution Executors ───────────────────────────────────────────────────── /** * Inject a new task into the current slice plan. @@ -82,15 +82,9 @@ export function executeInject(basePath, mid, sid, capture) { */ export function executeReplan(basePath, mid, sid, capture) { try { - const triggerPath = join( - basePath, - ".sf", - "milestones", - mid, - "slices", - sid, - `${sid}-REPLAN-TRIGGER.md`, - ); + const triggerDir = join(sfRoot(basePath), "milestones", mid, "slices", sid); + mkdirSync(triggerDir, { recursive: true }); + const triggerPath = join(triggerDir, `${sid}-REPLAN-TRIGGER.md`); const ts = new Date().toISOString(); const content = [ `# Replan Trigger`, @@ -106,13 +100,11 @@ export function executeReplan(basePath, mid, sid, capture) { atomicWriteSync(triggerPath, content, "utf-8"); // Also write replan_triggered_at column for DB-backed detection try { - const req = createRequire(import.meta.url); - const { isDbAvailable, setSliceReplanTriggeredAt } = req("./sf-db.js"); if (isDbAvailable()) { setSliceReplanTriggeredAt(mid, sid, ts); } } catch { - // DB write is best-effort — disk file is the primary trigger for fallback path + // Disk marker is retained as compatibility evidence when DB stamping fails. } return true; } catch {