@vibecheckai/cli 3.0.4 → 3.0.5

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 (58) hide show
  1. package/bin/dev/run-v2-torture.js +30 -0
  2. package/bin/guardrail.js +9 -0
  3. package/bin/runners/lib/analyzers.js +38 -0
  4. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  5. package/bin/runners/lib/contracts/auth-contract.js +8 -0
  6. package/bin/runners/lib/contracts/env-contract.js +3 -0
  7. package/bin/runners/lib/contracts/external-contract.js +10 -2
  8. package/bin/runners/lib/contracts/route-contract.js +7 -0
  9. package/bin/runners/lib/contracts.js +804 -0
  10. package/bin/runners/lib/detectors-v2.js +703 -0
  11. package/bin/runners/lib/drift.js +425 -0
  12. package/bin/runners/lib/entitlements.js +8 -3
  13. package/bin/runners/lib/env-resolver.js +417 -0
  14. package/bin/runners/lib/extractors/client-calls.js +990 -0
  15. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  16. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  17. package/bin/runners/lib/extractors/index.js +363 -0
  18. package/bin/runners/lib/extractors/next-routes.js +524 -0
  19. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  20. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  21. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  22. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  23. package/bin/runners/lib/findings-schema.js +281 -0
  24. package/bin/runners/lib/html-report.js +650 -0
  25. package/bin/runners/lib/missions/templates.js +45 -0
  26. package/bin/runners/lib/policy.js +295 -0
  27. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  28. package/bin/runners/lib/reality/index.js +318 -0
  29. package/bin/runners/lib/reality/request-hashing.js +416 -0
  30. package/bin/runners/lib/reality/request-mapper.js +453 -0
  31. package/bin/runners/lib/reality/safety-rails.js +463 -0
  32. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  33. package/bin/runners/lib/reality/toast-detector.js +393 -0
  34. package/bin/runners/lib/route-truth.js +10 -10
  35. package/bin/runners/lib/schema-validator.js +350 -0
  36. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  37. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  38. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  39. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  40. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  41. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  42. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  43. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  44. package/bin/runners/lib/schemas/validator.js +438 -0
  45. package/bin/runners/lib/verdict-engine.js +628 -0
  46. package/bin/runners/runAIAgent.js +228 -1
  47. package/bin/runners/runBadge.js +181 -1
  48. package/bin/runners/runCtxDiff.js +301 -0
  49. package/bin/runners/runInitGha.js +78 -15
  50. package/bin/runners/runLaunch.js +180 -1
  51. package/bin/runners/runProve.js +23 -0
  52. package/bin/runners/runReplay.js +114 -84
  53. package/bin/runners/runScan.js +111 -32
  54. package/bin/runners/runShip.js +23 -2
  55. package/bin/runners/runTruthpack.js +9 -7
  56. package/bin/runners/runValidate.js +161 -1
  57. package/bin/vibecheck.js +6 -1
  58. package/package.json +9 -8
@@ -478,8 +478,65 @@ async function runScan(args) {
478
478
  }
479
479
 
480
480
  try {
481
- // Import systems
482
- const { scanRouteIntegrity } = require('../../dist/lib/route-integrity');
481
+ // Import systems - try TypeScript compiled first, fallback to JS runtime
482
+ let scanRouteIntegrity;
483
+ let useFallbackScanner = false;
484
+
485
+ try {
486
+ scanRouteIntegrity = require('../../dist/lib/route-integrity').scanRouteIntegrity;
487
+ } catch (e) {
488
+ // Fallback to JS-based scanner using truth.js and analyzers.js
489
+ useFallbackScanner = true;
490
+ const { buildTruthpack } = require('./lib/truth');
491
+ const { findMissingRoutes, findEnvGaps, findFakeSuccess, findGhostAuth } = require('./lib/analyzers');
492
+
493
+ scanRouteIntegrity = async function({ projectPath, layers, baseUrl, verbose }) {
494
+ // Build truthpack for route analysis
495
+ const truthpack = await buildTruthpack({ repoRoot: projectPath });
496
+
497
+ // Run analyzers
498
+ const findings = [];
499
+ findings.push(...findMissingRoutes(truthpack));
500
+ findings.push(...findEnvGaps(truthpack));
501
+ findings.push(...findFakeSuccess(projectPath));
502
+ findings.push(...findGhostAuth(truthpack, projectPath));
503
+
504
+ // Convert to scan format matching TypeScript scanner output
505
+ const shipBlockers = findings.map((f, i) => ({
506
+ id: f.id || `finding-${i}`,
507
+ ruleId: f.category,
508
+ category: f.category,
509
+ severity: f.severity === 'BLOCK' ? 'critical' : f.severity === 'WARN' ? 'warning' : 'info',
510
+ title: f.title,
511
+ message: f.title,
512
+ description: f.why,
513
+ file: f.evidence?.[0]?.file || '',
514
+ line: parseInt(f.evidence?.[0]?.lines?.split('-')[0]) || 1,
515
+ evidence: f.evidence || [],
516
+ fixHints: f.fixHints || [],
517
+ autofixAvailable: false,
518
+ verdict: f.severity === 'BLOCK' ? 'FAIL' : 'WARN',
519
+ }));
520
+
521
+ // Return structure matching TypeScript scanner
522
+ return {
523
+ report: {
524
+ shipBlockers,
525
+ realitySniffFindings: [],
526
+ routeMap: truthpack.routes,
527
+ summary: {
528
+ total: shipBlockers.length,
529
+ critical: shipBlockers.filter(f => f.severity === 'critical').length,
530
+ warning: shipBlockers.filter(f => f.severity === 'warning').length,
531
+ },
532
+ verdict: shipBlockers.some(f => f.severity === 'critical') ? 'BLOCK' :
533
+ shipBlockers.some(f => f.severity === 'warning') ? 'WARN' : 'SHIP'
534
+ },
535
+ outputPaths: {},
536
+ truthpack
537
+ };
538
+ };
539
+ }
483
540
 
484
541
  // Try to import new unified output system (may not be compiled yet)
485
542
  let buildVerdictOutput, normalizeFinding, formatStandardOutput, formatScanOutput, getExitCode, CacheManager;
@@ -648,35 +705,6 @@ async function runScan(args) {
648
705
 
649
706
  const { report, outputPaths } = result;
650
707
 
651
- // Normalize findings with stable IDs
652
- const existingIDs = new Set();
653
- const normalizedFindings = [];
654
-
655
- // Normalize route integrity findings
656
- if (report.shipBlockers) {
657
- for (let i = 0; i < report.shipBlockers.length; i++) {
658
- const blocker = report.shipBlockers[i];
659
- const category = blocker.category || 'ROUTE';
660
- const normalized = normalizeFinding(blocker, category, i, existingIDs);
661
- normalizedFindings.push(normalized);
662
- }
663
- }
664
-
665
- // Normalize Reality Sniff findings if present
666
- if (report.realitySniffFindings) {
667
- for (let i = 0; i < report.realitySniffFindings.length; i++) {
668
- const finding = report.realitySniffFindings[i];
669
- const category = finding.ruleId?.startsWith('auth') ? 'AUTH' : 'REALITY';
670
- const normalized = normalizeFinding(finding, category, normalizedFindings.length, existingIDs);
671
- normalizedFindings.push(normalized);
672
- }
673
- }
674
-
675
- // Add detection engine findings (Dead UI, Billing, Fake Success)
676
- for (const finding of detectionFindings) {
677
- normalizedFindings.push(finding);
678
- }
679
-
680
708
  // Use new unified output if available, otherwise fallback to old format
681
709
  if (useUnifiedOutput && buildVerdictOutput && normalizeFinding) {
682
710
  // Normalize findings with stable IDs
@@ -778,7 +806,58 @@ async function runScan(args) {
778
806
  });
779
807
 
780
808
  return getExitCode(verdict);
781
- } // End of if (useUnifiedOutput)
809
+ } else {
810
+ // Legacy fallback output when unified output system isn't available
811
+ const findings = [...(report.shipBlockers || []), ...detectionFindings];
812
+ const criticalCount = findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length;
813
+ const warningCount = findings.filter(f => f.severity === 'warning' || f.severity === 'WARN').length;
814
+
815
+ const verdict = criticalCount > 0 ? 'BLOCK' : warningCount > 0 ? 'WARN' : 'SHIP';
816
+
817
+ // Print simple output
818
+ console.log();
819
+ console.log(` ${c.bold}═══════════════════════════════════════════════════════════════════${c.reset}`);
820
+
821
+ if (verdict === 'SHIP') {
822
+ console.log(` ${c.bgGreen}${c.white}${c.bold} ✓ SHIP ${c.reset} ${c.green}Ready to ship!${c.reset}`);
823
+ } else if (verdict === 'WARN') {
824
+ console.log(` ${c.bgYellow}${c.black}${c.bold} ⚠ WARN ${c.reset} ${c.yellow}Review recommended before shipping${c.reset}`);
825
+ } else {
826
+ console.log(` ${c.bgRed}${c.white}${c.bold} ✗ BLOCK ${c.reset} ${c.red}Issues must be fixed before shipping${c.reset}`);
827
+ }
828
+
829
+ console.log(` ${c.bold}═══════════════════════════════════════════════════════════════════${c.reset}`);
830
+ console.log();
831
+
832
+ if (findings.length > 0) {
833
+ console.log(` ${c.bold}Findings (${findings.length})${c.reset}`);
834
+ console.log();
835
+
836
+ for (const finding of findings.slice(0, 10)) {
837
+ const severityIcon = finding.severity === 'critical' || finding.severity === 'BLOCK'
838
+ ? `${c.red}✗${c.reset}`
839
+ : `${c.yellow}⚠${c.reset}`;
840
+ console.log(` ${severityIcon} ${finding.title || finding.message}`);
841
+ if (finding.file) {
842
+ console.log(` ${c.dim}${finding.file}${finding.line ? `:${finding.line}` : ''}${c.reset}`);
843
+ }
844
+ }
845
+
846
+ if (findings.length > 10) {
847
+ console.log(` ${c.dim}... and ${findings.length - 10} more findings${c.reset}`);
848
+ }
849
+ console.log();
850
+ }
851
+
852
+ // Emit audit event
853
+ emitScanComplete(projectPath, verdict === 'SHIP' ? 'success' : 'failure', {
854
+ score: verdict === 'SHIP' ? 100 : verdict === 'WARN' ? 70 : 40,
855
+ issueCount: criticalCount + warningCount,
856
+ durationMs: timings.total,
857
+ });
858
+
859
+ return verdict === 'SHIP' ? 0 : verdict === 'WARN' ? 1 : 2;
860
+ }
782
861
 
783
862
  } catch (error) {
784
863
  stopSpinner(`Scan failed: ${error.message}`, false);
@@ -22,6 +22,8 @@ const {
22
22
  findOwnerModeBypass
23
23
  } = require("./lib/analyzers");
24
24
  const { findingsFromReality } = require("./lib/reality-findings");
25
+ // Contract Drift Detection - per spec: "routes/env/auth drift → usually BLOCK"
26
+ const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = require("./lib/drift");
25
27
 
26
28
  // Build proof graph from findings for evidence-backed verdicts
27
29
  function buildProofGraph(findings, truthpack, root) {
@@ -103,7 +105,8 @@ function getClaimType(category) {
103
105
  'StripeWebhook': 'billing_enforced',
104
106
  'PaidSurface': 'billing_enforced',
105
107
  'OwnerModeBypass': 'billing_enforced',
106
- 'DeadUI': 'ui_wired'
108
+ 'DeadUI': 'ui_wired',
109
+ 'ContractDrift': 'contract_satisfied'
107
110
  };
108
111
  return map[category] || 'ui_wired';
109
112
  }
@@ -117,7 +120,8 @@ function getGapType(category) {
117
120
  'StripeWebhook': 'missing_verification',
118
121
  'PaidSurface': 'missing_gate',
119
122
  'OwnerModeBypass': 'missing_gate',
120
- 'DeadUI': 'missing_handler'
123
+ 'DeadUI': 'missing_handler',
124
+ 'ContractDrift': 'contract_drift'
121
125
  };
122
126
  return map[category] || 'untested_path';
123
127
  }
@@ -490,6 +494,23 @@ ${c.bold}EXAMPLES${c.reset}
490
494
  ...findingsFromReality(projectPath)
491
495
  ];
492
496
 
497
+ // Contract Drift Detection - per spec section 8.1:
498
+ // "drift can be WARN or BLOCK depending on category:
499
+ // routes/env/auth drift → usually BLOCK (because AI will lie here)"
500
+ if (hasContracts(projectPath)) {
501
+ const contracts = loadContracts(projectPath);
502
+ const driftFindings = findContractDrift(contracts, truthpack);
503
+ allFindings.push(...driftFindings);
504
+
505
+ const driftSummary = getDriftSummary(driftFindings);
506
+ if (driftSummary.hasDrift) {
507
+ console.log(` ${driftSummary.blocks > 0 ? c.red + '🛑' : c.yellow + '⚠️'} Contract drift detected: ${driftSummary.blocks} blocks, ${driftSummary.warns} warnings${c.reset}`);
508
+ if (driftSummary.blocks > 0) {
509
+ console.log(` ${c.dim}Run 'vibecheck ctx sync' to update contracts${c.reset}`);
510
+ }
511
+ }
512
+ }
513
+
493
514
  results.routeTruth = {
494
515
  serverRoutes: truthpack.routes.server.length,
495
516
  clientRefs: truthpack.routes.clientRefs.length,
@@ -11,11 +11,13 @@
11
11
  * vibecheck ctx --md - Also generate truthpack.md
12
12
  */
13
13
 
14
- import fs from 'fs/promises';
15
- import path from 'path';
16
- import crypto from 'crypto';
17
- import { execSync } from 'child_process';
18
- import { RouteIndex, resolveNextRoutes, resolveFastifyRoutes } from './lib/route-truth.js';
14
+ "use strict";
15
+
16
+ const fs = require('fs/promises');
17
+ const path = require('path');
18
+ const crypto = require('crypto');
19
+ const { execSync } = require('child_process');
20
+ const { RouteIndex, resolveNextRoutes, resolveFastifyRoutes } = require('./lib/route-truth.js');
19
21
 
20
22
  const VERSION = '1.0.0';
21
23
 
@@ -25,7 +27,7 @@ let evidenceCounter = 0;
25
27
  /**
26
28
  * Main entry point
27
29
  */
28
- export async function runTruthpack(projectPath = process.cwd(), options = {}) {
30
+ async function runTruthpack(projectPath = process.cwd(), options = {}) {
29
31
  const startTime = Date.now();
30
32
  console.log('📦 Generating Truth Pack...\n');
31
33
 
@@ -631,4 +633,4 @@ function generateMarkdown(truthpack) {
631
633
  return lines.join('\n');
632
634
  }
633
635
 
634
- export default runTruthpack;
636
+ module.exports = { runTruthpack };
@@ -1,2 +1,162 @@
1
- async function runValidate(args) { console.log("Validate command not yet implemented"); return 0; }
1
+ /**
2
+ * vibecheck validate - Validate AI-generated code for hallucinations
3
+ */
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { buildTruthpack } = require("./lib/truth");
8
+ const { routeMatches } = require("./lib/claims");
9
+
10
+ const c = {
11
+ reset: "\x1b[0m",
12
+ bold: "\x1b[1m",
13
+ dim: "\x1b[2m",
14
+ green: "\x1b[32m",
15
+ yellow: "\x1b[33m",
16
+ red: "\x1b[31m",
17
+ cyan: "\x1b[36m",
18
+ };
19
+
20
+ function parseArgs(args) {
21
+ const opts = {
22
+ help: false,
23
+ file: null,
24
+ json: false,
25
+ verbose: false,
26
+ };
27
+
28
+ for (let i = 0; i < args.length; i++) {
29
+ const arg = args[i];
30
+ switch (arg) {
31
+ case "--help":
32
+ case "-h":
33
+ opts.help = true;
34
+ break;
35
+ case "--json":
36
+ opts.json = true;
37
+ break;
38
+ case "--verbose":
39
+ case "-v":
40
+ opts.verbose = true;
41
+ break;
42
+ default:
43
+ if (!arg.startsWith("-")) {
44
+ opts.file = arg;
45
+ }
46
+ }
47
+ }
48
+
49
+ return opts;
50
+ }
51
+
52
+ function printHelp() {
53
+ console.log(`
54
+ ${c.bold}vibecheck validate${c.reset} - Validate AI-generated code for hallucinations
55
+
56
+ ${c.bold}USAGE${c.reset}
57
+ vibecheck validate [file] [options]
58
+
59
+ ${c.bold}OPTIONS${c.reset}
60
+ --help, -h Show this help
61
+ --json Output as JSON
62
+ --verbose, -v Show detailed output
63
+
64
+ ${c.bold}WHAT IT CHECKS${c.reset}
65
+ - API routes referenced but not defined
66
+ - Env vars used but not declared
67
+ - Imports that don't exist
68
+ - Function calls to undefined functions
69
+
70
+ ${c.bold}EXAMPLES${c.reset}
71
+ vibecheck validate # Validate all files
72
+ vibecheck validate src/api/route.ts # Validate specific file
73
+ vibecheck validate --json # JSON output
74
+ `);
75
+ }
76
+
77
+ async function runValidate(args) {
78
+ const opts = parseArgs(args);
79
+
80
+ if (opts.help) {
81
+ printHelp();
82
+ return 0;
83
+ }
84
+
85
+ const projectPath = process.cwd();
86
+
87
+ console.log(`\n${c.bold}🔍 Validating code for hallucinations...${c.reset}\n`);
88
+
89
+ try {
90
+ // Build truthpack for validation
91
+ const truthpack = await buildTruthpack({ repoRoot: projectPath });
92
+
93
+ const issues = [];
94
+
95
+ // Check for missing routes
96
+ const clientRefs = truthpack.routes?.clientRefs || [];
97
+ const serverRoutes = truthpack.routes?.server || [];
98
+
99
+ for (const ref of clientRefs) {
100
+ const method = ref.method || "*";
101
+ const p = ref.path;
102
+ const exists = serverRoutes.some(r => routeMatches(r, method, p) || routeMatches(r, "*", p));
103
+
104
+ if (!exists) {
105
+ issues.push({
106
+ type: "missing_route",
107
+ severity: "error",
108
+ message: `Route ${method} ${p} is referenced but not defined`,
109
+ file: ref.evidence?.[0]?.file || "unknown",
110
+ line: ref.evidence?.[0]?.lines?.split("-")[0] || 1,
111
+ });
112
+ }
113
+ }
114
+
115
+ // Check for undeclared env vars (excluding system vars)
116
+ const envVars = truthpack.env?.vars || [];
117
+ const declared = new Set(truthpack.env?.declared || []);
118
+ const systemVars = new Set([
119
+ 'HOME', 'USER', 'PATH', 'NODE_ENV', 'CI', 'DEBUG', 'PORT',
120
+ 'APPDATA', 'USERPROFILE', 'COMPUTERNAME', 'HOSTNAME',
121
+ ]);
122
+
123
+ for (const v of envVars) {
124
+ if (!declared.has(v.name) && !systemVars.has(v.name) && v.name.startsWith("VIBECHECK_")) {
125
+ issues.push({
126
+ type: "undeclared_env",
127
+ severity: "warning",
128
+ message: `Env var ${v.name} is used but not declared in .env.example`,
129
+ file: v.references?.[0]?.file || "unknown",
130
+ line: v.references?.[0]?.lines?.split("-")[0] || 1,
131
+ });
132
+ }
133
+ }
134
+
135
+ // Output results
136
+ if (opts.json) {
137
+ console.log(JSON.stringify({ issues, valid: issues.length === 0 }, null, 2));
138
+ } else {
139
+ if (issues.length === 0) {
140
+ console.log(`${c.green}✓${c.reset} No hallucinations detected!\n`);
141
+ console.log(` ${c.dim}All routes and env vars are properly defined.${c.reset}\n`);
142
+ } else {
143
+ console.log(`${c.yellow}⚠${c.reset} Found ${issues.length} potential issues:\n`);
144
+
145
+ for (const issue of issues) {
146
+ const icon = issue.severity === "error" ? `${c.red}✗${c.reset}` : `${c.yellow}⚠${c.reset}`;
147
+ console.log(` ${icon} ${issue.message}`);
148
+ console.log(` ${c.dim}${issue.file}:${issue.line}${c.reset}`);
149
+ }
150
+ console.log();
151
+ }
152
+ }
153
+
154
+ return issues.some(i => i.severity === "error") ? 1 : 0;
155
+
156
+ } catch (error) {
157
+ console.error(`${c.red}✗${c.reset} Validation failed: ${error.message}`);
158
+ return 1;
159
+ }
160
+ }
161
+
2
162
  module.exports = { runValidate };
package/bin/vibecheck.js CHANGED
@@ -298,6 +298,11 @@ ${c.dim}Run ${c.cyan}vibecheck login${c.dim} anytime to upgrade.${c.reset}
298
298
  }
299
299
 
300
300
  async function checkCommandAccess(cmd, entitlements, args = []) {
301
+ // Development mode - bypass all tier checks
302
+ if (process.env.VIBECHECK_SKIP_AUTH) {
303
+ return { allowed: true, tier: "dev" };
304
+ }
305
+
301
306
  // Free commands always work (no API key needed)
302
307
  if (FREE_COMMANDS.includes(cmd)) {
303
308
  return { allowed: true, tier: "free" };
@@ -730,7 +735,7 @@ ${c.dim}Run 'vibecheck <command> --help' for details.${c.reset}
730
735
  exitCode = await runValidate(args);
731
736
  break;
732
737
  case "doctor":
733
- exitCode = runDoctor(args);
738
+ exitCode = await runDoctor(args);
734
739
  break;
735
740
  case "init":
736
741
  // Enterprise init handles all flags including --gha, --gitlab, --compliance, etc.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecheckai/cli",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "Vibecheck CLI - Ship with confidence. One verdict: SHIP | WARN | BLOCK.",
5
5
  "main": "bin/vibecheck.js",
6
6
  "bin": {
@@ -12,6 +12,13 @@
12
12
  "README.md",
13
13
  "LICENSE"
14
14
  ],
15
+ "scripts": {
16
+ "build": "node scripts/copy-runners.js",
17
+ "build:legacy": "node scripts/build.js",
18
+ "dev": "node ../../bin/vibecheck.js",
19
+ "start": "node bin/vibecheck.js",
20
+ "prepublishOnly": "npm run build"
21
+ },
15
22
  "dependencies": {
16
23
  "@babel/parser": "^7.23.0",
17
24
  "@babel/traverse": "^7.23.0",
@@ -67,11 +74,5 @@
67
74
  },
68
75
  "publishConfig": {
69
76
  "access": "public"
70
- },
71
- "scripts": {
72
- "build": "node scripts/copy-runners.js",
73
- "build:legacy": "node scripts/build.js",
74
- "dev": "node ../../bin/vibecheck.js",
75
- "start": "node bin/vibecheck.js"
76
77
  }
77
- }
78
+ }