@nclamvn/vibecode-cli 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.vibecode/learning/fixes.json +1 -0
  2. package/.vibecode/learning/preferences.json +1 -0
  3. package/README.md +310 -49
  4. package/SESSION_NOTES.md +154 -0
  5. package/bin/vibecode.js +212 -2
  6. package/package.json +5 -2
  7. package/src/agent/decomposition.js +476 -0
  8. package/src/agent/index.js +391 -0
  9. package/src/agent/memory.js +542 -0
  10. package/src/agent/orchestrator.js +917 -0
  11. package/src/agent/self-healing.js +516 -0
  12. package/src/commands/agent.js +349 -0
  13. package/src/commands/ask.js +230 -0
  14. package/src/commands/assist.js +413 -0
  15. package/src/commands/build.js +345 -4
  16. package/src/commands/debug.js +565 -0
  17. package/src/commands/docs.js +167 -0
  18. package/src/commands/git.js +1024 -0
  19. package/src/commands/go.js +387 -0
  20. package/src/commands/learn.js +294 -0
  21. package/src/commands/migrate.js +341 -0
  22. package/src/commands/plan.js +8 -2
  23. package/src/commands/refactor.js +205 -0
  24. package/src/commands/review.js +126 -1
  25. package/src/commands/security.js +229 -0
  26. package/src/commands/shell.js +486 -0
  27. package/src/commands/test.js +194 -0
  28. package/src/commands/undo.js +281 -0
  29. package/src/commands/watch.js +556 -0
  30. package/src/commands/wizard.js +322 -0
  31. package/src/config/constants.js +5 -1
  32. package/src/config/templates.js +146 -15
  33. package/src/core/backup.js +325 -0
  34. package/src/core/error-analyzer.js +237 -0
  35. package/src/core/fix-generator.js +195 -0
  36. package/src/core/iteration.js +226 -0
  37. package/src/core/learning.js +295 -0
  38. package/src/core/session.js +18 -2
  39. package/src/core/test-runner.js +281 -0
  40. package/src/debug/analyzer.js +329 -0
  41. package/src/debug/evidence.js +228 -0
  42. package/src/debug/fixer.js +348 -0
  43. package/src/debug/image-analyzer.js +304 -0
  44. package/src/debug/index.js +378 -0
  45. package/src/debug/verifier.js +346 -0
  46. package/src/index.js +89 -0
  47. package/src/providers/claude-code.js +12 -7
  48. package/src/ui/__tests__/error-translator.test.js +390 -0
  49. package/src/ui/dashboard.js +364 -0
  50. package/src/ui/error-translator.js +775 -0
  51. package/src/utils/image.js +222 -0
@@ -0,0 +1,917 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE AGENT - Orchestrator
3
+ // Coordinates module builds in dependency order with Claude Code
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import path from 'path';
9
+ import fs from 'fs-extra';
10
+
11
+ import { spawnClaudeCode, isClaudeCodeAvailable } from '../providers/index.js';
12
+ import { runTests } from '../core/test-runner.js';
13
+ import { ensureDir, appendToFile } from '../utils/files.js';
14
+ import { ProgressDashboard } from '../ui/dashboard.js';
15
+ import { translateError, showError, inlineError } from '../ui/error-translator.js';
16
+ import { BackupManager } from '../core/backup.js';
17
+
18
+ /**
19
+ * Progress file for resume functionality
20
+ */
21
+ const PROGRESS_FILE = '.vibecode/agent-progress.json';
22
+ const DECOMPOSITION_FILE = '.vibecode/agent-decomposition.json';
23
+
24
+ /**
25
+ * Save agent progress to file
26
+ */
27
+ export async function saveProgress(projectPath, progress) {
28
+ const progressPath = path.join(projectPath, PROGRESS_FILE);
29
+ await fs.ensureDir(path.dirname(progressPath));
30
+ await fs.writeFile(progressPath, JSON.stringify(progress, null, 2));
31
+ }
32
+
33
+ /**
34
+ * Load agent progress from file
35
+ */
36
+ export async function loadProgress(projectPath) {
37
+ try {
38
+ const progressPath = path.join(projectPath, PROGRESS_FILE);
39
+ const content = await fs.readFile(progressPath, 'utf-8');
40
+ return JSON.parse(content);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Clear agent progress file
48
+ */
49
+ export async function clearProgress(projectPath) {
50
+ try {
51
+ await fs.unlink(path.join(projectPath, PROGRESS_FILE));
52
+ } catch {
53
+ // Ignore if file doesn't exist
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save decomposition for resume
59
+ */
60
+ export async function saveDecomposition(projectPath, decomposition) {
61
+ const decompositionPath = path.join(projectPath, DECOMPOSITION_FILE);
62
+ await fs.ensureDir(path.dirname(decompositionPath));
63
+ await fs.writeFile(decompositionPath, JSON.stringify(decomposition, null, 2));
64
+ }
65
+
66
+ /**
67
+ * Load decomposition from file
68
+ */
69
+ export async function loadDecomposition(projectPath) {
70
+ try {
71
+ const decompositionPath = path.join(projectPath, DECOMPOSITION_FILE);
72
+ const content = await fs.readFile(decompositionPath, 'utf-8');
73
+ return JSON.parse(content);
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Orchestrator states
81
+ */
82
+ const ORCHESTRATOR_STATES = {
83
+ IDLE: 'idle',
84
+ INITIALIZING: 'initializing',
85
+ DECOMPOSING: 'decomposing',
86
+ BUILDING: 'building',
87
+ HEALING: 'healing',
88
+ TESTING: 'testing',
89
+ COMPLETED: 'completed',
90
+ FAILED: 'failed',
91
+ PAUSED: 'paused'
92
+ };
93
+
94
+ /**
95
+ * Event types for callbacks
96
+ */
97
+ const EVENTS = {
98
+ STATE_CHANGE: 'state_change',
99
+ MODULE_START: 'module_start',
100
+ MODULE_COMPLETE: 'module_complete',
101
+ MODULE_FAIL: 'module_fail',
102
+ BUILD_OUTPUT: 'build_output',
103
+ HEALING_START: 'healing_start',
104
+ HEALING_COMPLETE: 'healing_complete',
105
+ PROGRESS: 'progress'
106
+ };
107
+
108
+ /**
109
+ * Orchestrator Class
110
+ * Main coordinator for multi-module builds
111
+ */
112
+ export class Orchestrator {
113
+ constructor(options = {}) {
114
+ this.decompositionEngine = options.decompositionEngine;
115
+ this.memoryEngine = options.memoryEngine;
116
+ this.selfHealingEngine = options.selfHealingEngine;
117
+
118
+ this.state = ORCHESTRATOR_STATES.IDLE;
119
+ this.projectPath = options.projectPath || process.cwd();
120
+ this.logPath = null;
121
+ this.eventHandlers = {};
122
+
123
+ // Build configuration
124
+ this.config = {
125
+ maxModuleRetries: options.maxModuleRetries || 3,
126
+ maxTotalRetries: options.maxTotalRetries || 10,
127
+ testAfterEachModule: options.testAfterEachModule ?? true,
128
+ continueOnFailure: options.continueOnFailure ?? false,
129
+ parallelBuilds: options.parallelBuilds ?? false, // Future feature
130
+ timeout: options.timeout || 30 * 60 * 1000, // 30 minutes per module
131
+ useDashboard: options.useDashboard ?? true, // Use visual dashboard
132
+ verbose: options.verbose ?? false
133
+ };
134
+
135
+ // Dashboard instance
136
+ this.dashboard = null;
137
+
138
+ // Build state
139
+ this.buildState = {
140
+ startTime: null,
141
+ currentModule: null,
142
+ completedModules: [],
143
+ failedModules: [],
144
+ skippedModules: [],
145
+ totalRetries: 0,
146
+ errors: []
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Register event handler
152
+ */
153
+ on(event, handler) {
154
+ if (!this.eventHandlers[event]) {
155
+ this.eventHandlers[event] = [];
156
+ }
157
+ this.eventHandlers[event].push(handler);
158
+ return this;
159
+ }
160
+
161
+ /**
162
+ * Emit event
163
+ */
164
+ emit(event, data) {
165
+ if (this.eventHandlers[event]) {
166
+ for (const handler of this.eventHandlers[event]) {
167
+ try {
168
+ handler(data);
169
+ } catch (e) {
170
+ console.error(`Event handler error: ${e.message}`);
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Set state and emit event
178
+ */
179
+ setState(newState) {
180
+ const oldState = this.state;
181
+ this.state = newState;
182
+ this.emit(EVENTS.STATE_CHANGE, { from: oldState, to: newState });
183
+ }
184
+
185
+ /**
186
+ * Initialize orchestrator
187
+ */
188
+ async initialize(projectPath) {
189
+ this.setState(ORCHESTRATOR_STATES.INITIALIZING);
190
+ this.projectPath = projectPath;
191
+
192
+ // Setup log directory
193
+ const agentDir = path.join(projectPath, '.vibecode', 'agent');
194
+ await ensureDir(agentDir);
195
+ this.logPath = path.join(agentDir, 'orchestrator.log');
196
+
197
+ // Check Claude Code availability
198
+ const claudeAvailable = await isClaudeCodeAvailable();
199
+ if (!claudeAvailable) {
200
+ throw new Error('Claude Code CLI not available. Install with: npm install -g @anthropic-ai/claude-code');
201
+ }
202
+
203
+ // Initialize memory if provided
204
+ if (this.memoryEngine) {
205
+ await this.memoryEngine.initialize();
206
+ }
207
+
208
+ // Link self-healing to memory
209
+ if (this.selfHealingEngine && this.memoryEngine) {
210
+ this.selfHealingEngine.setMemoryEngine(this.memoryEngine);
211
+ }
212
+
213
+ await this.log('Orchestrator initialized');
214
+ return this;
215
+ }
216
+
217
+ /**
218
+ * Log message to file
219
+ */
220
+ async log(message, level = 'info') {
221
+ const timestamp = new Date().toISOString();
222
+ const line = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
223
+
224
+ if (this.logPath) {
225
+ await appendToFile(this.logPath, line);
226
+ }
227
+
228
+ if (level === 'error') {
229
+ console.error(chalk.red(message));
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Main build entry point
235
+ */
236
+ async build(description, options = {}) {
237
+ this.buildState.startTime = Date.now();
238
+
239
+ // Create backup before agent build
240
+ const backup = new BackupManager(this.projectPath);
241
+ await backup.createBackup('agent-build');
242
+
243
+ try {
244
+ // Step 1: Decompose project
245
+ this.setState(ORCHESTRATOR_STATES.DECOMPOSING);
246
+ await this.log(`Decomposing: "${description}"`);
247
+
248
+ const decomposition = await this.decompositionEngine.decompose(description, options);
249
+
250
+ // Save decomposition for resume functionality
251
+ await saveDecomposition(this.projectPath, {
252
+ ...decomposition,
253
+ description,
254
+ startedAt: new Date().toISOString()
255
+ });
256
+
257
+ // Store in memory
258
+ if (this.memoryEngine) {
259
+ await this.memoryEngine.setProjectContext({
260
+ description,
261
+ type: decomposition.projectType,
262
+ complexity: decomposition.estimatedComplexity,
263
+ totalModules: decomposition.totalModules
264
+ });
265
+ }
266
+
267
+ await this.log(`Decomposed into ${decomposition.totalModules} modules: ${decomposition.buildOrder.join(', ')}`);
268
+
269
+ // Step 2: Build modules in order
270
+ this.setState(ORCHESTRATOR_STATES.BUILDING);
271
+
272
+ const results = await this.buildModules(decomposition, options);
273
+
274
+ // Step 3: Final summary
275
+ if (results.success) {
276
+ this.setState(ORCHESTRATOR_STATES.COMPLETED);
277
+ // Clear progress on success
278
+ await clearProgress(this.projectPath);
279
+ } else {
280
+ this.setState(ORCHESTRATOR_STATES.FAILED);
281
+ }
282
+
283
+ return this.generateBuildReport(decomposition, results);
284
+
285
+ } catch (error) {
286
+ this.setState(ORCHESTRATOR_STATES.FAILED);
287
+ await this.log(`Build failed: ${error.message}`, 'error');
288
+ throw error;
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Resume build from previous progress
294
+ */
295
+ async resumeBuild(options = {}) {
296
+ this.buildState.startTime = Date.now();
297
+
298
+ // Load progress
299
+ const progress = await loadProgress(this.projectPath);
300
+ if (!progress) {
301
+ throw new Error('No previous progress found. Start a new build with: vibecode agent "description"');
302
+ }
303
+
304
+ // Load decomposition
305
+ const savedDecomposition = await loadDecomposition(this.projectPath);
306
+ if (!savedDecomposition) {
307
+ throw new Error('No decomposition found. Start a new build with: vibecode agent "description"');
308
+ }
309
+
310
+ try {
311
+ // Restore decomposition engine state
312
+ await this.decompositionEngine.decompose(savedDecomposition.description, options);
313
+
314
+ // Update module statuses from progress
315
+ for (const mod of progress.modules) {
316
+ if (mod.status === 'done') {
317
+ this.decompositionEngine.updateModuleStatus(mod.id, 'completed', {
318
+ files: mod.files || []
319
+ });
320
+ this.buildState.completedModules.push(mod.id);
321
+ }
322
+ }
323
+
324
+ // Store in memory
325
+ if (this.memoryEngine) {
326
+ await this.memoryEngine.setProjectContext({
327
+ description: progress.description,
328
+ type: savedDecomposition.projectType,
329
+ complexity: savedDecomposition.estimatedComplexity,
330
+ totalModules: savedDecomposition.totalModules
331
+ });
332
+ }
333
+
334
+ await this.log(`Resuming from module ${progress.currentModule + 1}`);
335
+
336
+ // Determine start index
337
+ const startIndex = options.fromModule !== undefined
338
+ ? options.fromModule
339
+ : progress.currentModule;
340
+
341
+ // Build remaining modules
342
+ this.setState(ORCHESTRATOR_STATES.BUILDING);
343
+ const results = await this.buildModules(savedDecomposition, {
344
+ ...options,
345
+ startIndex
346
+ });
347
+
348
+ // Final summary
349
+ if (results.success) {
350
+ this.setState(ORCHESTRATOR_STATES.COMPLETED);
351
+ await clearProgress(this.projectPath);
352
+ } else {
353
+ this.setState(ORCHESTRATOR_STATES.FAILED);
354
+ }
355
+
356
+ return this.generateBuildReport(savedDecomposition, results);
357
+
358
+ } catch (error) {
359
+ this.setState(ORCHESTRATOR_STATES.FAILED);
360
+ await this.log(`Resume failed: ${error.message}`, 'error');
361
+ throw error;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Build all modules in dependency order
367
+ */
368
+ async buildModules(decomposition, options = {}) {
369
+ const results = {
370
+ success: true,
371
+ modules: {},
372
+ totalTime: 0
373
+ };
374
+
375
+ // Determine start index for resume
376
+ const startIndex = options.startIndex || 0;
377
+ const buildOrder = decomposition.buildOrder;
378
+
379
+ // Create and start dashboard if enabled
380
+ if (this.config.useDashboard) {
381
+ this.dashboard = new ProgressDashboard({
382
+ title: startIndex > 0 ? 'VIBECODE AGENT (RESUME)' : 'VIBECODE AGENT',
383
+ projectName: path.basename(this.projectPath),
384
+ mode: `Agent (${decomposition.totalModules} modules)`
385
+ });
386
+
387
+ // Set modules for dashboard with correct status for resumed builds
388
+ this.dashboard.setModules(buildOrder.map((id, idx) => {
389
+ const mod = this.decompositionEngine.getModule(id);
390
+ return {
391
+ name: mod?.name || id,
392
+ status: idx < startIndex ? 'completed' : 'pending'
393
+ };
394
+ }));
395
+
396
+ this.dashboard.start();
397
+
398
+ // Show resume message
399
+ if (startIndex > 0) {
400
+ this.dashboard.addLog(`Resuming from module ${startIndex + 1}: ${buildOrder[startIndex]}`);
401
+ }
402
+ }
403
+
404
+ try {
405
+ for (let i = startIndex; i < buildOrder.length; i++) {
406
+ const moduleId = buildOrder[i];
407
+
408
+ // Check if we should stop
409
+ if (this.state === ORCHESTRATOR_STATES.PAUSED) {
410
+ await this.log('Build paused');
411
+ break;
412
+ }
413
+
414
+ // Save progress before building this module
415
+ await saveProgress(this.projectPath, {
416
+ projectName: path.basename(this.projectPath),
417
+ description: decomposition.description,
418
+ totalModules: decomposition.totalModules,
419
+ completedModules: this.buildState.completedModules,
420
+ currentModule: i,
421
+ modules: buildOrder.map((id, idx) => {
422
+ const mod = this.decompositionEngine.getModule(id);
423
+ return {
424
+ id,
425
+ name: mod?.name || id,
426
+ status: idx < i ? 'done' : idx === i ? 'building' : 'pending',
427
+ files: mod?.files || []
428
+ };
429
+ }),
430
+ startedAt: decomposition.startedAt || new Date().toISOString(),
431
+ lastUpdated: new Date().toISOString(),
432
+ error: null
433
+ });
434
+
435
+ // Check if module can be built (dependencies satisfied)
436
+ if (!this.decompositionEngine.canBuildModule(moduleId)) {
437
+ const depStatus = decomposition.dependencyGraph[moduleId];
438
+ const failedDeps = depStatus?.dependsOn.filter(d =>
439
+ this.buildState.failedModules.includes(d)
440
+ );
441
+
442
+ if (failedDeps?.length > 0) {
443
+ await this.log(`Skipping ${moduleId}: dependencies failed (${failedDeps.join(', ')})`, 'warn');
444
+ this.buildState.skippedModules.push(moduleId);
445
+ results.modules[moduleId] = { status: 'skipped', reason: 'dependencies_failed' };
446
+
447
+ // Update dashboard
448
+ if (this.dashboard) {
449
+ const mod = this.decompositionEngine.getModule(moduleId);
450
+ this.dashboard.failModule(mod?.name || moduleId);
451
+ }
452
+ continue;
453
+ }
454
+ }
455
+
456
+ // Update dashboard - start module
457
+ if (this.dashboard) {
458
+ const mod = this.decompositionEngine.getModule(moduleId);
459
+ this.dashboard.startModule(mod?.name || moduleId);
460
+ }
461
+
462
+ // Build the module
463
+ const moduleResult = await this.buildModule(moduleId, decomposition);
464
+ results.modules[moduleId] = moduleResult;
465
+
466
+ // Update dashboard - complete/fail module
467
+ if (this.dashboard) {
468
+ const mod = this.decompositionEngine.getModule(moduleId);
469
+ if (moduleResult.success) {
470
+ this.dashboard.completeModule(mod?.name || moduleId, true);
471
+ } else {
472
+ this.dashboard.failModule(mod?.name || moduleId);
473
+ }
474
+ }
475
+
476
+ // Save progress after module completion
477
+ await saveProgress(this.projectPath, {
478
+ projectName: path.basename(this.projectPath),
479
+ description: decomposition.description,
480
+ totalModules: decomposition.totalModules,
481
+ completedModules: this.buildState.completedModules,
482
+ currentModule: moduleResult.success ? i + 1 : i,
483
+ failedModule: moduleResult.success ? null : i,
484
+ modules: buildOrder.map((id, idx) => {
485
+ const mod = this.decompositionEngine.getModule(id);
486
+ const status = idx < i ? 'done'
487
+ : idx === i ? (moduleResult.success ? 'done' : 'failed')
488
+ : 'pending';
489
+ return {
490
+ id,
491
+ name: mod?.name || id,
492
+ status,
493
+ files: mod?.files || []
494
+ };
495
+ }),
496
+ startedAt: decomposition.startedAt || new Date().toISOString(),
497
+ lastUpdated: new Date().toISOString(),
498
+ error: moduleResult.success ? null : moduleResult.error
499
+ });
500
+
501
+ if (!moduleResult.success) {
502
+ results.success = false;
503
+
504
+ if (!this.config.continueOnFailure) {
505
+ await this.log(`Stopping build due to module failure: ${moduleId}`, 'error');
506
+ console.log(chalk.yellow(`\n💡 Resume later with: vibecode agent --resume\n`));
507
+ break;
508
+ }
509
+ }
510
+ }
511
+ } finally {
512
+ // Stop dashboard
513
+ if (this.dashboard) {
514
+ this.dashboard.stop();
515
+ }
516
+ }
517
+
518
+ results.totalTime = Date.now() - this.buildState.startTime;
519
+ return results;
520
+ }
521
+
522
+ /**
523
+ * Build a single module
524
+ */
525
+ async buildModule(moduleId, decomposition) {
526
+ const module = this.decompositionEngine.getModule(moduleId);
527
+ if (!module) {
528
+ return { success: false, error: 'Module not found' };
529
+ }
530
+
531
+ this.buildState.currentModule = moduleId;
532
+ this.emit(EVENTS.MODULE_START, { moduleId, module });
533
+
534
+ // Only use spinner if dashboard is not enabled
535
+ const spinner = !this.config.useDashboard ? ora({
536
+ text: chalk.cyan(`Building module: ${module.name}`),
537
+ prefixText: this.getProgressPrefix()
538
+ }).start() : null;
539
+
540
+ // Record in memory
541
+ if (this.memoryEngine) {
542
+ await this.memoryEngine.startModule(moduleId, {
543
+ name: module.name,
544
+ description: module.description
545
+ });
546
+ }
547
+
548
+ let attempts = 0;
549
+ let lastError = null;
550
+ let healingPrompt = null; // Store fix prompt from self-healing
551
+
552
+ while (attempts < this.config.maxModuleRetries) {
553
+ attempts++;
554
+ module.buildAttempts = attempts;
555
+
556
+ try {
557
+ await this.log(`Building ${moduleId} (attempt ${attempts}/${this.config.maxModuleRetries})`);
558
+
559
+ // Use healing prompt if available (from previous retry), otherwise generate fresh
560
+ let prompt;
561
+ if (healingPrompt) {
562
+ prompt = healingPrompt;
563
+ healingPrompt = null; // Clear after use
564
+ } else {
565
+ prompt = this.generateBuildPrompt(module, decomposition);
566
+ }
567
+
568
+ // Run Claude Code
569
+ const buildResult = await this.runClaudeBuild(prompt, moduleId);
570
+
571
+ if (buildResult.success) {
572
+ // Run tests if configured
573
+ if (this.config.testAfterEachModule) {
574
+ if (spinner) spinner.text = chalk.cyan(`Testing module: ${module.name}`);
575
+ if (this.dashboard) this.dashboard.addLog(`Testing: ${module.name}`);
576
+ this.setState(ORCHESTRATOR_STATES.TESTING);
577
+
578
+ const testResult = await runTests(this.projectPath);
579
+
580
+ if (!testResult.passed) {
581
+ throw new Error(`Tests failed: ${testResult.summary.failed} failures`);
582
+ }
583
+ }
584
+
585
+ // Success!
586
+ if (spinner) spinner.succeed(chalk.green(`Module complete: ${module.name}`));
587
+
588
+ this.decompositionEngine.updateModuleStatus(moduleId, 'completed', {
589
+ files: buildResult.files || []
590
+ });
591
+ this.buildState.completedModules.push(moduleId);
592
+
593
+ // Record in memory
594
+ if (this.memoryEngine) {
595
+ await this.memoryEngine.completeModule(moduleId, {
596
+ files: buildResult.files,
597
+ attempts
598
+ });
599
+ }
600
+
601
+ this.emit(EVENTS.MODULE_COMPLETE, { moduleId, result: buildResult });
602
+
603
+ return {
604
+ success: true,
605
+ attempts,
606
+ files: buildResult.files
607
+ };
608
+ }
609
+
610
+ throw new Error(buildResult.error || 'Build failed');
611
+
612
+ } catch (error) {
613
+ lastError = error;
614
+ const translated = translateError(error);
615
+ await this.log(`Module ${moduleId} attempt ${attempts} failed: ${translated.title} - ${error.message}`, 'error');
616
+
617
+ // Show translated error if not using dashboard
618
+ if (!this.config.useDashboard) {
619
+ console.log(inlineError(error));
620
+ } else if (this.dashboard) {
621
+ this.dashboard.addLog(`Error: ${translated.title}`);
622
+ }
623
+
624
+ // Try self-healing
625
+ if (this.selfHealingEngine && attempts < this.config.maxModuleRetries) {
626
+ if (spinner) spinner.text = chalk.yellow(`Healing module: ${module.name}`);
627
+ if (this.dashboard) this.dashboard.addLog(`Healing: ${module.name}`);
628
+ this.setState(ORCHESTRATOR_STATES.HEALING);
629
+ this.emit(EVENTS.HEALING_START, { moduleId, error });
630
+
631
+ const healing = await this.selfHealingEngine.heal(error.message, moduleId, {
632
+ attempt: attempts,
633
+ completedModules: this.buildState.completedModules
634
+ });
635
+
636
+ if (healing.shouldRetry) {
637
+ const errorCategory = healing.analysis?.category || 'UNKNOWN';
638
+ await this.log(`Self-healing: ${errorCategory} error, retrying...`);
639
+ this.buildState.totalRetries++;
640
+
641
+ // Store healing prompt for next iteration
642
+ healingPrompt = healing.prompt;
643
+
644
+ // Record healing attempt
645
+ if (this.memoryEngine) {
646
+ await this.memoryEngine.recordError({
647
+ message: error.message,
648
+ type: errorCategory,
649
+ moduleId,
650
+ healingAttempt: attempts
651
+ });
652
+ }
653
+
654
+ continue;
655
+ }
656
+ }
657
+
658
+ break; // Exit retry loop if can't heal
659
+ }
660
+ }
661
+
662
+ // Module failed
663
+ if (spinner) spinner.fail(chalk.red(`Module failed: ${module.name}`));
664
+
665
+ this.decompositionEngine.updateModuleStatus(moduleId, 'failed', {
666
+ error: lastError?.message
667
+ });
668
+ this.buildState.failedModules.push(moduleId);
669
+ this.buildState.errors.push({ moduleId, error: lastError?.message });
670
+
671
+ if (this.memoryEngine) {
672
+ await this.memoryEngine.failModule(moduleId, lastError);
673
+ }
674
+
675
+ this.emit(EVENTS.MODULE_FAIL, { moduleId, error: lastError, attempts });
676
+
677
+ return {
678
+ success: false,
679
+ attempts,
680
+ error: lastError?.message
681
+ };
682
+ }
683
+
684
+ /**
685
+ * Generate build prompt for a module
686
+ */
687
+ generateBuildPrompt(module, decomposition) {
688
+ let prompt = `# Build Module: ${module.name}\n\n`;
689
+
690
+ // Module description
691
+ prompt += `## Description\n${module.description}\n\n`;
692
+
693
+ // Dependencies context
694
+ if (module.dependencies.length > 0) {
695
+ prompt += `## Dependencies (already built)\n`;
696
+ for (const depId of module.dependencies) {
697
+ const dep = this.decompositionEngine.getModule(depId);
698
+ if (dep) {
699
+ prompt += `- **${dep.name}**: ${dep.description}\n`;
700
+ if (dep.files?.length > 0) {
701
+ prompt += ` Files: ${dep.files.join(', ')}\n`;
702
+ }
703
+ }
704
+ }
705
+ prompt += '\n';
706
+ }
707
+
708
+ // Memory context
709
+ if (this.memoryEngine) {
710
+ const contextSummary = this.memoryEngine.generateContextSummary();
711
+ prompt += contextSummary;
712
+ }
713
+
714
+ // Project context
715
+ const projectContext = this.memoryEngine?.getProjectContext();
716
+ if (projectContext?.description) {
717
+ prompt += `## Project Goal\n${projectContext.description}\n\n`;
718
+ }
719
+
720
+ // Build instructions
721
+ prompt += `## Instructions\n`;
722
+ prompt += `1. Create all necessary files for the ${module.name} module\n`;
723
+ prompt += `2. Follow patterns established in completed modules\n`;
724
+ prompt += `3. Ensure compatibility with dependencies\n`;
725
+ prompt += `4. Add appropriate error handling\n`;
726
+ prompt += `5. Export any functions/components needed by dependent modules\n\n`;
727
+
728
+ // Specific instructions based on module type
729
+ const moduleInstructions = this.getModuleSpecificInstructions(module.id);
730
+ if (moduleInstructions) {
731
+ prompt += `## ${module.name} Specific Requirements\n${moduleInstructions}\n`;
732
+ }
733
+
734
+ return prompt;
735
+ }
736
+
737
+ /**
738
+ * Get module-specific build instructions
739
+ */
740
+ getModuleSpecificInstructions(moduleId) {
741
+ const instructions = {
742
+ core: `- Setup project structure (src/, public/, etc.)
743
+ - Create configuration files (package.json, tsconfig.json if needed)
744
+ - Setup base utilities and helpers`,
745
+
746
+ auth: `- Implement login/signup forms or endpoints
747
+ - Setup session/token management
748
+ - Add password hashing if applicable
749
+ - Create auth middleware/guards`,
750
+
751
+ database: `- Define data models/schemas
752
+ - Setup database connection
753
+ - Create migration scripts if needed
754
+ - Add seed data for development`,
755
+
756
+ api: `- Create REST/GraphQL endpoints
757
+ - Add request validation
758
+ - Implement error handling middleware
759
+ - Add API documentation`,
760
+
761
+ ui: `- Create reusable components
762
+ - Setup styling (CSS/Tailwind/etc.)
763
+ - Ensure responsive design
764
+ - Add accessibility attributes`,
765
+
766
+ pages: `- Create page components/routes
767
+ - Connect to API endpoints
768
+ - Add loading and error states
769
+ - Implement navigation`,
770
+
771
+ tests: `- Write unit tests for core functions
772
+ - Add integration tests for API
773
+ - Create component tests for UI
774
+ - Setup test utilities and mocks`
775
+ };
776
+
777
+ return instructions[moduleId] || null;
778
+ }
779
+
780
+ /**
781
+ * Run Claude Code build
782
+ */
783
+ async runClaudeBuild(prompt, moduleId) {
784
+ const evidencePath = path.join(this.projectPath, '.vibecode', 'agent', 'evidence');
785
+ await ensureDir(evidencePath);
786
+ const logPath = path.join(evidencePath, `${moduleId}.log`);
787
+
788
+ try {
789
+ const result = await spawnClaudeCode(prompt, {
790
+ cwd: this.projectPath,
791
+ logPath,
792
+ timeout: this.config.timeout
793
+ });
794
+
795
+ // Try to detect created files
796
+ const files = await this.detectCreatedFiles();
797
+
798
+ return {
799
+ success: result.code === 0,
800
+ code: result.code,
801
+ output: result.output,
802
+ files,
803
+ error: result.code !== 0 ? result.error : null
804
+ };
805
+ } catch (error) {
806
+ return {
807
+ success: false,
808
+ error: error.message
809
+ };
810
+ }
811
+ }
812
+
813
+ /**
814
+ * Detect files created during build
815
+ */
816
+ async detectCreatedFiles() {
817
+ // This is a simplified version - in real implementation,
818
+ // we would track git status or file system changes
819
+ try {
820
+ const srcPath = path.join(this.projectPath, 'src');
821
+ if (await fs.pathExists(srcPath)) {
822
+ const files = await fs.readdir(srcPath, { recursive: true });
823
+ return files.filter(f => !f.startsWith('.')).slice(0, 20);
824
+ }
825
+ } catch (e) {
826
+ // Ignore
827
+ }
828
+ return [];
829
+ }
830
+
831
+ /**
832
+ * Get progress prefix for spinner
833
+ */
834
+ getProgressPrefix() {
835
+ const completed = this.buildState.completedModules.length;
836
+ const total = this.decompositionEngine?.modules?.length || 0;
837
+ const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
838
+ return chalk.gray(`[${completed}/${total}] ${percent}%`);
839
+ }
840
+
841
+ /**
842
+ * Generate final build report
843
+ */
844
+ generateBuildReport(decomposition, results) {
845
+ const duration = ((Date.now() - this.buildState.startTime) / 1000 / 60).toFixed(1);
846
+
847
+ return {
848
+ success: results.success,
849
+ projectType: decomposition.projectType,
850
+ complexity: decomposition.estimatedComplexity,
851
+ duration: `${duration} minutes`,
852
+
853
+ modules: {
854
+ total: decomposition.totalModules,
855
+ completed: this.buildState.completedModules.length,
856
+ failed: this.buildState.failedModules.length,
857
+ skipped: this.buildState.skippedModules.length
858
+ },
859
+
860
+ buildOrder: decomposition.buildOrder,
861
+ completedModules: this.buildState.completedModules,
862
+ failedModules: this.buildState.failedModules,
863
+ skippedModules: this.buildState.skippedModules,
864
+
865
+ retries: {
866
+ total: this.buildState.totalRetries,
867
+ max: this.config.maxTotalRetries
868
+ },
869
+
870
+ errors: this.buildState.errors,
871
+ moduleResults: results.modules,
872
+
873
+ healingStats: this.selfHealingEngine?.getStats() || null,
874
+ memoryStats: this.memoryEngine?.getStats() || null
875
+ };
876
+ }
877
+
878
+ /**
879
+ * Pause the build
880
+ */
881
+ pause() {
882
+ if (this.state === ORCHESTRATOR_STATES.BUILDING) {
883
+ this.setState(ORCHESTRATOR_STATES.PAUSED);
884
+ }
885
+ }
886
+
887
+ /**
888
+ * Resume paused build
889
+ */
890
+ resume() {
891
+ if (this.state === ORCHESTRATOR_STATES.PAUSED) {
892
+ this.setState(ORCHESTRATOR_STATES.BUILDING);
893
+ }
894
+ }
895
+
896
+ /**
897
+ * Get current build state
898
+ */
899
+ getState() {
900
+ return {
901
+ state: this.state,
902
+ currentModule: this.buildState.currentModule,
903
+ completedModules: this.buildState.completedModules,
904
+ failedModules: this.buildState.failedModules,
905
+ progress: this.getProgressPrefix()
906
+ };
907
+ }
908
+ }
909
+
910
+ /**
911
+ * Create orchestrator instance
912
+ */
913
+ export function createOrchestrator(options = {}) {
914
+ return new Orchestrator(options);
915
+ }
916
+
917
+ export { ORCHESTRATOR_STATES, EVENTS };