fix: stamp replan triggers in db

This commit is contained in:
Mikael Hugo 2026-05-07 05:41:08 +02:00
parent 95b00d8963
commit 03ebc02277
2 changed files with 73 additions and 13 deletions

View file

@ -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,
);
});

View file

@ -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 {