@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.
- package/dist/{App-SSKWB7CT.js → App-HGJSYIVS.js} +133 -62
- package/dist/App-HGJSYIVS.js.map +1 -0
- package/dist/{App-DPXJYXKH.js → App-XCJW3A4I.js} +133 -62
- package/dist/App-XCJW3A4I.js.map +1 -0
- package/dist/brainstorm.js +1428 -150
- package/dist/brainstorm.js.map +1 -1
- package/dist/{chunk-7D4SUZUM.js → chunk-PR4QN5HX.js} +6 -1
- package/dist/{chunk-ZWE3DS7E.js → chunk-SEGVTWSK.js} +6 -1
- package/dist/{chunk-5NA3GH6X.js → chunk-WYQU3HAB.js} +175 -10
- package/dist/chunk-WYQU3HAB.js.map +1 -0
- package/dist/{chunk-55ITCWZZ.js → chunk-Z2QIGVYT.js} +175 -10
- package/dist/chunk-Z2QIGVYT.js.map +1 -0
- package/dist/{dist-GNHTH2DH.js → dist-3BC75XHW.js} +2 -2
- package/dist/dist-42TQFHMB.js +1640 -0
- package/dist/dist-42TQFHMB.js.map +1 -0
- package/dist/dist-BULARAL3.js +7308 -0
- package/dist/dist-BULARAL3.js.map +1 -0
- package/dist/{dist-JUDVPE7G.js → dist-ET6CSS7O.js} +2 -2
- package/dist/{dist-DUDO3RDM.js → dist-GVK5Q4YK.js} +62 -16
- package/dist/{dist-V5DTSTKJ.js.map → dist-GVK5Q4YK.js.map} +1 -1
- package/dist/{dist-V5DTSTKJ.js → dist-K7BDAMTO.js} +62 -16
- package/dist/{dist-DUDO3RDM.js.map → dist-K7BDAMTO.js.map} +1 -1
- package/dist/{dist-WLTQTLFO.js → dist-OYQTULIU.js} +2 -2
- package/dist/dist-S5JYXFUW.js +7307 -0
- package/dist/dist-S5JYXFUW.js.map +1 -0
- package/dist/{dist-YIGU37Q2.js → dist-Y25MC2VO.js} +2 -2
- package/dist/dist-Z4SBSK4Q.js +1639 -0
- package/dist/dist-Z4SBSK4Q.js.map +1 -0
- package/dist/index.js +1428 -150
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-CROPNYHI.js +4252 -0
- package/dist/mcp-server-CROPNYHI.js.map +1 -0
- package/dist/mcp-server-IJVEG6CS.js +4251 -0
- package/dist/mcp-server-IJVEG6CS.js.map +1 -0
- package/dist/{recorder-D6ILEOZP.js → recorder-33N4U6TO.js} +2 -2
- package/dist/{recorder-SPYYF4DL.js → recorder-APOOXJYA.js} +2 -2
- package/dist/{roles-2DGF4PZU.js → roles-GKDCLP5G.js} +2 -2
- package/dist/{roles-UIPX7GBC.js → roles-UUIISXEW.js} +2 -2
- package/dist/{slash-PDWKCZOQ.js → slash-4XSR3SJD.js} +3 -3
- package/dist/{slash-ZDC4DKL4.js → slash-CVWH3LTR.js} +3 -3
- package/package.json +4 -2
- package/dist/App-DPXJYXKH.js.map +0 -1
- package/dist/App-SSKWB7CT.js.map +0 -1
- package/dist/chunk-55ITCWZZ.js.map +0 -1
- package/dist/chunk-5NA3GH6X.js.map +0 -1
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-PR4QN5HX.js.map} +0 -0
- /package/dist/{chunk-ZWE3DS7E.js.map → chunk-SEGVTWSK.js.map} +0 -0
- /package/dist/{dist-GNHTH2DH.js.map → dist-3BC75XHW.js.map} +0 -0
- /package/dist/{dist-JUDVPE7G.js.map → dist-ET6CSS7O.js.map} +0 -0
- /package/dist/{dist-WLTQTLFO.js.map → dist-OYQTULIU.js.map} +0 -0
- /package/dist/{dist-YIGU37Q2.js.map → dist-Y25MC2VO.js.map} +0 -0
- /package/dist/{recorder-D6ILEOZP.js.map → recorder-33N4U6TO.js.map} +0 -0
- /package/dist/{recorder-SPYYF4DL.js.map → recorder-APOOXJYA.js.map} +0 -0
- /package/dist/{roles-2DGF4PZU.js.map → roles-GKDCLP5G.js.map} +0 -0
- /package/dist/{roles-UIPX7GBC.js.map → roles-UUIISXEW.js.map} +0 -0
- /package/dist/{slash-PDWKCZOQ.js.map → slash-4XSR3SJD.js.map} +0 -0
- /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
|