@nclamvn/vibecode-cli 1.2.0 → 1.5.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/README.md +310 -49
- package/bin/vibecode.js +65 -0
- package/package.json +1 -1
- package/src/agent/decomposition.js +476 -0
- package/src/agent/index.js +325 -0
- package/src/agent/memory.js +542 -0
- package/src/agent/orchestrator.js +644 -0
- package/src/agent/self-healing.js +516 -0
- package/src/commands/agent.js +255 -0
- package/src/commands/assist.js +413 -0
- package/src/commands/build.js +6 -5
- package/src/commands/debug.js +457 -0
- package/src/commands/go.js +380 -0
- package/src/commands/plan.js +8 -2
- package/src/config/templates.js +146 -15
- package/src/core/session.js +18 -2
- package/src/core/test-runner.js +38 -5
- package/src/debug/analyzer.js +329 -0
- package/src/debug/evidence.js +228 -0
- package/src/debug/fixer.js +348 -0
- package/src/debug/index.js +349 -0
- package/src/debug/verifier.js +346 -0
- package/src/index.js +31 -0
- package/src/providers/claude-code.js +12 -7
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Magic Mode (go command)
|
|
3
|
+
// One command = Full workflow
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
|
|
13
|
+
import { createWorkspace, workspaceExists, getProjectName, loadState, saveState } from '../core/workspace.js';
|
|
14
|
+
import {
|
|
15
|
+
createSession,
|
|
16
|
+
getCurrentSessionId,
|
|
17
|
+
getCurrentSessionPath,
|
|
18
|
+
writeSessionFile,
|
|
19
|
+
readSessionFile,
|
|
20
|
+
sessionFileExists
|
|
21
|
+
} from '../core/session.js';
|
|
22
|
+
import { getCurrentState, transitionTo } from '../core/state-machine.js';
|
|
23
|
+
import { validateContract } from '../core/contract.js';
|
|
24
|
+
import { generateSpecHash } from '../utils/hash.js';
|
|
25
|
+
import { STATES } from '../config/constants.js';
|
|
26
|
+
import {
|
|
27
|
+
getIntakeTemplate,
|
|
28
|
+
getBlueprintTemplate,
|
|
29
|
+
getContractTemplate,
|
|
30
|
+
getPlanTemplate,
|
|
31
|
+
getCoderPackTemplate
|
|
32
|
+
} from '../config/templates.js';
|
|
33
|
+
import { printBox, printError, printSuccess } from '../ui/output.js';
|
|
34
|
+
import {
|
|
35
|
+
spawnClaudeCode,
|
|
36
|
+
isClaudeCodeAvailable,
|
|
37
|
+
buildPromptWithContext
|
|
38
|
+
} from '../providers/index.js';
|
|
39
|
+
import { runTests } from '../core/test-runner.js';
|
|
40
|
+
import { analyzeErrors } from '../core/error-analyzer.js';
|
|
41
|
+
import { ensureDir, appendToFile } from '../utils/files.js';
|
|
42
|
+
|
|
43
|
+
const execAsync = promisify(exec);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Magic Mode: One command, full build
|
|
47
|
+
* vibecode go "description" → Everything automated
|
|
48
|
+
*/
|
|
49
|
+
export async function goCommand(description, options = {}) {
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
|
|
52
|
+
// Validate description
|
|
53
|
+
if (!description || description.trim().length < 5) {
|
|
54
|
+
printError('Description too short. Please provide more details.');
|
|
55
|
+
console.log(chalk.gray('Example: vibecode go "Landing page for my startup"'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check Claude Code availability
|
|
60
|
+
const claudeAvailable = await isClaudeCodeAvailable();
|
|
61
|
+
if (!claudeAvailable) {
|
|
62
|
+
printError('Claude Code CLI not found.');
|
|
63
|
+
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Generate project name
|
|
68
|
+
const projectName = generateProjectName(description);
|
|
69
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
70
|
+
|
|
71
|
+
// Check if directory exists
|
|
72
|
+
if (await fs.pathExists(projectPath)) {
|
|
73
|
+
printError(`Directory already exists: ${projectName}`);
|
|
74
|
+
console.log(chalk.gray('Choose a different name or delete the existing directory.'));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Show magic header
|
|
79
|
+
showMagicHeader(description, projectName);
|
|
80
|
+
|
|
81
|
+
// Define steps
|
|
82
|
+
const steps = [
|
|
83
|
+
{ name: 'INIT', label: 'Creating project', weight: 5 },
|
|
84
|
+
{ name: 'INTAKE', label: 'Capturing requirements', weight: 5 },
|
|
85
|
+
{ name: 'BLUEPRINT', label: 'Designing architecture', weight: 5 },
|
|
86
|
+
{ name: 'CONTRACT', label: 'Generating contract', weight: 5 },
|
|
87
|
+
{ name: 'LOCK', label: 'Locking contract', weight: 5 },
|
|
88
|
+
{ name: 'PLAN', label: 'Creating execution plan', weight: 5 },
|
|
89
|
+
{ name: 'BUILD', label: 'Building with AI', weight: 60 },
|
|
90
|
+
{ name: 'REVIEW', label: 'Running tests', weight: 10 }
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const results = {};
|
|
94
|
+
let currentProgress = 0;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Step 1: INIT
|
|
98
|
+
await executeStep(steps[0], currentProgress, async () => {
|
|
99
|
+
await fs.ensureDir(projectPath);
|
|
100
|
+
process.chdir(projectPath);
|
|
101
|
+
await createWorkspace();
|
|
102
|
+
results.projectPath = projectPath;
|
|
103
|
+
});
|
|
104
|
+
currentProgress += steps[0].weight;
|
|
105
|
+
|
|
106
|
+
// Step 2: INTAKE
|
|
107
|
+
await executeStep(steps[1], currentProgress, async () => {
|
|
108
|
+
const sessionId = await createSession(projectName);
|
|
109
|
+
const intakeContent = getIntakeTemplate(projectName, description, sessionId);
|
|
110
|
+
await writeSessionFile('intake.md', intakeContent);
|
|
111
|
+
await transitionTo(STATES.INTAKE_CAPTURED, 'magic_intake');
|
|
112
|
+
results.sessionId = sessionId;
|
|
113
|
+
});
|
|
114
|
+
currentProgress += steps[1].weight;
|
|
115
|
+
|
|
116
|
+
// Step 3: BLUEPRINT
|
|
117
|
+
await executeStep(steps[2], currentProgress, async () => {
|
|
118
|
+
const sessionId = await getCurrentSessionId();
|
|
119
|
+
const blueprintContent = getBlueprintTemplate(projectName, sessionId);
|
|
120
|
+
await writeSessionFile('blueprint.md', blueprintContent);
|
|
121
|
+
await transitionTo(STATES.BLUEPRINT_DRAFTED, 'magic_blueprint');
|
|
122
|
+
});
|
|
123
|
+
currentProgress += steps[2].weight;
|
|
124
|
+
|
|
125
|
+
// Step 4: CONTRACT
|
|
126
|
+
await executeStep(steps[3], currentProgress, async () => {
|
|
127
|
+
const sessionId = await getCurrentSessionId();
|
|
128
|
+
const intakeContent = await readSessionFile('intake.md');
|
|
129
|
+
const blueprintContent = await readSessionFile('blueprint.md');
|
|
130
|
+
const contractContent = getContractTemplate(projectName, sessionId, intakeContent, blueprintContent);
|
|
131
|
+
await writeSessionFile('contract.md', contractContent);
|
|
132
|
+
await transitionTo(STATES.CONTRACT_DRAFTED, 'magic_contract');
|
|
133
|
+
});
|
|
134
|
+
currentProgress += steps[3].weight;
|
|
135
|
+
|
|
136
|
+
// Step 5: LOCK
|
|
137
|
+
await executeStep(steps[4], currentProgress, async () => {
|
|
138
|
+
const contractContent = await readSessionFile('contract.md');
|
|
139
|
+
const specHash = generateSpecHash(contractContent);
|
|
140
|
+
|
|
141
|
+
// Update contract with hash
|
|
142
|
+
const updatedContract = contractContent.replace(
|
|
143
|
+
/## Spec Hash: \[hash when locked\]/,
|
|
144
|
+
`## Spec Hash: ${specHash}`
|
|
145
|
+
).replace(
|
|
146
|
+
/## Status: DRAFT/,
|
|
147
|
+
'## Status: LOCKED'
|
|
148
|
+
);
|
|
149
|
+
await writeSessionFile('contract.md', updatedContract);
|
|
150
|
+
|
|
151
|
+
// Save to state
|
|
152
|
+
const stateData = await loadState();
|
|
153
|
+
stateData.spec_hash = specHash;
|
|
154
|
+
stateData.contract_locked = new Date().toISOString();
|
|
155
|
+
await saveState(stateData);
|
|
156
|
+
|
|
157
|
+
await transitionTo(STATES.CONTRACT_LOCKED, 'magic_lock');
|
|
158
|
+
results.specHash = specHash.substring(0, 8);
|
|
159
|
+
});
|
|
160
|
+
currentProgress += steps[4].weight;
|
|
161
|
+
|
|
162
|
+
// Step 6: PLAN
|
|
163
|
+
await executeStep(steps[5], currentProgress, async () => {
|
|
164
|
+
const sessionId = await getCurrentSessionId();
|
|
165
|
+
const contractContent = await readSessionFile('contract.md');
|
|
166
|
+
const blueprintContent = await readSessionFile('blueprint.md');
|
|
167
|
+
const intakeContent = await readSessionFile('intake.md');
|
|
168
|
+
|
|
169
|
+
const stateData = await loadState();
|
|
170
|
+
const specHash = stateData.spec_hash;
|
|
171
|
+
|
|
172
|
+
const planContent = getPlanTemplate(projectName, sessionId, specHash, contractContent);
|
|
173
|
+
await writeSessionFile('plan.md', planContent);
|
|
174
|
+
|
|
175
|
+
const coderPackContent = getCoderPackTemplate(
|
|
176
|
+
projectName, sessionId, specHash, contractContent, blueprintContent, intakeContent
|
|
177
|
+
);
|
|
178
|
+
await writeSessionFile('coder_pack.md', coderPackContent);
|
|
179
|
+
|
|
180
|
+
await transitionTo(STATES.PLAN_CREATED, 'magic_plan');
|
|
181
|
+
});
|
|
182
|
+
currentProgress += steps[5].weight;
|
|
183
|
+
|
|
184
|
+
// Step 7: BUILD
|
|
185
|
+
await executeStep(steps[6], currentProgress, async () => {
|
|
186
|
+
const sessionPath = await getCurrentSessionPath();
|
|
187
|
+
const evidencePath = path.join(sessionPath, 'evidence');
|
|
188
|
+
await ensureDir(evidencePath);
|
|
189
|
+
const logPath = path.join(evidencePath, 'build.log');
|
|
190
|
+
|
|
191
|
+
await transitionTo(STATES.BUILD_IN_PROGRESS, 'magic_build_start');
|
|
192
|
+
|
|
193
|
+
const coderPackContent = await readSessionFile('coder_pack.md');
|
|
194
|
+
const fullPrompt = await buildPromptWithContext(coderPackContent, process.cwd());
|
|
195
|
+
|
|
196
|
+
await appendToFile(logPath, `[${new Date().toISOString()}] Magic Mode Build Started\n`);
|
|
197
|
+
|
|
198
|
+
const buildResult = await spawnClaudeCode(fullPrompt, {
|
|
199
|
+
cwd: process.cwd(),
|
|
200
|
+
logPath: logPath
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await appendToFile(logPath, `[${new Date().toISOString()}] Build completed with code: ${buildResult.code}\n`);
|
|
204
|
+
|
|
205
|
+
// Count files created
|
|
206
|
+
const files = await fs.readdir(process.cwd());
|
|
207
|
+
results.filesCreated = files.filter(f => !f.startsWith('.')).length;
|
|
208
|
+
|
|
209
|
+
// Reload state fresh before saving
|
|
210
|
+
const freshState = await loadState();
|
|
211
|
+
freshState.build_completed = new Date().toISOString();
|
|
212
|
+
await saveState(freshState);
|
|
213
|
+
|
|
214
|
+
await transitionTo(STATES.BUILD_DONE, 'magic_build_done');
|
|
215
|
+
});
|
|
216
|
+
currentProgress += steps[6].weight;
|
|
217
|
+
|
|
218
|
+
// Step 8: REVIEW
|
|
219
|
+
await executeStep(steps[7], currentProgress, async () => {
|
|
220
|
+
const testResult = await runTests(process.cwd());
|
|
221
|
+
results.testsPassed = testResult.summary.passed;
|
|
222
|
+
results.testsTotal = testResult.summary.total;
|
|
223
|
+
results.allPassed = testResult.passed;
|
|
224
|
+
|
|
225
|
+
if (testResult.passed) {
|
|
226
|
+
await transitionTo(STATES.REVIEW_PASSED, 'magic_review_passed');
|
|
227
|
+
} else {
|
|
228
|
+
const errors = analyzeErrors(testResult);
|
|
229
|
+
results.errors = errors.length;
|
|
230
|
+
// Still mark as review passed for magic mode (best effort)
|
|
231
|
+
await transitionTo(STATES.REVIEW_PASSED, 'magic_review_completed');
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
currentProgress = 100;
|
|
235
|
+
|
|
236
|
+
// Show final progress
|
|
237
|
+
console.log(renderProgressBar(100));
|
|
238
|
+
console.log();
|
|
239
|
+
|
|
240
|
+
// Show summary
|
|
241
|
+
const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
|
|
242
|
+
showMagicSummary(projectName, projectPath, duration, results, options);
|
|
243
|
+
|
|
244
|
+
// Auto-open if requested
|
|
245
|
+
if (options.open) {
|
|
246
|
+
await openProject(projectPath);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.log();
|
|
251
|
+
printError(`Magic mode failed: ${error.message}`);
|
|
252
|
+
console.log(chalk.gray(`Project location: ${projectPath}`));
|
|
253
|
+
console.log(chalk.gray('Check .vibecode/sessions/*/evidence/build.log for details.'));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Execute a single step with progress display
|
|
260
|
+
*/
|
|
261
|
+
async function executeStep(step, currentProgress, fn) {
|
|
262
|
+
const spinner = ora({
|
|
263
|
+
text: chalk.gray(step.label),
|
|
264
|
+
prefixText: renderProgressBar(currentProgress)
|
|
265
|
+
}).start();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await fn();
|
|
269
|
+
spinner.stopAndPersist({
|
|
270
|
+
symbol: chalk.green('✓'),
|
|
271
|
+
text: chalk.green(step.name),
|
|
272
|
+
prefixText: renderProgressBar(currentProgress + step.weight)
|
|
273
|
+
});
|
|
274
|
+
} catch (error) {
|
|
275
|
+
spinner.stopAndPersist({
|
|
276
|
+
symbol: chalk.red('✗'),
|
|
277
|
+
text: chalk.red(`${step.name}: ${error.message}`),
|
|
278
|
+
prefixText: renderProgressBar(currentProgress)
|
|
279
|
+
});
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Generate project name from description
|
|
286
|
+
*/
|
|
287
|
+
function generateProjectName(description) {
|
|
288
|
+
const stopWords = ['a', 'an', 'the', 'for', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'with', 'my', 'our'];
|
|
289
|
+
|
|
290
|
+
const words = description
|
|
291
|
+
.toLowerCase()
|
|
292
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
293
|
+
.split(/\s+/)
|
|
294
|
+
.filter(w => w.length > 2 && !stopWords.includes(w))
|
|
295
|
+
.slice(0, 3);
|
|
296
|
+
|
|
297
|
+
if (words.length === 0) {
|
|
298
|
+
return `vibecode-${Date.now().toString(36)}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return words.join('-');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Render progress bar
|
|
306
|
+
*/
|
|
307
|
+
function renderProgressBar(percent) {
|
|
308
|
+
const width = 40;
|
|
309
|
+
const filled = Math.round(width * percent / 100);
|
|
310
|
+
const empty = width - filled;
|
|
311
|
+
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
312
|
+
return `[${bar}] ${String(percent).padStart(3)}%`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Show magic mode header
|
|
317
|
+
*/
|
|
318
|
+
function showMagicHeader(description, projectName) {
|
|
319
|
+
const truncatedDesc = description.length > 50
|
|
320
|
+
? description.substring(0, 47) + '...'
|
|
321
|
+
: description;
|
|
322
|
+
|
|
323
|
+
console.log();
|
|
324
|
+
console.log(chalk.cyan('╭' + '─'.repeat(68) + '╮'));
|
|
325
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
326
|
+
console.log(chalk.cyan('│') + chalk.bold.white(' 🚀 VIBECODE MAGIC MODE') + ' '.repeat(42) + chalk.cyan('│'));
|
|
327
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
328
|
+
console.log(chalk.cyan('│') + chalk.gray(` "${truncatedDesc}"`) + ' '.repeat(Math.max(0, 65 - truncatedDesc.length - 3)) + chalk.cyan('│'));
|
|
329
|
+
console.log(chalk.cyan('│') + chalk.gray(` → ${projectName}`) + ' '.repeat(Math.max(0, 65 - projectName.length - 5)) + chalk.cyan('│'));
|
|
330
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
331
|
+
console.log(chalk.cyan('╰' + '─'.repeat(68) + '╯'));
|
|
332
|
+
console.log();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Show magic mode summary
|
|
337
|
+
*/
|
|
338
|
+
function showMagicSummary(projectName, projectPath, duration, results, options) {
|
|
339
|
+
const testsStatus = results.allPassed
|
|
340
|
+
? chalk.green(`${results.testsPassed}/${results.testsTotal} passed`)
|
|
341
|
+
: chalk.yellow(`${results.testsPassed}/${results.testsTotal} passed`);
|
|
342
|
+
|
|
343
|
+
console.log(chalk.green('╭' + '─'.repeat(68) + '╮'));
|
|
344
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
345
|
+
console.log(chalk.green('│') + chalk.bold.white(' 🎉 BUILD COMPLETE!') + ' '.repeat(46) + chalk.green('│'));
|
|
346
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
347
|
+
console.log(chalk.green('│') + chalk.white(` 📁 Project: ${projectName}`) + ' '.repeat(Math.max(0, 51 - projectName.length)) + chalk.green('│'));
|
|
348
|
+
console.log(chalk.green('│') + chalk.white(` 📂 Location: ${projectPath}`) + ' '.repeat(Math.max(0, 51 - projectPath.length)) + chalk.green('│'));
|
|
349
|
+
console.log(chalk.green('│') + chalk.white(` 🔐 Spec: ${results.specHash}...`) + ' '.repeat(42) + chalk.green('│'));
|
|
350
|
+
console.log(chalk.green('│') + chalk.white(` 📄 Files: ${results.filesCreated} created`) + ' '.repeat(42) + chalk.green('│'));
|
|
351
|
+
console.log(chalk.green('│') + chalk.white(` 🧪 Tests: `) + testsStatus + ' '.repeat(Math.max(0, 40)) + chalk.green('│'));
|
|
352
|
+
console.log(chalk.green('│') + chalk.white(` ⏱️ Duration: ${duration} minutes`) + ' '.repeat(Math.max(0, 44 - duration.length)) + chalk.green('│'));
|
|
353
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
354
|
+
console.log(chalk.green('│') + chalk.gray(` 💡 Next: cd ${projectName}`) + ' '.repeat(Math.max(0, 51 - projectName.length)) + chalk.green('│'));
|
|
355
|
+
console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
|
|
356
|
+
console.log(chalk.green('╰' + '─'.repeat(68) + '╯'));
|
|
357
|
+
console.log();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Open project in file explorer / browser
|
|
362
|
+
*/
|
|
363
|
+
async function openProject(projectPath) {
|
|
364
|
+
try {
|
|
365
|
+
const platform = process.platform;
|
|
366
|
+
let cmd;
|
|
367
|
+
|
|
368
|
+
if (platform === 'darwin') {
|
|
369
|
+
cmd = `open "${projectPath}"`;
|
|
370
|
+
} else if (platform === 'win32') {
|
|
371
|
+
cmd = `explorer "${projectPath}"`;
|
|
372
|
+
} else {
|
|
373
|
+
cmd = `xdg-open "${projectPath}"`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
await execAsync(cmd);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.log(chalk.gray(`Could not auto-open: ${error.message}`));
|
|
379
|
+
}
|
|
380
|
+
}
|
package/src/commands/plan.js
CHANGED
|
@@ -45,7 +45,7 @@ export async function planCommand(options = {}) {
|
|
|
45
45
|
const sessionPath = await getCurrentSessionPath();
|
|
46
46
|
const specHash = await getSpecHash();
|
|
47
47
|
|
|
48
|
-
// Read contract and
|
|
48
|
+
// Read contract, blueprint, and intake
|
|
49
49
|
spinner.text = 'Reading contract...';
|
|
50
50
|
const contractContent = await readSessionFile('contract.md');
|
|
51
51
|
|
|
@@ -54,6 +54,11 @@ export async function planCommand(options = {}) {
|
|
|
54
54
|
blueprintContent = await readSessionFile('blueprint.md');
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
let intakeContent = '';
|
|
58
|
+
if (await sessionFileExists('intake.md')) {
|
|
59
|
+
intakeContent = await readSessionFile('intake.md');
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
// Generate plan
|
|
58
63
|
spinner.text = 'Generating plan...';
|
|
59
64
|
const planContent = getPlanTemplate(projectName, sessionId, specHash, contractContent);
|
|
@@ -66,7 +71,8 @@ export async function planCommand(options = {}) {
|
|
|
66
71
|
sessionId,
|
|
67
72
|
specHash,
|
|
68
73
|
contractContent,
|
|
69
|
-
blueprintContent
|
|
74
|
+
blueprintContent,
|
|
75
|
+
intakeContent
|
|
70
76
|
);
|
|
71
77
|
await writeSessionFile('coder_pack.md', coderPackContent);
|
|
72
78
|
|
package/src/config/templates.js
CHANGED
|
@@ -124,11 +124,39 @@ src/
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
|
-
* Get contract template
|
|
127
|
+
* Get contract template with content extracted from intake and blueprint
|
|
128
128
|
*/
|
|
129
|
-
export function getContractTemplate(projectName, sessionId) {
|
|
129
|
+
export function getContractTemplate(projectName, sessionId, intakeContent = '', blueprintContent = '') {
|
|
130
130
|
const timestamp = new Date().toISOString();
|
|
131
131
|
|
|
132
|
+
// Extract goal from intake (look for "Mô tả dự án" section)
|
|
133
|
+
let goal = extractSection(intakeContent, '## 🎯 Mô tả dự án', '---') ||
|
|
134
|
+
extractSection(intakeContent, '## Mô tả dự án', '---') ||
|
|
135
|
+
'[Define your clear, specific goal here]';
|
|
136
|
+
|
|
137
|
+
// Extract tech stack from blueprint
|
|
138
|
+
let techStack = extractSection(blueprintContent, '## 💻 Tech Stack', '---') || '';
|
|
139
|
+
|
|
140
|
+
// Extract architecture from blueprint
|
|
141
|
+
let architecture = extractSection(blueprintContent, '## 📐 Architecture', '---') || '';
|
|
142
|
+
|
|
143
|
+
// Extract file structure from blueprint
|
|
144
|
+
let fileStructure = extractSection(blueprintContent, '## 📁 File Structure', '---') || '';
|
|
145
|
+
|
|
146
|
+
// Generate deliverables from goal and architecture
|
|
147
|
+
const deliverables = generateDeliverablesFromContent(goal, techStack, architecture);
|
|
148
|
+
|
|
149
|
+
// Generate acceptance criteria from deliverables
|
|
150
|
+
const acceptanceCriteria = generateAcceptanceCriteria(deliverables);
|
|
151
|
+
|
|
152
|
+
// Generate in-scope items
|
|
153
|
+
const inScopeItems = deliverables.map(d => `- [ ] ${d.item}`).join('\n');
|
|
154
|
+
|
|
155
|
+
// Generate deliverables table
|
|
156
|
+
const deliverablesTable = deliverables.map((d, i) =>
|
|
157
|
+
`| ${i + 1} | ${d.item} | ${d.description} | ⬜ |`
|
|
158
|
+
).join('\n');
|
|
159
|
+
|
|
132
160
|
return `# 📜 CONTRACT: ${projectName}
|
|
133
161
|
|
|
134
162
|
## Session: ${sessionId}
|
|
@@ -140,22 +168,21 @@ export function getContractTemplate(projectName, sessionId) {
|
|
|
140
168
|
|
|
141
169
|
## 🎯 Goal
|
|
142
170
|
|
|
143
|
-
|
|
171
|
+
${goal.trim()}
|
|
144
172
|
|
|
145
173
|
---
|
|
146
174
|
|
|
147
175
|
## ✅ In-Scope
|
|
148
176
|
|
|
149
|
-
- [ ] [Deliverable 1]
|
|
150
|
-
- [ ] [Deliverable 2]
|
|
151
|
-
- [ ] [Deliverable 3]
|
|
177
|
+
${inScopeItems || '- [ ] [Deliverable 1]\n- [ ] [Deliverable 2]\n- [ ] [Deliverable 3]'}
|
|
152
178
|
|
|
153
179
|
---
|
|
154
180
|
|
|
155
181
|
## ❌ Out-of-Scope
|
|
156
182
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
183
|
+
- Features not mentioned in goal
|
|
184
|
+
- Additional integrations not specified
|
|
185
|
+
- Performance optimization beyond MVP
|
|
159
186
|
|
|
160
187
|
---
|
|
161
188
|
|
|
@@ -163,14 +190,13 @@ export function getContractTemplate(projectName, sessionId) {
|
|
|
163
190
|
|
|
164
191
|
| # | Item | Description | Status |
|
|
165
192
|
|---|------|-------------|--------|
|
|
166
|
-
| 1 | [Item] | [Description] | ⬜ |
|
|
193
|
+
${deliverablesTable || '| 1 | [Item] | [Description] | ⬜ |'}
|
|
167
194
|
|
|
168
195
|
---
|
|
169
196
|
|
|
170
197
|
## ✔️ Acceptance Criteria
|
|
171
198
|
|
|
172
|
-
- [ ] [Criterion 1]
|
|
173
|
-
- [ ] [Criterion 2]
|
|
199
|
+
${acceptanceCriteria || '- [ ] [Criterion 1]\n- [ ] [Criterion 2]'}
|
|
174
200
|
|
|
175
201
|
---
|
|
176
202
|
|
|
@@ -178,13 +204,17 @@ export function getContractTemplate(projectName, sessionId) {
|
|
|
178
204
|
|
|
179
205
|
| Risk | Mitigation |
|
|
180
206
|
|------|------------|
|
|
181
|
-
|
|
|
207
|
+
| Dependencies unavailable | Use alternative packages |
|
|
208
|
+
| Scope creep | Strict contract adherence |
|
|
182
209
|
|
|
183
210
|
---
|
|
184
211
|
|
|
185
212
|
## 🔙 Rollback Plan
|
|
186
213
|
|
|
187
|
-
|
|
214
|
+
If build fails:
|
|
215
|
+
1. Revert to last stable commit
|
|
216
|
+
2. Review contract requirements
|
|
217
|
+
3. Rebuild with corrections
|
|
188
218
|
|
|
189
219
|
---
|
|
190
220
|
|
|
@@ -193,6 +223,91 @@ export function getContractTemplate(projectName, sessionId) {
|
|
|
193
223
|
`;
|
|
194
224
|
}
|
|
195
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Extract a section from markdown content
|
|
228
|
+
*/
|
|
229
|
+
function extractSection(content, startMarker, endMarker) {
|
|
230
|
+
if (!content) return null;
|
|
231
|
+
|
|
232
|
+
const startIndex = content.indexOf(startMarker);
|
|
233
|
+
if (startIndex === -1) return null;
|
|
234
|
+
|
|
235
|
+
const contentStart = startIndex + startMarker.length;
|
|
236
|
+
const endIndex = content.indexOf(endMarker, contentStart);
|
|
237
|
+
|
|
238
|
+
if (endIndex === -1) {
|
|
239
|
+
return content.substring(contentStart).trim();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return content.substring(contentStart, endIndex).trim();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Generate deliverables from content analysis
|
|
247
|
+
*/
|
|
248
|
+
function generateDeliverablesFromContent(goal, techStack, architecture) {
|
|
249
|
+
const deliverables = [];
|
|
250
|
+
|
|
251
|
+
// Parse goal for keywords to generate deliverables
|
|
252
|
+
const goalLower = goal.toLowerCase();
|
|
253
|
+
|
|
254
|
+
// Common patterns
|
|
255
|
+
if (goalLower.includes('landing page') || goalLower.includes('website')) {
|
|
256
|
+
deliverables.push({ item: 'Landing Page', description: 'Main landing page with hero section' });
|
|
257
|
+
deliverables.push({ item: 'Responsive Design', description: 'Mobile-friendly layout' });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (goalLower.includes('cli') || goalLower.includes('command')) {
|
|
261
|
+
deliverables.push({ item: 'CLI Commands', description: 'Core command implementations' });
|
|
262
|
+
deliverables.push({ item: 'Help System', description: 'Command help and documentation' });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (goalLower.includes('api') || goalLower.includes('backend')) {
|
|
266
|
+
deliverables.push({ item: 'API Endpoints', description: 'REST/GraphQL endpoints' });
|
|
267
|
+
deliverables.push({ item: 'Data Models', description: 'Database models and schemas' });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (goalLower.includes('auth') || goalLower.includes('login')) {
|
|
271
|
+
deliverables.push({ item: 'Authentication', description: 'User login/logout flow' });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (goalLower.includes('dashboard') || goalLower.includes('admin')) {
|
|
275
|
+
deliverables.push({ item: 'Dashboard UI', description: 'Admin dashboard interface' });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Always include core deliverables if none detected
|
|
279
|
+
if (deliverables.length === 0) {
|
|
280
|
+
deliverables.push({ item: 'Core Implementation', description: 'Main functionality as described' });
|
|
281
|
+
deliverables.push({ item: 'UI/UX', description: 'User interface implementation' });
|
|
282
|
+
deliverables.push({ item: 'Documentation', description: 'Usage documentation' });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Add testing if not already included
|
|
286
|
+
if (!deliverables.some(d => d.item.toLowerCase().includes('test'))) {
|
|
287
|
+
deliverables.push({ item: 'Testing', description: 'Basic functionality tests' });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return deliverables;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Generate acceptance criteria from deliverables
|
|
295
|
+
*/
|
|
296
|
+
function generateAcceptanceCriteria(deliverables) {
|
|
297
|
+
if (!deliverables || deliverables.length === 0) {
|
|
298
|
+
return '- [ ] All features work as expected\n- [ ] No console errors\n- [ ] Documentation complete';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const criteria = deliverables.map(d =>
|
|
302
|
+
`- [ ] ${d.item} is complete and functional`
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
criteria.push('- [ ] No console errors or warnings');
|
|
306
|
+
criteria.push('- [ ] Code is clean and documented');
|
|
307
|
+
|
|
308
|
+
return criteria.join('\n');
|
|
309
|
+
}
|
|
310
|
+
|
|
196
311
|
/**
|
|
197
312
|
* Get plan template
|
|
198
313
|
*/
|
|
@@ -260,9 +375,13 @@ ${deliverables}
|
|
|
260
375
|
/**
|
|
261
376
|
* Get coder pack template - instructions for AI coder
|
|
262
377
|
*/
|
|
263
|
-
export function getCoderPackTemplate(projectName, sessionId, specHash, contractContent, blueprintContent) {
|
|
378
|
+
export function getCoderPackTemplate(projectName, sessionId, specHash, contractContent, blueprintContent, intakeContent = '') {
|
|
264
379
|
const timestamp = new Date().toISOString();
|
|
265
380
|
|
|
381
|
+
// Extract the goal from intake for quick reference
|
|
382
|
+
const goalMatch = intakeContent.match(/## 🎯 Mô tả dự án\s*([\s\S]*?)(?=---|##|$)/);
|
|
383
|
+
const projectGoal = goalMatch ? goalMatch[1].trim() : 'See contract for details';
|
|
384
|
+
|
|
266
385
|
return `# 🏗️ CODER PACK: ${projectName}
|
|
267
386
|
|
|
268
387
|
## Session: ${sessionId}
|
|
@@ -283,6 +402,12 @@ You are the **Thợ (Builder)**. Your job is to execute the locked contract exac
|
|
|
283
402
|
|
|
284
403
|
---
|
|
285
404
|
|
|
405
|
+
## 🎯 PROJECT GOAL (Quick Reference)
|
|
406
|
+
|
|
407
|
+
${projectGoal}
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
286
411
|
## 📜 CONTRACT (LOCKED)
|
|
287
412
|
|
|
288
413
|
${contractContent}
|
|
@@ -291,7 +416,13 @@ ${contractContent}
|
|
|
291
416
|
|
|
292
417
|
## 📘 BLUEPRINT REFERENCE
|
|
293
418
|
|
|
294
|
-
${blueprintContent}
|
|
419
|
+
${blueprintContent || '[No blueprint provided - follow contract specifications]'}
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 📥 ORIGINAL INTAKE
|
|
424
|
+
|
|
425
|
+
${intakeContent || '[No intake provided]'}
|
|
295
426
|
|
|
296
427
|
---
|
|
297
428
|
|
package/src/core/session.js
CHANGED
|
@@ -114,9 +114,25 @@ export async function createBlueprint(projectName, sessionId) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* Create contract file
|
|
117
|
+
* Create contract file from intake and blueprint
|
|
118
118
|
*/
|
|
119
119
|
export async function createContract(projectName, sessionId) {
|
|
120
|
-
|
|
120
|
+
// Read intake and blueprint to extract real content
|
|
121
|
+
let intakeContent = '';
|
|
122
|
+
let blueprintContent = '';
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
intakeContent = await readSessionFile('intake.md');
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Intake not found, use empty
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
blueprintContent = await readSessionFile('blueprint.md');
|
|
132
|
+
} catch (e) {
|
|
133
|
+
// Blueprint not found, use empty
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const template = getContractTemplate(projectName, sessionId, intakeContent, blueprintContent);
|
|
121
137
|
await writeSessionFile('contract.md', template);
|
|
122
138
|
}
|