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