@nclamvn/vibecode-cli 1.0.0 → 1.0.1
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 +50 -1
- package/package.json +1 -1
- package/src/commands/build.js +470 -0
- package/src/commands/config.js +149 -0
- package/src/commands/plan.js +105 -0
- package/src/commands/review.js +290 -0
- package/src/commands/snapshot.js +252 -0
- package/src/commands/status.js +13 -2
- package/src/config/constants.js +26 -16
- package/src/config/templates.js +272 -1
- package/src/core/session.js +8 -1
- package/src/index.js +14 -0
- package/src/providers/claude-code.js +159 -0
- package/src/providers/index.js +45 -0
- package/src/ui/output.js +3 -3
package/bin/vibecode.js
CHANGED
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
statusCommand,
|
|
13
13
|
lockCommand,
|
|
14
14
|
doctorCommand,
|
|
15
|
+
planCommand,
|
|
16
|
+
buildCommand,
|
|
17
|
+
reviewCommand,
|
|
18
|
+
snapshotCommand,
|
|
19
|
+
configCommand,
|
|
15
20
|
VERSION
|
|
16
21
|
} from '../src/index.js';
|
|
17
22
|
|
|
@@ -23,7 +28,7 @@ program
|
|
|
23
28
|
.version(VERSION);
|
|
24
29
|
|
|
25
30
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
-
// Commands
|
|
31
|
+
// Phase A Commands - Planning
|
|
27
32
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
33
|
|
|
29
34
|
program
|
|
@@ -58,6 +63,50 @@ program
|
|
|
58
63
|
.description('Check configuration and diagnose issues')
|
|
59
64
|
.action(doctorCommand);
|
|
60
65
|
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
// Phase B Commands - Execution
|
|
68
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
program
|
|
71
|
+
.command('plan')
|
|
72
|
+
.description('Create execution plan from locked contract')
|
|
73
|
+
.action(planCommand);
|
|
74
|
+
|
|
75
|
+
program
|
|
76
|
+
.command('build')
|
|
77
|
+
.description('Manage build process and capture evidence')
|
|
78
|
+
.option('-s, --start', 'Start the build process')
|
|
79
|
+
.option('-c, --complete', 'Mark build as complete')
|
|
80
|
+
.option('-e, --evidence', 'Capture evidence snapshot')
|
|
81
|
+
.option('-a, --auto', 'Auto-build with Claude Code (--dangerously-skip-permissions)')
|
|
82
|
+
.option('--provider <name>', 'Provider to use: claude-code', 'claude-code')
|
|
83
|
+
.action(buildCommand);
|
|
84
|
+
|
|
85
|
+
program
|
|
86
|
+
.command('review')
|
|
87
|
+
.description('Review build against acceptance criteria')
|
|
88
|
+
.option('--skip-manual', 'Skip manual verification prompts')
|
|
89
|
+
.action(reviewCommand);
|
|
90
|
+
|
|
91
|
+
program
|
|
92
|
+
.command('snapshot')
|
|
93
|
+
.description('Create release snapshot with version bump')
|
|
94
|
+
.option('--patch', 'Patch version bump (default)')
|
|
95
|
+
.option('--minor', 'Minor version bump')
|
|
96
|
+
.option('--major', 'Major version bump')
|
|
97
|
+
.action(snapshotCommand);
|
|
98
|
+
|
|
99
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
// Phase C Commands - AI Integration
|
|
101
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
program
|
|
104
|
+
.command('config')
|
|
105
|
+
.description('Manage Vibecode configuration')
|
|
106
|
+
.option('--show', 'Show current configuration')
|
|
107
|
+
.option('--provider <name>', 'Set default AI provider')
|
|
108
|
+
.action(configCommand);
|
|
109
|
+
|
|
61
110
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
62
111
|
// Parse
|
|
63
112
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Build Command
|
|
3
|
+
// "Claude/LLM là PIPELINE, là KIẾN TRÚC SƯ"
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { exec } from 'child_process';
|
|
10
|
+
import { promisify } from 'util';
|
|
11
|
+
import { workspaceExists, getProjectName, loadState, saveState } from '../core/workspace.js';
|
|
12
|
+
import {
|
|
13
|
+
getCurrentSessionId,
|
|
14
|
+
getCurrentSessionPath,
|
|
15
|
+
writeSessionFile,
|
|
16
|
+
sessionFileExists,
|
|
17
|
+
readSessionFile
|
|
18
|
+
} from '../core/session.js';
|
|
19
|
+
import { getCurrentState, transitionTo } from '../core/state-machine.js';
|
|
20
|
+
import { getSpecHash } from '../core/contract.js';
|
|
21
|
+
import { STATES } from '../config/constants.js';
|
|
22
|
+
import { getBuildReportTemplate } from '../config/templates.js';
|
|
23
|
+
import { ensureDir, pathExists, appendToFile, readMarkdown } from '../utils/files.js';
|
|
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
|
+
|
|
32
|
+
const execAsync = promisify(exec);
|
|
33
|
+
|
|
34
|
+
export async function buildCommand(options = {}) {
|
|
35
|
+
try {
|
|
36
|
+
// Check workspace
|
|
37
|
+
if (!await workspaceExists()) {
|
|
38
|
+
printError('No Vibecode workspace found. Run `vibecode init` first.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const currentState = await getCurrentState();
|
|
43
|
+
const projectName = await getProjectName();
|
|
44
|
+
const sessionId = await getCurrentSessionId();
|
|
45
|
+
const sessionPath = await getCurrentSessionPath();
|
|
46
|
+
const specHash = await getSpecHash();
|
|
47
|
+
|
|
48
|
+
// Handle different build modes
|
|
49
|
+
if (options.auto) {
|
|
50
|
+
await handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options);
|
|
51
|
+
} else if (options.start) {
|
|
52
|
+
await handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash);
|
|
53
|
+
} else if (options.complete) {
|
|
54
|
+
await handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash);
|
|
55
|
+
} else if (options.evidence) {
|
|
56
|
+
await handleCaptureEvidence(currentState, sessionPath);
|
|
57
|
+
} else {
|
|
58
|
+
await handleBuildStatus(currentState, projectName, sessionId, sessionPath, specHash);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
} catch (error) {
|
|
62
|
+
printError(error.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Handle --auto mode: Spawn Claude Code with optimal config
|
|
69
|
+
* "Contract LOCKED = License to build"
|
|
70
|
+
*/
|
|
71
|
+
async function handleAutoBuild(currentState, projectName, sessionId, sessionPath, specHash, options) {
|
|
72
|
+
// Check state - must be PLAN_CREATED or BUILD_IN_PROGRESS or REVIEW_FAILED
|
|
73
|
+
const validStates = [STATES.PLAN_CREATED, STATES.BUILD_IN_PROGRESS, STATES.REVIEW_FAILED];
|
|
74
|
+
if (!validStates.includes(currentState)) {
|
|
75
|
+
printError(`Cannot auto-build in state: ${currentState}`);
|
|
76
|
+
console.log('Run `vibecode plan` first to create execution plan.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if Claude Code is available
|
|
81
|
+
const available = await isClaudeCodeAvailable();
|
|
82
|
+
if (!available) {
|
|
83
|
+
printError('Claude Code CLI not found.');
|
|
84
|
+
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
85
|
+
console.log(chalk.gray('Or use manual build: vibecode build --start'));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check coder_pack.md exists
|
|
90
|
+
if (!await sessionFileExists('coder_pack.md')) {
|
|
91
|
+
printError('coder_pack.md not found. Run `vibecode plan` first.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Setup evidence directory
|
|
96
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
97
|
+
await ensureDir(evidencePath);
|
|
98
|
+
await ensureDir(path.join(evidencePath, 'screenshots'));
|
|
99
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
100
|
+
|
|
101
|
+
// Save build start time
|
|
102
|
+
const stateData = await loadState();
|
|
103
|
+
const startTime = new Date().toISOString();
|
|
104
|
+
stateData.build_started = startTime;
|
|
105
|
+
stateData.build_provider = 'claude-code';
|
|
106
|
+
stateData.build_mode = 'auto';
|
|
107
|
+
await saveState(stateData);
|
|
108
|
+
|
|
109
|
+
// Transition to BUILD_IN_PROGRESS if not already
|
|
110
|
+
if (currentState !== STATES.BUILD_IN_PROGRESS) {
|
|
111
|
+
await transitionTo(STATES.BUILD_IN_PROGRESS, 'auto_build_started');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Load coder pack
|
|
115
|
+
const coderPackContent = await readSessionFile('coder_pack.md');
|
|
116
|
+
|
|
117
|
+
// Build full prompt with CLAUDE.md injection if exists
|
|
118
|
+
const fullPrompt = await buildPromptWithContext(coderPackContent, process.cwd());
|
|
119
|
+
|
|
120
|
+
// Log start
|
|
121
|
+
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
122
|
+
await appendToFile(logPath, `AUTO BUILD STARTED: ${startTime}\n`);
|
|
123
|
+
await appendToFile(logPath, `Provider: Claude Code (--dangerously-skip-permissions)\n`);
|
|
124
|
+
await appendToFile(logPath, `${'='.repeat(60)}\n\n`);
|
|
125
|
+
|
|
126
|
+
// Show starting message
|
|
127
|
+
const providerInfo = getProviderInfo();
|
|
128
|
+
|
|
129
|
+
const content = `🤖 AUTO BUILD
|
|
130
|
+
|
|
131
|
+
Project: ${projectName}
|
|
132
|
+
Session: ${sessionId}
|
|
133
|
+
Spec Hash: ${specHash}
|
|
134
|
+
|
|
135
|
+
Provider: ${providerInfo.name}
|
|
136
|
+
Mode: ${providerInfo.mode}
|
|
137
|
+
|
|
138
|
+
Starting AI build session...
|
|
139
|
+
Contract LOCKED = License to build`;
|
|
140
|
+
|
|
141
|
+
console.log();
|
|
142
|
+
printBox(content, { borderColor: 'magenta' });
|
|
143
|
+
console.log();
|
|
144
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
145
|
+
console.log(chalk.magenta('│ CLAUDE CODE OUTPUT'));
|
|
146
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
147
|
+
console.log();
|
|
148
|
+
|
|
149
|
+
// Spawn Claude Code
|
|
150
|
+
try {
|
|
151
|
+
const result = await spawnClaudeCode(fullPrompt, {
|
|
152
|
+
cwd: process.cwd(),
|
|
153
|
+
logPath: logPath
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
console.log();
|
|
157
|
+
console.log(chalk.magenta('─'.repeat(60)));
|
|
158
|
+
console.log();
|
|
159
|
+
|
|
160
|
+
// Capture evidence
|
|
161
|
+
await captureGitDiff(evidencePath);
|
|
162
|
+
|
|
163
|
+
const endTime = new Date().toISOString();
|
|
164
|
+
await appendToFile(logPath, `\n${'='.repeat(60)}\n`);
|
|
165
|
+
await appendToFile(logPath, `AUTO BUILD COMPLETED: ${endTime}\n`);
|
|
166
|
+
await appendToFile(logPath, `Exit code: ${result.code}\n`);
|
|
167
|
+
await appendToFile(logPath, `${'='.repeat(60)}\n`);
|
|
168
|
+
|
|
169
|
+
// Check evidence
|
|
170
|
+
const evidence = await checkEvidence(evidencePath);
|
|
171
|
+
|
|
172
|
+
// Generate build report
|
|
173
|
+
const reportContent = getBuildReportTemplate(
|
|
174
|
+
projectName,
|
|
175
|
+
sessionId,
|
|
176
|
+
specHash,
|
|
177
|
+
startTime,
|
|
178
|
+
endTime,
|
|
179
|
+
evidence
|
|
180
|
+
);
|
|
181
|
+
await writeSessionFile('build_report.md', reportContent);
|
|
182
|
+
|
|
183
|
+
// Update state
|
|
184
|
+
stateData.build_completed = endTime;
|
|
185
|
+
await saveState(stateData);
|
|
186
|
+
|
|
187
|
+
if (result.success) {
|
|
188
|
+
await transitionTo(STATES.BUILD_DONE, 'auto_build_completed');
|
|
189
|
+
|
|
190
|
+
const duration = Math.round((new Date(endTime) - new Date(startTime)) / 1000 / 60);
|
|
191
|
+
|
|
192
|
+
const successContent = `✅ AUTO BUILD COMPLETED
|
|
193
|
+
|
|
194
|
+
Project: ${projectName}
|
|
195
|
+
Duration: ${duration} minutes
|
|
196
|
+
Provider: Claude Code
|
|
197
|
+
|
|
198
|
+
Evidence:
|
|
199
|
+
${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
|
|
200
|
+
${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
|
|
201
|
+
${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
|
|
202
|
+
|
|
203
|
+
printBox(successContent, { borderColor: 'green' });
|
|
204
|
+
printNextStep('Run `vibecode review` to validate your build');
|
|
205
|
+
|
|
206
|
+
} else {
|
|
207
|
+
printWarning(`Claude Code exited with code: ${result.code}`);
|
|
208
|
+
console.log(chalk.gray('Check build.log for details.'));
|
|
209
|
+
console.log(chalk.gray('Run `vibecode build --auto` to retry.'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
} catch (error) {
|
|
213
|
+
await appendToFile(logPath, `\nERROR: ${error.message}\n`);
|
|
214
|
+
printError(`Auto build failed: ${error.message}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash) {
|
|
220
|
+
const spinner = ora('Starting build...').start();
|
|
221
|
+
|
|
222
|
+
// Check state
|
|
223
|
+
if (currentState !== STATES.PLAN_CREATED && currentState !== STATES.REVIEW_FAILED) {
|
|
224
|
+
spinner.fail();
|
|
225
|
+
printError(`Cannot start build in state: ${currentState}`);
|
|
226
|
+
console.log('Run `vibecode plan` first to create execution plan.');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Create evidence directory
|
|
231
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
232
|
+
await ensureDir(evidencePath);
|
|
233
|
+
await ensureDir(path.join(evidencePath, 'screenshots'));
|
|
234
|
+
|
|
235
|
+
// Save build start time
|
|
236
|
+
const stateData = await loadState();
|
|
237
|
+
stateData.build_started = new Date().toISOString();
|
|
238
|
+
await saveState(stateData);
|
|
239
|
+
|
|
240
|
+
// Initialize build log
|
|
241
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
242
|
+
await appendToFile(logPath, `=== BUILD STARTED: ${stateData.build_started} ===\n`);
|
|
243
|
+
|
|
244
|
+
// Transition state
|
|
245
|
+
await transitionTo(STATES.BUILD_IN_PROGRESS, 'build_started');
|
|
246
|
+
|
|
247
|
+
spinner.succeed('Build started!');
|
|
248
|
+
|
|
249
|
+
const content = `🏗️ BUILD IN PROGRESS
|
|
250
|
+
|
|
251
|
+
Project: ${projectName}
|
|
252
|
+
Session: ${sessionId}
|
|
253
|
+
Spec Hash: ${specHash}
|
|
254
|
+
Started: ${stateData.build_started}
|
|
255
|
+
|
|
256
|
+
Evidence folder ready:
|
|
257
|
+
${evidencePath}/
|
|
258
|
+
├── build.log
|
|
259
|
+
└── screenshots/`;
|
|
260
|
+
|
|
261
|
+
console.log();
|
|
262
|
+
printBox(content, { borderColor: 'yellow' });
|
|
263
|
+
|
|
264
|
+
console.log();
|
|
265
|
+
console.log(chalk.cyan('📝 While building:'));
|
|
266
|
+
console.log(chalk.gray(' • Follow coder_pack.md instructions'));
|
|
267
|
+
console.log(chalk.gray(' • Capture evidence with `vibecode build --evidence`'));
|
|
268
|
+
console.log(chalk.gray(' • Complete with `vibecode build --complete`'));
|
|
269
|
+
|
|
270
|
+
printNextStep('Build your deliverables and run `vibecode build --complete` when done');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash) {
|
|
274
|
+
const spinner = ora('Completing build...').start();
|
|
275
|
+
|
|
276
|
+
// Check state
|
|
277
|
+
if (currentState !== STATES.BUILD_IN_PROGRESS) {
|
|
278
|
+
spinner.fail();
|
|
279
|
+
printError(`Cannot complete build in state: ${currentState}`);
|
|
280
|
+
console.log('Run `vibecode build --start` first.');
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const stateData = await loadState();
|
|
285
|
+
const startTime = stateData.build_started;
|
|
286
|
+
const endTime = new Date().toISOString();
|
|
287
|
+
|
|
288
|
+
// Capture final evidence
|
|
289
|
+
spinner.text = 'Capturing evidence...';
|
|
290
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
291
|
+
await captureGitDiff(evidencePath);
|
|
292
|
+
|
|
293
|
+
// Append to build log
|
|
294
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
295
|
+
await appendToFile(logPath, `\n=== BUILD COMPLETED: ${endTime} ===`);
|
|
296
|
+
|
|
297
|
+
// Check evidence collected
|
|
298
|
+
const evidence = await checkEvidence(evidencePath);
|
|
299
|
+
|
|
300
|
+
// Generate build report
|
|
301
|
+
spinner.text = 'Generating build report...';
|
|
302
|
+
const reportContent = getBuildReportTemplate(
|
|
303
|
+
projectName,
|
|
304
|
+
sessionId,
|
|
305
|
+
specHash,
|
|
306
|
+
startTime,
|
|
307
|
+
endTime,
|
|
308
|
+
evidence
|
|
309
|
+
);
|
|
310
|
+
await writeSessionFile('build_report.md', reportContent);
|
|
311
|
+
|
|
312
|
+
// Save end time
|
|
313
|
+
stateData.build_completed = endTime;
|
|
314
|
+
await saveState(stateData);
|
|
315
|
+
|
|
316
|
+
// Transition state
|
|
317
|
+
await transitionTo(STATES.BUILD_DONE, 'build_completed');
|
|
318
|
+
|
|
319
|
+
spinner.succeed('Build completed!');
|
|
320
|
+
|
|
321
|
+
const duration = Math.round((new Date(endTime) - new Date(startTime)) / 1000 / 60);
|
|
322
|
+
|
|
323
|
+
const content = `✅ BUILD COMPLETED
|
|
324
|
+
|
|
325
|
+
Project: ${projectName}
|
|
326
|
+
Duration: ${duration} minutes
|
|
327
|
+
Evidence collected:
|
|
328
|
+
${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
|
|
329
|
+
${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
|
|
330
|
+
${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
|
|
331
|
+
|
|
332
|
+
console.log();
|
|
333
|
+
printBox(content, { borderColor: 'green' });
|
|
334
|
+
|
|
335
|
+
printNextStep('Run `vibecode review` to validate your build');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function handleCaptureEvidence(currentState, sessionPath) {
|
|
339
|
+
const spinner = ora('Capturing evidence...').start();
|
|
340
|
+
|
|
341
|
+
if (currentState !== STATES.BUILD_IN_PROGRESS) {
|
|
342
|
+
spinner.fail();
|
|
343
|
+
printError('Can only capture evidence during BUILD_IN_PROGRESS state');
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
348
|
+
await ensureDir(evidencePath);
|
|
349
|
+
|
|
350
|
+
// Capture git diff
|
|
351
|
+
await captureGitDiff(evidencePath);
|
|
352
|
+
|
|
353
|
+
// Append to build log
|
|
354
|
+
const timestamp = new Date().toISOString();
|
|
355
|
+
await appendToFile(
|
|
356
|
+
path.join(evidencePath, 'build.log'),
|
|
357
|
+
`\n[${timestamp}] Evidence snapshot captured\n`
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
spinner.succeed('Evidence captured!');
|
|
361
|
+
|
|
362
|
+
const evidence = await checkEvidence(evidencePath);
|
|
363
|
+
console.log();
|
|
364
|
+
console.log(chalk.cyan('📁 Evidence collected:'));
|
|
365
|
+
console.log(chalk.gray(` ${evidence.hasDiff ? '✅' : '⬜'} changes.diff`));
|
|
366
|
+
console.log(chalk.gray(` ${evidence.hasLog ? '✅' : '⬜'} build.log`));
|
|
367
|
+
console.log(chalk.gray(` ${evidence.screenshots > 0 ? `✅ ${evidence.screenshots}` : '⬜ 0'} screenshots`));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function handleBuildStatus(currentState, projectName, sessionId, sessionPath, specHash) {
|
|
371
|
+
if (currentState === STATES.BUILD_IN_PROGRESS) {
|
|
372
|
+
const stateData = await loadState();
|
|
373
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
374
|
+
const evidence = await checkEvidence(evidencePath);
|
|
375
|
+
|
|
376
|
+
const elapsed = Math.round((new Date() - new Date(stateData.build_started)) / 1000 / 60);
|
|
377
|
+
|
|
378
|
+
const content = `🏗️ BUILD IN PROGRESS
|
|
379
|
+
|
|
380
|
+
Project: ${projectName}
|
|
381
|
+
Session: ${sessionId}
|
|
382
|
+
Started: ${stateData.build_started}
|
|
383
|
+
Elapsed: ${elapsed} minutes
|
|
384
|
+
|
|
385
|
+
Evidence:
|
|
386
|
+
${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
|
|
387
|
+
${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
|
|
388
|
+
${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
|
|
389
|
+
|
|
390
|
+
console.log();
|
|
391
|
+
printBox(content, { borderColor: 'yellow' });
|
|
392
|
+
|
|
393
|
+
console.log();
|
|
394
|
+
console.log(chalk.cyan('Commands:'));
|
|
395
|
+
console.log(chalk.gray(' --evidence Capture current git diff'));
|
|
396
|
+
console.log(chalk.gray(' --complete Mark build as done'));
|
|
397
|
+
|
|
398
|
+
} else if (currentState === STATES.BUILD_DONE) {
|
|
399
|
+
printSuccess('Build already completed!');
|
|
400
|
+
printNextStep('Run `vibecode review` to validate');
|
|
401
|
+
|
|
402
|
+
} else {
|
|
403
|
+
printWarning(`Current state: ${currentState}`);
|
|
404
|
+
console.log('Run `vibecode build --start` to begin building.');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function captureGitDiff(evidencePath) {
|
|
409
|
+
try {
|
|
410
|
+
const { stdout } = await execAsync('git diff HEAD', { maxBuffer: 10 * 1024 * 1024 });
|
|
411
|
+
const diffPath = path.join(evidencePath, 'changes.diff');
|
|
412
|
+
|
|
413
|
+
if (stdout.trim()) {
|
|
414
|
+
const fs = await import('fs-extra');
|
|
415
|
+
await fs.default.writeFile(diffPath, stdout, 'utf-8');
|
|
416
|
+
} else {
|
|
417
|
+
// Try staged changes
|
|
418
|
+
const { stdout: stagedDiff } = await execAsync('git diff --cached', { maxBuffer: 10 * 1024 * 1024 });
|
|
419
|
+
if (stagedDiff.trim()) {
|
|
420
|
+
const fs = await import('fs-extra');
|
|
421
|
+
await fs.default.writeFile(diffPath, stagedDiff, 'utf-8');
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch (error) {
|
|
425
|
+
// Git might not be available, that's okay
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function checkEvidence(evidencePath) {
|
|
430
|
+
const result = {
|
|
431
|
+
hasDiff: false,
|
|
432
|
+
hasLog: false,
|
|
433
|
+
screenshots: 0,
|
|
434
|
+
filesChanged: 0,
|
|
435
|
+
linesAdded: 0,
|
|
436
|
+
linesRemoved: 0
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
result.hasDiff = await pathExists(path.join(evidencePath, 'changes.diff'));
|
|
441
|
+
result.hasLog = await pathExists(path.join(evidencePath, 'build.log'));
|
|
442
|
+
|
|
443
|
+
const screenshotsPath = path.join(evidencePath, 'screenshots');
|
|
444
|
+
if (await pathExists(screenshotsPath)) {
|
|
445
|
+
const fs = await import('fs-extra');
|
|
446
|
+
const files = await fs.default.readdir(screenshotsPath);
|
|
447
|
+
result.screenshots = files.filter(f => /\.(png|jpg|jpeg|gif)$/i.test(f)).length;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Parse git diff stats
|
|
451
|
+
if (result.hasDiff) {
|
|
452
|
+
try {
|
|
453
|
+
const { stdout } = await execAsync('git diff --stat HEAD');
|
|
454
|
+
const statsLine = stdout.split('\n').pop();
|
|
455
|
+
const match = statsLine?.match(/(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/);
|
|
456
|
+
if (match) {
|
|
457
|
+
result.filesChanged = parseInt(match[1]) || 0;
|
|
458
|
+
result.linesAdded = parseInt(match[2]) || 0;
|
|
459
|
+
result.linesRemoved = parseInt(match[3]) || 0;
|
|
460
|
+
}
|
|
461
|
+
} catch (e) {
|
|
462
|
+
// Ignore git errors
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
// Ignore errors
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Config Command
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { workspaceExists, getWorkspacePath } from '../core/workspace.js';
|
|
8
|
+
import { pathExists, readJson, writeJson } from '../utils/files.js';
|
|
9
|
+
import { PROVIDERS, getDefaultProvider } from '../providers/index.js';
|
|
10
|
+
import { printBox, printError, printSuccess, printInfo } from '../ui/output.js';
|
|
11
|
+
|
|
12
|
+
const CONFIG_FILE = 'config.json';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get config file path
|
|
16
|
+
*/
|
|
17
|
+
function getConfigPath() {
|
|
18
|
+
return path.join(getWorkspacePath(), CONFIG_FILE);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load config
|
|
23
|
+
*/
|
|
24
|
+
async function loadConfig() {
|
|
25
|
+
const configPath = getConfigPath();
|
|
26
|
+
if (await pathExists(configPath)) {
|
|
27
|
+
return await readJson(configPath);
|
|
28
|
+
}
|
|
29
|
+
return getDefaultConfig();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Save config
|
|
34
|
+
*/
|
|
35
|
+
async function saveConfig(config) {
|
|
36
|
+
const configPath = getConfigPath();
|
|
37
|
+
await writeJson(configPath, config);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get default config
|
|
42
|
+
*/
|
|
43
|
+
function getDefaultConfig() {
|
|
44
|
+
return {
|
|
45
|
+
provider: 'claude-code',
|
|
46
|
+
claudeCode: {
|
|
47
|
+
flags: ['--dangerously-skip-permissions'],
|
|
48
|
+
timeout: 30 // minutes
|
|
49
|
+
},
|
|
50
|
+
autoEvidence: true,
|
|
51
|
+
verbose: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function configCommand(options = {}) {
|
|
56
|
+
try {
|
|
57
|
+
// Check workspace for set operations
|
|
58
|
+
if (options.provider || options.set) {
|
|
59
|
+
if (!await workspaceExists()) {
|
|
60
|
+
printError('No Vibecode workspace found. Run `vibecode init` first.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Show current config
|
|
66
|
+
if (options.show || (!options.provider && !options.set)) {
|
|
67
|
+
await showConfig();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Set provider
|
|
72
|
+
if (options.provider) {
|
|
73
|
+
await setProvider(options.provider);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
printError(error.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function showConfig() {
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
// Check if workspace exists
|
|
87
|
+
if (!await workspaceExists()) {
|
|
88
|
+
// Show default config
|
|
89
|
+
console.log(chalk.cyan('Default Configuration (no workspace):'));
|
|
90
|
+
console.log();
|
|
91
|
+
const defaultConfig = getDefaultConfig();
|
|
92
|
+
console.log(chalk.gray('Provider: ') + chalk.white(defaultConfig.provider));
|
|
93
|
+
console.log(chalk.gray('Flags: ') + chalk.white(defaultConfig.claudeCode.flags.join(', ')));
|
|
94
|
+
console.log(chalk.gray('Timeout: ') + chalk.white(`${defaultConfig.claudeCode.timeout} minutes`));
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk.gray('Run `vibecode init` to create a workspace.'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const config = await loadConfig();
|
|
101
|
+
|
|
102
|
+
const content = `Current Configuration
|
|
103
|
+
|
|
104
|
+
Provider: ${config.provider}
|
|
105
|
+
Flags: ${config.claudeCode?.flags?.join(', ') || 'none'}
|
|
106
|
+
Timeout: ${config.claudeCode?.timeout || 30} minutes
|
|
107
|
+
Auto Evidence: ${config.autoEvidence ? 'enabled' : 'disabled'}
|
|
108
|
+
Verbose: ${config.verbose ? 'enabled' : 'disabled'}`;
|
|
109
|
+
|
|
110
|
+
printBox(content, { borderColor: 'cyan' });
|
|
111
|
+
|
|
112
|
+
// Show available providers
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(chalk.cyan('Available Providers:'));
|
|
115
|
+
Object.entries(PROVIDERS).forEach(([key, provider]) => {
|
|
116
|
+
const status = provider.available ? chalk.green('available') : chalk.gray('coming soon');
|
|
117
|
+
const current = key === config.provider ? chalk.yellow(' (current)') : '';
|
|
118
|
+
console.log(chalk.gray(` • ${key}: ${provider.description} [${status}]${current}`));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(chalk.gray('To change: vibecode config --provider <name>'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function setProvider(providerName) {
|
|
126
|
+
// Validate provider
|
|
127
|
+
if (!PROVIDERS[providerName]) {
|
|
128
|
+
printError(`Unknown provider: ${providerName}`);
|
|
129
|
+
console.log();
|
|
130
|
+
console.log(chalk.cyan('Available providers:'));
|
|
131
|
+
Object.keys(PROVIDERS).forEach(key => {
|
|
132
|
+
console.log(chalk.gray(` • ${key}`));
|
|
133
|
+
});
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!PROVIDERS[providerName].available) {
|
|
138
|
+
printError(`Provider "${providerName}" is not yet available.`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Load and update config
|
|
143
|
+
const config = await loadConfig();
|
|
144
|
+
config.provider = providerName;
|
|
145
|
+
await saveConfig(config);
|
|
146
|
+
|
|
147
|
+
printSuccess(`Provider set to: ${providerName}`);
|
|
148
|
+
console.log(chalk.gray(`Config saved to: ${getConfigPath()}`));
|
|
149
|
+
}
|