@firstpick/pi-extension-todo-progress 0.1.1 → 0.1.3

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 (3) hide show
  1. package/README.md +7 -5
  2. package/index.ts +51 -35
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,11 +4,12 @@ Auto todo/progress tracking for multi-goal prompts.
4
4
 
5
5
  ## What it does
6
6
 
7
- - Auto-creates todos when prompts appear multi-step.
8
- - Keeps a progress widget visible until completion.
7
+ - Instructs the agent to create concise, agent-authored todos for multi-step work.
8
+ - Tracks checklist markers from assistant messages instead of copying raw user prompt lines.
9
+ - Uses explicit status markers: `[ ]` not started, `[-]` partial, `[x]` done.
10
+ - Clears the widget automatically when all items are complete.
9
11
  - Shows up to 5 rows.
10
- - Supports mouse wheel scrolling in custom UI.
11
- - Supports hiding completed list.
12
+ - Supports hiding the current list manually.
12
13
 
13
14
  ## Install
14
15
 
@@ -26,7 +27,8 @@ None.
26
27
 
27
28
  ## Shortcuts
28
29
 
29
- - `Ctrl+Alt+X` — hide completed list.
30
+ - `Ctrl+Alt+X` — hide current list.
31
+ - `Ctrl+Alt+J` / `Ctrl+Alt+K` — scroll todo list down/up.
30
32
 
31
33
  ## Tools
32
34
 
package/index.ts CHANGED
@@ -1,72 +1,88 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
 
3
- type TodoItem = { text: string; done: boolean };
4
- type TodoState = { visible: boolean; items: TodoItem[]; offset: number; doneDismissHint: boolean };
3
+ type TodoStatus = "todo" | "partial" | "done";
4
+ type TodoItem = { text: string; status: TodoStatus };
5
+ type TodoState = { visible: boolean; items: TodoItem[]; offset: number };
5
6
 
6
7
  const KEY = "todo-progress";
7
8
  const MAX_ROWS = 5;
8
9
 
9
- function splitGoals(text: string): string[] {
10
- const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
11
- const bullets = lines.filter((l) => /^(-|\*|\d+[.)])\s+/.test(l)).map((l) => l.replace(/^(-|\*|\d+[.)])\s+/, ""));
12
- if (bullets.length >= 2) return bullets;
13
- const parts = text.split(/\band then\b|\bthen\b|,\s*and\s+/gi).map((s) => s.trim()).filter((s) => s.length > 8);
14
- return parts.slice(0, 12);
10
+ function statusLabel(status: TodoStatus): string {
11
+ if (status === "done") return "[x]";
12
+ if (status === "partial") return "[-]";
13
+ return "[ ]";
14
+ }
15
+
16
+ function clear(ctx: ExtensionContext, s: TodoState) {
17
+ s.visible = false;
18
+ s.items = [];
19
+ s.offset = 0;
20
+ if (ctx.hasUI) ctx.ui.setWidget(KEY, []);
15
21
  }
16
22
 
17
23
  function render(ctx: ExtensionContext, s: TodoState) {
18
- if (!ctx.hasUI || !s.visible || s.items.length === 0) return;
19
- const done = s.items.filter((i) => i.done).length;
24
+ if (!ctx.hasUI) return;
25
+ if (!s.visible || s.items.length === 0) {
26
+ ctx.ui.setWidget(KEY, []);
27
+ return;
28
+ }
29
+
30
+ const done = s.items.filter((i) => i.status === "done").length;
31
+ if (done === s.items.length) {
32
+ clear(ctx, s);
33
+ return;
34
+ }
35
+
36
+ const partial = s.items.filter((i) => i.status === "partial").length;
20
37
  const top = s.items.slice(s.offset, s.offset + MAX_ROWS);
21
- const lines = [ctx.ui.theme.fg("accent", `Todo ${done}/${s.items.length}`)];
22
- for (const item of top) lines.push(`${item.done ? "[x]" : "[ ]"} ${item.text}`);
38
+ const lines = [ctx.ui.theme.fg("accent", `Todo ${done}/${s.items.length} done${partial ? `, ${partial} partial` : ""}`)];
39
+ for (const item of top) lines.push(`${statusLabel(item.status)} ${item.text}`);
23
40
  if (s.items.length > MAX_ROWS) lines.push(ctx.ui.theme.fg("dim", `Scroll ${s.offset + 1}-${Math.min(s.offset + MAX_ROWS, s.items.length)} of ${s.items.length}`));
24
- if (done === s.items.length && s.doneDismissHint) lines.push(ctx.ui.theme.fg("success", "Done — Ctrl+Alt+X to dismiss"));
25
41
  ctx.ui.setWidget(KEY, lines);
26
42
  }
27
43
 
28
44
  export default function todoProgress(pi: ExtensionAPI) {
29
- const state: TodoState = { visible: true, items: [], offset: 0, doneDismissHint: true };
45
+ const state: TodoState = { visible: false, items: [], offset: 0 };
30
46
 
31
47
  pi.on("before_agent_start", async (event) => {
32
48
  return {
33
49
  systemPrompt:
34
50
  event.systemPrompt +
35
- "\n\n[TODO PROGRESS POLICY] Aggressively use a todo list for multi-goal requests. Prefer creating/updating todos once too much rather than once too little. Mark items done as soon as completed.",
51
+ "\n\n[TODO PROGRESS POLICY] For multi-step work, create a concise agent-authored checklist with 2-6 short items. Do not copy raw user-prompt lines as todos; rewrite them into clear action items. Use explicit markers: [ ] not started, [-] partial/in progress, [x] complete. Update checklist markers as work changes. Before your final answer, close the todo list by marking every remaining item [x] or by explicitly stating no todo list is needed for the completed single-step task.",
36
52
  };
37
53
  });
38
54
 
39
55
  pi.on("input", async (event, ctx) => {
40
- if (event.text.startsWith("/")) return { action: "continue" as const };
41
- const goals = splitGoals(event.text);
42
- if (goals.length >= 2) {
43
- state.items = goals.map((g) => ({ text: g, done: false }));
44
- state.offset = 0;
45
- state.visible = true;
46
- render(ctx, state);
47
- }
56
+ if (!event.text.startsWith("/")) clear(ctx, state);
48
57
  return { action: "continue" as const };
49
58
  });
50
59
 
51
60
  pi.on("message_end", async (event, ctx) => {
52
- if (!state.items.length || event.message.role !== "assistant") return;
53
- const text = event.message.content.filter((c: any) => c.type === "text").map((c: any) => c.text).join("\n").toLowerCase();
54
- for (const item of state.items) {
55
- if (!item.done && text.includes(item.text.toLowerCase().slice(0, 24))) item.done = true;
61
+ if (event.message.role !== "assistant") return;
62
+
63
+ const text = event.message.content.filter((c: any) => c.type === "text").map((c: any) => c.text).join("\n");
64
+
65
+ const checklistRegex = /^\s*(?:[-*]|\d+[.)])\s*\[( |x|X|-)\]\s+(.+)$/gm;
66
+ const checklist: Array<TodoItem> = [];
67
+ for (const match of text.matchAll(checklistRegex)) {
68
+ const mark = (match[1] || " ").toLowerCase();
69
+ const label = (match[2] || "").trim().replace(/\s+/g, " ");
70
+ const status: TodoStatus = mark === "x" ? "done" : mark === "-" ? "partial" : "todo";
71
+ if (label) checklist.push({ status, text: label });
56
72
  }
73
+
74
+ if (checklist.length === 0) return;
75
+
76
+ state.items = checklist.slice(0, 12);
77
+ state.offset = Math.min(state.offset, Math.max(0, state.items.length - MAX_ROWS));
78
+ state.visible = true;
57
79
  render(ctx, state);
58
80
  });
59
81
 
60
82
  pi.registerShortcut("ctrl+alt+x", {
61
83
  description: "Dismiss completed todo widget",
62
84
  handler: async (ctx) => {
63
- const done = state.items.length > 0 && state.items.every((i) => i.done);
64
- if (!done) {
65
- ctx.ui.notify("Todo not complete yet", "warning");
66
- return;
67
- }
68
- state.visible = false;
69
- ctx.ui.setWidget(KEY, []);
85
+ clear(ctx, state);
70
86
  ctx.ui.notify("Todo widget dismissed", "info");
71
87
  },
72
88
  });
@@ -74,5 +90,5 @@ export default function todoProgress(pi: ExtensionAPI) {
74
90
  pi.registerShortcut("ctrl+alt+j", { description: "Todo scroll down", handler: async (ctx) => { state.offset = Math.min(Math.max(0, state.items.length - MAX_ROWS), state.offset + 1); render(ctx, state); } });
75
91
  pi.registerShortcut("ctrl+alt+k", { description: "Todo scroll up", handler: async (ctx) => { state.offset = Math.max(0, state.offset - 1); render(ctx, state); } });
76
92
 
77
- pi.on("session_start", async (_event, ctx) => render(ctx, state));
93
+ pi.on("session_start", async (_event, ctx) => clear(ctx, state));
78
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firstpick/pi-extension-todo-progress",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Aggressive automatic todo progress widget for multi-goal prompts in Pi.",
5
5
  "license": "MIT",
6
6
  "keywords": [