@nclamvn/vibecode-cli 2.0.0 → 2.2.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.
Files changed (53) hide show
  1. package/.vibecode/learning/fixes.json +1 -0
  2. package/.vibecode/learning/preferences.json +1 -0
  3. package/README.md +310 -49
  4. package/SESSION_NOTES.md +154 -0
  5. package/bin/vibecode.js +235 -2
  6. package/package.json +5 -2
  7. package/src/agent/decomposition.js +476 -0
  8. package/src/agent/index.js +391 -0
  9. package/src/agent/memory.js +542 -0
  10. package/src/agent/orchestrator.js +917 -0
  11. package/src/agent/self-healing.js +516 -0
  12. package/src/commands/agent.js +349 -0
  13. package/src/commands/ask.js +230 -0
  14. package/src/commands/assist.js +413 -0
  15. package/src/commands/build.js +345 -4
  16. package/src/commands/debug.js +565 -0
  17. package/src/commands/docs.js +167 -0
  18. package/src/commands/git.js +1024 -0
  19. package/src/commands/go.js +635 -0
  20. package/src/commands/learn.js +294 -0
  21. package/src/commands/migrate.js +341 -0
  22. package/src/commands/plan.js +8 -2
  23. package/src/commands/refactor.js +205 -0
  24. package/src/commands/review.js +126 -1
  25. package/src/commands/security.js +229 -0
  26. package/src/commands/shell.js +486 -0
  27. package/src/commands/templates.js +397 -0
  28. package/src/commands/test.js +194 -0
  29. package/src/commands/undo.js +281 -0
  30. package/src/commands/watch.js +556 -0
  31. package/src/commands/wizard.js +322 -0
  32. package/src/config/constants.js +5 -1
  33. package/src/config/templates.js +146 -15
  34. package/src/core/backup.js +325 -0
  35. package/src/core/error-analyzer.js +237 -0
  36. package/src/core/fix-generator.js +195 -0
  37. package/src/core/iteration.js +226 -0
  38. package/src/core/learning.js +295 -0
  39. package/src/core/session.js +18 -2
  40. package/src/core/test-runner.js +281 -0
  41. package/src/debug/analyzer.js +329 -0
  42. package/src/debug/evidence.js +228 -0
  43. package/src/debug/fixer.js +348 -0
  44. package/src/debug/image-analyzer.js +304 -0
  45. package/src/debug/index.js +378 -0
  46. package/src/debug/verifier.js +346 -0
  47. package/src/index.js +102 -0
  48. package/src/providers/claude-code.js +12 -7
  49. package/src/templates/index.js +724 -0
  50. package/src/ui/__tests__/error-translator.test.js +390 -0
  51. package/src/ui/dashboard.js +364 -0
  52. package/src/ui/error-translator.js +775 -0
  53. package/src/utils/image.js +222 -0
@@ -0,0 +1,635 @@
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 { StepProgress, updateProgress, completeProgress } from '../ui/dashboard.js';
35
+ import { BackupManager } from '../core/backup.js';
36
+ import {
37
+ spawnClaudeCode,
38
+ isClaudeCodeAvailable,
39
+ buildPromptWithContext
40
+ } from '../providers/index.js';
41
+ import { runTests } from '../core/test-runner.js';
42
+ import { analyzeErrors } from '../core/error-analyzer.js';
43
+ import { ensureDir, appendToFile } from '../utils/files.js';
44
+ import { getTemplate, getCategoryIcon } from '../templates/index.js';
45
+
46
+ const execAsync = promisify(exec);
47
+
48
+ /**
49
+ * Magic Mode: One command, full build
50
+ * vibecode go "description" → Everything automated
51
+ * vibecode go --template <id> → Use template
52
+ */
53
+ export async function goCommand(description, options = {}) {
54
+ // Handle template mode
55
+ if (options.template) {
56
+ return goWithTemplate(options.template, options);
57
+ }
58
+
59
+ const startTime = Date.now();
60
+
61
+ // Handle description as array (from commander variadic)
62
+ const desc = Array.isArray(description) ? description.join(' ') : description;
63
+
64
+ // Validate description
65
+ if (!desc || desc.trim().length < 5) {
66
+ printError('Description too short. Please provide more details.');
67
+ console.log(chalk.gray('Example: vibecode go "Landing page for my startup"'));
68
+ console.log(chalk.gray('Or use: vibecode go --template landing-saas'));
69
+ process.exit(1);
70
+ }
71
+
72
+ // Check Claude Code availability
73
+ const claudeAvailable = await isClaudeCodeAvailable();
74
+ if (!claudeAvailable) {
75
+ printError('Claude Code CLI not found.');
76
+ console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
77
+ process.exit(1);
78
+ }
79
+
80
+ // Generate project name
81
+ const projectName = options.name ? sanitizeProjectName(options.name) : generateProjectName(desc);
82
+ const projectPath = path.join(process.cwd(), projectName);
83
+
84
+ // Check if directory exists
85
+ if (await fs.pathExists(projectPath)) {
86
+ printError(`Directory already exists: ${projectName}`);
87
+ console.log(chalk.gray('Choose a different name or delete the existing directory.'));
88
+ process.exit(1);
89
+ }
90
+
91
+ // Show magic header
92
+ showMagicHeader(desc, projectName);
93
+
94
+ // Create backup of current directory before go command
95
+ // (backup in parent directory since we're creating a new project)
96
+ const backup = new BackupManager(process.cwd());
97
+ await backup.createBackup('go-magic');
98
+
99
+ // Define steps
100
+ const steps = [
101
+ { name: 'INIT', label: 'Creating project', weight: 5 },
102
+ { name: 'INTAKE', label: 'Capturing requirements', weight: 5 },
103
+ { name: 'BLUEPRINT', label: 'Designing architecture', weight: 5 },
104
+ { name: 'CONTRACT', label: 'Generating contract', weight: 5 },
105
+ { name: 'LOCK', label: 'Locking contract', weight: 5 },
106
+ { name: 'PLAN', label: 'Creating execution plan', weight: 5 },
107
+ { name: 'BUILD', label: 'Building with AI', weight: 60 },
108
+ { name: 'REVIEW', label: 'Running tests', weight: 10 }
109
+ ];
110
+
111
+ const results = {};
112
+ let currentProgress = 0;
113
+
114
+ try {
115
+ // Step 1: INIT
116
+ await executeStep(steps[0], currentProgress, async () => {
117
+ await fs.ensureDir(projectPath);
118
+ process.chdir(projectPath);
119
+ await createWorkspace();
120
+ results.projectPath = projectPath;
121
+ });
122
+ currentProgress += steps[0].weight;
123
+
124
+ // Step 2: INTAKE
125
+ await executeStep(steps[1], currentProgress, async () => {
126
+ const sessionId = await createSession(projectName);
127
+ const intakeContent = getIntakeTemplate(projectName, description, sessionId);
128
+ await writeSessionFile('intake.md', intakeContent);
129
+ await transitionTo(STATES.INTAKE_CAPTURED, 'magic_intake');
130
+ results.sessionId = sessionId;
131
+ });
132
+ currentProgress += steps[1].weight;
133
+
134
+ // Step 3: BLUEPRINT
135
+ await executeStep(steps[2], currentProgress, async () => {
136
+ const sessionId = await getCurrentSessionId();
137
+ const blueprintContent = getBlueprintTemplate(projectName, sessionId);
138
+ await writeSessionFile('blueprint.md', blueprintContent);
139
+ await transitionTo(STATES.BLUEPRINT_DRAFTED, 'magic_blueprint');
140
+ });
141
+ currentProgress += steps[2].weight;
142
+
143
+ // Step 4: CONTRACT
144
+ await executeStep(steps[3], currentProgress, async () => {
145
+ const sessionId = await getCurrentSessionId();
146
+ const intakeContent = await readSessionFile('intake.md');
147
+ const blueprintContent = await readSessionFile('blueprint.md');
148
+ const contractContent = getContractTemplate(projectName, sessionId, intakeContent, blueprintContent);
149
+ await writeSessionFile('contract.md', contractContent);
150
+ await transitionTo(STATES.CONTRACT_DRAFTED, 'magic_contract');
151
+ });
152
+ currentProgress += steps[3].weight;
153
+
154
+ // Step 5: LOCK
155
+ await executeStep(steps[4], currentProgress, async () => {
156
+ const contractContent = await readSessionFile('contract.md');
157
+ const specHash = generateSpecHash(contractContent);
158
+
159
+ // Update contract with hash
160
+ const updatedContract = contractContent.replace(
161
+ /## Spec Hash: \[hash when locked\]/,
162
+ `## Spec Hash: ${specHash}`
163
+ ).replace(
164
+ /## Status: DRAFT/,
165
+ '## Status: LOCKED'
166
+ );
167
+ await writeSessionFile('contract.md', updatedContract);
168
+
169
+ // Save to state
170
+ const stateData = await loadState();
171
+ stateData.spec_hash = specHash;
172
+ stateData.contract_locked = new Date().toISOString();
173
+ await saveState(stateData);
174
+
175
+ await transitionTo(STATES.CONTRACT_LOCKED, 'magic_lock');
176
+ results.specHash = specHash.substring(0, 8);
177
+ });
178
+ currentProgress += steps[4].weight;
179
+
180
+ // Step 6: PLAN
181
+ await executeStep(steps[5], currentProgress, async () => {
182
+ const sessionId = await getCurrentSessionId();
183
+ const contractContent = await readSessionFile('contract.md');
184
+ const blueprintContent = await readSessionFile('blueprint.md');
185
+ const intakeContent = await readSessionFile('intake.md');
186
+
187
+ const stateData = await loadState();
188
+ const specHash = stateData.spec_hash;
189
+
190
+ const planContent = getPlanTemplate(projectName, sessionId, specHash, contractContent);
191
+ await writeSessionFile('plan.md', planContent);
192
+
193
+ const coderPackContent = getCoderPackTemplate(
194
+ projectName, sessionId, specHash, contractContent, blueprintContent, intakeContent
195
+ );
196
+ await writeSessionFile('coder_pack.md', coderPackContent);
197
+
198
+ await transitionTo(STATES.PLAN_CREATED, 'magic_plan');
199
+ });
200
+ currentProgress += steps[5].weight;
201
+
202
+ // Step 7: BUILD
203
+ await executeStep(steps[6], currentProgress, async () => {
204
+ const sessionPath = await getCurrentSessionPath();
205
+ const evidencePath = path.join(sessionPath, 'evidence');
206
+ await ensureDir(evidencePath);
207
+ const logPath = path.join(evidencePath, 'build.log');
208
+
209
+ await transitionTo(STATES.BUILD_IN_PROGRESS, 'magic_build_start');
210
+
211
+ const coderPackContent = await readSessionFile('coder_pack.md');
212
+ const fullPrompt = await buildPromptWithContext(coderPackContent, process.cwd());
213
+
214
+ await appendToFile(logPath, `[${new Date().toISOString()}] Magic Mode Build Started\n`);
215
+
216
+ const buildResult = await spawnClaudeCode(fullPrompt, {
217
+ cwd: process.cwd(),
218
+ logPath: logPath
219
+ });
220
+
221
+ await appendToFile(logPath, `[${new Date().toISOString()}] Build completed with code: ${buildResult.code}\n`);
222
+
223
+ // Count files created
224
+ const files = await fs.readdir(process.cwd());
225
+ results.filesCreated = files.filter(f => !f.startsWith('.')).length;
226
+
227
+ // Reload state fresh before saving
228
+ const freshState = await loadState();
229
+ freshState.build_completed = new Date().toISOString();
230
+ await saveState(freshState);
231
+
232
+ await transitionTo(STATES.BUILD_DONE, 'magic_build_done');
233
+ });
234
+ currentProgress += steps[6].weight;
235
+
236
+ // Step 8: REVIEW
237
+ await executeStep(steps[7], currentProgress, async () => {
238
+ const testResult = await runTests(process.cwd());
239
+ results.testsPassed = testResult.summary.passed;
240
+ results.testsTotal = testResult.summary.total;
241
+ results.allPassed = testResult.passed;
242
+
243
+ if (testResult.passed) {
244
+ await transitionTo(STATES.REVIEW_PASSED, 'magic_review_passed');
245
+ } else {
246
+ const errors = analyzeErrors(testResult);
247
+ results.errors = errors.length;
248
+ // Still mark as review passed for magic mode (best effort)
249
+ await transitionTo(STATES.REVIEW_PASSED, 'magic_review_completed');
250
+ }
251
+ });
252
+ currentProgress = 100;
253
+
254
+ // Show final progress
255
+ console.log(renderProgressBar(100));
256
+ console.log();
257
+
258
+ // Show summary
259
+ const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
260
+ showMagicSummary(projectName, projectPath, duration, results, options);
261
+
262
+ // Auto-open if requested
263
+ if (options.open) {
264
+ await openProject(projectPath);
265
+ }
266
+
267
+ } catch (error) {
268
+ console.log();
269
+ printError(`Magic mode failed: ${error.message}`);
270
+ console.log(chalk.gray(`Project location: ${projectPath}`));
271
+ console.log(chalk.gray('Check .vibecode/sessions/*/evidence/build.log for details.'));
272
+ process.exit(1);
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Execute a single step with progress display
278
+ */
279
+ async function executeStep(step, currentProgress, fn) {
280
+ const spinner = ora({
281
+ text: chalk.gray(step.label),
282
+ prefixText: renderProgressBar(currentProgress)
283
+ }).start();
284
+
285
+ try {
286
+ await fn();
287
+ spinner.stopAndPersist({
288
+ symbol: chalk.green('✓'),
289
+ text: chalk.green(step.name),
290
+ prefixText: renderProgressBar(currentProgress + step.weight)
291
+ });
292
+ } catch (error) {
293
+ spinner.stopAndPersist({
294
+ symbol: chalk.red('✗'),
295
+ text: chalk.red(`${step.name}: ${error.message}`),
296
+ prefixText: renderProgressBar(currentProgress)
297
+ });
298
+ throw error;
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Generate project name from description
304
+ */
305
+ function generateProjectName(description) {
306
+ const stopWords = ['a', 'an', 'the', 'for', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'with', 'my', 'our'];
307
+
308
+ const words = description
309
+ .toLowerCase()
310
+ .replace(/[^a-z0-9\s]/g, '')
311
+ .split(/\s+/)
312
+ .filter(w => w.length > 2 && !stopWords.includes(w))
313
+ .slice(0, 3);
314
+
315
+ if (words.length === 0) {
316
+ return `vibecode-${Date.now().toString(36)}`;
317
+ }
318
+
319
+ return words.join('-');
320
+ }
321
+
322
+ /**
323
+ * Render progress bar
324
+ */
325
+ function renderProgressBar(percent, label = '') {
326
+ const width = 40;
327
+ const filled = Math.round(width * percent / 100);
328
+ const empty = width - filled;
329
+ const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
330
+ return `[${bar}] ${String(percent).padStart(3)}%${label ? ' ' + label : ''}`;
331
+ }
332
+
333
+ /**
334
+ * Show magic mode header
335
+ */
336
+ function showMagicHeader(description, projectName) {
337
+ const truncatedDesc = description.length > 50
338
+ ? description.substring(0, 47) + '...'
339
+ : description;
340
+
341
+ console.log();
342
+ console.log(chalk.cyan('╭' + '─'.repeat(68) + '╮'));
343
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
344
+ console.log(chalk.cyan('│') + chalk.bold.white(' 🚀 VIBECODE MAGIC MODE') + ' '.repeat(42) + chalk.cyan('│'));
345
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
346
+ console.log(chalk.cyan('│') + chalk.gray(` "${truncatedDesc}"`) + ' '.repeat(Math.max(0, 65 - truncatedDesc.length - 3)) + chalk.cyan('│'));
347
+ console.log(chalk.cyan('│') + chalk.gray(` → ${projectName}`) + ' '.repeat(Math.max(0, 65 - projectName.length - 5)) + chalk.cyan('│'));
348
+ console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
349
+ console.log(chalk.cyan('╰' + '─'.repeat(68) + '╯'));
350
+ console.log();
351
+ }
352
+
353
+ /**
354
+ * Show magic mode summary
355
+ */
356
+ function showMagicSummary(projectName, projectPath, duration, results, options) {
357
+ const testsStatus = results.allPassed
358
+ ? chalk.green(`${results.testsPassed}/${results.testsTotal} passed`)
359
+ : chalk.yellow(`${results.testsPassed}/${results.testsTotal} passed`);
360
+
361
+ console.log(chalk.green('╭' + '─'.repeat(68) + '╮'));
362
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
363
+ console.log(chalk.green('│') + chalk.bold.white(' 🎉 BUILD COMPLETE!') + ' '.repeat(46) + chalk.green('│'));
364
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
365
+ console.log(chalk.green('│') + chalk.white(` 📁 Project: ${projectName}`) + ' '.repeat(Math.max(0, 51 - projectName.length)) + chalk.green('│'));
366
+ console.log(chalk.green('│') + chalk.white(` 📂 Location: ${projectPath}`) + ' '.repeat(Math.max(0, 51 - projectPath.length)) + chalk.green('│'));
367
+ console.log(chalk.green('│') + chalk.white(` 🔐 Spec: ${results.specHash}...`) + ' '.repeat(42) + chalk.green('│'));
368
+ console.log(chalk.green('│') + chalk.white(` 📄 Files: ${results.filesCreated} created`) + ' '.repeat(42) + chalk.green('│'));
369
+ console.log(chalk.green('│') + chalk.white(` 🧪 Tests: `) + testsStatus + ' '.repeat(Math.max(0, 40)) + chalk.green('│'));
370
+ console.log(chalk.green('│') + chalk.white(` ⏱️ Duration: ${duration} minutes`) + ' '.repeat(Math.max(0, 44 - duration.length)) + chalk.green('│'));
371
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
372
+ console.log(chalk.green('│') + chalk.gray(` 💡 Next: cd ${projectName}`) + ' '.repeat(Math.max(0, 51 - projectName.length)) + chalk.green('│'));
373
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
374
+ console.log(chalk.green('╰' + '─'.repeat(68) + '╯'));
375
+ console.log();
376
+ }
377
+
378
+ /**
379
+ * Open project in file explorer / browser
380
+ */
381
+ async function openProject(projectPath) {
382
+ try {
383
+ const platform = process.platform;
384
+ let cmd;
385
+
386
+ if (platform === 'darwin') {
387
+ cmd = `open "${projectPath}"`;
388
+ } else if (platform === 'win32') {
389
+ cmd = `explorer "${projectPath}"`;
390
+ } else {
391
+ cmd = `xdg-open "${projectPath}"`;
392
+ }
393
+
394
+ await execAsync(cmd);
395
+ } catch (error) {
396
+ console.log(chalk.gray(`Could not auto-open: ${error.message}`));
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Sanitize project name
402
+ */
403
+ function sanitizeProjectName(name) {
404
+ return name
405
+ .toLowerCase()
406
+ .replace(/[^a-z0-9\s-]/g, '')
407
+ .replace(/\s+/g, '-')
408
+ .replace(/-+/g, '-')
409
+ .replace(/^-|-$/g, '')
410
+ .substring(0, 50);
411
+ }
412
+
413
+ /**
414
+ * Template Mode: Use pre-defined template
415
+ * vibecode go --template <id>
416
+ */
417
+ async function goWithTemplate(templateId, options = {}) {
418
+ const template = getTemplate(templateId);
419
+
420
+ if (!template) {
421
+ printError(`Template "${templateId}" not found.`);
422
+ console.log(chalk.gray('Run "vibecode templates" to see available templates.'));
423
+ process.exit(1);
424
+ }
425
+
426
+ // Check Claude Code availability
427
+ const claudeAvailable = await isClaudeCodeAvailable();
428
+ if (!claudeAvailable) {
429
+ printError('Claude Code CLI not found.');
430
+ console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
431
+ process.exit(1);
432
+ }
433
+
434
+ const startTime = Date.now();
435
+ const icon = getCategoryIcon(template.category);
436
+
437
+ // Generate project name
438
+ const projectName = options.name
439
+ ? sanitizeProjectName(options.name)
440
+ : `${templateId}-${Date.now().toString(36)}`;
441
+ const projectPath = path.join(process.cwd(), projectName);
442
+
443
+ // Check if directory exists
444
+ if (await fs.pathExists(projectPath)) {
445
+ printError(`Directory already exists: ${projectName}`);
446
+ console.log(chalk.gray('Use --name to specify a different name.'));
447
+ process.exit(1);
448
+ }
449
+
450
+ // Show template header
451
+ showTemplateHeader(template, projectName, icon);
452
+
453
+ // Build customized prompt from template
454
+ let prompt = template.prompt;
455
+
456
+ // Apply template variables from options
457
+ for (const [key, config] of Object.entries(template.variables)) {
458
+ const value = options[key] !== undefined ? options[key] : config.default;
459
+ // Replace placeholders like {name} in prompt
460
+ prompt = prompt.replace(new RegExp(`\\{${key}\\}`, 'gi'), value);
461
+ }
462
+
463
+ // Add customizations from common options
464
+ if (options.name) {
465
+ prompt += `\n\nProject/Company name: "${options.name}"`;
466
+ }
467
+ if (options.color) {
468
+ prompt += `\n\nPrimary brand color: ${options.color}`;
469
+ }
470
+
471
+ // Create backup
472
+ const backup = new BackupManager(process.cwd());
473
+ await backup.createBackup('go-template');
474
+
475
+ // Define steps
476
+ const steps = [
477
+ { name: 'INIT', label: 'Creating project', weight: 5 },
478
+ { name: 'SETUP', label: 'Setting up template', weight: 5 },
479
+ { name: 'BUILD', label: `Building ${template.name}`, weight: 80 },
480
+ { name: 'REVIEW', label: 'Running tests', weight: 10 }
481
+ ];
482
+
483
+ const results = {
484
+ templateId,
485
+ templateName: template.name
486
+ };
487
+ let currentProgress = 0;
488
+
489
+ try {
490
+ // Step 1: INIT
491
+ await executeStep(steps[0], currentProgress, async () => {
492
+ await fs.ensureDir(projectPath);
493
+ process.chdir(projectPath);
494
+ await createWorkspace();
495
+ results.projectPath = projectPath;
496
+ });
497
+ currentProgress += steps[0].weight;
498
+
499
+ // Step 2: SETUP
500
+ await executeStep(steps[1], currentProgress, async () => {
501
+ const sessionId = await createSession(projectName);
502
+ results.sessionId = sessionId;
503
+
504
+ // Write template info to session
505
+ const templateInfo = `# Template: ${template.name}
506
+
507
+ ## ID: ${templateId}
508
+ ## Category: ${template.category}
509
+ ## Stack: ${template.stack.join(', ')}
510
+
511
+ ## Features
512
+ ${template.features.map(f => `- ${f}`).join('\n')}
513
+
514
+ ## Prompt
515
+ ${prompt}
516
+ `;
517
+ await writeSessionFile('template.md', templateInfo);
518
+ await transitionTo(STATES.INTAKE_CAPTURED, 'template_setup');
519
+ });
520
+ currentProgress += steps[1].weight;
521
+
522
+ // Step 3: BUILD
523
+ await executeStep(steps[2], currentProgress, async () => {
524
+ const sessionPath = await getCurrentSessionPath();
525
+ const evidencePath = path.join(sessionPath, 'evidence');
526
+ await ensureDir(evidencePath);
527
+ const logPath = path.join(evidencePath, 'build.log');
528
+
529
+ await transitionTo(STATES.BUILD_IN_PROGRESS, 'template_build_start');
530
+
531
+ const fullPrompt = await buildPromptWithContext(prompt, process.cwd());
532
+
533
+ await appendToFile(logPath, `[${new Date().toISOString()}] Template Build: ${templateId}\n`);
534
+ await appendToFile(logPath, `Prompt:\n${prompt}\n\n`);
535
+
536
+ const buildResult = await spawnClaudeCode(fullPrompt, {
537
+ cwd: process.cwd(),
538
+ logPath: logPath
539
+ });
540
+
541
+ await appendToFile(logPath, `[${new Date().toISOString()}] Build completed with code: ${buildResult.code}\n`);
542
+
543
+ // Count files created
544
+ const files = await fs.readdir(process.cwd());
545
+ results.filesCreated = files.filter(f => !f.startsWith('.')).length;
546
+
547
+ const freshState = await loadState();
548
+ freshState.build_completed = new Date().toISOString();
549
+ freshState.template_used = templateId;
550
+ await saveState(freshState);
551
+
552
+ await transitionTo(STATES.BUILD_DONE, 'template_build_done');
553
+ });
554
+ currentProgress += steps[2].weight;
555
+
556
+ // Step 4: REVIEW
557
+ await executeStep(steps[3], currentProgress, async () => {
558
+ const testResult = await runTests(process.cwd());
559
+ results.testsPassed = testResult.summary.passed;
560
+ results.testsTotal = testResult.summary.total;
561
+ results.allPassed = testResult.passed;
562
+
563
+ await transitionTo(STATES.REVIEW_PASSED, 'template_review');
564
+ });
565
+ currentProgress = 100;
566
+
567
+ // Show final progress
568
+ console.log(renderProgressBar(100));
569
+ console.log();
570
+
571
+ // Show summary
572
+ const duration = ((Date.now() - startTime) / 1000 / 60).toFixed(1);
573
+ showTemplateSummary(template, projectName, projectPath, duration, results, options);
574
+
575
+ // Auto-open if requested
576
+ if (options.preview || options.open) {
577
+ await openProject(projectPath);
578
+ }
579
+
580
+ } catch (error) {
581
+ console.log();
582
+ printError(`Template build failed: ${error.message}`);
583
+ console.log(chalk.gray(`Project location: ${projectPath}`));
584
+ console.log(chalk.gray('Check .vibecode/sessions/*/evidence/build.log for details.'));
585
+ process.exit(1);
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Show template mode header
591
+ */
592
+ function showTemplateHeader(template, projectName, icon) {
593
+ console.log();
594
+ console.log(chalk.magenta('╭' + '─'.repeat(68) + '╮'));
595
+ console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
596
+ console.log(chalk.magenta('│') + chalk.bold.white(` ${icon} TEMPLATE: ${template.name.toUpperCase()}`) + ' '.repeat(Math.max(0, 53 - template.name.length)) + chalk.magenta('│'));
597
+ console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
598
+
599
+ const desc = template.description.substring(0, 55);
600
+ console.log(chalk.magenta('│') + chalk.gray(` ${desc}`) + ' '.repeat(Math.max(0, 65 - desc.length)) + chalk.magenta('│'));
601
+
602
+ console.log(chalk.magenta('│') + chalk.gray(` Stack: ${template.stack.join(', ').substring(0, 52)}`) + ' '.repeat(Math.max(0, 55 - template.stack.join(', ').length)) + chalk.magenta('│'));
603
+ console.log(chalk.magenta('│') + chalk.gray(` → ${projectName}`) + ' '.repeat(Math.max(0, 63 - projectName.length)) + chalk.magenta('│'));
604
+
605
+ console.log(chalk.magenta('│') + ' '.repeat(68) + chalk.magenta('│'));
606
+ console.log(chalk.magenta('╰' + '─'.repeat(68) + '╯'));
607
+ console.log();
608
+ }
609
+
610
+ /**
611
+ * Show template mode summary
612
+ */
613
+ function showTemplateSummary(template, projectName, projectPath, duration, results, options) {
614
+ const icon = getCategoryIcon(template.category);
615
+ const testsStatus = results.allPassed
616
+ ? chalk.green(`${results.testsPassed}/${results.testsTotal} passed`)
617
+ : chalk.yellow(`${results.testsPassed}/${results.testsTotal} passed`);
618
+
619
+ console.log(chalk.green('╭' + '─'.repeat(68) + '╮'));
620
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
621
+ console.log(chalk.green('│') + chalk.bold.white(` ${icon} ${template.name.toUpperCase()} - COMPLETE!`) + ' '.repeat(Math.max(0, 48 - template.name.length)) + chalk.green('│'));
622
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
623
+ console.log(chalk.green('│') + chalk.white(` 📁 Project: ${projectName}`) + ' '.repeat(Math.max(0, 51 - projectName.length)) + chalk.green('│'));
624
+ console.log(chalk.green('│') + chalk.white(` 📦 Template: ${template.id}`) + ' '.repeat(Math.max(0, 51 - template.id.length)) + chalk.green('│'));
625
+ console.log(chalk.green('│') + chalk.white(` 📄 Files: ${results.filesCreated} created`) + ' '.repeat(42) + chalk.green('│'));
626
+ console.log(chalk.green('│') + chalk.white(` 🧪 Tests: `) + testsStatus + ' '.repeat(Math.max(0, 40)) + chalk.green('│'));
627
+ console.log(chalk.green('│') + chalk.white(` ⏱️ Duration: ${duration} minutes`) + ' '.repeat(Math.max(0, 44 - duration.length)) + chalk.green('│'));
628
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
629
+ console.log(chalk.green('│') + chalk.gray(` 💡 Next steps:`) + ' '.repeat(50) + chalk.green('│'));
630
+ console.log(chalk.green('│') + chalk.gray(` cd ${projectName}`) + ' '.repeat(Math.max(0, 57 - projectName.length)) + chalk.green('│'));
631
+ console.log(chalk.green('│') + chalk.gray(` npm install && npm run dev`) + ' '.repeat(33) + chalk.green('│'));
632
+ console.log(chalk.green('│') + ' '.repeat(68) + chalk.green('│'));
633
+ console.log(chalk.green('╰' + '─'.repeat(68) + '╯'));
634
+ console.log();
635
+ }