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.
Files changed (39) hide show
  1. package/README.md +200 -318
  2. package/package.json +21 -3
  3. package/src/advisor.ts +145 -0
  4. package/src/agent-pool.ts +39 -15
  5. package/src/agents/_defaults/architect.json +8 -1
  6. package/src/agents/_defaults/backend.json +9 -1
  7. package/src/agents/_defaults/debugger.json +8 -1
  8. package/src/agents/_defaults/devops.json +9 -1
  9. package/src/agents/_defaults/docs.json +8 -1
  10. package/src/agents/_defaults/frontend.json +10 -1
  11. package/src/agents/_defaults/fullstack.json +8 -1
  12. package/src/agents/_defaults/planner.json +7 -1
  13. package/src/agents/_defaults/qa.json +8 -1
  14. package/src/agents/_defaults/researcher.json +8 -1
  15. package/src/agents/_defaults/reviewer.json +10 -0
  16. package/src/agents/_defaults/security.json +8 -1
  17. package/src/index.ts +375 -212
  18. package/src/monitor.ts +20 -7
  19. package/src/panel/message-view.ts +2 -2
  20. package/src/panel/squad-panel.ts +3 -3
  21. package/src/panel/squad-widget.ts +3 -3
  22. package/src/panel/task-list.ts +2 -2
  23. package/src/plan-rules.ts +134 -0
  24. package/src/planner.ts +18 -14
  25. package/src/protocol.ts +11 -20
  26. package/src/scheduler.ts +172 -95
  27. package/src/skills/squad-architecture/SKILL.md +53 -0
  28. package/src/skills/squad-backend-dev/SKILL.md +45 -0
  29. package/src/skills/squad-code-review/SKILL.md +89 -0
  30. package/src/skills/{collaboration → squad-collaboration}/SKILL.md +6 -2
  31. package/src/skills/squad-debugging/SKILL.md +61 -0
  32. package/src/skills/squad-frontend-dev/SKILL.md +51 -0
  33. package/src/skills/squad-protocol/SKILL.md +19 -0
  34. package/src/skills/squad-qa-testing/SKILL.md +72 -0
  35. package/src/skills/squad-security-audit/SKILL.md +43 -0
  36. package/src/skills/squad-supervisor/SKILL.md +85 -11
  37. package/src/skills/{verification → squad-verification}/SKILL.md +1 -1
  38. package/src/store.ts +24 -26
  39. 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
- this.emit({
85
- type: "escalation",
86
- squadId: this.squadId,
87
- taskId: action.taskId,
88
- message: action.reason,
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 override
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 !== null && !qaAgents.includes(t.agent));
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