@diegopetrucci/pi-extensions 0.1.74 → 0.1.78
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/LICENSE +2 -0
- package/README.md +17 -12
- package/extensions/agent-workflow-audit/README.md +1 -1
- package/extensions/agent-workflow-audit/index.ts +1631 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +846 -768
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/index.html +3 -3
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +6 -6
- package/extensions/code-reviewer/index.ts +1825 -1373
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +50 -52
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/README.md +9 -9
- package/extensions/context-cap/index.ts +313 -305
- package/extensions/context-cap/package.json +2 -2
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/README.md +4 -3
- package/extensions/contrarian/index.ts +1863 -1737
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +37 -41
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +11 -43
- package/extensions/dynamic-context-pruning/index.ts +2131 -2105
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +52 -14
- package/extensions/fast/index.ts +513 -317
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +87 -103
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/README.md +1 -1
- package/extensions/librarian/index.ts +1877 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/README.md +5 -5
- package/extensions/minimal-footer/index.ts +987 -1012
- package/extensions/minimal-footer/minimal-footer.example.json +1 -1
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/README.md +4 -3
- package/extensions/oracle/index.ts +1740 -1627
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +3 -0
- package/extensions/permission-gate/index.ts +785 -612
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/README.md +30 -19
- package/extensions/quiet-tools/index.ts +376 -291
- package/extensions/quiet-tools/package.json +2 -2
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +280 -280
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/README.md +6 -4
- package/extensions/triage-comments/index.ts +2620 -1888
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +9 -6
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
package/extensions/todo/index.ts
CHANGED
|
@@ -1,301 +1,301 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Todo Extension - Demonstrates state management via session entries
|
|
3
|
-
*
|
|
4
|
-
* This extension:
|
|
5
|
-
* - Registers a `todo` tool for the LLM to manage todos
|
|
6
|
-
* - Registers a `/todos` command for users to view the list
|
|
7
|
-
*
|
|
8
|
-
* State is stored in tool result details (not external files), which allows
|
|
9
|
-
* proper branching - when you branch, the todo state is automatically
|
|
10
|
-
* correct for that point in history.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
14
2
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
15
3
|
import { matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
16
4
|
import { Type } from "typebox";
|
|
17
5
|
|
|
18
6
|
interface Todo {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
id: number;
|
|
8
|
+
text: string;
|
|
9
|
+
done: boolean;
|
|
22
10
|
}
|
|
23
11
|
|
|
24
12
|
interface TodoDetails {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
action: "list" | "add" | "toggle" | "clear";
|
|
14
|
+
todos: Todo[];
|
|
15
|
+
nextId: number;
|
|
16
|
+
error?: string;
|
|
29
17
|
}
|
|
30
18
|
|
|
31
19
|
const cloneTodos = (items: Todo[]): Todo[] => items.map((todo) => ({ ...todo }));
|
|
32
20
|
|
|
33
21
|
const TodoParams = Type.Object({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
action: StringEnum(["list", "add", "toggle", "clear"] as const),
|
|
23
|
+
text: Type.Optional(Type.String({ description: "Todo text (for add)" })),
|
|
24
|
+
id: Type.Optional(Type.Number({ description: "Todo ID (for toggle)" })),
|
|
37
25
|
});
|
|
38
26
|
|
|
39
|
-
/**
|
|
40
|
-
* UI component for the /todos command
|
|
41
|
-
*/
|
|
42
27
|
class TodoListComponent {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
28
|
+
private todos: Todo[];
|
|
29
|
+
private theme: Theme;
|
|
30
|
+
private onClose: () => void;
|
|
31
|
+
private cachedWidth?: number;
|
|
32
|
+
private cachedLines?: string[];
|
|
33
|
+
|
|
34
|
+
constructor(todos: Todo[], theme: Theme, onClose: () => void) {
|
|
35
|
+
this.todos = todos;
|
|
36
|
+
this.theme = theme;
|
|
37
|
+
this.onClose = onClose;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
handleInput(data: string): void {
|
|
41
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
42
|
+
this.onClose();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render(width: number): string[] {
|
|
47
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
48
|
+
return this.cachedLines;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const lines: string[] = [];
|
|
52
|
+
const th = this.theme;
|
|
53
|
+
|
|
54
|
+
lines.push("");
|
|
55
|
+
const title = th.fg("accent", " Todos ");
|
|
56
|
+
const headerLine =
|
|
57
|
+
th.fg("borderMuted", "─".repeat(3)) +
|
|
58
|
+
title +
|
|
59
|
+
th.fg("borderMuted", "─".repeat(Math.max(0, width - 10)));
|
|
60
|
+
lines.push(truncateToWidth(headerLine, width));
|
|
61
|
+
lines.push("");
|
|
62
|
+
|
|
63
|
+
if (this.todos.length === 0) {
|
|
64
|
+
lines.push(
|
|
65
|
+
truncateToWidth(` ${th.fg("dim", "No todos yet. Ask the agent to add some!")}`, width),
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
const done = this.todos.filter((t) => t.done).length;
|
|
69
|
+
const total = this.todos.length;
|
|
70
|
+
lines.push(truncateToWidth(` ${th.fg("muted", `${done}/${total} completed`)}`, width));
|
|
71
|
+
lines.push("");
|
|
72
|
+
|
|
73
|
+
for (const todo of this.todos) {
|
|
74
|
+
const check = todo.done ? th.fg("success", "✓") : th.fg("dim", "○");
|
|
75
|
+
const id = th.fg("accent", `#${todo.id}`);
|
|
76
|
+
const text = todo.done ? th.fg("dim", todo.text) : th.fg("text", todo.text);
|
|
77
|
+
lines.push(truncateToWidth(` ${check} ${id} ${text}`, width));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width));
|
|
83
|
+
lines.push("");
|
|
84
|
+
|
|
85
|
+
this.cachedWidth = width;
|
|
86
|
+
this.cachedLines = lines;
|
|
87
|
+
return lines;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
invalidate(): void {
|
|
91
|
+
this.cachedWidth = undefined;
|
|
92
|
+
this.cachedLines = undefined;
|
|
93
|
+
}
|
|
105
94
|
}
|
|
106
95
|
|
|
107
96
|
export default function (pi: ExtensionAPI) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
97
|
+
let todos: Todo[] = [];
|
|
98
|
+
let nextId = 1;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reconstruct state from session entries.
|
|
102
|
+
* Scans tool results for this tool and applies them in order.
|
|
103
|
+
*/
|
|
104
|
+
const reconstructState = (ctx: ExtensionContext) => {
|
|
105
|
+
todos = [];
|
|
106
|
+
nextId = 1;
|
|
107
|
+
|
|
108
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
109
|
+
if (entry.type !== "message") continue;
|
|
110
|
+
const msg = entry.message;
|
|
111
|
+
if (msg.role !== "toolResult" || msg.toolName !== "todo") continue;
|
|
112
|
+
|
|
113
|
+
const details = msg.details as TodoDetails | undefined;
|
|
114
|
+
if (details) {
|
|
115
|
+
todos = cloneTodos(details.todos);
|
|
116
|
+
nextId = details.nextId;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
pi.on("session_start", async (_event, ctx) => reconstructState(ctx));
|
|
122
|
+
pi.on("session_tree", async (_event, ctx) => reconstructState(ctx));
|
|
123
|
+
|
|
124
|
+
pi.registerTool({
|
|
125
|
+
name: "todo",
|
|
126
|
+
label: "Todo",
|
|
127
|
+
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
|
|
128
|
+
parameters: TodoParams,
|
|
129
|
+
|
|
130
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
131
|
+
switch (params.action) {
|
|
132
|
+
case "list":
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: todos.length
|
|
138
|
+
? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join("\n")
|
|
139
|
+
: "No todos",
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
details: { action: "list", todos: cloneTodos(todos), nextId } as TodoDetails,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
case "add": {
|
|
146
|
+
if (!params.text) {
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: "Error: text required for add" }],
|
|
149
|
+
details: {
|
|
150
|
+
action: "add",
|
|
151
|
+
todos: cloneTodos(todos),
|
|
152
|
+
nextId,
|
|
153
|
+
error: "text required",
|
|
154
|
+
} as TodoDetails,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const newTodo: Todo = { id: nextId++, text: params.text, done: false };
|
|
158
|
+
todos = [...todos, newTodo];
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: "text", text: `Added todo #${newTodo.id}: ${newTodo.text}` }],
|
|
161
|
+
details: { action: "add", todos: cloneTodos(todos), nextId } as TodoDetails,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case "toggle": {
|
|
166
|
+
if (params.id === undefined) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: "text", text: "Error: id required for toggle" }],
|
|
169
|
+
details: {
|
|
170
|
+
action: "toggle",
|
|
171
|
+
todos: cloneTodos(todos),
|
|
172
|
+
nextId,
|
|
173
|
+
error: "id required",
|
|
174
|
+
} as TodoDetails,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const targetId = params.id;
|
|
178
|
+
const todo = todos.find((t) => t.id === targetId);
|
|
179
|
+
if (!todo) {
|
|
180
|
+
return {
|
|
181
|
+
content: [{ type: "text", text: `Todo #${targetId} not found` }],
|
|
182
|
+
details: {
|
|
183
|
+
action: "toggle",
|
|
184
|
+
todos: cloneTodos(todos),
|
|
185
|
+
nextId,
|
|
186
|
+
error: `#${targetId} not found`,
|
|
187
|
+
} as TodoDetails,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
todos = todos.map((t) => (t.id === targetId ? { ...t, done: !t.done } : t));
|
|
191
|
+
const updated = todos.find((t) => t.id === targetId)!;
|
|
192
|
+
return {
|
|
193
|
+
content: [
|
|
194
|
+
{
|
|
195
|
+
type: "text",
|
|
196
|
+
text: `Todo #${updated.id} ${updated.done ? "completed" : "uncompleted"}`,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
details: { action: "toggle", todos: cloneTodos(todos), nextId } as TodoDetails,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
case "clear": {
|
|
204
|
+
const count = todos.length;
|
|
205
|
+
todos = [];
|
|
206
|
+
nextId = 1;
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: `Cleared ${count} todos` }],
|
|
209
|
+
details: { action: "clear", todos: [], nextId: 1 } as TodoDetails,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
default:
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: `Unknown action: ${params.action}` }],
|
|
216
|
+
details: {
|
|
217
|
+
action: "list",
|
|
218
|
+
todos: cloneTodos(todos),
|
|
219
|
+
nextId,
|
|
220
|
+
error: `unknown action: ${params.action}`,
|
|
221
|
+
} as TodoDetails,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
renderCall(args, theme, _context) {
|
|
227
|
+
let text = theme.fg("toolTitle", theme.bold("todo ")) + theme.fg("muted", args.action);
|
|
228
|
+
if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`;
|
|
229
|
+
if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`;
|
|
230
|
+
return new Text(text, 0, 0);
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
renderResult(result, { expanded }, theme, _context) {
|
|
234
|
+
const details = result.details as TodoDetails | undefined;
|
|
235
|
+
if (!details) {
|
|
236
|
+
const text = result.content[0];
|
|
237
|
+
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (details.error) {
|
|
241
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const todoList = details.todos;
|
|
245
|
+
|
|
246
|
+
switch (details.action) {
|
|
247
|
+
case "list": {
|
|
248
|
+
if (todoList.length === 0) {
|
|
249
|
+
return new Text(theme.fg("dim", "No todos"), 0, 0);
|
|
250
|
+
}
|
|
251
|
+
let listText = theme.fg("muted", `${todoList.length} todo(s):`);
|
|
252
|
+
const display = expanded ? todoList : todoList.slice(0, 5);
|
|
253
|
+
for (const t of display) {
|
|
254
|
+
const check = t.done ? theme.fg("success", "✓") : theme.fg("dim", "○");
|
|
255
|
+
const itemText = t.done ? theme.fg("dim", t.text) : theme.fg("muted", t.text);
|
|
256
|
+
listText += `\n${check} ${theme.fg("accent", `#${t.id}`)} ${itemText}`;
|
|
257
|
+
}
|
|
258
|
+
if (!expanded && todoList.length > 5) {
|
|
259
|
+
listText += `\n${theme.fg("dim", `... ${todoList.length - 5} more`)}`;
|
|
260
|
+
}
|
|
261
|
+
return new Text(listText, 0, 0);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
case "add": {
|
|
265
|
+
const added = todoList[todoList.length - 1];
|
|
266
|
+
return new Text(
|
|
267
|
+
theme.fg("success", "✓ Added ") +
|
|
268
|
+
theme.fg("accent", `#${added.id}`) +
|
|
269
|
+
" " +
|
|
270
|
+
theme.fg("muted", added.text),
|
|
271
|
+
0,
|
|
272
|
+
0,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
case "toggle": {
|
|
277
|
+
const text = result.content[0];
|
|
278
|
+
const msg = text?.type === "text" ? text.text : "";
|
|
279
|
+
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case "clear":
|
|
283
|
+
return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all todos"), 0, 0);
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
pi.registerCommand("todos", {
|
|
289
|
+
description: "Show all todos on the current branch",
|
|
290
|
+
handler: async (_args, ctx) => {
|
|
291
|
+
if (ctx.mode !== "tui") {
|
|
292
|
+
ctx.ui.notify("/todos requires interactive mode", "error");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
await ctx.ui.custom<void>((_tui, theme, _kb, done) => {
|
|
297
|
+
return new TodoListComponent(todos, theme, () => done());
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
301
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# triage-comments
|
|
2
2
|
|
|
3
|
-
Adds `/triage-comments` plus a
|
|
3
|
+
Adds `/triage-comments` plus a `triage_comments` subagent tool with read-only built-in tools and configured MCP tools for evidence-based review-comment triage.
|
|
4
4
|
|
|
5
|
-
Use it when you want
|
|
5
|
+
Use it when you want `/triage-comments` to inspect selected PR feedback, classify whether each comment is valid, cite local evidence, draft review-thread responses, and propose handling options. It does **not** implement changes; after triage, the main agent should ask which handling option to take before editing anything.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -43,7 +43,7 @@ The extension registers `/triage-comments` as an interactive intake flow.
|
|
|
43
43
|
/triage-comments 123
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
- With no arguments,
|
|
46
|
+
- With no arguments, `/triage-comments` asks whether to paste feedback or fetch PR comments.
|
|
47
47
|
- `paste` opens an editor for multiline reviewer feedback, then sends one selected feedback item to the main agent.
|
|
48
48
|
- `pr` with no explicit target first tries to detect an existing PR for the current named non-`main` git branch using read-only `git` and `gh pr view` calls. If the branch is `main`, detached, outside a git repository, `gh` is unavailable or unauthenticated, or no PR is found, it falls back to the PR URL/number prompt.
|
|
49
49
|
- `pr <PR URL or number>` and a bare PR URL/number fetch that explicit PR directly, display PR review comments, PR issue comments, and review bodies with `gh` as numbered items with stable IDs, and ask whether to investigate all displayed comments or an explicit subset such as `1,3-5`.
|
|
@@ -71,7 +71,8 @@ The tool accepts selected comments and optional PR, base, diff, and caller conte
|
|
|
71
71
|
When called, it launches an isolated in-memory subagent with:
|
|
72
72
|
|
|
73
73
|
- no inherited extensions, skills, prompt templates, themes, context files, or agents files;
|
|
74
|
-
- read-only tools
|
|
74
|
+
- read-only built-in tools: `read`, `grep`, `find`, `ls`, and guarded `bash`;
|
|
75
|
+
- configured MCP tools (run without confirmation in the subagent; may have side effects; use for evidence gathering only; requires host Pi >=1.0.4; older hosts degrade to no MCP, no error); the global `mcp.json` always applies; a project `.pi/mcp.json` is loaded only when the host session has already trusted the project;
|
|
75
76
|
- a local-checkout path guard for file inspection;
|
|
76
77
|
- a bash guard that allows only direct read-only `git`, `gh`, or `pwd` invocations and blocks write/edit tools, shell pipelines, redirection, destructive git commands, and mutating `gh`/GitHub API calls;
|
|
77
78
|
- a fixed triage output format with verdicts, evidence, reasoning, suggested responses, handling options, and a list of read-only checks performed.
|
|
@@ -83,6 +84,7 @@ Verdicts are one of `valid`, `invalid`, `partially valid`, `subjective`, or `nee
|
|
|
83
84
|
- The slash command only collects/selects comments and asks the main agent to run triage.
|
|
84
85
|
- The subagent is explicitly instructed not to implement changes.
|
|
85
86
|
- Runtime guards block write tools, filesystem mutation through shell syntax, mutating git commands, and mutating GitHub CLI/API calls.
|
|
87
|
+
- Runtime guards apply to built-in tools only; configured MCP tools pass the guard, run without confirmation, and are limited only by prompt guidance.
|
|
86
88
|
- The generated prompt tells the main agent to summarize findings and ask which handling option to take before implementation.
|
|
87
89
|
- Suggested responses are drafts for the user/agent to adapt; the extension does not post them to GitHub.
|
|
88
90
|
|