ai-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.
Files changed (49) hide show
  1. package/README.md +131 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +1013 -0
  4. package/package.json +49 -0
  5. package/sections/opencode/agents/identity/adversarial-reviewer.md +98 -0
  6. package/sections/opencode/agents/identity/architect.md +122 -0
  7. package/sections/opencode/agents/identity/docs-writer.md +53 -0
  8. package/sections/opencode/agents/identity/fullstack-dev.md +79 -0
  9. package/sections/opencode/agents/identity/memory-keeper.md +192 -0
  10. package/sections/opencode/agents/identity/po.md +102 -0
  11. package/sections/opencode/agents/identity/qa.md +90 -0
  12. package/sections/opencode/agents/identity/reviewer.md +84 -0
  13. package/sections/opencode/agents/identity/teamlead.md +101 -0
  14. package/sections/opencode/agents/identity/ux-ui.md +87 -0
  15. package/sections/opencode/agents/standards/generic.md +36 -0
  16. package/sections/opencode/agents/standards/nextjs.md +29 -0
  17. package/sections/opencode/agents/standards/python.md +217 -0
  18. package/sections/opencode/agents/standards/react.md +130 -0
  19. package/sections/opencode/agents/standards/typescript.md +30 -0
  20. package/sections/opencode/agents/workflow/context.md +24 -0
  21. package/sections/opencode/agents/workflow/memory.md +32 -0
  22. package/sections/opencode/agents/workflow/sessions.md +38 -0
  23. package/sections/opencode/agents/workflow/tasks.md +40 -0
  24. package/sections/opencode/config/agents.md +11 -0
  25. package/sections/opencode/context/nextjs/architecture.md +13 -0
  26. package/sections/opencode/context/nextjs/conventions.md +20 -0
  27. package/sections/opencode/context/nextjs/stack.md +14 -0
  28. package/sections/opencode/context/python/architecture.md +13 -0
  29. package/sections/opencode/context/python/conventions.md +19 -0
  30. package/sections/opencode/context/python/stack.md +14 -0
  31. package/sections/opencode/workflow/context.md +14 -0
  32. package/sections/opencode/workflow/memory.md +12 -0
  33. package/sections/opencode/workflow/sessions.md +20 -0
  34. package/sections/opencode/workflow/state.md +20 -0
  35. package/sections/opencode/workflow/tasks.md +13 -0
  36. package/sections/shared/frontmatter/adversarial-reviewer.yaml +7 -0
  37. package/sections/shared/frontmatter/architect.yaml +7 -0
  38. package/sections/shared/frontmatter/docs-writer.yaml +7 -0
  39. package/sections/shared/frontmatter/fullstack-dev.yaml +7 -0
  40. package/sections/shared/frontmatter/memory-keeper.yaml +7 -0
  41. package/sections/shared/frontmatter/po.yaml +7 -0
  42. package/sections/shared/frontmatter/qa.yaml +7 -0
  43. package/sections/shared/frontmatter/reviewer.yaml +7 -0
  44. package/sections/shared/frontmatter/teamlead.yaml +8 -0
  45. package/sections/shared/frontmatter/ux-ui.yaml +7 -0
  46. package/sections/shared/memory/conventions.md +10 -0
  47. package/sections/shared/memory/decisions.md +13 -0
  48. package/sections/shared/memory/patterns.md +12 -0
  49. package/sections/shared/task-template.md +44 -0
package/dist/index.js ADDED
@@ -0,0 +1,1013 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/wizard.ts
4
+ import {
5
+ intro,
6
+ outro,
7
+ text,
8
+ select,
9
+ multiselect,
10
+ spinner,
11
+ isCancel,
12
+ cancel,
13
+ note
14
+ } from "@clack/prompts";
15
+ import pc2 from "picocolors";
16
+
17
+ // src/squads.ts
18
+ var SQUADS = [
19
+ {
20
+ id: "solo",
21
+ name: "Solo",
22
+ description: "One developer, quick projects. Full-stack dev + reviewer.",
23
+ agents: ["fullstack-dev", "reviewer"]
24
+ },
25
+ {
26
+ id: "startup",
27
+ name: "Startup",
28
+ description: "Small team, fast iteration. PO + dev + reviewer + QA.",
29
+ agents: ["po", "fullstack-dev", "reviewer", "qa"]
30
+ },
31
+ {
32
+ id: "standard",
33
+ name: "Standard",
34
+ description: "Production apps. PO + architect + dev + reviewer + UX + QA.",
35
+ agents: ["po", "architect", "fullstack-dev", "reviewer", "ux-ui", "qa"]
36
+ },
37
+ {
38
+ id: "enterprise",
39
+ name: "Enterprise",
40
+ description: "High-stakes, compliance. All 9 agents.",
41
+ agents: [
42
+ "po",
43
+ "architect",
44
+ "fullstack-dev",
45
+ "reviewer",
46
+ "ux-ui",
47
+ "qa",
48
+ "adversarial-reviewer",
49
+ "docs-writer",
50
+ "memory-keeper"
51
+ ]
52
+ },
53
+ {
54
+ id: "frontend",
55
+ name: "Frontend",
56
+ description: "UI-heavy projects. Dev + UX + reviewer.",
57
+ agents: ["fullstack-dev", "ux-ui", "reviewer"]
58
+ },
59
+ {
60
+ id: "backend",
61
+ name: "Backend",
62
+ description: "API/DB projects. Architect + dev + reviewer + QA.",
63
+ agents: ["architect", "fullstack-dev", "reviewer", "qa"]
64
+ }
65
+ ];
66
+ function getSquad(id) {
67
+ return SQUADS.find((s) => s.id === id);
68
+ }
69
+ var PLATFORMS = ["opencode"];
70
+
71
+ // src/features.ts
72
+ var FEATURE_MAP = {
73
+ minimal: { state: false, tasks: false, memory: false, context: false, sessions: false },
74
+ essential: { state: true, tasks: true, memory: false, context: false, sessions: false },
75
+ standard: { state: true, tasks: true, memory: true, context: true, sessions: false },
76
+ comprehensive: { state: true, tasks: true, memory: true, context: true, sessions: true }
77
+ };
78
+ var INFRA_AGENTS = {
79
+ minimal: [],
80
+ essential: [],
81
+ standard: ["memory-keeper"],
82
+ comprehensive: ["memory-keeper", "docs-writer"]
83
+ };
84
+ function getInfraAgents(level) {
85
+ return INFRA_AGENTS[level];
86
+ }
87
+ function getOrchestratorInfra(squadAgents) {
88
+ if (squadAgents.includes("po")) {
89
+ return ["teamlead"];
90
+ }
91
+ return [];
92
+ }
93
+ function resolveFeatures(level) {
94
+ return { ...FEATURE_MAP[level] };
95
+ }
96
+
97
+ // src/skills-map.ts
98
+ var SKILL_SOURCES = {
99
+ nextjs: [
100
+ {
101
+ source: "vercel-labs/agent-skills",
102
+ skills: ["react-best-practices", "composition-patterns", "react-view-transitions"],
103
+ description: "React/Next.js best practices: 40+ rules, composition patterns, view transitions"
104
+ },
105
+ {
106
+ source: "shadcn/ui",
107
+ skills: ["shadcn"],
108
+ description: "shadcn/ui component usage, theming, and Tailwind integration"
109
+ }
110
+ ],
111
+ react: [
112
+ {
113
+ source: "vercel-labs/agent-skills",
114
+ skills: ["react-best-practices", "composition-patterns", "react-view-transitions"],
115
+ description: "React best practices: 40+ rules across 8 categories"
116
+ },
117
+ {
118
+ source: "shadcn/ui",
119
+ skills: ["shadcn"],
120
+ description: "shadcn/ui component usage, theming, and Tailwind integration"
121
+ }
122
+ ],
123
+ typescript: [
124
+ {
125
+ source: "mattpocock/skills",
126
+ description: "TypeScript engineering skills: code review, debugging, TDD, domain modeling"
127
+ }
128
+ ],
129
+ tailwind: [
130
+ {
131
+ source: "vercel-labs/agent-skills",
132
+ skills: ["web-design-guidelines"],
133
+ description: "100+ UI design rules including Tailwind patterns"
134
+ },
135
+ {
136
+ source: "shadcn/ui",
137
+ skills: ["shadcn"],
138
+ description: "shadcn/ui component usage, theming, and Tailwind integration"
139
+ }
140
+ ],
141
+ python: [],
142
+ fastapi: [],
143
+ express: [],
144
+ go: [],
145
+ convex: [
146
+ {
147
+ source: "get-convex/agent-skills",
148
+ skills: ["convex-quickstart", "convex-setup-auth", "convex-create-component", "convex-migration-helper"],
149
+ description: "Official Convex skills: schema, auth, components, migrations"
150
+ }
151
+ ],
152
+ cloudflare: [],
153
+ generic: []
154
+ };
155
+ function getSkillsForStack(stack) {
156
+ const skillMap = /* @__PURE__ */ new Map();
157
+ for (const tech of stack) {
158
+ const sources = SKILL_SOURCES[tech.toLowerCase()];
159
+ if (sources) {
160
+ for (const src of sources) {
161
+ const existing = skillMap.get(src.source) ?? /* @__PURE__ */ new Set();
162
+ if (src.skills) {
163
+ for (const sk of src.skills) existing.add(sk);
164
+ }
165
+ skillMap.set(src.source, existing);
166
+ }
167
+ }
168
+ }
169
+ return Array.from(skillMap.entries()).map(([source, skills]) => ({
170
+ source,
171
+ skills: skills.size > 0 ? [...skills].sort() : void 0,
172
+ directory: `.agents/skills/${source.split("/").pop()}`
173
+ }));
174
+ }
175
+ function getTechStacks() {
176
+ return Object.keys(SKILL_SOURCES);
177
+ }
178
+ function getSkillInstallCommands(skills) {
179
+ return skills.map((s) => {
180
+ if (s.skills && s.skills.length > 0) {
181
+ const skillFlags = s.skills.map((sk) => `--skill ${sk}`).join(" ");
182
+ return `npx skills add ${s.source} -a opencode ${skillFlags} -y`;
183
+ }
184
+ return `npx skills add ${s.source} -a opencode -y`;
185
+ });
186
+ }
187
+
188
+ // src/builders/agent.ts
189
+ import { parse as parseYaml } from "yaml";
190
+
191
+ // src/compose.ts
192
+ import { stringify as stringifyYaml } from "yaml";
193
+ function filterSections(registry, ctx) {
194
+ return registry.filter((s) => !s.condition || s.condition(ctx)).sort((a, b) => {
195
+ if (a.priority !== b.priority) return a.priority - b.priority;
196
+ return a.id.localeCompare(b.id);
197
+ });
198
+ }
199
+ function buildAgentBody(sections) {
200
+ return sections.map((s) => s.trim()).filter(Boolean).join("\n\n");
201
+ }
202
+ function buildAgentFile(frontmatter, body) {
203
+ const fm = {
204
+ name: frontmatter.name,
205
+ description: frontmatter.description,
206
+ mode: frontmatter.mode,
207
+ hidden: frontmatter.hidden,
208
+ color: frontmatter.color
209
+ };
210
+ const yamlBlock = stringifyYaml(fm).trim();
211
+ return `---
212
+ ${yamlBlock}
213
+ ---
214
+
215
+ ${body.trim()}
216
+ `;
217
+ }
218
+
219
+ // src/conditions.ts
220
+ function hasFeature(feature) {
221
+ return (ctx) => ctx.features[feature];
222
+ }
223
+ function hasStack(tech) {
224
+ return (ctx) => ctx.stack.includes(tech);
225
+ }
226
+ function isAgent(agent) {
227
+ return (ctx) => ctx.agent === agent;
228
+ }
229
+
230
+ // src/registry.ts
231
+ var AGENT_WORKFLOW_SECTIONS = [
232
+ {
233
+ id: "workflow-tasks",
234
+ file: "agents/workflow/tasks.md",
235
+ base: "platform",
236
+ priority: 100,
237
+ condition: hasFeature("tasks")
238
+ },
239
+ {
240
+ id: "workflow-memory",
241
+ file: "agents/workflow/memory.md",
242
+ base: "platform",
243
+ priority: 110,
244
+ condition: hasFeature("memory")
245
+ },
246
+ {
247
+ id: "workflow-context",
248
+ file: "agents/workflow/context.md",
249
+ base: "platform",
250
+ priority: 120,
251
+ condition: hasFeature("context")
252
+ },
253
+ {
254
+ id: "workflow-sessions",
255
+ file: "agents/workflow/sessions.md",
256
+ base: "platform",
257
+ priority: 130,
258
+ condition: hasFeature("sessions")
259
+ }
260
+ ];
261
+ var STACK_SECTIONS = [
262
+ {
263
+ id: "stack-typescript",
264
+ file: "agents/standards/typescript.md",
265
+ base: "platform",
266
+ priority: 200,
267
+ condition: hasStack("typescript")
268
+ },
269
+ {
270
+ id: "stack-nextjs",
271
+ file: "agents/standards/nextjs.md",
272
+ base: "platform",
273
+ priority: 210,
274
+ condition: hasStack("nextjs")
275
+ },
276
+ {
277
+ id: "stack-react",
278
+ file: "agents/standards/react.md",
279
+ base: "platform",
280
+ priority: 220,
281
+ condition: hasStack("react")
282
+ },
283
+ {
284
+ id: "stack-python",
285
+ file: "agents/standards/python.md",
286
+ base: "platform",
287
+ priority: 230,
288
+ condition: hasStack("python")
289
+ },
290
+ {
291
+ id: "stack-generic",
292
+ file: "agents/standards/generic.md",
293
+ base: "platform",
294
+ priority: 240
295
+ }
296
+ ];
297
+ var IDENTITY_SECTIONS = [
298
+ {
299
+ id: "identity-fullstack-dev",
300
+ file: "agents/identity/fullstack-dev.md",
301
+ base: "platform",
302
+ priority: 0,
303
+ condition: isAgent("fullstack-dev")
304
+ },
305
+ {
306
+ id: "identity-reviewer",
307
+ file: "agents/identity/reviewer.md",
308
+ base: "platform",
309
+ priority: 0,
310
+ condition: isAgent("reviewer")
311
+ },
312
+ {
313
+ id: "identity-po",
314
+ file: "agents/identity/po.md",
315
+ base: "platform",
316
+ priority: 0,
317
+ condition: isAgent("po")
318
+ },
319
+ {
320
+ id: "identity-architect",
321
+ file: "agents/identity/architect.md",
322
+ base: "platform",
323
+ priority: 0,
324
+ condition: isAgent("architect")
325
+ },
326
+ {
327
+ id: "identity-ux-ui",
328
+ file: "agents/identity/ux-ui.md",
329
+ base: "platform",
330
+ priority: 0,
331
+ condition: isAgent("ux-ui")
332
+ },
333
+ {
334
+ id: "identity-qa",
335
+ file: "agents/identity/qa.md",
336
+ base: "platform",
337
+ priority: 0,
338
+ condition: isAgent("qa")
339
+ },
340
+ {
341
+ id: "identity-adversarial-reviewer",
342
+ file: "agents/identity/adversarial-reviewer.md",
343
+ base: "platform",
344
+ priority: 0,
345
+ condition: isAgent("adversarial-reviewer")
346
+ },
347
+ {
348
+ id: "identity-docs-writer",
349
+ file: "agents/identity/docs-writer.md",
350
+ base: "platform",
351
+ priority: 0,
352
+ condition: isAgent("docs-writer")
353
+ },
354
+ {
355
+ id: "identity-memory-keeper",
356
+ file: "agents/identity/memory-keeper.md",
357
+ base: "platform",
358
+ priority: 0,
359
+ condition: isAgent("memory-keeper")
360
+ },
361
+ {
362
+ id: "identity-teamlead",
363
+ file: "agents/identity/teamlead.md",
364
+ base: "platform",
365
+ priority: 0,
366
+ condition: isAgent("teamlead")
367
+ }
368
+ ];
369
+ function getAgentSections(platform) {
370
+ return [
371
+ ...IDENTITY_SECTIONS,
372
+ ...AGENT_WORKFLOW_SECTIONS,
373
+ ...STACK_SECTIONS
374
+ ];
375
+ }
376
+
377
+ // src/utils/cache.ts
378
+ import { readFile } from "fs/promises";
379
+ var cache = /* @__PURE__ */ new Map();
380
+ async function loadTemplate(filePath) {
381
+ const cached = cache.get(filePath);
382
+ if (cached !== void 0) return cached;
383
+ const content = await readFile(filePath, "utf-8");
384
+ cache.set(filePath, content);
385
+ return content;
386
+ }
387
+ function clearCache() {
388
+ cache.clear();
389
+ }
390
+
391
+ // src/utils/resolve.ts
392
+ function resolveVars(text2, vars) {
393
+ const unresolved = [];
394
+ const resolved = text2.replace(/\$\{(\w+)\}/g, (match, key) => {
395
+ if (!(key in vars)) {
396
+ unresolved.push(key);
397
+ return match;
398
+ }
399
+ return vars[key] ?? match;
400
+ });
401
+ if (unresolved.length > 0) {
402
+ throw new Error(
403
+ `Unresolved variables: ${unresolved.join(", ")}. Available vars: ${Object.keys(vars).join(", ") || "(none)"}`
404
+ );
405
+ }
406
+ return resolved;
407
+ }
408
+
409
+ // src/builders/pipeline.ts
410
+ var AGENT_ROLES = {
411
+ "po": { title: "Product Owner", description: "Scopes features into tasks with acceptance criteria. Creates and manages the task backlog." },
412
+ "architect": { title: "System Architect", description: "Designs architecture, data flow, and contracts. Creates Architecture Decision Records (ADRs)." },
413
+ "fullstack-dev": { title: "Full-Stack Developer", description: "Implements features end-to-end. Writes code, tests, and verifies quality gates." },
414
+ "reviewer": { title: "Code Reviewer", description: "Reviews code for correctness, security, and conventions. Returns GREEN or RED with findings." },
415
+ "ux-ui": { title: "UX/UI Engineer", description: "Reviews UI for accessibility, visual consistency, and user experience quality." },
416
+ "qa": { title: "QA Engineer", description: "Tests implementations against acceptance criteria. Reports PASS, NEEDS_IMPROVEMENT, or FAIL." },
417
+ "adversarial-reviewer": { title: "Adversarial Reviewer", description: "Second-pass reviewer. Hunts for missed bugs, security gaps, and edge cases." },
418
+ "docs-writer": { title: "Documentation Writer", description: "Maintains project context documentation. Updates docs surgically based on code changes." },
419
+ "memory-keeper": { title: "Memory Keeper", description: "Records session logs, decisions, and conventions. Maintains project memory across sessions." },
420
+ "teamlead": { title: "Team Lead", description: "Orchestrates the squad. Only agent that talks to the user. Assesses work, dispatches pipelines, synthesizes results." }
421
+ };
422
+ function agentRef(agent) {
423
+ return `@squad-${agent}`;
424
+ }
425
+ function agentLine(agent) {
426
+ const role = AGENT_ROLES[agent];
427
+ if (!role) return "";
428
+ return `- ${agentRef(agent)} \u2014 ${role.title}. ${role.description}`;
429
+ }
430
+ function buildPipelineForLevel(level, steps, hasParallel, description) {
431
+ const pipelineAgents = steps.filter((a) => a !== "teamlead");
432
+ if (pipelineAgents.length === 0) return "";
433
+ const parallelIndex = hasParallel ? pipelineAgents.findIndex(
434
+ (a) => a === "reviewer" || a === "ux-ui" || a === "qa"
435
+ ) : -1;
436
+ const formatted = pipelineAgents.map((a, i) => {
437
+ if (parallelIndex !== -1 && i >= parallelIndex && (a === "reviewer" || a === "ux-ui" || a === "qa")) {
438
+ return ` ${agentRef(a)} \u2016`;
439
+ }
440
+ return ` ${i + 1}. ${agentRef(a)}`;
441
+ });
442
+ let pipelineStr = formatted.join("\n");
443
+ if (parallelIndex !== -1) {
444
+ const parallelAgents = pipelineAgents.filter(
445
+ (a, i) => i >= parallelIndex && (a === "reviewer" || a === "ux-ui" || a === "qa")
446
+ );
447
+ const prevNames = pipelineAgents.slice(0, parallelIndex);
448
+ const lastBeforeParallel = prevNames.length > 0 ? `${agentRef(prevNames[prevNames.length - 1])}` : "the previous step";
449
+ pipelineStr += `
450
+
451
+ **Parallel:** ${parallelAgents.map((a) => agentRef(a)).join(", ")} run simultaneously after ${lastBeforeParallel}. Dispatch them in a single message.`;
452
+ }
453
+ return `### ${level}
454
+ **When:** ${description}
455
+ **Pipeline:**
456
+ ${pipelineStr}`;
457
+ }
458
+ function buildPipelineSection(agents, features) {
459
+ const has = (a) => agents.includes(a);
460
+ const lines = [];
461
+ lines.push("## Available Squad Agents");
462
+ const displayOrder = [
463
+ "teamlead",
464
+ "po",
465
+ "architect",
466
+ "fullstack-dev",
467
+ "reviewer",
468
+ "ux-ui",
469
+ "qa",
470
+ "adversarial-reviewer",
471
+ "docs-writer",
472
+ "memory-keeper"
473
+ ];
474
+ for (const agent of displayOrder) {
475
+ if (has(agent)) {
476
+ lines.push(agentLine(agent));
477
+ }
478
+ }
479
+ lines.push("");
480
+ lines.push("## Work Type Assessment");
481
+ lines.push("");
482
+ lines.push("Before dispatching, classify the work into one of five levels:");
483
+ lines.push("");
484
+ lines.push("| Level | Criteria |");
485
+ lines.push("|---|---|");
486
+ lines.push("| **TINY** | Single-line fix, typo, copy change, color token swap. Zero logic change. Zero new files. A non-technical person could verify it. |");
487
+ lines.push("| **SMALL** | Minor bug fix, simple new component, single new field. Changes behavior in one isolated area. No schema changes. 1-2 files. |");
488
+ lines.push("| **MEDIUM** | New feature, new page/route, new data model, new component family (3+ files). Creates something new or changes cross-module behavior. |");
489
+ lines.push("| **SECURITY-SENSITIVE** | Any change touching auth, API keys, environment variables, file upload, webhooks, user data, or permissions. This is true EVEN IF the code change is one line. |");
490
+ lines.push("| **MAJOR** | New service integration, architecture pattern change, breaking schema change, data migration, significant refactor. High risk, wide blast radius. |");
491
+ lines.push("");
492
+ lines.push("**Rules for classifying:**");
493
+ lines.push("- When unsure between two levels, pick the HIGHER one. Under-process is a production bug. Over-process is a few extra agent calls.");
494
+ lines.push("- If the change touches auth, security, or user data at all, it is SECURITY-SENSITIVE regardless of size.");
495
+ lines.push('- If the user describes it as "small" but it touches 3+ files or crosses module boundaries, escalate to MEDIUM.');
496
+ lines.push("- A TINY task is genuinely atomic \u2014 one line, one file, no logic change. If you hesitate, it's not TINY.");
497
+ lines.push("");
498
+ lines.push("## Pipelines by Work Type");
499
+ const hasTasks = features.tasks && has("po");
500
+ const tinyAgents = ["fullstack-dev", "reviewer"];
501
+ if (has("adversarial-reviewer")) tinyAgents.push("adversarial-reviewer");
502
+ if (has("memory-keeper")) tinyAgents.push("memory-keeper");
503
+ lines.push(buildPipelineForLevel(
504
+ "TINY",
505
+ tinyAgents,
506
+ false,
507
+ "Single-character or single-line fix. Typo. Copy change. Zero logic. Zero new files."
508
+ ));
509
+ lines.push("");
510
+ const smallAgents = [];
511
+ if (hasTasks && has("po")) {
512
+ smallAgents.push("po");
513
+ smallAgents.push("fullstack-dev");
514
+ } else {
515
+ smallAgents.push("fullstack-dev");
516
+ }
517
+ smallAgents.push("reviewer");
518
+ if (has("ux-ui")) smallAgents.push("ux-ui");
519
+ if (has("qa")) smallAgents.push("qa");
520
+ if (has("adversarial-reviewer")) smallAgents.push("adversarial-reviewer");
521
+ if (has("docs-writer")) smallAgents.push("docs-writer");
522
+ if (has("memory-keeper")) smallAgents.push("memory-keeper");
523
+ const smallDesc = hasTasks ? "Minor bug fix, simple component, small feature. PO scopes it, dev implements, reviewer checks." : "Minor bug fix, simple component, small feature. Dev implements, reviewer checks.";
524
+ lines.push(buildPipelineForLevel(
525
+ "SMALL",
526
+ smallAgents,
527
+ has("reviewer") && (has("ux-ui") || has("qa")),
528
+ smallDesc
529
+ ));
530
+ lines.push("");
531
+ const mediumAgents = [];
532
+ if (hasTasks && has("po")) {
533
+ mediumAgents.push("po");
534
+ }
535
+ if (has("architect")) mediumAgents.push("architect");
536
+ mediumAgents.push("fullstack-dev");
537
+ if (has("reviewer")) mediumAgents.push("reviewer");
538
+ if (has("ux-ui")) mediumAgents.push("ux-ui");
539
+ if (has("qa")) mediumAgents.push("qa");
540
+ if (has("adversarial-reviewer")) mediumAgents.push("adversarial-reviewer");
541
+ if (has("docs-writer")) mediumAgents.push("docs-writer");
542
+ if (has("memory-keeper")) mediumAgents.push("memory-keeper");
543
+ lines.push(buildPipelineForLevel(
544
+ "MEDIUM",
545
+ mediumAgents,
546
+ true,
547
+ "New feature, new page/route, new component family. Full pipeline with parallel quality gates."
548
+ ));
549
+ lines.push("");
550
+ lines.push(buildPipelineForLevel(
551
+ "SECURITY-SENSITIVE",
552
+ mediumAgents,
553
+ true,
554
+ "Any change touching auth, secrets, user data, file upload, or permissions. EVEN IF one line. Heightened security scrutiny on all review stages."
555
+ ));
556
+ lines.push("");
557
+ lines.push(" **Extra:** The reviewer and adversarial reviewer apply heightened scrutiny on all security surfaces. Adversarial reviewer must explicitly check auth bypass, data leak vectors, injection surfaces, and secret handling.");
558
+ lines.push("");
559
+ lines.push(buildPipelineForLevel(
560
+ "MAJOR",
561
+ mediumAgents,
562
+ true,
563
+ "New service integration, architecture change, breaking schema change, data migration. High risk, wide blast radius."
564
+ ));
565
+ lines.push("");
566
+ if (has("architect")) {
567
+ lines.push(" **Extra:** Architect must produce full design before any implementation. Reviewer must do architecture-level review in addition to code review. QA must test full regression.");
568
+ }
569
+ if (has("memory-keeper")) {
570
+ lines.push(" **Extra:** Memory Keeper must record a detailed session log with all decisions and risks.");
571
+ }
572
+ if (!hasTasks) {
573
+ lines.push("");
574
+ lines.push("## User-Driven Workflow");
575
+ lines.push("");
576
+ lines.push("This squad has no Product Owner. The user defines tasks directly. When the user gives you a well-defined task, skip the PO step and dispatch the developer directly. When the user gives a vague request, interview them to extract scope before dispatching.");
577
+ }
578
+ if (!has("memory-keeper")) {
579
+ lines.push("");
580
+ lines.push("## Memory Management");
581
+ lines.push("");
582
+ lines.push("This squad has no Memory Keeper. At the end of each session, summarize what was accomplished, decisions made, and what remains open. This summary IS the session record.");
583
+ }
584
+ return lines.join("\n");
585
+ }
586
+
587
+ // src/utils/paths.ts
588
+ import { resolve } from "path";
589
+ import { fileURLToPath } from "url";
590
+ import { existsSync } from "fs";
591
+ var __dirname2 = fileURLToPath(new URL(".", import.meta.url));
592
+ var bundledPath = resolve(__dirname2, "..", "sections");
593
+ var devPath = resolve(__dirname2, "..", "..", "sections");
594
+ var SECTIONS_DIR = existsSync(bundledPath) ? bundledPath : devPath;
595
+
596
+ // src/builders/agent.ts
597
+ async function buildAgent(agent, ctx, pipelineData) {
598
+ const registry = getAgentSections(ctx.platform);
599
+ const activeSections = filterSections(registry, ctx);
600
+ const resolvedVars = { ...ctx.vars };
601
+ if (agent === "teamlead" && pipelineData) {
602
+ const pipelineContent = buildPipelineSection(pipelineData.agents, pipelineData.features);
603
+ resolvedVars["WORKFLOW_PIPELINE"] = pipelineContent;
604
+ }
605
+ const sectionContents = [];
606
+ for (const section of activeSections) {
607
+ const filePath = section.base === "platform" ? `${SECTIONS_DIR}/${ctx.platform}/${section.file}` : `${SECTIONS_DIR}/shared/${section.file}`;
608
+ let content = await loadTemplate(filePath);
609
+ content = resolveVars(content, resolvedVars);
610
+ sectionContents.push(content);
611
+ }
612
+ const body = buildAgentBody(sectionContents);
613
+ const frontmatterPath = `${SECTIONS_DIR}/shared/frontmatter/${agent}.yaml`;
614
+ let frontmatterYaml = await loadTemplate(frontmatterPath);
615
+ frontmatterYaml = resolveVars(frontmatterYaml, resolvedVars);
616
+ const parsed = parseYaml(frontmatterYaml);
617
+ const frontmatter = {
618
+ name: String(parsed.name ?? ""),
619
+ description: String(parsed.description ?? "").replace(/\n/g, " "),
620
+ mode: parsed.mode ?? "subagent",
621
+ hidden: Boolean(parsed.hidden),
622
+ color: String(parsed.color ?? "")
623
+ };
624
+ const fileContent = buildAgentFile(frontmatter, body);
625
+ const fileName = `squad-${agent}.md`;
626
+ return {
627
+ path: `.opencode/agents/${fileName}`,
628
+ content: fileContent
629
+ };
630
+ }
631
+
632
+ // src/builders/workflow.ts
633
+ var WORKFLOW_FILES = ["state", "tasks", "memory", "context", "sessions"];
634
+ var FEATURE_KEY = {
635
+ state: "state",
636
+ tasks: "tasks",
637
+ memory: "memory",
638
+ context: "context",
639
+ sessions: "sessions"
640
+ };
641
+ async function buildWorkflowFiles(platform, features, vars) {
642
+ const files = [];
643
+ for (const name of WORKFLOW_FILES) {
644
+ if (!features[FEATURE_KEY[name]]) continue;
645
+ const filePath = `${SECTIONS_DIR}/${platform}/workflow/${name}.md`;
646
+ let content = await loadTemplate(filePath);
647
+ content = resolveVars(content, vars);
648
+ files.push({
649
+ path: `.opencode/workflow/${name}.md`,
650
+ content
651
+ });
652
+ }
653
+ return files;
654
+ }
655
+
656
+ // src/builders/tasks.ts
657
+ async function buildTaskFiles(vars) {
658
+ const templatePath = `${SECTIONS_DIR}/shared/task-template.md`;
659
+ let template = await loadTemplate(templatePath);
660
+ template = resolveVars(template, vars);
661
+ const task01 = template.replaceAll("task-XX", "task-01").replaceAll("# Task XX:", "# Task 01:");
662
+ return [
663
+ {
664
+ path: ".agent/tasks/TASK_TEMPLATE.md",
665
+ content: template
666
+ },
667
+ {
668
+ path: ".agent/tasks/task-01.md",
669
+ content: task01
670
+ }
671
+ ];
672
+ }
673
+
674
+ // src/builders/context.ts
675
+ var CONTEXT_STACKS = ["nextjs", "python", "generic"];
676
+ var CONTEXT_FILES = ["stack", "conventions", "architecture"];
677
+ async function buildContextDocs(platform, features, stack, vars) {
678
+ if (!features.context) return [];
679
+ const files = [];
680
+ const relevantStacks = stack.filter((s) => CONTEXT_STACKS.includes(s));
681
+ if (relevantStacks.length === 0) {
682
+ relevantStacks.push("generic");
683
+ }
684
+ for (const tech of [...new Set(relevantStacks)]) {
685
+ for (const file of CONTEXT_FILES) {
686
+ try {
687
+ const filePath = `${SECTIONS_DIR}/${platform}/context/${tech}/${file}.md`;
688
+ let content = await loadTemplate(filePath);
689
+ content = resolveVars(content, vars);
690
+ files.push({
691
+ path: `.agent/context/project/${tech}-${file}.md`,
692
+ content
693
+ });
694
+ } catch {
695
+ }
696
+ }
697
+ }
698
+ return files;
699
+ }
700
+ async function buildMemoryFiles(features, vars) {
701
+ if (!features.memory) return [];
702
+ const files = [];
703
+ const memoryFiles = ["decisions", "conventions"];
704
+ if (features.sessions) {
705
+ memoryFiles.push("patterns");
706
+ }
707
+ for (const name of memoryFiles) {
708
+ const filePath = `${SECTIONS_DIR}/shared/memory/${name}.md`;
709
+ let content = await loadTemplate(filePath);
710
+ content = resolveVars(content, vars);
711
+ files.push({
712
+ path: `.agent/memory/${name}.md`,
713
+ content
714
+ });
715
+ }
716
+ return files;
717
+ }
718
+
719
+ // src/builders/config.ts
720
+ async function buildOpenCodeJson(config, agents) {
721
+ const agentAllowList = agents.map((a) => `squad-${a}`);
722
+ const json = {
723
+ permissions: {
724
+ bash: {
725
+ allow: ["npm *", "pnpm *", "npx *", "git *", "node *", "python *", "tsx *"]
726
+ },
727
+ edit: {
728
+ deny: [".opencode/agents/*"]
729
+ },
730
+ task: {
731
+ allow: agentAllowList
732
+ },
733
+ external_directory: {
734
+ allow: ["/var/folders/**"]
735
+ }
736
+ }
737
+ };
738
+ return {
739
+ path: "opencode.json",
740
+ content: JSON.stringify(json, null, 2) + "\n"
741
+ };
742
+ }
743
+ async function buildAgentsMd(config, agents) {
744
+ const templatePath = `${SECTIONS_DIR}/opencode/config/agents.md`;
745
+ let content = await loadTemplate(templatePath);
746
+ const agentList = agents.map((a) => `- **squad-${a}** \u2014 ${getAgentDesc(a)}`).join("\n");
747
+ content = content.replace("${PROJECT_NAME}", config.projectName);
748
+ content = content.replace("${AGENT_LIST}", agentList);
749
+ return {
750
+ path: "AGENTS.md",
751
+ content
752
+ };
753
+ }
754
+ function getAgentDesc(agent) {
755
+ const descriptions = {
756
+ "fullstack-dev": "Full-Stack Developer. Implements features end-to-end.",
757
+ "reviewer": "Code Reviewer. GREEN/RED review of completed code.",
758
+ "po": "Product Owner. Creates and manages task files.",
759
+ "architect": "System Architect. Designs systems and architecture.",
760
+ "ux-ui": "UX/UI Engineer. Reviews UI against design standards.",
761
+ "qa": "QA Engineer. Tests implementations against acceptance criteria.",
762
+ "adversarial-reviewer": "Adversarial Reviewer. Second-pass bug hunting.",
763
+ "docs-writer": "Documentation Writer. Maintains project context docs.",
764
+ "memory-keeper": "Memory Keeper. Manages project memory across sessions.",
765
+ "teamlead": "Team Lead. Orchestrates the squad. The only agent the user talks to."
766
+ };
767
+ return descriptions[agent] ?? "";
768
+ }
769
+
770
+ // src/utils/write.ts
771
+ import { access, mkdir, writeFile, readFile as readFile2 } from "fs/promises";
772
+ import { dirname, relative, resolve as resolve2 } from "path";
773
+ import pc from "picocolors";
774
+ var promptUser = null;
775
+ async function writeFileSafe(projectDir, filePath, content, dryRun) {
776
+ const absolutePath = resolve2(projectDir, filePath);
777
+ const relPath = relative(projectDir, absolutePath);
778
+ if (dryRun) {
779
+ console.log(pc.dim(` [dry-run] Would write: ${relPath}`));
780
+ return "dry-run";
781
+ }
782
+ await mkdir(dirname(absolutePath), { recursive: true });
783
+ const exists = await access(absolutePath).then(() => true).catch(() => false);
784
+ if (exists) {
785
+ const existing = await readFile2(absolutePath, "utf-8");
786
+ if (existing === content) {
787
+ return "skipped";
788
+ }
789
+ if (promptUser) {
790
+ const overwrite = await promptUser(
791
+ `File ${pc.yellow(relPath)} already exists. Overwrite?`
792
+ );
793
+ if (!overwrite) return "skipped";
794
+ }
795
+ await writeFile(absolutePath, content, "utf-8");
796
+ return "overwritten";
797
+ }
798
+ await writeFile(absolutePath, content, "utf-8");
799
+ return "created";
800
+ }
801
+
802
+ // src/wizard.ts
803
+ import { resolve as resolve3 } from "path";
804
+ import { mkdir as mkdir2, access as access2 } from "fs/promises";
805
+ import { execSync } from "child_process";
806
+ async function runWizard() {
807
+ intro(pc2.bold(pc2.cyan("ai-squad init")));
808
+ const projectName = await text({
809
+ message: "Project name?",
810
+ placeholder: "my-awesome-app",
811
+ validate(value) {
812
+ if (!value) return "Required.";
813
+ if (!/^[a-z0-9-]+$/.test(value)) return "Lowercase letters, numbers, and hyphens only.";
814
+ }
815
+ });
816
+ if (isCancel(projectName)) {
817
+ cancel("Cancelled.");
818
+ process.exit(0);
819
+ }
820
+ const allTech = getTechStacks();
821
+ const techStack = await multiselect({
822
+ message: "Tech stack? (space to select, enter to confirm)",
823
+ options: allTech.map((t) => ({ value: t, label: t.charAt(0).toUpperCase() + t.slice(1) })),
824
+ required: false
825
+ });
826
+ if (isCancel(techStack)) {
827
+ cancel("Cancelled.");
828
+ process.exit(0);
829
+ }
830
+ const squadChoice = await select({
831
+ message: "Choose a squad:",
832
+ options: SQUADS.map((s2) => ({
833
+ value: s2.id,
834
+ label: s2.name,
835
+ hint: s2.description
836
+ }))
837
+ });
838
+ if (isCancel(squadChoice)) {
839
+ cancel("Cancelled.");
840
+ process.exit(0);
841
+ }
842
+ const packageLevels = [
843
+ { value: "minimal", label: "Minimal", hint: "Just the agents" },
844
+ { value: "essential", label: "Essential", hint: "+ tasks + state tracking" },
845
+ { value: "standard", label: "Standard", hint: "+ memory + context docs" },
846
+ { value: "comprehensive", label: "Comprehensive", hint: "+ sessions + full context" }
847
+ ];
848
+ const packageLevel = await select({
849
+ message: "Infrastructure level?",
850
+ options: packageLevels
851
+ });
852
+ if (isCancel(packageLevel)) {
853
+ cancel("Cancelled.");
854
+ process.exit(0);
855
+ }
856
+ const recommendedSkills = getSkillsForStack(techStack);
857
+ let installSkills = false;
858
+ if (recommendedSkills.length > 0) {
859
+ const result = await select({
860
+ message: `Install ${recommendedSkills.length} recommended skills?`,
861
+ options: [
862
+ { value: true, label: "Yes, install skills", hint: recommendedSkills.map((s2) => s2.source).join(", ") },
863
+ { value: false, label: "Skip for now" }
864
+ ]
865
+ });
866
+ if (isCancel(result)) {
867
+ cancel("Cancelled.");
868
+ process.exit(0);
869
+ }
870
+ installSkills = Boolean(result);
871
+ }
872
+ const platformChoice = PLATFORMS[0];
873
+ const platform = platformChoice;
874
+ const config = {
875
+ projectName,
876
+ platform,
877
+ techStack: techStack ?? [],
878
+ squad: squadChoice,
879
+ packageLevel,
880
+ installSkills,
881
+ dryRun: false
882
+ };
883
+ const features = resolveFeatures(config.packageLevel);
884
+ const squad = getSquad(config.squad);
885
+ const infraAgents = getInfraAgents(config.packageLevel);
886
+ const orchestratorAgents = getOrchestratorInfra(squad.agents);
887
+ const allAgents = [.../* @__PURE__ */ new Set([...squad.agents, ...infraAgents, ...orchestratorAgents])];
888
+ const skills = config.installSkills ? getSkillsForStack(config.techStack) : [];
889
+ const featureList = Object.entries(features).filter(([, v]) => v).map(([k]) => k).join(", ") || "none";
890
+ note(
891
+ `${pc2.bold(config.projectName)}
892
+ Stack: ${config.techStack.join(", ") || "none"}
893
+ Squad: ${squad.name} (${allAgents.map((a) => `squad-${a}`).join(", ")})
894
+ Level: ${config.packageLevel}
895
+ Features: ${featureList}
896
+ ${skills.length > 0 ? `Skills: ${skills.map((s2) => s2.source).join(", ")}
897
+ ` : ""}`,
898
+ "Review"
899
+ );
900
+ const confirmGen = await select({
901
+ message: "Generate project?",
902
+ options: [
903
+ { value: "yes", label: "Yes, generate" },
904
+ { value: "dry-run", label: "Dry run (preview only)" },
905
+ { value: "no", label: "Cancel" }
906
+ ]
907
+ });
908
+ if (isCancel(confirmGen) || confirmGen === "no") {
909
+ cancel("Cancelled.");
910
+ process.exit(0);
911
+ }
912
+ const dryRun = confirmGen === "dry-run";
913
+ const projectDir = resolve3(process.cwd(), config.projectName);
914
+ if (!dryRun) {
915
+ try {
916
+ await access2(projectDir);
917
+ console.log(pc2.yellow(`
918
+ Directory ${config.projectName} already exists. Files will be merged.`));
919
+ } catch {
920
+ await mkdir2(projectDir, { recursive: true });
921
+ }
922
+ }
923
+ const s = spinner();
924
+ s.start("Generating agent infrastructure...");
925
+ try {
926
+ const vars = {
927
+ PROJECT_NAME: config.projectName
928
+ };
929
+ const files = [];
930
+ for (const agent of allAgents) {
931
+ const ctx = {
932
+ projectName: config.projectName,
933
+ platform: config.platform,
934
+ agent,
935
+ features,
936
+ stack: config.techStack,
937
+ vars: { ...vars, AGENT_NAME: `squad-${agent}` }
938
+ };
939
+ const pipelineData = agent === "teamlead" ? { agents: allAgents, features } : void 0;
940
+ const file = await buildAgent(agent, ctx, pipelineData);
941
+ files.push(file);
942
+ }
943
+ const workflowFiles = await buildWorkflowFiles(config.platform, features, vars);
944
+ files.push(...workflowFiles);
945
+ if (features.tasks) {
946
+ const taskFiles = await buildTaskFiles(vars);
947
+ files.push(...taskFiles);
948
+ }
949
+ if (features.memory) {
950
+ const memoryFiles = await buildMemoryFiles(features, vars);
951
+ files.push(...memoryFiles);
952
+ }
953
+ if (features.context) {
954
+ const contextFiles = await buildContextDocs(config.platform, features, config.techStack, vars);
955
+ files.push(...contextFiles);
956
+ }
957
+ const openCodeJson = await buildOpenCodeJson(config, allAgents);
958
+ files.push(openCodeJson);
959
+ const agentsMd = await buildAgentsMd(config, allAgents);
960
+ files.push(agentsMd);
961
+ clearCache();
962
+ let created = 0;
963
+ let skipped = 0;
964
+ for (const file of files) {
965
+ const result = await writeFileSafe(projectDir, file.path, file.content, dryRun);
966
+ if (result === "created" || result === "overwritten") created++;
967
+ if (result === "skipped") skipped++;
968
+ }
969
+ s.stop(dryRun ? "Dry run complete" : "Generation complete");
970
+ if (skills.length > 0 && config.installSkills) {
971
+ const commands = getSkillInstallCommands(skills);
972
+ if (dryRun) {
973
+ console.log(pc2.dim("\n Skills to install:"));
974
+ for (const cmd of commands) {
975
+ console.log(pc2.dim(` [dry-run] ${cmd}`));
976
+ }
977
+ } else {
978
+ console.log("");
979
+ for (const cmd of commands) {
980
+ console.log(pc2.dim(` Installing: ${cmd.replace(" -y", "")}`));
981
+ try {
982
+ execSync(cmd, { cwd: projectDir, stdio: "pipe" });
983
+ console.log(pc2.green(` \u2713 Installed`));
984
+ } catch {
985
+ console.log(pc2.yellow(` \u26A0 Skipped \u2014 npx skills may not be available`));
986
+ }
987
+ }
988
+ }
989
+ }
990
+ const summary = [
991
+ `${pc2.green("Created:")} ${created} files`,
992
+ ...skipped > 0 ? [`${pc2.yellow("Skipped:")} ${skipped} files (unchanged)`] : [],
993
+ `${pc2.dim("Total:")} ${files.length} files`,
994
+ "",
995
+ `${pc2.dim("Agents:")} ${allAgents.map((a) => `squad-${a}`).join(", ")}`
996
+ ].join("\n");
997
+ outro(summary);
998
+ if (!dryRun) {
999
+ console.log(pc2.dim(`
1000
+ Next: cd ${config.projectName} && opencode`));
1001
+ }
1002
+ } catch (err) {
1003
+ s.stop("Generation failed");
1004
+ console.error(pc2.red(err instanceof Error ? err.message : String(err)));
1005
+ process.exit(1);
1006
+ }
1007
+ }
1008
+
1009
+ // src/index.ts
1010
+ runWizard().catch((err) => {
1011
+ console.error(err instanceof Error ? err.message : String(err));
1012
+ process.exit(1);
1013
+ });