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