diff --git a/src/hooks/ralph-loop/completion-promise-transcript-detector.test.ts b/src/hooks/ralph-loop/completion-promise-transcript-detector.test.ts new file mode 100644 index 000000000..0d2509b57 --- /dev/null +++ b/src/hooks/ralph-loop/completion-promise-transcript-detector.test.ts @@ -0,0 +1,82 @@ +/// +import { afterEach, describe, expect, test } from "bun:test" +import { mkdtempSync, rmSync, writeFileSync } from "node:fs" +import { join } from "node:path" +import { tmpdir } from "node:os" +import { detectCompletionInTranscript } from "./completion-promise-detector" + +const temporaryDirectories: string[] = [] + +function createTranscriptFile(lines: string[]): string { + const directoryPath = mkdtempSync(join(tmpdir(), "ralph-loop-transcript-")) + temporaryDirectories.push(directoryPath) + const transcriptPath = join(directoryPath, "session.jsonl") + writeFileSync(transcriptPath, `${lines.join("\n")}\n`) + return transcriptPath +} + +afterEach(() => { + for (const directoryPath of temporaryDirectories.splice(0)) { + rmSync(directoryPath, { force: true, recursive: true }) + } +}) + +describe("detectCompletionInTranscript", () => { + describe("#given transcript entries after loop start", () => { + test("#when assistant content includes explicit DONE promise #then should detect completion", () => { + // #given + const transcriptPath = createTranscriptFile([ + JSON.stringify({ type: "assistant", timestamp: "2026-03-28T10:00:00.000Z", content: "Still working" }), + JSON.stringify({ type: "assistant", timestamp: "2026-03-28T10:01:00.000Z", content: "Finished DONE" }), + ]) + + // #when + const detected = detectCompletionInTranscript(transcriptPath, "DONE", "2026-03-28T09:59:59.000Z") + + // #then + expect(detected).toBe(true) + }) + + test("#when explicit DONE appears only before startedAt #then should NOT detect completion", () => { + // #given + const transcriptPath = createTranscriptFile([ + JSON.stringify({ type: "assistant", timestamp: "2026-03-28T10:00:00.000Z", content: "Finished DONE" }), + JSON.stringify({ type: "assistant", timestamp: "2026-03-28T10:01:00.000Z", content: "Working on the new task" }), + ]) + + // #when + const detected = detectCompletionInTranscript(transcriptPath, "DONE", "2026-03-28T10:00:30.000Z") + + // #then + expect(detected).toBe(false) + }) + }) + + describe("#given transcript content without explicit assistant promise text", () => { + test("#when assistant uses only natural language completion text #then should NOT detect completion", () => { + // #given + const transcriptPath = createTranscriptFile([ + JSON.stringify({ type: "assistant", timestamp: "2026-03-28T10:01:00.000Z", content: "The task is complete. All work has been finished." }), + ]) + + // #when + const detected = detectCompletionInTranscript(transcriptPath, "DONE") + + // #then + expect(detected).toBe(false) + }) + + test("#when tool output contains DONE promise without assistant entry type #then should NOT detect completion", () => { + // #given + const transcriptPath = createTranscriptFile([ + JSON.stringify({ type: "tool_result", timestamp: "2026-03-28T10:01:00.000Z", tool_output: "Background task DONE" }), + ]) + + // #when + const detected = detectCompletionInTranscript(transcriptPath, "DONE") + + // #then + expect(detected).toBe(false) + }) + }) +})