@brainst0rm/cli 0.13.0 → 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 (57) hide show
  1. package/dist/{App-SSKWB7CT.js → App-HGJSYIVS.js} +133 -62
  2. package/dist/App-HGJSYIVS.js.map +1 -0
  3. package/dist/{App-DPXJYXKH.js → App-XCJW3A4I.js} +133 -62
  4. package/dist/App-XCJW3A4I.js.map +1 -0
  5. package/dist/brainstorm.js +1428 -150
  6. package/dist/brainstorm.js.map +1 -1
  7. package/dist/{chunk-7D4SUZUM.js → chunk-PR4QN5HX.js} +6 -1
  8. package/dist/{chunk-ZWE3DS7E.js → chunk-SEGVTWSK.js} +6 -1
  9. package/dist/{chunk-5NA3GH6X.js → chunk-WYQU3HAB.js} +175 -10
  10. package/dist/chunk-WYQU3HAB.js.map +1 -0
  11. package/dist/{chunk-55ITCWZZ.js → chunk-Z2QIGVYT.js} +175 -10
  12. package/dist/chunk-Z2QIGVYT.js.map +1 -0
  13. package/dist/{dist-GNHTH2DH.js → dist-3BC75XHW.js} +2 -2
  14. package/dist/dist-42TQFHMB.js +1640 -0
  15. package/dist/dist-42TQFHMB.js.map +1 -0
  16. package/dist/dist-BULARAL3.js +7308 -0
  17. package/dist/dist-BULARAL3.js.map +1 -0
  18. package/dist/{dist-JUDVPE7G.js → dist-ET6CSS7O.js} +2 -2
  19. package/dist/{dist-DUDO3RDM.js → dist-GVK5Q4YK.js} +62 -16
  20. package/dist/{dist-V5DTSTKJ.js.map → dist-GVK5Q4YK.js.map} +1 -1
  21. package/dist/{dist-V5DTSTKJ.js → dist-K7BDAMTO.js} +62 -16
  22. package/dist/{dist-DUDO3RDM.js.map → dist-K7BDAMTO.js.map} +1 -1
  23. package/dist/{dist-WLTQTLFO.js → dist-OYQTULIU.js} +2 -2
  24. package/dist/dist-S5JYXFUW.js +7307 -0
  25. package/dist/dist-S5JYXFUW.js.map +1 -0
  26. package/dist/{dist-YIGU37Q2.js → dist-Y25MC2VO.js} +2 -2
  27. package/dist/dist-Z4SBSK4Q.js +1639 -0
  28. package/dist/dist-Z4SBSK4Q.js.map +1 -0
  29. package/dist/index.js +1428 -150
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp-server-CROPNYHI.js +4252 -0
  32. package/dist/mcp-server-CROPNYHI.js.map +1 -0
  33. package/dist/mcp-server-IJVEG6CS.js +4251 -0
  34. package/dist/mcp-server-IJVEG6CS.js.map +1 -0
  35. package/dist/{recorder-D6ILEOZP.js → recorder-33N4U6TO.js} +2 -2
  36. package/dist/{recorder-SPYYF4DL.js → recorder-APOOXJYA.js} +2 -2
  37. package/dist/{roles-2DGF4PZU.js → roles-GKDCLP5G.js} +2 -2
  38. package/dist/{roles-UIPX7GBC.js → roles-UUIISXEW.js} +2 -2
  39. package/dist/{slash-PDWKCZOQ.js → slash-4XSR3SJD.js} +3 -3
  40. package/dist/{slash-ZDC4DKL4.js → slash-CVWH3LTR.js} +3 -3
  41. package/package.json +4 -2
  42. package/dist/App-DPXJYXKH.js.map +0 -1
  43. package/dist/App-SSKWB7CT.js.map +0 -1
  44. package/dist/chunk-55ITCWZZ.js.map +0 -1
  45. package/dist/chunk-5NA3GH6X.js.map +0 -1
  46. /package/dist/{chunk-7D4SUZUM.js.map → chunk-PR4QN5HX.js.map} +0 -0
  47. /package/dist/{chunk-ZWE3DS7E.js.map → chunk-SEGVTWSK.js.map} +0 -0
  48. /package/dist/{dist-GNHTH2DH.js.map → dist-3BC75XHW.js.map} +0 -0
  49. /package/dist/{dist-JUDVPE7G.js.map → dist-ET6CSS7O.js.map} +0 -0
  50. /package/dist/{dist-WLTQTLFO.js.map → dist-OYQTULIU.js.map} +0 -0
  51. /package/dist/{dist-YIGU37Q2.js.map → dist-Y25MC2VO.js.map} +0 -0
  52. /package/dist/{recorder-D6ILEOZP.js.map → recorder-33N4U6TO.js.map} +0 -0
  53. /package/dist/{recorder-SPYYF4DL.js.map → recorder-APOOXJYA.js.map} +0 -0
  54. /package/dist/{roles-2DGF4PZU.js.map → roles-GKDCLP5G.js.map} +0 -0
  55. /package/dist/{roles-UIPX7GBC.js.map → roles-UUIISXEW.js.map} +0 -0
  56. /package/dist/{slash-PDWKCZOQ.js.map → slash-4XSR3SJD.js.map} +0 -0
  57. /package/dist/{slash-ZDC4DKL4.js.map → slash-CVWH3LTR.js.map} +0 -0
@@ -0,0 +1,1639 @@
1
+ import "./chunk-PR4QN5HX.js";
2
+
3
+ // ../onboard/dist/index.js
4
+ import { createLogger } from "@brainst0rm/shared";
5
+ import { analyzeProject } from "@brainst0rm/ingest";
6
+ import { existsSync } from "fs";
7
+ import { execFileSync } from "child_process";
8
+ import { existsSync as existsSync2, readFileSync } from "fs";
9
+ import { join } from "path";
10
+ import { writeFileSync, mkdirSync, existsSync as existsSync3 } from "fs";
11
+ import { join as join2 } from "path";
12
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
13
+ import { join as join3 } from "path";
14
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
15
+ import { join as join4 } from "path";
16
+ import { writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
17
+ import { join as join5, basename } from "path";
18
+ var ALL_PHASES = [
19
+ "static-analysis",
20
+ "deep-exploration",
21
+ "team-assembly",
22
+ "routing-rules",
23
+ "workflow-gen",
24
+ "brainstorm-md",
25
+ "verification"
26
+ ];
27
+ var PHASE_LABELS = {
28
+ "static-analysis": "Static Analysis",
29
+ "deep-exploration": "Deep Exploration",
30
+ "team-assembly": "Team Assembly",
31
+ "routing-rules": "Routing Rules",
32
+ "workflow-gen": "Workflow Generation",
33
+ "brainstorm-md": "BRAINSTORM.md",
34
+ verification: "Verification"
35
+ };
36
+ function inferBudget(analysis) {
37
+ const files = analysis.summary.totalFiles;
38
+ const complexity = analysis.summary.avgComplexity;
39
+ let base;
40
+ if (files < 50) base = 2;
41
+ else if (files < 500) base = 5;
42
+ else base = 10;
43
+ const multiplier = complexity > 15 ? 1.5 : 1;
44
+ return Math.round(base * multiplier * 100) / 100;
45
+ }
46
+ function createBudgetTracker(totalBudget) {
47
+ let spent = 0;
48
+ return {
49
+ get total() {
50
+ return totalBudget;
51
+ },
52
+ get spent() {
53
+ return Math.round(spent * 1e3) / 1e3;
54
+ },
55
+ get remaining() {
56
+ return Math.round((totalBudget - spent) * 1e3) / 1e3;
57
+ },
58
+ canAfford(estimatedCost) {
59
+ return spent + estimatedCost <= totalBudget;
60
+ },
61
+ record(cost) {
62
+ spent += cost;
63
+ return spent <= totalBudget;
64
+ }
65
+ };
66
+ }
67
+ var PHASE_COST_ESTIMATES = {
68
+ "static-analysis": 0,
69
+ "deep-exploration": 0.25,
70
+ "team-assembly": 0.4,
71
+ "routing-rules": 0.06,
72
+ "workflow-gen": 0.08,
73
+ "brainstorm-md": 0.15,
74
+ verification: 0
75
+ };
76
+ function runStaticAnalysis(projectPath) {
77
+ const analysis = analyzeProject(projectPath);
78
+ const gitSummary = collectGitSummary(projectPath);
79
+ return { analysis, gitSummary };
80
+ }
81
+ function collectGitSummary(projectPath) {
82
+ const gitDir = `${projectPath}/.git`;
83
+ if (!existsSync(gitDir)) return "";
84
+ try {
85
+ const log2 = execFileSync(
86
+ "git",
87
+ ["log", "--oneline", "--stat", "--no-color", "-30"],
88
+ {
89
+ cwd: projectPath,
90
+ encoding: "utf-8",
91
+ timeout: 1e4
92
+ }
93
+ );
94
+ return log2.trim();
95
+ } catch {
96
+ return "";
97
+ }
98
+ }
99
+ function runVerification(context) {
100
+ const result = {
101
+ agentsValid: true,
102
+ agentErrors: [],
103
+ routingValid: true,
104
+ routingErrors: [],
105
+ recipesValid: true,
106
+ recipeErrors: [],
107
+ brainstormMdValid: true,
108
+ brainstormMdErrors: []
109
+ };
110
+ if (context.agents) {
111
+ for (const agent of context.agents) {
112
+ const errors = validateAgentContent(agent.id, agent.content);
113
+ if (errors.length > 0) {
114
+ result.agentsValid = false;
115
+ result.agentErrors.push(...errors);
116
+ }
117
+ }
118
+ }
119
+ if (context.routingRules) {
120
+ for (const rule of context.routingRules) {
121
+ if (!rule.match || !rule.agentId) {
122
+ result.routingValid = false;
123
+ result.routingErrors.push(
124
+ `Rule missing required fields: match="${rule.match}", agentId="${rule.agentId}"`
125
+ );
126
+ }
127
+ if (context.agents && !context.agents.some((a) => a.id === rule.agentId)) {
128
+ result.routingValid = false;
129
+ result.routingErrors.push(
130
+ `Rule references unknown agent "${rule.agentId}"`
131
+ );
132
+ }
133
+ }
134
+ }
135
+ if (context.recipes) {
136
+ for (const recipe of context.recipes) {
137
+ const errors = validateRecipeContent(recipe.filename, recipe.content);
138
+ if (errors.length > 0) {
139
+ result.recipesValid = false;
140
+ result.recipeErrors.push(...errors);
141
+ }
142
+ }
143
+ }
144
+ if (context.brainstormMd) {
145
+ const errors = validateBrainstormMd(context.brainstormMd);
146
+ if (errors.length > 0) {
147
+ result.brainstormMdValid = false;
148
+ result.brainstormMdErrors.push(...errors);
149
+ }
150
+ }
151
+ return result;
152
+ }
153
+ function validateAgentContent(id, content) {
154
+ const errors = [];
155
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
156
+ if (!fmMatch) {
157
+ errors.push(`Agent "${id}": missing YAML frontmatter (---)`);
158
+ return errors;
159
+ }
160
+ const frontmatter = fmMatch[1];
161
+ const body = fmMatch[2].trim();
162
+ if (!/^name:\s*.+$/m.test(frontmatter)) {
163
+ errors.push(`Agent "${id}": missing "name" field in frontmatter`);
164
+ }
165
+ if (!/^role:\s*.+$/m.test(frontmatter)) {
166
+ errors.push(`Agent "${id}": missing "role" field in frontmatter`);
167
+ }
168
+ if (!body) {
169
+ errors.push(`Agent "${id}": empty system prompt body`);
170
+ }
171
+ return errors;
172
+ }
173
+ function validateRecipeContent(filename, content) {
174
+ const errors = [];
175
+ if (!content.includes("name:")) {
176
+ errors.push(`Recipe "${filename}": missing "name" field`);
177
+ }
178
+ if (!content.includes("steps:")) {
179
+ errors.push(`Recipe "${filename}": missing "steps" field`);
180
+ }
181
+ return errors;
182
+ }
183
+ function validateBrainstormMd(content) {
184
+ const errors = [];
185
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
186
+ if (!fmMatch) {
187
+ return errors;
188
+ }
189
+ const frontmatter = fmMatch[1];
190
+ if (!/version:\s*1/.test(frontmatter)) {
191
+ errors.push("BRAINSTORM.md: frontmatter missing version: 1");
192
+ }
193
+ return errors;
194
+ }
195
+ function buildExplorationPrompt(analysis, fileContents, gitSummary) {
196
+ const sections = [];
197
+ sections.push(`You are analyzing a codebase to understand its conventions, domain concepts, and development workflow.
198
+ Your goal: produce a JSON object that captures everything a new AI agent would need to know before writing code in this project.
199
+
200
+ ## Project Overview
201
+ - Path: ${analysis.projectPath}
202
+ - Primary language: ${analysis.summary.primaryLanguage}
203
+ - Frameworks: ${analysis.summary.frameworkList.join(", ") || "none detected"}
204
+ - Files: ${analysis.summary.totalFiles}, Lines: ${analysis.summary.totalLines.toLocaleString()}
205
+ - Modules: ${analysis.summary.moduleCount}
206
+ - Entry points: ${analysis.summary.entryPointCount}
207
+ - API routes: ${analysis.summary.apiRouteCount}
208
+ - Avg complexity: ${analysis.summary.avgComplexity.toFixed(1)}
209
+ - Hotspots: ${analysis.summary.hotspotCount}`);
210
+ if (fileContents.size > 0) {
211
+ sections.push("\n## Key Files\n");
212
+ for (const [path, content] of fileContents) {
213
+ sections.push(`### ${path}
214
+ \`\`\`
215
+ ${content}
216
+ \`\`\`
217
+ `);
218
+ }
219
+ }
220
+ if (gitSummary) {
221
+ sections.push(
222
+ `## Recent Git History (last 30 commits)
223
+ \`\`\`
224
+ ${gitSummary}
225
+ \`\`\``
226
+ );
227
+ }
228
+ if (analysis.frameworks.frameworks.length > 0) {
229
+ sections.push(`## Detected Stack
230
+ - Frameworks: ${analysis.frameworks.frameworks.join(", ")}
231
+ - Build tools: ${analysis.frameworks.buildTools.join(", ") || "none"}
232
+ - Databases: ${analysis.frameworks.databases.join(", ") || "none"}
233
+ - Testing: ${analysis.frameworks.testing.join(", ") || "none"}
234
+ - CI/CD: ${analysis.frameworks.ci?.join(", ") || "none"}
235
+ - Deployment: ${analysis.frameworks.deployment?.join(", ") || "none"}`);
236
+ }
237
+ if (analysis.dependencies.entryPoints.length > 0) {
238
+ sections.push(
239
+ `## Entry Points
240
+ ${analysis.dependencies.entryPoints.slice(0, 10).map((e) => `- ${e}`).join("\n")}`
241
+ );
242
+ }
243
+ if (analysis.complexity.summary.hotspots.length > 0) {
244
+ sections.push(
245
+ `## Complexity Hotspots
246
+ ${analysis.complexity.summary.hotspots.slice(0, 10).map((h) => `- ${h}`).join("\n")}`
247
+ );
248
+ }
249
+ sections.push(`
250
+ ## Instructions
251
+
252
+ Analyze all the information above and respond with a JSON object matching this exact schema:
253
+
254
+ \`\`\`json
255
+ {
256
+ "conventions": {
257
+ "naming": {
258
+ "variables": "camelCase | snake_case | PascalCase",
259
+ "files": "kebab-case | camelCase | PascalCase | snake_case",
260
+ "components": "PascalCase (if applicable)",
261
+ "exports": "named | default | barrel"
262
+ },
263
+ "errorHandling": "description of error handling patterns",
264
+ "testingPatterns": "description of testing approach and file organization",
265
+ "importStyle": "description of import patterns",
266
+ "stateManagement": "description if frontend (null if not applicable)",
267
+ "apiPatterns": "description of API design patterns (null if not applicable)",
268
+ "customRules": ["any other conventions discovered"]
269
+ },
270
+ "domainConcepts": [
271
+ {
272
+ "name": "concept name",
273
+ "definition": "what this concept means in the project",
274
+ "relatedFiles": ["path/to/relevant/files"]
275
+ }
276
+ ],
277
+ "gitWorkflow": {
278
+ "commitStyle": "conventional commits | freeform | prefixed | etc.",
279
+ "branchStrategy": "trunk-based | gitflow | github flow | unknown",
280
+ "prPatterns": "squash merge | merge commits | rebase | unknown",
281
+ "typicalPRSize": "small (<100 lines) | medium (100-500) | large (500+)",
282
+ "activeContributors": 1
283
+ },
284
+ "cicdSetup": {
285
+ "provider": "github-actions | vercel | circleci | none | etc.",
286
+ "stages": ["lint", "test", "build", "deploy"],
287
+ "deployTarget": "vercel | aws | docker | do-app-platform | none | etc.",
288
+ "hasPreCommitHooks": false
289
+ },
290
+ "keyFiles": [
291
+ {
292
+ "path": "relative/path",
293
+ "purpose": "what this file does",
294
+ "summary": "2-3 sentence summary"
295
+ }
296
+ ],
297
+ "projectPurpose": "One paragraph describing what this project does, its target users, and its primary value proposition."
298
+ }
299
+ \`\`\`
300
+
301
+ Important:
302
+ - Base ALL answers on the actual file contents and git history provided above
303
+ - For conventions, look at actual patterns in the code \u2014 don't guess
304
+ - For domain concepts, identify the core business/technical abstractions unique to this project
305
+ - For git workflow, analyze the commit messages and file change patterns
306
+ - Include 3-10 domain concepts and 5-15 key files
307
+ - Respond ONLY with the JSON object, no markdown fencing or explanation`);
308
+ return sections.join("\n\n");
309
+ }
310
+ var MAX_LINES_PER_FILE = 100;
311
+ var MAX_FILES = 20;
312
+ var MAX_TOTAL_CHARS = 5e4;
313
+ async function runDeepExploration(context, dispatcher) {
314
+ const { analysis } = context;
315
+ const gitSummary = context._gitSummary ?? "";
316
+ const selectedFiles = selectKeyFiles(analysis);
317
+ const fileContents = readSelectedFiles(analysis.projectPath, selectedFiles);
318
+ const prompt = buildExplorationPrompt(analysis, fileContents, gitSummary);
319
+ const response = await dispatcher.generate(prompt, 0.3);
320
+ let exploration;
321
+ try {
322
+ exploration = parseExplorationResponse(response.text);
323
+ } catch (error) {
324
+ exploration = createFallbackResult(analysis);
325
+ }
326
+ const conceptCount = exploration.domainConcepts.length;
327
+ const conventionCount = countConventions(exploration.conventions);
328
+ const workflowDesc = exploration.gitWorkflow.branchStrategy;
329
+ return {
330
+ contextPatch: { exploration },
331
+ cost: response.cost,
332
+ summary: `${conventionCount} conventions, ${conceptCount} domain concepts, ${workflowDesc} workflow`
333
+ };
334
+ }
335
+ function selectKeyFiles(analysis) {
336
+ const files = [];
337
+ const seen = /* @__PURE__ */ new Set();
338
+ const add = (path) => {
339
+ if (seen.has(path) || files.length >= MAX_FILES) return;
340
+ seen.add(path);
341
+ files.push(path);
342
+ };
343
+ for (const ep of analysis.dependencies.entryPoints.slice(0, 5)) {
344
+ add(ep);
345
+ }
346
+ const configFiles = [
347
+ "package.json",
348
+ "tsconfig.json",
349
+ "tsconfig.base.json",
350
+ ".eslintrc.json",
351
+ ".eslintrc.js",
352
+ ".eslintrc.cjs",
353
+ "eslint.config.js",
354
+ "eslint.config.mjs",
355
+ ".prettierrc",
356
+ ".prettierrc.json",
357
+ "prettier.config.js",
358
+ "turbo.json",
359
+ "vercel.json",
360
+ "next.config.js",
361
+ "next.config.mjs",
362
+ "next.config.ts",
363
+ "vite.config.ts",
364
+ "vitest.config.ts",
365
+ "jest.config.ts",
366
+ "jest.config.js",
367
+ "Dockerfile",
368
+ "docker-compose.yml",
369
+ "docker-compose.yaml",
370
+ ".github/workflows/ci.yml",
371
+ ".github/workflows/ci.yaml",
372
+ ".github/workflows/test.yml",
373
+ ".github/workflows/deploy.yml"
374
+ ];
375
+ for (const cf of configFiles) {
376
+ const fullPath = join(analysis.projectPath, cf);
377
+ if (existsSync2(fullPath)) {
378
+ add(cf);
379
+ }
380
+ }
381
+ for (const readme of ["README.md", "readme.md", "Readme.md"]) {
382
+ const fullPath = join(analysis.projectPath, readme);
383
+ if (existsSync2(fullPath)) {
384
+ add(readme);
385
+ break;
386
+ }
387
+ }
388
+ const testFile = findTestFile(analysis);
389
+ if (testFile) add(testFile);
390
+ for (const cluster of analysis.dependencies.clusters.slice(0, 5)) {
391
+ const hottest = findHottestFile(analysis, cluster.files);
392
+ if (hottest) add(hottest);
393
+ }
394
+ return files;
395
+ }
396
+ function findTestFile(analysis) {
397
+ for (const node of analysis.dependencies.nodes) {
398
+ if (node.path.includes(".test.") || node.path.includes(".spec.") || node.path.includes("__tests__/")) {
399
+ return node.path;
400
+ }
401
+ }
402
+ return null;
403
+ }
404
+ function findHottestFile(analysis, files) {
405
+ let best = null;
406
+ for (const f of files) {
407
+ const fc = analysis.complexity.files.find((c) => c.path === f);
408
+ if (fc && (!best || fc.score > best.score)) {
409
+ best = { path: fc.path, score: fc.score };
410
+ }
411
+ }
412
+ return best?.path ?? files[0] ?? null;
413
+ }
414
+ function readSelectedFiles(projectPath, files) {
415
+ const contents = /* @__PURE__ */ new Map();
416
+ let totalChars = 0;
417
+ for (const file of files) {
418
+ const fullPath = join(projectPath, file);
419
+ if (!existsSync2(fullPath)) continue;
420
+ try {
421
+ const raw = readFileSync(fullPath, "utf-8");
422
+ const lines = raw.split("\n").slice(0, MAX_LINES_PER_FILE);
423
+ const truncated = lines.join("\n");
424
+ if (totalChars + truncated.length > MAX_TOTAL_CHARS) break;
425
+ contents.set(file, truncated);
426
+ totalChars += truncated.length;
427
+ } catch {
428
+ }
429
+ }
430
+ return contents;
431
+ }
432
+ function parseExplorationResponse(text) {
433
+ let json = text.trim();
434
+ if (json.startsWith("```")) {
435
+ json = json.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
436
+ }
437
+ const parsed = JSON.parse(json);
438
+ if (!parsed.conventions || !parsed.domainConcepts || !parsed.gitWorkflow) {
439
+ throw new Error("Missing required fields in exploration response");
440
+ }
441
+ return parsed;
442
+ }
443
+ function createFallbackResult(analysis) {
444
+ return {
445
+ conventions: {
446
+ naming: {
447
+ variables: "unknown",
448
+ files: "unknown",
449
+ exports: "unknown"
450
+ },
451
+ errorHandling: "unknown",
452
+ testingPatterns: analysis.frameworks.testing.length > 0 ? `Uses ${analysis.frameworks.testing.join(", ")}` : "No testing framework detected",
453
+ importStyle: "unknown",
454
+ customRules: []
455
+ },
456
+ domainConcepts: [],
457
+ gitWorkflow: {
458
+ commitStyle: "unknown",
459
+ branchStrategy: "unknown",
460
+ prPatterns: "unknown",
461
+ typicalPRSize: "unknown",
462
+ activeContributors: 1
463
+ },
464
+ cicdSetup: {
465
+ provider: analysis.frameworks.ci?.[0] ?? "none",
466
+ stages: [],
467
+ deployTarget: analysis.frameworks.deployment?.[0] ?? "none",
468
+ hasPreCommitHooks: false
469
+ },
470
+ keyFiles: [],
471
+ projectPurpose: `A ${analysis.summary.primaryLanguage} project using ${analysis.summary.frameworkList.join(", ") || "no detected frameworks"}.`
472
+ };
473
+ }
474
+ function countConventions(conventions) {
475
+ let count = 0;
476
+ if (conventions.naming.variables !== "unknown") count++;
477
+ if (conventions.naming.files !== "unknown") count++;
478
+ if (conventions.naming.components) count++;
479
+ if (conventions.naming.exports !== "unknown") count++;
480
+ if (conventions.errorHandling !== "unknown") count++;
481
+ if (conventions.testingPatterns && !conventions.testingPatterns.startsWith("No"))
482
+ count++;
483
+ if (conventions.importStyle !== "unknown") count++;
484
+ if (conventions.stateManagement) count++;
485
+ if (conventions.apiPatterns) count++;
486
+ count += conventions.customRules.length;
487
+ return count;
488
+ }
489
+ function buildAssemblyPrompt(analysis, exploration, candidates) {
490
+ const sections = [];
491
+ sections.push(`You are designing a team of specialized AI agents for a codebase. Each agent needs a rich system prompt that embeds the project's actual conventions, domain knowledge, and module context.
492
+
493
+ ## Project
494
+ ${exploration?.projectPurpose ?? `A ${analysis.summary.primaryLanguage} project with ${analysis.summary.totalFiles} files.`}
495
+
496
+ ## Stack
497
+ - Language: ${analysis.summary.primaryLanguage}
498
+ - Frameworks: ${analysis.summary.frameworkList.join(", ") || "none"}
499
+ - Testing: ${analysis.frameworks.testing.join(", ") || "none"}
500
+ - Modules: ${analysis.summary.moduleCount}
501
+ - Complexity: avg ${analysis.summary.avgComplexity.toFixed(1)}`);
502
+ if (exploration) {
503
+ sections.push(`## Conventions
504
+ - Naming: variables=${exploration.conventions.naming.variables}, files=${exploration.conventions.naming.files}
505
+ - Error handling: ${exploration.conventions.errorHandling}
506
+ - Testing: ${exploration.conventions.testingPatterns}
507
+ - Imports: ${exploration.conventions.importStyle}${exploration.conventions.stateManagement ? `
508
+ - State: ${exploration.conventions.stateManagement}` : ""}${exploration.conventions.apiPatterns ? `
509
+ - API: ${exploration.conventions.apiPatterns}` : ""}
510
+ - Rules: ${exploration.conventions.customRules.join("; ") || "none"}`);
511
+ if (exploration.domainConcepts.length > 0) {
512
+ sections.push(`## Domain Concepts
513
+ ${exploration.domainConcepts.map((c) => `- **${c.name}**: ${c.definition}`).join("\n")}`);
514
+ }
515
+ }
516
+ sections.push(`## Agent Candidates
517
+
518
+ Generate a .agent.md system prompt for EACH of the following agents. The system prompt should be 10-30 lines of markdown that a code-writing AI reads before starting work.
519
+
520
+ ${candidates.map((c, i) => {
521
+ let desc = `### Agent ${i + 1}: ${c.id} (${c.role})
522
+ - Rationale: ${c.rationale}`;
523
+ if (c.tools) desc += `
524
+ - Allowed tools: ${c.tools.join(", ")}`;
525
+ if (c.moduleScope) {
526
+ desc += `
527
+ - Module scope: ${c.moduleScope.directory} (${c.moduleScope.files.length} files, cohesion: ${c.moduleScope.cohesion.toFixed(2)})`;
528
+ }
529
+ return desc;
530
+ }).join("\n\n")}`);
531
+ sections.push(`## Instructions
532
+
533
+ For each agent candidate, generate a complete .agent.md file. Respond with a JSON array of objects:
534
+
535
+ \`\`\`json
536
+ [
537
+ {
538
+ "id": "agent-id",
539
+ "role": "role",
540
+ "modelHint": "quality | capable | cheap",
541
+ "tools": ["tool1", "tool2"] or "all",
542
+ "maxSteps": 10,
543
+ "budget": 5.0,
544
+ "systemPrompt": "The full markdown system prompt content..."
545
+ }
546
+ ]
547
+ \`\`\`
548
+
549
+ Each systemPrompt MUST include:
550
+ 1. A clear role definition (what this agent does)
551
+ 2. Project-specific conventions (from the conventions section above)
552
+ 3. Domain concepts relevant to this agent's scope
553
+ 4. Specific do's and don'ts for this codebase
554
+ 5. Module context if the agent has a module scope
555
+
556
+ Keep prompts actionable and specific. Avoid generic advice like "write clean code."
557
+ Respond ONLY with the JSON array, no markdown fencing or explanation.`);
558
+ return sections.join("\n\n");
559
+ }
560
+ async function runTeamAssembly(context, dispatcher) {
561
+ const { analysis, exploration } = context;
562
+ const candidates = detectCandidates(analysis);
563
+ const prompt = buildAssemblyPrompt(analysis, exploration, candidates);
564
+ const response = await dispatcher.generate(prompt, 0.5);
565
+ let enrichedAgents;
566
+ try {
567
+ let json = response.text.trim();
568
+ if (json.startsWith("```")) {
569
+ json = json.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
570
+ }
571
+ enrichedAgents = JSON.parse(json);
572
+ } catch {
573
+ enrichedAgents = candidates.map((c) => ({
574
+ id: c.id,
575
+ role: c.role,
576
+ modelHint: c.modelHint,
577
+ tools: c.tools,
578
+ systemPrompt: buildFallbackPrompt(c, analysis)
579
+ }));
580
+ }
581
+ const agentsDir = join2(analysis.projectPath, ".brainstorm", "agents");
582
+ if (!existsSync3(agentsDir)) mkdirSync(agentsDir, { recursive: true });
583
+ const agents = [];
584
+ const filesWritten = [];
585
+ for (const enriched of enrichedAgents) {
586
+ const candidate = candidates.find((c) => c.id === enriched.id);
587
+ const filePath = join2(agentsDir, `${enriched.id}.agent.md`);
588
+ const content = formatAgentMd(enriched);
589
+ writeFileSync(filePath, content, "utf-8");
590
+ agents.push({
591
+ id: enriched.id,
592
+ role: enriched.role,
593
+ filePath,
594
+ content,
595
+ rationale: candidate?.rationale ?? "LLM-generated"
596
+ });
597
+ filesWritten.push(filePath);
598
+ }
599
+ const roleList = agents.map((a) => a.id).join(", ");
600
+ return {
601
+ contextPatch: { agents },
602
+ cost: response.cost,
603
+ summary: `${agents.length} agents: ${roleList}`,
604
+ filesWritten
605
+ };
606
+ }
607
+ function detectCandidates(analysis) {
608
+ const candidates = [];
609
+ candidates.push({
610
+ id: "architect",
611
+ role: "architect",
612
+ rationale: "Every project needs high-level design guidance",
613
+ modelHint: "quality"
614
+ });
615
+ candidates.push({
616
+ id: "code-reviewer",
617
+ role: "code-reviewer",
618
+ rationale: "Code review is critical for quality",
619
+ tools: ["file_read", "grep", "glob", "git_diff", "git_log"],
620
+ modelHint: "capable"
621
+ });
622
+ const frontendFrameworks = [
623
+ "react",
624
+ "nextjs",
625
+ "next.js",
626
+ "vue",
627
+ "angular",
628
+ "svelte"
629
+ ];
630
+ if (analysis.frameworks.frameworks.some(
631
+ (f) => frontendFrameworks.includes(f.toLowerCase())
632
+ )) {
633
+ candidates.push({
634
+ id: "frontend-expert",
635
+ role: "coder",
636
+ rationale: `Frontend framework detected: ${analysis.frameworks.frameworks.filter((f) => frontendFrameworks.includes(f.toLowerCase())).join(", ")}`,
637
+ modelHint: "capable"
638
+ });
639
+ }
640
+ if (analysis.summary.apiRouteCount > 0) {
641
+ candidates.push({
642
+ id: "api-expert",
643
+ role: "coder",
644
+ rationale: `${analysis.summary.apiRouteCount} API routes detected`,
645
+ modelHint: "capable"
646
+ });
647
+ }
648
+ if (analysis.frameworks.testing.length > 0) {
649
+ candidates.push({
650
+ id: "qa",
651
+ role: "qa",
652
+ rationale: `Testing with ${analysis.frameworks.testing.join(", ")}`,
653
+ modelHint: "capable"
654
+ });
655
+ }
656
+ if (analysis.frameworks.ci && analysis.frameworks.ci.length > 0) {
657
+ candidates.push({
658
+ id: "devops",
659
+ role: "devops",
660
+ rationale: `CI/CD detected: ${analysis.frameworks.ci.join(", ")}`,
661
+ tools: ["file_read", "file_write", "grep", "glob", "shell"],
662
+ modelHint: "capable"
663
+ });
664
+ }
665
+ if (analysis.summary.avgComplexity > 10 || analysis.summary.hotspotCount > 5) {
666
+ candidates.push({
667
+ id: "security-reviewer",
668
+ role: "security-reviewer",
669
+ rationale: `High complexity (avg ${analysis.summary.avgComplexity.toFixed(1)}) warrants security review`,
670
+ tools: ["file_read", "grep", "glob"],
671
+ modelHint: "quality"
672
+ });
673
+ }
674
+ const topClusters = analysis.dependencies.clusters.filter((c) => c.files.length >= 3).sort((a, b) => b.files.length - a.files.length).slice(0, 3);
675
+ for (const cluster of topClusters) {
676
+ const safeName = cluster.directory.replace(/[/\\]/g, "-").replace(/^-/, "").replace(/-$/, "");
677
+ if (!safeName || candidates.some((c) => c.id === `${safeName}-expert`))
678
+ continue;
679
+ candidates.push({
680
+ id: `${safeName}-expert`,
681
+ role: "coder",
682
+ rationale: `Module "${cluster.directory}" has ${cluster.files.length} files`,
683
+ moduleScope: cluster,
684
+ modelHint: "capable"
685
+ });
686
+ }
687
+ return candidates;
688
+ }
689
+ function formatAgentMd(agent) {
690
+ const lines = ["---"];
691
+ lines.push(`name: ${agent.id}`);
692
+ lines.push(`role: ${agent.role}`);
693
+ lines.push(`model: ${agent.modelHint ?? "capable"}`);
694
+ if (agent.tools && agent.tools !== "all") {
695
+ lines.push(`tools: [${agent.tools.map((t) => `"${t}"`).join(", ")}]`);
696
+ }
697
+ if (agent.maxSteps) lines.push(`max_steps: ${agent.maxSteps}`);
698
+ if (agent.budget) lines.push(`budget: ${agent.budget}`);
699
+ lines.push("---");
700
+ lines.push("");
701
+ lines.push(agent.systemPrompt);
702
+ return lines.join("\n");
703
+ }
704
+ function buildFallbackPrompt(candidate, analysis) {
705
+ const lines = [
706
+ `# ${candidate.id}`,
707
+ "",
708
+ `You are a ${candidate.role} for a ${analysis.summary.primaryLanguage} project.`,
709
+ "",
710
+ `## Context`,
711
+ `- Language: ${analysis.summary.primaryLanguage}`,
712
+ `- Frameworks: ${analysis.summary.frameworkList.join(", ") || "none"}`,
713
+ `- ${analysis.summary.totalFiles} files, ${analysis.summary.totalLines.toLocaleString()} lines`
714
+ ];
715
+ if (candidate.moduleScope) {
716
+ lines.push(
717
+ "",
718
+ `## Module Scope: ${candidate.moduleScope.directory}`,
719
+ `- Files: ${candidate.moduleScope.files.length}`,
720
+ `- Cohesion: ${candidate.moduleScope.cohesion > 0.5 ? "high" : candidate.moduleScope.cohesion > 0.2 ? "medium" : "low"}`
721
+ );
722
+ }
723
+ return lines.join("\n");
724
+ }
725
+ function buildRoutingPrompt(analysis, agents, heuristicRules) {
726
+ return `You are configuring task routing for an AI coding assistant. Each task should be routed to the most appropriate specialized agent.
727
+
728
+ ## Available Agents
729
+ ${agents.map((a) => `- **${a.id}** (${a.role}): ${a.rationale}`).join("\n")}
730
+
731
+ ## Project Context
732
+ - Language: ${analysis.summary.primaryLanguage}
733
+ - Frameworks: ${analysis.summary.frameworkList.join(", ") || "none"}
734
+ - ${analysis.summary.apiRouteCount} API routes, ${analysis.summary.moduleCount} modules
735
+
736
+ ## Heuristic Rules (auto-generated)
737
+ These rules were generated from static analysis. Review them and add any project-specific rules.
738
+
739
+ ${heuristicRules.map((r) => `- match: "${r.match}" \u2192 agent: ${r.agentId} (${r.modelHint ?? "auto"}) \u2014 ${r.rationale}`).join("\n")}
740
+
741
+ ## Instructions
742
+
743
+ Return a JSON array of routing rules. Include the heuristic rules (adjusted if needed) plus any additional project-specific rules.
744
+
745
+ \`\`\`json
746
+ [
747
+ {
748
+ "match": "task type or keyword pattern",
749
+ "agentId": "agent-id",
750
+ "modelHint": "quality | capable | cheap",
751
+ "rationale": "why this rule exists"
752
+ }
753
+ ]
754
+ \`\`\`
755
+
756
+ Guidelines:
757
+ - "match" should be a task type (code-generation, debugging, refactoring, etc.) or keyword pattern
758
+ - Every agent should have at least one routing rule
759
+ - Security-sensitive tasks should route to quality-tier models
760
+ - Simple fixes can use cheap-tier models
761
+ - Respond ONLY with the JSON array`;
762
+ }
763
+ async function runRoutingRules(context, dispatcher) {
764
+ const { analysis, agents } = context;
765
+ if (!agents || agents.length === 0) {
766
+ return {
767
+ contextPatch: { routingRules: [] },
768
+ cost: 0,
769
+ summary: "No agents to route to (skipped)"
770
+ };
771
+ }
772
+ const heuristicRules = generateHeuristicRules(analysis, agents);
773
+ const prompt = buildRoutingPrompt(analysis, agents, heuristicRules);
774
+ const response = await dispatcher.generate(prompt, 0.08);
775
+ let rules;
776
+ try {
777
+ let json = response.text.trim();
778
+ if (json.startsWith("```")) {
779
+ json = json.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
780
+ }
781
+ rules = JSON.parse(json);
782
+ } catch {
783
+ rules = heuristicRules;
784
+ }
785
+ const brainstormDir = join3(analysis.projectPath, ".brainstorm");
786
+ if (!existsSync4(brainstormDir)) mkdirSync2(brainstormDir, { recursive: true });
787
+ const routingPath = join3(brainstormDir, "routing.yaml");
788
+ const yamlContent = formatRoutingYaml(rules);
789
+ writeFileSync2(routingPath, yamlContent, "utf-8");
790
+ return {
791
+ contextPatch: { routingRules: rules },
792
+ cost: response.cost,
793
+ summary: `${rules.length} routing rules`,
794
+ filesWritten: [routingPath]
795
+ };
796
+ }
797
+ function generateHeuristicRules(analysis, agents) {
798
+ const rules = [];
799
+ const agentIds = new Set(agents.map((a) => a.id));
800
+ if (agentIds.has("code-reviewer")) {
801
+ rules.push({
802
+ match: "code-review",
803
+ agentId: "code-reviewer",
804
+ modelHint: "capable",
805
+ rationale: "Route review tasks to dedicated reviewer"
806
+ });
807
+ }
808
+ if (agentIds.has("architect")) {
809
+ rules.push({
810
+ match: "architecture",
811
+ agentId: "architect",
812
+ modelHint: "quality",
813
+ rationale: "Design tasks need quality-tier reasoning"
814
+ });
815
+ rules.push({
816
+ match: "refactoring",
817
+ agentId: "architect",
818
+ modelHint: "quality",
819
+ rationale: "Refactoring needs architectural awareness"
820
+ });
821
+ }
822
+ if (agentIds.has("frontend-expert")) {
823
+ rules.push({
824
+ match: "frontend",
825
+ agentId: "frontend-expert",
826
+ modelHint: "capable",
827
+ rationale: "Frontend-specific tasks"
828
+ });
829
+ rules.push({
830
+ match: "component",
831
+ agentId: "frontend-expert",
832
+ modelHint: "capable",
833
+ rationale: "Component creation/modification"
834
+ });
835
+ }
836
+ if (agentIds.has("api-expert")) {
837
+ rules.push({
838
+ match: "api",
839
+ agentId: "api-expert",
840
+ modelHint: "capable",
841
+ rationale: `${analysis.summary.apiRouteCount} API routes in project`
842
+ });
843
+ rules.push({
844
+ match: "endpoint",
845
+ agentId: "api-expert",
846
+ modelHint: "capable",
847
+ rationale: "Endpoint creation/modification"
848
+ });
849
+ }
850
+ if (agentIds.has("qa")) {
851
+ rules.push({
852
+ match: "test",
853
+ agentId: "qa",
854
+ modelHint: "capable",
855
+ rationale: "Test writing and debugging"
856
+ });
857
+ }
858
+ if (agentIds.has("security-reviewer")) {
859
+ rules.push({
860
+ match: "security",
861
+ agentId: "security-reviewer",
862
+ modelHint: "quality",
863
+ rationale: "Security tasks need thorough analysis"
864
+ });
865
+ rules.push({
866
+ match: "audit",
867
+ agentId: "security-reviewer",
868
+ modelHint: "quality",
869
+ rationale: "Code audits are security-adjacent"
870
+ });
871
+ }
872
+ if (agentIds.has("devops")) {
873
+ rules.push({
874
+ match: "deploy",
875
+ agentId: "devops",
876
+ modelHint: "capable",
877
+ rationale: "Deployment and infrastructure tasks"
878
+ });
879
+ rules.push({
880
+ match: "ci",
881
+ agentId: "devops",
882
+ modelHint: "capable",
883
+ rationale: "CI/CD pipeline tasks"
884
+ });
885
+ }
886
+ const coderAgent = agents.find(
887
+ (a) => a.role === "coder" && !a.id.includes("-expert")
888
+ );
889
+ if (coderAgent) {
890
+ rules.push({
891
+ match: "code-generation",
892
+ agentId: coderAgent.id,
893
+ modelHint: "capable",
894
+ rationale: "General code generation"
895
+ });
896
+ }
897
+ const debugAgent = agents.find((a) => a.role === "debugger") ?? coderAgent;
898
+ if (debugAgent) {
899
+ rules.push({
900
+ match: "debugging",
901
+ agentId: debugAgent.id,
902
+ modelHint: "capable",
903
+ rationale: "Bug investigation and fixing"
904
+ });
905
+ }
906
+ return rules;
907
+ }
908
+ function formatRoutingYaml(rules) {
909
+ const lines = [
910
+ "# Brainstorm Routing Rules",
911
+ "# Generated by `storm onboard` \u2014 maps task patterns to specialized agents.",
912
+ "#",
913
+ "# Format: match pattern \u2192 agent ID, model tier, rationale",
914
+ "# Edit freely \u2014 these rules override heuristic routing.",
915
+ "",
916
+ "rules:"
917
+ ];
918
+ for (const rule of rules) {
919
+ lines.push(` - match: "${rule.match}"`);
920
+ lines.push(` agent: ${rule.agentId}`);
921
+ if (rule.modelHint) lines.push(` model: ${rule.modelHint}`);
922
+ lines.push(` # ${rule.rationale}`);
923
+ lines.push("");
924
+ }
925
+ return lines.join("\n");
926
+ }
927
+ function buildWorkflowPrompt(analysis, exploration, heuristicRecipes) {
928
+ return `You are customizing workflow recipes for a project. Each recipe defines a multi-step workflow where AI agents collaborate.
929
+
930
+ ## Project
931
+ - Language: ${analysis.summary.primaryLanguage}
932
+ - Frameworks: ${analysis.summary.frameworkList.join(", ") || "none"}
933
+ - Testing: ${analysis.frameworks.testing.join(", ") || "none"}
934
+ ${exploration ? `- Conventions: ${exploration.conventions.testingPatterns}
935
+ - Error handling: ${exploration.conventions.errorHandling}
936
+ - Commit style: ${exploration.gitWorkflow.commitStyle}
937
+ - CI stages: ${exploration.cicdSetup.stages.join(" \u2192 ") || "unknown"}` : ""}
938
+
939
+ ## Heuristic Recipes
940
+ These recipes were auto-generated. Customize the step descriptions to match this project's conventions.
941
+
942
+ ${heuristicRecipes.map((r) => `### ${r.filename}
943
+ \`\`\`yaml
944
+ ${r.content}
945
+ \`\`\``).join("\n\n")}
946
+
947
+ ## Instructions
948
+
949
+ Return a JSON array of customized recipes. Keep the same structure but improve the step descriptions to be project-specific.
950
+
951
+ \`\`\`json
952
+ [
953
+ {
954
+ "filename": "recipe-name.yaml",
955
+ "content": "full YAML content",
956
+ "description": "what this recipe does"
957
+ }
958
+ ]
959
+ \`\`\`
960
+
961
+ Guidelines:
962
+ - Reference actual testing frameworks (${analysis.frameworks.testing.join(", ") || "none"})
963
+ - Reference actual build tools (${analysis.frameworks.buildTools.join(", ") || "none"})
964
+ - Step descriptions should be actionable and project-specific
965
+ - Keep YAML syntax valid
966
+ - Respond ONLY with the JSON array`;
967
+ }
968
+ async function runWorkflowGen(context, dispatcher) {
969
+ const { analysis, exploration } = context;
970
+ const heuristicRecipes = generateHeuristicRecipes(analysis);
971
+ if (heuristicRecipes.length === 0) {
972
+ return {
973
+ contextPatch: { recipes: [] },
974
+ cost: 0,
975
+ summary: "No recipes generated (project too simple)"
976
+ };
977
+ }
978
+ const prompt = buildWorkflowPrompt(analysis, exploration, heuristicRecipes);
979
+ const response = await dispatcher.generate(prompt, 0.1);
980
+ let recipes;
981
+ try {
982
+ let json = response.text.trim();
983
+ if (json.startsWith("```")) {
984
+ json = json.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
985
+ }
986
+ recipes = JSON.parse(json);
987
+ } catch {
988
+ recipes = heuristicRecipes;
989
+ }
990
+ const recipesDir = join4(analysis.projectPath, ".brainstorm", "recipes");
991
+ if (!existsSync5(recipesDir)) mkdirSync3(recipesDir, { recursive: true });
992
+ const filesWritten = [];
993
+ for (const recipe of recipes) {
994
+ const recipePath = join4(recipesDir, recipe.filename);
995
+ writeFileSync3(recipePath, recipe.content, "utf-8");
996
+ filesWritten.push(recipePath);
997
+ }
998
+ const recipeNames = recipes.map((r) => r.filename.replace(".yaml", "")).join(", ");
999
+ return {
1000
+ contextPatch: { recipes },
1001
+ cost: response.cost,
1002
+ summary: `${recipes.length} recipes: ${recipeNames}`,
1003
+ filesWritten
1004
+ };
1005
+ }
1006
+ function generateHeuristicRecipes(analysis) {
1007
+ const recipes = [];
1008
+ const frameworks = analysis.frameworks.frameworks.map((f) => f.toLowerCase());
1009
+ const hasTests = analysis.frameworks.testing.length > 0;
1010
+ const hasCI = analysis.frameworks.ci && analysis.frameworks.ci.length > 0;
1011
+ const isMonorepo = frameworks.includes("turborepo") || frameworks.includes("lerna");
1012
+ if (hasTests) {
1013
+ const testTool = analysis.frameworks.testing[0] ?? "test runner";
1014
+ recipes.push({
1015
+ filename: "pr-ready.yaml",
1016
+ content: buildPrReadyRecipe(analysis, testTool),
1017
+ description: "Full PR workflow: plan \u2192 implement \u2192 test \u2192 review"
1018
+ });
1019
+ }
1020
+ if (isMonorepo) {
1021
+ recipes.push({
1022
+ filename: "multi-package-change.yaml",
1023
+ content: buildMultiPackageRecipe(analysis),
1024
+ description: "Coordinate changes across multiple packages"
1025
+ });
1026
+ }
1027
+ if (hasCI) {
1028
+ recipes.push({
1029
+ filename: "deploy-flow.yaml",
1030
+ content: buildDeployRecipe(analysis),
1031
+ description: "Implement \u2192 verify \u2192 deploy pipeline"
1032
+ });
1033
+ }
1034
+ return recipes;
1035
+ }
1036
+ function buildPrReadyRecipe(analysis, testTool) {
1037
+ const buildCmd = inferBuildCmd(analysis);
1038
+ const testCmd = inferTestCmd(analysis, testTool);
1039
+ return `name: PR Ready
1040
+ description: Full workflow from plan to review-ready PR
1041
+ communication: handoff
1042
+ maxIterations: 2
1043
+ steps:
1044
+ - id: plan
1045
+ role: architect
1046
+ description: "Analyze the task and create an implementation plan with file changes needed"
1047
+ output: spec
1048
+ outputSchema: implementation-spec
1049
+
1050
+ - id: implement
1051
+ role: coder
1052
+ description: "Implement the changes according to the plan"
1053
+ input: [spec]
1054
+ output: code
1055
+
1056
+ - id: test
1057
+ role: qa
1058
+ description: "Run ${testTool} tests and verify the changes work correctly (${testCmd})"
1059
+ input: [spec, code]
1060
+ output: test-result
1061
+
1062
+ - id: review
1063
+ role: code-reviewer
1064
+ description: "Review for correctness, conventions, and potential issues"
1065
+ input: [spec, code, test-result]
1066
+ output: review
1067
+ review: true
1068
+ loopBack: implement
1069
+ `;
1070
+ }
1071
+ function buildMultiPackageRecipe(analysis) {
1072
+ return `name: Multi-Package Change
1073
+ description: Coordinate changes across multiple monorepo packages
1074
+ communication: handoff
1075
+ maxIterations: 2
1076
+ steps:
1077
+ - id: plan
1078
+ role: architect
1079
+ description: "Map which packages need changes and their dependency order"
1080
+ output: spec
1081
+ outputSchema: implementation-spec
1082
+
1083
+ - id: implement
1084
+ role: coder
1085
+ description: "Implement changes in dependency order \u2014 leaf packages first, dependents after"
1086
+ input: [spec]
1087
+ output: code
1088
+
1089
+ - id: verify
1090
+ role: qa
1091
+ description: "Run full monorepo build (npx turbo run build) and tests to verify cross-package compatibility"
1092
+ input: [spec, code]
1093
+ output: test-result
1094
+
1095
+ - id: review
1096
+ role: code-reviewer
1097
+ description: "Review for cross-package consistency and interface contracts"
1098
+ input: [spec, code, test-result]
1099
+ output: review
1100
+ review: true
1101
+ loopBack: implement
1102
+ `;
1103
+ }
1104
+ function buildDeployRecipe(analysis) {
1105
+ return `name: Deploy Flow
1106
+ description: Implement, verify, and prepare for deployment
1107
+ communication: handoff
1108
+ maxIterations: 2
1109
+ steps:
1110
+ - id: implement
1111
+ role: coder
1112
+ description: "Implement the changes"
1113
+ output: code
1114
+
1115
+ - id: verify
1116
+ role: qa
1117
+ description: "Run build and tests to verify deployment readiness"
1118
+ input: [code]
1119
+ output: test-result
1120
+
1121
+ - id: deploy-prep
1122
+ role: devops
1123
+ description: "Review deployment configuration and prepare deployment steps"
1124
+ input: [code, test-result]
1125
+ output: deploy-plan
1126
+
1127
+ - id: review
1128
+ role: code-reviewer
1129
+ description: "Final review before deployment"
1130
+ input: [code, test-result, deploy-plan]
1131
+ output: review
1132
+ review: true
1133
+ loopBack: implement
1134
+ `;
1135
+ }
1136
+ function inferBuildCmd(analysis) {
1137
+ const frameworks = analysis.frameworks.frameworks.map((f) => f.toLowerCase());
1138
+ if (frameworks.includes("turborepo")) return "npx turbo run build";
1139
+ return "npm run build";
1140
+ }
1141
+ function inferTestCmd(analysis, testTool) {
1142
+ const lower = testTool.toLowerCase();
1143
+ if (lower.includes("vitest")) return "npx vitest run";
1144
+ if (lower.includes("jest")) return "npx jest";
1145
+ if (lower.includes("pytest")) return "pytest";
1146
+ return "npm test";
1147
+ }
1148
+ function buildEnrichmentPrompt(analysis, exploration, agents) {
1149
+ const sections = [];
1150
+ sections.push(`You are writing the prose sections of a BRAINSTORM.md file \u2014 a context document that AI agents read before working on this project. Write concise, actionable content.
1151
+
1152
+ ## Project
1153
+ ${exploration?.projectPurpose ?? `A ${analysis.summary.primaryLanguage} project with ${analysis.summary.totalFiles} files.`}
1154
+
1155
+ ## Stack
1156
+ - Language: ${analysis.summary.primaryLanguage}
1157
+ - Frameworks: ${analysis.summary.frameworkList.join(", ") || "none"}
1158
+ - ${analysis.summary.totalFiles} files, ${analysis.summary.totalLines.toLocaleString()} lines, ${analysis.summary.moduleCount} modules`);
1159
+ if (exploration) {
1160
+ sections.push(`## Discovered Conventions
1161
+ - Naming: variables=${exploration.conventions.naming.variables}, files=${exploration.conventions.naming.files}, exports=${exploration.conventions.naming.exports}
1162
+ - Error handling: ${exploration.conventions.errorHandling}
1163
+ - Testing: ${exploration.conventions.testingPatterns}
1164
+ - Imports: ${exploration.conventions.importStyle}${exploration.conventions.stateManagement ? `
1165
+ - State management: ${exploration.conventions.stateManagement}` : ""}${exploration.conventions.apiPatterns ? `
1166
+ - API patterns: ${exploration.conventions.apiPatterns}` : ""}
1167
+ - Custom rules: ${exploration.conventions.customRules.join("; ") || "none"}`);
1168
+ if (exploration.domainConcepts.length > 0) {
1169
+ sections.push(`## Domain Concepts
1170
+ ${exploration.domainConcepts.map((c) => `- **${c.name}**: ${c.definition}`).join("\n")}`);
1171
+ }
1172
+ sections.push(`## Development Workflow
1173
+ - Commits: ${exploration.gitWorkflow.commitStyle}
1174
+ - Branching: ${exploration.gitWorkflow.branchStrategy}
1175
+ - PRs: ${exploration.gitWorkflow.prPatterns}
1176
+ - CI/CD: ${exploration.cicdSetup.provider}, stages: ${exploration.cicdSetup.stages.join(" \u2192 ") || "unknown"}
1177
+ - Deploy target: ${exploration.cicdSetup.deployTarget}`);
1178
+ }
1179
+ if (agents && agents.length > 0) {
1180
+ sections.push(`## Generated Agents
1181
+ ${agents.map((a) => `- **${a.id}** (${a.role}): ${a.rationale}`).join("\n")}`);
1182
+ }
1183
+ sections.push(`## Instructions
1184
+
1185
+ Write THREE sections as markdown. Each section should be 2-5 paragraphs of actionable content.
1186
+
1187
+ **Section 1: Architecture**
1188
+ Describe the project's architecture: how modules relate, data flow, key abstractions, and the overall design philosophy. Reference specific directories and files.
1189
+
1190
+ **Section 2: Gotchas**
1191
+ List 3-8 things a new developer (or AI agent) would trip over. Include non-obvious conventions, surprising behaviors, common mistakes, and "don't do X, do Y instead" guidance.
1192
+
1193
+ **Section 3: Anti-Patterns**
1194
+ List 2-5 patterns to avoid in this codebase, with brief explanations of why.
1195
+
1196
+ Respond with ONLY the three markdown sections (with ## headers), no JSON, no code fences around the whole response.`);
1197
+ return sections.join("\n\n");
1198
+ }
1199
+ async function runBrainstormMd(context, dispatcher) {
1200
+ const { analysis, exploration, agents } = context;
1201
+ const projectName = basename(analysis.projectPath);
1202
+ const frontmatter = buildFrontmatter(analysis, exploration);
1203
+ const staticSections = buildStaticSections(
1204
+ analysis,
1205
+ exploration,
1206
+ agents?.map((a) => ({ id: a.id, role: a.role }))
1207
+ );
1208
+ let proseSections = "";
1209
+ let cost = 0;
1210
+ const prompt = buildEnrichmentPrompt(analysis, exploration, agents);
1211
+ const response = await dispatcher.explore(prompt, 0.2);
1212
+ proseSections = response.text;
1213
+ cost = response.cost;
1214
+ const content = assembleBrainstormMd(
1215
+ frontmatter,
1216
+ projectName,
1217
+ staticSections,
1218
+ proseSections
1219
+ );
1220
+ const outputPath = join5(analysis.projectPath, "BRAINSTORM.md");
1221
+ const isUpdate = existsSync6(outputPath);
1222
+ writeFileSync4(outputPath, content, "utf-8");
1223
+ return {
1224
+ contextPatch: { brainstormMd: content },
1225
+ cost,
1226
+ summary: `${isUpdate ? "Updated" : "Generated"} with conventions, domain glossary, architecture`,
1227
+ filesWritten: [outputPath]
1228
+ };
1229
+ }
1230
+ function buildFrontmatter(analysis, exploration) {
1231
+ const lines = ["---", "version: 1"];
1232
+ lines.push(`name: ${basename(analysis.projectPath)}`);
1233
+ const type = inferProjectType(analysis);
1234
+ if (type) lines.push(`type: ${type}`);
1235
+ const lang = mapLanguage(analysis.summary.primaryLanguage);
1236
+ if (lang) lines.push(`language: ${lang}`);
1237
+ const framework = mapFramework(analysis.frameworks.frameworks);
1238
+ lines.push(`framework: ${framework}`);
1239
+ const runtime = inferRuntime(analysis);
1240
+ lines.push(`runtime: ${runtime}`);
1241
+ const deploy = inferDeployTarget(analysis, exploration);
1242
+ lines.push(`deploy: ${deploy}`);
1243
+ const buildCmd = inferBuildCommand(analysis);
1244
+ if (buildCmd) lines.push(`build_command: "${buildCmd}"`);
1245
+ const testCmd = inferTestCommand(analysis);
1246
+ if (testCmd) lines.push(`test_command: "${testCmd}"`);
1247
+ if (analysis.dependencies.entryPoints.length > 0) {
1248
+ const eps = analysis.dependencies.entryPoints.slice(0, 5).map((e) => `"${e}"`).join(", ");
1249
+ lines.push(`entry_points: [${eps}]`);
1250
+ }
1251
+ lines.push("routing:");
1252
+ lines.push(` typical_complexity: ${inferComplexity(analysis)}`);
1253
+ lines.push(` budget_tier: ${inferBudgetTier(analysis)}`);
1254
+ lines.push("---");
1255
+ return lines.join("\n");
1256
+ }
1257
+ function buildStaticSections(analysis, exploration, agents) {
1258
+ const sections = [];
1259
+ sections.push("## Stack\n");
1260
+ if (analysis.summary.primaryLanguage)
1261
+ sections.push(`- **Language:** ${analysis.summary.primaryLanguage}`);
1262
+ if (analysis.summary.frameworkList.length > 0)
1263
+ sections.push(
1264
+ `- **Frameworks:** ${analysis.summary.frameworkList.join(", ")}`
1265
+ );
1266
+ if (analysis.frameworks.databases.length > 0)
1267
+ sections.push(
1268
+ `- **Databases:** ${analysis.frameworks.databases.join(", ")}`
1269
+ );
1270
+ if (analysis.frameworks.testing.length > 0)
1271
+ sections.push(`- **Testing:** ${analysis.frameworks.testing.join(", ")}`);
1272
+ if (analysis.frameworks.buildTools.length > 0)
1273
+ sections.push(`- **Build:** ${analysis.frameworks.buildTools.join(", ")}`);
1274
+ sections.push(
1275
+ `- **Size:** ${analysis.summary.totalFiles} files, ${analysis.summary.totalLines.toLocaleString()} lines, ${analysis.summary.moduleCount} modules`
1276
+ );
1277
+ if (exploration) {
1278
+ sections.push("\n## Conventions\n");
1279
+ const c = exploration.conventions;
1280
+ sections.push(
1281
+ `- **Naming:** variables=${c.naming.variables}, files=${c.naming.files}, exports=${c.naming.exports}`
1282
+ );
1283
+ sections.push(`- **Error handling:** ${c.errorHandling}`);
1284
+ sections.push(`- **Testing:** ${c.testingPatterns}`);
1285
+ sections.push(`- **Imports:** ${c.importStyle}`);
1286
+ if (c.stateManagement)
1287
+ sections.push(`- **State management:** ${c.stateManagement}`);
1288
+ if (c.apiPatterns) sections.push(`- **API patterns:** ${c.apiPatterns}`);
1289
+ if (c.customRules.length > 0) {
1290
+ for (const rule of c.customRules) {
1291
+ sections.push(`- ${rule}`);
1292
+ }
1293
+ }
1294
+ }
1295
+ if (exploration && exploration.domainConcepts.length > 0) {
1296
+ sections.push("\n## Domain Glossary\n");
1297
+ for (const concept of exploration.domainConcepts) {
1298
+ sections.push(`- **${concept.name}:** ${concept.definition}`);
1299
+ }
1300
+ }
1301
+ if (exploration && exploration.keyFiles.length > 0) {
1302
+ sections.push("\n## Key Files\n");
1303
+ for (const kf of exploration.keyFiles) {
1304
+ sections.push(`- \`${kf.path}\` \u2014 ${kf.purpose}`);
1305
+ }
1306
+ }
1307
+ if (agents && agents.length > 0) {
1308
+ sections.push("\n## AI Team\n");
1309
+ for (const a of agents) {
1310
+ sections.push(`- **${a.id}** (${a.role})`);
1311
+ }
1312
+ }
1313
+ return sections.join("\n");
1314
+ }
1315
+ function assembleBrainstormMd(frontmatter, projectName, staticSections, proseSections) {
1316
+ const parts = [frontmatter, "", `# ${projectName}`, "", staticSections];
1317
+ if (proseSections) {
1318
+ parts.push("", proseSections.trim());
1319
+ }
1320
+ return parts.join("\n") + "\n";
1321
+ }
1322
+ function inferProjectType(analysis) {
1323
+ const frameworks = analysis.frameworks.frameworks.map((f) => f.toLowerCase());
1324
+ if (frameworks.includes("turborepo") || frameworks.includes("lerna"))
1325
+ return "monorepo";
1326
+ if (frameworks.some(
1327
+ (f) => ["nextjs", "react", "vue", "angular", "svelte"].includes(f)
1328
+ ))
1329
+ return "app";
1330
+ if (analysis.dependencies.entryPoints.some(
1331
+ (e) => e.includes("cli") || e.includes("bin")
1332
+ ))
1333
+ return "cli";
1334
+ if (analysis.summary.apiRouteCount > 0) return "api";
1335
+ return null;
1336
+ }
1337
+ function mapLanguage(primary) {
1338
+ const map = {
1339
+ TypeScript: "typescript",
1340
+ JavaScript: "typescript",
1341
+ Python: "python",
1342
+ Rust: "rust",
1343
+ Go: "go",
1344
+ Java: "java"
1345
+ };
1346
+ return map[primary] ?? null;
1347
+ }
1348
+ function mapFramework(frameworks) {
1349
+ const lower = frameworks.map((f) => f.toLowerCase());
1350
+ if (lower.includes("nextjs") || lower.includes("next.js")) return "nextjs";
1351
+ if (lower.includes("hono")) return "hono";
1352
+ if (lower.includes("fastapi")) return "fastapi";
1353
+ if (lower.includes("express")) return "express";
1354
+ return "none";
1355
+ }
1356
+ function inferRuntime(analysis) {
1357
+ const lang = analysis.summary.primaryLanguage.toLowerCase();
1358
+ if (lang === "python") return "python";
1359
+ if (lang === "go") return "go";
1360
+ return "node";
1361
+ }
1362
+ function inferDeployTarget(analysis, exploration) {
1363
+ if (exploration?.cicdSetup.deployTarget && exploration.cicdSetup.deployTarget !== "none") {
1364
+ return exploration.cicdSetup.deployTarget;
1365
+ }
1366
+ const deploy = analysis.frameworks.deployment ?? [];
1367
+ if (deploy.some((d) => d.toLowerCase().includes("vercel"))) return "vercel";
1368
+ if (deploy.some((d) => d.toLowerCase().includes("docker"))) return "docker";
1369
+ return "none";
1370
+ }
1371
+ function inferBuildCommand(analysis) {
1372
+ const frameworks = analysis.frameworks.frameworks.map((f) => f.toLowerCase());
1373
+ if (frameworks.includes("turborepo")) return "npx turbo run build";
1374
+ if (analysis.summary.primaryLanguage === "TypeScript" || analysis.summary.primaryLanguage === "JavaScript")
1375
+ return "npm run build";
1376
+ if (analysis.summary.primaryLanguage === "Python") return null;
1377
+ if (analysis.summary.primaryLanguage === "Go") return "go build ./...";
1378
+ return null;
1379
+ }
1380
+ function inferTestCommand(analysis) {
1381
+ if (analysis.frameworks.testing.length === 0) return null;
1382
+ const frameworks = analysis.frameworks.frameworks.map((f) => f.toLowerCase());
1383
+ if (frameworks.includes("turborepo")) return "npx turbo run test";
1384
+ if (analysis.frameworks.testing.some((t) => t.toLowerCase().includes("vitest")))
1385
+ return "npx vitest run";
1386
+ if (analysis.frameworks.testing.some((t) => t.toLowerCase().includes("jest")))
1387
+ return "npx jest";
1388
+ if (analysis.frameworks.testing.some((t) => t.toLowerCase().includes("pytest")))
1389
+ return "pytest";
1390
+ return "npm test";
1391
+ }
1392
+ function inferComplexity(analysis) {
1393
+ const avg = analysis.summary.avgComplexity;
1394
+ if (avg < 5) return "simple";
1395
+ if (avg < 10) return "moderate";
1396
+ if (avg < 20) return "complex";
1397
+ return "expert";
1398
+ }
1399
+ function inferBudgetTier(analysis) {
1400
+ const files = analysis.summary.totalFiles;
1401
+ if (files < 50) return "low";
1402
+ if (files < 500) return "standard";
1403
+ return "premium";
1404
+ }
1405
+ var log = createLogger("onboard");
1406
+ var LLM_PHASES = [
1407
+ "deep-exploration",
1408
+ "team-assembly",
1409
+ "routing-rules",
1410
+ "workflow-gen",
1411
+ "brainstorm-md"
1412
+ ];
1413
+ async function* runOnboardPipeline(options, dispatcher) {
1414
+ const startTime = Date.now();
1415
+ const phases = options.phases ?? ALL_PHASES;
1416
+ const filesWritten = [];
1417
+ const phasesRun = [];
1418
+ const phasesSkipped = [];
1419
+ let context = null;
1420
+ let budget = null;
1421
+ if (phases.includes("static-analysis")) {
1422
+ const phaseStart = Date.now();
1423
+ yield {
1424
+ type: "phase-started",
1425
+ phase: "static-analysis",
1426
+ description: "Analyzing codebase structure"
1427
+ };
1428
+ try {
1429
+ const { analysis, gitSummary } = runStaticAnalysis(options.projectPath);
1430
+ context = { analysis };
1431
+ context._gitSummary = gitSummary;
1432
+ const totalBudget = options.budget ?? inferBudget(analysis);
1433
+ budget = createBudgetTracker(totalBudget);
1434
+ const summary = [
1435
+ `${analysis.summary.totalFiles} files`,
1436
+ `${analysis.summary.totalLines.toLocaleString()} lines`,
1437
+ `${analysis.summary.moduleCount} modules`,
1438
+ analysis.summary.primaryLanguage,
1439
+ ...analysis.summary.frameworkList.slice(0, 3)
1440
+ ].join(", ");
1441
+ phasesRun.push("static-analysis");
1442
+ yield {
1443
+ type: "phase-completed",
1444
+ phase: "static-analysis",
1445
+ cost: 0,
1446
+ durationMs: Date.now() - phaseStart,
1447
+ summary
1448
+ };
1449
+ } catch (error) {
1450
+ yield {
1451
+ type: "phase-failed",
1452
+ phase: "static-analysis",
1453
+ error: error instanceof Error ? error.message : String(error)
1454
+ };
1455
+ return;
1456
+ }
1457
+ }
1458
+ if (!context) {
1459
+ yield {
1460
+ type: "phase-failed",
1461
+ phase: "static-analysis",
1462
+ error: "Static analysis is required but was not included in phases"
1463
+ };
1464
+ return;
1465
+ }
1466
+ if (!budget) {
1467
+ budget = createBudgetTracker(options.budget ?? 5);
1468
+ }
1469
+ const estimatedTotal = phases.filter((p) => LLM_PHASES.includes(p)).reduce((sum, p) => sum + (PHASE_COST_ESTIMATES[p] ?? 0), 0);
1470
+ yield {
1471
+ type: "onboard-started",
1472
+ options,
1473
+ estimatedBudget: Math.round(estimatedTotal * 100) / 100
1474
+ };
1475
+ if (!options.staticOnly && dispatcher) {
1476
+ for (const phase of LLM_PHASES) {
1477
+ if (!phases.includes(phase)) continue;
1478
+ const estimate = PHASE_COST_ESTIMATES[phase] ?? 0;
1479
+ if (estimate > 0 && !budget.canAfford(estimate)) {
1480
+ yield {
1481
+ type: "budget-warning",
1482
+ spent: budget.spent,
1483
+ remaining: budget.remaining
1484
+ };
1485
+ yield {
1486
+ type: "phase-skipped",
1487
+ phase,
1488
+ reason: `Budget insufficient (need ~$${estimate.toFixed(2)}, have $${budget.remaining.toFixed(2)})`
1489
+ };
1490
+ phasesSkipped.push(phase);
1491
+ continue;
1492
+ }
1493
+ if (options.dryRun) {
1494
+ yield {
1495
+ type: "phase-skipped",
1496
+ phase,
1497
+ reason: `Dry run \u2014 would cost ~$${estimate.toFixed(2)}`
1498
+ };
1499
+ phasesSkipped.push(phase);
1500
+ continue;
1501
+ }
1502
+ const phaseStart = Date.now();
1503
+ yield {
1504
+ type: "phase-started",
1505
+ phase,
1506
+ description: getPhaseDescription(phase)
1507
+ };
1508
+ try {
1509
+ const result = await runLLMPhase(phase, context, dispatcher);
1510
+ const cost = result.cost;
1511
+ budget.record(cost);
1512
+ Object.assign(context, result.contextPatch);
1513
+ if (result.filesWritten) {
1514
+ filesWritten.push(...result.filesWritten);
1515
+ for (const f of result.filesWritten) {
1516
+ yield { type: "file-written", path: f, description: phase };
1517
+ }
1518
+ }
1519
+ phasesRun.push(phase);
1520
+ yield {
1521
+ type: "phase-completed",
1522
+ phase,
1523
+ cost,
1524
+ durationMs: Date.now() - phaseStart,
1525
+ summary: result.summary
1526
+ };
1527
+ } catch (error) {
1528
+ yield {
1529
+ type: "phase-failed",
1530
+ phase,
1531
+ error: error instanceof Error ? error.message : String(error)
1532
+ };
1533
+ phasesSkipped.push(phase);
1534
+ }
1535
+ }
1536
+ } else {
1537
+ for (const phase of LLM_PHASES) {
1538
+ if (!phases.includes(phase)) continue;
1539
+ yield {
1540
+ type: "phase-skipped",
1541
+ phase,
1542
+ reason: options.staticOnly ? "Static-only mode" : "No LLM dispatcher provided"
1543
+ };
1544
+ phasesSkipped.push(phase);
1545
+ }
1546
+ }
1547
+ if (phases.includes("verification")) {
1548
+ const phaseStart = Date.now();
1549
+ yield {
1550
+ type: "phase-started",
1551
+ phase: "verification",
1552
+ description: "Validating generated artifacts"
1553
+ };
1554
+ const verification = runVerification(context);
1555
+ context.verification = verification;
1556
+ phasesRun.push("verification");
1557
+ const counts = [];
1558
+ if (context.agents) {
1559
+ counts.push(
1560
+ `${context.agents.length} agents ${verification.agentsValid ? "valid" : "INVALID"}`
1561
+ );
1562
+ }
1563
+ if (context.routingRules) {
1564
+ counts.push(
1565
+ `${context.routingRules.length} rules ${verification.routingValid ? "valid" : "INVALID"}`
1566
+ );
1567
+ }
1568
+ if (context.recipes) {
1569
+ counts.push(
1570
+ `${context.recipes.length} recipes ${verification.recipesValid ? "valid" : "INVALID"}`
1571
+ );
1572
+ }
1573
+ if (context.brainstormMd) {
1574
+ counts.push(
1575
+ `BRAINSTORM.md ${verification.brainstormMdValid ? "valid" : "INVALID"}`
1576
+ );
1577
+ }
1578
+ yield {
1579
+ type: "phase-completed",
1580
+ phase: "verification",
1581
+ cost: 0,
1582
+ durationMs: Date.now() - phaseStart,
1583
+ summary: counts.join(", ") || "No artifacts to verify"
1584
+ };
1585
+ }
1586
+ yield {
1587
+ type: "onboard-completed",
1588
+ result: {
1589
+ context,
1590
+ filesWritten,
1591
+ totalCost: budget.spent,
1592
+ totalDurationMs: Date.now() - startTime,
1593
+ phasesRun,
1594
+ phasesSkipped
1595
+ }
1596
+ };
1597
+ }
1598
+ async function runLLMPhase(phase, context, dispatcher) {
1599
+ switch (phase) {
1600
+ case "deep-exploration":
1601
+ return runDeepExploration(context, dispatcher);
1602
+ case "team-assembly":
1603
+ return runTeamAssembly(context, dispatcher);
1604
+ case "routing-rules":
1605
+ return runRoutingRules(context, dispatcher);
1606
+ case "workflow-gen":
1607
+ return runWorkflowGen(context, dispatcher);
1608
+ case "brainstorm-md":
1609
+ return runBrainstormMd(context, dispatcher);
1610
+ default:
1611
+ throw new Error(`Unknown LLM phase: ${phase}`);
1612
+ }
1613
+ }
1614
+ function getPhaseDescription(phase) {
1615
+ switch (phase) {
1616
+ case "deep-exploration":
1617
+ return "Reading key files, discovering conventions and domain concepts";
1618
+ case "team-assembly":
1619
+ return "Generating specialized agents with project-specific knowledge";
1620
+ case "routing-rules":
1621
+ return "Creating task-to-agent routing rules";
1622
+ case "workflow-gen":
1623
+ return "Building project-specific workflow recipes";
1624
+ case "brainstorm-md":
1625
+ return "Generating enhanced BRAINSTORM.md with real conventions";
1626
+ default:
1627
+ return PHASE_LABELS[phase] ?? phase;
1628
+ }
1629
+ }
1630
+ export {
1631
+ ALL_PHASES,
1632
+ PHASE_LABELS,
1633
+ createBudgetTracker,
1634
+ inferBudget,
1635
+ runOnboardPipeline,
1636
+ runStaticAnalysis,
1637
+ runVerification
1638
+ };
1639
+ //# sourceMappingURL=dist-Z4SBSK4Q.js.map