import assert from "node:assert/strict"; import test from "node:test"; import { TaskState } from "../src/state.ts"; import { registerTaskTools } from "../src/tools.ts"; function registeredTools(state = new TaskState()) { const tools = new Map(); let changes = 0; const pi = { registerTool(tool: any) { tools.set(tool.name, tool); }, }; registerTaskTools(pi as never, state, () => { changes += 1; }); return { tools, state, changes: () => changes }; } async function execute(tool: any, params: unknown) { return tool.execute("call", params, undefined, undefined, {}); } test("registers five focused tools without the old add_task alias", () => { const { tools } = registeredTools(); assert.deepEqual([...tools.keys()], ["set_tasks", "update_task", "rm_task", "list_task", "get_task"]); assert.deepEqual(Object.keys(tools.get("set_tasks").parameters.properties), ["mode", "tasks", "force"]); assert.deepEqual(Object.keys(tools.get("update_task").parameters.properties), ["tasks"]); assert.deepEqual(Object.keys(tools.get("rm_task").parameters.properties), ["ids", "reason"]); assert.deepEqual(Object.keys(tools.get("list_task").parameters.properties), []); assert.deepEqual(Object.keys(tools.get("get_task").parameters.properties), ["ids"]); }); test("set_tasks appends optional-description tasks and persists a complete snapshot", async () => { const harness = registeredTools(); const result = await execute(harness.tools.get("set_tasks"), { mode: "append", tasks: [{ name: "Parent", children: [{ name: "Child", description: "Useful detail" }] }], }); assert.equal(result.content[0].text, "Added 2 tasks: 1, 2. 0/2 done."); assert.equal(result.details.action, "append"); assert.equal(result.details.snapshot.tasks.length, 2); assert.equal(result.details.snapshot.tasks[0].description, undefined); assert.equal(result.details.snapshot.nextId, 3); assert.equal(harness.changes(), 1); }); test("set_tasks atomically replaces or clears the tree and resets IDs", async () => { const harness = registeredTools(); await execute(harness.tools.get("set_tasks"), { mode: "append", tasks: [{ name: "Old", status: "done" }], }); const replaced = await execute(harness.tools.get("set_tasks"), { mode: "replace", tasks: [{ name: "New", status: "in-progress" }], }); assert.equal(replaced.content[0].text, "Replaced task tree with 1 task. 0/1 done. Task IDs restart at 1."); assert.deepEqual(harness.state.list().map((task) => [task.id, task.name]), [[1, "New"]]); const cleared = await execute(harness.tools.get("set_tasks"), { mode: "replace", tasks: [], force: true, }); assert.equal(cleared.content[0].text, "Cleared task tree. Task IDs reset to 1."); assert.deepEqual(harness.state.snapshot(), { tasks: [], nextId: 1 }); }); test("tool validation errors preserve atomic state", async () => { const harness = registeredTools(); await execute(harness.tools.get("set_tasks"), { mode: "append", tasks: [{ name: "Parent", children: [{ name: "Child" }] }], }); const before = harness.state.snapshot(); await assert.rejects( execute(harness.tools.get("update_task"), { tasks: [{ id: 1, parentId: 2 }] }), /Update failed · 1 validation error\n- task [12]: parentId creates a cycle/, ); assert.deepEqual(harness.state.snapshot(), before); assert.equal(harness.changes(), 1); }); test("rm_task requires and reports a constrained removal reason", async () => { const harness = registeredTools(); await execute(harness.tools.get("set_tasks"), { mode: "append", tasks: [{ name: "Duplicate" }], }); const removed = await execute(harness.tools.get("rm_task"), { ids: [1], reason: "duplicate" }); assert.match(removed.content[0].text, /Reason: duplicate\.$/); assert.deepEqual(harness.state.list(), []); }); test("list is compact and get returns available details for multiple IDs", async () => { const harness = registeredTools(); await execute(harness.tools.get("set_tasks"), { mode: "append", tasks: [{ name: "Parent", description: "Detailed", children: [{ name: "Child" }] }], }); const list = await execute(harness.tools.get("list_task"), {}); assert.equal(list.content[0].text, "1 [pending] Parent\n└─ 2 [pending] Child"); const get = await execute(harness.tools.get("get_task"), { ids: [1, 2] }); const parsed = JSON.parse(get.content[0].text); assert.equal(parsed[0].description, "Detailed"); assert.deepEqual(parsed[0].childrenIds, [2]); assert.equal(parsed[1].description, undefined); assert.equal(parsed[1].parentId, 1); });