@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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Review 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 inquirer from 'inquirer';
|
|
11
|
+
import { workspaceExists, getProjectName } from '../core/workspace.js';
|
|
12
|
+
import {
|
|
13
|
+
getCurrentSessionId,
|
|
14
|
+
getCurrentSessionPath,
|
|
15
|
+
writeSessionFile,
|
|
16
|
+
readSessionFile,
|
|
17
|
+
sessionFileExists
|
|
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 { getReviewReportTemplate } from '../config/templates.js';
|
|
23
|
+
import { pathExists } from '../utils/files.js';
|
|
24
|
+
import { printBox, printError, printSuccess, printWarning, printNextStep } from '../ui/output.js';
|
|
25
|
+
|
|
26
|
+
const execAsync = promisify(exec);
|
|
27
|
+
|
|
28
|
+
export async function reviewCommand(options = {}) {
|
|
29
|
+
const spinner = ora('Starting review...').start();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Check workspace
|
|
33
|
+
if (!await workspaceExists()) {
|
|
34
|
+
spinner.fail();
|
|
35
|
+
printError('No Vibecode workspace found. Run `vibecode init` first.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check state
|
|
40
|
+
const currentState = await getCurrentState();
|
|
41
|
+
if (currentState !== STATES.BUILD_DONE) {
|
|
42
|
+
spinner.fail();
|
|
43
|
+
printError(`Cannot review in state: ${currentState}`);
|
|
44
|
+
console.log('Complete build first with `vibecode build --complete`.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const projectName = await getProjectName();
|
|
49
|
+
const sessionId = await getCurrentSessionId();
|
|
50
|
+
const sessionPath = await getCurrentSessionPath();
|
|
51
|
+
const specHash = await getSpecHash();
|
|
52
|
+
|
|
53
|
+
spinner.text = 'Running automated checks...';
|
|
54
|
+
|
|
55
|
+
// Run automated checks
|
|
56
|
+
const checks = [];
|
|
57
|
+
|
|
58
|
+
// Check 1: Evidence exists
|
|
59
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
60
|
+
const hasEvidence = await pathExists(path.join(evidencePath, 'changes.diff')) ||
|
|
61
|
+
await pathExists(path.join(evidencePath, 'build.log'));
|
|
62
|
+
checks.push({
|
|
63
|
+
name: 'Evidence captured',
|
|
64
|
+
passed: hasEvidence,
|
|
65
|
+
message: hasEvidence ? 'Evidence files found' : 'No evidence files found'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Check 2: Build report exists
|
|
69
|
+
const hasBuildReport = await sessionFileExists('build_report.md');
|
|
70
|
+
checks.push({
|
|
71
|
+
name: 'Build report',
|
|
72
|
+
passed: hasBuildReport,
|
|
73
|
+
message: hasBuildReport ? 'build_report.md exists' : 'Missing build_report.md'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Check 3: Try npm test if package.json exists
|
|
77
|
+
const npmTestResult = await runNpmTest();
|
|
78
|
+
if (npmTestResult.ran) {
|
|
79
|
+
checks.push({
|
|
80
|
+
name: 'npm test',
|
|
81
|
+
passed: npmTestResult.passed,
|
|
82
|
+
message: npmTestResult.message
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check 4: Try npm run lint if available
|
|
87
|
+
const lintResult = await runNpmLint();
|
|
88
|
+
if (lintResult.ran) {
|
|
89
|
+
checks.push({
|
|
90
|
+
name: 'npm run lint',
|
|
91
|
+
passed: lintResult.passed,
|
|
92
|
+
message: lintResult.message
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check 5: Git status clean (no uncommitted changes)
|
|
97
|
+
const gitStatus = await checkGitStatus();
|
|
98
|
+
checks.push({
|
|
99
|
+
name: 'Git status',
|
|
100
|
+
passed: gitStatus.clean,
|
|
101
|
+
message: gitStatus.message
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
spinner.stop();
|
|
105
|
+
|
|
106
|
+
// Display automated check results
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(chalk.cyan('📋 Automated Checks:'));
|
|
109
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
110
|
+
|
|
111
|
+
for (const check of checks) {
|
|
112
|
+
const icon = check.passed ? chalk.green('✅') : chalk.red('❌');
|
|
113
|
+
console.log(` ${icon} ${check.name}: ${chalk.gray(check.message)}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
117
|
+
|
|
118
|
+
// If skip-manual flag, auto-determine result
|
|
119
|
+
if (options.skipManual) {
|
|
120
|
+
const allPassed = checks.every(c => c.passed);
|
|
121
|
+
await finalizeReview(allPassed, checks, projectName, sessionId, specHash);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Manual checklist
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(chalk.cyan('📝 Manual Verification:'));
|
|
128
|
+
|
|
129
|
+
// Read contract for acceptance criteria
|
|
130
|
+
let acceptanceCriteria = [];
|
|
131
|
+
if (await sessionFileExists('contract.md')) {
|
|
132
|
+
const contract = await readSessionFile('contract.md');
|
|
133
|
+
const match = contract.match(/## ✔️ Acceptance Criteria[\s\S]*?(?=\n---|\n##|$)/);
|
|
134
|
+
if (match) {
|
|
135
|
+
const criteriaText = match[0];
|
|
136
|
+
const criteriaLines = criteriaText.match(/- \[[ x]\] .+/g) || [];
|
|
137
|
+
acceptanceCriteria = criteriaLines.map(line => line.replace(/- \[[ x]\] /, ''));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (acceptanceCriteria.length > 0) {
|
|
142
|
+
console.log(chalk.gray('\nVerify each acceptance criterion from the contract:\n'));
|
|
143
|
+
|
|
144
|
+
const { verified } = await inquirer.prompt([
|
|
145
|
+
{
|
|
146
|
+
type: 'checkbox',
|
|
147
|
+
name: 'verified',
|
|
148
|
+
message: 'Check all criteria that are met:',
|
|
149
|
+
choices: acceptanceCriteria.map(c => ({ name: c, checked: false }))
|
|
150
|
+
}
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
const allCriteriaMet = verified.length === acceptanceCriteria.length;
|
|
154
|
+
|
|
155
|
+
checks.push({
|
|
156
|
+
name: 'Acceptance criteria',
|
|
157
|
+
passed: allCriteriaMet,
|
|
158
|
+
message: `${verified.length}/${acceptanceCriteria.length} criteria verified`
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Final confirmation
|
|
163
|
+
console.log();
|
|
164
|
+
const { approve } = await inquirer.prompt([
|
|
165
|
+
{
|
|
166
|
+
type: 'confirm',
|
|
167
|
+
name: 'approve',
|
|
168
|
+
message: 'Do you approve this build for release?',
|
|
169
|
+
default: false
|
|
170
|
+
}
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const allPassed = approve && checks.every(c => c.passed);
|
|
174
|
+
await finalizeReview(allPassed, checks, projectName, sessionId, specHash);
|
|
175
|
+
|
|
176
|
+
} catch (error) {
|
|
177
|
+
spinner.fail('Review failed');
|
|
178
|
+
printError(error.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function finalizeReview(passed, checks, projectName, sessionId, specHash) {
|
|
184
|
+
const spinner = ora('Generating review report...').start();
|
|
185
|
+
|
|
186
|
+
// Generate report
|
|
187
|
+
const reportContent = getReviewReportTemplate(
|
|
188
|
+
projectName,
|
|
189
|
+
sessionId,
|
|
190
|
+
specHash,
|
|
191
|
+
checks,
|
|
192
|
+
passed
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
await writeSessionFile('review_report.md', reportContent);
|
|
196
|
+
|
|
197
|
+
// Transition state
|
|
198
|
+
if (passed) {
|
|
199
|
+
await transitionTo(STATES.REVIEW_PASSED, 'review_passed');
|
|
200
|
+
spinner.succeed('Review PASSED!');
|
|
201
|
+
|
|
202
|
+
const content = `✅ REVIEW PASSED
|
|
203
|
+
|
|
204
|
+
Project: ${projectName}
|
|
205
|
+
Spec Hash: ${specHash}
|
|
206
|
+
|
|
207
|
+
All checks passed!
|
|
208
|
+
Ready for release.`;
|
|
209
|
+
|
|
210
|
+
console.log();
|
|
211
|
+
printBox(content, { borderColor: 'green' });
|
|
212
|
+
printNextStep('Run `vibecode snapshot` to create release');
|
|
213
|
+
|
|
214
|
+
} else {
|
|
215
|
+
await transitionTo(STATES.REVIEW_FAILED, 'review_failed');
|
|
216
|
+
spinner.warn('Review FAILED');
|
|
217
|
+
|
|
218
|
+
const failedChecks = checks.filter(c => !c.passed);
|
|
219
|
+
|
|
220
|
+
const content = `❌ REVIEW FAILED
|
|
221
|
+
|
|
222
|
+
Project: ${projectName}
|
|
223
|
+
Spec Hash: ${specHash}
|
|
224
|
+
|
|
225
|
+
Failed checks:
|
|
226
|
+
${failedChecks.map(c => ` • ${c.name}: ${c.message}`).join('\n')}`;
|
|
227
|
+
|
|
228
|
+
console.log();
|
|
229
|
+
printBox(content, { borderColor: 'red' });
|
|
230
|
+
printNextStep('Fix issues and run `vibecode build --start` to rebuild');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runNpmTest() {
|
|
235
|
+
try {
|
|
236
|
+
// Check if package.json exists
|
|
237
|
+
const packageExists = await pathExists('package.json');
|
|
238
|
+
if (!packageExists) {
|
|
239
|
+
return { ran: false };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check if test script exists
|
|
243
|
+
const fs = await import('fs-extra');
|
|
244
|
+
const pkg = await fs.default.readJson('package.json');
|
|
245
|
+
if (!pkg.scripts?.test || pkg.scripts.test.includes('no test specified')) {
|
|
246
|
+
return { ran: false };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Run tests
|
|
250
|
+
await execAsync('npm test', { timeout: 120000 });
|
|
251
|
+
return { ran: true, passed: true, message: 'All tests passed' };
|
|
252
|
+
|
|
253
|
+
} catch (error) {
|
|
254
|
+
return { ran: true, passed: false, message: error.message.split('\n')[0] };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function runNpmLint() {
|
|
259
|
+
try {
|
|
260
|
+
const packageExists = await pathExists('package.json');
|
|
261
|
+
if (!packageExists) {
|
|
262
|
+
return { ran: false };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const fs = await import('fs-extra');
|
|
266
|
+
const pkg = await fs.default.readJson('package.json');
|
|
267
|
+
if (!pkg.scripts?.lint) {
|
|
268
|
+
return { ran: false };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
await execAsync('npm run lint', { timeout: 60000 });
|
|
272
|
+
return { ran: true, passed: true, message: 'No linting errors' };
|
|
273
|
+
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return { ran: true, passed: false, message: 'Linting errors found' };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function checkGitStatus() {
|
|
280
|
+
try {
|
|
281
|
+
const { stdout } = await execAsync('git status --porcelain');
|
|
282
|
+
const clean = stdout.trim() === '';
|
|
283
|
+
return {
|
|
284
|
+
clean,
|
|
285
|
+
message: clean ? 'Working tree clean' : 'Uncommitted changes exist'
|
|
286
|
+
};
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return { clean: true, message: 'Git not available' };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Snapshot Command
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
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
|
+
getSessionFilesStatus
|
|
17
|
+
} from '../core/session.js';
|
|
18
|
+
import { getCurrentState, transitionTo } from '../core/state-machine.js';
|
|
19
|
+
import { getSpecHash } from '../core/contract.js';
|
|
20
|
+
import { STATES } from '../config/constants.js';
|
|
21
|
+
import { getManifestTemplate } from '../config/templates.js';
|
|
22
|
+
import { pathExists, readJson, writeJson, appendToFile } from '../utils/files.js';
|
|
23
|
+
import { printBox, printError, printSuccess, printWarning, printNextStep } from '../ui/output.js';
|
|
24
|
+
|
|
25
|
+
const execAsync = promisify(exec);
|
|
26
|
+
|
|
27
|
+
export async function snapshotCommand(options = {}) {
|
|
28
|
+
try {
|
|
29
|
+
// Check workspace
|
|
30
|
+
if (!await workspaceExists()) {
|
|
31
|
+
printError('No Vibecode workspace found. Run `vibecode init` first.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check state
|
|
36
|
+
const currentState = await getCurrentState();
|
|
37
|
+
if (currentState !== STATES.REVIEW_PASSED) {
|
|
38
|
+
printError(`Cannot create snapshot in state: ${currentState}`);
|
|
39
|
+
console.log('Review must pass first. Run `vibecode review`.');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const projectName = await getProjectName();
|
|
44
|
+
const sessionId = await getCurrentSessionId();
|
|
45
|
+
const sessionPath = await getCurrentSessionPath();
|
|
46
|
+
const specHash = await getSpecHash();
|
|
47
|
+
|
|
48
|
+
// Determine version bump
|
|
49
|
+
let versionBump = options.major ? 'major' : options.minor ? 'minor' : options.patch ? 'patch' : null;
|
|
50
|
+
|
|
51
|
+
if (!versionBump) {
|
|
52
|
+
const { bump } = await inquirer.prompt([
|
|
53
|
+
{
|
|
54
|
+
type: 'list',
|
|
55
|
+
name: 'bump',
|
|
56
|
+
message: 'What type of release is this?',
|
|
57
|
+
choices: [
|
|
58
|
+
{ name: 'patch (bug fixes)', value: 'patch' },
|
|
59
|
+
{ name: 'minor (new features)', value: 'minor' },
|
|
60
|
+
{ name: 'major (breaking changes)', value: 'major' }
|
|
61
|
+
],
|
|
62
|
+
default: 'patch'
|
|
63
|
+
}
|
|
64
|
+
]);
|
|
65
|
+
versionBump = bump;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const spinner = ora('Creating snapshot...').start();
|
|
69
|
+
|
|
70
|
+
// Get current version and calculate new version
|
|
71
|
+
let currentVersion = '0.0.0';
|
|
72
|
+
let newVersion = '0.0.1';
|
|
73
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
74
|
+
|
|
75
|
+
if (await pathExists(packageJsonPath)) {
|
|
76
|
+
const pkg = await readJson(packageJsonPath);
|
|
77
|
+
currentVersion = pkg.version || '0.0.0';
|
|
78
|
+
newVersion = bumpVersion(currentVersion, versionBump);
|
|
79
|
+
|
|
80
|
+
// Update package.json
|
|
81
|
+
spinner.text = 'Updating package.json...';
|
|
82
|
+
pkg.version = newVersion;
|
|
83
|
+
await writeJson(packageJsonPath, pkg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Collect session files
|
|
87
|
+
spinner.text = 'Collecting files...';
|
|
88
|
+
const files = await collectSessionFiles(sessionPath);
|
|
89
|
+
|
|
90
|
+
// Generate manifest
|
|
91
|
+
spinner.text = 'Generating manifest...';
|
|
92
|
+
const manifest = getManifestTemplate(
|
|
93
|
+
projectName,
|
|
94
|
+
newVersion,
|
|
95
|
+
specHash,
|
|
96
|
+
sessionId,
|
|
97
|
+
files
|
|
98
|
+
);
|
|
99
|
+
await writeSessionFile('manifest.json', JSON.stringify(manifest, null, 2));
|
|
100
|
+
|
|
101
|
+
// Update CHANGELOG
|
|
102
|
+
spinner.text = 'Updating CHANGELOG...';
|
|
103
|
+
await updateChangelog(newVersion, specHash, projectName);
|
|
104
|
+
|
|
105
|
+
// Create git tag if git is available
|
|
106
|
+
spinner.text = 'Creating git tag...';
|
|
107
|
+
const gitTagged = await createGitTag(newVersion, specHash);
|
|
108
|
+
|
|
109
|
+
// Update state
|
|
110
|
+
const stateData = await loadState();
|
|
111
|
+
stateData.shipped_version = newVersion;
|
|
112
|
+
stateData.shipped_at = new Date().toISOString();
|
|
113
|
+
await saveState(stateData);
|
|
114
|
+
|
|
115
|
+
// Transition state
|
|
116
|
+
await transitionTo(STATES.SHIPPED, 'snapshot_created');
|
|
117
|
+
|
|
118
|
+
spinner.succeed('Snapshot created!');
|
|
119
|
+
|
|
120
|
+
// Success output
|
|
121
|
+
const content = `🚀 SHIPPED!
|
|
122
|
+
|
|
123
|
+
Project: ${projectName}
|
|
124
|
+
Version: ${currentVersion} → ${newVersion}
|
|
125
|
+
Spec Hash: ${specHash}
|
|
126
|
+
Session: ${sessionId}
|
|
127
|
+
|
|
128
|
+
Files:
|
|
129
|
+
• manifest.json - Release manifest
|
|
130
|
+
• CHANGELOG.md - Updated
|
|
131
|
+
${gitTagged ? `• Git tag: v${newVersion}` : ''}`;
|
|
132
|
+
|
|
133
|
+
console.log();
|
|
134
|
+
printBox(content, { borderColor: 'green' });
|
|
135
|
+
|
|
136
|
+
console.log();
|
|
137
|
+
console.log(chalk.cyan('📦 Release Summary:'));
|
|
138
|
+
console.log(chalk.gray(` Version: ${newVersion}`));
|
|
139
|
+
console.log(chalk.gray(` Spec Hash: ${specHash}`));
|
|
140
|
+
console.log(chalk.gray(` Files: ${files.length} session files`));
|
|
141
|
+
if (gitTagged) {
|
|
142
|
+
console.log(chalk.gray(` Git Tag: v${newVersion}`));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log();
|
|
146
|
+
printSuccess('Your project has been shipped!');
|
|
147
|
+
|
|
148
|
+
if (await pathExists(packageJsonPath)) {
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(chalk.cyan('📤 To publish:'));
|
|
151
|
+
console.log(chalk.gray(' npm publish'));
|
|
152
|
+
console.log(chalk.gray(' # or'));
|
|
153
|
+
console.log(chalk.gray(' git push && git push --tags'));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
} catch (error) {
|
|
157
|
+
printError(error.message);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function bumpVersion(version, type) {
|
|
163
|
+
const parts = version.split('.').map(Number);
|
|
164
|
+
|
|
165
|
+
switch (type) {
|
|
166
|
+
case 'major':
|
|
167
|
+
return `${parts[0] + 1}.0.0`;
|
|
168
|
+
case 'minor':
|
|
169
|
+
return `${parts[0]}.${parts[1] + 1}.0`;
|
|
170
|
+
case 'patch':
|
|
171
|
+
default:
|
|
172
|
+
return `${parts[0]}.${parts[1]}.${parts[2] + 1}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function collectSessionFiles(sessionPath) {
|
|
177
|
+
const files = [];
|
|
178
|
+
const fs = await import('fs-extra');
|
|
179
|
+
|
|
180
|
+
const sessionFiles = [
|
|
181
|
+
'intake.md',
|
|
182
|
+
'blueprint.md',
|
|
183
|
+
'contract.md',
|
|
184
|
+
'plan.md',
|
|
185
|
+
'coder_pack.md',
|
|
186
|
+
'build_report.md',
|
|
187
|
+
'review_report.md'
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
for (const file of sessionFiles) {
|
|
191
|
+
const filePath = path.join(sessionPath, file);
|
|
192
|
+
if (await pathExists(filePath)) {
|
|
193
|
+
const stat = await fs.default.stat(filePath);
|
|
194
|
+
files.push({
|
|
195
|
+
name: file,
|
|
196
|
+
size: stat.size,
|
|
197
|
+
modified: stat.mtime.toISOString()
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return files;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function updateChangelog(version, specHash, projectName) {
|
|
206
|
+
const changelogPath = path.join(process.cwd(), 'CHANGELOG.md');
|
|
207
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
208
|
+
|
|
209
|
+
const newEntry = `
|
|
210
|
+
## [${version}] - ${timestamp}
|
|
211
|
+
|
|
212
|
+
### Shipped via Vibecode CLI
|
|
213
|
+
- Spec Hash: \`${specHash}\`
|
|
214
|
+
- Project: ${projectName}
|
|
215
|
+
- Generated by vibecode-cli
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
if (await pathExists(changelogPath)) {
|
|
221
|
+
const fs = await import('fs-extra');
|
|
222
|
+
const content = await fs.default.readFile(changelogPath, 'utf-8');
|
|
223
|
+
|
|
224
|
+
// Insert after the header
|
|
225
|
+
const headerMatch = content.match(/^# .+\n/);
|
|
226
|
+
if (headerMatch) {
|
|
227
|
+
const newContent = headerMatch[0] + newEntry + content.slice(headerMatch[0].length);
|
|
228
|
+
await fs.default.writeFile(changelogPath, newContent, 'utf-8');
|
|
229
|
+
} else {
|
|
230
|
+
await fs.default.writeFile(changelogPath, `# Changelog\n${newEntry}${content}`, 'utf-8');
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
const fs = await import('fs-extra');
|
|
234
|
+
await fs.default.writeFile(changelogPath, `# Changelog\n${newEntry}`, 'utf-8');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function createGitTag(version, specHash) {
|
|
239
|
+
try {
|
|
240
|
+
// Check if git is available
|
|
241
|
+
await execAsync('git status');
|
|
242
|
+
|
|
243
|
+
// Create annotated tag
|
|
244
|
+
const tagMessage = `Release v${version}\n\nSpec Hash: ${specHash}\nGenerated by vibecode-cli`;
|
|
245
|
+
await execAsync(`git tag -a v${version} -m "${tagMessage}"`);
|
|
246
|
+
|
|
247
|
+
return true;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
// Git not available or tag failed
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
package/src/commands/status.js
CHANGED
|
@@ -58,7 +58,12 @@ export async function statusCommand(options = {}) {
|
|
|
58
58
|
console.log(chalk.cyan('📁 Session Files:'));
|
|
59
59
|
console.log(chalk.gray(` ├── intake.md ${filesStatus.intake ? '✅' : '⬜'}`));
|
|
60
60
|
console.log(chalk.gray(` ├── blueprint.md ${filesStatus.blueprint ? '✅' : '⬜'}`));
|
|
61
|
-
console.log(chalk.gray(`
|
|
61
|
+
console.log(chalk.gray(` ├── contract.md ${filesStatus.contract ? (specHash ? '🔒' : '📝') : '⬜'}`));
|
|
62
|
+
console.log(chalk.gray(` ├── plan.md ${filesStatus.plan ? '✅' : '⬜'}`));
|
|
63
|
+
console.log(chalk.gray(` ├── coder_pack.md ${filesStatus.coderPack ? '✅' : '⬜'}`));
|
|
64
|
+
console.log(chalk.gray(` ├── build_report.md ${filesStatus.buildReport ? '✅' : '⬜'}`));
|
|
65
|
+
console.log(chalk.gray(` ├── review_report.md ${filesStatus.reviewReport ? '✅' : '⬜'}`));
|
|
66
|
+
console.log(chalk.gray(` └── manifest.json ${filesStatus.manifest ? '✅' : '⬜'}`));
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
// Next step hint
|
|
@@ -67,7 +72,13 @@ export async function statusCommand(options = {}) {
|
|
|
67
72
|
[STATES.INTAKE_CAPTURED]: 'Create blueprint for your project',
|
|
68
73
|
[STATES.BLUEPRINT_DRAFTED]: 'Create contract with deliverables',
|
|
69
74
|
[STATES.CONTRACT_DRAFTED]: 'Run `vibecode lock` to finalize contract',
|
|
70
|
-
[STATES.CONTRACT_LOCKED]: '
|
|
75
|
+
[STATES.CONTRACT_LOCKED]: 'Run `vibecode plan` to create execution plan',
|
|
76
|
+
[STATES.PLAN_CREATED]: 'Run `vibecode build --start` to begin building',
|
|
77
|
+
[STATES.BUILD_IN_PROGRESS]: 'Build in progress. Run `vibecode build --complete` when done',
|
|
78
|
+
[STATES.BUILD_DONE]: 'Run `vibecode review` to validate your build',
|
|
79
|
+
[STATES.REVIEW_PASSED]: 'Run `vibecode snapshot` to create release',
|
|
80
|
+
[STATES.REVIEW_FAILED]: 'Fix issues and run `vibecode build --start` to rebuild',
|
|
81
|
+
[STATES.SHIPPED]: 'Project shipped! Start a new session with `vibecode start`',
|
|
71
82
|
};
|
|
72
83
|
|
|
73
84
|
printNextStep(hints[stateData.current_state] || 'Continue with your workflow');
|