@maxiaochao/pi-toolkit 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +1 -1
  3. package/package.json +2 -2
  4. package/packages/herdr-subagents/CHANGELOG.md +15 -0
  5. package/packages/herdr-subagents/README.md +9 -6
  6. package/packages/herdr-subagents/TESTING.md +6 -4
  7. package/packages/herdr-subagents/docs/demo.webm +0 -0
  8. package/packages/herdr-subagents/docs/screenshot.png +0 -0
  9. package/packages/herdr-subagents/extensions/herdr-subagents.ts +40 -6
  10. package/packages/herdr-subagents/package.json +1 -1
  11. package/packages/herdr-subagents/src/batch-runner.ts +7 -7
  12. package/packages/herdr-subagents/src/blocking.ts +4 -4
  13. package/packages/herdr-subagents/src/dispatcher.ts +8 -0
  14. package/packages/herdr-subagents/src/fleet-controller.ts +167 -16
  15. package/packages/herdr-subagents/src/fleet.ts +119 -8
  16. package/packages/herdr-subagents/src/history.ts +11 -5
  17. package/packages/herdr-subagents/src/parameters.ts +2 -2
  18. package/packages/herdr-subagents/src/reconcile.ts +2 -1
  19. package/packages/herdr-subagents/src/records.ts +53 -1
  20. package/packages/herdr-subagents/src/tab-cleanup.ts +18 -2
  21. package/packages/herdr-subagents/src/tool.ts +30 -3
  22. package/packages/herdr-subagents/src/validation.ts +2 -1
  23. package/packages/herdr-subagents/tests/extension-contract.ts +16 -2
  24. package/packages/herdr-subagents/tests/run-tests.mjs +169 -8
  25. package/packages/todo/CHANGELOG.md +4 -0
  26. package/packages/todo/README.md +19 -9
  27. package/packages/todo/TESTING.md +9 -7
  28. package/packages/todo/docs/demo.gif +0 -0
  29. package/packages/todo/docs/screenshot.png +0 -0
  30. package/packages/todo/extensions/todo.ts +120 -90
  31. package/packages/todo/package.json +2 -2
  32. package/packages/todo/src/todo-state.ts +48 -49
  33. package/packages/todo/tests/todo-extension.mjs +134 -0
  34. package/packages/todo/tests/todo-state.mjs +80 -70
@@ -3,96 +3,106 @@ import {
3
3
  activeTodoRows,
4
4
  activeTodos,
5
5
  applyTodoAction,
6
+ formatCurrentPlan,
6
7
  restoreTodoState,
7
8
  } from "../src/todo-state.ts";
8
9
 
9
10
  const empty = { todos: [], nextId: 1 };
10
11
 
11
- assert.deepEqual(
12
- activeTodoRows([
13
- { id: 8, text: "closed", status: "done" },
14
- { id: 9, text: "current", status: "doing" },
15
- { id: 11, text: "next", status: "todo" },
16
- ]).map(({ position, todo }) => ({ position, id: todo.id })),
17
- [
18
- { position: 1, id: 9 },
19
- { position: 2, id: 11 },
20
- ],
21
- "active rows expose contiguous queue positions independently of internal IDs",
22
- );
23
-
24
- const replaced = applyTodoAction(empty, {
25
- action: "replace",
12
+ let state = applyTodoAction(empty, {
13
+ action: "plan",
26
14
  items: ["Inspect the failure", "Implement the fix", "Run verification"],
27
15
  });
28
- assert.deepEqual(
29
- replaced.todos.map(({ id, text, status }) => ({ id, text, status })),
30
- [
31
- { id: 1, text: "Inspect the failure", status: "doing" },
32
- { id: 2, text: "Implement the fix", status: "todo" },
33
- { id: 3, text: "Run verification", status: "todo" },
34
- ],
35
- "replace installs a fresh ordered plan and starts its first task",
36
- );
37
- assert.equal(replaced.nextId, 4, "replace advances the ID sequence past the new plan");
16
+ assert.deepEqual(state.todos, [
17
+ { id: 1, text: "Inspect the failure", status: "doing" },
18
+ { id: 2, text: "Implement the fix", status: "todo" },
19
+ { id: 3, text: "Run verification", status: "todo" },
20
+ ]);
21
+ assert.equal(state.nextId, 4);
22
+
23
+ state = applyTodoAction(state, { action: "complete_current", expected_current_id: 1 });
24
+ assert.deepEqual(state.todos.map(({ id, status }) => ({ id, status })), [
25
+ { id: 1, status: "done" },
26
+ { id: 2, status: "doing" },
27
+ { id: 3, status: "todo" },
28
+ ]);
29
+
30
+ const stale = applyTodoAction(state, { action: "complete_current", expected_current_id: 1 });
31
+ assert.match(stale.error ?? "", /current is #2/);
32
+ assert.deepEqual(stale.todos, state.todos, "repeated or stale completion is atomic");
33
+ assert.equal(stale.nextId, state.nextId);
34
+
35
+ const missingExpectedId = applyTodoAction(state, { action: "cancel_current" });
36
+ assert.match(missingExpectedId.error ?? "", /expected_current_id is required/);
37
+ assert.deepEqual(missingExpectedId.todos, state.todos);
38
38
 
39
- const completed = applyTodoAction(replaced, { action: "update", id: 1, status: "done" });
40
- assert.deepEqual(
41
- completed.todos.map(({ id, status }) => ({ id, status })),
42
- [
43
- { id: 1, status: "done" },
44
- { id: 2, status: "doing" },
45
- { id: 3, status: "todo" },
46
- ],
47
- "finishing active work advances the next queued task",
48
- );
39
+ state = applyTodoAction(state, { action: "cancel_current", expected_current_id: 2 });
40
+ assert.deepEqual(state.todos.map(({ id, status }) => ({ id, status })), [
41
+ { id: 1, status: "done" },
42
+ { id: 2, status: "cancelled" },
43
+ { id: 3, status: "doing" },
44
+ ], "cancelling only the current step keeps the later plan valid and advances once");
49
45
 
50
- const cancelled = applyTodoAction(completed, { action: "update", id: 2, status: "cancelled" });
51
- assert.equal(cancelled.todos[2].status, "doing", "cancelling active work advances the next queued task");
52
- assert.deepEqual(activeTodos(cancelled.todos).map((todo) => todo.id), [3], "closed work is absent from the active view");
46
+ state = applyTodoAction(state, { action: "add_step", text: "Review the result" });
47
+ assert.deepEqual(activeTodos(state.todos).map(({ id, status }) => ({ id, status })), [
48
+ { id: 3, status: "doing" },
49
+ { id: 4, status: "todo" },
50
+ ], "add_step only appends to the current plan");
53
51
 
54
- const restarted = applyTodoAction(cancelled, { action: "add", text: "Review the redirected implementation", status: "doing" });
55
- assert.equal(restarted.todos.find((todo) => todo.id === 3).status, "todo", "starting another task demotes the previous active task");
56
- assert.equal(restarted.todos.find((todo) => todo.id === 4).status, "doing", "the requested task becomes active");
52
+ state = applyTodoAction(state, { action: "plan", items: ["Investigate new direction", "Verify new direction"] });
53
+ assert.deepEqual(state.todos, [
54
+ { id: 1, text: "Inspect the failure", status: "done" },
55
+ { id: 2, text: "Implement the fix", status: "cancelled" },
56
+ { id: 3, text: "Run verification", status: "cancelled" },
57
+ { id: 4, text: "Review the result", status: "cancelled" },
58
+ { id: 5, text: "Investigate new direction", status: "doing" },
59
+ { id: 6, text: "Verify new direction", status: "todo" },
60
+ ], "replanning preserves closed history and cancels every old unfinished step");
61
+ assert.equal(state.nextId, 7, "replanning allocates fresh monotonic IDs");
62
+ assert.deepEqual(activeTodoRows(state.todos).map(({ position, todo }) => ({ position, id: todo.id })), [
63
+ { position: 1, id: 5 },
64
+ { position: 2, id: 6 },
65
+ ], "display positions are contiguous and independent of internal IDs");
66
+ assert.equal(formatCurrentPlan(state.todos), "CURRENT plan\n#5 [doing] Investigate new direction\n#6 [todo] Verify new direction");
57
67
 
58
- const removed = applyTodoAction(restarted, { action: "remove", id: 4 });
59
- assert.equal(removed.todos.find((todo) => todo.id === 3).status, "doing", "removing active work advances the queue");
60
- assert.equal(removed.nextId, 5, "removing a task does not reuse its ID");
68
+ const invalidPlan = applyTodoAction(state, { action: "plan", items: ["valid", " "] });
69
+ assert.ok(invalidPlan.error);
70
+ assert.deepEqual(invalidPlan.todos, state.todos, "invalid plan text leaves history and current plan unchanged");
61
71
 
62
- const duplicate = applyTodoAction(removed, { action: "add", text: "Run verification" });
63
- assert.match(duplicate.error ?? "", /already exists/, "active duplicates are rejected");
64
- assert.deepEqual(duplicate.todos, removed.todos, "a rejected mutation preserves the previous state");
72
+ const repeatedText = applyTodoAction(state, { action: "add_step", text: "Verify new direction" });
73
+ assert.equal(repeatedText.error, undefined, "step text is not interpreted as a marker or uniqueness key");
74
+ assert.equal(repeatedText.todos.at(-1)?.text, "Verify new direction");
65
75
 
66
- const replacedAgain = applyTodoAction(removed, { action: "replace", items: ["New direction"] });
67
- assert.deepEqual(
68
- replacedAgain.todos,
69
- [{ id: 5, text: "New direction", status: "doing" }],
70
- "replace discards the obsolete plan without reusing an old task ID",
71
- );
72
- assert.equal(replacedAgain.nextId, 6);
76
+ state = applyTodoAction(state, { action: "plan", items: [] });
77
+ assert.equal(activeTodos(state.todos).length, 0, "an empty plan leaves no current plan");
78
+ assert.equal(formatCurrentPlan(state.todos), "CURRENT plan: none");
79
+ assert.equal(state.nextId, 7, "an empty plan does not reset or consume IDs");
80
+ assert.equal(state.todos.find(({ id }) => id === 1)?.status, "done", "empty plan retains closed history");
73
81
 
74
- const invalidReplace = applyTodoAction(replacedAgain, { action: "replace", items: [" "] });
75
- assert.ok(invalidReplace.error, "replace rejects empty task text");
76
- assert.deepEqual(invalidReplace.todos, replacedAgain.todos, "invalid replacement is atomic");
82
+ const appendedAfterEmpty = applyTodoAction(state, { action: "add_step", text: "A later explicit step" });
83
+ assert.deepEqual(appendedAfterEmpty.todos.at(-1), { id: 7, text: "A later explicit step", status: "doing" });
77
84
 
78
85
  const restored = restoreTodoState([
79
86
  { id: 4, text: "legacy complete", done: true },
80
87
  { id: 2, text: "first active", status: "doing" },
81
88
  { id: 3, text: "second active", status: "doing" },
82
89
  ], 2);
83
- assert.deepEqual(
84
- restored.todos.map(({ id, status }) => ({ id, status })),
85
- [
86
- { id: 2, status: "doing" },
87
- { id: 3, status: "todo" },
88
- { id: 4, status: "done" },
89
- ],
90
- "restoration migrates legacy entries and enforces one active task",
91
- );
92
- assert.equal(restored.nextId, 5, "restoration repairs stale next IDs");
90
+ assert.deepEqual(restored.todos.map(({ id, status }) => ({ id, status })), [
91
+ { id: 2, status: "doing" },
92
+ { id: 3, status: "todo" },
93
+ { id: 4, status: "done" },
94
+ ]);
95
+ assert.equal(restored.nextId, 5, "branch restoration repairs stale ID counters");
93
96
 
94
- const cleared = applyTodoAction(restored, { action: "clear" });
95
- assert.deepEqual(cleared.todos, []);
96
- assert.equal(cleared.nextId, 1, "clear resets the ID sequence");
97
+ const legacyReordered = restoreTodoState([
98
+ { id: 1, text: "demoted by legacy update", status: "todo" },
99
+ { id: 2, text: "legacy current", status: "doing" },
100
+ { id: 3, text: "legacy queued", status: "todo" },
101
+ ]);
102
+ assert.deepEqual(activeTodos(legacyReordered.todos).map(({ id, status }) => ({ id, status })), [
103
+ { id: 2, status: "doing" },
104
+ { id: 1, status: "todo" },
105
+ { id: 3, status: "todo" },
106
+ ], "legacy snapshots project the sole doing step before every queued step");
97
107
 
98
108
  console.log("Todo state tests passed");