@firstpick/pi-extension-todo-progress 0.1.5 → 0.1.6

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 +69 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,9 @@ Auto todo/progress tracking for multi-goal prompts.
6
6
 
7
7
  - Instructs the agent to create concise, agent-authored todos for multi-step work.
8
8
  - Tracks checklist markers from assistant messages instead of copying raw user prompt lines.
9
- - Uses explicit status markers: `[ ]` not started, `[-]` partial, `[x]` done.
9
+ - Instructs the agent to emit markdown checklist lines exactly like `- [ ] item`, `- [-] item`, or `- [x] item`.
10
+ - Also accepts bare markers like `[ ] item` as a fallback for robustness.
11
+ - Strips matched checklist lines from assistant messages after mirroring them into the widget, keeping the widget as the canonical todo view.
10
12
  - Clears the widget automatically when all items are complete.
11
13
  - Shows up to 5 rows.
12
14
  - Supports hiding the current list manually.
@@ -37,10 +39,10 @@ None.
37
39
  ## Example view
38
40
 
39
41
  ```text
40
- Todo
41
- - [x] Inspect package structure
42
- - [-] Update README examples
43
- - [ ] Run readiness checks
42
+ Todo 1/3 done, 1 partial
43
+ [x] Inspect package structure
44
+ [-] Update README examples
45
+ [ ] Run readiness checks
44
46
  ```
45
47
 
46
48
  For multi-step requests, Pi keeps a compact progress widget visible and updates it as work moves from planned to in-progress to done.
package/index.ts CHANGED
@@ -6,6 +6,8 @@ type TodoState = { visible: boolean; items: TodoItem[]; offset: number };
6
6
 
7
7
  const KEY = "todo-progress";
8
8
  const MAX_ROWS = 5;
9
+ const MAX_ITEMS = 12;
10
+ const TODO_LINE_REGEX = /^\s*(?:(?:[-*]|\d+[.)])\s*)?\[( |x|X|-)\]\s+(.+)$/;
9
11
 
10
12
  function statusLabel(status: TodoStatus): string {
11
13
  if (status === "done") return "[x]";
@@ -41,6 +43,56 @@ function render(ctx: ExtensionContext, s: TodoState) {
41
43
  ctx.ui.setWidget(KEY, lines);
42
44
  }
43
45
 
46
+ function parseTodoLine(line: string): TodoItem | undefined {
47
+ const match = TODO_LINE_REGEX.exec(line);
48
+ if (!match) return undefined;
49
+
50
+ const mark = (match[1] || " ").toLowerCase();
51
+ const label = (match[2] || "").trim().replace(/\s+/g, " ");
52
+ if (!label) return undefined;
53
+
54
+ return {
55
+ status: mark === "x" ? "done" : mark === "-" ? "partial" : "todo",
56
+ text: label,
57
+ };
58
+ }
59
+
60
+ function extractChecklist(text: string): TodoItem[] {
61
+ const checklist: TodoItem[] = [];
62
+ let inFence = false;
63
+
64
+ for (const line of text.split(/\r?\n/)) {
65
+ if (/^\s*```/.test(line)) {
66
+ inFence = !inFence;
67
+ continue;
68
+ }
69
+ if (inFence) continue;
70
+
71
+ const item = parseTodoLine(line);
72
+ if (item) checklist.push(item);
73
+ }
74
+
75
+ return checklist;
76
+ }
77
+
78
+ function stripChecklistLines(text: string): string {
79
+ let inFence = false;
80
+ const kept: string[] = [];
81
+
82
+ for (const line of text.split(/\r?\n/)) {
83
+ if (/^\s*```/.test(line)) {
84
+ inFence = !inFence;
85
+ kept.push(line);
86
+ continue;
87
+ }
88
+
89
+ if (!inFence && parseTodoLine(line)) continue;
90
+ kept.push(line);
91
+ }
92
+
93
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
94
+ }
95
+
44
96
  export default function todoProgress(pi: ExtensionAPI) {
45
97
  const state: TodoState = { visible: false, items: [], offset: 0 };
46
98
 
@@ -48,7 +100,7 @@ export default function todoProgress(pi: ExtensionAPI) {
48
100
  return {
49
101
  systemPrompt:
50
102
  event.systemPrompt +
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.",
103
+ "\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. Emit todo updates as markdown checklist lines exactly like `- [ ] item`, `- [-] item`, or `- [x] item`; do not use raw user prompt lines as todos. Update checklist markers as work changes. Todo checklists are live-turn progress only: still emit `[x]` updates when possible, but the extension will close the widget deterministically when the agent turn ends.",
52
104
  };
53
105
  });
54
106
 
@@ -60,23 +112,28 @@ export default function todoProgress(pi: ExtensionAPI) {
60
112
  pi.on("message_end", async (event, ctx) => {
61
113
  if (event.message.role !== "assistant") return;
62
114
 
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 });
72
- }
115
+ const textParts = event.message.content.filter((c: any) => c.type === "text");
116
+ const checklist = textParts.flatMap((c: any) => extractChecklist(c.text));
73
117
 
74
118
  if (checklist.length === 0) return;
75
119
 
76
- state.items = checklist.slice(0, 12);
120
+ state.items = checklist.slice(0, MAX_ITEMS);
77
121
  state.offset = Math.min(state.offset, Math.max(0, state.items.length - MAX_ROWS));
78
122
  state.visible = true;
79
123
  render(ctx, state);
124
+
125
+ return {
126
+ message: {
127
+ ...event.message,
128
+ content: event.message.content.map((c: any) => (c.type === "text" ? { ...c, text: stripChecklistLines(c.text) } : c)),
129
+ },
130
+ };
131
+ });
132
+
133
+ pi.on("agent_end", async (_event, ctx) => {
134
+ // The widget represents live progress for the active turn, not persistent task state.
135
+ // Do not leave stale partial/todo items visible if the model forgets a final update.
136
+ clear(ctx, state);
80
137
  });
81
138
 
82
139
  pi.registerShortcut("ctrl+alt+x", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firstpick/pi-extension-todo-progress",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Aggressive automatic todo progress widget for multi-goal prompts in Pi.",
5
5
  "license": "MIT",
6
6
  "keywords": [