@nclamvn/vibecode-cli 1.0.0 → 1.1.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 CHANGED
@@ -12,6 +12,10 @@ import {
12
12
  statusCommand,
13
13
  lockCommand,
14
14
  doctorCommand,
15
+ planCommand,
16
+ buildCommand,
17
+ reviewCommand,
18
+ snapshotCommand,
15
19
  VERSION
16
20
  } from '../src/index.js';
17
21
 
@@ -23,7 +27,7 @@ program
23
27
  .version(VERSION);
24
28
 
25
29
  // ─────────────────────────────────────────────────────────────────────────────
26
- // Commands
30
+ // Phase A Commands - Planning
27
31
  // ─────────────────────────────────────────────────────────────────────────────
28
32
 
29
33
  program
@@ -58,6 +62,37 @@ program
58
62
  .description('Check configuration and diagnose issues')
59
63
  .action(doctorCommand);
60
64
 
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+ // Phase B Commands - Execution
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+
69
+ program
70
+ .command('plan')
71
+ .description('Create execution plan from locked contract')
72
+ .action(planCommand);
73
+
74
+ program
75
+ .command('build')
76
+ .description('Manage build process and capture evidence')
77
+ .option('-s, --start', 'Start the build process')
78
+ .option('-c, --complete', 'Mark build as complete')
79
+ .option('-e, --evidence', 'Capture evidence snapshot')
80
+ .action(buildCommand);
81
+
82
+ program
83
+ .command('review')
84
+ .description('Review build against acceptance criteria')
85
+ .option('--skip-manual', 'Skip manual verification prompts')
86
+ .action(reviewCommand);
87
+
88
+ program
89
+ .command('snapshot')
90
+ .description('Create release snapshot with version bump')
91
+ .option('--patch', 'Patch version bump (default)')
92
+ .option('--minor', 'Minor version bump')
93
+ .option('--major', 'Major version bump')
94
+ .action(snapshotCommand);
95
+
61
96
  // ─────────────────────────────────────────────────────────────────────────────
62
97
  // Parse
63
98
  // ─────────────────────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nclamvn/vibecode-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Build software with discipline - AI coding with guardrails",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,308 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Build Command
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import path from 'path';
8
+ import { exec } from 'child_process';
9
+ import { promisify } from 'util';
10
+ import { workspaceExists, getProjectName, loadState, saveState } from '../core/workspace.js';
11
+ import {
12
+ getCurrentSessionId,
13
+ getCurrentSessionPath,
14
+ writeSessionFile,
15
+ sessionFileExists
16
+ } from '../core/session.js';
17
+ import { getCurrentState, transitionTo } from '../core/state-machine.js';
18
+ import { getSpecHash } from '../core/contract.js';
19
+ import { STATES } from '../config/constants.js';
20
+ import { getBuildReportTemplate } from '../config/templates.js';
21
+ import { ensureDir, pathExists, appendToFile } from '../utils/files.js';
22
+ import { printBox, printError, printSuccess, printWarning, printNextStep } from '../ui/output.js';
23
+
24
+ const execAsync = promisify(exec);
25
+
26
+ export async function buildCommand(options = {}) {
27
+ try {
28
+ // Check workspace
29
+ if (!await workspaceExists()) {
30
+ printError('No Vibecode workspace found. Run `vibecode init` first.');
31
+ process.exit(1);
32
+ }
33
+
34
+ const currentState = await getCurrentState();
35
+ const projectName = await getProjectName();
36
+ const sessionId = await getCurrentSessionId();
37
+ const sessionPath = await getCurrentSessionPath();
38
+ const specHash = await getSpecHash();
39
+
40
+ // Handle different build modes
41
+ if (options.start) {
42
+ await handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash);
43
+ } else if (options.complete) {
44
+ await handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash);
45
+ } else if (options.evidence) {
46
+ await handleCaptureEvidence(currentState, sessionPath);
47
+ } else {
48
+ await handleBuildStatus(currentState, projectName, sessionId, sessionPath, specHash);
49
+ }
50
+
51
+ } catch (error) {
52
+ printError(error.message);
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ async function handleBuildStart(currentState, projectName, sessionId, sessionPath, specHash) {
58
+ const spinner = ora('Starting build...').start();
59
+
60
+ // Check state
61
+ if (currentState !== STATES.PLAN_CREATED && currentState !== STATES.REVIEW_FAILED) {
62
+ spinner.fail();
63
+ printError(`Cannot start build in state: ${currentState}`);
64
+ console.log('Run `vibecode plan` first to create execution plan.');
65
+ process.exit(1);
66
+ }
67
+
68
+ // Create evidence directory
69
+ const evidencePath = path.join(sessionPath, 'evidence');
70
+ await ensureDir(evidencePath);
71
+ await ensureDir(path.join(evidencePath, 'screenshots'));
72
+
73
+ // Save build start time
74
+ const stateData = await loadState();
75
+ stateData.build_started = new Date().toISOString();
76
+ await saveState(stateData);
77
+
78
+ // Initialize build log
79
+ const logPath = path.join(evidencePath, 'build.log');
80
+ await appendToFile(logPath, `=== BUILD STARTED: ${stateData.build_started} ===\n`);
81
+
82
+ // Transition state
83
+ await transitionTo(STATES.BUILD_IN_PROGRESS, 'build_started');
84
+
85
+ spinner.succeed('Build started!');
86
+
87
+ const content = `🏗️ BUILD IN PROGRESS
88
+
89
+ Project: ${projectName}
90
+ Session: ${sessionId}
91
+ Spec Hash: ${specHash}
92
+ Started: ${stateData.build_started}
93
+
94
+ Evidence folder ready:
95
+ ${evidencePath}/
96
+ ├── build.log
97
+ └── screenshots/`;
98
+
99
+ console.log();
100
+ printBox(content, { borderColor: 'yellow' });
101
+
102
+ console.log();
103
+ console.log(chalk.cyan('📝 While building:'));
104
+ console.log(chalk.gray(' • Follow coder_pack.md instructions'));
105
+ console.log(chalk.gray(' • Capture evidence with `vibecode build --evidence`'));
106
+ console.log(chalk.gray(' • Complete with `vibecode build --complete`'));
107
+
108
+ printNextStep('Build your deliverables and run `vibecode build --complete` when done');
109
+ }
110
+
111
+ async function handleBuildComplete(currentState, projectName, sessionId, sessionPath, specHash) {
112
+ const spinner = ora('Completing build...').start();
113
+
114
+ // Check state
115
+ if (currentState !== STATES.BUILD_IN_PROGRESS) {
116
+ spinner.fail();
117
+ printError(`Cannot complete build in state: ${currentState}`);
118
+ console.log('Run `vibecode build --start` first.');
119
+ process.exit(1);
120
+ }
121
+
122
+ const stateData = await loadState();
123
+ const startTime = stateData.build_started;
124
+ const endTime = new Date().toISOString();
125
+
126
+ // Capture final evidence
127
+ spinner.text = 'Capturing evidence...';
128
+ const evidencePath = path.join(sessionPath, 'evidence');
129
+ await captureGitDiff(evidencePath);
130
+
131
+ // Append to build log
132
+ const logPath = path.join(evidencePath, 'build.log');
133
+ await appendToFile(logPath, `\n=== BUILD COMPLETED: ${endTime} ===`);
134
+
135
+ // Check evidence collected
136
+ const evidence = await checkEvidence(evidencePath);
137
+
138
+ // Generate build report
139
+ spinner.text = 'Generating build report...';
140
+ const reportContent = getBuildReportTemplate(
141
+ projectName,
142
+ sessionId,
143
+ specHash,
144
+ startTime,
145
+ endTime,
146
+ evidence
147
+ );
148
+ await writeSessionFile('build_report.md', reportContent);
149
+
150
+ // Save end time
151
+ stateData.build_completed = endTime;
152
+ await saveState(stateData);
153
+
154
+ // Transition state
155
+ await transitionTo(STATES.BUILD_DONE, 'build_completed');
156
+
157
+ spinner.succeed('Build completed!');
158
+
159
+ const duration = Math.round((new Date(endTime) - new Date(startTime)) / 1000 / 60);
160
+
161
+ const content = `✅ BUILD COMPLETED
162
+
163
+ Project: ${projectName}
164
+ Duration: ${duration} minutes
165
+ Evidence collected:
166
+ ${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
167
+ ${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
168
+ ${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
169
+
170
+ console.log();
171
+ printBox(content, { borderColor: 'green' });
172
+
173
+ printNextStep('Run `vibecode review` to validate your build');
174
+ }
175
+
176
+ async function handleCaptureEvidence(currentState, sessionPath) {
177
+ const spinner = ora('Capturing evidence...').start();
178
+
179
+ if (currentState !== STATES.BUILD_IN_PROGRESS) {
180
+ spinner.fail();
181
+ printError('Can only capture evidence during BUILD_IN_PROGRESS state');
182
+ process.exit(1);
183
+ }
184
+
185
+ const evidencePath = path.join(sessionPath, 'evidence');
186
+ await ensureDir(evidencePath);
187
+
188
+ // Capture git diff
189
+ await captureGitDiff(evidencePath);
190
+
191
+ // Append to build log
192
+ const timestamp = new Date().toISOString();
193
+ await appendToFile(
194
+ path.join(evidencePath, 'build.log'),
195
+ `\n[${timestamp}] Evidence snapshot captured\n`
196
+ );
197
+
198
+ spinner.succeed('Evidence captured!');
199
+
200
+ const evidence = await checkEvidence(evidencePath);
201
+ console.log();
202
+ console.log(chalk.cyan('📁 Evidence collected:'));
203
+ console.log(chalk.gray(` ${evidence.hasDiff ? '✅' : '⬜'} changes.diff`));
204
+ console.log(chalk.gray(` ${evidence.hasLog ? '✅' : '⬜'} build.log`));
205
+ console.log(chalk.gray(` ${evidence.screenshots > 0 ? `✅ ${evidence.screenshots}` : '⬜ 0'} screenshots`));
206
+ }
207
+
208
+ async function handleBuildStatus(currentState, projectName, sessionId, sessionPath, specHash) {
209
+ if (currentState === STATES.BUILD_IN_PROGRESS) {
210
+ const stateData = await loadState();
211
+ const evidencePath = path.join(sessionPath, 'evidence');
212
+ const evidence = await checkEvidence(evidencePath);
213
+
214
+ const elapsed = Math.round((new Date() - new Date(stateData.build_started)) / 1000 / 60);
215
+
216
+ const content = `🏗️ BUILD IN PROGRESS
217
+
218
+ Project: ${projectName}
219
+ Session: ${sessionId}
220
+ Started: ${stateData.build_started}
221
+ Elapsed: ${elapsed} minutes
222
+
223
+ Evidence:
224
+ ${evidence.hasDiff ? ' ✅ changes.diff' : ' ⬜ changes.diff'}
225
+ ${evidence.hasLog ? ' ✅ build.log' : ' ⬜ build.log'}
226
+ ${evidence.screenshots > 0 ? ` ✅ ${evidence.screenshots} screenshots` : ' ⬜ No screenshots'}`;
227
+
228
+ console.log();
229
+ printBox(content, { borderColor: 'yellow' });
230
+
231
+ console.log();
232
+ console.log(chalk.cyan('Commands:'));
233
+ console.log(chalk.gray(' --evidence Capture current git diff'));
234
+ console.log(chalk.gray(' --complete Mark build as done'));
235
+
236
+ } else if (currentState === STATES.BUILD_DONE) {
237
+ printSuccess('Build already completed!');
238
+ printNextStep('Run `vibecode review` to validate');
239
+
240
+ } else {
241
+ printWarning(`Current state: ${currentState}`);
242
+ console.log('Run `vibecode build --start` to begin building.');
243
+ }
244
+ }
245
+
246
+ async function captureGitDiff(evidencePath) {
247
+ try {
248
+ const { stdout } = await execAsync('git diff HEAD', { maxBuffer: 10 * 1024 * 1024 });
249
+ const diffPath = path.join(evidencePath, 'changes.diff');
250
+
251
+ if (stdout.trim()) {
252
+ const fs = await import('fs-extra');
253
+ await fs.default.writeFile(diffPath, stdout, 'utf-8');
254
+ } else {
255
+ // Try staged changes
256
+ const { stdout: stagedDiff } = await execAsync('git diff --cached', { maxBuffer: 10 * 1024 * 1024 });
257
+ if (stagedDiff.trim()) {
258
+ const fs = await import('fs-extra');
259
+ await fs.default.writeFile(diffPath, stagedDiff, 'utf-8');
260
+ }
261
+ }
262
+ } catch (error) {
263
+ // Git might not be available, that's okay
264
+ }
265
+ }
266
+
267
+ async function checkEvidence(evidencePath) {
268
+ const result = {
269
+ hasDiff: false,
270
+ hasLog: false,
271
+ screenshots: 0,
272
+ filesChanged: 0,
273
+ linesAdded: 0,
274
+ linesRemoved: 0
275
+ };
276
+
277
+ try {
278
+ result.hasDiff = await pathExists(path.join(evidencePath, 'changes.diff'));
279
+ result.hasLog = await pathExists(path.join(evidencePath, 'build.log'));
280
+
281
+ const screenshotsPath = path.join(evidencePath, 'screenshots');
282
+ if (await pathExists(screenshotsPath)) {
283
+ const fs = await import('fs-extra');
284
+ const files = await fs.default.readdir(screenshotsPath);
285
+ result.screenshots = files.filter(f => /\.(png|jpg|jpeg|gif)$/i.test(f)).length;
286
+ }
287
+
288
+ // Parse git diff stats
289
+ if (result.hasDiff) {
290
+ try {
291
+ const { stdout } = await execAsync('git diff --stat HEAD');
292
+ const statsLine = stdout.split('\n').pop();
293
+ const match = statsLine?.match(/(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/);
294
+ if (match) {
295
+ result.filesChanged = parseInt(match[1]) || 0;
296
+ result.linesAdded = parseInt(match[2]) || 0;
297
+ result.linesRemoved = parseInt(match[3]) || 0;
298
+ }
299
+ } catch (e) {
300
+ // Ignore git errors
301
+ }
302
+ }
303
+ } catch (error) {
304
+ // Ignore errors
305
+ }
306
+
307
+ return result;
308
+ }
@@ -0,0 +1,105 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Plan Command
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import path from 'path';
8
+ import { workspaceExists, getProjectName } from '../core/workspace.js';
9
+ import {
10
+ getCurrentSessionId,
11
+ getCurrentSessionPath,
12
+ readSessionFile,
13
+ writeSessionFile,
14
+ sessionFileExists
15
+ } from '../core/session.js';
16
+ import { getCurrentState, transitionTo } from '../core/state-machine.js';
17
+ import { getSpecHash } from '../core/contract.js';
18
+ import { STATES } from '../config/constants.js';
19
+ import { getPlanTemplate, getCoderPackTemplate } from '../config/templates.js';
20
+ import { printBox, printError, printSuccess, printNextStep } from '../ui/output.js';
21
+
22
+ export async function planCommand(options = {}) {
23
+ const spinner = ora('Creating execution plan...').start();
24
+
25
+ try {
26
+ // Check workspace
27
+ if (!await workspaceExists()) {
28
+ spinner.fail();
29
+ printError('No Vibecode workspace found. Run `vibecode init` first.');
30
+ process.exit(1);
31
+ }
32
+
33
+ // Check state
34
+ const currentState = await getCurrentState();
35
+ if (currentState !== STATES.CONTRACT_LOCKED) {
36
+ spinner.fail();
37
+ printError(`Cannot create plan in state: ${currentState}`);
38
+ console.log('Contract must be locked first. Run `vibecode lock`.');
39
+ process.exit(1);
40
+ }
41
+
42
+ // Get session info
43
+ const projectName = await getProjectName();
44
+ const sessionId = await getCurrentSessionId();
45
+ const sessionPath = await getCurrentSessionPath();
46
+ const specHash = await getSpecHash();
47
+
48
+ // Read contract and blueprint
49
+ spinner.text = 'Reading contract...';
50
+ const contractContent = await readSessionFile('contract.md');
51
+
52
+ let blueprintContent = '';
53
+ if (await sessionFileExists('blueprint.md')) {
54
+ blueprintContent = await readSessionFile('blueprint.md');
55
+ }
56
+
57
+ // Generate plan
58
+ spinner.text = 'Generating plan...';
59
+ const planContent = getPlanTemplate(projectName, sessionId, specHash, contractContent);
60
+ await writeSessionFile('plan.md', planContent);
61
+
62
+ // Generate coder pack
63
+ spinner.text = 'Generating coder pack...';
64
+ const coderPackContent = getCoderPackTemplate(
65
+ projectName,
66
+ sessionId,
67
+ specHash,
68
+ contractContent,
69
+ blueprintContent
70
+ );
71
+ await writeSessionFile('coder_pack.md', coderPackContent);
72
+
73
+ // Transition state
74
+ await transitionTo(STATES.PLAN_CREATED, 'plan_created');
75
+
76
+ spinner.succeed('Execution plan created!');
77
+
78
+ // Success output
79
+ const content = `📋 PLAN CREATED
80
+
81
+ Project: ${projectName}
82
+ Session: ${sessionId}
83
+ Spec Hash: ${specHash}
84
+
85
+ Files generated:
86
+ • plan.md - Execution steps
87
+ • coder_pack.md - Instructions for AI builder`;
88
+
89
+ console.log();
90
+ printBox(content, { borderColor: 'cyan' });
91
+
92
+ console.log();
93
+ console.log(chalk.cyan('📁 Files:'));
94
+ console.log(chalk.gray(` ${sessionPath}/`));
95
+ console.log(chalk.gray(' ├── plan.md'));
96
+ console.log(chalk.gray(' └── coder_pack.md'));
97
+
98
+ printNextStep('Transfer coder_pack.md to Claude Code and run `vibecode build --start`');
99
+
100
+ } catch (error) {
101
+ spinner.fail('Failed to create plan');
102
+ printError(error.message);
103
+ process.exit(1);
104
+ }
105
+ }