Files
oh-my-pi/packages/coding-agent/test/tools/todo-write.test.ts
T
can1357 1a5bbc3e51 feat(coding-agent): added details field to TodoItem for storing implementation specifics
- Added optional `details` field to TodoItem type for storing implementation specifics, file paths, and edge cases.
- Enhanced todo item display to show multi-line details with automatic indentation in interactive and reminder modes.
- Updated eager-todo system prompt to enforce separation of short task content (5-10 words) from detailed implementation information.
- Extended TodoWriteTool to support creating and updating tasks with details field via add_task and update operations.
- Added comprehensive test coverage for details field handling across todo operations (replace, add_task, update).
2026-03-11 00:56:54 +01:00

188 lines
5.6 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
import { type TodoPhase, TodoWriteTool } from "@oh-my-pi/pi-coding-agent/tools";
function createSession(initialPhases: TodoPhase[] = []): ToolSession {
let phases = initialPhases;
return {
cwd: "/tmp/test",
hasUI: false,
getSessionFile: () => null,
getSessionSpawns: () => "*",
settings: Settings.isolated(),
getTodoPhases: () => phases,
setTodoPhases: next => {
phases = next;
},
};
}
describe("TodoWriteTool auto-start behavior", () => {
it("auto-starts the first task after replace", async () => {
const tool = new TodoWriteTool(createSession());
const result = await tool.execute("call-1", {
ops: [
{
op: "replace",
phases: [
{
name: "Execution",
tasks: [{ content: "status" }, { content: "diagnostics" }],
},
],
},
],
});
const tasks = result.details?.phases[0]?.tasks ?? [];
expect(tasks.map(task => task.status)).toEqual(["in_progress", "pending"]);
const summary = result.content.find(part => part.type === "text");
if (!summary || summary.type !== "text") throw new Error("Expected text summary from todo_write");
expect(summary.text).toContain("Remaining items (2):");
expect(summary.text).toContain("task-1 status [in_progress] (Execution)");
expect(summary.text).toContain("task-2 diagnostics [pending] (Execution)");
});
it("auto-promotes the next pending task when current task is completed", async () => {
const tool = new TodoWriteTool(createSession());
await tool.execute("call-1", {
ops: [
{
op: "replace",
phases: [
{
name: "Execution",
tasks: [{ content: "status" }, { content: "diagnostics" }],
},
],
},
],
});
const result = await tool.execute("call-2", {
ops: [{ op: "update", id: "task-1", status: "completed" }],
});
const tasks = result.details?.phases[0]?.tasks ?? [];
expect(tasks.map(task => task.status)).toEqual(["completed", "in_progress"]);
const summary = result.content.find(part => part.type === "text");
if (!summary || summary.type !== "text") throw new Error("Expected text summary from todo_write");
expect(summary.text).toContain("Remaining items (1):");
expect(summary.text).toContain("task-2 diagnostics [in_progress] (Execution)");
const completedResult = await tool.execute("call-3", {
ops: [{ op: "update", id: "task-2", status: "completed" }],
});
const completedSummary = completedResult.content.find(part => part.type === "text");
if (!completedSummary || completedSummary.type !== "text") {
throw new Error("Expected text summary from todo_write");
}
expect(completedSummary.text).toContain("Remaining items: none.");
});
it("keeps only one in_progress task when replace input contains multiples", async () => {
const tool = new TodoWriteTool(createSession());
const result = await tool.execute("call-1", {
ops: [
{
op: "replace",
phases: [
{
name: "Execution",
tasks: [
{ content: "status", status: "in_progress" },
{ content: "diagnostics", status: "in_progress" },
],
},
],
},
],
});
const tasks = result.details?.phases[0]?.tasks ?? [];
expect(tasks.map(task => task.status)).toEqual(["in_progress", "pending"]);
});
});
describe("TodoWriteTool details field", () => {
it("preserves details through replace op", async () => {
const tool = new TodoWriteTool(createSession());
const result = await tool.execute("call-1", {
ops: [
{
op: "replace",
phases: [
{
name: "Work",
tasks: [
{ content: "Fix parser", details: "Update src/parser.ts line 42" },
{ content: "Add tests" },
],
},
],
},
],
});
const tasks = result.details?.phases[0]?.tasks ?? [];
expect(tasks[0].details).toBe("Update src/parser.ts line 42");
expect(tasks[1].details).toBeUndefined();
});
it("preserves details through add_task op", async () => {
const tool = new TodoWriteTool(createSession());
await tool.execute("call-1", {
ops: [{ op: "replace", phases: [{ name: "Work", tasks: [{ content: "First" }] }] }],
});
const result = await tool.execute("call-2", {
ops: [{ op: "add_task", phase: "phase-1", content: "Second", details: "Check edge cases" }],
});
const tasks = result.details?.phases[0]?.tasks ?? [];
expect(tasks[1].details).toBe("Check edge cases");
});
it("updates details via update op", async () => {
const tool = new TodoWriteTool(createSession());
await tool.execute("call-1", {
ops: [
{
op: "replace",
phases: [{ name: "Work", tasks: [{ content: "Fix bug", details: "Old details" }] }],
},
],
});
const result = await tool.execute("call-2", {
ops: [{ op: "update", id: "task-1", details: "New details with\nlines" }],
});
const task = result.details?.phases[0]?.tasks[0];
expect(task?.details).toBe("New details with\nlines");
});
it("includes details in summary for in_progress tasks", async () => {
const tool = new TodoWriteTool(createSession());
const result = await tool.execute("call-1", {
ops: [
{
op: "replace",
phases: [
{
name: "Work",
tasks: [{ content: "Fix parser", details: "Edit src/parser.ts" }],
},
],
},
],
});
const summary = result.content.find(part => part.type === "text");
if (!summary || summary.type !== "text") throw new Error("Expected text summary");
// Task is auto-promoted to in_progress, so details should appear in summary
expect(summary.text).toContain("Edit src/parser.ts");
});
});