@nclamvn/vibecode-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/bin/vibecode.js +65 -0
- package/package.json +45 -0
- package/src/commands/doctor.js +130 -0
- package/src/commands/init.js +62 -0
- package/src/commands/lock.js +105 -0
- package/src/commands/start.js +222 -0
- package/src/commands/status.js +80 -0
- package/src/config/constants.js +89 -0
- package/src/config/templates.js +194 -0
- package/src/core/contract.js +103 -0
- package/src/core/session.js +115 -0
- package/src/core/state-machine.js +95 -0
- package/src/core/workspace.js +152 -0
- package/src/index.js +12 -0
- package/src/ui/branding.js +36 -0
- package/src/ui/output.js +96 -0
- package/src/ui/prompts.js +120 -0
- package/src/utils/files.js +84 -0
- package/src/utils/hash.js +34 -0
- package/templates/blueprint.md +61 -0
- package/templates/contract.md +61 -0
- package/templates/intake.md +37 -0
- package/templates/vibecode.yaml +15 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Start Command
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import {
|
|
9
|
+
workspaceExists,
|
|
10
|
+
loadState,
|
|
11
|
+
getProjectName
|
|
12
|
+
} from '../core/workspace.js';
|
|
13
|
+
import {
|
|
14
|
+
createSession,
|
|
15
|
+
getCurrentSessionId,
|
|
16
|
+
getCurrentSessionPath,
|
|
17
|
+
createIntake,
|
|
18
|
+
createBlueprint,
|
|
19
|
+
createContract,
|
|
20
|
+
getSessionFilesStatus
|
|
21
|
+
} from '../core/session.js';
|
|
22
|
+
import { getCurrentState, transitionTo } from '../core/state-machine.js';
|
|
23
|
+
import { STATES } from '../config/constants.js';
|
|
24
|
+
import {
|
|
25
|
+
printBox,
|
|
26
|
+
printError,
|
|
27
|
+
printSuccess,
|
|
28
|
+
printProgress,
|
|
29
|
+
printStatus,
|
|
30
|
+
printNextStep
|
|
31
|
+
} from '../ui/output.js';
|
|
32
|
+
import { printWelcome, printDivider } from '../ui/branding.js';
|
|
33
|
+
import { askSimpleDescription, askNextAction, confirmAction } from '../ui/prompts.js';
|
|
34
|
+
|
|
35
|
+
export async function startCommand(options = {}) {
|
|
36
|
+
try {
|
|
37
|
+
// Check workspace
|
|
38
|
+
if (!await workspaceExists()) {
|
|
39
|
+
printError('No Vibecode workspace found.');
|
|
40
|
+
console.log('Run `vibecode init` first.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Load current state
|
|
45
|
+
const currentState = await getCurrentState();
|
|
46
|
+
const projectName = await getProjectName();
|
|
47
|
+
let sessionId = await getCurrentSessionId();
|
|
48
|
+
|
|
49
|
+
// Print welcome
|
|
50
|
+
printWelcome();
|
|
51
|
+
printDivider();
|
|
52
|
+
printStatus(projectName, currentState, sessionId);
|
|
53
|
+
printProgress(currentState);
|
|
54
|
+
printDivider();
|
|
55
|
+
|
|
56
|
+
// Handle based on state
|
|
57
|
+
switch (currentState) {
|
|
58
|
+
case STATES.INIT:
|
|
59
|
+
await handleInit(projectName);
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case STATES.INTAKE_CAPTURED:
|
|
63
|
+
await handleIntakeCaptured(projectName);
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case STATES.BLUEPRINT_DRAFTED:
|
|
67
|
+
await handleBlueprintDrafted(projectName);
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
case STATES.CONTRACT_DRAFTED:
|
|
71
|
+
await handleContractDrafted(projectName);
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case STATES.CONTRACT_LOCKED:
|
|
75
|
+
await handleContractLocked(projectName);
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
default:
|
|
79
|
+
console.log(chalk.yellow(`State ${currentState} not fully handled in MVP.`));
|
|
80
|
+
console.log('This will be implemented in Phase B.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
printError(error.message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function handleInit(projectName) {
|
|
90
|
+
console.log();
|
|
91
|
+
console.log(chalk.cyan('🏗️ Bắt đầu dự án mới!\n'));
|
|
92
|
+
|
|
93
|
+
// Get project description
|
|
94
|
+
const description = await askSimpleDescription();
|
|
95
|
+
|
|
96
|
+
if (!description || description.trim().length < 10) {
|
|
97
|
+
printError('Mô tả quá ngắn. Vui lòng thử lại.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const spinner = ora('Creating session...').start();
|
|
102
|
+
|
|
103
|
+
// Create session
|
|
104
|
+
const sessionId = await createSession(projectName);
|
|
105
|
+
spinner.text = 'Capturing intake...';
|
|
106
|
+
|
|
107
|
+
// Create intake
|
|
108
|
+
await createIntake(projectName, description, sessionId);
|
|
109
|
+
|
|
110
|
+
// Transition state
|
|
111
|
+
await transitionTo(STATES.INTAKE_CAPTURED, 'intake_captured');
|
|
112
|
+
|
|
113
|
+
spinner.succeed('Intake captured!');
|
|
114
|
+
|
|
115
|
+
const sessionPath = await getCurrentSessionPath();
|
|
116
|
+
console.log();
|
|
117
|
+
printSuccess(`Intake saved to: ${chalk.gray(path.join(sessionPath, 'intake.md'))}`);
|
|
118
|
+
printNextStep('Review intake.md then run `vibecode start` to continue');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function handleIntakeCaptured(projectName) {
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(chalk.cyan('📋 Intake đã được capture. Sẵn sàng tạo Blueprint.\n'));
|
|
124
|
+
|
|
125
|
+
const action = await askNextAction(STATES.INTAKE_CAPTURED);
|
|
126
|
+
|
|
127
|
+
if (action === 'blueprint') {
|
|
128
|
+
const spinner = ora('Creating blueprint...').start();
|
|
129
|
+
const sessionId = await getCurrentSessionId();
|
|
130
|
+
|
|
131
|
+
await createBlueprint(projectName, sessionId);
|
|
132
|
+
await transitionTo(STATES.BLUEPRINT_DRAFTED, 'blueprint_created');
|
|
133
|
+
|
|
134
|
+
spinner.succeed('Blueprint created!');
|
|
135
|
+
|
|
136
|
+
const sessionPath = await getCurrentSessionPath();
|
|
137
|
+
printSuccess(`Blueprint saved to: ${chalk.gray(path.join(sessionPath, 'blueprint.md'))}`);
|
|
138
|
+
printNextStep('Edit blueprint.md then run `vibecode start` to continue');
|
|
139
|
+
|
|
140
|
+
} else if (action === 'view_intake') {
|
|
141
|
+
const sessionPath = await getCurrentSessionPath();
|
|
142
|
+
console.log(chalk.gray(`\nIntake file: ${path.join(sessionPath, 'intake.md')}`));
|
|
143
|
+
|
|
144
|
+
} else {
|
|
145
|
+
console.log('Goodbye!');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function handleBlueprintDrafted(projectName) {
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(chalk.cyan('📘 Blueprint đã sẵn sàng. Tạo Contract?\n'));
|
|
152
|
+
|
|
153
|
+
const action = await askNextAction(STATES.BLUEPRINT_DRAFTED);
|
|
154
|
+
|
|
155
|
+
if (action === 'contract') {
|
|
156
|
+
const spinner = ora('Creating contract...').start();
|
|
157
|
+
const sessionId = await getCurrentSessionId();
|
|
158
|
+
|
|
159
|
+
await createContract(projectName, sessionId);
|
|
160
|
+
await transitionTo(STATES.CONTRACT_DRAFTED, 'contract_created');
|
|
161
|
+
|
|
162
|
+
spinner.succeed('Contract created!');
|
|
163
|
+
|
|
164
|
+
const sessionPath = await getCurrentSessionPath();
|
|
165
|
+
printSuccess(`Contract saved to: ${chalk.gray(path.join(sessionPath, 'contract.md'))}`);
|
|
166
|
+
printNextStep('Edit contract.md then run `vibecode lock` to finalize');
|
|
167
|
+
|
|
168
|
+
} else if (action === 'edit_blueprint') {
|
|
169
|
+
const sessionPath = await getCurrentSessionPath();
|
|
170
|
+
console.log(chalk.gray(`\nBlueprint file: ${path.join(sessionPath, 'blueprint.md')}`));
|
|
171
|
+
|
|
172
|
+
} else {
|
|
173
|
+
console.log('Goodbye!');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function handleContractDrafted(projectName) {
|
|
178
|
+
console.log();
|
|
179
|
+
console.log(chalk.cyan('📜 Contract đã được tạo. Sẵn sàng lock?\n'));
|
|
180
|
+
|
|
181
|
+
const sessionPath = await getCurrentSessionPath();
|
|
182
|
+
console.log(chalk.gray(`Contract file: ${path.join(sessionPath, 'contract.md')}`));
|
|
183
|
+
console.log();
|
|
184
|
+
|
|
185
|
+
const action = await askNextAction(STATES.CONTRACT_DRAFTED);
|
|
186
|
+
|
|
187
|
+
if (action === 'lock') {
|
|
188
|
+
console.log(chalk.yellow('\nRun `vibecode lock` to lock the contract.'));
|
|
189
|
+
|
|
190
|
+
} else if (action === 'edit_contract') {
|
|
191
|
+
console.log(chalk.gray(`\nEdit: ${path.join(sessionPath, 'contract.md')}`));
|
|
192
|
+
|
|
193
|
+
} else {
|
|
194
|
+
console.log('Goodbye!');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function handleContractLocked(projectName) {
|
|
199
|
+
console.log();
|
|
200
|
+
|
|
201
|
+
const content = `🎉 Contract is LOCKED!
|
|
202
|
+
|
|
203
|
+
Your project is ready for build.
|
|
204
|
+
|
|
205
|
+
In Phase B, you will be able to:
|
|
206
|
+
• Run \`vibecode build\` to start building
|
|
207
|
+
• Run \`vibecode review\` for QA checks
|
|
208
|
+
|
|
209
|
+
For now, you can:
|
|
210
|
+
• Transfer the contract to Claude Code
|
|
211
|
+
• Use Vibecode Kit prompts for building`;
|
|
212
|
+
|
|
213
|
+
printBox(content, { borderColor: 'green' });
|
|
214
|
+
|
|
215
|
+
const sessionPath = await getCurrentSessionPath();
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(chalk.cyan('📁 Your files:'));
|
|
218
|
+
console.log(chalk.gray(` ${sessionPath}/`));
|
|
219
|
+
console.log(chalk.gray(' ├── intake.md'));
|
|
220
|
+
console.log(chalk.gray(' ├── blueprint.md'));
|
|
221
|
+
console.log(chalk.gray(' └── contract.md 🔒'));
|
|
222
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Status Command
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import {
|
|
7
|
+
workspaceExists,
|
|
8
|
+
loadState,
|
|
9
|
+
getProjectName
|
|
10
|
+
} from '../core/workspace.js';
|
|
11
|
+
import { getCurrentSessionId, getSessionFilesStatus } from '../core/session.js';
|
|
12
|
+
import { getSpecHash } from '../core/contract.js';
|
|
13
|
+
import {
|
|
14
|
+
printBox,
|
|
15
|
+
printError,
|
|
16
|
+
printProgress,
|
|
17
|
+
printStatus,
|
|
18
|
+
printNextStep
|
|
19
|
+
} from '../ui/output.js';
|
|
20
|
+
import { STATES } from '../config/constants.js';
|
|
21
|
+
|
|
22
|
+
export async function statusCommand(options = {}) {
|
|
23
|
+
try {
|
|
24
|
+
// Check workspace exists
|
|
25
|
+
if (!await workspaceExists()) {
|
|
26
|
+
printError('No Vibecode workspace found.');
|
|
27
|
+
console.log('Run `vibecode init` first.');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Load data
|
|
32
|
+
const stateData = await loadState();
|
|
33
|
+
const projectName = await getProjectName();
|
|
34
|
+
const sessionId = await getCurrentSessionId();
|
|
35
|
+
const specHash = await getSpecHash();
|
|
36
|
+
const filesStatus = await getSessionFilesStatus();
|
|
37
|
+
|
|
38
|
+
// Output
|
|
39
|
+
if (options.json) {
|
|
40
|
+
console.log(JSON.stringify({
|
|
41
|
+
project: projectName,
|
|
42
|
+
state: stateData.current_state,
|
|
43
|
+
session: sessionId,
|
|
44
|
+
specHash: specHash,
|
|
45
|
+
files: filesStatus
|
|
46
|
+
}, null, 2));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Visual output
|
|
51
|
+
console.log();
|
|
52
|
+
printStatus(projectName, stateData.current_state, sessionId, specHash);
|
|
53
|
+
printProgress(stateData.current_state);
|
|
54
|
+
|
|
55
|
+
// Files status
|
|
56
|
+
if (filesStatus) {
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(chalk.cyan('📁 Session Files:'));
|
|
59
|
+
console.log(chalk.gray(` ├── intake.md ${filesStatus.intake ? '✅' : '⬜'}`));
|
|
60
|
+
console.log(chalk.gray(` ├── blueprint.md ${filesStatus.blueprint ? '✅' : '⬜'}`));
|
|
61
|
+
console.log(chalk.gray(` └── contract.md ${filesStatus.contract ? (specHash ? '🔒' : '📝') : '⬜'}`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Next step hint
|
|
65
|
+
const hints = {
|
|
66
|
+
[STATES.INIT]: 'Run `vibecode start` to capture requirements',
|
|
67
|
+
[STATES.INTAKE_CAPTURED]: 'Create blueprint for your project',
|
|
68
|
+
[STATES.BLUEPRINT_DRAFTED]: 'Create contract with deliverables',
|
|
69
|
+
[STATES.CONTRACT_DRAFTED]: 'Run `vibecode lock` to finalize contract',
|
|
70
|
+
[STATES.CONTRACT_LOCKED]: 'Contract locked! Ready for build (Phase B)',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
printNextStep(hints[stateData.current_state] || 'Continue with your workflow');
|
|
74
|
+
console.log();
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
printError(error.message);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Constants & Configuration
|
|
3
|
+
// Spec Hash: 0fe43335f5a325e3279a079ce616c052
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
export const VERSION = '1.0.0';
|
|
7
|
+
export const SPEC_HASH = '0fe43335f5a325e3279a079ce616c052';
|
|
8
|
+
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
// State Machine
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const STATES = {
|
|
14
|
+
INIT: 'INIT',
|
|
15
|
+
INTAKE_CAPTURED: 'INTAKE_CAPTURED',
|
|
16
|
+
BLUEPRINT_DRAFTED: 'BLUEPRINT_DRAFTED',
|
|
17
|
+
CONTRACT_DRAFTED: 'CONTRACT_DRAFTED',
|
|
18
|
+
CONTRACT_LOCKED: 'CONTRACT_LOCKED',
|
|
19
|
+
BUILD_IN_PROGRESS: 'BUILD_IN_PROGRESS',
|
|
20
|
+
BUILD_DONE: 'BUILD_DONE',
|
|
21
|
+
REVIEW_PASSED: 'REVIEW_PASSED',
|
|
22
|
+
REVIEW_FAILED: 'REVIEW_FAILED'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const TRANSITIONS = {
|
|
26
|
+
[STATES.INIT]: [STATES.INTAKE_CAPTURED],
|
|
27
|
+
[STATES.INTAKE_CAPTURED]: [STATES.BLUEPRINT_DRAFTED],
|
|
28
|
+
[STATES.BLUEPRINT_DRAFTED]: [STATES.CONTRACT_DRAFTED, STATES.INTAKE_CAPTURED],
|
|
29
|
+
[STATES.CONTRACT_DRAFTED]: [STATES.CONTRACT_LOCKED, STATES.BLUEPRINT_DRAFTED],
|
|
30
|
+
[STATES.CONTRACT_LOCKED]: [STATES.BUILD_IN_PROGRESS],
|
|
31
|
+
[STATES.BUILD_IN_PROGRESS]: [STATES.BUILD_DONE],
|
|
32
|
+
[STATES.BUILD_DONE]: [STATES.REVIEW_PASSED, STATES.REVIEW_FAILED],
|
|
33
|
+
[STATES.REVIEW_PASSED]: [],
|
|
34
|
+
[STATES.REVIEW_FAILED]: [STATES.BUILD_IN_PROGRESS, STATES.CONTRACT_DRAFTED]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// Progress Display Mapping
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export const PROGRESS_MAP = {
|
|
42
|
+
[STATES.INIT]: { intake: '🔄', blueprint: '⬜', contract: '⬜', build: '⬜', ship: '⬜' },
|
|
43
|
+
[STATES.INTAKE_CAPTURED]: { intake: '✅', blueprint: '🔄', contract: '⬜', build: '⬜', ship: '⬜' },
|
|
44
|
+
[STATES.BLUEPRINT_DRAFTED]: { intake: '✅', blueprint: '✅', contract: '🔄', build: '⬜', ship: '⬜' },
|
|
45
|
+
[STATES.CONTRACT_DRAFTED]: { intake: '✅', blueprint: '✅', contract: '🔄', build: '⬜', ship: '⬜' },
|
|
46
|
+
[STATES.CONTRACT_LOCKED]: { intake: '✅', blueprint: '✅', contract: '✅', build: '🔄', ship: '⬜' },
|
|
47
|
+
[STATES.BUILD_IN_PROGRESS]: { intake: '✅', blueprint: '✅', contract: '✅', build: '🔄', ship: '⬜' },
|
|
48
|
+
[STATES.BUILD_DONE]: { intake: '✅', blueprint: '✅', contract: '✅', build: '✅', ship: '🔄' },
|
|
49
|
+
[STATES.REVIEW_PASSED]: { intake: '✅', blueprint: '✅', contract: '✅', build: '✅', ship: '✅' },
|
|
50
|
+
[STATES.REVIEW_FAILED]: { intake: '✅', blueprint: '✅', contract: '✅', build: '⚠️', ship: '⬜' }
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
// File & Folder Names
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
export const WORKSPACE_DIR = '.vibecode';
|
|
58
|
+
export const CONFIG_FILE = 'vibecode.yaml';
|
|
59
|
+
export const STATE_FILE = 'state.json';
|
|
60
|
+
export const SESSIONS_DIR = 'sessions';
|
|
61
|
+
export const LIBRARY_DIR = 'library';
|
|
62
|
+
export const LOGS_DIR = 'logs';
|
|
63
|
+
export const AUDIT_LOG = 'audit.log';
|
|
64
|
+
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// Contract Validation
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export const CONTRACT_REQUIRED_SECTIONS = [
|
|
70
|
+
{ key: 'goal', name: 'Goal', pattern: /##\s*🎯\s*Goal/i },
|
|
71
|
+
{ key: 'inscope', name: 'In-Scope', pattern: /##\s*✅\s*In-Scope/i },
|
|
72
|
+
{ key: 'outscope', name: 'Out-of-Scope', pattern: /##\s*❌\s*Out-of-Scope/i },
|
|
73
|
+
{ key: 'deliverables', name: 'Deliverables', pattern: /##\s*📦\s*Deliverables/i },
|
|
74
|
+
{ key: 'acceptance', name: 'Acceptance Criteria', pattern: /##\s*✔️\s*Acceptance Criteria/i }
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
78
|
+
// UI Colors
|
|
79
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export const COLORS = {
|
|
82
|
+
primary: 'cyan',
|
|
83
|
+
success: 'green',
|
|
84
|
+
warning: 'yellow',
|
|
85
|
+
error: 'red',
|
|
86
|
+
muted: 'gray',
|
|
87
|
+
highlight: 'magenta',
|
|
88
|
+
info: 'blue'
|
|
89
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Template Generators
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get intake template with values filled
|
|
7
|
+
*/
|
|
8
|
+
export function getIntakeTemplate(projectName, description, sessionId) {
|
|
9
|
+
const timestamp = new Date().toISOString();
|
|
10
|
+
|
|
11
|
+
return `# 📥 INTAKE: ${projectName}
|
|
12
|
+
|
|
13
|
+
## Captured: ${timestamp}
|
|
14
|
+
## Session: ${sessionId}
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🎯 Mô tả dự án
|
|
19
|
+
|
|
20
|
+
${description}
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📋 Parsed Information
|
|
25
|
+
|
|
26
|
+
### Project Type
|
|
27
|
+
[To be detected by AI]
|
|
28
|
+
|
|
29
|
+
### Key Requirements
|
|
30
|
+
- [To be extracted from description]
|
|
31
|
+
|
|
32
|
+
### Target Users
|
|
33
|
+
[To be identified]
|
|
34
|
+
|
|
35
|
+
### Constraints
|
|
36
|
+
[To be noted]
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ✅ Status
|
|
41
|
+
|
|
42
|
+
- [x] Intake captured
|
|
43
|
+
- [ ] Ready for Blueprint
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
*Generated by Vibecode CLI v1.0*
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get blueprint template
|
|
53
|
+
*/
|
|
54
|
+
export function getBlueprintTemplate(projectName, sessionId) {
|
|
55
|
+
const timestamp = new Date().toISOString();
|
|
56
|
+
|
|
57
|
+
return `# 📘 BLUEPRINT: ${projectName}
|
|
58
|
+
|
|
59
|
+
## Session: ${sessionId}
|
|
60
|
+
## Created: ${timestamp}
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🎯 Project Overview
|
|
65
|
+
|
|
66
|
+
| Field | Value |
|
|
67
|
+
|-------|-------|
|
|
68
|
+
| Type | [Fill in] |
|
|
69
|
+
| Name | ${projectName} |
|
|
70
|
+
| Goal | [Fill in] |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 📐 Architecture
|
|
75
|
+
|
|
76
|
+
[Describe your architecture here]
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 🎨 Design System
|
|
81
|
+
|
|
82
|
+
### Colors
|
|
83
|
+
- Primary: #3B82F6
|
|
84
|
+
- Secondary: #10B981
|
|
85
|
+
- Accent: #F59E0B
|
|
86
|
+
|
|
87
|
+
### Typography
|
|
88
|
+
- Headings: Inter
|
|
89
|
+
- Body: Inter
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 💻 Tech Stack
|
|
94
|
+
|
|
95
|
+
- Framework: Next.js 14
|
|
96
|
+
- Styling: Tailwind CSS
|
|
97
|
+
- Database: [If needed]
|
|
98
|
+
- Auth: [If needed]
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 📁 File Structure
|
|
103
|
+
|
|
104
|
+
\`\`\`
|
|
105
|
+
src/
|
|
106
|
+
├── app/
|
|
107
|
+
├── components/
|
|
108
|
+
├── lib/
|
|
109
|
+
└── styles/
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ✅ Checkpoint
|
|
115
|
+
|
|
116
|
+
- [ ] Structure approved
|
|
117
|
+
- [ ] Design approved
|
|
118
|
+
- [ ] Tech stack approved
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
*Generated by Vibecode CLI v1.0*
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get contract template
|
|
128
|
+
*/
|
|
129
|
+
export function getContractTemplate(projectName, sessionId) {
|
|
130
|
+
const timestamp = new Date().toISOString();
|
|
131
|
+
|
|
132
|
+
return `# 📜 CONTRACT: ${projectName}
|
|
133
|
+
|
|
134
|
+
## Session: ${sessionId}
|
|
135
|
+
## Created: ${timestamp}
|
|
136
|
+
## Status: DRAFT
|
|
137
|
+
## Spec Hash: [hash when locked]
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 🎯 Goal
|
|
142
|
+
|
|
143
|
+
[Define your clear, specific goal here]
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## ✅ In-Scope
|
|
148
|
+
|
|
149
|
+
- [ ] [Deliverable 1]
|
|
150
|
+
- [ ] [Deliverable 2]
|
|
151
|
+
- [ ] [Deliverable 3]
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## ❌ Out-of-Scope
|
|
156
|
+
|
|
157
|
+
- [What is NOT included]
|
|
158
|
+
- [Explicitly excluded items]
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 📦 Deliverables
|
|
163
|
+
|
|
164
|
+
| # | Item | Description | Status |
|
|
165
|
+
|---|------|-------------|--------|
|
|
166
|
+
| 1 | [Item] | [Description] | ⬜ |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## ✔️ Acceptance Criteria
|
|
171
|
+
|
|
172
|
+
- [ ] [Criterion 1]
|
|
173
|
+
- [ ] [Criterion 2]
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## ⚠️ Risks & Mitigations
|
|
178
|
+
|
|
179
|
+
| Risk | Mitigation |
|
|
180
|
+
|------|------------|
|
|
181
|
+
| [Risk] | [Mitigation] |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 🔙 Rollback Plan
|
|
186
|
+
|
|
187
|
+
[Define rollback strategy]
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
*Generated by Vibecode CLI v1.0*
|
|
192
|
+
*Lock this contract with \`vibecode lock\` when ready*
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Contract Validation & Locking
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import { CONTRACT_REQUIRED_SECTIONS } from '../config/constants.js';
|
|
6
|
+
import { readSessionFile, writeSessionFile, sessionFileExists } from './session.js';
|
|
7
|
+
import { loadState, saveState } from './workspace.js';
|
|
8
|
+
import { generateSpecHash } from '../utils/hash.js';
|
|
9
|
+
import { logAudit } from './state-machine.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate contract content
|
|
13
|
+
*/
|
|
14
|
+
export function validateContract(content) {
|
|
15
|
+
const errors = [];
|
|
16
|
+
const warnings = [];
|
|
17
|
+
|
|
18
|
+
for (const section of CONTRACT_REQUIRED_SECTIONS) {
|
|
19
|
+
if (!section.pattern.test(content)) {
|
|
20
|
+
errors.push(`Missing required section: ${section.name}`);
|
|
21
|
+
} else {
|
|
22
|
+
// Check if section has content (not just header)
|
|
23
|
+
const sectionMatch = content.match(new RegExp(`${section.pattern.source}[\\s\\S]*?(?=##|$)`, 'i'));
|
|
24
|
+
if (sectionMatch) {
|
|
25
|
+
const sectionContent = sectionMatch[0].replace(section.pattern, '').trim();
|
|
26
|
+
if (sectionContent.length < 10) {
|
|
27
|
+
warnings.push(`Section "${section.name}" appears to be empty or too short`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
valid: errors.length === 0,
|
|
35
|
+
errors,
|
|
36
|
+
warnings
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Lock contract and generate spec hash
|
|
42
|
+
*/
|
|
43
|
+
export async function lockContract() {
|
|
44
|
+
// Check contract exists
|
|
45
|
+
if (!await sessionFileExists('contract.md')) {
|
|
46
|
+
throw new Error('Contract file not found. Create contract first.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Read contract
|
|
50
|
+
const content = await readSessionFile('contract.md');
|
|
51
|
+
|
|
52
|
+
// Validate
|
|
53
|
+
const validation = validateContract(content);
|
|
54
|
+
if (!validation.valid) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
errors: validation.errors,
|
|
58
|
+
warnings: validation.warnings
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Generate spec hash
|
|
63
|
+
const timestamp = new Date().toISOString();
|
|
64
|
+
const specHash = generateSpecHash(content, timestamp);
|
|
65
|
+
|
|
66
|
+
// Update contract with spec hash and locked status
|
|
67
|
+
const updatedContent = content
|
|
68
|
+
.replace(/## Status: DRAFT/i, '## Status: LOCKED')
|
|
69
|
+
.replace(/## Spec Hash: \[hash when locked\]/i, `## Spec Hash: ${specHash}`);
|
|
70
|
+
|
|
71
|
+
await writeSessionFile('contract.md', updatedContent);
|
|
72
|
+
|
|
73
|
+
// Update state
|
|
74
|
+
const stateData = await loadState();
|
|
75
|
+
stateData.spec_hash = specHash;
|
|
76
|
+
await saveState(stateData);
|
|
77
|
+
|
|
78
|
+
// Log
|
|
79
|
+
await logAudit(`Contract locked with spec hash: ${specHash}`);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
success: true,
|
|
83
|
+
specHash,
|
|
84
|
+
timestamp,
|
|
85
|
+
warnings: validation.warnings
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get current spec hash
|
|
91
|
+
*/
|
|
92
|
+
export async function getSpecHash() {
|
|
93
|
+
const stateData = await loadState();
|
|
94
|
+
return stateData.spec_hash;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if contract is locked
|
|
99
|
+
*/
|
|
100
|
+
export async function isContractLocked() {
|
|
101
|
+
const specHash = await getSpecHash();
|
|
102
|
+
return specHash !== null;
|
|
103
|
+
}
|