@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.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/packages/herdr-subagents/CHANGELOG.md +15 -0
- package/packages/herdr-subagents/README.md +9 -6
- package/packages/herdr-subagents/TESTING.md +6 -4
- package/packages/herdr-subagents/docs/demo.webm +0 -0
- package/packages/herdr-subagents/docs/screenshot.png +0 -0
- package/packages/herdr-subagents/extensions/herdr-subagents.ts +40 -6
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/batch-runner.ts +7 -7
- package/packages/herdr-subagents/src/blocking.ts +4 -4
- package/packages/herdr-subagents/src/dispatcher.ts +8 -0
- package/packages/herdr-subagents/src/fleet-controller.ts +167 -16
- package/packages/herdr-subagents/src/fleet.ts +119 -8
- package/packages/herdr-subagents/src/history.ts +11 -5
- package/packages/herdr-subagents/src/parameters.ts +2 -2
- package/packages/herdr-subagents/src/reconcile.ts +2 -1
- package/packages/herdr-subagents/src/records.ts +53 -1
- package/packages/herdr-subagents/src/tab-cleanup.ts +18 -2
- package/packages/herdr-subagents/src/tool.ts +30 -3
- package/packages/herdr-subagents/src/validation.ts +2 -1
- package/packages/herdr-subagents/tests/extension-contract.ts +16 -2
- package/packages/herdr-subagents/tests/run-tests.mjs +169 -8
- package/packages/todo/CHANGELOG.md +4 -0
- package/packages/todo/README.md +19 -9
- package/packages/todo/TESTING.md +9 -7
- package/packages/todo/docs/demo.gif +0 -0
- package/packages/todo/docs/screenshot.png +0 -0
- package/packages/todo/extensions/todo.ts +120 -90
- package/packages/todo/package.json +2 -2
- package/packages/todo/src/todo-state.ts +48 -49
- package/packages/todo/tests/todo-extension.mjs +134 -0
- 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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
assert.deepEqual(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
51
|
-
assert.
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
assert.
|
|
56
|
-
|
|
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
|
|
59
|
-
assert.
|
|
60
|
-
assert.
|
|
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
|
|
63
|
-
assert.
|
|
64
|
-
assert.
|
|
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
|
-
|
|
67
|
-
assert.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
75
|
-
assert.
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
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");
|