pi-squad 0.1.0
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 +21 -0
- package/README.md +244 -0
- package/package.json +30 -0
- package/src/agent-pool.ts +445 -0
- package/src/agents/_defaults/architect.json +9 -0
- package/src/agents/_defaults/backend.json +9 -0
- package/src/agents/_defaults/debugger.json +9 -0
- package/src/agents/_defaults/devops.json +9 -0
- package/src/agents/_defaults/docs.json +9 -0
- package/src/agents/_defaults/frontend.json +9 -0
- package/src/agents/_defaults/fullstack.json +9 -0
- package/src/agents/_defaults/planner.json +9 -0
- package/src/agents/_defaults/qa.json +9 -0
- package/src/agents/_defaults/researcher.json +9 -0
- package/src/agents/_defaults/security.json +9 -0
- package/src/index.ts +1121 -0
- package/src/monitor.ts +204 -0
- package/src/panel/message-view.ts +232 -0
- package/src/panel/squad-panel.ts +383 -0
- package/src/panel/task-list.ts +264 -0
- package/src/planner.ts +275 -0
- package/src/protocol.ts +265 -0
- package/src/router.ts +207 -0
- package/src/scheduler.ts +732 -0
- package/src/skills/collaboration/SKILL.md +39 -0
- package/src/skills/squad-protocol/SKILL.md +65 -0
- package/src/skills/verification/SKILL.md +64 -0
- package/src/store.ts +458 -0
- package/src/supervisor.ts +143 -0
- package/src/types.ts +210 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-pool.ts — RpcClient lifecycle management for squad agents.
|
|
3
|
+
*
|
|
4
|
+
* Spawns pi processes in RPC mode, subscribes to events,
|
|
5
|
+
* provides steer/abort/kill, tracks activity.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import type { AgentDef, AgentActivity, Task, TaskMessage } from "./types.js";
|
|
13
|
+
import { buildAgentSystemPrompt, type ProtocolBuildOptions } from "./protocol.js";
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface AgentProcess {
|
|
20
|
+
taskId: string;
|
|
21
|
+
agentName: string;
|
|
22
|
+
process: ChildProcess;
|
|
23
|
+
activity: AgentActivity;
|
|
24
|
+
/** Queued messages for this agent (received while stopped, consumed on spawn) */
|
|
25
|
+
pendingMessages: TaskMessage[];
|
|
26
|
+
/** Abort controller for cleanup */
|
|
27
|
+
aborted: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AgentEventType =
|
|
31
|
+
| "message_end"
|
|
32
|
+
| "tool_execution_start"
|
|
33
|
+
| "tool_execution_end"
|
|
34
|
+
| "turn_end"
|
|
35
|
+
| "agent_end"
|
|
36
|
+
| "error";
|
|
37
|
+
|
|
38
|
+
export interface AgentEvent {
|
|
39
|
+
type: AgentEventType;
|
|
40
|
+
taskId: string;
|
|
41
|
+
agentName: string;
|
|
42
|
+
data: any;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type AgentEventListener = (event: AgentEvent) => void;
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// RPC JSON Line Protocol
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
function serializeJsonLine(obj: unknown): string {
|
|
52
|
+
return JSON.stringify(obj) + "\n";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function attachLineReader(
|
|
56
|
+
stream: NodeJS.ReadableStream,
|
|
57
|
+
onLine: (line: string) => void,
|
|
58
|
+
): () => void {
|
|
59
|
+
let buffer = "";
|
|
60
|
+
const onData = (chunk: Buffer) => {
|
|
61
|
+
buffer += chunk.toString();
|
|
62
|
+
const lines = buffer.split("\n");
|
|
63
|
+
buffer = lines.pop() || "";
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
if (line.trim()) onLine(line);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
stream.on("data", onData);
|
|
69
|
+
return () => stream.removeListener("data", onData);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Agent Pool
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
export class AgentPool {
|
|
77
|
+
private agents = new Map<string, AgentProcess>();
|
|
78
|
+
private listeners: AgentEventListener[] = [];
|
|
79
|
+
private messageQueues = new Map<string, TaskMessage[]>();
|
|
80
|
+
|
|
81
|
+
/** Subscribe to agent events */
|
|
82
|
+
onEvent(listener: AgentEventListener): () => void {
|
|
83
|
+
this.listeners.push(listener);
|
|
84
|
+
return () => {
|
|
85
|
+
const idx = this.listeners.indexOf(listener);
|
|
86
|
+
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private emit(event: AgentEvent): void {
|
|
91
|
+
for (const listener of this.listeners) {
|
|
92
|
+
try {
|
|
93
|
+
listener(event);
|
|
94
|
+
} catch {
|
|
95
|
+
/* ignore listener errors */
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Check if an agent is currently running */
|
|
101
|
+
isRunning(taskId: string): boolean {
|
|
102
|
+
const agent = this.agents.get(taskId);
|
|
103
|
+
return agent !== undefined && !agent.aborted && agent.process.exitCode === null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Get the task ID a named agent is working on */
|
|
107
|
+
getTaskIdForAgent(agentName: string): string | undefined {
|
|
108
|
+
for (const [taskId, agent] of this.agents) {
|
|
109
|
+
if (agent.agentName === agentName && !agent.aborted) return taskId;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Get activity tracker for a task */
|
|
115
|
+
getActivity(taskId: string): AgentActivity | undefined {
|
|
116
|
+
return this.agents.get(taskId)?.activity;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Get all running agent names */
|
|
120
|
+
getRunningAgents(): string[] {
|
|
121
|
+
return Array.from(this.agents.values())
|
|
122
|
+
.filter((a) => !a.aborted && a.process.exitCode === null)
|
|
123
|
+
.map((a) => a.agentName);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Queue a message for an agent (delivered on next spawn or via steer if running) */
|
|
127
|
+
queueMessage(agentName: string, message: TaskMessage): void {
|
|
128
|
+
const queue = this.messageQueues.get(agentName) || [];
|
|
129
|
+
queue.push(message);
|
|
130
|
+
this.messageQueues.set(agentName, queue);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Consume queued messages for an agent */
|
|
134
|
+
consumeQueue(agentName: string): TaskMessage[] {
|
|
135
|
+
const queue = this.messageQueues.get(agentName) || [];
|
|
136
|
+
this.messageQueues.delete(agentName);
|
|
137
|
+
return queue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Spawn a pi process in RPC mode for a task.
|
|
142
|
+
*/
|
|
143
|
+
async spawn(options: {
|
|
144
|
+
taskId: string;
|
|
145
|
+
agentDef: AgentDef;
|
|
146
|
+
protocolOptions: ProtocolBuildOptions;
|
|
147
|
+
cwd: string;
|
|
148
|
+
skillPaths: string[];
|
|
149
|
+
}): Promise<AgentProcess> {
|
|
150
|
+
const { taskId, agentDef, protocolOptions, cwd, skillPaths } = options;
|
|
151
|
+
|
|
152
|
+
// Kill existing process for this task if any
|
|
153
|
+
if (this.agents.has(taskId)) {
|
|
154
|
+
await this.kill(taskId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Write system prompt to temp file
|
|
158
|
+
const systemPrompt = buildAgentSystemPrompt(protocolOptions);
|
|
159
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-squad-"));
|
|
160
|
+
const promptFile = path.join(tmpDir, `${agentDef.name}-prompt.md`);
|
|
161
|
+
fs.writeFileSync(promptFile, systemPrompt, "utf-8");
|
|
162
|
+
|
|
163
|
+
// Build pi CLI args
|
|
164
|
+
const args = buildPiArgs(agentDef, promptFile, skillPaths);
|
|
165
|
+
|
|
166
|
+
// Spawn pi process — set env var to prevent recursive squad extension loading
|
|
167
|
+
const invocation = getPiInvocation(["--mode", "rpc", ...args]);
|
|
168
|
+
const proc = spawn(invocation.command, invocation.args, {
|
|
169
|
+
cwd,
|
|
170
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
171
|
+
env: { ...process.env, PI_SQUAD_CHILD: "1" },
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const activity: AgentActivity = {
|
|
175
|
+
taskId,
|
|
176
|
+
agentName: agentDef.name,
|
|
177
|
+
lastOutputTs: Date.now(),
|
|
178
|
+
startedAt: Date.now(),
|
|
179
|
+
turnCount: 0,
|
|
180
|
+
recentToolCalls: [],
|
|
181
|
+
modifiedFiles: new Set(),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const agentProc: AgentProcess = {
|
|
185
|
+
taskId,
|
|
186
|
+
agentName: agentDef.name,
|
|
187
|
+
process: proc,
|
|
188
|
+
activity,
|
|
189
|
+
pendingMessages: this.consumeQueue(agentDef.name),
|
|
190
|
+
aborted: false,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
this.agents.set(taskId, agentProc);
|
|
194
|
+
|
|
195
|
+
// Read stdout events
|
|
196
|
+
let stderr = "";
|
|
197
|
+
proc.stderr?.on("data", (d) => {
|
|
198
|
+
stderr += d.toString();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
attachLineReader(proc.stdout!, (line) => {
|
|
202
|
+
try {
|
|
203
|
+
const event = JSON.parse(line);
|
|
204
|
+
this.handleRpcEvent(agentProc, event);
|
|
205
|
+
} catch {
|
|
206
|
+
/* skip non-JSON lines */
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
let agentEndEmitted = false;
|
|
211
|
+
proc.on("exit", (code) => {
|
|
212
|
+
// Only emit if we haven't already emitted via RPC agent_end event
|
|
213
|
+
if (!agentEndEmitted) {
|
|
214
|
+
agentEndEmitted = true;
|
|
215
|
+
this.emit({
|
|
216
|
+
type: "agent_end",
|
|
217
|
+
taskId,
|
|
218
|
+
agentName: agentDef.name,
|
|
219
|
+
data: { exitCode: code, stderr: stderr.slice(-2000) },
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Cleanup temp files — delay to avoid race with last stdout reads
|
|
223
|
+
setTimeout(() => {
|
|
224
|
+
try { fs.unlinkSync(promptFile); } catch { /* ignore */ }
|
|
225
|
+
try { fs.rmdirSync(tmpDir); } catch { /* ignore */ }
|
|
226
|
+
}, 500);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Expose the guard so handleRpcEvent can set it
|
|
230
|
+
(agentProc as any)._agentEndEmitted = () => { agentEndEmitted = true; };
|
|
231
|
+
|
|
232
|
+
// Wait for process to initialize — pi needs time to load extensions, models, etc.
|
|
233
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
234
|
+
|
|
235
|
+
if (proc.exitCode !== null) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Agent ${agentDef.name} exited immediately (code ${proc.exitCode}). Stderr: ${stderr}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Send initial prompt
|
|
242
|
+
const taskPrompt = `Your task: ${protocolOptions.task.title}\n\n${protocolOptions.task.description || ""}`;
|
|
243
|
+
this.sendRpcCommand(proc, { type: "prompt", message: taskPrompt });
|
|
244
|
+
|
|
245
|
+
return agentProc;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Inject a steering message into a running agent */
|
|
249
|
+
async steer(taskId: string, message: string): Promise<boolean> {
|
|
250
|
+
const agent = this.agents.get(taskId);
|
|
251
|
+
if (!agent || agent.aborted || agent.process.exitCode !== null) return false;
|
|
252
|
+
this.sendRpcCommand(agent.process, { type: "steer", message });
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Queue a follow-up message for after the current turn */
|
|
257
|
+
async followUp(taskId: string, message: string): Promise<boolean> {
|
|
258
|
+
const agent = this.agents.get(taskId);
|
|
259
|
+
if (!agent || agent.aborted || agent.process.exitCode !== null) return false;
|
|
260
|
+
this.sendRpcCommand(agent.process, { type: "follow_up", message });
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Abort the current operation */
|
|
265
|
+
async abort(taskId: string): Promise<void> {
|
|
266
|
+
const agent = this.agents.get(taskId);
|
|
267
|
+
if (!agent || agent.aborted) return;
|
|
268
|
+
try {
|
|
269
|
+
this.sendRpcCommand(agent.process, { type: "abort" });
|
|
270
|
+
} catch {
|
|
271
|
+
/* ignore */
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Kill agent process */
|
|
276
|
+
async kill(taskId: string): Promise<void> {
|
|
277
|
+
const agent = this.agents.get(taskId);
|
|
278
|
+
if (!agent) return;
|
|
279
|
+
agent.aborted = true;
|
|
280
|
+
agent.process.kill("SIGTERM");
|
|
281
|
+
// Force kill after 5s
|
|
282
|
+
const timer = setTimeout(() => {
|
|
283
|
+
if (!agent.process.killed) agent.process.kill("SIGKILL");
|
|
284
|
+
}, 5000);
|
|
285
|
+
await new Promise<void>((resolve) => {
|
|
286
|
+
agent.process.on("exit", () => {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
resolve();
|
|
289
|
+
});
|
|
290
|
+
// If already exited
|
|
291
|
+
if (agent.process.exitCode !== null) {
|
|
292
|
+
clearTimeout(timer);
|
|
293
|
+
resolve();
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
this.agents.delete(taskId);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Kill all running agents */
|
|
300
|
+
async killAll(): Promise<void> {
|
|
301
|
+
const kills = Array.from(this.agents.keys()).map((taskId) => this.kill(taskId));
|
|
302
|
+
await Promise.all(kills);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Wait for an agent to finish */
|
|
306
|
+
async waitForCompletion(taskId: string): Promise<number> {
|
|
307
|
+
const agent = this.agents.get(taskId);
|
|
308
|
+
if (!agent) return -1;
|
|
309
|
+
if (agent.process.exitCode !== null) return agent.process.exitCode;
|
|
310
|
+
return new Promise<number>((resolve) => {
|
|
311
|
+
agent.process.on("exit", (code) => resolve(code ?? 1));
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// =========================================================================
|
|
316
|
+
// Internal
|
|
317
|
+
// =========================================================================
|
|
318
|
+
|
|
319
|
+
private sendRpcCommand(proc: ChildProcess, command: Record<string, unknown>): void {
|
|
320
|
+
if (!proc.stdin || proc.stdin.destroyed) return;
|
|
321
|
+
proc.stdin.write(serializeJsonLine(command));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private handleRpcEvent(agent: AgentProcess, event: any): void {
|
|
325
|
+
agent.activity.lastOutputTs = Date.now();
|
|
326
|
+
|
|
327
|
+
// Parse event type and emit
|
|
328
|
+
if (event.type === "message_end" && event.message) {
|
|
329
|
+
agent.activity.turnCount++;
|
|
330
|
+
this.emit({
|
|
331
|
+
type: "message_end",
|
|
332
|
+
taskId: agent.taskId,
|
|
333
|
+
agentName: agent.agentName,
|
|
334
|
+
data: event.message,
|
|
335
|
+
});
|
|
336
|
+
} else if (event.type === "tool_execution_start") {
|
|
337
|
+
const sig = `${event.toolName}:${JSON.stringify(event.args || {}).slice(0, 100)}`;
|
|
338
|
+
agent.activity.recentToolCalls.push(sig);
|
|
339
|
+
if (agent.activity.recentToolCalls.length > 20) {
|
|
340
|
+
agent.activity.recentToolCalls.shift();
|
|
341
|
+
}
|
|
342
|
+
this.emit({
|
|
343
|
+
type: "tool_execution_start",
|
|
344
|
+
taskId: agent.taskId,
|
|
345
|
+
agentName: agent.agentName,
|
|
346
|
+
data: event,
|
|
347
|
+
});
|
|
348
|
+
} else if (event.type === "tool_execution_end") {
|
|
349
|
+
// Track modified files
|
|
350
|
+
if (event.toolName === "write" || event.toolName === "edit") {
|
|
351
|
+
const filePath = event.args?.path || event.args?.file_path;
|
|
352
|
+
if (filePath) agent.activity.modifiedFiles.add(filePath);
|
|
353
|
+
}
|
|
354
|
+
this.emit({
|
|
355
|
+
type: "tool_execution_end",
|
|
356
|
+
taskId: agent.taskId,
|
|
357
|
+
agentName: agent.agentName,
|
|
358
|
+
data: event,
|
|
359
|
+
});
|
|
360
|
+
} else if (event.type === "tool_result_end") {
|
|
361
|
+
this.emit({
|
|
362
|
+
type: "tool_execution_end",
|
|
363
|
+
taskId: agent.taskId,
|
|
364
|
+
agentName: agent.agentName,
|
|
365
|
+
data: event,
|
|
366
|
+
});
|
|
367
|
+
} else if (event.type === "agent_end") {
|
|
368
|
+
// Pi RPC mode emits agent_end when the agent loop finishes.
|
|
369
|
+
// The RPC process stays alive waiting for more commands,
|
|
370
|
+
// so we need to explicitly kill it and emit our own agent_end.
|
|
371
|
+
console.error(`[squad-pool] agent_end from RPC: ${agent.agentName} (task: ${agent.taskId})`);
|
|
372
|
+
// Mark the guard to prevent double-emit from proc.on("exit")
|
|
373
|
+
const guardFn = (agent as any)._agentEndEmitted;
|
|
374
|
+
if (guardFn) guardFn();
|
|
375
|
+
// Remove from agents map BEFORE emitting so getRunningAgents() doesn't count it
|
|
376
|
+
this.agents.delete(agent.taskId);
|
|
377
|
+
this.emit({
|
|
378
|
+
type: "agent_end",
|
|
379
|
+
taskId: agent.taskId,
|
|
380
|
+
agentName: agent.agentName,
|
|
381
|
+
data: { exitCode: 0, stderr: "" },
|
|
382
|
+
});
|
|
383
|
+
// Kill the RPC process since the agent's work is done
|
|
384
|
+
agent.process.kill("SIGTERM");
|
|
385
|
+
setTimeout(() => {
|
|
386
|
+
if (!agent.process.killed) agent.process.kill("SIGKILL");
|
|
387
|
+
}, 3000);
|
|
388
|
+
} else if (event.type === "error") {
|
|
389
|
+
this.emit({
|
|
390
|
+
type: "error",
|
|
391
|
+
taskId: agent.taskId,
|
|
392
|
+
agentName: agent.agentName,
|
|
393
|
+
data: event,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Helpers
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
function buildPiArgs(agentDef: AgentDef, promptFile: string, skillPaths: string[]): string[] {
|
|
404
|
+
const args: string[] = ["--no-session", "--append-system-prompt", promptFile];
|
|
405
|
+
|
|
406
|
+
if (agentDef.model) {
|
|
407
|
+
args.push("--model", agentDef.model);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (agentDef.tools && agentDef.tools.length > 0) {
|
|
411
|
+
args.push("--tools", agentDef.tools.join(","));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
for (const skillPath of skillPaths) {
|
|
415
|
+
args.push("--skill", skillPath);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return args;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function getPiInvocation(args: string[]): { command: string; args: string[] } {
|
|
422
|
+
// Try to find the pi CLI binary in PATH
|
|
423
|
+
// This is the most reliable approach — works regardless of how the parent was invoked
|
|
424
|
+
const piPaths = [
|
|
425
|
+
// Check PATH
|
|
426
|
+
"pi",
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
// Check if process.argv[1] is a .js file we can re-invoke
|
|
430
|
+
const currentScript = process.argv[1];
|
|
431
|
+
if (currentScript && currentScript.endsWith(".js") && fs.existsSync(currentScript)) {
|
|
432
|
+
return { command: process.execPath, args: [currentScript, ...args] };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Check if process.execPath is pi itself (not node/bun)
|
|
436
|
+
const execName = path.basename(process.execPath).toLowerCase();
|
|
437
|
+
const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
|
|
438
|
+
if (!isGenericRuntime) {
|
|
439
|
+
// execPath is the pi binary
|
|
440
|
+
return { command: process.execPath, args };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Fall back to pi in PATH
|
|
444
|
+
return { command: "pi", args };
|
|
445
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "architect",
|
|
3
|
+
"role": "Software Architect",
|
|
4
|
+
"description": "System design, architecture decisions, tech stack evaluation, performance optimization, design patterns.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["architecture", "design", "performance", "patterns", "system-design"],
|
|
8
|
+
"prompt": "You are a software architect. You design systems, evaluate technology choices, and ensure codebases are well-structured.\n\n## Principles\n- Understand the existing architecture before proposing changes\n- Prefer simple solutions over clever ones\n- Consider maintainability, scalability, and team capacity\n- Document architectural decisions with rationale\n- Identify risks and trade-offs explicitly"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backend",
|
|
3
|
+
"role": "Backend Engineer",
|
|
4
|
+
"description": "APIs, databases, server-side logic, middleware, authentication, performance optimization.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["api", "server", "database", "middleware", "auth", "backend"],
|
|
8
|
+
"prompt": "You are a backend engineer. You build robust APIs, design database schemas, write server-side logic, and optimize performance.\n\n## Principles\n- Validate all inputs at API boundaries\n- Use migrations for schema changes — never ALTER in application code\n- Handle errors explicitly — never swallow exceptions\n- Write tests for critical paths\n- Never store secrets in code or logs\n\n## Patterns\n- RESTful conventions for APIs\n- Consistent error responses: { error: string, code?: string }\n- Foreign keys with ON DELETE CASCADE where appropriate\n- Index frequently queried columns\n- Rate-limit public endpoints"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "debugger",
|
|
3
|
+
"role": "Debugger & Root Cause Analyst",
|
|
4
|
+
"description": "Systematic debugging, root cause analysis, bug reproduction, issue investigation, fix verification.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["debugging", "investigation", "root-cause", "bugs", "fix"],
|
|
8
|
+
"prompt": "You are a debugger and root cause analyst. You find the real cause of bugs, not just the symptoms.\n\n## The Four Phases\n\n1. **Investigate** — Read error messages completely. Reproduce consistently. Check recent changes (git log). Trace data flow backward from the error.\n\n2. **Analyze** — Find working examples of similar code. Compare against references. Identify every difference, however small.\n\n3. **Hypothesize** — Form ONE testable theory. Make the smallest possible change. Verify before continuing.\n\n4. **Fix** — Create a failing test first. Implement a single fix addressing the root cause. Verify fix AND no regressions.\n\n## Red Flags — Return to Phase 1\n- 'Quick fix for now, investigate later'\n- 'Just try changing X and see'\n- Changing multiple things at once\n- 3+ failed fix attempts — question the architecture, escalate"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "devops",
|
|
3
|
+
"role": "DevOps Engineer",
|
|
4
|
+
"description": "CI/CD pipelines, infrastructure, deployment, Docker, monitoring, cloud services.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["devops", "ci-cd", "docker", "deployment", "infrastructure", "monitoring"],
|
|
8
|
+
"prompt": "You are a DevOps engineer. You build reliable pipelines, manage infrastructure, and ensure systems stay healthy.\n\n## Principles\n- Infrastructure as code — no manual setup\n- Automate everything that runs more than twice\n- Build pipelines that fail fast and report clearly\n- Use health checks and monitoring from day one\n- Keep secrets out of repos — use environment variables or secret managers\n- Document how to deploy, rollback, and troubleshoot"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docs",
|
|
3
|
+
"role": "Technical Writer",
|
|
4
|
+
"description": "Documentation, README files, API docs, code comments, architecture docs, user guides.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["documentation", "writing", "readme", "api-docs", "comments"],
|
|
8
|
+
"prompt": "You are a technical writer. You create clear, accurate, useful documentation.\n\n## Principles\n- Read the code before documenting it — don't guess\n- Lead with the most important information\n- Use concrete examples, not abstract descriptions\n- Keep it concise — every sentence should earn its place\n- Match the project's existing documentation style\n- Include setup instructions, usage examples, and common pitfalls"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frontend",
|
|
3
|
+
"role": "Frontend Engineer",
|
|
4
|
+
"description": "UI/UX implementation, React, component architecture, responsive design, accessibility, CSS/Tailwind.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["frontend", "react", "ui", "css", "tailwind", "responsive", "accessibility"],
|
|
8
|
+
"prompt": "You are a frontend engineer. You build accessible, responsive, production-quality user interfaces.\n\n## Principles\n- Use semantic HTML (button, nav, main, section)\n- Follow the project's existing design system and component patterns\n- Handle all states: loading, error, empty, overflow\n- Ensure keyboard navigation works\n- Test at mobile (375px), tablet (768px), and desktop (1440px) widths\n\n## Patterns\n- Functional components with hooks\n- Consistent spacing and typography from the design system\n- Transitions: 150-300ms for interactions\n- All interactive elements need cursor-pointer and hover states\n- Avoid layout shift on state changes"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fullstack",
|
|
3
|
+
"role": "Fullstack Developer",
|
|
4
|
+
"description": "General-purpose coding agent. Handles any task that doesn't require deep specialization. Frontend, backend, scripting, configuration.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["general", "coding", "implementation", "scripting", "config"],
|
|
8
|
+
"prompt": "You are a fullstack developer. You handle any coding task competently — frontend, backend, scripting, configuration, tooling.\n\n## Principles\n- Read existing code before writing new code\n- Follow the project's existing patterns and conventions\n- Write clean, tested, documented code\n- Ask for help on tasks that need deep specialist knowledge"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "planner",
|
|
3
|
+
"role": "Project Planner",
|
|
4
|
+
"description": "Analyzes codebases and breaks goals into concrete tasks with dependencies. Assigns tasks to the right specialist agents.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["planning", "architecture", "coordination", "breakdown"],
|
|
8
|
+
"prompt": "You are a project planner. You analyze codebases and break goals into concrete, implementable tasks.\n\n## How You Work\n\n1. Read the project structure and key files to understand the tech stack\n2. Identify what needs to be built or changed\n3. Break the work into minimal but complete tasks\n4. Assign each task to the most appropriate specialist\n5. Define dependencies — what must complete before what\n\n## Principles\n\n- Keep plans minimal — 3-7 tasks for most goals\n- Each task should be completable by one agent in one session\n- Be specific in descriptions — agents should know exactly what to build\n- Include a QA/verification task for user-facing changes\n- Don't create tasks for things that already exist"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qa",
|
|
3
|
+
"role": "QA Engineer",
|
|
4
|
+
"description": "Testing, verification, integration testing, edge cases, regression testing, quality assurance.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["testing", "qa", "verification", "integration", "e2e"],
|
|
8
|
+
"prompt": "You are a QA engineer. Your job is to find problems, not confirm things work.\n\n## Principles\n- Actually RUN the code — don't just read it and say 'looks correct'\n- Paste REAL output as evidence for every claim\n- Try to BREAK it — empty inputs, long strings, special characters, edge cases\n- Check at multiple viewport sizes for web UI\n- Compare against acceptance criteria LINE BY LINE\n\n## Report Format\n- Environment details\n- Test execution with evidence (commands + actual output)\n- Edge cases tested\n- Console/error output\n- Verdict: PASS / FAIL / PASS WITH ISSUES\n\n## Minimum evidence per report\n- At least 3 pieces of concrete evidence (test output, screenshots, commands)\n- At least 1 edge case that pushed boundaries\n- Actual command output pasted, not summarized"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "researcher",
|
|
3
|
+
"role": "Research Analyst",
|
|
4
|
+
"description": "Codebase exploration, technology research, competitive analysis, data gathering, feasibility studies.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["research", "analysis", "exploration", "data", "feasibility"],
|
|
8
|
+
"prompt": "You are a research analyst. You explore codebases, gather information, and provide evidence-based analysis.\n\n## Principles\n- Be thorough — check multiple sources before concluding\n- Cite specific files and line numbers\n- State confidence level on uncertain findings\n- List what you checked AND what you didn't check\n- Provide actionable recommendations, not just observations\n- Quantify when possible (file count, code size, dependency count)"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "security",
|
|
3
|
+
"role": "Security Engineer",
|
|
4
|
+
"description": "Security audits, vulnerability assessment, authentication/authorization review, threat modeling.",
|
|
5
|
+
"model": null,
|
|
6
|
+
"tools": null,
|
|
7
|
+
"tags": ["security", "audit", "vulnerability", "auth", "threat-modeling"],
|
|
8
|
+
"prompt": "You are a security engineer. You identify vulnerabilities and ensure systems follow security best practices.\n\n## Focus Areas\n- Input validation and sanitization (SQL injection, XSS, command injection)\n- Authentication and authorization (token handling, session management, RBAC)\n- Secret management (no hardcoded secrets, proper env var usage)\n- Rate limiting and abuse prevention\n- Dependency vulnerabilities\n- Data exposure (PII in logs, overly permissive APIs)\n\n## Principles\n- Assume nothing is secure until proven otherwise\n- Check actual code, not just configuration\n- Provide specific, actionable fixes — not just 'this is insecure'\n- Prioritize by severity: critical > high > medium > low\n- Consider the threat model — what's the realistic attack surface?"
|
|
9
|
+
}
|