@xynogen/pix-todo 0.1.6 → 0.1.7
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/package.json +1 -1
- package/src/todo.test.ts +47 -1
- package/src/todo.ts +10 -4
package/package.json
CHANGED
package/src/todo.test.ts
CHANGED
|
@@ -357,6 +357,50 @@ describe("todo actions", () => {
|
|
|
357
357
|
expect(out).toContain("Todos 1/3 done");
|
|
358
358
|
});
|
|
359
359
|
|
|
360
|
+
test("opening a later item cascade-closes skipped pending items", async () => {
|
|
361
|
+
const host = makeHost();
|
|
362
|
+
registerTodo(host.pi);
|
|
363
|
+
await host.emit(
|
|
364
|
+
"session_start",
|
|
365
|
+
{},
|
|
366
|
+
{ sessionManager: host.sessionManager },
|
|
367
|
+
);
|
|
368
|
+
await run(host.execute, { action: "set", items: "a\nb\nc\nd" });
|
|
369
|
+
// Jump straight to id 4 without opening 1-3; they should all auto-close.
|
|
370
|
+
const result = await run(host.execute, {
|
|
371
|
+
action: "update",
|
|
372
|
+
id: 4,
|
|
373
|
+
status: "in_progress",
|
|
374
|
+
});
|
|
375
|
+
const out = text(result);
|
|
376
|
+
expect(out).toContain("● 1. a");
|
|
377
|
+
expect(out).toContain("● 2. b");
|
|
378
|
+
expect(out).toContain("● 3. c");
|
|
379
|
+
expect(out).toContain("◐ 4. d");
|
|
380
|
+
expect(out).toContain("Todos 3/4 done");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("cascade-close leaves a blocked earlier item untouched", async () => {
|
|
384
|
+
const host = makeHost();
|
|
385
|
+
registerTodo(host.pi);
|
|
386
|
+
await host.emit(
|
|
387
|
+
"session_start",
|
|
388
|
+
{},
|
|
389
|
+
{ sessionManager: host.sessionManager },
|
|
390
|
+
);
|
|
391
|
+
await run(host.execute, { action: "set", items: "a\nb\nc" });
|
|
392
|
+
await run(host.execute, { action: "update", id: 1, status: "blocked" });
|
|
393
|
+
const result = await run(host.execute, {
|
|
394
|
+
action: "update",
|
|
395
|
+
id: 3,
|
|
396
|
+
status: "in_progress",
|
|
397
|
+
});
|
|
398
|
+
const out = text(result);
|
|
399
|
+
expect(out).toContain("⊘ 1. a"); // still blocked, not force-closed
|
|
400
|
+
expect(out).toContain("● 2. b"); // pending -> done
|
|
401
|
+
expect(out).toContain("◐ 3. c");
|
|
402
|
+
});
|
|
403
|
+
|
|
360
404
|
test("update unknown id returns error", async () => {
|
|
361
405
|
const host = makeHost();
|
|
362
406
|
registerTodo(host.pi);
|
|
@@ -439,8 +483,10 @@ describe("todo actions", () => {
|
|
|
439
483
|
{ sessionManager: host.sessionManager },
|
|
440
484
|
);
|
|
441
485
|
await run(host.execute, { action: "set", items: "a\nb\nc\nd" });
|
|
442
|
-
|
|
486
|
+
// Open id 2 first (cascade-closes nothing earlier we assert on), then set
|
|
487
|
+
// the others directly so each glyph is exercised without cascade interfering.
|
|
443
488
|
await run(host.execute, { action: "update", id: 2, status: "in_progress" });
|
|
489
|
+
await run(host.execute, { action: "update", id: 1, status: "pending" });
|
|
444
490
|
await run(host.execute, { action: "update", id: 3, status: "done" });
|
|
445
491
|
await run(host.execute, { action: "update", id: 4, status: "blocked" });
|
|
446
492
|
const out = text(await run(host.execute, { action: "list" }));
|
package/src/todo.ts
CHANGED
|
@@ -111,7 +111,7 @@ export default function registerTodo(pi: ExtensionAPI): void {
|
|
|
111
111
|
"todo(action, items?, id?, status?, text?) — action: list|set|add|update|clear. Use to track implementation progress, especially when executing a plan.",
|
|
112
112
|
promptGuidelines: [
|
|
113
113
|
"When you start executing a multi-step plan in BUILD mode, seed the todo list with `todo(action:'set', items: <plan Implementation Phases>)`.",
|
|
114
|
-
"Mark each item in_progress before working it via `todo(action:'update', id, status)`; opening one auto-closes
|
|
114
|
+
"Mark each item in_progress before working it via `todo(action:'update', id, status)`; opening one auto-closes every earlier item, so just open the next and skipped steps mark done themselves.",
|
|
115
115
|
"Call `todo(action:'list')` to recover your place after long runs or context compaction.",
|
|
116
116
|
],
|
|
117
117
|
parameters: Type.Object({
|
|
@@ -215,11 +215,17 @@ export default function registerTodo(pi: ExtensionAPI): void {
|
|
|
215
215
|
const t = todos.find((x) => x.id === params.id);
|
|
216
216
|
if (!t) return fail(`No todo with id ${params.id}.`);
|
|
217
217
|
if (params.status) {
|
|
218
|
-
//
|
|
219
|
-
//
|
|
218
|
+
// Sequential-progress invariant: opening a task means everything
|
|
219
|
+
// before it is finished. Cascade-close every earlier pending or
|
|
220
|
+
// in_progress item (ids are sequential) so the model never has to
|
|
221
|
+
// mark skipped steps done by hand. `blocked` is left untouched.
|
|
220
222
|
if (params.status === "in_progress")
|
|
221
223
|
for (const other of todos)
|
|
222
|
-
if (
|
|
224
|
+
if (
|
|
225
|
+
other.id < t.id &&
|
|
226
|
+
(other.status === "pending" ||
|
|
227
|
+
other.status === "in_progress")
|
|
228
|
+
)
|
|
223
229
|
other.status = "done";
|
|
224
230
|
t.status = params.status;
|
|
225
231
|
}
|