codebakers 2.5.3 → 3.0.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 (60) hide show
  1. package/README.md +54 -255
  2. package/dist/chunk-HOWR3YTF.js +146 -0
  3. package/dist/index.d.ts +0 -3
  4. package/dist/index.js +10489 -7994
  5. package/dist/terminal-6ZQVP6R7.js +10 -0
  6. package/package.json +26 -41
  7. package/AUDIT_REPORT.md +0 -138
  8. package/dist/advisors-RWRTSJRR.js +0 -7
  9. package/dist/chunk-ASIJIQYC.js +0 -320
  10. package/dist/chunk-D44U3IEA.js +0 -565
  11. package/dist/chunk-LANM5XQW.js +0 -326
  12. package/dist/prd-RYITSL6Q.js +0 -7
  13. package/install.bat +0 -9
  14. package/installers/CodeBakers-Install.bat +0 -207
  15. package/installers/CodeBakers-Install.command +0 -232
  16. package/installers/README.md +0 -157
  17. package/installers/mac/assets/README.txt +0 -31
  18. package/installers/mac/build-mac-installer.sh +0 -240
  19. package/installers/windows/CodeBakers.iss +0 -256
  20. package/installers/windows/assets/README.txt +0 -16
  21. package/installers/windows/scripts/post-install.bat +0 -15
  22. package/src/channels/discord.ts +0 -5
  23. package/src/channels/slack.ts +0 -5
  24. package/src/channels/sms.ts +0 -4
  25. package/src/channels/telegram.ts +0 -5
  26. package/src/channels/whatsapp.ts +0 -7
  27. package/src/commands/advisors.ts +0 -699
  28. package/src/commands/build.ts +0 -1025
  29. package/src/commands/check.ts +0 -365
  30. package/src/commands/code.ts +0 -806
  31. package/src/commands/connect.ts +0 -12
  32. package/src/commands/deploy.ts +0 -448
  33. package/src/commands/design.ts +0 -298
  34. package/src/commands/fix.ts +0 -20
  35. package/src/commands/gateway.ts +0 -604
  36. package/src/commands/generate.ts +0 -178
  37. package/src/commands/init.ts +0 -634
  38. package/src/commands/integrate.ts +0 -884
  39. package/src/commands/learn.ts +0 -36
  40. package/src/commands/migrate.ts +0 -419
  41. package/src/commands/prd-maker.ts +0 -588
  42. package/src/commands/prd.ts +0 -419
  43. package/src/commands/security.ts +0 -102
  44. package/src/commands/setup.ts +0 -600
  45. package/src/commands/status.ts +0 -56
  46. package/src/commands/website.ts +0 -741
  47. package/src/index.ts +0 -627
  48. package/src/patterns/loader.ts +0 -337
  49. package/src/services/github.ts +0 -61
  50. package/src/services/supabase.ts +0 -147
  51. package/src/services/vercel.ts +0 -61
  52. package/src/utils/claude-md.ts +0 -287
  53. package/src/utils/config.ts +0 -375
  54. package/src/utils/display.ts +0 -338
  55. package/src/utils/files.ts +0 -418
  56. package/src/utils/nlp.ts +0 -312
  57. package/src/utils/ui.ts +0 -441
  58. package/src/utils/updates.ts +0 -8
  59. package/src/utils/voice.ts +0 -323
  60. package/tsconfig.json +0 -26
@@ -1,1025 +0,0 @@
1
- import * as p from '@clack/prompts';
2
- import chalk from 'chalk';
3
- import fs from 'fs-extra';
4
- import * as path from 'path';
5
- import Anthropic from '@anthropic-ai/sdk';
6
- import { execa } from 'execa';
7
- import { Config } from '../utils/config.js';
8
- import { EventEmitter } from 'events';
9
-
10
- // ============================================================================
11
- // TYPES
12
- // ============================================================================
13
-
14
- interface AgentTask {
15
- id: string;
16
- name: string;
17
- description: string;
18
- folder: string;
19
- dependencies: string[];
20
- exports: string[];
21
- status: 'waiting' | 'running' | 'done' | 'error' | 'healing' | 'asking';
22
- progress: number;
23
- currentAction: string;
24
- branch: string;
25
- files: string[];
26
- error?: string;
27
- healAttempts: number;
28
- }
29
-
30
- interface BuildWave {
31
- wave: number;
32
- agents: AgentTask[];
33
- parallel: boolean;
34
- }
35
-
36
- interface BuildPlan {
37
- projectName: string;
38
- description: string;
39
- sharedSetup: {
40
- types: string[];
41
- components: string[];
42
- conventions: string;
43
- };
44
- waves: BuildWave[];
45
- }
46
-
47
- interface HealerSolution {
48
- canFix: boolean;
49
- solution: string;
50
- type: 'retry' | 'modify-code' | 'wait' | 'skip-optional' | 'generate-missing';
51
- newCode?: string;
52
- waitTime?: number;
53
- description: string;
54
- }
55
-
56
- interface UserQuestion {
57
- question: string;
58
- options: string[];
59
- context?: string;
60
- }
61
-
62
- // Global flag to pause/resume display during questions
63
- let displayPaused = false;
64
-
65
- // ============================================================================
66
- // MAIN BUILD COMMAND
67
- // ============================================================================
68
-
69
- export async function buildCommand(prdPath?: string, options: { sequential?: boolean } = {}): Promise<void> {
70
- const config = new Config();
71
-
72
- if (!config.isConfigured()) {
73
- console.log(chalk.yellow(`
74
- ⚠️ CodeBakers isn't set up yet.
75
-
76
- Run this first:
77
- ${chalk.cyan('codebakers setup')}
78
- `));
79
- return;
80
- }
81
-
82
- const anthropicCreds = config.getCredentials('anthropic');
83
- if (!anthropicCreds?.apiKey) {
84
- console.log(chalk.yellow(`
85
- ⚠️ Anthropic API key not configured.
86
-
87
- The parallel build needs Claude AI to work.
88
-
89
- Run this to add your API key:
90
- ${chalk.cyan('codebakers setup')}
91
- `));
92
- return;
93
- }
94
-
95
- // Get PRD file
96
- let prdFile = prdPath;
97
- if (!prdFile) {
98
- console.log(chalk.dim(`
99
- A PRD (Product Requirements Document) describes what you want to build.
100
-
101
- Don't have one? Create one with:
102
- ${chalk.cyan('codebakers prd-maker')}
103
- `));
104
-
105
- const file = await p.text({
106
- message: 'Path to PRD file:',
107
- placeholder: './my-app-prd.md',
108
- validate: (v) => !v ? 'PRD file required' : undefined,
109
- });
110
- if (p.isCancel(file)) return;
111
- prdFile = file as string;
112
- }
113
-
114
- // Read PRD
115
- if (!await fs.pathExists(prdFile)) {
116
- console.log(chalk.red(`
117
- ❌ PRD file not found: ${prdFile}
118
-
119
- Make sure the file exists, or create one:
120
- ${chalk.cyan('codebakers prd-maker')}
121
- `));
122
- return;
123
- }
124
-
125
- const prdContent = await fs.readFile(prdFile, 'utf-8');
126
-
127
- console.log(chalk.cyan(`
128
- ╔═══════════════════════════════════════════════════════════════╗
129
- ║ 🚀 CODEBAKERS PARALLEL BUILD ║
130
- ║ ║
131
- ║ This will: ║
132
- ║ 1. Analyze your PRD ║
133
- ║ 2. Break it into features ║
134
- ║ 3. Run up to 3 AI agents in parallel ║
135
- ║ 4. Auto-fix any errors ║
136
- ║ 5. Merge everything together ║
137
- ╚═══════════════════════════════════════════════════════════════╝
138
- `));
139
-
140
- const anthropic = new Anthropic({ apiKey: anthropicCreds.apiKey });
141
-
142
- // Step 1: Analyze PRD and create build plan
143
- const spinner = p.spinner();
144
- spinner.start('Analyzing PRD...');
145
-
146
- const buildPlan = await analyzePRD(anthropic, prdContent);
147
-
148
- spinner.stop('PRD analyzed');
149
-
150
- // Show build plan
151
- displayBuildPlan(buildPlan);
152
-
153
- // Check if parallel makes sense
154
- const totalAgents = buildPlan.waves.reduce((sum, w) => sum + w.agents.length, 0);
155
- const useParallel = !options.sequential && totalAgents > 2;
156
-
157
- if (!useParallel) {
158
- console.log(chalk.dim('\nUsing sequential build (parallel not needed for small projects)\n'));
159
- }
160
-
161
- // Confirm
162
- const proceed = await p.confirm({
163
- message: `Start ${useParallel ? 'parallel' : 'sequential'} build?`,
164
- initialValue: true,
165
- });
166
-
167
- if (!proceed || p.isCancel(proceed)) {
168
- p.cancel('Build cancelled');
169
- return;
170
- }
171
-
172
- // Step 2: Create project directory
173
- const projectPath = path.join(process.cwd(), buildPlan.projectName);
174
-
175
- if (await fs.pathExists(projectPath)) {
176
- const overwrite = await p.confirm({
177
- message: `${buildPlan.projectName} already exists. Overwrite?`,
178
- initialValue: false,
179
- });
180
- if (!overwrite || p.isCancel(overwrite)) return;
181
- await fs.remove(projectPath);
182
- }
183
-
184
- await fs.ensureDir(projectPath);
185
- process.chdir(projectPath);
186
-
187
- // Step 3: Initialize git
188
- spinner.start('Initializing project...');
189
- await execa('git', ['init'], { cwd: projectPath });
190
- spinner.stop('Project initialized');
191
-
192
- // Step 4: Run setup phase (shared code)
193
- await runSetupPhase(anthropic, buildPlan, projectPath);
194
-
195
- // Step 5: Execute build waves
196
- const startTime = Date.now();
197
-
198
- if (useParallel) {
199
- await executeParallelBuild(anthropic, buildPlan, projectPath, config);
200
- } else {
201
- await executeSequentialBuild(anthropic, buildPlan, projectPath, config);
202
- }
203
-
204
- // Step 6: Integration phase
205
- await runIntegrationPhase(anthropic, buildPlan, projectPath);
206
-
207
- // Step 7: Final setup
208
- spinner.start('Installing dependencies...');
209
- try {
210
- await execa('npm', ['install'], { cwd: projectPath, stdio: 'inherit' });
211
- spinner.stop('Dependencies installed');
212
- } catch (error) {
213
- spinner.stop('Dependencies installation failed');
214
- console.log(chalk.yellow(' ⚠️ Run "npm install" manually in the project folder'));
215
- }
216
-
217
- // Done!
218
- const elapsed = Math.round((Date.now() - startTime) / 1000);
219
- const minutes = Math.floor(elapsed / 60);
220
- const seconds = elapsed % 60;
221
-
222
- console.log(chalk.green(`
223
- ╔═══════════════════════════════════════════════════════════════╗
224
- ║ ✅ BUILD COMPLETE ║
225
- ╠═══════════════════════════════════════════════════════════════╣
226
- ║ ║
227
- ║ Project: ${buildPlan.projectName.padEnd(46)}║
228
- ║ Time: ${(minutes + 'm ' + seconds + 's').padEnd(46)}║
229
- ║ Features: ${(buildPlan.waves.reduce((s, w) => s + w.agents.length, 0) + ' built').padEnd(46)}║
230
- ║ ║
231
- ║ Next steps: ║
232
- ║ cd ${buildPlan.projectName.padEnd(52)}║
233
- ║ npm run dev ║
234
- ║ ║
235
- ╚═══════════════════════════════════════════════════════════════╝
236
- `));
237
- }
238
-
239
- // ============================================================================
240
- // PRD ANALYZER
241
- // ============================================================================
242
-
243
- async function analyzePRD(anthropic: Anthropic, prdContent: string): Promise<BuildPlan> {
244
- const response = await anthropic.messages.create({
245
- model: 'claude-sonnet-4-5-20250929',
246
- max_tokens: 4096,
247
- messages: [{
248
- role: 'user',
249
- content: `Analyze this PRD and create a build plan with parallel-safe task assignment.
250
-
251
- PRD:
252
- ${prdContent}
253
-
254
- Rules:
255
- 1. Each feature gets its own folder (src/features/[name])
256
- 2. Features with no dependencies can run in parallel
257
- 3. Features that depend on others must wait
258
- 4. Group into "waves" - each wave runs after previous completes
259
- 5. Maximum 3 agents per wave
260
-
261
- Return JSON only:
262
- {
263
- "projectName": "kebab-case-name",
264
- "description": "one line description",
265
- "sharedSetup": {
266
- "types": ["User", "Invoice", etc - shared types needed],
267
- "components": ["Button", "Card", etc - shared UI needed],
268
- "conventions": "Next.js App Router, TypeScript, Tailwind, shadcn/ui"
269
- },
270
- "waves": [
271
- {
272
- "wave": 1,
273
- "parallel": true,
274
- "agents": [
275
- {
276
- "id": "auth",
277
- "name": "Authentication",
278
- "description": "Login, signup, session management",
279
- "folder": "src/features/auth",
280
- "dependencies": [],
281
- "exports": ["AuthProvider", "useAuth", "LoginForm"]
282
- }
283
- ]
284
- }
285
- ]
286
- }
287
-
288
- Think about:
289
- - What MUST come first (auth, database setup)?
290
- - What can run at the same time (independent features)?
291
- - What needs data from other features (dashboards, reports)?`
292
- }],
293
- });
294
-
295
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
296
- const jsonMatch = text.match(/\{[\s\S]*\}/);
297
-
298
- if (!jsonMatch) {
299
- throw new Error('Failed to parse PRD analysis');
300
- }
301
-
302
- const plan = JSON.parse(jsonMatch[0]) as BuildPlan;
303
-
304
- // Initialize agent task fields
305
- for (const wave of plan.waves) {
306
- for (const agent of wave.agents) {
307
- (agent as AgentTask).status = 'waiting';
308
- (agent as AgentTask).progress = 0;
309
- (agent as AgentTask).currentAction = '';
310
- (agent as AgentTask).branch = `codebakers/${agent.id}`;
311
- (agent as AgentTask).files = [];
312
- (agent as AgentTask).healAttempts = 0;
313
- }
314
- }
315
-
316
- return plan;
317
- }
318
-
319
- // ============================================================================
320
- // DISPLAY
321
- // ============================================================================
322
-
323
- function displayBuildPlan(plan: BuildPlan): void {
324
- console.log(chalk.bold(`\n📋 Build Plan: ${plan.projectName}\n`));
325
- console.log(chalk.dim(` ${plan.description}\n`));
326
-
327
- for (const wave of plan.waves) {
328
- const parallel = wave.parallel && wave.agents.length > 1;
329
- const mode = parallel ? chalk.green('parallel') : chalk.yellow('sequential');
330
-
331
- console.log(chalk.bold(` Wave ${wave.wave} (${mode}):`));
332
-
333
- for (const agent of wave.agents) {
334
- const deps = agent.dependencies.length > 0
335
- ? chalk.dim(` ← needs: ${agent.dependencies.join(', ')}`)
336
- : '';
337
- console.log(` • ${agent.name} ${deps}`);
338
- }
339
- console.log('');
340
- }
341
-
342
- const totalAgents = plan.waves.reduce((sum, w) => sum + w.agents.length, 0);
343
- const parallelWaves = plan.waves.filter(w => w.parallel && w.agents.length > 1).length;
344
-
345
- console.log(chalk.dim(` Total: ${totalAgents} features, ${plan.waves.length} waves, ${parallelWaves} parallel\n`));
346
- }
347
-
348
- // ============================================================================
349
- // SETUP PHASE
350
- // ============================================================================
351
-
352
- async function runSetupPhase(
353
- anthropic: Anthropic,
354
- plan: BuildPlan,
355
- projectPath: string
356
- ): Promise<void> {
357
- const spinner = p.spinner();
358
- spinner.start('Setting up project structure...');
359
-
360
- // Create base structure
361
- await fs.ensureDir(path.join(projectPath, 'src/app'));
362
- await fs.ensureDir(path.join(projectPath, 'src/components/ui'));
363
- await fs.ensureDir(path.join(projectPath, 'src/lib'));
364
- await fs.ensureDir(path.join(projectPath, 'src/types'));
365
- await fs.ensureDir(path.join(projectPath, 'src/features'));
366
- await fs.ensureDir(path.join(projectPath, '.codebakers'));
367
-
368
- // Generate shared setup code
369
- const setupResponse = await anthropic.messages.create({
370
- model: 'claude-sonnet-4-5-20250929',
371
- max_tokens: 8192,
372
- messages: [{
373
- role: 'user',
374
- content: `Generate the shared setup files for this project.
375
-
376
- Project: ${plan.projectName}
377
- Description: ${plan.description}
378
- Conventions: ${plan.sharedSetup.conventions}
379
- Shared Types: ${plan.sharedSetup.types.join(', ')}
380
- Shared Components: ${plan.sharedSetup.components.join(', ')}
381
-
382
- Generate these files:
383
-
384
- 1. package.json (Next.js 14, TypeScript, Tailwind, shadcn/ui)
385
- 2. tsconfig.json
386
- 3. tailwind.config.ts
387
- 4. src/lib/utils.ts (cn helper)
388
- 5. src/types/index.ts (shared types)
389
- 6. src/app/layout.tsx (basic layout)
390
- 7. src/app/page.tsx (simple home page placeholder)
391
- 8. .env.example
392
- 9. CLAUDE.md (CodeBakers patterns)
393
-
394
- Output format:
395
- <<<FILE: path/to/file>>>
396
- content
397
- <<<END_FILE>>>
398
-
399
- Use TypeScript. Include all necessary imports.`
400
- }],
401
- });
402
-
403
- const setupText = setupResponse.content[0].type === 'text' ? setupResponse.content[0].text : '';
404
- await writeFilesFromResponse(setupText, projectPath);
405
-
406
- // Initial git commit
407
- await execa('git', ['add', '.'], { cwd: projectPath });
408
- await execa('git', ['commit', '-m', 'Initial setup'], { cwd: projectPath });
409
-
410
- spinner.stop('Project structure ready');
411
- }
412
-
413
- // ============================================================================
414
- // PARALLEL BUILD EXECUTION
415
- // ============================================================================
416
-
417
- async function executeParallelBuild(
418
- anthropic: Anthropic,
419
- plan: BuildPlan,
420
- projectPath: string,
421
- config: Config
422
- ): Promise<void> {
423
- for (const wave of plan.waves) {
424
- console.log(chalk.bold(`\n🏗️ Wave ${wave.wave} of ${plan.waves.length}`));
425
-
426
- if (wave.parallel && wave.agents.length > 1) {
427
- console.log(chalk.dim(` Running ${wave.agents.length} agents in parallel\n`));
428
- await executeWaveParallel(anthropic, wave.agents as AgentTask[], projectPath, plan);
429
- } else {
430
- console.log(chalk.dim(` Running sequentially\n`));
431
- for (const agent of wave.agents) {
432
- await executeAgent(anthropic, agent as AgentTask, projectPath, plan);
433
- }
434
- }
435
-
436
- // Merge all branches from this wave
437
- await mergeWaveBranches(wave.agents as AgentTask[], projectPath);
438
- }
439
- }
440
-
441
- async function executeWaveParallel(
442
- anthropic: Anthropic,
443
- agents: AgentTask[],
444
- projectPath: string,
445
- plan: BuildPlan
446
- ): Promise<void> {
447
- // Create progress display
448
- const display = new ProgressDisplay(agents);
449
- display.start();
450
-
451
- // Run agents in parallel
452
- const promises = agents.map(agent =>
453
- executeAgentWithProgress(anthropic, agent, projectPath, plan, display)
454
- );
455
-
456
- await Promise.all(promises);
457
-
458
- display.stop();
459
- }
460
-
461
- async function executeAgentWithProgress(
462
- anthropic: Anthropic,
463
- agent: AgentTask,
464
- projectPath: string,
465
- plan: BuildPlan,
466
- display: ProgressDisplay
467
- ): Promise<void> {
468
- agent.status = 'running';
469
- display.update();
470
-
471
- try {
472
- await executeAgent(anthropic, agent, projectPath, plan, (progress, action) => {
473
- agent.progress = progress;
474
- agent.currentAction = action;
475
- display.update();
476
- });
477
-
478
- agent.status = 'done';
479
- agent.progress = 100;
480
- display.update();
481
- } catch (error) {
482
- agent.status = 'error';
483
- agent.error = error instanceof Error ? error.message : 'Unknown error';
484
- display.update();
485
- }
486
- }
487
-
488
- // ============================================================================
489
- // SEQUENTIAL BUILD EXECUTION
490
- // ============================================================================
491
-
492
- async function executeSequentialBuild(
493
- anthropic: Anthropic,
494
- plan: BuildPlan,
495
- projectPath: string,
496
- config: Config
497
- ): Promise<void> {
498
- for (const wave of plan.waves) {
499
- for (const agent of wave.agents) {
500
- const spinner = p.spinner();
501
- spinner.start(`Building ${agent.name}...`);
502
-
503
- try {
504
- await executeAgent(anthropic, agent as AgentTask, projectPath, plan, (progress, action) => {
505
- spinner.message = `${agent.name}: ${action} (${progress}%)`;
506
- });
507
- spinner.stop(`✓ ${agent.name} complete`);
508
- } catch (error) {
509
- spinner.stop(`✗ ${agent.name} failed`);
510
- throw error;
511
- }
512
-
513
- // Merge branch
514
- await mergeWaveBranches([agent as AgentTask], projectPath);
515
- }
516
- }
517
- }
518
-
519
- // ============================================================================
520
- // AGENT EXECUTION WITH SELF-HEALING
521
- // ============================================================================
522
-
523
- async function executeAgent(
524
- anthropic: Anthropic,
525
- agent: AgentTask,
526
- projectPath: string,
527
- plan: BuildPlan,
528
- onProgress?: (progress: number, action: string) => void
529
- ): Promise<void> {
530
- const maxAttempts = 3;
531
-
532
- // Create branch for this agent
533
- await execa('git', ['checkout', '-b', agent.branch], { cwd: projectPath });
534
-
535
- while (agent.healAttempts < maxAttempts) {
536
- try {
537
- await runAgentTask(anthropic, agent, projectPath, plan, onProgress);
538
-
539
- // Commit changes
540
- await execa('git', ['add', '.'], { cwd: projectPath });
541
- await execa('git', ['commit', '-m', `feat: ${agent.name}`], { cwd: projectPath, reject: false });
542
-
543
- // Switch back to main
544
- await execa('git', ['checkout', 'main'], { cwd: projectPath });
545
-
546
- return; // Success!
547
-
548
- } catch (error) {
549
- agent.healAttempts++;
550
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
551
-
552
- if (agent.healAttempts >= maxAttempts) {
553
- // Switch back to main before throwing
554
- await execa('git', ['checkout', 'main'], { cwd: projectPath, reject: false });
555
- throw new Error(`Agent ${agent.name} failed after ${maxAttempts} attempts: ${errorMsg}`);
556
- }
557
-
558
- // Try to heal
559
- agent.status = 'healing';
560
- if (onProgress) onProgress(agent.progress, 'Self-healing...');
561
-
562
- const solution = await healerAgent(anthropic, agent, errorMsg, projectPath, plan);
563
-
564
- if (solution.canFix) {
565
- if (onProgress) onProgress(agent.progress, `Healing: ${solution.description}`);
566
-
567
- if (solution.type === 'wait' && solution.waitTime) {
568
- await sleep(solution.waitTime);
569
- } else if (solution.type === 'modify-code' && solution.newCode) {
570
- // Apply the fix
571
- await writeFilesFromResponse(solution.newCode, projectPath);
572
- }
573
-
574
- agent.status = 'running';
575
- // Loop continues with fix applied
576
- } else {
577
- // Healer can't fix, switch back and throw
578
- await execa('git', ['checkout', 'main'], { cwd: projectPath, reject: false });
579
- throw new Error(`Agent ${agent.name} failed and could not self-heal: ${errorMsg}`);
580
- }
581
- }
582
- }
583
- }
584
-
585
- async function runAgentTask(
586
- anthropic: Anthropic,
587
- agent: AgentTask,
588
- projectPath: string,
589
- plan: BuildPlan,
590
- onProgress?: (progress: number, action: string) => void
591
- ): Promise<void> {
592
- if (onProgress) onProgress(10, 'Analyzing requirements...');
593
-
594
- // Create feature folder
595
- const featurePath = path.join(projectPath, agent.folder);
596
- await fs.ensureDir(featurePath);
597
-
598
- if (onProgress) onProgress(20, 'Generating code...');
599
-
600
- // Conversation loop to handle questions
601
- const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [];
602
- let userAnswers: Record<string, string> = {};
603
- let iteration = 0;
604
- const maxIterations = 5; // Prevent infinite loops
605
-
606
- // Initial prompt
607
- messages.push({
608
- role: 'user',
609
- content: `You are Agent "${agent.id}" building the "${agent.name}" feature.
610
-
611
- Feature: ${agent.name}
612
- Description: ${agent.description}
613
- Your folder: ${agent.folder}
614
- Exports needed: ${agent.exports.join(', ')}
615
-
616
- Project context:
617
- - Project: ${plan.projectName}
618
- - Conventions: ${plan.sharedSetup.conventions}
619
- - Shared types available: ${plan.sharedSetup.types.join(', ')}
620
-
621
- Rules:
622
- 1. ONLY create files in your folder: ${agent.folder}/
623
- 2. Export everything from ${agent.folder}/index.ts
624
- 3. Import shared types from @/types
625
- 4. Import shared components from @/components/ui
626
- 5. Use TypeScript, proper error handling, loading states
627
- 6. Every form needs Zod validation
628
- 7. Every async operation needs try/catch
629
- 8. Every list needs empty state
630
-
631
- IMPORTANT: If you need to make a significant decision (auth provider, database choice,
632
- styling approach, etc.) and it's not specified, ASK THE USER first:
633
-
634
- <<<ASK_USER>>>
635
- question: Your question here?
636
- options: Option 1, Option 2, Option 3
637
- context: Brief context for why you're asking
638
- <<<END_ASK>>>
639
-
640
- Only ask for IMPORTANT decisions, not minor implementation details.
641
-
642
- Generate ALL files needed for this feature.
643
-
644
- Output format:
645
- <<<FILE: ${agent.folder}/path/to/file.ts>>>
646
- content
647
- <<<END_FILE>>>
648
-
649
- Start with the index.ts that exports everything.`
650
- });
651
-
652
- while (iteration < maxIterations) {
653
- iteration++;
654
-
655
- const response = await anthropic.messages.create({
656
- model: 'claude-sonnet-4-5-20250929',
657
- max_tokens: 8192,
658
- messages,
659
- });
660
-
661
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
662
- messages.push({ role: 'assistant', content: text });
663
-
664
- // Check if agent is asking a question
665
- const questionMatch = text.match(/<<<ASK_USER>>>([\s\S]*?)<<<END_ASK>>>/);
666
-
667
- if (questionMatch) {
668
- // Parse the question
669
- const questionBlock = questionMatch[1];
670
- const questionLine = questionBlock.match(/question:\s*(.+)/);
671
- const optionsLine = questionBlock.match(/options:\s*(.+)/);
672
- const contextLine = questionBlock.match(/context:\s*(.+)/);
673
-
674
- if (questionLine && optionsLine) {
675
- const question = questionLine[1].trim();
676
- const options = optionsLine[1].split(',').map(o => o.trim());
677
- const context = contextLine ? contextLine[1].trim() : '';
678
-
679
- // Ask the user
680
- const answer = await askUserQuestion(agent, {
681
- question,
682
- options,
683
- context,
684
- }, onProgress);
685
-
686
- if (answer) {
687
- userAnswers[question] = answer;
688
-
689
- // Send answer back to agent
690
- messages.push({
691
- role: 'user',
692
- content: `User answered: "${answer}"
693
-
694
- Now continue building the feature with this choice. Generate the code files.`
695
- });
696
-
697
- if (onProgress) onProgress(30 + (iteration * 10), 'Continuing with your choice...');
698
- continue; // Loop to get the code
699
- }
700
- }
701
- }
702
-
703
- // No question found, check for files
704
- if (onProgress) onProgress(60, 'Writing files...');
705
-
706
- const files = await writeFilesFromResponse(text, projectPath);
707
- agent.files = files;
708
-
709
- if (onProgress) onProgress(80, 'Validating...');
710
-
711
- // Basic validation - check if files were created
712
- if (files.length === 0) {
713
- throw new Error('No files generated');
714
- }
715
-
716
- // Check for index.ts
717
- const hasIndex = files.some(f => f.endsWith('index.ts'));
718
- if (!hasIndex) {
719
- throw new Error('Missing index.ts export file');
720
- }
721
-
722
- if (onProgress) onProgress(100, 'Complete');
723
- return; // Success!
724
- }
725
-
726
- throw new Error('Agent exceeded maximum iterations');
727
- }
728
-
729
- // ============================================================================
730
- // ASK USER QUESTION
731
- // ============================================================================
732
-
733
- async function askUserQuestion(
734
- agent: AgentTask,
735
- question: UserQuestion,
736
- onProgress?: (progress: number, action: string) => void
737
- ): Promise<string | null> {
738
- // Pause display and update status
739
- displayPaused = true;
740
- const previousStatus = agent.status;
741
- agent.status = 'asking';
742
-
743
- if (onProgress) onProgress(agent.progress, 'Waiting for your input...');
744
-
745
- // Clear some space
746
- console.log('\n');
747
-
748
- // Show the question
749
- console.log(chalk.cyan(` ┌─────────────────────────────────────────────────────────────`));
750
- console.log(chalk.cyan(` │ 🤖 ${agent.name} needs your input`));
751
- console.log(chalk.cyan(` └─────────────────────────────────────────────────────────────`));
752
-
753
- if (question.context) {
754
- console.log(chalk.dim(` ${question.context}\n`));
755
- }
756
-
757
- const answer = await p.select({
758
- message: question.question,
759
- options: question.options.map(o => ({ value: o, label: o })),
760
- });
761
-
762
- if (p.isCancel(answer)) {
763
- // User cancelled - use first option as default
764
- console.log(chalk.yellow(` Using default: ${question.options[0]}`));
765
- agent.status = previousStatus;
766
- displayPaused = false;
767
- return question.options[0];
768
- }
769
-
770
- console.log(chalk.green(` ✓ Got it: ${answer}\n`));
771
-
772
- // Resume
773
- agent.status = previousStatus;
774
- displayPaused = false;
775
-
776
- return answer as string;
777
- }
778
-
779
- // ============================================================================
780
- // HEALER AGENT
781
- // ============================================================================
782
-
783
- async function healerAgent(
784
- anthropic: Anthropic,
785
- agent: AgentTask,
786
- error: string,
787
- projectPath: string,
788
- plan: BuildPlan
789
- ): Promise<HealerSolution> {
790
- const response = await anthropic.messages.create({
791
- model: 'claude-sonnet-4-5-20250929',
792
- max_tokens: 4096,
793
- messages: [{
794
- role: 'user',
795
- content: `You are the Healer Agent. An agent failed and you need to diagnose and fix it.
796
-
797
- Failed Agent: ${agent.name} (${agent.id})
798
- Folder: ${agent.folder}
799
- Error: ${error}
800
- Attempt: ${agent.healAttempts + 1} of 3
801
-
802
- Files created so far: ${agent.files.join(', ') || 'none'}
803
-
804
- Project context:
805
- - Other features: ${plan.waves.flatMap(w => w.agents.map(a => a.id)).join(', ')}
806
- - Shared types: ${plan.sharedSetup.types.join(', ')}
807
-
808
- Diagnose the error and provide a solution.
809
-
810
- Return JSON only:
811
- {
812
- "canFix": true,
813
- "solution": "explanation of what went wrong",
814
- "type": "retry" | "modify-code" | "wait" | "skip-optional" | "generate-missing",
815
- "description": "short description for UI",
816
- "waitTime": 5000, // if type is "wait", milliseconds
817
- "newCode": "<<<FILE: path>>>\\ncontent\\n<<<END_FILE>>>" // if type is "modify-code" or "generate-missing"
818
- }
819
-
820
- Common fixes:
821
- - Missing import → generate the missing file
822
- - Type error → fix the types
823
- - Module not found → check path or generate stub
824
- - Syntax error → fix the syntax
825
- - Timeout → retry with simpler approach`
826
- }],
827
- });
828
-
829
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
830
- const jsonMatch = text.match(/\{[\s\S]*\}/);
831
-
832
- if (!jsonMatch) {
833
- return {
834
- canFix: false,
835
- solution: 'Could not parse healer response',
836
- type: 'retry',
837
- description: 'Unknown error',
838
- };
839
- }
840
-
841
- return JSON.parse(jsonMatch[0]);
842
- }
843
-
844
- // ============================================================================
845
- // INTEGRATION PHASE
846
- // ============================================================================
847
-
848
- async function runIntegrationPhase(
849
- anthropic: Anthropic,
850
- plan: BuildPlan,
851
- projectPath: string
852
- ): Promise<void> {
853
- const spinner = p.spinner();
854
- spinner.start('Running integration...');
855
-
856
- // Get all feature exports
857
- const features = plan.waves.flatMap(w => w.agents);
858
-
859
- const response = await anthropic.messages.create({
860
- model: 'claude-sonnet-4-5-20250929',
861
- max_tokens: 8192,
862
- messages: [{
863
- role: 'user',
864
- content: `You are the Integration Agent. Wire together all the features.
865
-
866
- Features built:
867
- ${features.map(f => `- ${f.name}: ${f.folder} exports [${f.exports.join(', ')}]`).join('\n')}
868
-
869
- Generate:
870
- 1. src/app/layout.tsx - Wire up providers (AuthProvider, etc.)
871
- 2. src/app/page.tsx - Main dashboard/home linking to features
872
- 3. src/app/[feature]/page.tsx - Route for each feature
873
- 4. src/components/Navigation.tsx - Nav linking all features
874
- 5. src/types/index.ts - Re-export all feature types
875
-
876
- Output format:
877
- <<<FILE: path>>>
878
- content
879
- <<<END_FILE>>>
880
-
881
- Make sure all features are accessible and properly connected.`
882
- }],
883
- });
884
-
885
- const text = response.content[0].type === 'text' ? response.content[0].text : '';
886
- await writeFilesFromResponse(text, projectPath);
887
-
888
- // Final commit
889
- await execa('git', ['add', '.'], { cwd: projectPath });
890
- await execa('git', ['commit', '-m', 'Integration: wire up all features'], { cwd: projectPath, reject: false });
891
-
892
- spinner.stop('Integration complete');
893
- }
894
-
895
- // ============================================================================
896
- // GIT HELPERS
897
- // ============================================================================
898
-
899
- async function mergeWaveBranches(agents: AgentTask[], projectPath: string): Promise<void> {
900
- for (const agent of agents) {
901
- if (agent.status === 'done') {
902
- try {
903
- await execa('git', ['merge', agent.branch, '--no-edit'], { cwd: projectPath });
904
- await execa('git', ['branch', '-d', agent.branch], { cwd: projectPath, reject: false });
905
- } catch (error) {
906
- // Merge conflict - shouldn't happen with isolated folders
907
- console.log(chalk.yellow(` Warning: Merge issue with ${agent.name}, continuing...`));
908
- await execa('git', ['merge', '--abort'], { cwd: projectPath, reject: false });
909
- }
910
- }
911
- }
912
- }
913
-
914
- // ============================================================================
915
- // PROGRESS DISPLAY
916
- // ============================================================================
917
-
918
- class ProgressDisplay {
919
- private agents: AgentTask[];
920
- private interval: ReturnType<typeof setInterval> | null = null;
921
- private lastLineCount: number = 0;
922
-
923
- constructor(agents: AgentTask[]) {
924
- this.agents = agents;
925
- }
926
-
927
- start(): void {
928
- this.render();
929
- this.interval = setInterval(() => {
930
- if (!displayPaused) {
931
- this.render();
932
- }
933
- }, 500);
934
- }
935
-
936
- stop(): void {
937
- if (this.interval) {
938
- clearInterval(this.interval);
939
- this.interval = null;
940
- }
941
- if (!displayPaused) {
942
- this.render();
943
- }
944
- console.log('');
945
- }
946
-
947
- update(): void {
948
- // Will be rendered on next interval
949
- }
950
-
951
- pause(): void {
952
- displayPaused = true;
953
- }
954
-
955
- resume(): void {
956
- displayPaused = false;
957
- // Re-render after question is answered
958
- console.log(''); // Add spacing
959
- this.render();
960
- }
961
-
962
- private render(): void {
963
- if (displayPaused) return;
964
-
965
- // Move cursor up and clear previous render
966
- if (this.lastLineCount > 0) {
967
- process.stdout.write(`\x1b[${this.lastLineCount}A\x1b[0J`);
968
- }
969
-
970
- for (const agent of this.agents) {
971
- const icon = this.getStatusIcon(agent.status);
972
- const bar = this.getProgressBar(agent.progress);
973
- const action = agent.currentAction ? chalk.dim(` ${agent.currentAction}`) : '';
974
-
975
- console.log(` ${icon} ${agent.name.padEnd(15)} ${bar} ${agent.progress.toString().padStart(3)}%${action}`);
976
- }
977
- console.log('');
978
-
979
- this.lastLineCount = this.agents.length + 1;
980
- }
981
-
982
- private getStatusIcon(status: AgentTask['status']): string {
983
- switch (status) {
984
- case 'waiting': return chalk.gray('○');
985
- case 'running': return chalk.blue('●');
986
- case 'healing': return chalk.yellow('⚕');
987
- case 'asking': return chalk.magenta('?');
988
- case 'done': return chalk.green('✓');
989
- case 'error': return chalk.red('✗');
990
- default: return '?';
991
- }
992
- }
993
-
994
- private getProgressBar(progress: number): string {
995
- const width = 20;
996
- const filled = Math.round((progress / 100) * width);
997
- const empty = width - filled;
998
- return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
999
- }
1000
- }
1001
-
1002
- // ============================================================================
1003
- // FILE HELPERS
1004
- // ============================================================================
1005
-
1006
- async function writeFilesFromResponse(text: string, projectPath: string): Promise<string[]> {
1007
- const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
1008
- const files: string[] = [];
1009
- let match;
1010
-
1011
- while ((match = fileRegex.exec(text)) !== null) {
1012
- const filePath = path.join(projectPath, match[1].trim());
1013
- const content = match[2].trim();
1014
-
1015
- await fs.ensureDir(path.dirname(filePath));
1016
- await fs.writeFile(filePath, content);
1017
- files.push(match[1].trim());
1018
- }
1019
-
1020
- return files;
1021
- }
1022
-
1023
- function sleep(ms: number): Promise<void> {
1024
- return new Promise(resolve => setTimeout(resolve, ms));
1025
- }