fix: auto-skip stale instruction-conflict tasks
This commit is contained in:
parent
46174c1183
commit
d6fc1211b7
4 changed files with 98 additions and 11 deletions
|
|
@ -32,7 +32,10 @@ import {
|
|||
checkNeedsRunUat,
|
||||
} from "./auto-prompts.js";
|
||||
import { hasImplementationArtifacts } from "./auto-recovery.js";
|
||||
import { getExecuteTaskInstructionConflict } from "./execution-instruction-guard.js";
|
||||
import {
|
||||
getExecuteTaskInstructionConflict,
|
||||
skipExecuteTaskForInstructionConflict,
|
||||
} from "./execution-instruction-guard.js";
|
||||
import {
|
||||
extractUatType,
|
||||
loadActiveOverrides,
|
||||
|
|
@ -921,6 +924,17 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|||
tTitle,
|
||||
);
|
||||
if (instructionConflict) {
|
||||
if (isDbAvailable()) {
|
||||
await skipExecuteTaskForInstructionConflict(
|
||||
basePath,
|
||||
mid,
|
||||
sid,
|
||||
tid,
|
||||
instructionConflict.reason,
|
||||
);
|
||||
logWarning("dispatch", instructionConflict.reason);
|
||||
return { action: "skip" };
|
||||
}
|
||||
return {
|
||||
action: "stop",
|
||||
reason: instructionConflict.reason,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { resolveTaskFile } from "./paths.js";
|
||||
import { updateTaskStatus } from "./sf-db.js";
|
||||
import { invalidateStateCache } from "./state.js";
|
||||
import { appendEvent } from "./workflow-events.js";
|
||||
import { logWarning } from "./workflow-logger.js";
|
||||
import { writeManifest } from "./workflow-manifest.js";
|
||||
import { renderAllProjections } from "./workflow-projections.js";
|
||||
|
||||
export interface ExecutionInstructionConflict {
|
||||
reason: string;
|
||||
|
|
@ -92,3 +98,39 @@ export function getExecuteTaskInstructionConflict(
|
|||
"Replan or skip this stale task, and use repo-appropriate verification instead.",
|
||||
};
|
||||
}
|
||||
|
||||
export async function skipExecuteTaskForInstructionConflict(
|
||||
basePath: string,
|
||||
mid: string,
|
||||
sid: string,
|
||||
tid: string,
|
||||
reason: string,
|
||||
): Promise<void> {
|
||||
const ts = new Date().toISOString();
|
||||
updateTaskStatus(mid, sid, tid, "skipped", ts);
|
||||
|
||||
try {
|
||||
await renderAllProjections(basePath, mid);
|
||||
writeManifest(basePath);
|
||||
appendEvent(basePath, {
|
||||
cmd: "skip-task",
|
||||
params: {
|
||||
milestoneId: mid,
|
||||
sliceId: sid,
|
||||
taskId: tid,
|
||||
reason,
|
||||
},
|
||||
ts,
|
||||
actor: "system",
|
||||
actor_name: "instruction-conflict-guard",
|
||||
trigger_reason: "repo instructions conflict with planned task",
|
||||
});
|
||||
} catch (err) {
|
||||
logWarning(
|
||||
"dispatch",
|
||||
`instruction-conflict skip post-mutation hook warning: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
}
|
||||
|
||||
invalidateStateCache();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ import { join } from "node:path";
|
|||
import test from "node:test";
|
||||
import type { DispatchContext } from "../auto-dispatch.ts";
|
||||
import { resolveDispatch } from "../auto-dispatch.ts";
|
||||
import {
|
||||
closeDatabase,
|
||||
getTask,
|
||||
insertMilestone,
|
||||
insertSlice,
|
||||
insertTask,
|
||||
openDatabase,
|
||||
} from "../sf-db.ts";
|
||||
import type { SFState } from "../types.ts";
|
||||
|
||||
function makeState(overrides: Partial<SFState> = {}): SFState {
|
||||
|
|
@ -129,9 +137,14 @@ test("dispatch: present task plan proceeds to execute-task normally", async (t)
|
|||
);
|
||||
});
|
||||
|
||||
test("dispatch: stale Docker Compose staging task is blocked by current repo instructions", async (t) => {
|
||||
test("dispatch: stale Docker Compose staging task is auto-skipped when DB can advance state", async (t) => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "sf-stale-staging-"));
|
||||
t.after(() => rmSync(tmp, { recursive: true, force: true }));
|
||||
t.after(() => {
|
||||
closeDatabase();
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
mkdirSync(join(tmp, ".sf"), { recursive: true });
|
||||
openDatabase(join(tmp, ".sf", "sf.db"));
|
||||
|
||||
writeFileSync(
|
||||
join(tmp, "AGENTS.md"),
|
||||
|
|
@ -146,6 +159,20 @@ test("dispatch: stale Docker Compose staging task is blocked by current repo ins
|
|||
);
|
||||
scaffoldSlicePlan(tmp, "M002", "S03");
|
||||
scaffoldTaskPlan(tmp, "M002", "S03", "T01");
|
||||
insertMilestone({ id: "M002", title: "Test Milestone" });
|
||||
insertSlice({
|
||||
id: "S03",
|
||||
milestoneId: "M002",
|
||||
title: "Third Slice",
|
||||
status: "active",
|
||||
});
|
||||
insertTask({
|
||||
id: "T01",
|
||||
sliceId: "S03",
|
||||
milestoneId: "M002",
|
||||
title: "Validate docker-compose staging stack starts cleanly",
|
||||
status: "pending",
|
||||
});
|
||||
writeFileSync(
|
||||
join(
|
||||
tmp,
|
||||
|
|
@ -169,14 +196,11 @@ test("dispatch: stale Docker Compose staging task is blocked by current repo ins
|
|||
|
||||
const result = await resolveDispatch(makeContext(tmp));
|
||||
|
||||
assert.equal(result.action, "stop");
|
||||
assert.match(
|
||||
result.action === "stop" ? result.reason : "",
|
||||
/Docker Compose staging/,
|
||||
);
|
||||
assert.match(
|
||||
result.action === "stop" ? result.reason : "",
|
||||
/deploy\/staging as historical/,
|
||||
assert.equal(result.action, "skip");
|
||||
assert.equal(
|
||||
getTask("M002", "S03", "T01")?.status,
|
||||
"skipped",
|
||||
"stale staging task should be closed before skip re-derives state",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,13 @@ function replayEvents(events: WorkflowEvent[]): void {
|
|||
updateTaskStatus(milestoneId, sliceId, taskId, "done", event.ts);
|
||||
break;
|
||||
}
|
||||
case "skip_task": {
|
||||
const milestoneId = p["milestoneId"] as string;
|
||||
const sliceId = p["sliceId"] as string;
|
||||
const taskId = p["taskId"] as string;
|
||||
updateTaskStatus(milestoneId, sliceId, taskId, "skipped", event.ts);
|
||||
break;
|
||||
}
|
||||
case "start_task": {
|
||||
const milestoneId = p["milestoneId"] as string;
|
||||
const sliceId = p["sliceId"] as string;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue