@nclamvn/vibecode-cli 1.1.0 → 1.2.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 +17 -0
- package/package.json +1 -1
- package/src/commands/build.js +495 -3
- package/src/commands/config.js +149 -0
- package/src/config/constants.js +1 -1
- package/src/core/error-analyzer.js +237 -0
- package/src/core/fix-generator.js +195 -0
- package/src/core/iteration.js +226 -0
- package/src/core/test-runner.js +248 -0
- package/src/index.js +7 -0
- package/src/providers/claude-code.js +159 -0
- package/src/providers/index.js +45 -0
package/bin/vibecode.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
buildCommand,
|
|
17
17
|
reviewCommand,
|
|
18
18
|
snapshotCommand,
|
|
19
|
+
configCommand,
|
|
19
20
|
VERSION
|
|
20
21
|
} from '../src/index.js';
|
|
21
22
|
|
|
@@ -77,6 +78,11 @@ program
|
|
|
77
78
|
.option('-s, --start', 'Start the build process')
|
|
78
79
|
.option('-c, --complete', 'Mark build as complete')
|
|
79
80
|
.option('-e, --evidence', 'Capture evidence snapshot')
|
|
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
|
+
.option('--provider <name>', 'Provider to use: claude-code', 'claude-code')
|
|
80
86
|
.action(buildCommand);
|
|
81
87
|
|
|
82
88
|
program
|
|
@@ -93,6 +99,17 @@ program
|
|
|
93
99
|
.option('--major', 'Major version bump')
|
|
94
100
|
.action(snapshotCommand);
|
|
95
101
|
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
// Phase C Commands - AI Integration
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command('config')
|
|
108
|
+
.description('Manage Vibecode configuration')
|
|
109
|
+
.option('--show', 'Show current configuration')
|
|
110
|
+
.option('--provider <name>', 'Set default AI provider')
|
|
111
|
+
.action(configCommand);
|
|
112
|
+
|
|
96
113
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
114
|
// Parse
|
|
98
115
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
package/src/commands/build.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
2
|
// VIBECODE CLI - Build Command
|
|
3
|
+
// "Claude/LLM là PIPELINE, là KIẾN TRÚC SƯ"
|
|
3
4
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
5
|
|
|
5
6
|
import chalk from 'chalk';
|
|
@@ -12,14 +13,34 @@ import {
|
|
|
12
13
|
getCurrentSessionId,
|
|
13
14
|
getCurrentSessionPath,
|
|
14
15
|
writeSessionFile,
|
|
15
|
-
sessionFileExists
|
|
16
|
+
sessionFileExists,
|
|
17
|
+
readSessionFile
|
|
16
18
|
} from '../core/session.js';
|
|
17
19
|
import { getCurrentState, transitionTo } from '../core/state-machine.js';
|
|
18
20
|
import { getSpecHash } from '../core/contract.js';
|
|
19
21
|
import { STATES } from '../config/constants.js';
|
|
20
22
|
import { getBuildReportTemplate } from '../config/templates.js';
|
|
21
|
-
import { ensureDir, pathExists, appendToFile } from '../utils/files.js';
|
|
23
|
+
import { ensureDir, pathExists, appendToFile, readMarkdown, writeJson } from '../utils/files.js';
|
|
22
24
|
import { printBox, printError, printSuccess, printWarning, printNextStep } from '../ui/output.js';
|
|
25
|
+
import {
|
|
26
|
+
spawnClaudeCode,
|
|
27
|
+
isClaudeCodeAvailable,
|
|
28
|
+
buildPromptWithContext,
|
|
29
|
+
getProviderInfo
|
|
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';
|
|
23
44
|
|
|
24
45
|
const execAsync = promisify(exec);
|
|
25
46
|
|
|
@@ -38,7 +59,12 @@ export async function buildCommand(options = {}) {
|
|
|
38
59
|
const specHash = await getSpecHash();
|
|
39
60
|
|
|
40
61
|
// Handle different build modes
|
|
41
|
-
if (options.
|
|
62
|
+
if (options.iterate) {
|
|
63
|
+
// Phase D: Iterative Build
|
|
64
|
+
await handleIterativeBuild(currentState, projectName, sessionId, sessionPath, specHash, options);
|
|
65
|
+
} else if (options.auto) {
|
|
66
|
+
await handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options);
|
|
67
|
+
} else if (options.start) {
|
|
42
68
|
await handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash);
|
|
43
69
|
} else if (options.complete) {
|
|
44
70
|
await handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash);
|
|
@@ -54,6 +80,472 @@ export async function buildCommand(options = {}) {
|
|
|
54
80
|
}
|
|
55
81
|
}
|
|
56
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Handle --auto mode: Spawn Claude Code with optimal config
|
|
85
|
+
* "Contract LOCKED = License to build"
|
|
86
|
+
*/
|
|
87
|
+
async function handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options) {
|
|
88
|
+
// Check state - must be PLAN_CREATED or BUILD_IN_PROGRESS or REVIEW_FAILED
|
|
89
|
+
const validStates = [STATES.PLAN_CREATED, STATES.BUILD_IN_PROGRESS, STATES.REVIEW_FAILED];
|
|
90
|
+
if (!validStates.includes(currentState)) {
|
|
91
|
+
printError(`Cannot auto-build in state: ${currentState}`);
|
|
92
|
+
console.log('Run `vibecode plan` first to create execution plan.');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if Claude Code is available
|
|
97
|
+
const available = await isClaudeCodeAvailable();
|
|
98
|
+
if (!available) {
|
|
99
|
+
printError('Claude Code CLI not found.');
|
|
100
|
+
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
101
|
+
console.log(chalk.gray('Or use manual build: vibecode build --start'));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check coder_pack.md exists
|
|
106
|
+
if (!await sessionFileExists('coder_pack.md')) {
|
|
107
|
+
printError('coder_pack.md not found. Run `vibecode plan` first.');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Setup evidence directory
|
|
112
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
113
|
+
await ensureDir(evidencePath);
|
|
114
|
+
await ensureDir(path.join(evidencePath, 'screenshots'));
|
|
115
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
116
|
+
|
|
117
|
+
// Save build start time
|
|
118
|
+
const stateData = await loadState();
|
|
119
|
+
const startTime = new Date().toISOString();
|
|
120
|
+
stateData.build_started = startTime;
|
|
121
|
+
stateData.build_provider = 'claude-code';
|
|
122
|
+
stateData.build_mode = 'auto';
|
|
123
|
+
await saveState(stateData);
|
|
124
|
+
|
|
125
|
+
// Transition to BUILD_IN_PROGRESS if not already
|
|
126
|
+
if (currentState !== STATES.BUILD_IN_PROGRESS) {
|
|
127
|
+
await transitionTo(STATES.BUILD_IN_PROGRESS, 'auto_build_started');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Load coder pack
|
|
131
|
+
const coderPackContent = await readSessionFile('coder_pack.md');
|
|
132
|
+
|
|
133
|
+
// Build full prompt with CLAUDE.md injection if exists
|
|
134
|
+
const fullPrompt = await buildPromptWithContext(coderPackContent, process.cwd());
|
|
135
|
+
|
|
136
|
+
// Log start
|
|
137
|
+
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
138
|
+
await appendToFile(logPath, `AUTO BUILD STARTED: ${startTime}\n`);
|
|
139
|
+
await appendToFile(logPath, `Provider: Claude Code (--dangerously-skip-permissions)\n`);
|
|
140
|
+
await appendToFile(logPath, `${'='.repeat(60)}\n\n`);
|
|
141
|
+
|
|
142
|
+
// Show starting message
|
|
143
|
+
const providerInfo = getProviderInfo();
|
|
144
|
+
|
|
145
|
+
const content = `🤖 AUTO BUILD
|
|
146
|
+
|
|
147
|
+
Project: ${projectName}
|
|
148
|
+
Session: ${sessionId}
|
|
149
|
+
Spec Hash: ${specHash}
|
|
150
|
+
|
|
151
|
+
Provider: ${providerInfo.name}
|
|
152
|
+
Mode: ${providerInfo.mode}
|
|
153
|
+
|
|
154
|
+
Starting AI build session...
|
|
155
|
+
Contract LOCKED = License to build`;
|
|
156
|
+
|
|
157
|
+
console.log();
|
|
158
|
+
printBox(content, { borderColor: 'magenta' });
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
161
|
+
console.log(chalk.magenta('│ CLAUDE CODE OUTPUT'));
|
|
162
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
163
|
+
console.log();
|
|
164
|
+
|
|
165
|
+
// Spawn Claude Code
|
|
166
|
+
try {
|
|
167
|
+
const result = await spawnClaudeCode(fullPrompt, {
|
|
168
|
+
cwd: process.cwd(),
|
|
169
|
+
logPath: logPath
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log();
|
|
173
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
174
|
+
console.log();
|
|
175
|
+
|
|
176
|
+
// Capture evidence
|
|
177
|
+
await captureGitDiff(evidencePath);
|
|
178
|
+
|
|
179
|
+
const endTime = new Date().toISOString();
|
|
180
|
+
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
181
|
+
await appendToFile(logPath, `AUTO BUILD COMPLETED: ${endTime}\n`);
|
|
182
|
+
await appendToFile(logPath, `Exit code: ${result.code}\n`);
|
|
183
|
+
await appendToFile(logPath, `${'='.repeat(60)}\n`);
|
|
184
|
+
|
|
185
|
+
// Check evidence
|
|
186
|
+
const evidence = await checkEvidence(evidencePath);
|
|
187
|
+
|
|
188
|
+
// Generate build report
|
|
189
|
+
const reportContent = getBuildReportTemplate(
|
|
190
|
+
projectName,
|
|
191
|
+
sessionId,
|
|
192
|
+
specHash,
|
|
193
|
+
startTime,
|
|
194
|
+
endTime,
|
|
195
|
+
evidence
|
|
196
|
+
);
|
|
197
|
+
await writeSessionFile('build_report.md', reportContent);
|
|
198
|
+
|
|
199
|
+
// Update state
|
|
200
|
+
stateData.build_completed = endTime;
|
|
201
|
+
await saveState(stateData);
|
|
202
|
+
|
|
203
|
+
if (result.success) {
|
|
204
|
+
await transitionTo(STATES.BUILD_DONE, 'auto_build_completed');
|
|
205
|
+
|
|
206
|
+
const duration = Math.round((new Date(endTime) - new Date(startTime)) / 1000 / 60);
|
|
207
|
+
|
|
208
|
+
const successContent = `✅ AUTO BUILD COMPLETED
|
|
209
|
+
|
|
210
|
+
Project: ${projectName}
|
|
211
|
+
Duration: ${duration} minutes
|
|
212
|
+
Provider: Claude Code
|
|
213
|
+
|
|
214
|
+
Evidence:
|
|
215
|
+
${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
|
|
216
|
+
${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
|
|
217
|
+
${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
|
|
218
|
+
|
|
219
|
+
printBox(successContent, { borderColor: 'green' });
|
|
220
|
+
printNextStep('Run `vibecode review` to validate your build');
|
|
221
|
+
|
|
222
|
+
} else {
|
|
223
|
+
printWarning(`Claude Code exited with code: ${result.code}`);
|
|
224
|
+
console.log(chalk.gray('Check build.log for details.'));
|
|
225
|
+
console.log(chalk.gray('Run `vibecode build --auto` to retry.'));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
} catch (error) {
|
|
229
|
+
await appendToFile(logPath, `\nERROR: ${error.message}\n`);
|
|
230
|
+
printError(`Auto build failed: ${error.message}`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
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
|
+
|
|
57
549
|
async function handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash) {
|
|
58
550
|
const spinner = ora('Starting build...').start();
|
|
59
551
|
|