pi-squad 0.6.5 → 0.14.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/README.md +200 -318
- package/package.json +21 -3
- package/src/advisor.ts +145 -0
- package/src/agent-pool.ts +39 -15
- package/src/agents/_defaults/architect.json +8 -1
- package/src/agents/_defaults/backend.json +9 -1
- package/src/agents/_defaults/debugger.json +8 -1
- package/src/agents/_defaults/devops.json +9 -1
- package/src/agents/_defaults/docs.json +8 -1
- package/src/agents/_defaults/frontend.json +10 -1
- package/src/agents/_defaults/fullstack.json +8 -1
- package/src/agents/_defaults/planner.json +7 -1
- package/src/agents/_defaults/qa.json +8 -1
- package/src/agents/_defaults/researcher.json +8 -1
- package/src/agents/_defaults/reviewer.json +10 -0
- package/src/agents/_defaults/security.json +8 -1
- package/src/index.ts +375 -212
- package/src/monitor.ts +20 -7
- package/src/panel/message-view.ts +2 -2
- package/src/panel/squad-panel.ts +3 -3
- package/src/panel/squad-widget.ts +3 -3
- package/src/panel/task-list.ts +2 -2
- package/src/plan-rules.ts +134 -0
- package/src/planner.ts +18 -14
- package/src/protocol.ts +11 -20
- package/src/scheduler.ts +172 -95
- package/src/skills/squad-architecture/SKILL.md +53 -0
- package/src/skills/squad-backend-dev/SKILL.md +45 -0
- package/src/skills/squad-code-review/SKILL.md +89 -0
- package/src/skills/{collaboration → squad-collaboration}/SKILL.md +6 -2
- package/src/skills/squad-debugging/SKILL.md +61 -0
- package/src/skills/squad-frontend-dev/SKILL.md +51 -0
- package/src/skills/squad-protocol/SKILL.md +19 -0
- package/src/skills/squad-qa-testing/SKILL.md +72 -0
- package/src/skills/squad-security-audit/SKILL.md +43 -0
- package/src/skills/squad-supervisor/SKILL.md +85 -11
- package/src/skills/{verification → squad-verification}/SKILL.md +1 -1
- package/src/store.ts +24 -26
- package/src/types.ts +48 -1
package/src/scheduler.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* - Detects squad completion
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
12
14
|
import type { AgentDef, Squad, SquadConfig, Task, TaskMessage, TaskStatus } from "./types.js";
|
|
13
15
|
import { AgentPool, type AgentEvent } from "./agent-pool.js";
|
|
14
16
|
import { Monitor } from "./monitor.js";
|
|
@@ -16,6 +18,7 @@ import { Router } from "./router.js";
|
|
|
16
18
|
import * as store from "./store.js";
|
|
17
19
|
import { debug, logError } from "./logger.js";
|
|
18
20
|
import { buildAgentSystemPrompt } from "./protocol.js";
|
|
21
|
+
import { buildAdvisorConsultText, formatAdvisorSteerMessage, adviceNeedsHuman, type AdvisorConsultInput } from "./advisor.js";
|
|
19
22
|
|
|
20
23
|
// ============================================================================
|
|
21
24
|
// Types
|
|
@@ -44,6 +47,16 @@ export interface SchedulerEvent {
|
|
|
44
47
|
|
|
45
48
|
export type SchedulerEventListener = (event: SchedulerEvent) => void;
|
|
46
49
|
|
|
50
|
+
/** Host-session capabilities passed in by the extension (index.ts) */
|
|
51
|
+
export interface SchedulerSpawnContext {
|
|
52
|
+
/** Resolve a model string (or null = default model) to its context window in tokens */
|
|
53
|
+
resolveContextWindow?: (model: string | null) => number | undefined;
|
|
54
|
+
/** Resolve the squad default model/thinking policy (settings.json + main session state) */
|
|
55
|
+
getDefaultModelThinking?: () => { model?: string; thinking?: string };
|
|
56
|
+
/** Consult the advisor model with a curated digest. Returns advice text, or null when disabled/unavailable. */
|
|
57
|
+
consultAdvisor?: (input: AdvisorConsultInput) => Promise<string | null>;
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
// ============================================================================
|
|
48
61
|
// Scheduler
|
|
49
62
|
// ============================================================================
|
|
@@ -55,6 +68,7 @@ export class Scheduler {
|
|
|
55
68
|
private router: Router;
|
|
56
69
|
private listeners: SchedulerEventListener[] = [];
|
|
57
70
|
private skillPaths: string[] = [];
|
|
71
|
+
private spawnContext?: SchedulerSpawnContext;
|
|
58
72
|
private running = false;
|
|
59
73
|
/** Track spawn retries to allow one retry per task */
|
|
60
74
|
private spawnRetries = new Set<string>();
|
|
@@ -64,9 +78,10 @@ export class Scheduler {
|
|
|
64
78
|
return store.loadSquad(this.squadId)?.cwd;
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
constructor(squadId: string, skillPaths: string[]) {
|
|
81
|
+
constructor(squadId: string, skillPaths: string[], spawnContext?: SchedulerSpawnContext) {
|
|
68
82
|
this.squadId = squadId;
|
|
69
83
|
this.skillPaths = skillPaths;
|
|
84
|
+
this.spawnContext = spawnContext;
|
|
70
85
|
this.pool = new AgentPool();
|
|
71
86
|
this.monitor = new Monitor(this.pool, squadId);
|
|
72
87
|
this.router = new Router(this.pool, squadId);
|
|
@@ -81,11 +96,16 @@ export class Scheduler {
|
|
|
81
96
|
} else if (action.type === "abort") {
|
|
82
97
|
this.handleTaskFailed(action.taskId, action.reason);
|
|
83
98
|
} else if (action.type === "escalate") {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
// Advisor-first: try a strong-model rescue before interrupting the human
|
|
100
|
+
void this.tryAdvisorRescue(action.taskId, action.agentName, action.reason).then((rescued) => {
|
|
101
|
+
if (rescued) return;
|
|
102
|
+
this.emit({
|
|
103
|
+
type: "escalation",
|
|
104
|
+
squadId: this.squadId,
|
|
105
|
+
taskId: action.taskId,
|
|
106
|
+
agentName: action.agentName,
|
|
107
|
+
message: action.reason,
|
|
108
|
+
});
|
|
89
109
|
});
|
|
90
110
|
}
|
|
91
111
|
});
|
|
@@ -248,11 +268,25 @@ export class Scheduler {
|
|
|
248
268
|
return;
|
|
249
269
|
}
|
|
250
270
|
|
|
251
|
-
// Apply squad-level model
|
|
271
|
+
// Apply squad-level model/thinking overrides
|
|
252
272
|
const squadAgentEntry = squad.agents[task.agent];
|
|
253
273
|
if (squadAgentEntry?.model) {
|
|
254
274
|
agentDef.model = squadAgentEntry.model;
|
|
255
275
|
}
|
|
276
|
+
if (squadAgentEntry?.thinking) {
|
|
277
|
+
agentDef.thinking = squadAgentEntry.thinking;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Apply squad defaults for anything still unset (policy resolved by the
|
|
281
|
+
// extension host: "main" = main session's model/thinking, "pi-default" =
|
|
282
|
+
// leave unset, or an explicit value from ~/.pi/squad/settings.json)
|
|
283
|
+
const defaults = this.spawnContext?.getDefaultModelThinking?.();
|
|
284
|
+
if (!agentDef.model && defaults?.model) {
|
|
285
|
+
agentDef.model = defaults.model;
|
|
286
|
+
}
|
|
287
|
+
if (!agentDef.thinking && defaults?.thinking) {
|
|
288
|
+
agentDef.thinking = defaults.thinking;
|
|
289
|
+
}
|
|
256
290
|
|
|
257
291
|
// Build modified files map from all running agents
|
|
258
292
|
const modifiedFiles: Record<string, string[]> = {};
|
|
@@ -285,6 +319,9 @@ export class Scheduler {
|
|
|
285
319
|
agentName: task.agent,
|
|
286
320
|
});
|
|
287
321
|
|
|
322
|
+
// Decide whether to fork the main session for context inheritance
|
|
323
|
+
const forkSessionFile = this.resolveForkSession(task, squad, agentDef);
|
|
324
|
+
|
|
288
325
|
try {
|
|
289
326
|
await this.pool.spawn({
|
|
290
327
|
taskId: task.id,
|
|
@@ -299,6 +336,14 @@ export class Scheduler {
|
|
|
299
336
|
},
|
|
300
337
|
cwd: squad.cwd,
|
|
301
338
|
skillPaths: this.skillPaths,
|
|
339
|
+
...(forkSessionFile
|
|
340
|
+
? {
|
|
341
|
+
forkSession: {
|
|
342
|
+
file: forkSessionFile,
|
|
343
|
+
sessionDir: path.join(store.getSquadDir(this.squadId), "sessions"),
|
|
344
|
+
},
|
|
345
|
+
}
|
|
346
|
+
: {}),
|
|
302
347
|
});
|
|
303
348
|
} catch (error) {
|
|
304
349
|
this.handleTaskFailed(task.id, (error as Error).message);
|
|
@@ -307,6 +352,123 @@ export class Scheduler {
|
|
|
307
352
|
this.updateContext();
|
|
308
353
|
}
|
|
309
354
|
|
|
355
|
+
/** Advisor consultations per task (advisor-first escalation) */
|
|
356
|
+
private advisorAttempts = new Map<string, number>();
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Consult the advisor for a stuck agent and steer it with the advice.
|
|
360
|
+
* Returns true when the agent was steered (escalation suppressed).
|
|
361
|
+
* Returns false when the advisor is disabled, exhausted, unavailable,
|
|
362
|
+
* failed, or explicitly said the problem needs human input.
|
|
363
|
+
*/
|
|
364
|
+
private async tryAdvisorRescue(taskId: string, agentName: string | undefined, reason: string): Promise<boolean> {
|
|
365
|
+
try {
|
|
366
|
+
const consult = this.spawnContext?.consultAdvisor;
|
|
367
|
+
if (!consult) return false;
|
|
368
|
+
|
|
369
|
+
const settings = store.loadSquadSettings();
|
|
370
|
+
if (!settings.advisor.enabled) return false;
|
|
371
|
+
|
|
372
|
+
const attempts = this.advisorAttempts.get(taskId) || 0;
|
|
373
|
+
if (attempts >= settings.advisor.maxCallsPerTask) {
|
|
374
|
+
debug("squad-advisor", `${taskId}: advisor exhausted (${attempts}/${settings.advisor.maxCallsPerTask}), escalating`);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
if (!this.pool.isRunning(taskId)) return false;
|
|
378
|
+
|
|
379
|
+
const task = store.loadTask(this.squadId, taskId);
|
|
380
|
+
const squad = store.loadSquad(this.squadId);
|
|
381
|
+
if (!task || !squad) return false;
|
|
382
|
+
const agentDef = store.loadAgentDef(task.agent, squad.cwd);
|
|
383
|
+
const activity = this.pool.getActivity(taskId);
|
|
384
|
+
|
|
385
|
+
this.advisorAttempts.set(taskId, attempts + 1);
|
|
386
|
+
|
|
387
|
+
const input: AdvisorConsultInput = {
|
|
388
|
+
taskId,
|
|
389
|
+
taskTitle: task.title,
|
|
390
|
+
taskDescription: task.description,
|
|
391
|
+
agentName: task.agent,
|
|
392
|
+
agentRole: agentDef?.role || task.agent,
|
|
393
|
+
reason,
|
|
394
|
+
recentMessages: store.loadMessages(this.squadId, taskId).slice(-12).map((m) => ({ from: m.from, type: m.type, text: m.text })),
|
|
395
|
+
recentToolCalls: activity ? [...activity.recentToolCalls] : [],
|
|
396
|
+
turnCount: activity?.turnCount || 0,
|
|
397
|
+
elapsedMinutes: activity ? (Date.now() - activity.startedAt) / 60000 : 0,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const advice = await consult(input);
|
|
401
|
+
if (!advice) return false;
|
|
402
|
+
|
|
403
|
+
store.appendMessage(this.squadId, taskId, {
|
|
404
|
+
ts: store.now(),
|
|
405
|
+
from: "advisor",
|
|
406
|
+
type: "message",
|
|
407
|
+
text: advice,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Advisor says a human decision is required — escalate with the advice attached
|
|
411
|
+
if (adviceNeedsHuman(advice)) {
|
|
412
|
+
this.emit({
|
|
413
|
+
type: "escalation",
|
|
414
|
+
squadId: this.squadId,
|
|
415
|
+
taskId,
|
|
416
|
+
agentName,
|
|
417
|
+
message: `${reason}\n\nAdvisor assessment:\n${advice.slice(0, 800)}`,
|
|
418
|
+
});
|
|
419
|
+
return true; // escalation already emitted with richer context
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const delivered = await this.pool.steer(taskId, formatAdvisorSteerMessage(advice, reason));
|
|
423
|
+
debug("squad-advisor", `${taskId}: advisor steered agent (attempt ${attempts + 1}, delivered=${delivered})`);
|
|
424
|
+
return delivered;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
logError("squad-advisor", `rescue failed for ${taskId}: ${(error as Error).message}`);
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Decide whether this task's agent should be spawned as a fork of the main
|
|
433
|
+
* pi session (context inheritance). Guards against blowing the child model's
|
|
434
|
+
* context window: forks only when the estimated session tokens fit within
|
|
435
|
+
* 50% of the agent model's context window.
|
|
436
|
+
*/
|
|
437
|
+
private resolveForkSession(task: Task, squad: Squad, agentDef: AgentDef): string | undefined {
|
|
438
|
+
if (!task.inheritContext) return undefined;
|
|
439
|
+
|
|
440
|
+
const skip = (reason: string): undefined => {
|
|
441
|
+
logError("squad-scheduler", `inheritContext skipped for ${task.id}: ${reason}`);
|
|
442
|
+
store.appendMessage(this.squadId, task.id, {
|
|
443
|
+
ts: store.now(),
|
|
444
|
+
from: "system",
|
|
445
|
+
type: "status",
|
|
446
|
+
text: `Context inheritance skipped: ${reason}. Agent starts with standard squad context only.`,
|
|
447
|
+
});
|
|
448
|
+
return undefined;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const sessionFile = squad.sessionFile;
|
|
452
|
+
if (!sessionFile) return skip("main session has no session file (ephemeral --no-session run)");
|
|
453
|
+
if (!fs.existsSync(sessionFile)) return skip(`session file not found: ${sessionFile}`);
|
|
454
|
+
|
|
455
|
+
// Rough token estimate: JSONL bytes / 4. Overestimates (JSON overhead), which is safe.
|
|
456
|
+
const estTokens = Math.ceil(fs.statSync(sessionFile).size / 4);
|
|
457
|
+
|
|
458
|
+
const window = this.spawnContext?.resolveContextWindow?.(agentDef.model ?? null);
|
|
459
|
+
if (!window) {
|
|
460
|
+
return skip(`cannot determine context window for model "${agentDef.model || "(default)"}"`);
|
|
461
|
+
}
|
|
462
|
+
if (estTokens > window * 0.5) {
|
|
463
|
+
return skip(
|
|
464
|
+
`estimated session context (~${Math.round(estTokens / 1000)}k tokens) exceeds 50% of ${agentDef.model || "default model"}'s ${Math.round(window / 1000)}k window — restate key context in the task description instead`,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
debug("squad-scheduler", `inheritContext: forking ${sessionFile} for ${task.id} (~${Math.round(estTokens / 1000)}k tokens, window ${Math.round(window / 1000)}k)`);
|
|
469
|
+
return sessionFile;
|
|
470
|
+
}
|
|
471
|
+
|
|
310
472
|
// =========================================================================
|
|
311
473
|
// Event Handlers
|
|
312
474
|
// =========================================================================
|
|
@@ -463,10 +625,6 @@ export class Scheduler {
|
|
|
463
625
|
text: "Task completed",
|
|
464
626
|
});
|
|
465
627
|
|
|
466
|
-
// Reload task after status update so overview has output, completed time, etc.
|
|
467
|
-
const updatedTask = store.loadTask(this.squadId, taskId) || task;
|
|
468
|
-
this.appendTaskOverview(updatedTask, taskId, messages);
|
|
469
|
-
|
|
470
628
|
this.emit({
|
|
471
629
|
type: "task_completed",
|
|
472
630
|
squadId: this.squadId,
|
|
@@ -603,7 +761,7 @@ export class Scheduler {
|
|
|
603
761
|
*/
|
|
604
762
|
private checkForRework(task: Task, output: string): boolean {
|
|
605
763
|
// Only trigger rework for QA/test agent tasks
|
|
606
|
-
const qaAgents = ["qa", "tester", "security"];
|
|
764
|
+
const qaAgents = ["qa", "tester", "security", "reviewer"];
|
|
607
765
|
if (!qaAgents.includes(task.agent)) return false;
|
|
608
766
|
|
|
609
767
|
// Parse verdict from output
|
|
@@ -614,7 +772,7 @@ export class Scheduler {
|
|
|
614
772
|
const allTasks = store.loadAllTasks(this.squadId);
|
|
615
773
|
const implDeps = task.depends
|
|
616
774
|
.map((depId) => allTasks.find((t) => t.id === depId))
|
|
617
|
-
.filter((t): t is Task => t !==
|
|
775
|
+
.filter((t): t is Task => t !== undefined && !qaAgents.includes(t.agent));
|
|
618
776
|
|
|
619
777
|
if (implDeps.length === 0) return false;
|
|
620
778
|
|
|
@@ -653,6 +811,7 @@ export class Scheduler {
|
|
|
653
811
|
agent: implTask.agent,
|
|
654
812
|
status: "pending",
|
|
655
813
|
depends: [],
|
|
814
|
+
...(implTask.inheritContext ? { inheritContext: true } : {}),
|
|
656
815
|
created: store.now(),
|
|
657
816
|
started: null,
|
|
658
817
|
completed: null,
|
|
@@ -943,88 +1102,6 @@ export class Scheduler {
|
|
|
943
1102
|
return textParts.length > 0 ? textParts.join("\n") : null;
|
|
944
1103
|
}
|
|
945
1104
|
|
|
946
|
-
// =========================================================================
|
|
947
|
-
// Overview Document — append task summary after completion
|
|
948
|
-
// =========================================================================
|
|
949
|
-
|
|
950
|
-
/**
|
|
951
|
-
* Build a concise markdown summary of a completed task and append it
|
|
952
|
-
* to the squad's OVERVIEW.md. This gives subsequent agents a narrative
|
|
953
|
-
* understanding of what was accomplished, issues hit, and decisions made.
|
|
954
|
-
*/
|
|
955
|
-
private appendTaskOverview(
|
|
956
|
-
task: Task,
|
|
957
|
-
taskId: string,
|
|
958
|
-
messages: import("./types.js").TaskMessage[],
|
|
959
|
-
): void {
|
|
960
|
-
try {
|
|
961
|
-
const lines: string[] = [];
|
|
962
|
-
|
|
963
|
-
// Header
|
|
964
|
-
lines.push(`## ${task.id}: ${task.title}`);
|
|
965
|
-
lines.push(``);
|
|
966
|
-
lines.push(`**Agent:** ${task.agent} | **Status:** ${task.status}`);
|
|
967
|
-
if (task.started && task.completed) {
|
|
968
|
-
const dur = new Date(task.completed).getTime() - new Date(task.started).getTime();
|
|
969
|
-
lines.push(`**Duration:** ${formatElapsed(dur)}`);
|
|
970
|
-
}
|
|
971
|
-
lines.push(``);
|
|
972
|
-
|
|
973
|
-
// Output
|
|
974
|
-
if (task.output) {
|
|
975
|
-
lines.push(`### Output`);
|
|
976
|
-
lines.push(task.output.slice(0, 800));
|
|
977
|
-
if (task.output.length > 800) lines.push(`... (truncated)`);
|
|
978
|
-
lines.push(``);
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Issues / errors encountered
|
|
982
|
-
const errors = messages.filter((m) => m.type === "error" && m.from !== "system");
|
|
983
|
-
if (errors.length > 0) {
|
|
984
|
-
lines.push(`### Issues Encountered`);
|
|
985
|
-
for (const err of errors.slice(-5)) {
|
|
986
|
-
lines.push(`- ${err.text.split("\n")[0].slice(0, 200)}`);
|
|
987
|
-
}
|
|
988
|
-
lines.push(``);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// Decisions — scan agent text for decision-like language
|
|
992
|
-
const decisionPatterns = /\b(decided|chose|approach|instead of|trade-?off|opted|going with|switched to|using .+ because)\b/i;
|
|
993
|
-
const agentTexts = messages.filter((m) => m.from === task.agent && m.type === "text");
|
|
994
|
-
const decisions: string[] = [];
|
|
995
|
-
for (const msg of agentTexts) {
|
|
996
|
-
for (const line of msg.text.split("\n")) {
|
|
997
|
-
if (decisionPatterns.test(line) && line.trim().length > 10) {
|
|
998
|
-
decisions.push(line.trim().slice(0, 200));
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
if (decisions.length > 0) {
|
|
1003
|
-
lines.push(`### Decisions`);
|
|
1004
|
-
for (const d of decisions.slice(-5)) {
|
|
1005
|
-
lines.push(`- ${d}`);
|
|
1006
|
-
}
|
|
1007
|
-
lines.push(``);
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
// Files modified
|
|
1011
|
-
const activity = this.pool.getActivity(taskId);
|
|
1012
|
-
if (activity && activity.modifiedFiles.size > 0) {
|
|
1013
|
-
lines.push(`### Files Modified`);
|
|
1014
|
-
for (const f of Array.from(activity.modifiedFiles).slice(0, 15)) {
|
|
1015
|
-
lines.push(`- ${f}`);
|
|
1016
|
-
}
|
|
1017
|
-
if (activity.modifiedFiles.size > 15) {
|
|
1018
|
-
lines.push(`- ... and ${activity.modifiedFiles.size - 15} more`);
|
|
1019
|
-
}
|
|
1020
|
-
lines.push(``);
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
store.appendOverview(this.squadId, lines.join("\n"));
|
|
1024
|
-
} catch (err) {
|
|
1025
|
-
logError("squad-scheduler", `Failed to append overview for ${taskId}: ${(err as Error).message}`);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
1105
|
}
|
|
1029
1106
|
|
|
1030
1107
|
function formatElapsed(ms: number): string {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: squad-architecture
|
|
3
|
+
description: >
|
|
4
|
+
System architecture practices — project structure, API contracts, shared types,
|
|
5
|
+
monorepo setup, and technical decision documentation. Use when designing system
|
|
6
|
+
architecture, defining contracts between components, or setting up project structure.
|
|
7
|
+
version: 1.0.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Architecture & System Design
|
|
11
|
+
|
|
12
|
+
## Project Structure
|
|
13
|
+
- Define clear boundaries between components (backend, frontend, shared)
|
|
14
|
+
- Use a monorepo with workspaces when frontend and backend share types
|
|
15
|
+
- Create a shared types/constants package that both sides import
|
|
16
|
+
- Document the project structure in a README or ARCHITECTURE.md
|
|
17
|
+
|
|
18
|
+
## API Contract Definition
|
|
19
|
+
When defining API contracts that other agents will implement:
|
|
20
|
+
- List EVERY endpoint with method, path, request body, and response shape
|
|
21
|
+
- Specify exact field names, types, and which fields are optional
|
|
22
|
+
- Define error response format consistently
|
|
23
|
+
- Specify authentication requirements per endpoint
|
|
24
|
+
- Include example request/response pairs
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
```
|
|
28
|
+
POST /api/auth/login
|
|
29
|
+
Request: { email: string, password: string }
|
|
30
|
+
Response: { user: { id, email, name }, accessToken: string, refreshToken: string }
|
|
31
|
+
Errors: 401 { error: "Invalid credentials" }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Shared Types
|
|
35
|
+
- Define TypeScript interfaces for all data models
|
|
36
|
+
- Include validation schemas (zod) alongside types
|
|
37
|
+
- Export constants (status enums, priority levels, config values)
|
|
38
|
+
- Version the shared package so consumers know when contracts change
|
|
39
|
+
|
|
40
|
+
## Technical Decisions
|
|
41
|
+
Document every significant decision in your output:
|
|
42
|
+
- What was decided and why
|
|
43
|
+
- What alternatives were considered
|
|
44
|
+
- What trade-offs were accepted
|
|
45
|
+
|
|
46
|
+
This helps downstream agents understand the rationale and stay consistent.
|
|
47
|
+
|
|
48
|
+
## Handoff Quality
|
|
49
|
+
Your output is the contract that all other agents build against. Be precise:
|
|
50
|
+
- Don't leave ambiguity in field names or types
|
|
51
|
+
- Specify exact port numbers, file paths, directory structure
|
|
52
|
+
- Include the database schema with column types and constraints
|
|
53
|
+
- List all environment variables needed
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: squad-backend-dev
|
|
3
|
+
description: >
|
|
4
|
+
Backend engineering practices — API design, database patterns, auth implementation,
|
|
5
|
+
input validation, error handling, and security. Use when building APIs, servers,
|
|
6
|
+
databases, or backend services.
|
|
7
|
+
version: 1.0.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Backend Development
|
|
11
|
+
|
|
12
|
+
## API Design
|
|
13
|
+
- RESTful conventions: GET (read), POST (create), PUT (replace), PATCH (update), DELETE (remove)
|
|
14
|
+
- Consistent response format: success returns data directly, errors return `{ error: string, code?: string }`
|
|
15
|
+
- Validate all inputs at the boundary (use zod, joi, or manual checks)
|
|
16
|
+
- Paginate list endpoints (limit/offset or cursor-based)
|
|
17
|
+
- Use proper HTTP status codes: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 500 Server Error
|
|
18
|
+
- Document every endpoint in your completion output (method, path, request/response shape)
|
|
19
|
+
|
|
20
|
+
## Database
|
|
21
|
+
- Use migrations for schema changes (never ALTER in application code)
|
|
22
|
+
- Add indexes for frequently queried columns and foreign keys
|
|
23
|
+
- Use foreign keys with appropriate ON DELETE behavior (CASCADE, SET NULL)
|
|
24
|
+
- Use transactions for multi-step writes
|
|
25
|
+
- Sanitize all user inputs in queries (parameterized queries, never string interpolation)
|
|
26
|
+
|
|
27
|
+
## Authentication
|
|
28
|
+
- Never store passwords in plain text — use bcrypt with sufficient rounds (10+)
|
|
29
|
+
- JWT access tokens: short-lived (15min), stateless verification
|
|
30
|
+
- Refresh tokens: longer-lived (7d), stored server-side, rotated on use
|
|
31
|
+
- Validate tokens on every protected endpoint via middleware
|
|
32
|
+
- Return 401 for invalid/expired tokens, 403 for insufficient permissions
|
|
33
|
+
|
|
34
|
+
## Error Handling
|
|
35
|
+
- Catch errors at route level, don't let unhandled rejections crash the server
|
|
36
|
+
- Log errors with context (request id, user id, endpoint)
|
|
37
|
+
- Never expose stack traces or internal details in API responses
|
|
38
|
+
- Use typed error classes for different error categories
|
|
39
|
+
- Implement graceful shutdown (close DB connections, finish in-flight requests)
|
|
40
|
+
|
|
41
|
+
## Security
|
|
42
|
+
- Rate-limit public endpoints (login, register, password reset)
|
|
43
|
+
- Set security headers (CORS, Helmet for Express)
|
|
44
|
+
- Validate file uploads (type whitelist, size limits)
|
|
45
|
+
- Never log sensitive data (passwords, tokens, PII)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: squad-code-review
|
|
3
|
+
description: >
|
|
4
|
+
Code review methodology — two-pass review (correctness/scope, then
|
|
5
|
+
over-engineering), surgical-change discipline, complexity findings with
|
|
6
|
+
delete/stdlib/native/yagni/shrink tags, machine-parsed verdicts. Use when
|
|
7
|
+
reviewing diffs, implementations, or pull requests.
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Code Review
|
|
12
|
+
|
|
13
|
+
Two passes, in order. Findings from pass 1 gate the verdict; pass 2 findings
|
|
14
|
+
are usually non-blocking. Derived from the karpathy-guidelines and ponytail
|
|
15
|
+
review disciplines.
|
|
16
|
+
|
|
17
|
+
## Pass 1 — Correctness & Scope
|
|
18
|
+
|
|
19
|
+
**Every changed line must trace to the task.**
|
|
20
|
+
- Flag unrequested refactors, drive-by "improvements", formatting churn in
|
|
21
|
+
untouched code, and deleted code the task didn't ask to remove
|
|
22
|
+
- Orphan check: did the change leave now-unused imports/variables/functions
|
|
23
|
+
it created? (Pre-existing dead code is a mention, not a finding)
|
|
24
|
+
|
|
25
|
+
**Verify with evidence, not by reading.**
|
|
26
|
+
- Run the task's Verify criterion yourself (tests, build, curl). Your review
|
|
27
|
+
is only as strong as the commands you ran
|
|
28
|
+
- Reproduce at least one happy path and one failure path where practical
|
|
29
|
+
- If you can't run something, say so explicitly — don't imply you did
|
|
30
|
+
|
|
31
|
+
**Assumptions and interpretations.**
|
|
32
|
+
- If the implementation silently picked one of several readings of the task,
|
|
33
|
+
name the choice and whether it matches the task's Goal/Boundaries
|
|
34
|
+
- Check the task's Boundaries were respected (unchanged APIs, schemas, configs)
|
|
35
|
+
|
|
36
|
+
**Error handling in the right places.**
|
|
37
|
+
- Trust boundaries (user input, network, file I/O) must handle failure
|
|
38
|
+
- Impossible-scenario handling is noise — flag it in pass 2 as yagni
|
|
39
|
+
|
|
40
|
+
## Pass 2 — Complexity Hunt
|
|
41
|
+
|
|
42
|
+
Climb the ladder for each addition; stop at the first rung that holds:
|
|
43
|
+
need to exist? → already in codebase? → stdlib? → native platform? →
|
|
44
|
+
installed dep? → one line? → minimum that works.
|
|
45
|
+
|
|
46
|
+
One line per finding — location, what to cut, what replaces it:
|
|
47
|
+
|
|
48
|
+
- `delete:` dead code, unused flexibility, speculative feature → nothing
|
|
49
|
+
- `stdlib:` hand-rolled thing the standard library ships → name the function
|
|
50
|
+
- `native:` dep/code doing what the platform does → name the feature
|
|
51
|
+
- `yagni:` abstraction with one implementation, config nobody sets, layer with one caller
|
|
52
|
+
- `shrink:` same logic, fewer lines → show the shorter form
|
|
53
|
+
|
|
54
|
+
Example: `api.ts:L88: yagni: RepositoryFactory with one product. Inline it until a second exists.`
|
|
55
|
+
|
|
56
|
+
**Never flag as bloat**: trust-boundary validation, data-loss handling,
|
|
57
|
+
security, accessibility, or a minimal test. Lean is not careless.
|
|
58
|
+
|
|
59
|
+
If nothing to cut: `Lean already.` — don't invent findings to look thorough.
|
|
60
|
+
|
|
61
|
+
## Verdict (machine-parsed — REQUIRED)
|
|
62
|
+
|
|
63
|
+
End your final message with exactly:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
## Verdict: PASS | FAIL | PASS WITH ISSUES
|
|
67
|
+
|
|
68
|
+
## Issues
|
|
69
|
+
1. **[file:line]** (Critical/High/Medium/Low) description
|
|
70
|
+
- Expected: X
|
|
71
|
+
- Got: Y
|
|
72
|
+
- Fix: specific change
|
|
73
|
+
|
|
74
|
+
## Evidence
|
|
75
|
+
[commands run + output]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **FAIL**: pass-1 problems (correctness, scope violations, broken Verify).
|
|
79
|
+
Creates a rework task — the fixer only sees your Issues section
|
|
80
|
+
- **PASS WITH ISSUES**: works and in-scope, but pass-2 cuts are worth making
|
|
81
|
+
- **PASS**: correct, in scope, lean. Say what you verified
|
|
82
|
+
- Severity: correctness/security → Critical/High; complexity → Medium/Low
|
|
83
|
+
- Do not rename the sections — `## Issues` is extracted verbatim as fix feedback
|
|
84
|
+
|
|
85
|
+
## You Don't Fix
|
|
86
|
+
|
|
87
|
+
You report; the squad routes fixes to the original agent. Applying fixes
|
|
88
|
+
yourself would bypass the rework loop and confuse file ownership. If a fix
|
|
89
|
+
is truly one character, still report it — with the exact edit in the Fix line.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: collaboration
|
|
2
|
+
name: squad-collaboration
|
|
3
3
|
description: Multi-agent collaboration patterns — how to build on others' work, ask questions, share knowledge, and work as a team.
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -22,10 +22,14 @@ Bad: "@backend FYI I'm working on the frontend" (no question, wastes their time)
|
|
|
22
22
|
Don't just post conclusions. Explain why you made a choice, so others can course-correct early.
|
|
23
23
|
"I chose RS256 over HS256 because the frontend needs to verify tokens without the signing secret"
|
|
24
24
|
|
|
25
|
-
## Admit uncertainty
|
|
25
|
+
## Admit uncertainty — flag gaps, never guess
|
|
26
26
|
If you're not sure about something, say so and ask.
|
|
27
27
|
"I'm not sure if this migration is backwards-compatible — @backend can you verify?"
|
|
28
28
|
|
|
29
|
+
When required information is missing from your task or dependency outputs, flag the gap
|
|
30
|
+
and ask — don't invent values, endpoints, or formats to fill it. A wrong guess propagates
|
|
31
|
+
to every dependent task; a question costs one message.
|
|
32
|
+
|
|
29
33
|
Better to ask than to silently introduce a breaking change.
|
|
30
34
|
|
|
31
35
|
## Respond when asked
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: squad-debugging
|
|
3
|
+
description: >
|
|
4
|
+
Debugging and rework discipline — reproduce-first workflow, hypothesis testing,
|
|
5
|
+
minimal fixes, regression tests. Use when fixing bugs, handling QA feedback,
|
|
6
|
+
working on rework/fix tasks, or investigating failures.
|
|
7
|
+
version: 1.0.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Debugging & Rework
|
|
11
|
+
|
|
12
|
+
## The Iron Rule: Reproduce First
|
|
13
|
+
|
|
14
|
+
Never change code before you've seen the failure with your own eyes.
|
|
15
|
+
|
|
16
|
+
1. **REPRODUCE**: Run the failing test, repro steps, or command from the QA
|
|
17
|
+
feedback. Confirm you see the same failure. If you can't reproduce it,
|
|
18
|
+
say so explicitly — don't "fix" what you can't observe.
|
|
19
|
+
2. **LOCALIZE**: Read the error, stack trace, or diff. Form ONE hypothesis
|
|
20
|
+
about the cause. State it before changing anything.
|
|
21
|
+
3. **FIX**: Make the smallest change that addresses the hypothesized cause.
|
|
22
|
+
4. **VERIFY**: Re-run the exact reproduction from step 1 — it must now pass.
|
|
23
|
+
Then run the surrounding test suite to catch regressions.
|
|
24
|
+
5. **PROTECT**: Add a regression test if none exists for this failure mode.
|
|
25
|
+
|
|
26
|
+
## Hypothesis Discipline
|
|
27
|
+
|
|
28
|
+
- One hypothesis at a time. If the fix doesn't work, REVERT it before trying
|
|
29
|
+
the next hypothesis — never stack speculative changes
|
|
30
|
+
- If two hypotheses fail, stop and gather more evidence (add logging, isolate
|
|
31
|
+
with a minimal test case) instead of guessing a third time
|
|
32
|
+
- Distinguish "the symptom moved" from "the cause is fixed" — re-run the
|
|
33
|
+
ORIGINAL repro, not just your new test
|
|
34
|
+
|
|
35
|
+
## Rework Tasks (QA sent your work back)
|
|
36
|
+
|
|
37
|
+
- Fix ONLY the issues listed in the QA feedback — no rewrites, no opportunistic
|
|
38
|
+
refactoring, no unrelated improvements
|
|
39
|
+
- Reproduce each reported issue before touching code (the feedback includes
|
|
40
|
+
Expected/Got/Repro for each — use them)
|
|
41
|
+
- If a reported issue looks wrong or is not reproducible, say so explicitly
|
|
42
|
+
with evidence instead of silently skipping it
|
|
43
|
+
- Your completion message must show each issue → what changed → re-run evidence
|
|
44
|
+
|
|
45
|
+
## Common Traps
|
|
46
|
+
|
|
47
|
+
- **Fixing the test instead of the code** — only adjust a test when you can
|
|
48
|
+
argue the test itself was wrong, and say so
|
|
49
|
+
- **Shotgun debugging** — many small changes at once; you won't know which
|
|
50
|
+
one mattered and you'll introduce new breakage
|
|
51
|
+
- **Environment blame** — "works on my machine" requires evidence: show the
|
|
52
|
+
environment difference, don't assume it
|
|
53
|
+
- **Silent scope creep** — noticing other bugs is good; fixing them inside a
|
|
54
|
+
rework task is not. Mention them so the squad can create tasks
|
|
55
|
+
|
|
56
|
+
## Output Checklist
|
|
57
|
+
|
|
58
|
+
Your completion message must include:
|
|
59
|
+
1. Each issue: root cause (one sentence) + the fix (file:line)
|
|
60
|
+
2. Evidence: the original failing command now passing (actual output)
|
|
61
|
+
3. Regression scope: which suites you ran and their results
|