@vibecheckai/cli 3.0.4 → 3.0.7

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 (108) hide show
  1. package/bin/dev/run-v2-torture.js +30 -0
  2. package/bin/runners/context/index.js +1 -1
  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-v2.js +3 -1
  13. package/bin/runners/lib/entitlements.js +11 -3
  14. package/bin/runners/lib/env-resolver.js +417 -0
  15. package/bin/runners/lib/extractors/client-calls.js +990 -0
  16. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  17. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  18. package/bin/runners/lib/extractors/index.js +363 -0
  19. package/bin/runners/lib/extractors/next-routes.js +524 -0
  20. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  21. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  22. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  23. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  24. package/bin/runners/lib/findings-schema.js +281 -0
  25. package/bin/runners/lib/html-report.js +650 -0
  26. package/bin/runners/lib/missions/templates.js +45 -0
  27. package/bin/runners/lib/policy.js +295 -0
  28. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  29. package/bin/runners/lib/reality/index.js +318 -0
  30. package/bin/runners/lib/reality/request-hashing.js +416 -0
  31. package/bin/runners/lib/reality/request-mapper.js +453 -0
  32. package/bin/runners/lib/reality/safety-rails.js +463 -0
  33. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  34. package/bin/runners/lib/reality/toast-detector.js +393 -0
  35. package/bin/runners/lib/report-html.js +5 -0
  36. package/bin/runners/lib/report-templates.js +5 -0
  37. package/bin/runners/lib/report.js +135 -0
  38. package/bin/runners/lib/route-truth.js +10 -10
  39. package/bin/runners/lib/schema-validator.js +350 -0
  40. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  41. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  42. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  43. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  44. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  45. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  46. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  47. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  48. package/bin/runners/lib/schemas/validator.js +438 -0
  49. package/bin/runners/lib/ui.js +562 -0
  50. package/bin/runners/lib/verdict-engine.js +628 -0
  51. package/bin/runners/runAIAgent.js +228 -1
  52. package/bin/runners/runBadge.js +181 -1
  53. package/bin/runners/runCtx.js +7 -2
  54. package/bin/runners/runCtxDiff.js +301 -0
  55. package/bin/runners/runGuard.js +168 -0
  56. package/bin/runners/runInitGha.js +78 -15
  57. package/bin/runners/runLabs.js +341 -0
  58. package/bin/runners/runLaunch.js +180 -1
  59. package/bin/runners/runMdc.js +203 -1
  60. package/bin/runners/runProof.zip +0 -0
  61. package/bin/runners/runProve.js +23 -0
  62. package/bin/runners/runReplay.js +114 -84
  63. package/bin/runners/runScan.js +111 -32
  64. package/bin/runners/runShip.js +23 -2
  65. package/bin/runners/runTruthpack.js +9 -7
  66. package/bin/runners/runValidate.js +161 -1
  67. package/bin/vibecheck.js +416 -770
  68. package/mcp-server/.guardrail/audit/audit.log.jsonl +2 -0
  69. package/mcp-server/.specs/architecture.mdc +90 -0
  70. package/mcp-server/.specs/security.mdc +30 -0
  71. package/mcp-server/README.md +252 -0
  72. package/mcp-server/agent-checkpoint.js +364 -0
  73. package/mcp-server/architect-tools.js +707 -0
  74. package/mcp-server/audit-mcp.js +206 -0
  75. package/mcp-server/codebase-architect-tools.js +838 -0
  76. package/mcp-server/consolidated-tools.js +804 -0
  77. package/mcp-server/hygiene-tools.js +428 -0
  78. package/mcp-server/index-v1.js +698 -0
  79. package/mcp-server/index.js +2092 -0
  80. package/mcp-server/index.old.js +4137 -0
  81. package/mcp-server/intelligence-tools.js +664 -0
  82. package/mcp-server/intent-drift-tools.js +873 -0
  83. package/mcp-server/mdc-generator.js +298 -0
  84. package/mcp-server/package-lock.json +165 -0
  85. package/mcp-server/package.json +47 -0
  86. package/mcp-server/premium-tools.js +1275 -0
  87. package/mcp-server/test-mcp.js +108 -0
  88. package/mcp-server/test-tools.js +36 -0
  89. package/mcp-server/tier-auth.js +147 -0
  90. package/mcp-server/tools/index.js +72 -0
  91. package/mcp-server/tools-reorganized.ts +244 -0
  92. package/mcp-server/truth-context.js +581 -0
  93. package/mcp-server/truth-firewall-tools.js +1500 -0
  94. package/mcp-server/vibecheck-2.0-tools.js +748 -0
  95. package/mcp-server/vibecheck-tools.js +1075 -0
  96. package/package.json +10 -8
  97. package/bin/guardrail.js +0 -834
  98. package/bin/runners/runAudit.js +0 -2
  99. package/bin/runners/runAutopilot.js +0 -2
  100. package/bin/runners/runCertify.js +0 -2
  101. package/bin/runners/runDashboard.js +0 -10
  102. package/bin/runners/runEnhancedShip.js +0 -2
  103. package/bin/runners/runFixPacks.js +0 -2
  104. package/bin/runners/runNaturalLanguage.js +0 -3
  105. package/bin/runners/runProof.js +0 -2
  106. package/bin/runners/runRealitySniff.js +0 -2
  107. package/bin/runners/runUpgrade.js +0 -2
  108. package/bin/runners/runVerifyAgentOutput.js +0 -2
@@ -1,2 +1,204 @@
1
- async function runMdc(args) { console.log("MDC generator not yet implemented"); return 0; }
1
+ /**
2
+ * vibecheck mdc - Generate MDC (Markdown Context) documentation
3
+ *
4
+ * Generates .mdc specification files for AI coding agents.
5
+ * These files help AI understand your codebase architecture.
6
+ */
7
+
8
+ const path = require("path");
9
+ const fs = require("fs");
10
+
11
+ // ANSI colors
12
+ const c = {
13
+ reset: "\x1b[0m",
14
+ dim: "\x1b[2m",
15
+ bold: "\x1b[1m",
16
+ cyan: "\x1b[36m",
17
+ green: "\x1b[32m",
18
+ yellow: "\x1b[33m",
19
+ red: "\x1b[31m",
20
+ };
21
+
22
+ function printHelp() {
23
+ console.log(`
24
+ ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
25
+ ${c.bold}vibecheck mdc${c.reset} - Generate MDC Specifications
26
+ ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
27
+
28
+ ${c.green}USAGE${c.reset}
29
+ vibecheck mdc [options]
30
+
31
+ ${c.yellow}OPTIONS${c.reset}
32
+ --output, -o <dir> Output directory (default: .specs)
33
+ --categories <list> Categories: architecture,security,data-flow,design-system
34
+ --depth <level> Analysis depth: shallow, medium, deep (default: medium)
35
+ --min-score <n> Minimum importance score 0-100 (default: 70)
36
+ --no-examples Skip code examples
37
+ --json Output JSON instead of MDC files
38
+
39
+ ${c.dim}EXAMPLE${c.reset}
40
+ vibecheck mdc --output .cursor/rules --categories architecture,security
41
+
42
+ ${c.dim}MDC files help AI coding agents understand your codebase structure.${c.reset}
43
+ `);
44
+ }
45
+
46
+ async function runMdc(args = []) {
47
+ if (args.includes("--help") || args.includes("-h")) {
48
+ printHelp();
49
+ return 0;
50
+ }
51
+
52
+ // Parse arguments
53
+ const outputIdx = args.findIndex(a => a === "--output" || a === "-o");
54
+ const outputDir = outputIdx !== -1 ? args[outputIdx + 1] : ".specs";
55
+
56
+ const catIdx = args.findIndex(a => a === "--categories");
57
+ const categories = catIdx !== -1 ? args[catIdx + 1] : null;
58
+
59
+ const depthIdx = args.findIndex(a => a === "--depth");
60
+ const depth = depthIdx !== -1 ? args[depthIdx + 1] : "medium";
61
+
62
+ const minScoreIdx = args.findIndex(a => a === "--min-score");
63
+ const minScore = minScoreIdx !== -1 ? parseInt(args[minScoreIdx + 1]) : 70;
64
+
65
+ const noExamples = args.includes("--no-examples");
66
+ const jsonOutput = args.includes("--json");
67
+
68
+ console.log(`
69
+ ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
70
+ ${c.bold}📄 VIBECHECK MDC${c.reset} - Generating Specifications
71
+ ${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}
72
+ `);
73
+
74
+ const repoRoot = process.cwd();
75
+
76
+ // Try to use the context module for MDC generation
77
+ try {
78
+ const { runContext } = require("./runContext");
79
+
80
+ // Build args for context command which can generate rules
81
+ const contextArgs = ["--output", outputDir];
82
+ if (categories) {
83
+ contextArgs.push("--categories", categories);
84
+ }
85
+
86
+ console.log(`${c.dim}▸ Analyzing codebase...${c.reset}`);
87
+
88
+ // Generate context which includes MDC-compatible output
89
+ const exitCode = await runContext(contextArgs);
90
+
91
+ if (exitCode === 0) {
92
+ console.log(`
93
+ ${c.green}✓${c.reset} MDC specifications generated in ${c.cyan}${outputDir}/${c.reset}
94
+
95
+ ${c.dim}Generated files can be used with:${c.reset}
96
+ • Cursor (.cursor/rules/)
97
+ • Windsurf (.windsurf/rules/)
98
+ • GitHub Copilot (.github/copilot-instructions.md)
99
+ • Claude (.claude/project-context.md)
100
+ `);
101
+ }
102
+
103
+ return exitCode;
104
+ } catch (e) {
105
+ // Fallback: generate basic MDC from truthpack
106
+ console.log(`${c.dim}▸ Using basic MDC generation...${c.reset}`);
107
+
108
+ try {
109
+ const truthpackPath = path.join(repoRoot, ".vibecheck", "truth", "truthpack.json");
110
+
111
+ if (!fs.existsSync(truthpackPath)) {
112
+ console.log(`${c.yellow}⚠${c.reset} No truthpack found. Run ${c.cyan}vibecheck ctx${c.reset} first.`);
113
+ return 1;
114
+ }
115
+
116
+ const truthpack = JSON.parse(fs.readFileSync(truthpackPath, "utf8"));
117
+
118
+ // Generate basic MDC content
119
+ const mdcContent = generateBasicMDC(truthpack, repoRoot);
120
+
121
+ // Ensure output directory exists
122
+ const fullOutputDir = path.join(repoRoot, outputDir);
123
+ if (!fs.existsSync(fullOutputDir)) {
124
+ fs.mkdirSync(fullOutputDir, { recursive: true });
125
+ }
126
+
127
+ // Write MDC file
128
+ const mdcPath = path.join(fullOutputDir, "architecture.mdc");
129
+ fs.writeFileSync(mdcPath, mdcContent, "utf8");
130
+
131
+ console.log(`${c.green}✓${c.reset} Generated ${c.cyan}${outputDir}/architecture.mdc${c.reset}`);
132
+ return 0;
133
+ } catch (err) {
134
+ console.error(`${c.red}✗${c.reset} MDC generation failed:`, err.message);
135
+ return 1;
136
+ }
137
+ }
138
+ }
139
+
140
+ function generateBasicMDC(truthpack, repoRoot) {
141
+ const routes = truthpack.routes?.server || [];
142
+ const env = truthpack.env?.vars || [];
143
+ const auth = truthpack.auth || {};
144
+
145
+ let content = `---
146
+ description: Auto-generated architecture context from vibecheck
147
+ globs: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]
148
+ ---
149
+
150
+ # Project Architecture
151
+
152
+ ## API Routes (${routes.length} total)
153
+
154
+ `;
155
+
156
+ // Group routes by method
157
+ const byMethod = {};
158
+ routes.forEach(r => {
159
+ const method = r.method || "GET";
160
+ if (!byMethod[method]) byMethod[method] = [];
161
+ byMethod[method].push(r);
162
+ });
163
+
164
+ for (const [method, rs] of Object.entries(byMethod)) {
165
+ content += `### ${method}\n`;
166
+ rs.slice(0, 10).forEach(r => {
167
+ content += `- \`${r.path}\`\n`;
168
+ });
169
+ if (rs.length > 10) {
170
+ content += `- ... and ${rs.length - 10} more\n`;
171
+ }
172
+ content += "\n";
173
+ }
174
+
175
+ if (env.length > 0) {
176
+ content += `## Environment Variables (${env.length} total)\n\n`;
177
+ env.slice(0, 15).forEach(e => {
178
+ content += `- \`${e.name}\` - ${e.required ? "required" : "optional"}\n`;
179
+ });
180
+ if (env.length > 15) {
181
+ content += `- ... and ${env.length - 15} more\n`;
182
+ }
183
+ content += "\n";
184
+ }
185
+
186
+ if (auth.nextMiddleware?.length > 0 || auth.fastify?.hooks?.length > 0) {
187
+ content += `## Authentication\n\n`;
188
+ if (auth.nextMiddleware?.length > 0) {
189
+ content += `- Next.js middleware: ${auth.nextMiddleware.length} rules\n`;
190
+ }
191
+ if (auth.fastify?.hooks?.length > 0) {
192
+ content += `- Fastify hooks: ${auth.fastify.hooks.length} hooks\n`;
193
+ }
194
+ content += "\n";
195
+ }
196
+
197
+ content += `---
198
+ *Generated by vibecheck on ${new Date().toISOString().split("T")[0]}*
199
+ `;
200
+
201
+ return content;
202
+ }
203
+
2
204
  module.exports = { runMdc };
Binary file
@@ -17,6 +17,7 @@ const fs = require("fs");
17
17
  const path = require("path");
18
18
  const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
19
19
  const { shipCore } = require("./runShip");
20
+ const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = require("./lib/drift");
20
21
 
21
22
  let runReality;
22
23
  try {
@@ -105,6 +106,28 @@ ${c.cyan}${c.bold}╔═══════════════════
105
106
  });
106
107
 
107
108
  console.log(` ${c.green}✓${c.reset} Truthpack refreshed (${truthpack.routes?.server?.length || 0} routes, ${truthpack.env?.vars?.length || 0} env vars)`);
109
+
110
+ // Check for contract drift after truthpack refresh
111
+ if (hasContracts(root)) {
112
+ const contracts = loadContracts(root);
113
+ const driftFindings = findContractDrift(contracts, truthpack);
114
+ const driftSummary = getDriftSummary(driftFindings);
115
+
116
+ if (driftSummary.hasDrift) {
117
+ console.log(` ${driftSummary.blocks > 0 ? c.yellow + '⚠️' : c.dim + 'ℹ️'} Contract drift: ${driftSummary.blocks} blocks, ${driftSummary.warns} warnings${c.reset}`);
118
+ timeline.push({
119
+ step: "1.5",
120
+ action: "drift_check",
121
+ status: driftSummary.verdict,
122
+ blocks: driftSummary.blocks,
123
+ warns: driftSummary.warns
124
+ });
125
+
126
+ if (driftSummary.blocks > 0) {
127
+ console.log(` ${c.dim}Run 'vibecheck ctx sync' to update contracts${c.reset}`);
128
+ }
129
+ }
130
+ }
108
131
  } catch (err) {
109
132
  timeline.push({ step: 1, action: "ctx", status: "error", error: err.message });
110
133
  console.log(` ${c.yellow}⚠️ Context refresh failed: ${err.message}${c.reset}`);
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  /**
4
2
  * Vibecheck Replay CLI
5
3
  *
@@ -13,13 +11,11 @@
13
11
  * vibecheck replay import <file> Import a replay capsule
14
12
  */
15
13
 
16
- const { program } = require('commander');
17
- const chalk = require('chalk');
18
- const fs = require('fs').promises;
14
+ const { Command } = require('commander');
19
15
  const path = require('path');
20
- const { chromium } = require('playwright');
21
- const { createReplayEngine } = require('./lib/replay');
22
- const { version } = require('../../package.json');
16
+
17
+ // Lazy-load heavy dependencies
18
+ let chalk, fs, chromium, createReplayEngine, version;
23
19
 
24
20
  // Global options
25
21
  let verbose = false;
@@ -28,10 +24,28 @@ let outputDir = path.join(process.cwd(), '.vibecheck', 'replays');
28
24
  // Initialize replay engine
29
25
  let replayEngine;
30
26
 
27
+ /**
28
+ * Load heavy dependencies on demand
29
+ */
30
+ function loadDeps() {
31
+ if (!chalk) {
32
+ chalk = require('chalk');
33
+ fs = require('fs').promises;
34
+ chromium = require('playwright').chromium;
35
+ createReplayEngine = require('./lib/replay').createReplayEngine;
36
+ try {
37
+ version = require('../../package.json').version;
38
+ } catch {
39
+ version = '0.0.0';
40
+ }
41
+ }
42
+ }
43
+
31
44
  /**
32
45
  * Initialize the replay engine
33
46
  */
34
47
  async function initEngine() {
48
+ loadDeps();
35
49
  if (!replayEngine) {
36
50
  replayEngine = await createReplayEngine({
37
51
  basePath: process.cwd(),
@@ -45,6 +59,7 @@ async function initEngine() {
45
59
  * Record a user session
46
60
  */
47
61
  async function recordSession(url, options) {
62
+ loadDeps();
48
63
  console.log(chalk.blue(`\n🚀 Starting recording session for ${url}\n`));
49
64
 
50
65
  const browser = await chromium.launch({
@@ -115,6 +130,7 @@ async function recordSession(url, options) {
115
130
  * Replay a recorded session
116
131
  */
117
132
  async function replaySession(capsuleId, options) {
133
+ loadDeps();
118
134
  console.log(chalk.blue(`\n▶️ Replaying capsule: ${capsuleId}\n`));
119
135
 
120
136
  const browser = await chromium.launch({
@@ -381,89 +397,103 @@ async function importCapsule(filePath) {
381
397
  }
382
398
  }
383
399
 
384
- // Set up CLI
385
- program
386
- .name('vibecheck replay')
387
- .description('Record and replay user interactions for testing and debugging')
388
- .version(version)
389
- .option('-v, --verbose', 'Enable verbose output', false)
390
- .option('--output-dir <dir>', 'Directory to save replay files', outputDir)
391
- .hook('preAction', (thisCommand) => {
392
- verbose = thisCommand.opts().verbose;
393
- outputDir = thisCommand.opts().outputDir || outputDir;
394
- });
400
+ /**
401
+ * Main entry point for replay command
402
+ * Only parses args when explicitly called
403
+ */
404
+ async function runReplay(args = []) {
405
+ loadDeps();
406
+
407
+ const program = new Command();
408
+
409
+ program
410
+ .name('vibecheck replay')
411
+ .description('Record and replay user interactions for testing and debugging')
412
+ .version(version || '0.0.0')
413
+ .option('-v, --verbose', 'Enable verbose output', false)
414
+ .option('--output-dir <dir>', 'Directory to save replay files', outputDir)
415
+ .hook('preAction', (thisCommand) => {
416
+ verbose = thisCommand.opts().verbose;
417
+ outputDir = thisCommand.opts().outputDir || outputDir;
418
+ });
395
419
 
396
- // Record command
397
- program
398
- .command('record <url>')
399
- .description('Record a user session')
400
- .option('-n, --name <name>', 'Name for this recording')
401
- .option('-d, --description <description>', 'Description of the recording')
402
- .option('--tags <tags>', 'Comma-separated list of tags')
403
- .option('--duration <seconds>', 'Auto-stop after specified seconds')
404
- .option('--headed', 'Run browser in headed mode', false)
405
- .option('--devtools', 'Open devtools', false)
406
- .option('--video', 'Record video', false)
407
- .action((url, options) => {
408
- recordSession(url, options).catch(console.error);
409
- });
420
+ // Record command
421
+ program
422
+ .command('record <url>')
423
+ .description('Record a user session')
424
+ .option('-n, --name <name>', 'Name for this recording')
425
+ .option('-d, --description <description>', 'Description of the recording')
426
+ .option('--tags <tags>', 'Comma-separated list of tags')
427
+ .option('--duration <seconds>', 'Auto-stop after specified seconds')
428
+ .option('--headed', 'Run browser in headed mode', false)
429
+ .option('--devtools', 'Open devtools', false)
430
+ .option('--video', 'Record video', false)
431
+ .action((url, options) => {
432
+ recordSession(url, options).catch(console.error);
433
+ });
410
434
 
411
- // Play command
412
- program
413
- .command('play <capsule>')
414
- .description('Replay a recorded session')
415
- .option('--speed <speed>', 'Playback speed (1.0 = normal, 2.0 = 2x, etc.)', parseFloat, 1.0)
416
- .option('--headed', 'Run browser in headed mode', false)
417
- .option('--devtools', 'Open devtools', false)
418
- .option('--stop-on-failure', 'Stop on first failure', false)
419
- .action((capsule, options) => {
420
- replaySession(capsule, options).catch(console.error);
421
- });
435
+ // Play command
436
+ program
437
+ .command('play <capsule>')
438
+ .description('Replay a recorded session')
439
+ .option('--speed <speed>', 'Playback speed (1.0 = normal, 2.0 = 2x, etc.)', parseFloat, 1.0)
440
+ .option('--headed', 'Run browser in headed mode', false)
441
+ .option('--devtools', 'Open devtools', false)
442
+ .option('--stop-on-failure', 'Stop on first failure', false)
443
+ .action((capsule, options) => {
444
+ replaySession(capsule, options).catch(console.error);
445
+ });
422
446
 
423
- // List command
424
- program
425
- .command('list')
426
- .description('List available replay capsules')
427
- .action(() => {
428
- listCapsules().catch(console.error);
429
- });
447
+ // List command
448
+ program
449
+ .command('list')
450
+ .description('List available replay capsules')
451
+ .action(() => {
452
+ listCapsules().catch(console.error);
453
+ });
430
454
 
431
- // Show command
432
- program
433
- .command('show <id>')
434
- .description('Show details of a replay capsule')
435
- .action((id) => {
436
- showCapsule(id).catch(console.error);
437
- });
455
+ // Show command
456
+ program
457
+ .command('show <id>')
458
+ .description('Show details of a replay capsule')
459
+ .action((id) => {
460
+ showCapsule(id).catch(console.error);
461
+ });
438
462
 
439
- // Delete command
440
- program
441
- .command('delete <id>')
442
- .description('Delete a replay capsule')
443
- .action((id) => {
444
- deleteCapsule(id).catch(console.error);
445
- });
463
+ // Delete command
464
+ program
465
+ .command('delete <id>')
466
+ .description('Delete a replay capsule')
467
+ .action((id) => {
468
+ deleteCapsule(id).catch(console.error);
469
+ });
446
470
 
447
- // Export command
448
- program
449
- .command('export <id> <file>')
450
- .description('Export a replay capsule to a file')
451
- .action((id, file) => {
452
- exportCapsule(id, file).catch(console.error);
453
- });
471
+ // Export command
472
+ program
473
+ .command('export <id> <file>')
474
+ .description('Export a replay capsule to a file')
475
+ .action((id, file) => {
476
+ exportCapsule(id, file).catch(console.error);
477
+ });
454
478
 
455
- // Import command
456
- program
457
- .command('import <file>')
458
- .description('Import a replay capsule from a file')
459
- .action((file) => {
460
- importCapsule(file).catch(console.error);
461
- });
479
+ // Import command
480
+ program
481
+ .command('import <file>')
482
+ .description('Import a replay capsule from a file')
483
+ .action((file) => {
484
+ importCapsule(file).catch(console.error);
485
+ });
462
486
 
463
- // Show help if no arguments
464
- if (process.argv.length <= 2) {
465
- program.help();
487
+ // Parse the provided args (prepend fake node/script path for commander)
488
+ const argv = ['node', 'vibecheck-replay', ...args];
489
+
490
+ if (args.length === 0) {
491
+ program.help();
492
+ return 0;
493
+ }
494
+
495
+ await program.parseAsync(argv);
496
+ return 0;
466
497
  }
467
498
 
468
- // Parse command line arguments
469
- program.parse(process.argv);
499
+ module.exports = { runReplay };
@@ -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);