From 55c2b80703163c70155d795b4d5c2a8d2df1464d Mon Sep 17 00:00:00 2001 From: JulioMCruz Date: Sat, 18 Jul 2026 19:29:45 -0400 Subject: [PATCH] Keep completed tasks terminal --- src/tools/taskStatusPolicy.ts | 4 ++++ src/tools/updateTaskStatus.ts | 6 ++---- tests/task-status.test.ts | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/tools/taskStatusPolicy.ts create mode 100644 tests/task-status.test.ts diff --git a/src/tools/taskStatusPolicy.ts b/src/tools/taskStatusPolicy.ts new file mode 100644 index 0000000..8babf5c --- /dev/null +++ b/src/tools/taskStatusPolicy.ts @@ -0,0 +1,4 @@ +/** Worker-facing task completion is terminal and cannot move backwards. */ +export function isTerminalTaskTransition(current: unknown, requested: string): boolean { + return current === "Done" && requested !== "Done"; +} diff --git a/src/tools/updateTaskStatus.ts b/src/tools/updateTaskStatus.ts index 77d9e00..7273ffb 100644 --- a/src/tools/updateTaskStatus.ts +++ b/src/tools/updateTaskStatus.ts @@ -33,6 +33,7 @@ import { db } from "../firestore.js"; import { logActivity } from "../activityEvents.js"; import type { Tool } from "./types.js"; import { redactClaimTokens } from "./outputSanitizer.js"; +import { isTerminalTaskTransition } from "./taskStatusPolicy.js"; const ProofSchema = z .object({ @@ -103,10 +104,7 @@ export const updateTaskStatus: Tool = { // unbounded loop that burns LLM tokens for no new output. Treat re-opening // a Done task as a no-op success so the agent moves on instead of churning. const current = data.status; - if ( - current === "Done" && - (args.status === "In progress" || args.status === "Backlog") - ) { + if (isTerminalTaskTransition(current, args.status)) { return { ok: true, data: { taskId: args.taskId, status: "Done" }, diff --git a/tests/task-status.test.ts b/tests/task-status.test.ts new file mode 100644 index 0000000..5bb5e91 --- /dev/null +++ b/tests/task-status.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; + +import { isTerminalTaskTransition } from "../src/tools/taskStatusPolicy.js"; + +describe("isTerminalTaskTransition", () => { + it.each(["Backlog", "In progress", "Review"])( + "blocks Done → %s", + (requested) => { + expect(isTerminalTaskTransition("Done", requested)).toBe(true); + }, + ); + + it("allows idempotent completion and normal forward movement", () => { + expect(isTerminalTaskTransition("Done", "Done")).toBe(false); + expect(isTerminalTaskTransition("In progress", "Done")).toBe(false); + }); +});