@firstpick/pi-extension-todo-progress 0.1.0 → 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.
- package/README.md +32 -6
- package/index.ts +48 -9
- package/package.json +18 -4
package/README.md
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
# @firstpick/pi-extension-todo-progress
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Auto todo/progress tracking for multi-goal prompts.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Auto-creates todos when prompts appear multi-step.
|
|
8
|
+
- Keeps a progress widget visible until completion.
|
|
9
|
+
- Uses explicit status markers: `[ ]` not started, `[-]` partial, `[x]` done.
|
|
10
|
+
- Shows up to 5 rows.
|
|
11
|
+
- Supports mouse wheel scrolling in custom UI.
|
|
12
|
+
- Supports hiding completed list.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pi install npm:@firstpick/pi-extension-todo-progress
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
No required configuration.
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
None.
|
|
27
|
+
|
|
28
|
+
## Shortcuts
|
|
29
|
+
|
|
30
|
+
- `Ctrl+Alt+X` — hide completed list.
|
|
31
|
+
- `Ctrl+Alt+J` / `Ctrl+Alt+K` — scroll todo list down/up.
|
|
32
|
+
|
|
33
|
+
## Tools
|
|
34
|
+
|
|
35
|
+
None.
|
package/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
type
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,13 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firstpick/pi-extension-todo-progress",
|
|
3
|
-
"version": "0.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
|
-
"keywords": [
|
|
7
|
-
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi",
|
|
9
|
+
"extension",
|
|
10
|
+
"todo",
|
|
11
|
+
"progress"
|
|
12
|
+
],
|
|
13
|
+
"pi": {
|
|
14
|
+
"extensions": [
|
|
15
|
+
"./index.ts"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
8
18
|
"peerDependencies": {
|
|
9
19
|
"@mariozechner/pi-coding-agent": "*",
|
|
10
20
|
"@mariozechner/pi-tui": "*"
|
|
11
21
|
},
|
|
12
|
-
"files": [
|
|
22
|
+
"files": [
|
|
23
|
+
"index.ts",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
]
|
|
13
27
|
}
|