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

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 +2 -0
  2. package/index.ts +48 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,6 +6,7 @@ Auto todo/progress tracking for multi-goal prompts.
6
6
 
7
7
  - Auto-creates todos when prompts appear multi-step.
8
8
  - Keeps a progress widget visible until completion.
9
+ - Uses explicit status markers: `[ ]` not started, `[-]` partial, `[x]` done.
9
10
  - Shows up to 5 rows.
10
11
  - Supports mouse wheel scrolling in custom UI.
11
12
  - Supports hiding completed list.
@@ -27,6 +28,7 @@ None.
27
28
  ## Shortcuts
28
29
 
29
30
  - `Ctrl+Alt+X` — hide completed 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,6 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
 
3
- type TodoItem = { text: string; done: boolean };
3
+ type TodoStatus = "todo" | "partial" | "done";
4
+ type TodoItem = { text: string; status: TodoStatus };
4
5
  type TodoState = { visible: boolean; items: TodoItem[]; offset: number; doneDismissHint: boolean };
5
6
 
6
7
  const KEY = "todo-progress";
@@ -14,12 +15,23 @@ function splitGoals(text: string): string[] {
14
15
  return parts.slice(0, 12);
15
16
  }
16
17
 
18
+ function normalize(text: string): string {
19
+ return text.toLowerCase().replace(/[`*_~]/g, "").replace(/\s+/g, " ").trim();
20
+ }
21
+
22
+ function statusLabel(status: TodoStatus): string {
23
+ if (status === "done") return "[x]";
24
+ if (status === "partial") return "[-]";
25
+ return "[ ]";
26
+ }
27
+
17
28
  function render(ctx: ExtensionContext, s: TodoState) {
18
29
  if (!ctx.hasUI || !s.visible || s.items.length === 0) return;
19
- const done = s.items.filter((i) => i.done).length;
30
+ const done = s.items.filter((i) => i.status === "done").length;
31
+ const partial = s.items.filter((i) => i.status === "partial").length;
20
32
  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}`);
33
+ const lines = [ctx.ui.theme.fg("accent", `Todo ${done}/${s.items.length} done${partial ? `, ${partial} partial` : ""}`)];
34
+ for (const item of top) lines.push(`${statusLabel(item.status)} ${item.text}`);
23
35
  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
36
  if (done === s.items.length && s.doneDismissHint) lines.push(ctx.ui.theme.fg("success", "Done — Ctrl+Alt+X to dismiss"));
25
37
  ctx.ui.setWidget(KEY, lines);
@@ -32,7 +44,7 @@ export default function todoProgress(pi: ExtensionAPI) {
32
44
  return {
33
45
  systemPrompt:
34
46
  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.",
47
+ "\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. Use explicit statuses: [ ] not started, [-] partial/in progress, [x] complete. Update checklist markers immediately when work status changes, including partial progress.",
36
48
  };
37
49
  });
38
50
 
@@ -40,7 +52,7 @@ export default function todoProgress(pi: ExtensionAPI) {
40
52
  if (event.text.startsWith("/")) return { action: "continue" as const };
41
53
  const goals = splitGoals(event.text);
42
54
  if (goals.length >= 2) {
43
- state.items = goals.map((g) => ({ text: g, done: false }));
55
+ state.items = goals.map((g) => ({ text: g, status: "todo" }));
44
56
  state.offset = 0;
45
57
  state.visible = true;
46
58
  render(ctx, state);
@@ -50,17 +62,44 @@ export default function todoProgress(pi: ExtensionAPI) {
50
62
 
51
63
  pi.on("message_end", async (event, ctx) => {
52
64
  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();
65
+
66
+ const text = event.message.content.filter((c: any) => c.type === "text").map((c: any) => c.text).join("\n");
67
+ const normalizedText = normalize(text);
68
+
69
+ const checklistRegex = /^\s*(?:[-*]|\d+[.)])\s*\[( |x|X|-)\]\s+(.+)$/gm;
70
+ const checklist: Array<{ status: TodoStatus; text: string }> = [];
71
+ for (const match of text.matchAll(checklistRegex)) {
72
+ const mark = (match[1] || " ").toLowerCase();
73
+ const label = normalize(match[2] || "");
74
+ const status: TodoStatus = mark === "x" ? "done" : mark === "-" ? "partial" : "todo";
75
+ checklist.push({ status, text: label });
76
+ }
77
+
54
78
  for (const item of state.items) {
55
- if (!item.done && text.includes(item.text.toLowerCase().slice(0, 24))) item.done = true;
79
+ const itemNorm = normalize(item.text);
80
+ const exact = checklist.find((c) => c.text === itemNorm);
81
+ const fuzzy = checklist.find((c) => c.text.includes(itemNorm) || itemNorm.includes(c.text));
82
+ const candidate = exact ?? fuzzy;
83
+
84
+ if (candidate) {
85
+ item.status = candidate.status;
86
+ continue;
87
+ }
88
+
89
+ // Fallback heuristic for free-form summaries without explicit checklist markers.
90
+ if (item.status !== "done" && normalizedText.includes(itemNorm.slice(0, 24))) {
91
+ if (/\b(done|completed|finished)\b/.test(normalizedText)) item.status = "done";
92
+ else if (/\b(partial|partly|in progress|started|wip)\b/.test(normalizedText)) item.status = "partial";
93
+ }
56
94
  }
95
+
57
96
  render(ctx, state);
58
97
  });
59
98
 
60
99
  pi.registerShortcut("ctrl+alt+x", {
61
100
  description: "Dismiss completed todo widget",
62
101
  handler: async (ctx) => {
63
- const done = state.items.length > 0 && state.items.every((i) => i.done);
102
+ const done = state.items.length > 0 && state.items.every((i) => i.status === "done");
64
103
  if (!done) {
65
104
  ctx.ui.notify("Todo not complete yet", "warning");
66
105
  return;
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.2",
4
4
  "description": "Aggressive automatic todo progress widget for multi-goal prompts in Pi.",
5
5
  "license": "MIT",
6
6
  "keywords": [