@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.
@@ -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
+ }
@@ -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 blueprint
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
 
@@ -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
- [Define your clear, specific goal here]
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
- - [What is NOT included]
158
- - [Explicitly excluded items]
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
- | [Risk] | [Mitigation] |
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
- [Define rollback strategy]
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
 
@@ -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
- const template = getContractTemplate(projectName, sessionId);
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
  }