@nclamvn/vibecode-cli 1.3.0 → 1.6.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.
@@ -0,0 +1,713 @@
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
+ * Orchestrator states
20
+ */
21
+ const ORCHESTRATOR_STATES = {
22
+ IDLE: 'idle',
23
+ INITIALIZING: 'initializing',
24
+ DECOMPOSING: 'decomposing',
25
+ BUILDING: 'building',
26
+ HEALING: 'healing',
27
+ TESTING: 'testing',
28
+ COMPLETED: 'completed',
29
+ FAILED: 'failed',
30
+ PAUSED: 'paused'
31
+ };
32
+
33
+ /**
34
+ * Event types for callbacks
35
+ */
36
+ const EVENTS = {
37
+ STATE_CHANGE: 'state_change',
38
+ MODULE_START: 'module_start',
39
+ MODULE_COMPLETE: 'module_complete',
40
+ MODULE_FAIL: 'module_fail',
41
+ BUILD_OUTPUT: 'build_output',
42
+ HEALING_START: 'healing_start',
43
+ HEALING_COMPLETE: 'healing_complete',
44
+ PROGRESS: 'progress'
45
+ };
46
+
47
+ /**
48
+ * Orchestrator Class
49
+ * Main coordinator for multi-module builds
50
+ */
51
+ export class Orchestrator {
52
+ constructor(options = {}) {
53
+ this.decompositionEngine = options.decompositionEngine;
54
+ this.memoryEngine = options.memoryEngine;
55
+ this.selfHealingEngine = options.selfHealingEngine;
56
+
57
+ this.state = ORCHESTRATOR_STATES.IDLE;
58
+ this.projectPath = options.projectPath || process.cwd();
59
+ this.logPath = null;
60
+ this.eventHandlers = {};
61
+
62
+ // Build configuration
63
+ this.config = {
64
+ maxModuleRetries: options.maxModuleRetries || 3,
65
+ maxTotalRetries: options.maxTotalRetries || 10,
66
+ testAfterEachModule: options.testAfterEachModule ?? true,
67
+ continueOnFailure: options.continueOnFailure ?? false,
68
+ parallelBuilds: options.parallelBuilds ?? false, // Future feature
69
+ timeout: options.timeout || 30 * 60 * 1000, // 30 minutes per module
70
+ useDashboard: options.useDashboard ?? true, // Use visual dashboard
71
+ verbose: options.verbose ?? false
72
+ };
73
+
74
+ // Dashboard instance
75
+ this.dashboard = null;
76
+
77
+ // Build state
78
+ this.buildState = {
79
+ startTime: null,
80
+ currentModule: null,
81
+ completedModules: [],
82
+ failedModules: [],
83
+ skippedModules: [],
84
+ totalRetries: 0,
85
+ errors: []
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Register event handler
91
+ */
92
+ on(event, handler) {
93
+ if (!this.eventHandlers[event]) {
94
+ this.eventHandlers[event] = [];
95
+ }
96
+ this.eventHandlers[event].push(handler);
97
+ return this;
98
+ }
99
+
100
+ /**
101
+ * Emit event
102
+ */
103
+ emit(event, data) {
104
+ if (this.eventHandlers[event]) {
105
+ for (const handler of this.eventHandlers[event]) {
106
+ try {
107
+ handler(data);
108
+ } catch (e) {
109
+ console.error(`Event handler error: ${e.message}`);
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Set state and emit event
117
+ */
118
+ setState(newState) {
119
+ const oldState = this.state;
120
+ this.state = newState;
121
+ this.emit(EVENTS.STATE_CHANGE, { from: oldState, to: newState });
122
+ }
123
+
124
+ /**
125
+ * Initialize orchestrator
126
+ */
127
+ async initialize(projectPath) {
128
+ this.setState(ORCHESTRATOR_STATES.INITIALIZING);
129
+ this.projectPath = projectPath;
130
+
131
+ // Setup log directory
132
+ const agentDir = path.join(projectPath, '.vibecode', 'agent');
133
+ await ensureDir(agentDir);
134
+ this.logPath = path.join(agentDir, 'orchestrator.log');
135
+
136
+ // Check Claude Code availability
137
+ const claudeAvailable = await isClaudeCodeAvailable();
138
+ if (!claudeAvailable) {
139
+ throw new Error('Claude Code CLI not available. Install with: npm install -g @anthropic-ai/claude-code');
140
+ }
141
+
142
+ // Initialize memory if provided
143
+ if (this.memoryEngine) {
144
+ await this.memoryEngine.initialize();
145
+ }
146
+
147
+ // Link self-healing to memory
148
+ if (this.selfHealingEngine && this.memoryEngine) {
149
+ this.selfHealingEngine.setMemoryEngine(this.memoryEngine);
150
+ }
151
+
152
+ await this.log('Orchestrator initialized');
153
+ return this;
154
+ }
155
+
156
+ /**
157
+ * Log message to file
158
+ */
159
+ async log(message, level = 'info') {
160
+ const timestamp = new Date().toISOString();
161
+ const line = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
162
+
163
+ if (this.logPath) {
164
+ await appendToFile(this.logPath, line);
165
+ }
166
+
167
+ if (level === 'error') {
168
+ console.error(chalk.red(message));
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Main build entry point
174
+ */
175
+ async build(description, options = {}) {
176
+ this.buildState.startTime = Date.now();
177
+
178
+ // Create backup before agent build
179
+ const backup = new BackupManager(this.projectPath);
180
+ await backup.createBackup('agent-build');
181
+
182
+ try {
183
+ // Step 1: Decompose project
184
+ this.setState(ORCHESTRATOR_STATES.DECOMPOSING);
185
+ await this.log(`Decomposing: "${description}"`);
186
+
187
+ const decomposition = await this.decompositionEngine.decompose(description, options);
188
+
189
+ // Store in memory
190
+ if (this.memoryEngine) {
191
+ await this.memoryEngine.setProjectContext({
192
+ description,
193
+ type: decomposition.projectType,
194
+ complexity: decomposition.estimatedComplexity,
195
+ totalModules: decomposition.totalModules
196
+ });
197
+ }
198
+
199
+ await this.log(`Decomposed into ${decomposition.totalModules} modules: ${decomposition.buildOrder.join(', ')}`);
200
+
201
+ // Step 2: Build modules in order
202
+ this.setState(ORCHESTRATOR_STATES.BUILDING);
203
+
204
+ const results = await this.buildModules(decomposition);
205
+
206
+ // Step 3: Final summary
207
+ if (results.success) {
208
+ this.setState(ORCHESTRATOR_STATES.COMPLETED);
209
+ } else {
210
+ this.setState(ORCHESTRATOR_STATES.FAILED);
211
+ }
212
+
213
+ return this.generateBuildReport(decomposition, results);
214
+
215
+ } catch (error) {
216
+ this.setState(ORCHESTRATOR_STATES.FAILED);
217
+ await this.log(`Build failed: ${error.message}`, 'error');
218
+ throw error;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Build all modules in dependency order
224
+ */
225
+ async buildModules(decomposition) {
226
+ const results = {
227
+ success: true,
228
+ modules: {},
229
+ totalTime: 0
230
+ };
231
+
232
+ // Create and start dashboard if enabled
233
+ if (this.config.useDashboard) {
234
+ this.dashboard = new ProgressDashboard({
235
+ title: 'VIBECODE AGENT',
236
+ projectName: path.basename(this.projectPath),
237
+ mode: `Agent (${decomposition.totalModules} modules)`
238
+ });
239
+
240
+ // Set modules for dashboard
241
+ this.dashboard.setModules(decomposition.buildOrder.map(id => {
242
+ const mod = this.decompositionEngine.getModule(id);
243
+ return { name: mod?.name || id };
244
+ }));
245
+
246
+ this.dashboard.start();
247
+ }
248
+
249
+ try {
250
+ for (const moduleId of decomposition.buildOrder) {
251
+ // Check if we should stop
252
+ if (this.state === ORCHESTRATOR_STATES.PAUSED) {
253
+ await this.log('Build paused');
254
+ break;
255
+ }
256
+
257
+ // Check if module can be built (dependencies satisfied)
258
+ if (!this.decompositionEngine.canBuildModule(moduleId)) {
259
+ const depStatus = decomposition.dependencyGraph[moduleId];
260
+ const failedDeps = depStatus?.dependsOn.filter(d =>
261
+ this.buildState.failedModules.includes(d)
262
+ );
263
+
264
+ if (failedDeps?.length > 0) {
265
+ await this.log(`Skipping ${moduleId}: dependencies failed (${failedDeps.join(', ')})`, 'warn');
266
+ this.buildState.skippedModules.push(moduleId);
267
+ results.modules[moduleId] = { status: 'skipped', reason: 'dependencies_failed' };
268
+
269
+ // Update dashboard
270
+ if (this.dashboard) {
271
+ const mod = this.decompositionEngine.getModule(moduleId);
272
+ this.dashboard.failModule(mod?.name || moduleId);
273
+ }
274
+ continue;
275
+ }
276
+ }
277
+
278
+ // Update dashboard - start module
279
+ if (this.dashboard) {
280
+ const mod = this.decompositionEngine.getModule(moduleId);
281
+ this.dashboard.startModule(mod?.name || moduleId);
282
+ }
283
+
284
+ // Build the module
285
+ const moduleResult = await this.buildModule(moduleId, decomposition);
286
+ results.modules[moduleId] = moduleResult;
287
+
288
+ // Update dashboard - complete/fail module
289
+ if (this.dashboard) {
290
+ const mod = this.decompositionEngine.getModule(moduleId);
291
+ if (moduleResult.success) {
292
+ this.dashboard.completeModule(mod?.name || moduleId, true);
293
+ } else {
294
+ this.dashboard.failModule(mod?.name || moduleId);
295
+ }
296
+ }
297
+
298
+ if (!moduleResult.success) {
299
+ results.success = false;
300
+
301
+ if (!this.config.continueOnFailure) {
302
+ await this.log(`Stopping build due to module failure: ${moduleId}`, 'error');
303
+ break;
304
+ }
305
+ }
306
+ }
307
+ } finally {
308
+ // Stop dashboard
309
+ if (this.dashboard) {
310
+ this.dashboard.stop();
311
+ }
312
+ }
313
+
314
+ results.totalTime = Date.now() - this.buildState.startTime;
315
+ return results;
316
+ }
317
+
318
+ /**
319
+ * Build a single module
320
+ */
321
+ async buildModule(moduleId, decomposition) {
322
+ const module = this.decompositionEngine.getModule(moduleId);
323
+ if (!module) {
324
+ return { success: false, error: 'Module not found' };
325
+ }
326
+
327
+ this.buildState.currentModule = moduleId;
328
+ this.emit(EVENTS.MODULE_START, { moduleId, module });
329
+
330
+ // Only use spinner if dashboard is not enabled
331
+ const spinner = !this.config.useDashboard ? ora({
332
+ text: chalk.cyan(`Building module: ${module.name}`),
333
+ prefixText: this.getProgressPrefix()
334
+ }).start() : null;
335
+
336
+ // Record in memory
337
+ if (this.memoryEngine) {
338
+ await this.memoryEngine.startModule(moduleId, {
339
+ name: module.name,
340
+ description: module.description
341
+ });
342
+ }
343
+
344
+ let attempts = 0;
345
+ let lastError = null;
346
+ let healingPrompt = null; // Store fix prompt from self-healing
347
+
348
+ while (attempts < this.config.maxModuleRetries) {
349
+ attempts++;
350
+ module.buildAttempts = attempts;
351
+
352
+ try {
353
+ await this.log(`Building ${moduleId} (attempt ${attempts}/${this.config.maxModuleRetries})`);
354
+
355
+ // Use healing prompt if available (from previous retry), otherwise generate fresh
356
+ let prompt;
357
+ if (healingPrompt) {
358
+ prompt = healingPrompt;
359
+ healingPrompt = null; // Clear after use
360
+ } else {
361
+ prompt = this.generateBuildPrompt(module, decomposition);
362
+ }
363
+
364
+ // Run Claude Code
365
+ const buildResult = await this.runClaudeBuild(prompt, moduleId);
366
+
367
+ if (buildResult.success) {
368
+ // Run tests if configured
369
+ if (this.config.testAfterEachModule) {
370
+ if (spinner) spinner.text = chalk.cyan(`Testing module: ${module.name}`);
371
+ if (this.dashboard) this.dashboard.addLog(`Testing: ${module.name}`);
372
+ this.setState(ORCHESTRATOR_STATES.TESTING);
373
+
374
+ const testResult = await runTests(this.projectPath);
375
+
376
+ if (!testResult.passed) {
377
+ throw new Error(`Tests failed: ${testResult.summary.failed} failures`);
378
+ }
379
+ }
380
+
381
+ // Success!
382
+ if (spinner) spinner.succeed(chalk.green(`Module complete: ${module.name}`));
383
+
384
+ this.decompositionEngine.updateModuleStatus(moduleId, 'completed', {
385
+ files: buildResult.files || []
386
+ });
387
+ this.buildState.completedModules.push(moduleId);
388
+
389
+ // Record in memory
390
+ if (this.memoryEngine) {
391
+ await this.memoryEngine.completeModule(moduleId, {
392
+ files: buildResult.files,
393
+ attempts
394
+ });
395
+ }
396
+
397
+ this.emit(EVENTS.MODULE_COMPLETE, { moduleId, result: buildResult });
398
+
399
+ return {
400
+ success: true,
401
+ attempts,
402
+ files: buildResult.files
403
+ };
404
+ }
405
+
406
+ throw new Error(buildResult.error || 'Build failed');
407
+
408
+ } catch (error) {
409
+ lastError = error;
410
+ const translated = translateError(error);
411
+ await this.log(`Module ${moduleId} attempt ${attempts} failed: ${translated.title} - ${error.message}`, 'error');
412
+
413
+ // Show translated error if not using dashboard
414
+ if (!this.config.useDashboard) {
415
+ console.log(inlineError(error));
416
+ } else if (this.dashboard) {
417
+ this.dashboard.addLog(`Error: ${translated.title}`);
418
+ }
419
+
420
+ // Try self-healing
421
+ if (this.selfHealingEngine && attempts < this.config.maxModuleRetries) {
422
+ if (spinner) spinner.text = chalk.yellow(`Healing module: ${module.name}`);
423
+ if (this.dashboard) this.dashboard.addLog(`Healing: ${module.name}`);
424
+ this.setState(ORCHESTRATOR_STATES.HEALING);
425
+ this.emit(EVENTS.HEALING_START, { moduleId, error });
426
+
427
+ const healing = await this.selfHealingEngine.heal(error.message, moduleId, {
428
+ attempt: attempts,
429
+ completedModules: this.buildState.completedModules
430
+ });
431
+
432
+ if (healing.shouldRetry) {
433
+ const errorCategory = healing.analysis?.category || 'UNKNOWN';
434
+ await this.log(`Self-healing: ${errorCategory} error, retrying...`);
435
+ this.buildState.totalRetries++;
436
+
437
+ // Store healing prompt for next iteration
438
+ healingPrompt = healing.prompt;
439
+
440
+ // Record healing attempt
441
+ if (this.memoryEngine) {
442
+ await this.memoryEngine.recordError({
443
+ message: error.message,
444
+ type: errorCategory,
445
+ moduleId,
446
+ healingAttempt: attempts
447
+ });
448
+ }
449
+
450
+ continue;
451
+ }
452
+ }
453
+
454
+ break; // Exit retry loop if can't heal
455
+ }
456
+ }
457
+
458
+ // Module failed
459
+ if (spinner) spinner.fail(chalk.red(`Module failed: ${module.name}`));
460
+
461
+ this.decompositionEngine.updateModuleStatus(moduleId, 'failed', {
462
+ error: lastError?.message
463
+ });
464
+ this.buildState.failedModules.push(moduleId);
465
+ this.buildState.errors.push({ moduleId, error: lastError?.message });
466
+
467
+ if (this.memoryEngine) {
468
+ await this.memoryEngine.failModule(moduleId, lastError);
469
+ }
470
+
471
+ this.emit(EVENTS.MODULE_FAIL, { moduleId, error: lastError, attempts });
472
+
473
+ return {
474
+ success: false,
475
+ attempts,
476
+ error: lastError?.message
477
+ };
478
+ }
479
+
480
+ /**
481
+ * Generate build prompt for a module
482
+ */
483
+ generateBuildPrompt(module, decomposition) {
484
+ let prompt = `# Build Module: ${module.name}\n\n`;
485
+
486
+ // Module description
487
+ prompt += `## Description\n${module.description}\n\n`;
488
+
489
+ // Dependencies context
490
+ if (module.dependencies.length > 0) {
491
+ prompt += `## Dependencies (already built)\n`;
492
+ for (const depId of module.dependencies) {
493
+ const dep = this.decompositionEngine.getModule(depId);
494
+ if (dep) {
495
+ prompt += `- **${dep.name}**: ${dep.description}\n`;
496
+ if (dep.files?.length > 0) {
497
+ prompt += ` Files: ${dep.files.join(', ')}\n`;
498
+ }
499
+ }
500
+ }
501
+ prompt += '\n';
502
+ }
503
+
504
+ // Memory context
505
+ if (this.memoryEngine) {
506
+ const contextSummary = this.memoryEngine.generateContextSummary();
507
+ prompt += contextSummary;
508
+ }
509
+
510
+ // Project context
511
+ const projectContext = this.memoryEngine?.getProjectContext();
512
+ if (projectContext?.description) {
513
+ prompt += `## Project Goal\n${projectContext.description}\n\n`;
514
+ }
515
+
516
+ // Build instructions
517
+ prompt += `## Instructions\n`;
518
+ prompt += `1. Create all necessary files for the ${module.name} module\n`;
519
+ prompt += `2. Follow patterns established in completed modules\n`;
520
+ prompt += `3. Ensure compatibility with dependencies\n`;
521
+ prompt += `4. Add appropriate error handling\n`;
522
+ prompt += `5. Export any functions/components needed by dependent modules\n\n`;
523
+
524
+ // Specific instructions based on module type
525
+ const moduleInstructions = this.getModuleSpecificInstructions(module.id);
526
+ if (moduleInstructions) {
527
+ prompt += `## ${module.name} Specific Requirements\n${moduleInstructions}\n`;
528
+ }
529
+
530
+ return prompt;
531
+ }
532
+
533
+ /**
534
+ * Get module-specific build instructions
535
+ */
536
+ getModuleSpecificInstructions(moduleId) {
537
+ const instructions = {
538
+ core: `- Setup project structure (src/, public/, etc.)
539
+ - Create configuration files (package.json, tsconfig.json if needed)
540
+ - Setup base utilities and helpers`,
541
+
542
+ auth: `- Implement login/signup forms or endpoints
543
+ - Setup session/token management
544
+ - Add password hashing if applicable
545
+ - Create auth middleware/guards`,
546
+
547
+ database: `- Define data models/schemas
548
+ - Setup database connection
549
+ - Create migration scripts if needed
550
+ - Add seed data for development`,
551
+
552
+ api: `- Create REST/GraphQL endpoints
553
+ - Add request validation
554
+ - Implement error handling middleware
555
+ - Add API documentation`,
556
+
557
+ ui: `- Create reusable components
558
+ - Setup styling (CSS/Tailwind/etc.)
559
+ - Ensure responsive design
560
+ - Add accessibility attributes`,
561
+
562
+ pages: `- Create page components/routes
563
+ - Connect to API endpoints
564
+ - Add loading and error states
565
+ - Implement navigation`,
566
+
567
+ tests: `- Write unit tests for core functions
568
+ - Add integration tests for API
569
+ - Create component tests for UI
570
+ - Setup test utilities and mocks`
571
+ };
572
+
573
+ return instructions[moduleId] || null;
574
+ }
575
+
576
+ /**
577
+ * Run Claude Code build
578
+ */
579
+ async runClaudeBuild(prompt, moduleId) {
580
+ const evidencePath = path.join(this.projectPath, '.vibecode', 'agent', 'evidence');
581
+ await ensureDir(evidencePath);
582
+ const logPath = path.join(evidencePath, `${moduleId}.log`);
583
+
584
+ try {
585
+ const result = await spawnClaudeCode(prompt, {
586
+ cwd: this.projectPath,
587
+ logPath,
588
+ timeout: this.config.timeout
589
+ });
590
+
591
+ // Try to detect created files
592
+ const files = await this.detectCreatedFiles();
593
+
594
+ return {
595
+ success: result.code === 0,
596
+ code: result.code,
597
+ output: result.output,
598
+ files,
599
+ error: result.code !== 0 ? result.error : null
600
+ };
601
+ } catch (error) {
602
+ return {
603
+ success: false,
604
+ error: error.message
605
+ };
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Detect files created during build
611
+ */
612
+ async detectCreatedFiles() {
613
+ // This is a simplified version - in real implementation,
614
+ // we would track git status or file system changes
615
+ try {
616
+ const srcPath = path.join(this.projectPath, 'src');
617
+ if (await fs.pathExists(srcPath)) {
618
+ const files = await fs.readdir(srcPath, { recursive: true });
619
+ return files.filter(f => !f.startsWith('.')).slice(0, 20);
620
+ }
621
+ } catch (e) {
622
+ // Ignore
623
+ }
624
+ return [];
625
+ }
626
+
627
+ /**
628
+ * Get progress prefix for spinner
629
+ */
630
+ getProgressPrefix() {
631
+ const completed = this.buildState.completedModules.length;
632
+ const total = this.decompositionEngine?.modules?.length || 0;
633
+ const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
634
+ return chalk.gray(`[${completed}/${total}] ${percent}%`);
635
+ }
636
+
637
+ /**
638
+ * Generate final build report
639
+ */
640
+ generateBuildReport(decomposition, results) {
641
+ const duration = ((Date.now() - this.buildState.startTime) / 1000 / 60).toFixed(1);
642
+
643
+ return {
644
+ success: results.success,
645
+ projectType: decomposition.projectType,
646
+ complexity: decomposition.estimatedComplexity,
647
+ duration: `${duration} minutes`,
648
+
649
+ modules: {
650
+ total: decomposition.totalModules,
651
+ completed: this.buildState.completedModules.length,
652
+ failed: this.buildState.failedModules.length,
653
+ skipped: this.buildState.skippedModules.length
654
+ },
655
+
656
+ buildOrder: decomposition.buildOrder,
657
+ completedModules: this.buildState.completedModules,
658
+ failedModules: this.buildState.failedModules,
659
+ skippedModules: this.buildState.skippedModules,
660
+
661
+ retries: {
662
+ total: this.buildState.totalRetries,
663
+ max: this.config.maxTotalRetries
664
+ },
665
+
666
+ errors: this.buildState.errors,
667
+ moduleResults: results.modules,
668
+
669
+ healingStats: this.selfHealingEngine?.getStats() || null,
670
+ memoryStats: this.memoryEngine?.getStats() || null
671
+ };
672
+ }
673
+
674
+ /**
675
+ * Pause the build
676
+ */
677
+ pause() {
678
+ if (this.state === ORCHESTRATOR_STATES.BUILDING) {
679
+ this.setState(ORCHESTRATOR_STATES.PAUSED);
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Resume paused build
685
+ */
686
+ resume() {
687
+ if (this.state === ORCHESTRATOR_STATES.PAUSED) {
688
+ this.setState(ORCHESTRATOR_STATES.BUILDING);
689
+ }
690
+ }
691
+
692
+ /**
693
+ * Get current build state
694
+ */
695
+ getState() {
696
+ return {
697
+ state: this.state,
698
+ currentModule: this.buildState.currentModule,
699
+ completedModules: this.buildState.completedModules,
700
+ failedModules: this.buildState.failedModules,
701
+ progress: this.getProgressPrefix()
702
+ };
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Create orchestrator instance
708
+ */
709
+ export function createOrchestrator(options = {}) {
710
+ return new Orchestrator(options);
711
+ }
712
+
713
+ export { ORCHESTRATOR_STATES, EVENTS };