@nclamvn/vibecode-cli 1.2.0 → 2.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.
- package/bin/vibecode.js +0 -3
- package/package.json +1 -1
- package/src/commands/build.js +2 -332
- package/src/config/constants.js +1 -1
- package/src/core/error-analyzer.js +0 -237
- package/src/core/fix-generator.js +0 -195
- package/src/core/iteration.js +0 -226
- package/src/core/test-runner.js +0 -248
package/bin/vibecode.js
CHANGED
|
@@ -79,9 +79,6 @@ program
|
|
|
79
79
|
.option('-c, --complete', 'Mark build as complete')
|
|
80
80
|
.option('-e, --evidence', 'Capture evidence snapshot')
|
|
81
81
|
.option('-a, --auto', 'Auto-build with Claude Code (--dangerously-skip-permissions)')
|
|
82
|
-
.option('-i, --iterate', 'Iterative build: build-test-fix loop until tests pass')
|
|
83
|
-
.option('-m, --max <n>', 'Max iterations for --iterate mode', parseInt)
|
|
84
|
-
.option('--strict', 'Strict mode: exit with error if tests fail after max iterations')
|
|
85
82
|
.option('--provider <name>', 'Provider to use: claude-code', 'claude-code')
|
|
86
83
|
.action(buildCommand);
|
|
87
84
|
|
package/package.json
CHANGED
package/src/commands/build.js
CHANGED
|
@@ -20,7 +20,7 @@ import { getCurrentState, transitionTo } from '../core/state-machine.js';
|
|
|
20
20
|
import { getSpecHash } from '../core/contract.js';
|
|
21
21
|
import { STATES } from '../config/constants.js';
|
|
22
22
|
import { getBuildReportTemplate } from '../config/templates.js';
|
|
23
|
-
import { ensureDir, pathExists, appendToFile, readMarkdown
|
|
23
|
+
import { ensureDir, pathExists, appendToFile, readMarkdown } from '../utils/files.js';
|
|
24
24
|
import { printBox, printError, printSuccess, printWarning, printNextStep } from '../ui/output.js';
|
|
25
25
|
import {
|
|
26
26
|
spawnClaudeCode,
|
|
@@ -28,19 +28,6 @@ import {
|
|
|
28
28
|
buildPromptWithContext,
|
|
29
29
|
getProviderInfo
|
|
30
30
|
} from '../providers/index.js';
|
|
31
|
-
// Phase D: Iterative Build
|
|
32
|
-
import { runTests, formatTestResults } from '../core/test-runner.js';
|
|
33
|
-
import { analyzeErrors, formatErrors, createErrorSummary } from '../core/error-analyzer.js';
|
|
34
|
-
import { generateFixPrompt, areErrorsFixable, estimateFixComplexity } from '../core/fix-generator.js';
|
|
35
|
-
import {
|
|
36
|
-
createIterationState,
|
|
37
|
-
recordIteration,
|
|
38
|
-
canContinue,
|
|
39
|
-
finalizeIterationState,
|
|
40
|
-
saveIterationState,
|
|
41
|
-
formatIterationSummary,
|
|
42
|
-
logIteration
|
|
43
|
-
} from '../core/iteration.js';
|
|
44
31
|
|
|
45
32
|
const execAsync = promisify(exec);
|
|
46
33
|
|
|
@@ -59,10 +46,7 @@ export async function buildCommand(options = {}) {
|
|
|
59
46
|
const specHash = await getSpecHash();
|
|
60
47
|
|
|
61
48
|
// Handle different build modes
|
|
62
|
-
if (options.
|
|
63
|
-
// Phase D: Iterative Build
|
|
64
|
-
await handleIterativeBuild(currentState, projectName, sessionId, sessionPath, specHash, options);
|
|
65
|
-
} else if (options.auto) {
|
|
49
|
+
if (options.auto) {
|
|
66
50
|
await handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options);
|
|
67
51
|
} else if (options.start) {
|
|
68
52
|
await handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash);
|
|
@@ -232,320 +216,6 @@ ${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : '
|
|
|
232
216
|
}
|
|
233
217
|
}
|
|
234
218
|
|
|
235
|
-
/**
|
|
236
|
-
* Handle --iterate mode: Build-Test-Fix loop
|
|
237
|
-
* "Build until tests pass or max iterations reached"
|
|
238
|
-
*/
|
|
239
|
-
async function handleIterativeBuild(currentState, projectName, sessionId, sessionPath, specHash, options) {
|
|
240
|
-
const maxIterations = options.max || 3;
|
|
241
|
-
const strictMode = options.strict || false;
|
|
242
|
-
|
|
243
|
-
// Check state - must be PLAN_CREATED or BUILD_IN_PROGRESS or REVIEW_FAILED
|
|
244
|
-
const validStates = [STATES.PLAN_CREATED, STATES.BUILD_IN_PROGRESS, STATES.REVIEW_FAILED];
|
|
245
|
-
if (!validStates.includes(currentState)) {
|
|
246
|
-
printError(`Cannot iterate in state: ${currentState}`);
|
|
247
|
-
console.log('Run `vibecode plan` first to create execution plan.');
|
|
248
|
-
process.exit(1);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Check if Claude Code is available
|
|
252
|
-
const available = await isClaudeCodeAvailable();
|
|
253
|
-
if (!available) {
|
|
254
|
-
printError('Claude Code CLI not found.');
|
|
255
|
-
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
256
|
-
process.exit(1);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Check coder_pack.md exists
|
|
260
|
-
if (!await sessionFileExists('coder_pack.md')) {
|
|
261
|
-
printError('coder_pack.md not found. Run `vibecode plan` first.');
|
|
262
|
-
process.exit(1);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Setup directories
|
|
266
|
-
const evidencePath = path.join(sessionPath, 'evidence');
|
|
267
|
-
await ensureDir(evidencePath);
|
|
268
|
-
await ensureDir(path.join(evidencePath, 'screenshots'));
|
|
269
|
-
const iterationDir = path.join(sessionPath, 'iterations');
|
|
270
|
-
await ensureDir(iterationDir);
|
|
271
|
-
const logPath = path.join(evidencePath, 'build.log');
|
|
272
|
-
|
|
273
|
-
// Initialize iteration state
|
|
274
|
-
let iterationState = createIterationState(sessionId, maxIterations);
|
|
275
|
-
|
|
276
|
-
// Save initial state
|
|
277
|
-
const stateData = await loadState();
|
|
278
|
-
const startTime = new Date().toISOString();
|
|
279
|
-
stateData.build_started = startTime;
|
|
280
|
-
stateData.build_provider = 'claude-code';
|
|
281
|
-
stateData.build_mode = 'iterate';
|
|
282
|
-
stateData.max_iterations = maxIterations;
|
|
283
|
-
await saveState(stateData);
|
|
284
|
-
|
|
285
|
-
// Transition to BUILD_IN_PROGRESS if not already
|
|
286
|
-
if (currentState !== STATES.BUILD_IN_PROGRESS) {
|
|
287
|
-
await transitionTo(STATES.BUILD_IN_PROGRESS, 'iterative_build_started');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Load coder pack
|
|
291
|
-
const originalCoderPack = await readSessionFile('coder_pack.md');
|
|
292
|
-
|
|
293
|
-
// Log start
|
|
294
|
-
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
295
|
-
await appendToFile(logPath, `ITERATIVE BUILD STARTED: ${startTime}\n`);
|
|
296
|
-
await appendToFile(logPath, `Max Iterations: ${maxIterations}\n`);
|
|
297
|
-
await appendToFile(logPath, `Strict Mode: ${strictMode}\n`);
|
|
298
|
-
await appendToFile(logPath, `${'='.repeat(60)}\n\n`);
|
|
299
|
-
|
|
300
|
-
// Show starting message
|
|
301
|
-
const providerInfo = getProviderInfo();
|
|
302
|
-
|
|
303
|
-
const content = `🔄 ITERATIVE BUILD
|
|
304
|
-
|
|
305
|
-
Project: ${projectName}
|
|
306
|
-
Session: ${sessionId}
|
|
307
|
-
Spec Hash: ${specHash}
|
|
308
|
-
|
|
309
|
-
Provider: ${providerInfo.name}
|
|
310
|
-
Max Iterations: ${maxIterations}
|
|
311
|
-
Strict Mode: ${strictMode ? 'Yes' : 'No'}
|
|
312
|
-
|
|
313
|
-
Starting build-test-fix loop...`;
|
|
314
|
-
|
|
315
|
-
console.log();
|
|
316
|
-
printBox(content, { borderColor: 'magenta' });
|
|
317
|
-
console.log();
|
|
318
|
-
|
|
319
|
-
// Build-Test-Fix Loop
|
|
320
|
-
let currentPrompt = await buildPromptWithContext(originalCoderPack, process.cwd());
|
|
321
|
-
let loopResult = { success: false, reason: '' };
|
|
322
|
-
|
|
323
|
-
while (true) {
|
|
324
|
-
const iteration = iterationState.currentIteration + 1;
|
|
325
|
-
const iterationStart = Date.now();
|
|
326
|
-
|
|
327
|
-
console.log(chalk.cyan(`\n${'─'.repeat(60)}`));
|
|
328
|
-
console.log(chalk.cyan(`│ ITERATION ${iteration}/${maxIterations}`));
|
|
329
|
-
console.log(chalk.cyan(`${'─'.repeat(60)}\n`));
|
|
330
|
-
|
|
331
|
-
await logIteration(logPath, iteration, 'Starting iteration');
|
|
332
|
-
|
|
333
|
-
// Step 1: Run Claude Code
|
|
334
|
-
console.log(chalk.yellow('▶ Running Claude Code...'));
|
|
335
|
-
console.log();
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
const buildResult = await spawnClaudeCode(currentPrompt, {
|
|
339
|
-
cwd: process.cwd(),
|
|
340
|
-
logPath: logPath
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
console.log();
|
|
344
|
-
await logIteration(logPath, iteration, `Claude Code exited with code: ${buildResult.code}`);
|
|
345
|
-
|
|
346
|
-
// Capture evidence for this iteration
|
|
347
|
-
await captureGitDiff(evidencePath);
|
|
348
|
-
const iterEvidencePath = path.join(iterationDir, `iteration-${iteration}-diff.txt`);
|
|
349
|
-
try {
|
|
350
|
-
const { stdout } = await execAsync('git diff HEAD', { maxBuffer: 10 * 1024 * 1024 });
|
|
351
|
-
if (stdout.trim()) {
|
|
352
|
-
const fs = await import('fs-extra');
|
|
353
|
-
await fs.default.writeFile(iterEvidencePath, stdout, 'utf-8');
|
|
354
|
-
}
|
|
355
|
-
} catch (e) { /* ignore */ }
|
|
356
|
-
|
|
357
|
-
// Step 2: Run Tests
|
|
358
|
-
console.log(chalk.yellow('▶ Running tests...'));
|
|
359
|
-
const spinner = ora('Testing...').start();
|
|
360
|
-
|
|
361
|
-
const testResult = await runTests(process.cwd());
|
|
362
|
-
const iterationDuration = Date.now() - iterationStart;
|
|
363
|
-
|
|
364
|
-
if (testResult.passed) {
|
|
365
|
-
spinner.succeed('All tests passed!');
|
|
366
|
-
|
|
367
|
-
// Record successful iteration
|
|
368
|
-
iterationState = recordIteration(iterationState, {
|
|
369
|
-
passed: true,
|
|
370
|
-
errorCount: 0,
|
|
371
|
-
errorTypes: [],
|
|
372
|
-
affectedFiles: [],
|
|
373
|
-
duration: iterationDuration,
|
|
374
|
-
action: 'build'
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// Finalize as success
|
|
378
|
-
iterationState = finalizeIterationState(iterationState, 'success');
|
|
379
|
-
await saveIterationState(sessionPath, iterationState);
|
|
380
|
-
|
|
381
|
-
loopResult = { success: true, reason: 'All tests passed' };
|
|
382
|
-
break;
|
|
383
|
-
|
|
384
|
-
} else {
|
|
385
|
-
spinner.fail(`Tests failed: ${testResult.summary.failed}/${testResult.summary.total}`);
|
|
386
|
-
|
|
387
|
-
// Step 3: Analyze Errors
|
|
388
|
-
const analyzedErrors = analyzeErrors(testResult);
|
|
389
|
-
const summary = createErrorSummary(analyzedErrors);
|
|
390
|
-
|
|
391
|
-
console.log();
|
|
392
|
-
console.log(formatErrors(analyzedErrors));
|
|
393
|
-
|
|
394
|
-
await logIteration(logPath, iteration, `Found ${analyzedErrors.length} errors`);
|
|
395
|
-
|
|
396
|
-
// Save error analysis for this iteration
|
|
397
|
-
await writeJson(path.join(iterationDir, `iteration-${iteration}-errors.json`), {
|
|
398
|
-
iteration,
|
|
399
|
-
timestamp: new Date().toISOString(),
|
|
400
|
-
summary,
|
|
401
|
-
errors: analyzedErrors
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// Record failed iteration
|
|
405
|
-
iterationState = recordIteration(iterationState, {
|
|
406
|
-
passed: false,
|
|
407
|
-
errorCount: analyzedErrors.length,
|
|
408
|
-
errorTypes: [...new Set(analyzedErrors.map(e => e.type))],
|
|
409
|
-
affectedFiles: [...new Set(analyzedErrors.filter(e => e.file).map(e => e.file))],
|
|
410
|
-
duration: iterationDuration,
|
|
411
|
-
action: 'build'
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Check if errors are fixable
|
|
415
|
-
const fixableCheck = areErrorsFixable(analyzedErrors);
|
|
416
|
-
if (!fixableCheck.fixable) {
|
|
417
|
-
console.log(chalk.red(`\n⚠️ ${fixableCheck.reason}`));
|
|
418
|
-
await logIteration(logPath, iteration, `Errors not fixable: ${fixableCheck.reason}`);
|
|
419
|
-
|
|
420
|
-
iterationState = finalizeIterationState(iterationState, 'unfixable');
|
|
421
|
-
await saveIterationState(sessionPath, iterationState);
|
|
422
|
-
|
|
423
|
-
loopResult = { success: false, reason: fixableCheck.reason };
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Check if we can continue
|
|
428
|
-
const continueCheck = canContinue(iterationState);
|
|
429
|
-
if (!continueCheck.canContinue) {
|
|
430
|
-
console.log(chalk.yellow(`\n⚠️ ${continueCheck.reason}`));
|
|
431
|
-
await logIteration(logPath, iteration, continueCheck.reason);
|
|
432
|
-
|
|
433
|
-
iterationState = finalizeIterationState(iterationState, 'max_reached');
|
|
434
|
-
await saveIterationState(sessionPath, iterationState);
|
|
435
|
-
|
|
436
|
-
loopResult = { success: false, reason: continueCheck.reason };
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Step 4: Generate Fix Prompt
|
|
441
|
-
const complexity = estimateFixComplexity(analyzedErrors);
|
|
442
|
-
console.log(chalk.gray(`\nFix complexity: ${complexity}`));
|
|
443
|
-
console.log(chalk.yellow(`\n▶ Generating fix prompt for iteration ${iteration + 1}...`));
|
|
444
|
-
|
|
445
|
-
currentPrompt = generateFixPrompt(analyzedErrors, originalCoderPack, iteration + 1);
|
|
446
|
-
|
|
447
|
-
// Save fix prompt for evidence
|
|
448
|
-
await writeSessionFile(`iterations/fix-prompt-${iteration + 1}.md`, currentPrompt);
|
|
449
|
-
|
|
450
|
-
await logIteration(logPath, iteration, 'Generated fix prompt for next iteration');
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
} catch (error) {
|
|
454
|
-
console.log(chalk.red(`\n❌ Build error: ${error.message}`));
|
|
455
|
-
await logIteration(logPath, iteration, `Error: ${error.message}`);
|
|
456
|
-
|
|
457
|
-
iterationState = finalizeIterationState(iterationState, 'error');
|
|
458
|
-
await saveIterationState(sessionPath, iterationState);
|
|
459
|
-
|
|
460
|
-
loopResult = { success: false, reason: error.message };
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Final Summary
|
|
466
|
-
const endTime = new Date().toISOString();
|
|
467
|
-
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
468
|
-
await appendToFile(logPath, `ITERATIVE BUILD COMPLETED: ${endTime}\n`);
|
|
469
|
-
await appendToFile(logPath, `Result: ${loopResult.success ? 'SUCCESS' : 'FAILED'}\n`);
|
|
470
|
-
await appendToFile(logPath, `Iterations: ${iterationState.currentIteration}\n`);
|
|
471
|
-
await appendToFile(logPath, `${'='.repeat(60)}\n`);
|
|
472
|
-
|
|
473
|
-
// Save final iteration state
|
|
474
|
-
await saveIterationState(sessionPath, iterationState);
|
|
475
|
-
|
|
476
|
-
// Check evidence
|
|
477
|
-
const evidence = await checkEvidence(evidencePath);
|
|
478
|
-
|
|
479
|
-
// Generate build report
|
|
480
|
-
const reportContent = getBuildReportTemplate(
|
|
481
|
-
projectName,
|
|
482
|
-
sessionId,
|
|
483
|
-
specHash,
|
|
484
|
-
startTime,
|
|
485
|
-
endTime,
|
|
486
|
-
evidence
|
|
487
|
-
);
|
|
488
|
-
await writeSessionFile('build_report.md', reportContent);
|
|
489
|
-
|
|
490
|
-
// Update state
|
|
491
|
-
stateData.build_completed = endTime;
|
|
492
|
-
stateData.iterations = iterationState.currentIteration;
|
|
493
|
-
stateData.iteration_result = loopResult.success ? 'success' : 'failed';
|
|
494
|
-
await saveState(stateData);
|
|
495
|
-
|
|
496
|
-
console.log();
|
|
497
|
-
console.log(chalk.cyan('─'.repeat(60)));
|
|
498
|
-
console.log();
|
|
499
|
-
|
|
500
|
-
if (loopResult.success) {
|
|
501
|
-
await transitionTo(STATES.BUILD_DONE, 'iterative_build_success');
|
|
502
|
-
|
|
503
|
-
const duration = Math.round((new Date(endTime) - new Date(startTime)) / 1000 / 60);
|
|
504
|
-
|
|
505
|
-
const successContent = `✅ ITERATIVE BUILD SUCCESS
|
|
506
|
-
|
|
507
|
-
Project: ${projectName}
|
|
508
|
-
Iterations: ${iterationState.currentIteration}/${maxIterations}
|
|
509
|
-
Duration: ${duration} minutes
|
|
510
|
-
Result: All tests passed!
|
|
511
|
-
|
|
512
|
-
Evidence:
|
|
513
|
-
${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
|
|
514
|
-
${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
|
|
515
|
-
✅ ${iterationState.currentIteration} iteration records`;
|
|
516
|
-
|
|
517
|
-
printBox(successContent, { borderColor: 'green' });
|
|
518
|
-
printNextStep('Run `vibecode review` to validate your build');
|
|
519
|
-
|
|
520
|
-
} else {
|
|
521
|
-
// Still transition to BUILD_DONE but with failure note
|
|
522
|
-
await transitionTo(STATES.BUILD_DONE, 'iterative_build_completed_with_errors');
|
|
523
|
-
|
|
524
|
-
const failContent = `⚠️ ITERATIVE BUILD INCOMPLETE
|
|
525
|
-
|
|
526
|
-
Project: ${projectName}
|
|
527
|
-
Iterations: ${iterationState.currentIteration}/${maxIterations}
|
|
528
|
-
Result: ${loopResult.reason}
|
|
529
|
-
|
|
530
|
-
Evidence saved in:
|
|
531
|
-
${iterationDir}/
|
|
532
|
-
|
|
533
|
-
Check iteration logs for details.`;
|
|
534
|
-
|
|
535
|
-
printBox(failContent, { borderColor: 'yellow' });
|
|
536
|
-
|
|
537
|
-
if (strictMode) {
|
|
538
|
-
printError('Strict mode: Build failed with errors');
|
|
539
|
-
process.exit(1);
|
|
540
|
-
} else {
|
|
541
|
-
console.log(chalk.gray('\nYou can:'));
|
|
542
|
-
console.log(chalk.gray(' • Run `vibecode build --iterate` to try again'));
|
|
543
|
-
console.log(chalk.gray(' • Run `vibecode review` to review current state'));
|
|
544
|
-
console.log(chalk.gray(' • Fix errors manually and run `vibecode build --complete`'));
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
219
|
async function handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash) {
|
|
550
220
|
const spinner = ora('Starting build...').start();
|
|
551
221
|
|
package/src/config/constants.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Spec Hash: 0fe43335f5a325e3279a079ce616c052
|
|
4
4
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
5
|
|
|
6
|
-
export const VERSION = '
|
|
6
|
+
export const VERSION = '2.0.0';
|
|
7
7
|
export const SPEC_HASH = '0fe43335f5a325e3279a079ce616c052';
|
|
8
8
|
|
|
9
9
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
-
// VIBECODE CLI - Error Analyzer
|
|
3
|
-
// Intelligent error analysis for iterative builds
|
|
4
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Analyze test results and extract actionable errors
|
|
8
|
-
* @param {TestResults} testResult - Results from test runner
|
|
9
|
-
* @returns {AnalyzedError[]}
|
|
10
|
-
*/
|
|
11
|
-
export function analyzeErrors(testResult) {
|
|
12
|
-
const errors = [];
|
|
13
|
-
|
|
14
|
-
for (const test of testResult.tests) {
|
|
15
|
-
if (!test.passed) {
|
|
16
|
-
const testErrors = test.errors || [];
|
|
17
|
-
|
|
18
|
-
for (const error of testErrors) {
|
|
19
|
-
errors.push({
|
|
20
|
-
source: test.name,
|
|
21
|
-
type: categorizeError(error),
|
|
22
|
-
file: error.file || null,
|
|
23
|
-
line: error.line || null,
|
|
24
|
-
column: error.column || null,
|
|
25
|
-
message: error.message,
|
|
26
|
-
suggestion: generateSuggestion(error),
|
|
27
|
-
priority: calculatePriority(error),
|
|
28
|
-
raw: error.raw
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// If no specific errors but test failed, add generic error
|
|
33
|
-
if (testErrors.length === 0) {
|
|
34
|
-
errors.push({
|
|
35
|
-
source: test.name,
|
|
36
|
-
type: 'unknown',
|
|
37
|
-
message: test.error || `${test.name} failed`,
|
|
38
|
-
suggestion: `Check ${test.name} output for details`,
|
|
39
|
-
priority: 'medium',
|
|
40
|
-
raw: test.output
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Sort by priority
|
|
47
|
-
return errors.sort((a, b) => {
|
|
48
|
-
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
49
|
-
return (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Categorize error type
|
|
55
|
-
*/
|
|
56
|
-
function categorizeError(error) {
|
|
57
|
-
const message = (error.message || '').toLowerCase();
|
|
58
|
-
const raw = (error.raw || '').toLowerCase();
|
|
59
|
-
const combined = message + ' ' + raw;
|
|
60
|
-
|
|
61
|
-
if (combined.includes('syntaxerror') || combined.includes('unexpected token')) {
|
|
62
|
-
return 'syntax';
|
|
63
|
-
}
|
|
64
|
-
if (combined.includes('typeerror') || combined.includes('type error') || combined.includes('is not a function')) {
|
|
65
|
-
return 'type';
|
|
66
|
-
}
|
|
67
|
-
if (combined.includes('referenceerror') || combined.includes('is not defined')) {
|
|
68
|
-
return 'reference';
|
|
69
|
-
}
|
|
70
|
-
if (combined.includes('import') || combined.includes('require') || combined.includes('module not found')) {
|
|
71
|
-
return 'import';
|
|
72
|
-
}
|
|
73
|
-
if (combined.includes('eslint') || combined.includes('lint')) {
|
|
74
|
-
return 'lint';
|
|
75
|
-
}
|
|
76
|
-
if (combined.includes('test') || combined.includes('expect') || combined.includes('assert')) {
|
|
77
|
-
return 'test';
|
|
78
|
-
}
|
|
79
|
-
if (combined.includes('typescript') || combined.includes('ts(')) {
|
|
80
|
-
return 'typescript';
|
|
81
|
-
}
|
|
82
|
-
if (combined.includes('build') || combined.includes('compile')) {
|
|
83
|
-
return 'build';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return 'unknown';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Generate fix suggestion based on error type
|
|
91
|
-
*/
|
|
92
|
-
function generateSuggestion(error) {
|
|
93
|
-
const type = categorizeError(error);
|
|
94
|
-
const message = error.message || '';
|
|
95
|
-
|
|
96
|
-
switch (type) {
|
|
97
|
-
case 'syntax':
|
|
98
|
-
return 'Check for missing brackets, semicolons, or typos near the error location';
|
|
99
|
-
|
|
100
|
-
case 'type':
|
|
101
|
-
if (message.includes('undefined')) {
|
|
102
|
-
return 'Check if the variable/property is properly initialized';
|
|
103
|
-
}
|
|
104
|
-
if (message.includes('is not a function')) {
|
|
105
|
-
return 'Verify the function exists and is properly imported';
|
|
106
|
-
}
|
|
107
|
-
return 'Check type compatibility and ensure proper type handling';
|
|
108
|
-
|
|
109
|
-
case 'reference':
|
|
110
|
-
return 'Ensure the variable/function is defined or imported before use';
|
|
111
|
-
|
|
112
|
-
case 'import':
|
|
113
|
-
if (message.includes('module not found')) {
|
|
114
|
-
return 'Install missing package with npm install or fix import path';
|
|
115
|
-
}
|
|
116
|
-
return 'Check import path and ensure the module exports correctly';
|
|
117
|
-
|
|
118
|
-
case 'lint':
|
|
119
|
-
return 'Fix the linting issue as specified in the error message';
|
|
120
|
-
|
|
121
|
-
case 'test':
|
|
122
|
-
return 'Update the implementation to match expected behavior, or fix the test assertion';
|
|
123
|
-
|
|
124
|
-
case 'typescript':
|
|
125
|
-
return 'Fix type errors by adding proper types or type guards';
|
|
126
|
-
|
|
127
|
-
case 'build':
|
|
128
|
-
return 'Check build configuration and dependencies';
|
|
129
|
-
|
|
130
|
-
default:
|
|
131
|
-
return 'Review the error message and fix accordingly';
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Calculate error priority
|
|
137
|
-
*/
|
|
138
|
-
function calculatePriority(error) {
|
|
139
|
-
const type = categorizeError(error);
|
|
140
|
-
|
|
141
|
-
// Critical - blocks everything
|
|
142
|
-
if (type === 'syntax' || type === 'import') {
|
|
143
|
-
return 'critical';
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// High - likely causes cascading failures
|
|
147
|
-
if (type === 'reference' || type === 'type') {
|
|
148
|
-
return 'high';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Medium - should be fixed
|
|
152
|
-
if (type === 'typescript' || type === 'build' || type === 'test') {
|
|
153
|
-
return 'medium';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Low - nice to fix
|
|
157
|
-
if (type === 'lint') {
|
|
158
|
-
return 'low';
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return 'medium';
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Group errors by file for better organization
|
|
166
|
-
*/
|
|
167
|
-
export function groupErrorsByFile(errors) {
|
|
168
|
-
const grouped = {};
|
|
169
|
-
|
|
170
|
-
for (const error of errors) {
|
|
171
|
-
const file = error.file || 'unknown';
|
|
172
|
-
if (!grouped[file]) {
|
|
173
|
-
grouped[file] = [];
|
|
174
|
-
}
|
|
175
|
-
grouped[file].push(error);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return grouped;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Get unique files with errors
|
|
183
|
-
*/
|
|
184
|
-
export function getAffectedFiles(errors) {
|
|
185
|
-
const files = new Set();
|
|
186
|
-
for (const error of errors) {
|
|
187
|
-
if (error.file) {
|
|
188
|
-
files.add(error.file);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return Array.from(files);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Format errors for display
|
|
196
|
-
*/
|
|
197
|
-
export function formatErrors(errors) {
|
|
198
|
-
const lines = [];
|
|
199
|
-
|
|
200
|
-
lines.push(`Found ${errors.length} error(s):`);
|
|
201
|
-
lines.push('');
|
|
202
|
-
|
|
203
|
-
const grouped = groupErrorsByFile(errors);
|
|
204
|
-
|
|
205
|
-
for (const [file, fileErrors] of Object.entries(grouped)) {
|
|
206
|
-
lines.push(`📄 ${file}`);
|
|
207
|
-
for (const error of fileErrors) {
|
|
208
|
-
const loc = error.line ? `:${error.line}` : '';
|
|
209
|
-
const priority = error.priority === 'critical' ? '🔴' :
|
|
210
|
-
error.priority === 'high' ? '🟠' :
|
|
211
|
-
error.priority === 'medium' ? '🟡' : '🟢';
|
|
212
|
-
lines.push(` ${priority} ${error.type}: ${error.message?.substring(0, 60) || 'Unknown error'}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return lines.join('\n');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Create a summary of errors for logging
|
|
221
|
-
*/
|
|
222
|
-
export function createErrorSummary(errors) {
|
|
223
|
-
const byType = {};
|
|
224
|
-
const byPriority = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
225
|
-
|
|
226
|
-
for (const error of errors) {
|
|
227
|
-
byType[error.type] = (byType[error.type] || 0) + 1;
|
|
228
|
-
byPriority[error.priority] = (byPriority[error.priority] || 0) + 1;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
total: errors.length,
|
|
233
|
-
byType,
|
|
234
|
-
byPriority,
|
|
235
|
-
files: getAffectedFiles(errors)
|
|
236
|
-
};
|
|
237
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
-
// VIBECODE CLI - Fix Generator
|
|
3
|
-
// Generate fix prompts for iterative builds
|
|
4
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
|
|
6
|
-
import { formatErrors, createErrorSummary } from './error-analyzer.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Generate a fix prompt based on errors from previous iteration
|
|
10
|
-
* @param {AnalyzedError[]} errors - Analyzed errors from test runner
|
|
11
|
-
* @param {string} originalPack - Original coder pack content
|
|
12
|
-
* @param {number} iteration - Current iteration number
|
|
13
|
-
* @returns {string} - Fix prompt for Claude Code
|
|
14
|
-
*/
|
|
15
|
-
export function generateFixPrompt(errors, originalPack, iteration = 1) {
|
|
16
|
-
const summary = createErrorSummary(errors);
|
|
17
|
-
|
|
18
|
-
const sections = [
|
|
19
|
-
'# 🔧 FIX REQUIRED - Iteration ' + iteration,
|
|
20
|
-
'',
|
|
21
|
-
`The previous build had **${errors.length} error(s)**. Please fix them.`,
|
|
22
|
-
'',
|
|
23
|
-
'---',
|
|
24
|
-
'',
|
|
25
|
-
'## 📋 Error Summary',
|
|
26
|
-
'',
|
|
27
|
-
`- **Total Errors:** ${summary.total}`,
|
|
28
|
-
`- **Critical:** ${summary.byPriority.critical}`,
|
|
29
|
-
`- **High:** ${summary.byPriority.high}`,
|
|
30
|
-
`- **Medium:** ${summary.byPriority.medium}`,
|
|
31
|
-
`- **Low:** ${summary.byPriority.low}`,
|
|
32
|
-
'',
|
|
33
|
-
`**Affected Files:** ${summary.files.length > 0 ? summary.files.join(', ') : 'Unknown'}`,
|
|
34
|
-
'',
|
|
35
|
-
'---',
|
|
36
|
-
'',
|
|
37
|
-
'## 🚨 Errors to Fix',
|
|
38
|
-
'',
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
// Add detailed errors grouped by priority
|
|
42
|
-
const byPriority = groupByPriority(errors);
|
|
43
|
-
|
|
44
|
-
for (const [priority, priorityErrors] of Object.entries(byPriority)) {
|
|
45
|
-
if (priorityErrors.length === 0) continue;
|
|
46
|
-
|
|
47
|
-
const emoji = priority === 'critical' ? '🔴' :
|
|
48
|
-
priority === 'high' ? '🟠' :
|
|
49
|
-
priority === 'medium' ? '🟡' : '🟢';
|
|
50
|
-
|
|
51
|
-
sections.push(`### ${emoji} ${priority.toUpperCase()} Priority`);
|
|
52
|
-
sections.push('');
|
|
53
|
-
|
|
54
|
-
for (const error of priorityErrors) {
|
|
55
|
-
const location = error.file
|
|
56
|
-
? `\`${error.file}${error.line ? ':' + error.line : ''}\``
|
|
57
|
-
: 'Unknown location';
|
|
58
|
-
|
|
59
|
-
sections.push(`**${error.type}** at ${location}`);
|
|
60
|
-
sections.push(`- Message: ${error.message}`);
|
|
61
|
-
sections.push(`- Suggestion: ${error.suggestion}`);
|
|
62
|
-
if (error.raw && error.raw !== error.message) {
|
|
63
|
-
sections.push(`- Raw output: \`${truncate(error.raw, 200)}\``);
|
|
64
|
-
}
|
|
65
|
-
sections.push('');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
sections.push('---');
|
|
70
|
-
sections.push('');
|
|
71
|
-
sections.push('## 📝 Original Task Reference');
|
|
72
|
-
sections.push('');
|
|
73
|
-
sections.push('<details>');
|
|
74
|
-
sections.push('<summary>Click to expand original task</summary>');
|
|
75
|
-
sections.push('');
|
|
76
|
-
sections.push(originalPack);
|
|
77
|
-
sections.push('');
|
|
78
|
-
sections.push('</details>');
|
|
79
|
-
sections.push('');
|
|
80
|
-
sections.push('---');
|
|
81
|
-
sections.push('');
|
|
82
|
-
sections.push('## ⚡ Fix Instructions');
|
|
83
|
-
sections.push('');
|
|
84
|
-
sections.push('1. **Fix ONLY the errors listed above** - Do not refactor or change working code');
|
|
85
|
-
sections.push('2. **Start with CRITICAL errors** - They likely cause cascading failures');
|
|
86
|
-
sections.push('3. **Run tests after each fix** - Verify the error is resolved');
|
|
87
|
-
sections.push('4. **Keep changes minimal** - Focus on the specific issue');
|
|
88
|
-
sections.push('');
|
|
89
|
-
sections.push('When all errors are fixed, the build will be validated again.');
|
|
90
|
-
sections.push('');
|
|
91
|
-
|
|
92
|
-
return sections.join('\n');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Generate a minimal fix prompt for single error
|
|
97
|
-
*/
|
|
98
|
-
export function generateSingleFixPrompt(error) {
|
|
99
|
-
const location = error.file
|
|
100
|
-
? `${error.file}${error.line ? ':' + error.line : ''}`
|
|
101
|
-
: 'unknown location';
|
|
102
|
-
|
|
103
|
-
return `# Fix Required
|
|
104
|
-
|
|
105
|
-
**Error Type:** ${error.type}
|
|
106
|
-
**Location:** ${location}
|
|
107
|
-
**Message:** ${error.message}
|
|
108
|
-
|
|
109
|
-
**Suggestion:** ${error.suggestion}
|
|
110
|
-
|
|
111
|
-
Please fix this specific error. Keep the change minimal and focused.`;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Generate iteration context for logging
|
|
116
|
-
*/
|
|
117
|
-
export function generateIterationContext(iteration, errors, duration) {
|
|
118
|
-
return {
|
|
119
|
-
iteration,
|
|
120
|
-
timestamp: new Date().toISOString(),
|
|
121
|
-
errorCount: errors.length,
|
|
122
|
-
errorTypes: [...new Set(errors.map(e => e.type))],
|
|
123
|
-
affectedFiles: [...new Set(errors.filter(e => e.file).map(e => e.file))],
|
|
124
|
-
duration
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Group errors by priority
|
|
130
|
-
*/
|
|
131
|
-
function groupByPriority(errors) {
|
|
132
|
-
const grouped = {
|
|
133
|
-
critical: [],
|
|
134
|
-
high: [],
|
|
135
|
-
medium: [],
|
|
136
|
-
low: []
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
for (const error of errors) {
|
|
140
|
-
const priority = error.priority || 'medium';
|
|
141
|
-
if (grouped[priority]) {
|
|
142
|
-
grouped[priority].push(error);
|
|
143
|
-
} else {
|
|
144
|
-
grouped.medium.push(error);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return grouped;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Truncate string with ellipsis
|
|
153
|
-
*/
|
|
154
|
-
function truncate(str, maxLen) {
|
|
155
|
-
if (!str) return '';
|
|
156
|
-
if (str.length <= maxLen) return str;
|
|
157
|
-
return str.substring(0, maxLen - 3) + '...';
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Check if errors are fixable (not system/config errors)
|
|
162
|
-
*/
|
|
163
|
-
export function areErrorsFixable(errors) {
|
|
164
|
-
// If all errors are unknown type with no file info, might be config issue
|
|
165
|
-
const unknownWithoutFile = errors.filter(e => e.type === 'unknown' && !e.file);
|
|
166
|
-
|
|
167
|
-
if (unknownWithoutFile.length === errors.length) {
|
|
168
|
-
return {
|
|
169
|
-
fixable: false,
|
|
170
|
-
reason: 'All errors are unstructured with no file information. This may indicate a configuration or environment issue.'
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return { fixable: true };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Estimate fix complexity
|
|
179
|
-
*/
|
|
180
|
-
export function estimateFixComplexity(errors) {
|
|
181
|
-
let score = 0;
|
|
182
|
-
|
|
183
|
-
for (const error of errors) {
|
|
184
|
-
switch (error.priority) {
|
|
185
|
-
case 'critical': score += 3; break;
|
|
186
|
-
case 'high': score += 2; break;
|
|
187
|
-
case 'medium': score += 1; break;
|
|
188
|
-
case 'low': score += 0.5; break;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (score <= 3) return 'simple';
|
|
193
|
-
if (score <= 8) return 'moderate';
|
|
194
|
-
return 'complex';
|
|
195
|
-
}
|
package/src/core/iteration.js
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
-
// VIBECODE CLI - Iteration Tracker
|
|
3
|
-
// Manage and track build-test-fix iterations
|
|
4
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { ensureDir, writeJson, readJson, pathExists, appendToFile } from '../utils/files.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {Object} IterationRecord
|
|
11
|
-
* @property {number} iteration - Iteration number
|
|
12
|
-
* @property {string} timestamp - ISO timestamp
|
|
13
|
-
* @property {boolean} passed - Whether tests passed
|
|
14
|
-
* @property {number} errorCount - Number of errors
|
|
15
|
-
* @property {string[]} errorTypes - Types of errors found
|
|
16
|
-
* @property {string[]} affectedFiles - Files with errors
|
|
17
|
-
* @property {number} duration - Duration in ms
|
|
18
|
-
* @property {string} action - What action was taken (build/fix)
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {Object} IterationState
|
|
23
|
-
* @property {string} sessionId - Session identifier
|
|
24
|
-
* @property {string} startTime - When iteration started
|
|
25
|
-
* @property {number} currentIteration - Current iteration number
|
|
26
|
-
* @property {number} maxIterations - Max allowed iterations
|
|
27
|
-
* @property {IterationRecord[]} history - History of iterations
|
|
28
|
-
* @property {boolean} completed - Whether iteration loop completed
|
|
29
|
-
* @property {string} result - Final result (success/max_reached/error)
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create new iteration state
|
|
34
|
-
* @param {string} sessionId - Session identifier
|
|
35
|
-
* @param {number} maxIterations - Maximum iterations allowed
|
|
36
|
-
* @returns {IterationState}
|
|
37
|
-
*/
|
|
38
|
-
export function createIterationState(sessionId, maxIterations = 3) {
|
|
39
|
-
return {
|
|
40
|
-
sessionId,
|
|
41
|
-
startTime: new Date().toISOString(),
|
|
42
|
-
currentIteration: 0,
|
|
43
|
-
maxIterations,
|
|
44
|
-
history: [],
|
|
45
|
-
completed: false,
|
|
46
|
-
result: null
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Record an iteration result
|
|
52
|
-
* @param {IterationState} state - Current iteration state
|
|
53
|
-
* @param {Object} result - Iteration result
|
|
54
|
-
* @returns {IterationState} - Updated state
|
|
55
|
-
*/
|
|
56
|
-
export function recordIteration(state, result) {
|
|
57
|
-
const record = {
|
|
58
|
-
iteration: state.currentIteration + 1,
|
|
59
|
-
timestamp: new Date().toISOString(),
|
|
60
|
-
passed: result.passed,
|
|
61
|
-
errorCount: result.errorCount || 0,
|
|
62
|
-
errorTypes: result.errorTypes || [],
|
|
63
|
-
affectedFiles: result.affectedFiles || [],
|
|
64
|
-
duration: result.duration || 0,
|
|
65
|
-
action: result.action || 'build'
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
...state,
|
|
70
|
-
currentIteration: state.currentIteration + 1,
|
|
71
|
-
history: [...state.history, record]
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Check if can continue iterating
|
|
77
|
-
* @param {IterationState} state - Current state
|
|
78
|
-
* @returns {{canContinue: boolean, reason: string}}
|
|
79
|
-
*/
|
|
80
|
-
export function canContinue(state) {
|
|
81
|
-
if (state.completed) {
|
|
82
|
-
return { canContinue: false, reason: 'Iteration already completed' };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (state.currentIteration >= state.maxIterations) {
|
|
86
|
-
return { canContinue: false, reason: `Max iterations (${state.maxIterations}) reached` };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Check if last iteration passed
|
|
90
|
-
const lastRecord = state.history[state.history.length - 1];
|
|
91
|
-
if (lastRecord && lastRecord.passed) {
|
|
92
|
-
return { canContinue: false, reason: 'Tests passed - no more iterations needed' };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Check for stuck loop (same errors repeated 3 times)
|
|
96
|
-
if (state.history.length >= 3) {
|
|
97
|
-
const last3 = state.history.slice(-3);
|
|
98
|
-
const errorCounts = last3.map(r => r.errorCount);
|
|
99
|
-
if (errorCounts.every(c => c === errorCounts[0]) && errorCounts[0] > 0) {
|
|
100
|
-
return { canContinue: false, reason: 'Stuck in loop - same error count for 3 iterations' };
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return { canContinue: true, reason: '' };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Finalize iteration state
|
|
109
|
-
* @param {IterationState} state - Current state
|
|
110
|
-
* @param {string} result - Result type (success/max_reached/error/stuck)
|
|
111
|
-
* @returns {IterationState}
|
|
112
|
-
*/
|
|
113
|
-
export function finalizeIterationState(state, result) {
|
|
114
|
-
return {
|
|
115
|
-
...state,
|
|
116
|
-
completed: true,
|
|
117
|
-
result,
|
|
118
|
-
endTime: new Date().toISOString(),
|
|
119
|
-
totalDuration: state.history.reduce((sum, r) => sum + r.duration, 0)
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Save iteration state to session directory
|
|
125
|
-
* @param {string} sessionDir - Session directory path
|
|
126
|
-
* @param {IterationState} state - Iteration state
|
|
127
|
-
*/
|
|
128
|
-
export async function saveIterationState(sessionDir, state) {
|
|
129
|
-
const iterationDir = path.join(sessionDir, 'iterations');
|
|
130
|
-
await ensureDir(iterationDir);
|
|
131
|
-
|
|
132
|
-
const stateFile = path.join(iterationDir, 'state.json');
|
|
133
|
-
await writeJson(stateFile, state, { spaces: 2 });
|
|
134
|
-
|
|
135
|
-
// Also write individual iteration files for evidence
|
|
136
|
-
for (const record of state.history) {
|
|
137
|
-
const recordFile = path.join(iterationDir, `iteration-${record.iteration}.json`);
|
|
138
|
-
if (!await pathExists(recordFile)) {
|
|
139
|
-
await writeJson(recordFile, record, { spaces: 2 });
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Load iteration state from session directory
|
|
146
|
-
* @param {string} sessionDir - Session directory path
|
|
147
|
-
* @returns {Promise<IterationState|null>}
|
|
148
|
-
*/
|
|
149
|
-
export async function loadIterationState(sessionDir) {
|
|
150
|
-
const stateFile = path.join(sessionDir, 'iterations', 'state.json');
|
|
151
|
-
if (await pathExists(stateFile)) {
|
|
152
|
-
return await readJson(stateFile);
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Format iteration summary for display
|
|
159
|
-
* @param {IterationState} state - Iteration state
|
|
160
|
-
* @returns {string}
|
|
161
|
-
*/
|
|
162
|
-
export function formatIterationSummary(state) {
|
|
163
|
-
const lines = [];
|
|
164
|
-
|
|
165
|
-
lines.push(`Iteration Summary (${state.sessionId})`);
|
|
166
|
-
lines.push('═'.repeat(50));
|
|
167
|
-
lines.push(`Total Iterations: ${state.currentIteration}/${state.maxIterations}`);
|
|
168
|
-
lines.push(`Result: ${state.result || 'In Progress'}`);
|
|
169
|
-
lines.push('');
|
|
170
|
-
|
|
171
|
-
if (state.history.length > 0) {
|
|
172
|
-
lines.push('History:');
|
|
173
|
-
for (const record of state.history) {
|
|
174
|
-
const status = record.passed ? '✅' : '❌';
|
|
175
|
-
const errors = record.errorCount > 0 ? ` (${record.errorCount} errors)` : '';
|
|
176
|
-
lines.push(` ${record.iteration}. ${status} ${record.action}${errors} - ${formatDuration(record.duration)}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (state.totalDuration) {
|
|
181
|
-
lines.push('');
|
|
182
|
-
lines.push(`Total Duration: ${formatDuration(state.totalDuration)}`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return lines.join('\n');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Format duration in human readable format
|
|
190
|
-
*/
|
|
191
|
-
function formatDuration(ms) {
|
|
192
|
-
if (ms < 1000) return `${ms}ms`;
|
|
193
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
194
|
-
return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Write iteration log entry
|
|
199
|
-
*/
|
|
200
|
-
export async function logIteration(logPath, iteration, message) {
|
|
201
|
-
const timestamp = new Date().toISOString();
|
|
202
|
-
const entry = `[${timestamp}] [Iteration ${iteration}] ${message}\n`;
|
|
203
|
-
await appendToFile(logPath, entry);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Get progress percentage
|
|
208
|
-
*/
|
|
209
|
-
export function getProgressPercent(state) {
|
|
210
|
-
if (state.completed && state.result === 'success') {
|
|
211
|
-
return 100;
|
|
212
|
-
}
|
|
213
|
-
return Math.round((state.currentIteration / state.maxIterations) * 100);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Check if errors are improving (decreasing)
|
|
218
|
-
*/
|
|
219
|
-
export function isImproving(state) {
|
|
220
|
-
if (state.history.length < 2) return true;
|
|
221
|
-
|
|
222
|
-
const last = state.history[state.history.length - 1];
|
|
223
|
-
const prev = state.history[state.history.length - 2];
|
|
224
|
-
|
|
225
|
-
return last.errorCount < prev.errorCount;
|
|
226
|
-
}
|
package/src/core/test-runner.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
-
// VIBECODE CLI - Test Runner
|
|
3
|
-
// Automated test execution for iterative builds
|
|
4
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
|
|
6
|
-
import { exec } from 'child_process';
|
|
7
|
-
import { promisify } from 'util';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import { pathExists, readJson } from '../utils/files.js';
|
|
10
|
-
|
|
11
|
-
const execAsync = promisify(exec);
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Run all available tests for a project
|
|
15
|
-
* @param {string} projectPath - Path to the project
|
|
16
|
-
* @returns {Promise<TestResults>}
|
|
17
|
-
*/
|
|
18
|
-
export async function runTests(projectPath) {
|
|
19
|
-
const results = {
|
|
20
|
-
passed: true,
|
|
21
|
-
tests: [],
|
|
22
|
-
errors: [],
|
|
23
|
-
summary: {
|
|
24
|
-
total: 0,
|
|
25
|
-
passed: 0,
|
|
26
|
-
failed: 0
|
|
27
|
-
},
|
|
28
|
-
duration: 0
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const startTime = Date.now();
|
|
32
|
-
|
|
33
|
-
// 1. Check if package.json exists
|
|
34
|
-
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
35
|
-
const hasPackageJson = await pathExists(packageJsonPath);
|
|
36
|
-
|
|
37
|
-
if (hasPackageJson) {
|
|
38
|
-
const pkg = await readJson(packageJsonPath);
|
|
39
|
-
|
|
40
|
-
// 2. Run npm test (if script exists)
|
|
41
|
-
if (pkg.scripts?.test && !pkg.scripts.test.includes('no test specified')) {
|
|
42
|
-
const npmTest = await runCommand('npm test', projectPath, 'npm test');
|
|
43
|
-
results.tests.push(npmTest);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 3. Run npm run lint (if script exists)
|
|
47
|
-
if (pkg.scripts?.lint) {
|
|
48
|
-
const npmLint = await runCommand('npm run lint', projectPath, 'npm lint');
|
|
49
|
-
results.tests.push(npmLint);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 4. Run npm run build (if script exists) - check for build errors
|
|
53
|
-
if (pkg.scripts?.build) {
|
|
54
|
-
const npmBuild = await runCommand('npm run build', projectPath, 'npm build');
|
|
55
|
-
results.tests.push(npmBuild);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 5. Run TypeScript check if tsconfig exists
|
|
59
|
-
const tsconfigPath = path.join(projectPath, 'tsconfig.json');
|
|
60
|
-
if (await pathExists(tsconfigPath)) {
|
|
61
|
-
const tscCheck = await runCommand('npx tsc --noEmit', projectPath, 'typescript');
|
|
62
|
-
results.tests.push(tscCheck);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 6. Check for syntax errors in JS files
|
|
67
|
-
const syntaxCheck = await checkJsSyntax(projectPath);
|
|
68
|
-
if (syntaxCheck.ran) {
|
|
69
|
-
results.tests.push(syntaxCheck);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 7. Aggregate results
|
|
73
|
-
results.summary.total = results.tests.length;
|
|
74
|
-
results.summary.passed = results.tests.filter(t => t.passed).length;
|
|
75
|
-
results.summary.failed = results.tests.filter(t => !t.passed).length;
|
|
76
|
-
results.passed = results.tests.length === 0 || results.tests.every(t => t.passed);
|
|
77
|
-
results.errors = results.tests.filter(t => !t.passed).flatMap(t => t.errors || []);
|
|
78
|
-
results.duration = Date.now() - startTime;
|
|
79
|
-
|
|
80
|
-
return results;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Run a single command and capture results
|
|
85
|
-
*/
|
|
86
|
-
async function runCommand(command, cwd, name) {
|
|
87
|
-
const result = {
|
|
88
|
-
name,
|
|
89
|
-
command,
|
|
90
|
-
passed: false,
|
|
91
|
-
ran: true,
|
|
92
|
-
output: '',
|
|
93
|
-
errors: [],
|
|
94
|
-
duration: 0
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const startTime = Date.now();
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const { stdout, stderr } = await execAsync(command, {
|
|
101
|
-
cwd,
|
|
102
|
-
timeout: 120000, // 2 minute timeout
|
|
103
|
-
maxBuffer: 10 * 1024 * 1024
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
result.passed = true;
|
|
107
|
-
result.output = stdout + stderr;
|
|
108
|
-
result.duration = Date.now() - startTime;
|
|
109
|
-
|
|
110
|
-
} catch (error) {
|
|
111
|
-
result.passed = false;
|
|
112
|
-
result.output = error.stdout || '';
|
|
113
|
-
result.error = error.stderr || error.message;
|
|
114
|
-
result.exitCode = error.code;
|
|
115
|
-
result.duration = Date.now() - startTime;
|
|
116
|
-
|
|
117
|
-
// Parse errors from output
|
|
118
|
-
result.errors = parseErrors(error.stderr || error.stdout || error.message, name);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Check JavaScript syntax errors
|
|
126
|
-
*/
|
|
127
|
-
async function checkJsSyntax(projectPath) {
|
|
128
|
-
const result = {
|
|
129
|
-
name: 'syntax-check',
|
|
130
|
-
passed: true,
|
|
131
|
-
ran: false,
|
|
132
|
-
errors: []
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
// Find JS/TS files (limited to src/ to avoid node_modules)
|
|
137
|
-
const { stdout } = await execAsync(
|
|
138
|
-
'find src -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" 2>/dev/null | head -20',
|
|
139
|
-
{ cwd: projectPath }
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const files = stdout.trim().split('\n').filter(f => f);
|
|
143
|
-
if (files.length === 0) return result;
|
|
144
|
-
|
|
145
|
-
result.ran = true;
|
|
146
|
-
|
|
147
|
-
// Check each file for syntax errors using node --check
|
|
148
|
-
for (const file of files) {
|
|
149
|
-
if (file.endsWith('.ts') || file.endsWith('.tsx')) continue; // Skip TS files
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
await execAsync(`node --check "${file}"`, { cwd: projectPath });
|
|
153
|
-
} catch (error) {
|
|
154
|
-
result.passed = false;
|
|
155
|
-
result.errors.push({
|
|
156
|
-
file,
|
|
157
|
-
message: error.message,
|
|
158
|
-
type: 'syntax'
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
} catch (error) {
|
|
163
|
-
// find command failed, skip syntax check
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return result;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Parse error messages into structured format
|
|
171
|
-
*/
|
|
172
|
-
function parseErrors(errorOutput, source) {
|
|
173
|
-
const errors = [];
|
|
174
|
-
const lines = errorOutput.split('\n');
|
|
175
|
-
|
|
176
|
-
for (const line of lines) {
|
|
177
|
-
// Match common error patterns
|
|
178
|
-
// Pattern: file.js:10:5: error message
|
|
179
|
-
const fileLineMatch = line.match(/([^\s:]+):(\d+):(\d+)?:?\s*(.+)/);
|
|
180
|
-
if (fileLineMatch) {
|
|
181
|
-
errors.push({
|
|
182
|
-
source,
|
|
183
|
-
file: fileLineMatch[1],
|
|
184
|
-
line: parseInt(fileLineMatch[2]),
|
|
185
|
-
column: fileLineMatch[3] ? parseInt(fileLineMatch[3]) : null,
|
|
186
|
-
message: fileLineMatch[4].trim(),
|
|
187
|
-
raw: line
|
|
188
|
-
});
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Pattern: Error: message
|
|
193
|
-
const errorMatch = line.match(/^(Error|TypeError|SyntaxError|ReferenceError):\s*(.+)/);
|
|
194
|
-
if (errorMatch) {
|
|
195
|
-
errors.push({
|
|
196
|
-
source,
|
|
197
|
-
type: errorMatch[1],
|
|
198
|
-
message: errorMatch[2].trim(),
|
|
199
|
-
raw: line
|
|
200
|
-
});
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Pattern: ✖ or ✗ or FAIL
|
|
205
|
-
if (line.includes('✖') || line.includes('✗') || line.includes('FAIL')) {
|
|
206
|
-
errors.push({
|
|
207
|
-
source,
|
|
208
|
-
message: line.trim(),
|
|
209
|
-
raw: line
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// If no structured errors found, add the whole output
|
|
215
|
-
if (errors.length === 0 && errorOutput.trim()) {
|
|
216
|
-
errors.push({
|
|
217
|
-
source,
|
|
218
|
-
message: errorOutput.substring(0, 500),
|
|
219
|
-
raw: errorOutput
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return errors;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Format test results for display
|
|
228
|
-
*/
|
|
229
|
-
export function formatTestResults(results) {
|
|
230
|
-
const lines = [];
|
|
231
|
-
|
|
232
|
-
lines.push(`Tests: ${results.summary.passed}/${results.summary.total} passed`);
|
|
233
|
-
lines.push(`Duration: ${(results.duration / 1000).toFixed(1)}s`);
|
|
234
|
-
|
|
235
|
-
if (!results.passed) {
|
|
236
|
-
lines.push('');
|
|
237
|
-
lines.push('Failed tests:');
|
|
238
|
-
for (const test of results.tests.filter(t => !t.passed)) {
|
|
239
|
-
lines.push(` ❌ ${test.name}`);
|
|
240
|
-
for (const error of test.errors || []) {
|
|
241
|
-
const loc = error.file ? `${error.file}:${error.line || '?'}` : '';
|
|
242
|
-
lines.push(` ${loc} ${error.message?.substring(0, 80) || ''}`);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return lines.join('\n');
|
|
248
|
-
}
|