@vibecheckai/cli 3.0.8 → 3.0.10

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.
@@ -1,20 +1,577 @@
1
- // bin/runners/runCtx.js
1
+ /**
2
+ * vibecheck ctx - Truth Pack Generator
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * ENTERPRISE EDITION - World-Class Terminal Experience
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Build a comprehensive truth pack of your codebase for AI agents.
9
+ * The truth pack is the ground truth that powers all vibecheck analysis.
10
+ */
11
+
2
12
  const path = require("path");
3
13
  const fs = require("fs");
4
14
  const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
5
15
 
16
+ // ═══════════════════════════════════════════════════════════════════════════════
17
+ // ADVANCED TERMINAL - ANSI CODES & UTILITIES
18
+ // ═══════════════════════════════════════════════════════════════════════════════
19
+
6
20
  const c = {
7
21
  reset: '\x1b[0m',
8
22
  bold: '\x1b[1m',
9
23
  dim: '\x1b[2m',
24
+ italic: '\x1b[3m',
25
+ underline: '\x1b[4m',
26
+ blink: '\x1b[5m',
27
+ inverse: '\x1b[7m',
28
+ hidden: '\x1b[8m',
29
+ strike: '\x1b[9m',
30
+ // Colors
31
+ black: '\x1b[30m',
10
32
  red: '\x1b[31m',
11
33
  green: '\x1b[32m',
12
34
  yellow: '\x1b[33m',
13
35
  blue: '\x1b[34m',
14
- cyan: '\x1b[36m',
15
36
  magenta: '\x1b[35m',
37
+ cyan: '\x1b[36m',
38
+ white: '\x1b[37m',
39
+ // Bright colors
40
+ gray: '\x1b[90m',
41
+ brightRed: '\x1b[91m',
42
+ brightGreen: '\x1b[92m',
43
+ brightYellow: '\x1b[93m',
44
+ brightBlue: '\x1b[94m',
45
+ brightMagenta: '\x1b[95m',
46
+ brightCyan: '\x1b[96m',
47
+ brightWhite: '\x1b[97m',
48
+ // Background
49
+ bgBlack: '\x1b[40m',
50
+ bgRed: '\x1b[41m',
51
+ bgGreen: '\x1b[42m',
52
+ bgYellow: '\x1b[43m',
53
+ bgBlue: '\x1b[44m',
54
+ bgMagenta: '\x1b[45m',
55
+ bgCyan: '\x1b[46m',
56
+ bgWhite: '\x1b[47m',
57
+ // Cursor control
58
+ cursorUp: (n = 1) => `\x1b[${n}A`,
59
+ cursorDown: (n = 1) => `\x1b[${n}B`,
60
+ cursorRight: (n = 1) => `\x1b[${n}C`,
61
+ cursorLeft: (n = 1) => `\x1b[${n}D`,
62
+ clearLine: '\x1b[2K',
63
+ clearScreen: '\x1b[2J',
64
+ saveCursor: '\x1b[s',
65
+ restoreCursor: '\x1b[u',
66
+ hideCursor: '\x1b[?25l',
67
+ showCursor: '\x1b[?25h',
16
68
  };
17
69
 
70
+ // True color support
71
+ const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
72
+ const bgRgb = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
73
+
74
+ // Premium color palette (blue/teal theme for "truth/context")
75
+ const colors = {
76
+ // Gradient for banner
77
+ gradient1: rgb(0, 200, 255), // Bright cyan
78
+ gradient2: rgb(0, 180, 240), // Cyan
79
+ gradient3: rgb(0, 160, 220), // Cyan-blue
80
+ gradient4: rgb(0, 140, 200), // Blue-cyan
81
+ gradient5: rgb(0, 120, 180), // Blue
82
+ gradient6: rgb(0, 100, 160), // Deep blue
83
+
84
+ // Category colors
85
+ routes: rgb(100, 200, 255), // Light blue
86
+ env: rgb(200, 150, 255), // Purple
87
+ auth: rgb(100, 180, 255), // Blue
88
+ billing: rgb(255, 200, 100), // Gold
89
+ enforcement: rgb(255, 150, 100), // Orange
90
+
91
+ // Status colors
92
+ success: rgb(0, 255, 150),
93
+ warning: rgb(255, 200, 0),
94
+ error: rgb(255, 80, 80),
95
+ info: rgb(100, 200, 255),
96
+
97
+ // UI colors
98
+ accent: rgb(0, 200, 255),
99
+ muted: rgb(120, 140, 160),
100
+ subtle: rgb(80, 100, 120),
101
+ highlight: rgb(255, 255, 255),
102
+ };
103
+
104
+ // ═══════════════════════════════════════════════════════════════════════════════
105
+ // PREMIUM BANNER
106
+ // ═══════════════════════════════════════════════════════════════════════════════
107
+
108
+ const CTX_BANNER = `
109
+ ${rgb(0, 220, 255)} ██████╗████████╗██╗ ██╗${c.reset}
110
+ ${rgb(0, 200, 255)} ██╔════╝╚══██╔══╝╚██╗██╔╝${c.reset}
111
+ ${rgb(0, 180, 240)} ██║ ██║ ╚███╔╝ ${c.reset}
112
+ ${rgb(0, 160, 220)} ██║ ██║ ██╔██╗ ${c.reset}
113
+ ${rgb(0, 140, 200)} ╚██████╗ ██║ ██╔╝ ██╗${c.reset}
114
+ ${rgb(0, 120, 180)} ╚═════╝ ╚═╝ ╚═╝ ╚═╝${c.reset}
115
+ `;
116
+
117
+ const BANNER_FULL = `
118
+ ${rgb(0, 220, 255)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
119
+ ${rgb(0, 200, 255)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
120
+ ${rgb(0, 180, 240)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
121
+ ${rgb(0, 160, 220)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
122
+ ${rgb(0, 140, 200)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
123
+ ${rgb(0, 120, 180)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
124
+
125
+ ${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
126
+ ${c.dim} │${c.reset} ${rgb(0, 200, 255)}📦${c.reset} ${c.bold}CTX${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Truth Pack Generator${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Ground Truth for AI${c.reset} ${c.dim}│${c.reset}
127
+ ${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
128
+ `;
129
+
130
+ // ═══════════════════════════════════════════════════════════════════════════════
131
+ // ICONS & SYMBOLS
132
+ // ═══════════════════════════════════════════════════════════════════════════════
133
+
134
+ const ICONS = {
135
+ // Main
136
+ package: '📦',
137
+ truth: '🎯',
138
+ context: '📋',
139
+
140
+ // Status
141
+ check: '✓',
142
+ cross: '✗',
143
+ warning: '⚠',
144
+ info: 'ℹ',
145
+ arrow: '→',
146
+ bullet: '•',
147
+
148
+ // Categories
149
+ routes: '🛤️',
150
+ env: '🌍',
151
+ auth: '🔒',
152
+ billing: '💰',
153
+ enforcement: '🛡️',
154
+
155
+ // Frameworks
156
+ nextjs: '▲',
157
+ fastify: '⚡',
158
+ express: '🚂',
159
+
160
+ // Objects
161
+ file: '📄',
162
+ folder: '📁',
163
+ snapshot: '📸',
164
+ clock: '⏱',
165
+ lightning: '⚡',
166
+ sparkle: '✨',
167
+ link: '🔗',
168
+ gap: '🕳️',
169
+ };
170
+
171
+ // ═══════════════════════════════════════════════════════════════════════════════
172
+ // BOX DRAWING
173
+ // ═══════════════════════════════════════════════════════════════════════════════
174
+
175
+ const BOX = {
176
+ topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯',
177
+ horizontal: '─', vertical: '│',
178
+ teeRight: '├', teeLeft: '┤', teeDown: '┬', teeUp: '┴',
179
+ cross: '┼',
180
+ // Double line
181
+ dTopLeft: '╔', dTopRight: '╗', dBottomLeft: '╚', dBottomRight: '╝',
182
+ dHorizontal: '═', dVertical: '║',
183
+ // Rounded
184
+ rTopLeft: '╭', rTopRight: '╮', rBottomLeft: '╰', rBottomRight: '╯',
185
+ };
186
+
187
+ // ═══════════════════════════════════════════════════════════════════════════════
188
+ // SPINNER & PROGRESS
189
+ // ═══════════════════════════════════════════════════════════════════════════════
190
+
191
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
192
+ const SPINNER_DOTS = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
193
+ const SPINNER_BLOCKS = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▂'];
194
+
195
+ let spinnerIndex = 0;
196
+ let spinnerInterval = null;
197
+ let spinnerStartTime = null;
198
+
199
+ function formatDuration(ms) {
200
+ if (ms < 1000) return `${ms}ms`;
201
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
202
+ const mins = Math.floor(ms / 60000);
203
+ const secs = Math.floor((ms % 60000) / 1000);
204
+ return `${mins}m ${secs}s`;
205
+ }
206
+
207
+ function formatNumber(num) {
208
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
209
+ }
210
+
211
+ function truncate(str, len) {
212
+ if (!str) return '';
213
+ if (str.length <= len) return str;
214
+ return str.slice(0, len - 3) + '...';
215
+ }
216
+
217
+ function padCenter(str, width) {
218
+ const padding = Math.max(0, width - str.length);
219
+ const left = Math.floor(padding / 2);
220
+ const right = padding - left;
221
+ return ' '.repeat(left) + str + ' '.repeat(right);
222
+ }
223
+
224
+ function progressBar(percent, width = 30, opts = {}) {
225
+ const filled = Math.round((percent / 100) * width);
226
+ const empty = width - filled;
227
+
228
+ let filledColor = opts.color || colors.accent;
229
+ if (!opts.color) {
230
+ if (percent >= 80) filledColor = colors.success;
231
+ else if (percent >= 50) filledColor = colors.warning;
232
+ else filledColor = colors.error;
233
+ }
234
+
235
+ const filledChar = opts.filled || '█';
236
+ const emptyChar = opts.empty || '░';
237
+
238
+ return `${filledColor}${filledChar.repeat(filled)}${c.dim}${emptyChar.repeat(empty)}${c.reset}`;
239
+ }
240
+
241
+ function startSpinner(message, color = colors.accent) {
242
+ spinnerStartTime = Date.now();
243
+ process.stdout.write(c.hideCursor);
244
+
245
+ spinnerInterval = setInterval(() => {
246
+ const elapsed = formatDuration(Date.now() - spinnerStartTime);
247
+ process.stdout.write(`\r${c.clearLine} ${color}${SPINNER_DOTS[spinnerIndex]}${c.reset} ${message} ${c.dim}${elapsed}${c.reset}`);
248
+ spinnerIndex = (spinnerIndex + 1) % SPINNER_DOTS.length;
249
+ }, 80);
250
+ }
251
+
252
+ function stopSpinner(message, success = true) {
253
+ if (spinnerInterval) {
254
+ clearInterval(spinnerInterval);
255
+ spinnerInterval = null;
256
+ }
257
+ const elapsed = spinnerStartTime ? formatDuration(Date.now() - spinnerStartTime) : '';
258
+ const icon = success ? `${colors.success}${ICONS.check}${c.reset}` : `${colors.error}${ICONS.cross}${c.reset}`;
259
+ process.stdout.write(`\r${c.clearLine} ${icon} ${message} ${c.dim}${elapsed}${c.reset}\n`);
260
+ process.stdout.write(c.showCursor);
261
+ spinnerStartTime = null;
262
+ }
263
+
264
+ // ═══════════════════════════════════════════════════════════════════════════════
265
+ // SECTION HEADERS
266
+ // ═══════════════════════════════════════════════════════════════════════════════
267
+
268
+ function printBanner() {
269
+ console.log(BANNER_FULL);
270
+ }
271
+
272
+ function printCompactBanner() {
273
+ console.log(CTX_BANNER);
274
+ }
275
+
276
+ function printDivider(char = '─', width = 69, color = c.dim) {
277
+ console.log(`${color} ${char.repeat(width)}${c.reset}`);
278
+ }
279
+
280
+ function printSection(title, icon = '◆') {
281
+ console.log();
282
+ console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
283
+ printDivider();
284
+ }
285
+
286
+ // ═══════════════════════════════════════════════════════════════════════════════
287
+ // CATEGORY BOXES - Premium Truth Pack Display
288
+ // ═══════════════════════════════════════════════════════════════════════════════
289
+
290
+ function printCategoryBox(title, icon, color, items) {
291
+ const w = 68;
292
+
293
+ console.log(` ${color}${BOX.topLeft}${BOX.horizontal} ${icon} ${title} ${BOX.horizontal.repeat(w - title.length - 6)}${BOX.topRight}${c.reset}`);
294
+
295
+ for (const item of items) {
296
+ let line = ` ${color}${BOX.vertical}${c.reset} `;
297
+
298
+ if (item.label && item.value !== undefined) {
299
+ const valueStr = typeof item.value === 'number' ? formatNumber(item.value) : item.value;
300
+ const labelPad = 18;
301
+ line += `${c.bold}${item.label.padEnd(labelPad)}${c.reset} ${valueStr}`;
302
+
303
+ if (item.extra) {
304
+ line += ` ${c.dim}${item.extra}${c.reset}`;
305
+ }
306
+ if (item.status) {
307
+ const statusColor = item.status === 'success' ? colors.success :
308
+ item.status === 'warning' ? colors.warning :
309
+ item.status === 'error' ? colors.error : colors.muted;
310
+ const statusIcon = item.status === 'success' ? ICONS.check :
311
+ item.status === 'warning' ? ICONS.warning :
312
+ item.status === 'error' ? ICONS.cross : ICONS.bullet;
313
+ line += ` ${statusColor}${statusIcon}${c.reset}`;
314
+ if (item.statusText) {
315
+ line += ` ${statusColor}${item.statusText}${c.reset}`;
316
+ }
317
+ }
318
+ } else if (item.text) {
319
+ line += item.text;
320
+ } else if (item.divider) {
321
+ line = ` ${color}${BOX.teeRight}${BOX.horizontal.repeat(w)}${BOX.teeLeft}${c.reset}`;
322
+ }
323
+
324
+ console.log(line);
325
+ }
326
+
327
+ console.log(` ${color}${BOX.bottomLeft}${BOX.horizontal.repeat(w)}${BOX.bottomRight}${c.reset}`);
328
+ }
329
+
330
+ // ═══════════════════════════════════════════════════════════════════════════════
331
+ // TRUTH PACK SUMMARY DISPLAY
332
+ // ═══════════════════════════════════════════════════════════════════════════════
333
+
334
+ function printTruthpackSummary(truthpack, root) {
335
+ // Calculate stats
336
+ const nextRoutes = (truthpack.routes?.server || []).filter(r =>
337
+ r.handler?.includes("app/api") || r.handler?.includes("pages/api")
338
+ ).length;
339
+ const otherRoutes = (truthpack.routes?.server || []).filter(r =>
340
+ !r.handler?.includes("app/api") && !r.handler?.includes("pages/api")
341
+ ).length;
342
+
343
+ const detectedFramework = truthpack.meta?.framework ||
344
+ (truthpack.routes?.server || []).find(r => r.evidence?.[0]?.reason)?.evidence?.[0]?.reason?.split(' ')[0] ||
345
+ 'Express';
346
+
347
+ const clientRefs = truthpack.routes?.clientRefs?.length || 0;
348
+ const gaps = truthpack.routes?.gaps?.length || 0;
349
+ const totalRoutes = (truthpack.routes?.server || []).length;
350
+
351
+ const envUsed = truthpack.env?.vars?.length || 0;
352
+ const envDeclared = truthpack.env?.declared?.length || 0;
353
+ const envSources = (truthpack.env?.declaredSources || []).join(", ") || "none";
354
+ const envCoverage = envUsed > 0 ? Math.round((envDeclared / envUsed) * 100) : 100;
355
+
356
+ const nextMiddleware = truthpack.auth?.nextMiddleware?.length || 0;
357
+ const fastifySignals = truthpack.auth?.fastify?.signalTypes?.length || 0;
358
+ const authPatterns = truthpack.auth?.patterns?.length || 0;
359
+
360
+ const hasStripe = truthpack.billing?.hasStripe;
361
+ const webhooks = truthpack.billing?.summary?.webhookHandlersFound || 0;
362
+ const stripeUsage = truthpack.billing?.stripeUsage?.length || 0;
363
+
364
+ const enforced = truthpack.enforcement?.enforcedCount || 0;
365
+ const checked = truthpack.enforcement?.checkedCount || 0;
366
+ const enforcementRate = checked > 0 ? Math.round((enforced / checked) * 100) : 100;
367
+
368
+ // Routes Box
369
+ console.log();
370
+ printCategoryBox('ROUTES', ICONS.routes, colors.routes, [
371
+ {
372
+ label: `${ICONS.nextjs} Next.js:`,
373
+ value: nextRoutes,
374
+ extra: 'API routes'
375
+ },
376
+ {
377
+ label: `${ICONS.fastify} ${detectedFramework}:`,
378
+ value: otherRoutes,
379
+ extra: 'server routes'
380
+ },
381
+ { divider: true },
382
+ {
383
+ label: 'Client refs:',
384
+ value: clientRefs,
385
+ status: gaps > 0 ? 'warning' : 'success',
386
+ statusText: gaps > 0 ? `${gaps} gaps` : 'no gaps'
387
+ },
388
+ {
389
+ label: 'Total:',
390
+ value: totalRoutes,
391
+ extra: 'routes mapped'
392
+ },
393
+ ]);
394
+
395
+ // Environment Box
396
+ printCategoryBox('ENVIRONMENT', ICONS.env, colors.env, [
397
+ {
398
+ label: 'Used:',
399
+ value: envUsed,
400
+ extra: 'env vars'
401
+ },
402
+ {
403
+ label: 'Declared:',
404
+ value: envDeclared,
405
+ extra: `(${envSources})`
406
+ },
407
+ { divider: true },
408
+ {
409
+ label: 'Coverage:',
410
+ value: `${envCoverage}%`,
411
+ status: envCoverage >= 100 ? 'success' : envCoverage >= 80 ? 'warning' : 'error',
412
+ statusText: envCoverage >= 100 ? 'complete' : 'missing declarations'
413
+ },
414
+ ]);
415
+
416
+ // Auth Box
417
+ printCategoryBox('AUTH', ICONS.auth, colors.auth, [
418
+ {
419
+ label: 'Next middleware:',
420
+ value: nextMiddleware,
421
+ extra: 'handlers'
422
+ },
423
+ {
424
+ label: 'Fastify signals:',
425
+ value: fastifySignals,
426
+ extra: 'types'
427
+ },
428
+ {
429
+ label: 'Auth patterns:',
430
+ value: authPatterns,
431
+ extra: 'detected'
432
+ },
433
+ ]);
434
+
435
+ // Billing Box
436
+ printCategoryBox('BILLING', ICONS.billing, colors.billing, [
437
+ {
438
+ label: 'Stripe:',
439
+ value: hasStripe ? 'detected' : 'not detected',
440
+ status: hasStripe ? 'success' : null,
441
+ },
442
+ {
443
+ label: 'Webhooks:',
444
+ value: webhooks,
445
+ extra: 'handlers'
446
+ },
447
+ {
448
+ label: 'Stripe usage:',
449
+ value: stripeUsage,
450
+ extra: 'call sites'
451
+ },
452
+ ]);
453
+
454
+ // Enforcement Box
455
+ printCategoryBox('ENFORCEMENT', ICONS.enforcement, colors.enforcement, [
456
+ {
457
+ label: 'Paid routes:',
458
+ value: `${enforced}/${checked}`,
459
+ extra: 'enforced'
460
+ },
461
+ {
462
+ label: 'Coverage:',
463
+ value: `${enforcementRate}%`,
464
+ status: enforcementRate >= 100 ? 'success' : enforcementRate >= 80 ? 'warning' : 'error',
465
+ },
466
+ ]);
467
+ }
468
+
469
+ // ═══════════════════════════════════════════════════════════════════════════════
470
+ // SUCCESS CARD
471
+ // ═══════════════════════════════════════════════════════════════════════════════
472
+
473
+ function printSuccessCard(outputPath, snapshotPath = null, fastifyEntry = null, duration = null) {
474
+ const w = 68;
475
+
476
+ console.log();
477
+ console.log(` ${colors.success}${BOX.dTopLeft}${BOX.dHorizontal.repeat(w)}${BOX.dTopRight}${c.reset}`);
478
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${' '.repeat(w)}${colors.success}${BOX.dVertical}${c.reset}`);
479
+
480
+ const headline = `${ICONS.sparkle} TRUTH PACK GENERATED`;
481
+ const headlinePadded = padCenter(headline, w);
482
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${colors.success}${c.bold}${headlinePadded}${c.reset}${colors.success}${BOX.dVertical}${c.reset}`);
483
+
484
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${' '.repeat(w)}${colors.success}${BOX.dVertical}${c.reset}`);
485
+
486
+ // Output path
487
+ const outputLabel = `${ICONS.arrow} `;
488
+ const outputValue = truncate(outputPath, 55);
489
+ const outputLine = ` ${outputLabel}${outputValue}`;
490
+ const outputPadded = outputLine + ' '.repeat(Math.max(0, w - outputLine.length + 5));
491
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${outputPadded}${colors.success}${BOX.dVertical}${c.reset}`);
492
+
493
+ // Snapshot path (if any)
494
+ if (snapshotPath) {
495
+ const snapLabel = `${ICONS.snapshot} `;
496
+ const snapValue = truncate(snapshotPath, 55);
497
+ const snapLine = ` ${snapLabel}${snapValue}`;
498
+ const snapPadded = snapLine + ' '.repeat(Math.max(0, w - snapLine.length + 5));
499
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${snapPadded}${colors.success}${BOX.dVertical}${c.reset}`);
500
+ }
501
+
502
+ // Fastify entry (if detected)
503
+ if (fastifyEntry) {
504
+ const entryLabel = `${ICONS.fastify} Fastify: `;
505
+ const entryValue = truncate(fastifyEntry, 45);
506
+ const entryLine = ` ${entryLabel}${c.dim}${entryValue}${c.reset}`;
507
+ // Need to account for ANSI codes in padding
508
+ const visibleLength = ` ${ICONS.fastify} Fastify: ${entryValue}`.length;
509
+ const entryPadded = entryLine + ' '.repeat(Math.max(0, w - visibleLength));
510
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${entryPadded}${colors.success}${BOX.dVertical}${c.reset}`);
511
+ }
512
+
513
+ // Duration (if provided)
514
+ if (duration) {
515
+ const durLabel = `${ICONS.clock} `;
516
+ const durValue = formatDuration(duration);
517
+ const durLine = ` ${durLabel}${durValue}`;
518
+ const durPadded = durLine + ' '.repeat(Math.max(0, w - durLine.length + 5));
519
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${c.dim}${durPadded}${c.reset}${colors.success}${BOX.dVertical}${c.reset}`);
520
+ }
521
+
522
+ console.log(` ${colors.success}${BOX.dVertical}${c.reset}${' '.repeat(w)}${colors.success}${BOX.dVertical}${c.reset}`);
523
+ console.log(` ${colors.success}${BOX.dBottomLeft}${BOX.dHorizontal.repeat(w)}${BOX.dBottomRight}${c.reset}`);
524
+ console.log();
525
+ }
526
+
527
+ // ═══════════════════════════════════════════════════════════════════════════════
528
+ // HELP DISPLAY
529
+ // ═══════════════════════════════════════════════════════════════════════════════
530
+
531
+ function printHelp() {
532
+ console.log(BANNER_FULL);
533
+ console.log(`
534
+ ${c.bold}Usage:${c.reset} vibecheck ctx [options]
535
+
536
+ ${c.bold}Truth Pack Generator${c.reset} — Build ground truth for AI agents.
537
+
538
+ ${c.bold}What It Generates:${c.reset}
539
+ ${colors.routes}${ICONS.routes} routes${c.reset} Server routes + client refs + gaps
540
+ ${colors.env}${ICONS.env} env${c.reset} Used vars + declared vars + sources
541
+ ${colors.auth}${ICONS.auth} auth${c.reset} Middleware + patterns + signals
542
+ ${colors.billing}${ICONS.billing} billing${c.reset} Stripe usage + webhooks + gates
543
+ ${colors.enforcement}${ICONS.enforcement} enforcement${c.reset} Paid surface checks
544
+
545
+ ${c.bold}Options:${c.reset}
546
+ ${colors.accent}--json, -j${c.reset} Output raw JSON
547
+ ${colors.accent}--snapshot, -s${c.reset} Save timestamped snapshot
548
+ ${colors.accent}--fastify-entry${c.reset} Fastify entry file ${c.dim}(e.g. src/server.ts)${c.reset}
549
+ ${colors.accent}--path, -p${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
550
+ ${colors.accent}--help, -h${c.reset} Show this help
551
+
552
+ ${c.bold}Output:${c.reset}
553
+ ${c.dim}.vibecheck/truth/truthpack.json${c.reset} Main truth pack
554
+ ${c.dim}.vibecheck/truth/truthpack.md${c.reset} Human-readable summary
555
+
556
+ ${c.bold}Examples:${c.reset}
557
+ ${c.dim}# Generate truth pack${c.reset}
558
+ vibecheck ctx
559
+
560
+ ${c.dim}# Save timestamped snapshot${c.reset}
561
+ vibecheck ctx --snapshot
562
+
563
+ ${c.dim}# Export as JSON${c.reset}
564
+ vibecheck ctx --json > truthpack.json
565
+
566
+ ${c.dim}# Specify fastify entry${c.reset}
567
+ vibecheck ctx --fastify-entry src/server.ts
568
+ `);
569
+ }
570
+
571
+ // ═══════════════════════════════════════════════════════════════════════════════
572
+ // ARGS PARSER
573
+ // ═══════════════════════════════════════════════════════════════════════════════
574
+
18
575
  function parseArgs(args) {
19
576
  const opts = {
20
577
  path: process.cwd(),
@@ -22,12 +579,14 @@ function parseArgs(args) {
22
579
  json: false,
23
580
  snapshot: false,
24
581
  help: false,
582
+ verbose: false,
25
583
  };
26
584
 
27
585
  for (let i = 0; i < args.length; i++) {
28
586
  const arg = args[i];
29
587
  if (arg === '--json' || arg === '-j') opts.json = true;
30
588
  else if (arg === '--snapshot' || arg === '-s') opts.snapshot = true;
589
+ else if (arg === '--verbose' || arg === '-v') opts.verbose = true;
31
590
  else if (arg === '--fastify-entry') opts.fastifyEntry = args[++i];
32
591
  else if (arg === '--path' || arg === '-p') opts.path = args[++i];
33
592
  else if (arg === '--help' || arg === '-h') opts.help = true;
@@ -36,41 +595,9 @@ function parseArgs(args) {
36
595
  return opts;
37
596
  }
38
597
 
39
- function printHelp() {
40
- console.log(`
41
- ${c.cyan}${c.bold}📦 vibecheck ctx${c.reset} — Truth Pack Generator
42
-
43
- ${c.dim}Build a comprehensive truth pack of your codebase for AI agents.${c.reset}
44
-
45
- ${c.bold}USAGE${c.reset}
46
- vibecheck ctx Generate truth pack
47
- vibecheck ctx --snapshot Save timestamped snapshot
48
- vibecheck ctx --json Output as JSON
49
-
50
- ${c.bold}OPTIONS${c.reset}
51
- --json, -j Output raw JSON
52
- --snapshot, -s Save to snapshots/ with timestamp
53
- --fastify-entry Fastify entry file (e.g. src/server.ts)
54
- --path, -p Project path (default: current directory)
55
- --help, -h Show this help
56
-
57
- ${c.bold}TRUTH PACK CONTENTS${c.reset}
58
- • ${c.cyan}routes${c.reset} Server routes + client refs + gaps
59
- • ${c.cyan}env${c.reset} Used vars + declared vars + sources
60
- • ${c.cyan}auth${c.reset} Middleware + patterns + signals
61
- • ${c.cyan}billing${c.reset} Stripe usage + webhooks + gates
62
- • ${c.cyan}enforcement${c.reset} Paid surface checks
63
-
64
- ${c.bold}OUTPUT${c.reset}
65
- .vibecheck/truth/truthpack.json Main truth pack
66
- .vibecheck/truth/truthpack.md Human-readable summary
67
-
68
- ${c.bold}EXAMPLES${c.reset}
69
- vibecheck ctx # Generate truth pack
70
- vibecheck ctx --snapshot # Save snapshot
71
- vibecheck ctx --json > truthpack.json # Export as JSON
72
- `);
73
- }
598
+ // ═══════════════════════════════════════════════════════════════════════════════
599
+ // MAIN CTX FUNCTION
600
+ // ═══════════════════════════════════════════════════════════════════════════════
74
601
 
75
602
  async function runCtx(args) {
76
603
  const opts = typeof args === 'object' && !Array.isArray(args)
@@ -83,110 +610,66 @@ async function runCtx(args) {
83
610
  }
84
611
 
85
612
  const root = opts.repoRoot || opts.path || process.cwd();
613
+ const projectName = path.basename(root);
86
614
  const entry = opts.fastifyEntry || detectFastifyEntry(root);
615
+ const startTime = Date.now();
87
616
 
88
- // Print header
89
- if (!opts.json && !opts.print) {
90
- console.log('');
91
- console.log(`${c.cyan}╔══════════════════════════════════════════════════════════════════════╗${c.reset}`);
92
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
93
- console.log(`${c.cyan}║${c.reset} ${c.bold}📦 TRUTH PACK GENERATOR${c.reset} ${c.cyan}║${c.reset}`);
94
- console.log(`${c.cyan}║${c.reset} ${c.dim}Building ground truth for AI agents${c.reset} ${c.cyan}║${c.reset}`);
95
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
96
- console.log(`${c.cyan}╚══════════════════════════════════════════════════════════════════════╝${c.reset}`);
97
- console.log('');
617
+ // JSON mode - minimal output
618
+ if (opts.json || opts.print) {
619
+ const truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: entry });
620
+ writeTruthpack(root, truthpack);
621
+ console.log(JSON.stringify(truthpack, null, 2));
622
+ return 0;
98
623
  }
99
624
 
100
- // Build truthpack with progress
101
- if (!opts.json && !opts.print) {
102
- process.stdout.write(`${c.dim}○${c.reset} Building truth pack...`);
625
+ // Print banner
626
+ printBanner();
627
+
628
+ console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
629
+ console.log(` ${c.dim}Path:${c.reset} ${root}`);
630
+ if (entry) {
631
+ console.log(` ${c.dim}Fastify:${c.reset} ${colors.accent}${entry}${c.reset}`);
103
632
  }
104
633
 
634
+ // Build truthpack with spinner
635
+ console.log();
636
+ startSpinner('Building truth pack...', colors.accent);
637
+
105
638
  const truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: entry });
106
639
  writeTruthpack(root, truthpack);
107
640
 
108
- if (!opts.json && !opts.print) {
109
- process.stdout.write(`\r${c.green}✓${c.reset} Truth pack built \n`);
110
- }
641
+ const buildDuration = Date.now() - startTime;
642
+ stopSpinner('Truth pack built', true);
111
643
 
112
644
  // Handle snapshot
645
+ let snapshotPath = null;
113
646
  if (opts.snapshot) {
647
+ startSpinner('Saving snapshot...', colors.accent);
648
+
114
649
  const snapshotsDir = path.join(root, ".vibecheck", "truth", "snapshots");
115
650
  fs.mkdirSync(snapshotsDir, { recursive: true });
116
651
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
117
- const snapshotPath = path.join(snapshotsDir, `truthpack-${timestamp}.json`);
652
+ snapshotPath = path.join(snapshotsDir, `truthpack-${timestamp}.json`);
118
653
  fs.writeFileSync(snapshotPath, JSON.stringify(truthpack, null, 2));
119
- if (!opts.json) {
120
- console.log(`${c.green}✓${c.reset} Snapshot saved: ${c.dim}${path.relative(root, snapshotPath)}${c.reset}`);
121
- }
654
+
655
+ stopSpinner('Snapshot saved', true);
122
656
  }
123
657
 
124
- // JSON output
125
- if (opts.json || opts.print) {
126
- console.log(JSON.stringify(truthpack, null, 2));
127
- return 0;
128
- }
658
+ // Print summary
659
+ printTruthpackSummary(truthpack, root);
129
660
 
130
- // Beautiful summary
131
- const nextRoutes = (truthpack.routes?.server || []).filter(r =>
132
- r.handler?.includes("app/api") || r.handler?.includes("pages/api")
133
- ).length;
134
- const otherRoutes = (truthpack.routes?.server || []).filter(r =>
135
- !r.handler?.includes("app/api") && !r.handler?.includes("pages/api")
136
- ).length;
661
+ // Success card
662
+ const outputPath = path.join(root, ".vibecheck", "truth", "truthpack.json");
663
+ const relativeOutput = path.relative(root, outputPath);
664
+ const relativeSnapshot = snapshotPath ? path.relative(root, snapshotPath) : null;
137
665
 
138
- // Detect framework from routes evidence or truthpack meta
139
- const detectedFramework = truthpack.meta?.framework ||
140
- (truthpack.routes?.server || []).find(r => r.evidence?.[0]?.reason)?.evidence?.[0]?.reason?.split(' ')[0] ||
141
- 'Express';
142
- const clientRefs = truthpack.routes?.clientRefs?.length || 0;
143
- const gaps = truthpack.routes?.gaps?.length || 0;
144
-
145
- const envUsed = truthpack.env?.vars?.length || 0;
146
- const envDeclared = truthpack.env?.declared?.length || 0;
147
- const envSources = (truthpack.env?.declaredSources || []).join(", ") || "none";
148
-
149
- const nextMiddleware = truthpack.auth?.nextMiddleware?.length || 0;
150
- const fastifySignals = truthpack.auth?.fastify?.signalTypes?.length || 0;
151
-
152
- const hasStripe = truthpack.billing?.hasStripe;
153
- const webhooks = truthpack.billing?.summary?.webhookHandlersFound || 0;
666
+ printSuccessCard(relativeOutput, relativeSnapshot, entry, buildDuration);
154
667
 
155
- const enforced = truthpack.enforcement?.enforcedCount || 0;
156
- const checked = truthpack.enforcement?.checkedCount || 0;
157
-
158
- console.log('');
159
- console.log(`${c.cyan}┌─ Routes ──────────────────────────────────────────────────────────────┐${c.reset}`);
160
- console.log(`${c.cyan}│${c.reset} ${c.bold}▲ Next.js:${c.reset} ${nextRoutes} routes`);
161
- console.log(`${c.cyan}│${c.reset} ${c.bold}⚡ ${detectedFramework}:${c.reset} ${otherRoutes} routes`);
162
- console.log(`${c.cyan}│${c.reset} ${c.dim}Client refs:${c.reset} ${clientRefs} ${gaps > 0 ? `${c.yellow}⚠ ${gaps} gaps${c.reset}` : `${c.green}✓ no gaps${c.reset}`}`);
163
- console.log(`${c.cyan}└───────────────────────────────────────────────────────────────────────┘${c.reset}`);
164
-
165
- console.log(`${c.magenta}┌─ Environment ─────────────────────────────────────────────────────────┐${c.reset}`);
166
- console.log(`${c.magenta}│${c.reset} ${c.bold}Used:${c.reset} ${envUsed} env vars`);
167
- console.log(`${c.magenta}│${c.reset} ${c.bold}Declared:${c.reset} ${envDeclared} (${envSources})`);
168
- console.log(`${c.magenta}└───────────────────────────────────────────────────────────────────────┘${c.reset}`);
169
-
170
- console.log(`${c.blue}┌─ Auth ─────────────────────────────────────────────────────────────────┐${c.reset}`);
171
- console.log(`${c.blue}│${c.reset} ${c.bold}Next middleware:${c.reset} ${nextMiddleware}`);
172
- console.log(`${c.blue}│${c.reset} ${c.bold}Fastify signals:${c.reset} ${fastifySignals}`);
173
- console.log(`${c.blue}└───────────────────────────────────────────────────────────────────────┘${c.reset}`);
174
-
175
- console.log(`${c.yellow}┌─ Billing ──────────────────────────────────────────────────────────────┐${c.reset}`);
176
- console.log(`${c.yellow}│${c.reset} ${c.bold}Stripe:${c.reset} ${hasStripe ? `${c.green}✓ detected${c.reset}` : `${c.dim}not detected${c.reset}`}`);
177
- console.log(`${c.yellow}│${c.reset} ${c.bold}Webhooks:${c.reset} ${webhooks} handlers`);
178
- console.log(`${c.yellow}│${c.reset} ${c.bold}Enforced:${c.reset} ${enforced}/${checked} paid routes`);
179
- console.log(`${c.yellow}└───────────────────────────────────────────────────────────────────────┘${c.reset}`);
180
-
181
- console.log('');
182
- console.log(`${c.green}${c.bold}✓ Truth pack saved${c.reset}`);
183
- console.log(` ${c.dim}→${c.reset} ${path.join(root, ".vibecheck", "truth", "truthpack.json")}`);
184
- if (entry) {
185
- console.log(` ${c.dim}Fastify entry:${c.reset} ${entry}`);
186
- }
187
- console.log('');
668
+ // Next steps hint
669
+ console.log(` ${c.dim}Next:${c.reset} ${colors.accent}vibecheck ship${c.reset} ${c.dim}to check if you're ready to ship${c.reset}`);
670
+ console.log();
188
671
 
189
672
  return 0;
190
673
  }
191
674
 
192
- module.exports = { runCtx };
675
+ module.exports = { runCtx };