@vibecheckai/cli 3.1.2 → 3.1.4

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 (47) hide show
  1. package/README.md +60 -33
  2. package/bin/registry.js +319 -34
  3. package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
  4. package/bin/runners/REPORT_AUDIT.md +64 -0
  5. package/bin/runners/lib/entitlements-v2.js +97 -28
  6. package/bin/runners/lib/entitlements.js +3 -6
  7. package/bin/runners/lib/init-wizard.js +1 -1
  8. package/bin/runners/lib/report-engine.js +459 -280
  9. package/bin/runners/lib/report-html.js +1154 -1423
  10. package/bin/runners/lib/report-output.js +187 -0
  11. package/bin/runners/lib/report-templates.js +848 -850
  12. package/bin/runners/lib/scan-output.js +545 -0
  13. package/bin/runners/lib/server-usage.js +0 -12
  14. package/bin/runners/lib/ship-output.js +641 -0
  15. package/bin/runners/lib/status-output.js +253 -0
  16. package/bin/runners/lib/terminal-ui.js +853 -0
  17. package/bin/runners/runCheckpoint.js +502 -0
  18. package/bin/runners/runContracts.js +105 -0
  19. package/bin/runners/runExport.js +93 -0
  20. package/bin/runners/runFix.js +31 -24
  21. package/bin/runners/runInit.js +377 -112
  22. package/bin/runners/runInstall.js +1 -5
  23. package/bin/runners/runLabs.js +3 -3
  24. package/bin/runners/runPolish.js +2452 -0
  25. package/bin/runners/runProve.js +2 -2
  26. package/bin/runners/runReport.js +251 -200
  27. package/bin/runners/runRuntime.js +110 -0
  28. package/bin/runners/runScan.js +477 -379
  29. package/bin/runners/runSecurity.js +92 -0
  30. package/bin/runners/runShip.js +137 -207
  31. package/bin/runners/runStatus.js +16 -68
  32. package/bin/runners/utils.js +5 -5
  33. package/bin/vibecheck.js +25 -11
  34. package/mcp-server/index.js +150 -18
  35. package/mcp-server/package.json +2 -2
  36. package/mcp-server/premium-tools.js +13 -13
  37. package/mcp-server/tier-auth.js +292 -27
  38. package/mcp-server/vibecheck-tools.js +9 -9
  39. package/package.json +1 -1
  40. package/bin/runners/runClaimVerifier.js +0 -483
  41. package/bin/runners/runContextCompiler.js +0 -385
  42. package/bin/runners/runGate.js +0 -17
  43. package/bin/runners/runInitGha.js +0 -164
  44. package/bin/runners/runInteractive.js +0 -388
  45. package/bin/runners/runMdc.js +0 -204
  46. package/bin/runners/runMissionGenerator.js +0 -282
  47. package/bin/runners/runTruthpack.js +0 -636
@@ -0,0 +1,502 @@
1
+ /**
2
+ * vibecheck checkpoint - Compare Baseline vs Current
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * Compares baseline scan results with current state to track progress:
6
+ * - Fixed issues
7
+ * - Remaining issues
8
+ * - Regressions (new issues)
9
+ * - Hallucination scoring (PRO)
10
+ * ═══════════════════════════════════════════════════════════════════════════════
11
+ */
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const entitlements = require("./lib/entitlements-v2");
16
+
17
+ // ═══════════════════════════════════════════════════════════════════════════════
18
+ // TERMINAL STYLING
19
+ // ═══════════════════════════════════════════════════════════════════════════════
20
+
21
+ const c = {
22
+ reset: '\x1b[0m',
23
+ bold: '\x1b[1m',
24
+ dim: '\x1b[2m',
25
+ red: '\x1b[31m',
26
+ green: '\x1b[32m',
27
+ yellow: '\x1b[33m',
28
+ blue: '\x1b[34m',
29
+ magenta: '\x1b[35m',
30
+ cyan: '\x1b[36m',
31
+ };
32
+
33
+ const icons = {
34
+ check: '✓',
35
+ cross: '✗',
36
+ arrow: '→',
37
+ warning: '⚠',
38
+ info: 'ℹ',
39
+ star: '★',
40
+ rocket: '🚀',
41
+ target: '🎯',
42
+ chart: '📊',
43
+ brain: '🧠',
44
+ };
45
+
46
+ // ═══════════════════════════════════════════════════════════════════════════════
47
+ // CHECKPOINT CORE
48
+ // ═══════════════════════════════════════════════════════════════════════════════
49
+
50
+ function loadResults(filePath) {
51
+ if (!fs.existsSync(filePath)) {
52
+ return null;
53
+ }
54
+ try {
55
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
56
+ } catch (e) {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function getFindingId(finding) {
62
+ // Create a stable ID for a finding based on its properties
63
+ return `${finding.id || finding.type || 'unknown'}-${finding.file || ''}-${finding.line || 0}`;
64
+ }
65
+
66
+ function compareFindingSets(baseline, current) {
67
+ const baselineSet = new Set(baseline.map(getFindingId));
68
+ const currentSet = new Set(current.map(getFindingId));
69
+ const baselineMap = new Map(baseline.map(f => [getFindingId(f), f]));
70
+ const currentMap = new Map(current.map(f => [getFindingId(f), f]));
71
+
72
+ const fixed = [];
73
+ const remaining = [];
74
+ const regressions = [];
75
+
76
+ // Find fixed (in baseline but not in current)
77
+ for (const id of baselineSet) {
78
+ if (!currentSet.has(id)) {
79
+ fixed.push(baselineMap.get(id));
80
+ }
81
+ }
82
+
83
+ // Find remaining (in both)
84
+ for (const id of baselineSet) {
85
+ if (currentSet.has(id)) {
86
+ remaining.push(currentMap.get(id));
87
+ }
88
+ }
89
+
90
+ // Find regressions (in current but not in baseline)
91
+ for (const id of currentSet) {
92
+ if (!baselineSet.has(id)) {
93
+ regressions.push(currentMap.get(id));
94
+ }
95
+ }
96
+
97
+ return { fixed, remaining, regressions };
98
+ }
99
+
100
+ // ═══════════════════════════════════════════════════════════════════════════════
101
+ // HALLUCINATION DETECTION (PRO)
102
+ // ═══════════════════════════════════════════════════════════════════════════════
103
+
104
+ function detectHallucinations(baseline, current, projectPath) {
105
+ const hallucinations = {
106
+ nonexistentRoutes: [],
107
+ nonexistentEnvVars: [],
108
+ nonexistentImports: [],
109
+ nonexistentFunctions: [],
110
+ contractDriftViolations: [],
111
+ score: 0,
112
+ };
113
+
114
+ // Load truthpack if available
115
+ const truthpackPath = path.join(projectPath, '.vibecheck', 'truthpack.json');
116
+ let truthpack = null;
117
+ if (fs.existsSync(truthpackPath)) {
118
+ try {
119
+ truthpack = JSON.parse(fs.readFileSync(truthpackPath, 'utf8'));
120
+ } catch (e) {
121
+ // Ignore
122
+ }
123
+ }
124
+
125
+ // Check for edits introducing nonexistent routes
126
+ if (truthpack?.routes?.server) {
127
+ const validRoutes = new Set(truthpack.routes.server.map(r => r.path || r.route));
128
+
129
+ for (const finding of current) {
130
+ if (finding.type === 'route-reference' || finding.category === 'routes') {
131
+ const referencedRoute = finding.route || finding.path;
132
+ if (referencedRoute && !validRoutes.has(referencedRoute)) {
133
+ hallucinations.nonexistentRoutes.push({
134
+ finding,
135
+ referencedRoute,
136
+ reason: 'Route does not exist in truthpack',
137
+ });
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ // Check for env vars
144
+ if (truthpack?.env?.vars) {
145
+ const validEnvVars = new Set(truthpack.env.vars.map(v => v.name || v));
146
+
147
+ for (const finding of current) {
148
+ if (finding.type === 'env-reference' || finding.category === 'env') {
149
+ const referencedVar = finding.envVar || finding.name;
150
+ if (referencedVar && !validEnvVars.has(referencedVar)) {
151
+ hallucinations.nonexistentEnvVars.push({
152
+ finding,
153
+ referencedVar,
154
+ reason: 'Environment variable not declared',
155
+ });
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ // Load contracts and check for drift violations
162
+ const contractsPath = path.join(projectPath, '.vibecheck', 'contracts');
163
+ if (fs.existsSync(contractsPath)) {
164
+ // Check route contract
165
+ const routeContractPath = path.join(contractsPath, 'routes.contract.json');
166
+ if (fs.existsSync(routeContractPath)) {
167
+ try {
168
+ const routeContract = JSON.parse(fs.readFileSync(routeContractPath, 'utf8'));
169
+ const contractedRoutes = new Set(routeContract.routes?.map(r => r.path) || []);
170
+
171
+ // Check if current introduces routes not in contract
172
+ if (truthpack?.routes?.server) {
173
+ for (const route of truthpack.routes.server) {
174
+ const routePath = route.path || route.route;
175
+ if (routePath && !contractedRoutes.has(routePath)) {
176
+ hallucinations.contractDriftViolations.push({
177
+ type: 'route-drift',
178
+ route: routePath,
179
+ reason: 'Route exists but not in contract',
180
+ });
181
+ }
182
+ }
183
+ }
184
+ } catch (e) {
185
+ // Ignore
186
+ }
187
+ }
188
+ }
189
+
190
+ // Calculate hallucination score (0-100, lower is better)
191
+ const totalHallucinations =
192
+ hallucinations.nonexistentRoutes.length +
193
+ hallucinations.nonexistentEnvVars.length +
194
+ hallucinations.nonexistentImports.length +
195
+ hallucinations.nonexistentFunctions.length +
196
+ hallucinations.contractDriftViolations.length;
197
+
198
+ hallucinations.score = Math.min(100, totalHallucinations * 10);
199
+
200
+ return hallucinations;
201
+ }
202
+
203
+ // ═══════════════════════════════════════════════════════════════════════════════
204
+ // CLI
205
+ // ═══════════════════════════════════════════════════════════════════════════════
206
+
207
+ function parseArgs(args) {
208
+ const opts = {
209
+ help: false,
210
+ json: false,
211
+ baseline: null,
212
+ current: null,
213
+ hallucination: false,
214
+ save: false,
215
+ path: process.cwd(),
216
+ };
217
+
218
+ for (let i = 0; i < args.length; i++) {
219
+ const arg = args[i];
220
+ if (arg === '--help' || arg === '-h') opts.help = true;
221
+ else if (arg === '--json') opts.json = true;
222
+ else if (arg === '--baseline' || arg === '-b') opts.baseline = args[++i];
223
+ else if (arg === '--current' || arg === '-c') opts.current = args[++i];
224
+ else if (arg === '--hallucination' || arg === '--hall') opts.hallucination = true;
225
+ else if (arg === '--save' || arg === '-s') opts.save = true;
226
+ else if (arg === '--path' || arg === '-p') opts.path = args[++i];
227
+ else if (!arg.startsWith('-')) opts.path = arg;
228
+ }
229
+
230
+ return opts;
231
+ }
232
+
233
+ function printHelp() {
234
+ console.log(`
235
+ ${c.bold}${icons.target} vibecheck checkpoint${c.reset} - Compare Baseline vs Current
236
+
237
+ ${c.dim}Track progress by comparing scan results over time.${c.reset}
238
+
239
+ ${c.bold}USAGE${c.reset}
240
+ vibecheck checkpoint [options]
241
+
242
+ ${c.bold}OPTIONS${c.reset}
243
+ -b, --baseline <file> Path to baseline results file
244
+ -c, --current <file> Path to current results file (default: latest)
245
+ --hallucination Include hallucination scoring ${c.magenta}[PRO]${c.reset}
246
+ -s, --save Save checkpoint to .vibecheck/checkpoints/
247
+ --json Output as JSON
248
+ -p, --path <dir> Project path (default: current directory)
249
+ -h, --help Show this help
250
+
251
+ ${c.bold}EXAMPLES${c.reset}
252
+ vibecheck checkpoint # Compare latest vs baseline
253
+ vibecheck checkpoint --baseline scan-v1.json # Use specific baseline
254
+ vibecheck checkpoint --hallucination # Include AI hallucination check
255
+ vibecheck checkpoint --save # Save checkpoint report
256
+
257
+ ${c.bold}OUTPUT${c.reset}
258
+ • Fixed issues - Issues resolved since baseline
259
+ • Remaining issues - Issues still present
260
+ • Regressions - New issues introduced
261
+ • Hallucination score - AI-generated code issues ${c.magenta}[PRO]${c.reset}
262
+
263
+ ${c.bold}TIER${c.reset}
264
+ ${c.cyan}FREE${c.reset} Basic comparison (fixed, remaining, regressions)
265
+ ${c.magenta}PRO${c.reset} + Hallucination scoring
266
+ `);
267
+ }
268
+
269
+ async function runCheckpoint(args) {
270
+ const opts = parseArgs(args);
271
+
272
+ if (opts.help) {
273
+ printHelp();
274
+ return 0;
275
+ }
276
+
277
+ const projectPath = path.resolve(opts.path);
278
+ const vibecheckDir = path.join(projectPath, '.vibecheck');
279
+ const resultsDir = path.join(vibecheckDir, 'results');
280
+ const checkpointsDir = path.join(vibecheckDir, 'checkpoints');
281
+
282
+ // Check hallucination entitlement
283
+ if (opts.hallucination) {
284
+ const access = await entitlements.enforce("checkpoint.hallucination", { silent: true });
285
+ if (!access.allowed) {
286
+ console.log(`\n${c.yellow}${icons.warning}${c.reset} Hallucination scoring requires ${c.magenta}PRO${c.reset} plan.`);
287
+ console.log(`${c.dim}Running basic checkpoint instead...${c.reset}\n`);
288
+ opts.hallucination = false;
289
+ }
290
+ }
291
+
292
+ // Load baseline
293
+ let baseline = null;
294
+ if (opts.baseline) {
295
+ baseline = loadResults(opts.baseline);
296
+ } else {
297
+ // Try to load from default location
298
+ const baselinePath = path.join(resultsDir, 'baseline.json');
299
+ baseline = loadResults(baselinePath);
300
+ }
301
+
302
+ if (!baseline) {
303
+ console.log(`\n${c.yellow}${icons.warning}${c.reset} No baseline found.`);
304
+ console.log(`${c.dim}Run 'vibecheck scan' first to create a baseline.${c.reset}`);
305
+ console.log(`${c.dim}Or specify --baseline <file> to use a specific file.${c.reset}\n`);
306
+
307
+ if (!opts.json) {
308
+ console.log(`${c.bold}TIP:${c.reset} Create a baseline with:`);
309
+ console.log(` ${c.cyan}vibecheck scan${c.reset}`);
310
+ console.log(` ${c.cyan}cp .vibecheck/results/latest.json .vibecheck/results/baseline.json${c.reset}\n`);
311
+ }
312
+
313
+ return 1;
314
+ }
315
+
316
+ // Load current
317
+ let current = null;
318
+ if (opts.current) {
319
+ current = loadResults(opts.current);
320
+ } else {
321
+ // Try to load latest
322
+ const latestPath = path.join(resultsDir, 'latest.json');
323
+ current = loadResults(latestPath);
324
+ }
325
+
326
+ if (!current) {
327
+ console.log(`\n${c.yellow}${icons.warning}${c.reset} No current results found.`);
328
+ console.log(`${c.dim}Run 'vibecheck scan' to generate current results.${c.reset}\n`);
329
+ return 1;
330
+ }
331
+
332
+ // Extract findings arrays
333
+ const baselineFindings = baseline.findings || baseline.results?.findings || [];
334
+ const currentFindings = current.findings || current.results?.findings || [];
335
+
336
+ // Compare
337
+ const comparison = compareFindingSets(baselineFindings, currentFindings);
338
+
339
+ // Hallucination detection (PRO)
340
+ let hallucinations = null;
341
+ if (opts.hallucination) {
342
+ hallucinations = detectHallucinations(baselineFindings, currentFindings, projectPath);
343
+ }
344
+
345
+ // Build checkpoint result
346
+ const checkpoint = {
347
+ timestamp: new Date().toISOString(),
348
+ baseline: {
349
+ path: opts.baseline || path.join(resultsDir, 'baseline.json'),
350
+ totalFindings: baselineFindings.length,
351
+ generatedAt: baseline.generatedAt || baseline.timestamp,
352
+ },
353
+ current: {
354
+ path: opts.current || path.join(resultsDir, 'latest.json'),
355
+ totalFindings: currentFindings.length,
356
+ generatedAt: current.generatedAt || current.timestamp,
357
+ },
358
+ summary: {
359
+ fixed: comparison.fixed.length,
360
+ remaining: comparison.remaining.length,
361
+ regressions: comparison.regressions.length,
362
+ progressPercent: baselineFindings.length > 0
363
+ ? Math.round((comparison.fixed.length / baselineFindings.length) * 100)
364
+ : 0,
365
+ },
366
+ fixed: comparison.fixed,
367
+ remaining: comparison.remaining,
368
+ regressions: comparison.regressions,
369
+ hallucinations: hallucinations,
370
+ };
371
+
372
+ // Save if requested
373
+ if (opts.save) {
374
+ if (!fs.existsSync(checkpointsDir)) {
375
+ fs.mkdirSync(checkpointsDir, { recursive: true });
376
+ }
377
+ const checkpointFile = path.join(
378
+ checkpointsDir,
379
+ `checkpoint-${new Date().toISOString().split('T')[0]}.json`
380
+ );
381
+ fs.writeFileSync(checkpointFile, JSON.stringify(checkpoint, null, 2));
382
+ }
383
+
384
+ // JSON output
385
+ if (opts.json) {
386
+ console.log(JSON.stringify(checkpoint, null, 2));
387
+ return checkpoint.summary.regressions > 0 ? 1 : 0;
388
+ }
389
+
390
+ // Human-readable output
391
+ console.log(`
392
+ ${c.bold}╔══════════════════════════════════════════════════════════════════════════════╗
393
+ ║ ║
394
+ ║ ${icons.target} ${c.cyan}VIBECHECK CHECKPOINT${c.reset}${c.bold} ║
395
+ ║ ║
396
+ ╚══════════════════════════════════════════════════════════════════════════════╝${c.reset}
397
+ `);
398
+
399
+ // Summary card
400
+ console.log(`${c.bold}${icons.chart} PROGRESS SUMMARY${c.reset}`);
401
+ console.log(`${'─'.repeat(60)}`);
402
+ console.log(` Baseline: ${baselineFindings.length} findings`);
403
+ console.log(` Current: ${currentFindings.length} findings`);
404
+ console.log(` Progress: ${c.bold}${checkpoint.summary.progressPercent}%${c.reset}`);
405
+ console.log();
406
+
407
+ // Stats
408
+ console.log(` ${c.green}${icons.check} Fixed:${c.reset} ${comparison.fixed.length}`);
409
+ console.log(` ${c.yellow}${icons.warning} Remaining:${c.reset} ${comparison.remaining.length}`);
410
+ console.log(` ${c.red}${icons.cross} Regressions:${c.reset} ${comparison.regressions.length}`);
411
+ console.log();
412
+
413
+ // Fixed issues
414
+ if (comparison.fixed.length > 0) {
415
+ console.log(`${c.bold}${c.green}${icons.check} FIXED ISSUES${c.reset} (${comparison.fixed.length})`);
416
+ console.log(`${'─'.repeat(60)}`);
417
+ for (const f of comparison.fixed.slice(0, 10)) {
418
+ console.log(` ${c.green}${icons.check}${c.reset} ${f.title || f.id || f.type}`);
419
+ if (f.file) console.log(` ${c.dim}${f.file}${f.line ? `:${f.line}` : ''}${c.reset}`);
420
+ }
421
+ if (comparison.fixed.length > 10) {
422
+ console.log(` ${c.dim}... and ${comparison.fixed.length - 10} more${c.reset}`);
423
+ }
424
+ console.log();
425
+ }
426
+
427
+ // Regressions (new issues)
428
+ if (comparison.regressions.length > 0) {
429
+ console.log(`${c.bold}${c.red}${icons.cross} REGRESSIONS${c.reset} (${comparison.regressions.length})`);
430
+ console.log(`${'─'.repeat(60)}`);
431
+ for (const f of comparison.regressions.slice(0, 10)) {
432
+ console.log(` ${c.red}${icons.cross}${c.reset} ${f.title || f.id || f.type}`);
433
+ if (f.file) console.log(` ${c.dim}${f.file}${f.line ? `:${f.line}` : ''}${c.reset}`);
434
+ }
435
+ if (comparison.regressions.length > 10) {
436
+ console.log(` ${c.dim}... and ${comparison.regressions.length - 10} more${c.reset}`);
437
+ }
438
+ console.log();
439
+ }
440
+
441
+ // Hallucination report (PRO)
442
+ if (hallucinations) {
443
+ console.log(`${c.bold}${icons.brain} HALLUCINATION ANALYSIS${c.reset} ${c.magenta}[PRO]${c.reset}`);
444
+ console.log(`${'─'.repeat(60)}`);
445
+ console.log(` Score: ${hallucinations.score <= 20 ? c.green : hallucinations.score <= 50 ? c.yellow : c.red}${hallucinations.score}/100${c.reset} ${c.dim}(lower is better)${c.reset}`);
446
+
447
+ if (hallucinations.nonexistentRoutes.length > 0) {
448
+ console.log(`\n ${c.red}Nonexistent routes referenced:${c.reset} ${hallucinations.nonexistentRoutes.length}`);
449
+ for (const h of hallucinations.nonexistentRoutes.slice(0, 5)) {
450
+ console.log(` ${c.red}${icons.cross}${c.reset} ${h.referencedRoute}`);
451
+ }
452
+ }
453
+
454
+ if (hallucinations.nonexistentEnvVars.length > 0) {
455
+ console.log(`\n ${c.red}Undeclared env vars used:${c.reset} ${hallucinations.nonexistentEnvVars.length}`);
456
+ for (const h of hallucinations.nonexistentEnvVars.slice(0, 5)) {
457
+ console.log(` ${c.red}${icons.cross}${c.reset} ${h.referencedVar}`);
458
+ }
459
+ }
460
+
461
+ if (hallucinations.contractDriftViolations.length > 0) {
462
+ console.log(`\n ${c.yellow}Contract drift violations:${c.reset} ${hallucinations.contractDriftViolations.length}`);
463
+ for (const h of hallucinations.contractDriftViolations.slice(0, 5)) {
464
+ console.log(` ${c.yellow}${icons.warning}${c.reset} ${h.route || h.type}`);
465
+ }
466
+ }
467
+
468
+ if (hallucinations.score === 0) {
469
+ console.log(` ${c.green}${icons.check} No hallucinations detected${c.reset}`);
470
+ }
471
+ console.log();
472
+ }
473
+
474
+ // Verdict
475
+ console.log(`${c.bold}${'━'.repeat(60)}${c.reset}`);
476
+ if (comparison.regressions.length === 0 && comparison.fixed.length > 0) {
477
+ console.log(`${c.green}${c.bold}${icons.rocket} GREAT PROGRESS!${c.reset} ${comparison.fixed.length} issues fixed, no regressions.`);
478
+ } else if (comparison.regressions.length === 0) {
479
+ console.log(`${c.green}${icons.check} No regressions detected.${c.reset}`);
480
+ } else {
481
+ console.log(`${c.red}${icons.warning} ${comparison.regressions.length} regression(s) detected. Review before shipping.${c.reset}`);
482
+ }
483
+ console.log();
484
+
485
+ // Next steps
486
+ console.log(`${c.bold}NEXT STEPS${c.reset}`);
487
+ if (comparison.regressions.length > 0) {
488
+ console.log(` 1. Fix regressions: ${c.cyan}vibecheck scan --autofix${c.reset}`);
489
+ console.log(` 2. Re-run checkpoint: ${c.cyan}vibecheck checkpoint${c.reset}`);
490
+ } else if (comparison.remaining.length > 0) {
491
+ console.log(` 1. Fix remaining issues: ${c.cyan}vibecheck scan --autofix${c.reset}`);
492
+ console.log(` 2. Verify with: ${c.cyan}vibecheck ship${c.reset}`);
493
+ } else {
494
+ console.log(` ${c.green}${icons.check} Ready to ship: ${c.cyan}vibecheck ship${c.reset}`);
495
+ }
496
+ console.log();
497
+
498
+ // Return code: 1 if regressions, 0 otherwise
499
+ return comparison.regressions.length > 0 ? 1 : 0;
500
+ }
501
+
502
+ module.exports = { runCheckpoint };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * vibecheck contracts - CI Gate for Contract Drift / Invariants
3
+ *
4
+ * Replaces: ctx guard
5
+ * Purpose: Validate code against contracts (SHIP/WARN/BLOCK)
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const { runCtxGuard } = require("./runCtxGuard");
11
+
12
+ const c = {
13
+ reset: '\x1b[0m',
14
+ bold: '\x1b[1m',
15
+ dim: '\x1b[2m',
16
+ green: '\x1b[32m',
17
+ yellow: '\x1b[33m',
18
+ cyan: '\x1b[36m',
19
+ red: '\x1b[31m',
20
+ };
21
+
22
+ function parseArgs(args) {
23
+ const opts = {
24
+ repoRoot: process.cwd(),
25
+ fastifyEntry: null,
26
+ json: false,
27
+ failOnWarn: false,
28
+ failOnMissing: false,
29
+ strict: false,
30
+ help: false
31
+ };
32
+
33
+ for (let i = 0; i < args.length; i++) {
34
+ const arg = args[i];
35
+ if (arg === "--json") opts.json = true;
36
+ else if (arg === "--strict") opts.strict = true;
37
+ else if (arg === "--fail-on-warn") opts.failOnWarn = true;
38
+ else if (arg === "--fail-on-missing") opts.failOnMissing = true;
39
+ else if (arg === "--fastify-entry") opts.fastifyEntry = args[++i];
40
+ else if (arg === "--path" || arg === "-p") opts.repoRoot = args[++i];
41
+ else if (arg === "--help" || arg === "-h") opts.help = true;
42
+ }
43
+
44
+ // --strict implies --fail-on-warn
45
+ if (opts.strict) opts.failOnWarn = true;
46
+
47
+ return opts;
48
+ }
49
+
50
+ function printHelp() {
51
+ console.log(`
52
+ ${c.cyan}${c.bold}🛡️ vibecheck contracts${c.reset} - CI Gate for Contract Drift
53
+
54
+ Validates current code against contracts. Use in CI to block drift.
55
+
56
+ ${c.bold}USAGE${c.reset}
57
+ vibecheck contracts ${c.dim}# Validate against contracts${c.reset}
58
+ vibecheck contracts --strict ${c.dim}# Fail on warnings${c.reset}
59
+ vibecheck contracts --json ${c.dim}# Output JSON for CI${c.reset}
60
+
61
+ ${c.bold}OPTIONS${c.reset}
62
+ --strict Fail on warnings (exit 1 on WARN)
63
+ --json Output JSON result to .vibecheck/contracts-result.json
64
+ --fail-on-warn Exit 1 on warnings (same as --strict)
65
+ --fail-on-missing Exit 2 if no contracts exist
66
+ --fastify-entry Fastify entry file (e.g. src/server.ts)
67
+ --path, -p Project path (default: current directory)
68
+ --help, -h Show this help
69
+
70
+ ${c.bold}VIOLATIONS (BLOCK)${c.reset}
71
+ • Route used in code but not in routes.json
72
+ • Webhook without signature verification
73
+ • Protected route accessible anonymously
74
+
75
+ ${c.bold}WARNINGS${c.reset}
76
+ • Env var used but not in env.json
77
+ • Required env var not used
78
+ • Undeclared external service
79
+
80
+ ${c.bold}EXIT CODES${c.reset}
81
+ 0 = SHIP (or WARN without --strict)
82
+ 1 = WARN (with --strict)
83
+ 2 = BLOCK
84
+
85
+ ${c.bold}EXAMPLES${c.reset}
86
+ vibecheck contracts ${c.dim}# Local validation${c.reset}
87
+ vibecheck contracts --strict --json ${c.dim}# CI mode${c.reset}
88
+
89
+ ${c.dim}Note: This command replaces 'vibecheck ctx guard'.${c.reset}
90
+ `);
91
+ }
92
+
93
+ async function runContracts(args) {
94
+ const opts = parseArgs(args);
95
+
96
+ if (opts.help) {
97
+ printHelp();
98
+ return 0;
99
+ }
100
+
101
+ // Delegate to runCtxGuard
102
+ return await runCtxGuard(opts);
103
+ }
104
+
105
+ module.exports = { runContracts };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * vibecheck export - Lightweight Collaboration Outputs
3
+ *
4
+ * Subcommands:
5
+ * export pr = current pr
6
+ * export badge = current badge
7
+ * export bundle = current share
8
+ *
9
+ * Replaces: pr, badge, share
10
+ */
11
+
12
+ "use strict";
13
+
14
+ const c = {
15
+ reset: '\x1b[0m',
16
+ bold: '\x1b[1m',
17
+ dim: '\x1b[2m',
18
+ cyan: '\x1b[36m',
19
+ yellow: '\x1b[33m',
20
+ red: '\x1b[31m',
21
+ };
22
+
23
+ function printHelp() {
24
+ console.log(`
25
+ ${c.cyan}${c.bold}📤 vibecheck export${c.reset} - Generate Collaboration Outputs
26
+
27
+ Lightweight outputs from latest results for sharing and collaboration.
28
+
29
+ ${c.bold}SUBCOMMANDS${c.reset}
30
+ ${c.cyan}pr${c.reset} ${c.dim}Generate GitHub PR comment (replaces 'pr')${c.reset}
31
+ ${c.cyan}badge${c.reset} ${c.dim}Generate ship badge for README (replaces 'badge')${c.reset}
32
+ ${c.cyan}bundle${c.reset} ${c.dim}Generate share pack for PR/docs (replaces 'share')${c.reset}
33
+
34
+ ${c.bold}EXAMPLES${c.reset}
35
+ vibecheck export pr
36
+ vibecheck export badge --style gradient
37
+ vibecheck export bundle
38
+
39
+ ${c.dim}Note: Old commands still work as aliases:
40
+ vibecheck pr → vibecheck export pr
41
+ vibecheck badge → vibecheck export badge
42
+ vibecheck share → vibecheck export bundle${c.reset}
43
+ `);
44
+ }
45
+
46
+ async function runExport(args) {
47
+ if (!args || args.length === 0 || args[0] === "--help" || args[0] === "-h") {
48
+ printHelp();
49
+ return 0;
50
+ }
51
+
52
+ const subcommand = args[0];
53
+ const subArgs = args.slice(1);
54
+
55
+ switch (subcommand) {
56
+ case "pr":
57
+ // Delegate to runPR
58
+ try {
59
+ const { runPR } = require("./runPR");
60
+ return await runPR(subArgs);
61
+ } catch (e) {
62
+ console.error(`${c.red}Error:${c.reset} Export PR unavailable: ${e.message}`);
63
+ return 1;
64
+ }
65
+
66
+ case "badge":
67
+ // Delegate to runBadge
68
+ try {
69
+ const { runBadge } = require("./runBadge");
70
+ return await runBadge(subArgs);
71
+ } catch (e) {
72
+ console.error(`${c.red}Error:${c.reset} Export badge unavailable: ${e.message}`);
73
+ return 1;
74
+ }
75
+
76
+ case "bundle":
77
+ // Delegate to runShare
78
+ try {
79
+ const { runShare } = require("./runShare");
80
+ return await runShare(subArgs);
81
+ } catch (e) {
82
+ console.error(`${c.red}Error:${c.reset} Export bundle unavailable: ${e.message}`);
83
+ return 1;
84
+ }
85
+
86
+ default:
87
+ console.error(`${c.red}Unknown subcommand:${c.reset} ${subcommand}`);
88
+ console.log(`\n${c.dim}Run 'vibecheck export --help' for available subcommands.${c.reset}\n`);
89
+ return 1;
90
+ }
91
+ }
92
+
93
+ module.exports = { runExport };