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