@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-todo",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Pi tool — durable execution checklist (todo)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
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
- await run(host.execute, { action: "update", id: 1, status: "pending" });
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 the previous in_progress item, so just open the next.",
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
- // Single-active invariant: opening a new task closes any other
219
- // in_progress one, so the checklist always shows one focus.
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 (other.id !== t.id && other.status === "in_progress")
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
  }