feat(KB-142): add confirm gate and abort flow to task_add_dep tool

- Add confirm parameter to task_add_dep requiring explicit opt-in before destructive action
- On confirm, abort execution, discard worktree/branch, and move task to triage
- Add triage as valid transition from in-progress for dependency-triggered re-specification
- Auto-move todo tasks to triage when dependencies are added
- Update EXECUTOR_SYSTEM_PROMPT with task_add_dep confirm behavior documentation
This commit is contained in:
Dustin Byrne
2026-03-28 00:41:01 -04:00
parent a59fb5b857
commit 2bdd628d6d
5 changed files with 550 additions and 4 deletions

View File

@@ -401,6 +401,66 @@ describe("TaskStore", () => {
});
});
describe("updateTask — auto-move todo to triage on new deps", () => {
it("moves a todo task to triage when a new dependency is added", async () => {
const task = await store.createTask({ description: "Todo task", column: "todo" });
expect(task.column).toBe("todo");
const updated = await store.updateTask(task.id, { dependencies: ["KB-999"] });
expect(updated.column).toBe("triage");
expect(updated.status).toBeUndefined();
// Verify log entry
expect(updated.log.some((l: any) => l.action.includes("Moved to triage for re-specification"))).toBe(true);
// Verify persistence
const fetched = await store.getTask(task.id);
expect(fetched.column).toBe("triage");
});
it("emits task:moved event with { from: 'todo', to: 'triage' }", async () => {
const task = await store.createTask({ description: "Todo task", column: "todo" });
const events: any[] = [];
store.on("task:moved", (data: any) => events.push(data));
await store.updateTask(task.id, { dependencies: ["KB-999"] });
expect(events).toHaveLength(1);
expect(events[0].from).toBe("todo");
expect(events[0].to).toBe("triage");
});
it("does NOT move when dependencies are removed from a todo task", async () => {
const task = await store.createTask({ description: "Todo task", column: "todo", dependencies: ["KB-001"] });
const updated = await store.updateTask(task.id, { dependencies: [] });
expect(updated.column).toBe("todo");
});
it("does NOT move when dependencies are replaced with same set", async () => {
const task = await store.createTask({ description: "Todo task", column: "todo", dependencies: ["KB-001"] });
const updated = await store.updateTask(task.id, { dependencies: ["KB-001"] });
expect(updated.column).toBe("todo");
});
it("does NOT move a triage task when dependencies are added", async () => {
const task = await store.createTask({ description: "Triage task" });
expect(task.column).toBe("triage");
const updated = await store.updateTask(task.id, { dependencies: ["KB-999"] });
expect(updated.column).toBe("triage");
});
it("does NOT move an in-progress task when dependencies are added (handled by executor)", async () => {
const task = await store.createTask({ description: "IP task", column: "todo" });
await store.moveTask(task.id, "in-progress");
const updated = await store.updateTask(task.id, { dependencies: ["KB-999"] });
expect(updated.column).toBe("in-progress");
});
});
describe("updateTask — blockedBy", () => {
it("sets blockedBy to a string value", async () => {
const task = await store.createTask({ title: "Blocked task", description: "A task" });
@@ -866,6 +926,17 @@ describe("TaskStore", () => {
});
});
describe("moveTask — in-progress to triage", () => {
it("allows moving an in-progress task to triage", async () => {
const task = await store.createTask({ description: "test in-progress to triage" });
await store.moveTask(task.id, "todo");
await store.moveTask(task.id, "in-progress");
const moved = await store.moveTask(task.id, "triage");
expect(moved.column).toBe("triage");
});
});
describe("columnMovedAt", () => {
it("createTask sets columnMovedAt", async () => {
const before = new Date().toISOString();