@vibecheckai/cli 3.5.0 → 3.5.1

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 (326) hide show
  1. package/bin/registry.js +174 -449
  2. package/bin/runners/cli-utils.js +33 -2
  3. package/bin/runners/context/generators/cursor.js +2 -49
  4. package/bin/runners/context/generators/mcp.js +13 -15
  5. package/bin/runners/context/proof-context.js +1 -248
  6. package/bin/runners/lib/analysis-core.js +180 -198
  7. package/bin/runners/lib/analyzers.js +241 -2212
  8. package/bin/runners/lib/cli-output.js +210 -242
  9. package/bin/runners/lib/detectors-v2.js +785 -547
  10. package/bin/runners/lib/entitlements-v2.js +431 -161
  11. package/bin/runners/lib/error-handler.js +9 -16
  12. package/bin/runners/lib/global-flags.js +0 -37
  13. package/bin/runners/lib/html-proof-report.js +700 -350
  14. package/bin/runners/lib/missions/plan.js +6 -46
  15. package/bin/runners/lib/missions/templates.js +0 -232
  16. package/bin/runners/lib/route-truth.js +322 -1167
  17. package/bin/runners/lib/scan-output.js +467 -493
  18. package/bin/runners/lib/ship-output.js +27 -280
  19. package/bin/runners/lib/terminal-ui.js +700 -310
  20. package/bin/runners/lib/truth.js +321 -1004
  21. package/bin/runners/lib/unified-output.js +158 -162
  22. package/bin/runners/lib/upsell.js +204 -104
  23. package/bin/runners/runAIAgent.js +10 -5
  24. package/bin/runners/runAllowlist.js +324 -0
  25. package/bin/runners/runAuth.js +94 -344
  26. package/bin/runners/runCheckpoint.js +45 -43
  27. package/bin/runners/runContext.js +24 -139
  28. package/bin/runners/runDoctor.js +101 -136
  29. package/bin/runners/runEvidencePack.js +219 -0
  30. package/bin/runners/runFix.js +71 -82
  31. package/bin/runners/runGuard.js +119 -606
  32. package/bin/runners/runInit.js +60 -22
  33. package/bin/runners/runInstall.js +281 -0
  34. package/bin/runners/runLabs.js +341 -0
  35. package/bin/runners/runMcp.js +62 -139
  36. package/bin/runners/runPolish.js +83 -282
  37. package/bin/runners/runPromptFirewall.js +12 -5
  38. package/bin/runners/runProve.js +58 -33
  39. package/bin/runners/runReality.js +58 -81
  40. package/bin/runners/runReport.js +7 -34
  41. package/bin/runners/runRuntime.js +8 -5
  42. package/bin/runners/runScan.js +844 -219
  43. package/bin/runners/runShip.js +59 -721
  44. package/bin/runners/runValidate.js +11 -24
  45. package/bin/runners/runWatch.js +76 -131
  46. package/bin/vibecheck.js +69 -295
  47. package/mcp-server/ARCHITECTURE.md +339 -0
  48. package/mcp-server/__tests__/cache.test.ts +313 -0
  49. package/mcp-server/__tests__/executor.test.ts +239 -0
  50. package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
  51. package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
  52. package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
  53. package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
  54. package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
  55. package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
  56. package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
  57. package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
  58. package/mcp-server/__tests__/ids.test.ts +345 -0
  59. package/mcp-server/__tests__/integration/tools.test.ts +410 -0
  60. package/mcp-server/__tests__/registry.test.ts +365 -0
  61. package/mcp-server/__tests__/sandbox.test.ts +323 -0
  62. package/mcp-server/__tests__/schemas.test.ts +372 -0
  63. package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
  64. package/mcp-server/examples/doctor.request.json +14 -0
  65. package/mcp-server/examples/doctor.response.json +53 -0
  66. package/mcp-server/examples/error.response.json +15 -0
  67. package/mcp-server/examples/scan.request.json +14 -0
  68. package/mcp-server/examples/scan.response.json +108 -0
  69. package/mcp-server/handlers/tool-handler.ts +671 -0
  70. package/mcp-server/index-v1.js +698 -0
  71. package/mcp-server/index-v3.ts +293 -0
  72. package/mcp-server/index.js +1080 -1757
  73. package/mcp-server/index.old.js +4137 -0
  74. package/mcp-server/lib/cache.ts +341 -0
  75. package/mcp-server/lib/errors.ts +346 -0
  76. package/mcp-server/lib/executor.ts +792 -0
  77. package/mcp-server/lib/ids.ts +238 -0
  78. package/mcp-server/lib/logger.ts +368 -0
  79. package/mcp-server/lib/metrics.ts +365 -0
  80. package/mcp-server/lib/sandbox.ts +337 -0
  81. package/mcp-server/lib/validator.ts +229 -0
  82. package/mcp-server/package-lock.json +165 -0
  83. package/mcp-server/package.json +32 -7
  84. package/mcp-server/premium-tools.js +2 -2
  85. package/mcp-server/registry/tools.json +476 -0
  86. package/mcp-server/schemas/error-envelope.schema.json +125 -0
  87. package/mcp-server/schemas/finding.schema.json +167 -0
  88. package/mcp-server/schemas/report-artifact.schema.json +88 -0
  89. package/mcp-server/schemas/run-request.schema.json +75 -0
  90. package/mcp-server/schemas/verdict.schema.json +168 -0
  91. package/mcp-server/tier-auth.d.ts +71 -0
  92. package/mcp-server/tier-auth.js +371 -183
  93. package/mcp-server/truth-context.js +90 -131
  94. package/mcp-server/truth-firewall-tools.js +1000 -1611
  95. package/mcp-server/tsconfig.json +34 -0
  96. package/mcp-server/vibecheck-tools.js +2 -2
  97. package/mcp-server/vitest.config.ts +16 -0
  98. package/package.json +3 -4
  99. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
  100. package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
  101. package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
  102. package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
  103. package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
  104. package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
  105. package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
  106. package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
  107. package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
  108. package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
  109. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
  110. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
  111. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
  112. package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
  113. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
  114. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
  115. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
  116. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
  117. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
  118. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
  119. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
  120. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
  121. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
  122. package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
  123. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
  124. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
  125. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
  126. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
  127. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
  128. package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
  129. package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
  130. package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
  131. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
  132. package/bin/runners/lib/agent-firewall/logger.js +0 -141
  133. package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
  134. package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
  135. package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
  136. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
  137. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
  138. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
  139. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
  140. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
  141. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
  142. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
  143. package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
  144. package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
  145. package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
  146. package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
  147. package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
  148. package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
  149. package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
  150. package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
  151. package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
  152. package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
  153. package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
  154. package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
  155. package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
  156. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
  157. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
  158. package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
  159. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
  160. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
  161. package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
  162. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
  163. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
  164. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
  165. package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
  166. package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
  167. package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
  168. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
  169. package/bin/runners/lib/api-client.js +0 -269
  170. package/bin/runners/lib/audit-logger.js +0 -532
  171. package/bin/runners/lib/authority/authorities/architecture.js +0 -364
  172. package/bin/runners/lib/authority/authorities/compliance.js +0 -341
  173. package/bin/runners/lib/authority/authorities/human.js +0 -343
  174. package/bin/runners/lib/authority/authorities/quality.js +0 -420
  175. package/bin/runners/lib/authority/authorities/security.js +0 -228
  176. package/bin/runners/lib/authority/index.js +0 -293
  177. package/bin/runners/lib/authority-badge.js +0 -425
  178. package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
  179. package/bin/runners/lib/cli-charts.js +0 -368
  180. package/bin/runners/lib/cli-config-display.js +0 -405
  181. package/bin/runners/lib/cli-demo.js +0 -275
  182. package/bin/runners/lib/cli-errors.js +0 -438
  183. package/bin/runners/lib/cli-help-formatter.js +0 -439
  184. package/bin/runners/lib/cli-interactive-menu.js +0 -509
  185. package/bin/runners/lib/cli-prompts.js +0 -441
  186. package/bin/runners/lib/cli-scan-cards.js +0 -362
  187. package/bin/runners/lib/compliance-reporter.js +0 -710
  188. package/bin/runners/lib/conductor/index.js +0 -671
  189. package/bin/runners/lib/easy/README.md +0 -123
  190. package/bin/runners/lib/easy/index.js +0 -140
  191. package/bin/runners/lib/easy/interactive-wizard.js +0 -788
  192. package/bin/runners/lib/easy/one-click-firewall.js +0 -564
  193. package/bin/runners/lib/easy/zero-config-reality.js +0 -714
  194. package/bin/runners/lib/engines/accessibility-engine.js +0 -390
  195. package/bin/runners/lib/engines/api-consistency-engine.js +0 -467
  196. package/bin/runners/lib/engines/ast-cache.js +0 -99
  197. package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
  198. package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
  199. package/bin/runners/lib/engines/code-quality-engine.js +0 -255
  200. package/bin/runners/lib/engines/confidence-scoring.js +0 -276
  201. package/bin/runners/lib/engines/console-logs-engine.js +0 -115
  202. package/bin/runners/lib/engines/context-detection.js +0 -264
  203. package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -533
  204. package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
  205. package/bin/runners/lib/engines/dead-code-engine.js +0 -198
  206. package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
  207. package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
  208. package/bin/runners/lib/engines/empty-catch-engine.js +0 -260
  209. package/bin/runners/lib/engines/env-variables-engine.js +0 -458
  210. package/bin/runners/lib/engines/error-handling-engine.js +0 -437
  211. package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
  212. package/bin/runners/lib/engines/file-filter.js +0 -131
  213. package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
  214. package/bin/runners/lib/engines/framework-detection.js +0 -508
  215. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
  216. package/bin/runners/lib/engines/import-order-engine.js +0 -429
  217. package/bin/runners/lib/engines/mock-data-engine.js +0 -315
  218. package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
  219. package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
  220. package/bin/runners/lib/engines/orchestrator.js +0 -334
  221. package/bin/runners/lib/engines/parallel-processor.js +0 -71
  222. package/bin/runners/lib/engines/performance-issues-engine.js +0 -405
  223. package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
  224. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -571
  225. package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
  226. package/bin/runners/lib/engines/type-aware-engine.js +0 -376
  227. package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
  228. package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
  229. package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -124
  230. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
  231. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -439
  232. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
  233. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
  234. package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
  235. package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
  236. package/bin/runners/lib/enhanced-features/index.js +0 -305
  237. package/bin/runners/lib/enhanced-output.js +0 -631
  238. package/bin/runners/lib/enterprise.js +0 -300
  239. package/bin/runners/lib/exit-codes.js +0 -275
  240. package/bin/runners/lib/fingerprint.js +0 -377
  241. package/bin/runners/lib/firewall/command-validator.js +0 -351
  242. package/bin/runners/lib/firewall/config.js +0 -341
  243. package/bin/runners/lib/firewall/content-validator.js +0 -519
  244. package/bin/runners/lib/firewall/index.js +0 -101
  245. package/bin/runners/lib/firewall/path-validator.js +0 -256
  246. package/bin/runners/lib/help-formatter.js +0 -413
  247. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
  248. package/bin/runners/lib/logger.js +0 -38
  249. package/bin/runners/lib/mcp-utils.js +0 -425
  250. package/bin/runners/lib/output/index.js +0 -1022
  251. package/bin/runners/lib/policy-engine.js +0 -652
  252. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
  253. package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
  254. package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
  255. package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
  256. package/bin/runners/lib/polish/autofix/index.js +0 -200
  257. package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
  258. package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
  259. package/bin/runners/lib/polish/backend-checks.js +0 -148
  260. package/bin/runners/lib/polish/documentation-checks.js +0 -111
  261. package/bin/runners/lib/polish/frontend-checks.js +0 -168
  262. package/bin/runners/lib/polish/index.js +0 -71
  263. package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
  264. package/bin/runners/lib/polish/library-detection.js +0 -175
  265. package/bin/runners/lib/polish/performance-checks.js +0 -100
  266. package/bin/runners/lib/polish/security-checks.js +0 -148
  267. package/bin/runners/lib/polish/utils.js +0 -203
  268. package/bin/runners/lib/prompt-builder.js +0 -540
  269. package/bin/runners/lib/proof-certificate.js +0 -634
  270. package/bin/runners/lib/reality/accessibility-audit.js +0 -946
  271. package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
  272. package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
  273. package/bin/runners/lib/reality/performance-tracker.js +0 -1077
  274. package/bin/runners/lib/reality/scenario-generator.js +0 -1404
  275. package/bin/runners/lib/reality/visual-regression.js +0 -852
  276. package/bin/runners/lib/reality-profiler.js +0 -717
  277. package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
  278. package/bin/runners/lib/review/ai-code-review.js +0 -832
  279. package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
  280. package/bin/runners/lib/sbom-generator.js +0 -641
  281. package/bin/runners/lib/scan-output-enhanced.js +0 -512
  282. package/bin/runners/lib/security/owasp-scanner.js +0 -939
  283. package/bin/runners/lib/ship-output-enterprise.js +0 -239
  284. package/bin/runners/lib/unified-cli-output.js +0 -777
  285. package/bin/runners/lib/validators/contract-validator.js +0 -283
  286. package/bin/runners/lib/validators/dead-export-detector.js +0 -279
  287. package/bin/runners/lib/validators/dep-audit.js +0 -245
  288. package/bin/runners/lib/validators/env-validator.js +0 -319
  289. package/bin/runners/lib/validators/index.js +0 -120
  290. package/bin/runners/lib/validators/license-checker.js +0 -252
  291. package/bin/runners/lib/validators/route-validator.js +0 -290
  292. package/bin/runners/runAgent.d.ts +0 -5
  293. package/bin/runners/runAgent.js +0 -164
  294. package/bin/runners/runApprove.js +0 -1233
  295. package/bin/runners/runAuthority.js +0 -528
  296. package/bin/runners/runClassify.js +0 -862
  297. package/bin/runners/runConductor.js +0 -772
  298. package/bin/runners/runContainer.js +0 -366
  299. package/bin/runners/runContext.d.ts +0 -4
  300. package/bin/runners/runEasy.js +0 -410
  301. package/bin/runners/runFirewall.d.ts +0 -5
  302. package/bin/runners/runFirewall.js +0 -137
  303. package/bin/runners/runFirewallHook.d.ts +0 -5
  304. package/bin/runners/runFirewallHook.js +0 -59
  305. package/bin/runners/runIaC.js +0 -372
  306. package/bin/runners/runPolish.d.ts +0 -4
  307. package/bin/runners/runProof.zip +0 -0
  308. package/bin/runners/runTruth.d.ts +0 -5
  309. package/bin/runners/runTruth.js +0 -104
  310. package/bin/runners/runVibe.js +0 -791
  311. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  312. package/mcp-server/agent-firewall-interceptor.js +0 -500
  313. package/mcp-server/authority-tools.js +0 -569
  314. package/mcp-server/conductor/conflict-resolver.js +0 -588
  315. package/mcp-server/conductor/execution-planner.js +0 -544
  316. package/mcp-server/conductor/index.js +0 -377
  317. package/mcp-server/conductor/lock-manager.js +0 -615
  318. package/mcp-server/conductor/request-queue.js +0 -550
  319. package/mcp-server/conductor/session-manager.js +0 -500
  320. package/mcp-server/conductor/tools.js +0 -510
  321. package/mcp-server/lib/api-client.cjs +0 -13
  322. package/mcp-server/lib/logger.cjs +0 -30
  323. package/mcp-server/logger.js +0 -173
  324. package/mcp-server/tools-v3.js +0 -1039
  325. package/mcp-server/tools.js +0 -495
  326. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
@@ -1,43 +1,44 @@
1
1
  /**
2
- * vibecheck Scan - Quick Code Analysis (FREE)
2
+ * vibecheck Scan - Route Integrity & Code Analysis
3
3
  *
4
- * Fast scan running only 5 essential engines:
5
- * - hardcoded-secrets-engine (security)
6
- * - console-logs-engine (code hygiene)
7
- * - ai-hallucination-engine (vibecheck core)
8
- * - type-aware-engine (type safety)
9
- * - error-handling-engine (reliability)
4
+ * The ultimate scanner combining:
5
+ * - Route integrity (dead links, orphan routes, coverage)
6
+ * - Security analysis (secrets, auth, vulnerabilities)
7
+ * - Code quality (mocks, placeholders, hygiene)
10
8
  *
11
- * Target: < 3 seconds on medium project (100-500 files)
12
- *
13
- * For comprehensive analysis (17+ engines + validators), use: vibecheck ship [PRO]
9
+ * Modes:
10
+ * - vibecheck scan: Layer 1 (AST) - Fast static analysis
11
+ * - vibecheck scan --truth: Layer 1+2 (+ build manifests) - CI/ship
12
+ * - vibecheck scan --reality --url <url>: Layer 1+2+3 (+ Playwright) - Full proof
14
13
  */
15
14
 
16
15
  const path = require("path");
17
16
  const fs = require("fs");
18
17
  const { withErrorHandling, createUserError } = require("./lib/error-handler");
19
- const { enforceLimit, trackUsage, getCurrentTier } = require("./lib/entitlements-v2");
18
+ const { enforceLimit, trackUsage } = require("./lib/entitlements");
20
19
  const { emitScanStart, emitScanComplete } = require("./lib/audit-bridge");
21
20
  const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
22
- const { EXIT, verdictToExitCode } = require("./lib/exit-codes");
23
-
24
- // Import orchestrator for engine management
25
- const { runScanEngines, SCAN_ENGINES } = require("./lib/engines/orchestrator");
26
21
 
27
22
  // ═══════════════════════════════════════════════════════════════════════════════
28
- // TERMINAL UI
23
+ // ENHANCED TERMINAL UI & OUTPUT MODULES
29
24
  // ═══════════════════════════════════════════════════════════════════════════════
30
25
 
31
26
  const {
32
27
  ansi,
33
28
  colors,
34
29
  Spinner,
30
+ PhaseProgress,
31
+ renderBanner,
32
+ renderSection,
35
33
  formatDuration,
36
34
  } = require("./lib/terminal-ui");
37
35
 
38
36
  const {
37
+ formatScanOutput,
39
38
  formatSARIF,
40
39
  getExitCode,
40
+ printError,
41
+ EXIT_CODES,
41
42
  calculateScore,
42
43
  } = require("./lib/scan-output");
43
44
 
@@ -50,7 +51,7 @@ ${ansi.rgb(120, 120, 255)} ╚████╔╝ ██║██████
50
51
  ${ansi.rgb(150, 100, 255)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${ansi.reset}
51
52
 
52
53
  ${ansi.dim} ┌─────────────────────────────────────────────────────────────────────┐${ansi.reset}
53
- ${ansi.dim} │${ansi.reset} ${ansi.rgb(255, 255, 255)}${ansi.bold}Quick Scan${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(200, 200, 200)}5 Essential Engines${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(150, 150, 150)}< 3 seconds${ansi.reset} ${ansi.dim}│${ansi.reset}
54
+ ${ansi.dim} │${ansi.reset} ${ansi.rgb(255, 255, 255)}${ansi.bold}Route Integrity${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(200, 200, 200)}Security${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(150, 150, 150)}Quality${ansi.reset} ${ansi.dim}•${ansi.reset} ${ansi.rgb(100, 100, 100)}Ship with Confidence${ansi.reset} ${ansi.dim}│${ansi.reset}
54
55
  ${ansi.dim} └─────────────────────────────────────────────────────────────────────┘${ansi.reset}
55
56
  `;
56
57
 
@@ -58,29 +59,62 @@ function printBanner() {
58
59
  console.log(BANNER);
59
60
  }
60
61
 
62
+ // Legacy compatibility functions - now use enhanced modules
63
+ function printCoverageMap(coverageMap) {
64
+ const { renderCoverageMap } = require("./lib/scan-output");
65
+ console.log(renderCoverageMap(coverageMap));
66
+ }
67
+
68
+ function printBreakdown(breakdown) {
69
+ const { renderBreakdown } = require("./lib/scan-output");
70
+ console.log(renderBreakdown(breakdown));
71
+ }
72
+
73
+ function printBlockers(blockers) {
74
+ const { renderBlockers } = require("./lib/scan-output");
75
+ console.log(renderBlockers(blockers));
76
+ }
77
+
78
+ function printLayers(layers) {
79
+ const { renderLayers } = require("./lib/scan-output");
80
+ console.log(renderLayers(layers));
81
+ }
82
+
61
83
  // ═══════════════════════════════════════════════════════════════════════════════
62
84
  // ARGS PARSER
63
85
  // ═══════════════════════════════════════════════════════════════════════════════
64
86
 
65
87
  function parseArgs(args) {
88
+ // Parse global flags first
66
89
  const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
67
90
 
68
91
  const opts = {
69
92
  path: globalFlags.path || process.cwd(),
93
+ truth: false,
94
+ reality: false,
95
+ realitySniff: false,
96
+ baseUrl: null,
70
97
  json: globalFlags.json || false,
71
98
  sarif: false,
72
99
  verbose: globalFlags.verbose || false,
73
100
  help: globalFlags.help || false,
74
- save: true,
101
+ autofix: false,
102
+ save: true, // Always save results by default
75
103
  noBanner: globalFlags.noBanner || false,
76
104
  ci: globalFlags.ci || false,
77
105
  quiet: globalFlags.quiet || false,
78
106
  };
79
107
 
108
+ // Parse command-specific args from cleanArgs
80
109
  for (let i = 0; i < cleanArgs.length; i++) {
81
110
  const arg = cleanArgs[i];
82
111
 
83
- if (arg === '--sarif') opts.sarif = true;
112
+ if (arg === '--truth' || arg === '-t') opts.truth = true;
113
+ else if (arg === '--reality' || arg === '-r') { opts.reality = true; opts.truth = true; }
114
+ else if (arg === '--reality-sniff' || arg === '--sniff') opts.realitySniff = true;
115
+ else if (arg === '--url' || arg === '-u') { opts.baseUrl = cleanArgs[++i]; opts.reality = true; opts.truth = true; }
116
+ else if (arg === '--sarif') opts.sarif = true;
117
+ else if (arg === '--autofix' || arg === '--fix' || arg === '-f') opts.autofix = true;
84
118
  else if (arg === '--no-save') opts.save = false;
85
119
  else if (arg === '--path' || arg === '-p') opts.path = cleanArgs[++i] || process.cwd();
86
120
  else if (arg.startsWith('--path=')) opts.path = arg.split('=')[1];
@@ -95,51 +129,319 @@ function printHelp(showBanner = true) {
95
129
  console.log(BANNER);
96
130
  }
97
131
  console.log(`
98
- ${ansi.bold}USAGE${ansi.reset}
99
- ${colors.accent}vibecheck scan${ansi.reset} [path] [options]
100
-
101
- ${ansi.bold}QUICK SCAN${ansi.reset} - Runs 5 essential engines in < 3 seconds:
102
- • hardcoded-secrets-engine ${ansi.dim}(security)${ansi.reset}
103
- console-logs-engine ${ansi.dim}(code hygiene)${ansi.reset}
104
- ai-hallucination-engine ${ansi.dim}(vibecheck core)${ansi.reset}
105
- type-aware-engine ${ansi.dim}(type safety)${ansi.reset}
106
- • error-handling-engine ${ansi.dim}(reliability)${ansi.reset}
107
-
108
- ${ansi.bold}OUTPUT OPTIONS${ansi.reset}
109
- ${colors.accent}--json${ansi.reset} Output as JSON
110
- ${colors.accent}--sarif${ansi.reset} SARIF format (GitHub code scanning)
111
- ${colors.accent}--no-save${ansi.reset} Don't save results to .vibecheck/results/
112
- ${colors.accent}--verbose, -v${ansi.reset} Show detailed progress
113
-
114
- ${ansi.bold}GLOBAL OPTIONS${ansi.reset}
115
- ${colors.accent}--path, -p <dir>${ansi.reset} Run in specified directory
116
- ${colors.accent}--quiet, -q${ansi.reset} Suppress non-essential output
117
- ${colors.accent}--ci${ansi.reset} CI mode (quiet + no-banner)
118
- ${colors.accent}--help, -h${ansi.reset} Show this help
119
-
120
- ${ansi.bold}💡 EXAMPLES${ansi.reset}
121
-
122
- ${ansi.dim}# Quick scan (default)${ansi.reset}
132
+ ${ansi.bold}Usage:${ansi.reset} vibecheck scan ${ansi.dim}(s)${ansi.reset} [path] [options]
133
+
134
+ ${ansi.bold}Aliases:${ansi.reset} ${ansi.dim}s${ansi.reset}
135
+
136
+ ${ansi.bold}Scan Modes:${ansi.reset}
137
+ ${colors.accent}(default)${ansi.reset} Layer 1: AST static analysis ${ansi.dim}(fast)${ansi.reset}
138
+ ${colors.accent}--truth, -t${ansi.reset} Layer 1+2: Include build manifest verification ${ansi.dim}(CI/ship)${ansi.reset}
139
+ ${colors.accent}--reality, -r${ansi.reset} Layer 1+2+3: Include Playwright runtime proof ${ansi.dim}(full)${ansi.reset}
140
+ ${colors.accent}--reality-sniff${ansi.reset} Include Reality Sniff AI artifact detection ${ansi.dim}(recommended)${ansi.reset}
141
+
142
+ ${ansi.bold}Fix Mode:${ansi.reset}
143
+ ${colors.accent}--autofix, -f${ansi.reset} Apply safe fixes + generate AI missions ${ansi.rgb(0, 200, 255)}[STARTER]${ansi.reset}
144
+
145
+ ${ansi.bold}Options:${ansi.reset}
146
+ ${colors.accent}--url, -u${ansi.reset} Base URL for reality testing (e.g., http://localhost:3000)
147
+ ${colors.accent}--verbose, -v${ansi.reset} Show detailed progress
148
+ ${colors.accent}--json${ansi.reset} Output results as JSON
149
+ ${colors.accent}--sarif${ansi.reset} Output in SARIF format (GitHub code scanning)
150
+ ${colors.accent}--no-save${ansi.reset} Don't save results to .vibecheck/results/
151
+ ${colors.accent}--help, -h${ansi.reset} Show this help
152
+
153
+ ${ansi.bold}Examples:${ansi.reset}
154
+ ${ansi.dim}# Quick scan (AST only)${ansi.reset}
123
155
  vibecheck scan
124
156
 
125
- ${ansi.dim}# Quick scan with JSON output${ansi.reset}
126
- vibecheck scan --json
157
+ ${ansi.dim}# Scan + autofix with missions${ansi.reset}
158
+ vibecheck scan --autofix
127
159
 
128
- ${ansi.dim}# Scan a specific directory${ansi.reset}
129
- vibecheck scan ./my-project
160
+ ${ansi.dim}# CI/CD scan with manifest verification${ansi.reset}
161
+ vibecheck scan --truth
130
162
 
131
- ${ansi.bold}🚀 COMPREHENSIVE ANALYSIS${ansi.reset}
163
+ ${ansi.dim}# Full proof with Playwright${ansi.reset}
164
+ vibecheck scan --reality --url http://localhost:3000
132
165
 
133
- For 17+ engines plus ship-only validators, use:
134
- ${colors.accent}vibecheck ship${ansi.reset} ${ansi.magenta}[PRO]${ansi.reset}
135
-
136
- ${ansi.dim}─────────────────────────────────────────────────────────────${ansi.reset}
137
- ${ansi.dim}Documentation: https://docs.vibecheckai.dev/cli/scan${ansi.reset}
166
+ ${ansi.bold}Output:${ansi.reset}
167
+ Results saved to: .vibecheck/results/latest.json
168
+ Missions saved to: .vibecheck/missions/ ${ansi.dim}(with --autofix)${ansi.reset}
138
169
  `);
139
170
  }
140
171
 
141
172
  // ═══════════════════════════════════════════════════════════════════════════════
142
- // MAIN SCAN FUNCTION
173
+ // AUTOFIX & MISSION GENERATION
174
+ // ═══════════════════════════════════════════════════════════════════════════════
175
+
176
+ /**
177
+ * Normalize severity values to standard format
178
+ */
179
+ function normalizeSeverity(severity) {
180
+ if (!severity) return 'medium';
181
+ const sev = String(severity).toLowerCase();
182
+ if (sev === 'block' || sev === 'critical') return 'critical';
183
+ if (sev === 'high') return 'high';
184
+ if (sev === 'warn' || sev === 'warning' || sev === 'medium') return 'medium';
185
+ if (sev === 'info' || sev === 'low') return 'low';
186
+ return 'medium'; // Default fallback
187
+ }
188
+
189
+ async function generateMissions(findings, projectPath, opts) {
190
+ const missionsDir = path.join(projectPath, '.vibecheck', 'missions');
191
+
192
+ // Ensure missions directory exists
193
+ if (!fs.existsSync(missionsDir)) {
194
+ fs.mkdirSync(missionsDir, { recursive: true });
195
+ }
196
+
197
+ const missions = [];
198
+ const missionIndex = [];
199
+
200
+ for (let i = 0; i < findings.length; i++) {
201
+ const finding = findings[i];
202
+ const missionId = `mission-${String(i + 1).padStart(3, '0')}`;
203
+ const normalizedSeverity = normalizeSeverity(finding.severity);
204
+
205
+ // Generate AI-ready mission prompt
206
+ const mission = {
207
+ id: missionId,
208
+ createdAt: new Date().toISOString(),
209
+ finding: {
210
+ id: finding.id || `finding-${i + 1}`,
211
+ severity: normalizedSeverity,
212
+ originalSeverity: finding.severity, // Keep original for reference
213
+ category: finding.category,
214
+ title: finding.title || finding.message,
215
+ file: finding.file,
216
+ line: finding.line,
217
+ },
218
+ prompt: generateMissionPrompt(finding),
219
+ constraints: generateConstraints(finding),
220
+ verification: generateVerificationSteps(finding),
221
+ status: 'pending',
222
+ };
223
+
224
+ missions.push(mission);
225
+ missionIndex.push({
226
+ id: missionId,
227
+ severity: normalizedSeverity,
228
+ title: finding.title || finding.message,
229
+ file: finding.file,
230
+ status: 'pending',
231
+ });
232
+
233
+ // Save individual mission JSON
234
+ const missionPath = path.join(missionsDir, `${missionId}.json`);
235
+ fs.writeFileSync(missionPath, JSON.stringify(mission, null, 2));
236
+ }
237
+
238
+ // Generate MISSIONS.md index
239
+ const missionsMarkdown = generateMissionsMarkdown(missionIndex, projectPath);
240
+ fs.writeFileSync(path.join(missionsDir, 'MISSIONS.md'), missionsMarkdown);
241
+
242
+ // Save missions summary
243
+ const summary = {
244
+ generatedAt: new Date().toISOString(),
245
+ totalMissions: missions.length,
246
+ bySeverity: {
247
+ critical: missionIndex.filter(m => m.severity === 'critical').length,
248
+ high: missionIndex.filter(m => m.severity === 'high').length,
249
+ medium: missionIndex.filter(m => m.severity === 'medium').length,
250
+ low: missionIndex.filter(m => m.severity === 'low').length,
251
+ },
252
+ missions: missionIndex,
253
+ };
254
+ fs.writeFileSync(path.join(missionsDir, 'summary.json'), JSON.stringify(summary, null, 2));
255
+
256
+ return { missions, summary };
257
+ }
258
+
259
+ function generateMissionPrompt(finding) {
260
+ const file = finding.file || 'unknown file';
261
+ const line = finding.line ? ` at line ${finding.line}` : '';
262
+ const title = finding.title || finding.message || 'Unknown issue';
263
+ const category = finding.category || 'general';
264
+
265
+ let prompt = `## Mission: Fix ${title}
266
+
267
+ **File:** \`${file}\`${line}
268
+ **Category:** ${category}
269
+ **Severity:** ${finding.severity || 'medium'}
270
+
271
+ ### Problem
272
+ ${finding.message || title}
273
+
274
+ ### Context
275
+ `;
276
+
277
+ // Add category-specific context
278
+ if (category === 'ROUTE' || category === 'routes') {
279
+ prompt += `This is a route integrity issue. The route may be:
280
+ - Referenced but not defined
281
+ - Defined but not reachable
282
+ - Missing proper error handling
283
+ `;
284
+ } else if (category === 'ENV' || category === 'env') {
285
+ prompt += `This is an environment variable issue. The variable may be:
286
+ - Used but not defined in .env
287
+ - Missing from .env.example
288
+ - Not documented
289
+ `;
290
+ } else if (category === 'AUTH' || category === 'auth' || category === 'security') {
291
+ prompt += `This is a security/authentication issue. Check for:
292
+ - Proper authentication middleware
293
+ - Authorization checks
294
+ - Secure defaults
295
+ `;
296
+ } else if (category === 'MOCK' || category === 'mock') {
297
+ prompt += `This is a mock/placeholder issue. The code may contain:
298
+ - TODO comments that need implementation
299
+ - Placeholder data that should be real
300
+ - Fake success responses
301
+ `;
302
+ }
303
+
304
+ prompt += `
305
+ ### Requirements
306
+ 1. Fix the issue while maintaining existing functionality
307
+ 2. Follow the project's coding style and patterns
308
+ 3. Add appropriate error handling
309
+ 4. Update tests if they exist
310
+
311
+ ### Verification
312
+ After making changes:
313
+ 1. Run \`vibecheck scan\` to verify the issue is resolved
314
+ 2. Run any existing tests for the affected file
315
+ 3. Manually verify the functionality if applicable
316
+ `;
317
+
318
+ if (finding.fix || finding.fixSuggestion) {
319
+ prompt += `
320
+ ### Suggested Fix
321
+ ${finding.fix || finding.fixSuggestion}
322
+ `;
323
+ }
324
+
325
+ return prompt;
326
+ }
327
+
328
+ function generateConstraints(finding) {
329
+ const constraints = [
330
+ 'Do not break existing functionality',
331
+ 'Follow existing code patterns in the project',
332
+ 'Add error handling where appropriate',
333
+ ];
334
+
335
+ if (finding.category === 'security' || finding.category === 'AUTH') {
336
+ constraints.push('Ensure no security regressions');
337
+ constraints.push('Follow OWASP security guidelines');
338
+ }
339
+
340
+ if (finding.category === 'ROUTE' || finding.category === 'routes') {
341
+ constraints.push('Maintain API backwards compatibility');
342
+ constraints.push('Update route documentation if changed');
343
+ }
344
+
345
+ return constraints;
346
+ }
347
+
348
+ function generateVerificationSteps(finding) {
349
+ const steps = [
350
+ 'Run `vibecheck scan` and confirm this issue no longer appears',
351
+ 'Run `vibecheck checkpoint` to ensure no regressions',
352
+ ];
353
+
354
+ if (finding.file) {
355
+ steps.push(`Review changes in \`${finding.file}\``);
356
+ }
357
+
358
+ if (finding.category === 'ROUTE' || finding.category === 'routes') {
359
+ steps.push('Test the affected route(s) manually or with automated tests');
360
+ }
361
+
362
+ if (finding.category === 'ENV' || finding.category === 'env') {
363
+ steps.push('Verify environment variable is properly documented');
364
+ steps.push('Check .env.example is updated');
365
+ }
366
+
367
+ return steps;
368
+ }
369
+
370
+ function generateMissionsMarkdown(missionIndex, projectPath) {
371
+ const projectName = path.basename(projectPath);
372
+ const now = new Date().toISOString();
373
+
374
+ let md = `# Vibecheck Missions
375
+
376
+ > Generated: ${now}
377
+ > Project: ${projectName}
378
+
379
+ ## Summary
380
+
381
+ | Severity | Count |
382
+ |----------|-------|
383
+ | Critical | ${missionIndex.filter(m => m.severity === 'critical').length} |
384
+ | High | ${missionIndex.filter(m => m.severity === 'high').length} |
385
+ | Medium | ${missionIndex.filter(m => m.severity === 'medium').length} |
386
+ | Low | ${missionIndex.filter(m => m.severity === 'low').length} |
387
+ | **Total** | **${missionIndex.length}** |
388
+
389
+ ## Missions
390
+
391
+ `;
392
+
393
+ // Group by severity
394
+ const bySeverity = {
395
+ critical: missionIndex.filter(m => m.severity === 'critical'),
396
+ high: missionIndex.filter(m => m.severity === 'high'),
397
+ medium: missionIndex.filter(m => m.severity === 'medium'),
398
+ low: missionIndex.filter(m => m.severity === 'low'),
399
+ };
400
+
401
+ for (const [severity, missions] of Object.entries(bySeverity)) {
402
+ if (missions.length === 0) continue;
403
+
404
+ const emoji = severity === 'critical' ? '🔴' : severity === 'high' ? '🟠' : severity === 'medium' ? '🟡' : '🔵';
405
+ md += `### ${emoji} ${severity.charAt(0).toUpperCase() + severity.slice(1)} (${missions.length})\n\n`;
406
+
407
+ for (const mission of missions) {
408
+ const checkbox = mission.status === 'completed' ? '[x]' : '[ ]';
409
+ md += `- ${checkbox} **${mission.id}**: ${mission.title}\n`;
410
+ if (mission.file) {
411
+ md += ` - File: \`${mission.file}\`\n`;
412
+ }
413
+ }
414
+ md += '\n';
415
+ }
416
+
417
+ md += `---
418
+
419
+ ## How to Use
420
+
421
+ 1. **Review missions**: Read each mission file in \`.vibecheck/missions/\`
422
+ 2. **Copy prompts**: Use the prompt in each mission file with your AI assistant
423
+ 3. **Verify fixes**: Run \`vibecheck scan\` after each fix
424
+ 4. **Track progress**: Update mission status in this file
425
+
426
+ ## Commands
427
+
428
+ \`\`\`bash
429
+ # Re-scan after fixes
430
+ vibecheck scan
431
+
432
+ # Check progress
433
+ vibecheck checkpoint
434
+
435
+ # Final verification
436
+ vibecheck ship
437
+ \`\`\`
438
+ `;
439
+
440
+ return md;
441
+ }
442
+
443
+ // ═══════════════════════════════════════════════════════════════════════════════
444
+ // MAIN SCAN FUNCTION - ROUTE INTEGRITY SYSTEM
143
445
  // ═══════════════════════════════════════════════════════════════════════════════
144
446
 
145
447
  async function runScan(args) {
@@ -151,7 +453,7 @@ async function runScan(args) {
151
453
  return 0;
152
454
  }
153
455
 
154
- // Entitlement check (scan is FREE)
456
+ // Entitlement check (graceful offline handling)
155
457
  try {
156
458
  await enforceLimit('scans');
157
459
  await trackUsage('scans');
@@ -160,239 +462,562 @@ async function runScan(args) {
160
462
  console.error(err.upgradePrompt || err.message);
161
463
  return 1;
162
464
  }
163
- // Network errors - continue with free tier
164
- if (!['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND'].includes(err.code) && err.name !== 'NetworkError') {
165
- throw err;
465
+ // Network error - fall back to free tier only (SECURITY: never grant paid features offline)
466
+ if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.code === 'ENOTFOUND' || err.name === 'NetworkError') {
467
+ console.warn(` ${colors.warning}⚠${ansi.reset} API unavailable, running in ${colors.success}FREE${ansi.reset} tier mode`);
468
+ console.warn(` ${ansi.dim}Paid features require API connection. Continuing with free features only.${ansi.reset}\n`);
469
+ // Continue with free tier features only - scan command is free tier
470
+ } else {
471
+ throw err; // Re-throw unexpected errors
166
472
  }
167
473
  }
168
474
 
169
- // Print banner
475
+ // Print banner (respects --no-banner, VIBECHECK_NO_BANNER, --ci, --quiet, --json)
170
476
  if (shouldShowBanner(opts)) {
171
477
  printBanner();
172
478
  }
173
479
 
174
480
  const projectPath = path.resolve(opts.path);
175
481
  const startTime = Date.now();
176
- const projectName = path.basename(projectPath);
177
482
 
178
- // Emit audit event
483
+ // Emit audit event for scan start
179
484
  emitScanStart(projectPath, args);
485
+ const projectName = path.basename(projectPath);
180
486
 
181
487
  // Validate project path
182
488
  if (!fs.existsSync(projectPath)) {
183
489
  throw createUserError(`Project path does not exist: ${projectPath}`, "ValidationError");
184
490
  }
185
491
 
492
+ // Determine layers
493
+ const layers = {
494
+ ast: true,
495
+ truth: opts.truth,
496
+ reality: opts.reality,
497
+ realitySniff: opts.realitySniff,
498
+ };
499
+
186
500
  // Print scan info
187
- if (!opts.json && !opts.quiet) {
188
- console.log(` ${ansi.dim}Project:${ansi.reset} ${ansi.bold}${projectName}${ansi.reset}`);
189
- console.log(` ${ansi.dim}Path:${ansi.reset} ${projectPath}`);
190
- console.log(` ${ansi.dim}Mode:${ansi.reset} ${colors.accent}Quick Scan${ansi.reset} ${ansi.dim}(${SCAN_ENGINES.length} engines)${ansi.reset}`);
501
+ const layerNames = [];
502
+ if (layers.ast) layerNames.push('AST');
503
+ if (layers.truth) layerNames.push('Truth');
504
+ if (layers.reality) layerNames.push('Reality');
505
+ if (layers.realitySniff) layerNames.push('Reality Sniff');
506
+
507
+ console.log(` ${ansi.dim}Project:${ansi.reset} ${ansi.bold}${projectName}${ansi.reset}`);
508
+ console.log(` ${ansi.dim}Path:${ansi.reset} ${projectPath}`);
509
+ console.log(` ${ansi.dim}Layers:${ansi.reset} ${colors.accent}${layerNames.join(' → ')}${ansi.reset}`);
510
+ console.log();
511
+
512
+ // Reality layer requires URL
513
+ if (opts.reality && !opts.baseUrl) {
514
+ console.log(` ${colors.warning}⚠${ansi.reset} ${ansi.bold}Reality layer requires --url${ansi.reset}`);
515
+ console.log(` ${ansi.dim}Example: vibecheck scan --reality --url http://localhost:3000${ansi.reset}`);
191
516
  console.log();
517
+ return 1;
192
518
  }
193
519
 
520
+ // Initialize spinner outside try block for error handling
194
521
  let spinner = null;
195
522
 
196
523
  try {
197
- // Start spinner
198
- if (!opts.json && !opts.quiet) {
199
- spinner = new Spinner({ color: colors.primary });
200
- spinner.start(`Running quick scan...`);
524
+ // Import systems - try TypeScript compiled first, fallback to JS runtime
525
+ let scanRouteIntegrity;
526
+ let useFallbackScanner = false;
527
+
528
+ try {
529
+ scanRouteIntegrity = require('../../dist/lib/route-integrity').scanRouteIntegrity;
530
+ } catch (e) {
531
+ // Fallback to JS-based scanner using truth.js and analyzers.js
532
+ useFallbackScanner = true;
533
+ const { buildTruthpack } = require('./lib/truth');
534
+ const { findMissingRoutes, findEnvGaps, findFakeSuccess, findGhostAuth } = require('./lib/analyzers');
535
+
536
+ scanRouteIntegrity = async function({ projectPath, layers, baseUrl, verbose }) {
537
+ // Build truthpack for route analysis
538
+ const truthpack = await buildTruthpack({ repoRoot: projectPath });
539
+
540
+ // Run analyzers
541
+ const findings = [];
542
+ findings.push(...findMissingRoutes(truthpack));
543
+ findings.push(...findEnvGaps(truthpack));
544
+ findings.push(...findFakeSuccess(projectPath));
545
+ findings.push(...findGhostAuth(truthpack, projectPath));
546
+
547
+ // Convert to scan format matching TypeScript scanner output
548
+ const shipBlockers = findings.map((f, i) => ({
549
+ id: f.id || `finding-${i}`,
550
+ ruleId: f.category,
551
+ category: f.category,
552
+ severity: f.severity === 'BLOCK' ? 'critical' : f.severity === 'WARN' ? 'warning' : 'info',
553
+ title: f.title,
554
+ message: f.title,
555
+ description: f.why,
556
+ file: f.evidence?.[0]?.file || '',
557
+ line: parseInt(f.evidence?.[0]?.lines?.split('-')[0]) || 1,
558
+ evidence: f.evidence || [],
559
+ fixHints: f.fixHints || [],
560
+ autofixAvailable: false,
561
+ verdict: f.severity === 'BLOCK' ? 'FAIL' : 'WARN',
562
+ }));
563
+
564
+ // Return structure matching TypeScript scanner
565
+ return {
566
+ report: {
567
+ shipBlockers,
568
+ realitySniffFindings: [],
569
+ routeMap: truthpack.routes,
570
+ summary: {
571
+ total: shipBlockers.length,
572
+ critical: shipBlockers.filter(f => f.severity === 'critical').length,
573
+ warning: shipBlockers.filter(f => f.severity === 'warning').length,
574
+ },
575
+ verdict: shipBlockers.some(f => f.severity === 'critical') ? 'BLOCK' :
576
+ shipBlockers.some(f => f.severity === 'warning') ? 'WARN' : 'SHIP'
577
+ },
578
+ outputPaths: {},
579
+ truthpack
580
+ };
581
+ };
201
582
  }
202
-
203
- // Run the 5 quick scan engines via orchestrator
204
- const scanResult = await runScanEngines(projectPath, {
205
- maxFiles: 500,
206
- onProgress: opts.verbose ? (phase, pct) => {
207
- if (spinner) spinner.text = `${phase}: ${pct}%`;
208
- } : undefined,
209
- });
210
583
 
211
- const duration = Date.now() - startTime;
212
-
213
- if (spinner) {
214
- spinner.succeed(`Quick scan complete (${scanResult.findings.length} findings in ${formatDuration(duration)})`);
584
+ // Try to import new unified output system (may not be compiled yet)
585
+ let buildVerdictOutput, normalizeFinding, formatStandardOutput, formatScanOutputFromUnified, getExitCodeFromUnified, CacheManager;
586
+ let useUnifiedOutput = false;
587
+
588
+ try {
589
+ const outputContract = require('../../dist/lib/cli/output-contract');
590
+ buildVerdictOutput = outputContract.buildVerdictOutput;
591
+ normalizeFinding = outputContract.normalizeFinding;
592
+ formatStandardOutput = outputContract.formatStandardOutput;
593
+
594
+ const unifiedOutput = require('./lib/unified-output');
595
+ formatScanOutputFromUnified = unifiedOutput.formatScanOutput;
596
+ getExitCodeFromUnified = unifiedOutput.getExitCode;
597
+
598
+ const cacheModule = require('../../dist/lib/cli/cache-manager');
599
+ CacheManager = cacheModule.CacheManager;
600
+ useUnifiedOutput = true;
601
+ } catch (error) {
602
+ // Fallback to old system if new one not available
603
+ if (opts.verbose) {
604
+ console.warn('Unified output system not available, using enhanced format');
605
+ }
606
+ useUnifiedOutput = false;
215
607
  }
216
608
 
217
- // Normalize findings
218
- const findings = scanResult.findings.map((f, i) => ({
219
- id: f.id || `finding-${i}`,
220
- ruleId: f.type || f.category,
221
- category: f.category || 'CodeQuality',
222
- severity: normalizeSeverity(f.severity),
223
- title: f.title || f.message,
224
- message: f.message || f.title,
225
- file: f.file || '',
226
- line: f.line || 1,
227
- confidence: f.confidence || 'medium',
228
- engine: f.engine,
229
- fixHint: f.fixHint,
230
- }));
609
+ // Initialize cache if available
610
+ let cache = null;
611
+ let cached = false;
612
+ let cachedResult = null;
231
613
 
232
- // Calculate verdict
233
- const blockers = findings.filter(f => f.severity === 'critical');
234
- const warnings = findings.filter(f => f.severity === 'warning');
235
- const verdict = blockers.length > 0 ? 'BLOCK' : warnings.length > 0 ? 'WARN' : 'PASS';
236
- const score = calculateScore({
237
- critical: blockers.length,
238
- high: 0,
239
- medium: warnings.length,
240
- low: findings.length - blockers.length - warnings.length
614
+ if (CacheManager) {
615
+ cache = new CacheManager(projectPath);
616
+ const cacheKey = 'scan';
617
+
618
+ // Compute project hash for caching
619
+ const sourceFiles = await findSourceFiles(projectPath);
620
+ const projectHash = await cache.computeProjectHash(sourceFiles, { layers, baseUrl: opts.baseUrl });
621
+
622
+ // Check cache
623
+ if (!opts.verbose) {
624
+ cachedResult = await cache.get(cacheKey, projectHash);
625
+ if (cachedResult && buildVerdictOutput) {
626
+ cached = true;
627
+ // Use cached result
628
+ const verdict = buildVerdictOutput(cachedResult.findings, cachedResult.timings, true);
629
+ const output = formatStandardOutput(verdict, cachedResult.findings, cachedResult.scanId, projectPath, {
630
+ version: require('../../package.json').version || '1.0.0',
631
+ nodeVersion: process.version,
632
+ platform: process.platform,
633
+ });
634
+
635
+ if (opts.json) {
636
+ console.log(JSON.stringify(output, null, 2));
637
+ return getExitCode(verdict);
638
+ }
639
+
640
+ console.log(formatScanOutput({ verdict, findings: cachedResult.findings, cached }, { verbose: opts.verbose }));
641
+ return getExitCode(verdict);
642
+ }
643
+ }
644
+ }
645
+
646
+ // Start scanning with enhanced spinner
647
+ const timings = { discovery: 0, analysis: 0, verification: 0, detection: 0, total: 0 };
648
+ timings.discovery = Date.now();
649
+
650
+ spinner = new Spinner({ color: colors.primary });
651
+ spinner.start('Analyzing codebase...');
652
+
653
+ const result = await scanRouteIntegrity({
654
+ projectPath,
655
+ layers,
656
+ baseUrl: opts.baseUrl,
657
+ verbose: opts.verbose,
658
+ onProgress: opts.verbose ? (phase, progress) => {
659
+ spinner.succeed(`${phase}: ${Math.round(progress)}%`);
660
+ if (progress < 100) spinner.start(`Running ${phase}...`);
661
+ } : undefined,
241
662
  });
242
663
 
243
- // ═══════════════════════════════════════════════════════════════════════════
244
- // OUTPUT
245
- // ═══════════════════════════════════════════════════════════════════════════
664
+ timings.analysis = Date.now() - timings.discovery;
246
665
 
247
- // JSON output
248
- if (opts.json) {
249
- const jsonOutput = {
250
- version: "1.0.0",
251
- command: "scan",
252
- mode: "quick",
253
- engines: SCAN_ENGINES,
254
- verdict,
255
- score,
256
- summary: {
257
- total: findings.length,
258
- blockers: blockers.length,
259
- warnings: warnings.length,
260
- filesScanned: scanResult.stats.filesScanned,
261
- duration,
262
- },
263
- findings: findings.map(f => ({
264
- id: f.id,
265
- category: f.category,
266
- severity: f.severity,
267
- title: f.title,
268
- message: f.message,
269
- file: f.file,
270
- line: f.line,
271
- engine: f.engine,
272
- })),
273
- timestamp: new Date().toISOString(),
274
- };
275
- console.log(JSON.stringify(jsonOutput, null, 2));
276
- return verdictToExitCode(verdict);
666
+ // Run new detection engines (Dead UI, Billing Bypass, Fake Success)
667
+ let detectionFindings = [];
668
+ try {
669
+ startSpinner('Running detection engines...');
670
+ const detectionStart = Date.now();
671
+
672
+ // Dynamic import for TypeScript detection engines
673
+ const { DeadUIDetector } = require('../../dist/engines/detection/dead-ui-detector');
674
+ const { BillingBypassDetector } = require('../../dist/engines/detection/billing-bypass-detector');
675
+ const { FakeSuccessDetector } = require('../../dist/engines/detection/fake-success-detector');
676
+
677
+ const exclude = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '_archive'];
678
+
679
+ // Run detectors in parallel
680
+ const [deadUIResult, billingResult, fakeSuccessResult] = await Promise.all([
681
+ new DeadUIDetector(projectPath).scan({ exclude }),
682
+ new BillingBypassDetector(projectPath).scan({ exclude }),
683
+ new FakeSuccessDetector(projectPath).scan({ exclude }),
684
+ ]);
685
+
686
+ // Convert to normalized findings format
687
+ for (const finding of deadUIResult.findings) {
688
+ detectionFindings.push({
689
+ id: finding.id,
690
+ ruleId: finding.type,
691
+ category: 'DEAD_UI',
692
+ severity: finding.severity,
693
+ title: finding.message,
694
+ description: finding.suggestion,
695
+ file: finding.file,
696
+ line: finding.line,
697
+ evidence: finding.evidence,
698
+ autofixAvailable: false,
699
+ verdict: 'FAIL',
700
+ });
701
+ }
702
+
703
+ for (const finding of billingResult.findings) {
704
+ detectionFindings.push({
705
+ id: finding.id,
706
+ ruleId: finding.type,
707
+ category: 'BILLING',
708
+ severity: finding.severity,
709
+ title: finding.message,
710
+ description: finding.suggestion,
711
+ file: finding.file,
712
+ line: finding.line,
713
+ evidence: finding.evidence,
714
+ autofixAvailable: false,
715
+ verdict: 'FAIL',
716
+ });
717
+ }
718
+
719
+ for (const finding of fakeSuccessResult.findings) {
720
+ detectionFindings.push({
721
+ id: finding.id,
722
+ ruleId: finding.type,
723
+ category: 'FAKE_SUCCESS',
724
+ severity: finding.severity,
725
+ title: finding.message,
726
+ description: finding.suggestion,
727
+ file: finding.file,
728
+ line: finding.line,
729
+ evidence: finding.evidence,
730
+ autofixAvailable: false,
731
+ verdict: 'FAIL',
732
+ });
733
+ }
734
+
735
+ timings.detection = Date.now() - detectionStart;
736
+ spinner.succeed(`Detection complete (${detectionFindings.length} findings)`);
737
+ } catch (detectionError) {
738
+ // Detection engines not compiled yet - continue without them
739
+ if (opts.verbose) {
740
+ console.log(` ${ansi.dim}Detection engines not available: ${detectionError.message}${ansi.reset}`);
741
+ }
742
+ spinner.warn('Detection skipped (not compiled)');
277
743
  }
278
744
 
279
- // SARIF output
745
+ timings.verification = Date.now() - timings.analysis - timings.discovery;
746
+ timings.total = Date.now() - startTime;
747
+
748
+ spinner.succeed('Analysis complete');
749
+
750
+ const { report, outputPaths } = result;
751
+
752
+ // Use new unified output if available, otherwise fallback to enhanced format
753
+ if (useUnifiedOutput && buildVerdictOutput && normalizeFinding && formatScanOutputFromUnified) {
754
+ // Normalize findings with stable IDs
755
+ const existingIDs = new Set();
756
+ const normalizedFindings = [];
757
+
758
+ // Normalize route integrity findings
759
+ if (report.shipBlockers) {
760
+ for (let i = 0; i < report.shipBlockers.length; i++) {
761
+ const blocker = report.shipBlockers[i];
762
+ const category = blocker.category || 'ROUTE';
763
+ const normalized = normalizeFinding(blocker, category, i, existingIDs);
764
+ normalizedFindings.push(normalized);
765
+ }
766
+ }
767
+
768
+ // Normalize Reality Sniff findings if present
769
+ if (report.realitySniffFindings) {
770
+ for (let i = 0; i < report.realitySniffFindings.length; i++) {
771
+ const finding = report.realitySniffFindings[i];
772
+ const category = finding.ruleId?.startsWith('auth') ? 'AUTH' : 'REALITY';
773
+ const normalized = normalizeFinding(finding, category, normalizedFindings.length, existingIDs);
774
+ normalizedFindings.push(normalized);
775
+ }
776
+ }
777
+
778
+ // Add detection engine findings (Dead UI, Billing, Fake Success)
779
+ for (const finding of detectionFindings) {
780
+ normalizedFindings.push(finding);
781
+ }
782
+
783
+ // Build verdict
784
+ const verdict = buildVerdictOutput(normalizedFindings, timings, false);
785
+ const scanId = `scan_${Date.now()}`;
786
+
787
+ // Cache result
788
+ if (cache) {
789
+ const sourceFiles = await findSourceFiles(projectPath);
790
+ const projectHash = await cache.computeProjectHash(sourceFiles, { layers, baseUrl: opts.baseUrl });
791
+ await cache.set('scan', projectHash, {
792
+ findings: normalizedFindings,
793
+ timings,
794
+ scanId,
795
+ }, {
796
+ filesScanned: sourceFiles.length,
797
+ findings: normalizedFindings.length,
798
+ duration: timings.total,
799
+ });
800
+ }
801
+
802
+ // Build standard output
803
+ const standardOutput = formatStandardOutput(verdict, normalizedFindings, scanId, projectPath, {
804
+ version: require('../../package.json').version || '1.0.0',
805
+ nodeVersion: process.version,
806
+ platform: process.platform,
807
+ });
808
+
809
+ // JSON output mode
810
+ if (opts.json) {
811
+ console.log(JSON.stringify(standardOutput, null, 2));
812
+ return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
813
+ }
814
+
815
+ // SARIF output mode
280
816
  if (opts.sarif) {
281
- const sarif = formatSARIF(findings, {
817
+ const sarif = formatSARIF(normalizedFindings, {
282
818
  projectPath,
283
819
  version: require('../../package.json').version || '1.0.0'
284
820
  });
285
821
  console.log(JSON.stringify(sarif, null, 2));
286
- return verdictToExitCode(verdict);
822
+ return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
287
823
  }
288
-
289
- // Human-readable output
290
- console.log();
291
-
292
- // Verdict
293
- const verdictColor = verdict === 'PASS' ? colors.success : verdict === 'WARN' ? colors.warning : colors.error;
294
- const verdictIcon = verdict === 'PASS' ? '✓' : verdict === 'WARN' ? '⚠' : '✗';
295
-
296
- console.log(` ${verdictColor}${ansi.bold}${verdictIcon} ${verdict}${ansi.reset} ${ansi.dim}Score: ${score}/100${ansi.reset}`);
297
- console.log(` ${ansi.dim}${findings.length} findings (${blockers.length} blockers, ${warnings.length} warnings)${ansi.reset}`);
298
- console.log(` ${ansi.dim}${scanResult.stats.filesScanned} files scanned in ${formatDuration(duration)}${ansi.reset}`);
299
- console.log();
300
-
301
- // Show blockers
302
- if (blockers.length > 0) {
303
- console.log(` ${colors.error}${ansi.bold}Blockers (${blockers.length})${ansi.reset}`);
304
- for (const f of blockers.slice(0, 5)) {
305
- console.log(` ${colors.error}●${ansi.reset} ${f.title}`);
306
- if (f.file) console.log(` ${ansi.dim}${f.file}:${f.line}${ansi.reset}`);
307
- }
308
- if (blockers.length > 5) {
309
- console.log(` ${ansi.dim}... and ${blockers.length - 5} more${ansi.reset}`);
310
- }
824
+
825
+ // ═══════════════════════════════════════════════════════════════════════════
826
+ // ENHANCED OUTPUT
827
+ // ═══════════════════════════════════════════════════════════════════════════
828
+
829
+ // Use enhanced output formatter (from scan-output.js)
830
+ const resultForOutput = {
831
+ verdict,
832
+ findings: normalizedFindings,
833
+ layers: report.layers || [],
834
+ coverage: report.coverageMap,
835
+ breakdown: report.score?.breakdown,
836
+ timings,
837
+ cached,
838
+ };
839
+ console.log(formatScanOutput(resultForOutput, { verbose: opts.verbose }));
840
+
841
+ // Additional details if verbose
842
+ if (opts.verbose) {
843
+ printBreakdown(report.score.breakdown);
844
+ printCoverageMap(report.coverageMap);
845
+ printLayers(report.layers);
846
+
847
+ printSection('REPORTS', '📄');
311
848
  console.log();
312
- }
313
-
314
- // Show warnings (verbose only)
315
- if (warnings.length > 0 && opts.verbose) {
316
- console.log(` ${colors.warning}${ansi.bold}Warnings (${warnings.length})${ansi.reset}`);
317
- for (const f of warnings.slice(0, 5)) {
318
- console.log(` ${colors.warning}◐${ansi.reset} ${f.title}`);
319
- if (f.file) console.log(` ${ansi.dim}${f.file}:${f.line}${ansi.reset}`);
849
+ console.log(` ${c.cyan}${outputPaths.md}${c.reset}`);
850
+ console.log(` ${c.dim}${outputPaths.json}${c.reset}`);
851
+ if (outputPaths.sarif) {
852
+ console.log(` ${c.dim}${outputPaths.sarif}${c.reset}`);
320
853
  }
321
- if (warnings.length > 5) {
322
- console.log(` ${ansi.dim}... and ${warnings.length - 5} more${ansi.reset}`);
323
- }
324
- console.log();
325
- }
326
-
327
- // Pro upsell
328
- if (!opts.quiet) {
329
- console.log(` ${ansi.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${ansi.reset}`);
330
- console.log(` ${ansi.dim}Quick scan ran ${SCAN_ENGINES.length} engines.${ansi.reset}`);
331
- console.log(` ${colors.accent}For comprehensive analysis (17+ engines + validators):${ansi.reset}`);
332
- console.log(` ${colors.success}vibecheck ship${ansi.reset} ${ansi.dim}[PRO]${ansi.reset}`);
333
- console.log();
334
854
  }
335
-
336
- // Emit completion
337
- emitScanComplete(projectPath, verdict === 'PASS' ? 'success' : 'failure', {
338
- score,
339
- issueCount: findings.length,
340
- durationMs: duration,
341
- });
342
855
 
343
- // Save results
856
+ // ═══════════════════════════════════════════════════════════════════════════
857
+ // SAVE RESULTS
858
+ // ═══════════════════════════════════════════════════════════════════════════
859
+
344
860
  if (opts.save) {
345
861
  const resultsDir = path.join(projectPath, '.vibecheck', 'results');
346
862
  if (!fs.existsSync(resultsDir)) {
347
863
  fs.mkdirSync(resultsDir, { recursive: true });
348
864
  }
349
865
 
350
- fs.writeFileSync(
351
- path.join(resultsDir, 'latest.json'),
352
- JSON.stringify({
353
- verdict,
354
- score,
355
- findings,
356
- timestamp: new Date().toISOString(),
357
- mode: 'quick',
358
- engines: SCAN_ENGINES,
359
- stats: scanResult.stats,
360
- }, null, 2)
361
- );
866
+ // Save latest.json
867
+ const latestPath = path.join(resultsDir, 'latest.json');
868
+ fs.writeFileSync(latestPath, JSON.stringify(standardOutput, null, 2));
869
+
870
+ // Save timestamped copy
871
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
872
+ const historyDir = path.join(resultsDir, 'history');
873
+ if (!fs.existsSync(historyDir)) {
874
+ fs.mkdirSync(historyDir, { recursive: true });
875
+ }
876
+ fs.writeFileSync(path.join(historyDir, `scan-${timestamp}.json`), JSON.stringify(standardOutput, null, 2));
877
+
878
+ if (!opts.json) {
879
+ console.log(`\n ${ansi.dim}Results saved to: ${latestPath}${ansi.reset}`);
880
+ }
362
881
  }
363
882
 
364
- return verdictToExitCode(verdict);
883
+ // ═══════════════════════════════════════════════════════════════════════════
884
+ // AUTOFIX MODE - Generate missions
885
+ // ═══════════════════════════════════════════════════════════════════════════
886
+
887
+ if (opts.autofix && normalizedFindings.length > 0) {
888
+ // Check entitlement
889
+ const entitlementsV2 = require("./lib/entitlements-v2");
890
+ const access = await entitlementsV2.enforce("scan.autofix", {
891
+ projectPath,
892
+ silent: false,
893
+ });
894
+
895
+ if (!access.allowed) {
896
+ console.log(`\n ${colors.warning}${icons.warning}${ansi.reset} ${ansi.bold}--autofix requires STARTER plan${ansi.reset}`);
897
+ console.log(` ${ansi.dim}Upgrade at: https://vibecheckai.dev/pricing${ansi.reset}`);
898
+ console.log(` ${ansi.dim}Scan results saved. Run 'vibecheck fix' for manual mission generation.${ansi.reset}\n`);
899
+ } else {
900
+ console.log(`\n ${colors.accent}${icons.lightning}${ansi.reset} ${ansi.bold}Generating AI missions...${ansi.reset}\n`);
901
+
902
+ const { missions, summary } = await generateMissions(normalizedFindings, projectPath, opts);
903
+
904
+ console.log(` ${colors.success}✓${ansi.reset} Generated ${missions.length} missions`);
905
+ console.log(` ${ansi.dim}Critical: ${summary.bySeverity.critical}${ansi.reset}`);
906
+ console.log(` ${ansi.dim}High: ${summary.bySeverity.high}${ansi.reset}`);
907
+ console.log(` ${ansi.dim}Medium: ${summary.bySeverity.medium}${ansi.reset}`);
908
+ console.log(` ${ansi.dim}Low: ${summary.bySeverity.low}${ansi.reset}`);
909
+ console.log();
910
+ console.log(` ${ansi.dim}Missions saved to: .vibecheck/missions/${ansi.reset}`);
911
+ console.log(` ${ansi.dim}Open MISSIONS.md for prompts to use with your AI assistant.${ansi.reset}`);
912
+ console.log();
913
+ }
914
+ }
915
+
916
+ // Emit audit event for scan complete
917
+ emitScanComplete(projectPath, verdict.verdict === 'PASS' ? 'success' : 'failure', {
918
+ score: report.score?.overall || (verdict.verdict === 'PASS' ? 100 : 50),
919
+ grade: report.score?.grade || (verdict.verdict === 'PASS' ? 'A' : 'F'),
920
+ issueCount: verdict.summary.blockers,
921
+ durationMs: timings.total,
922
+ });
923
+
924
+ return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
925
+ } else {
926
+ // Legacy fallback output when unified output system isn't available
927
+ const findings = [...(report.shipBlockers || []), ...detectionFindings];
928
+ const criticalCount = findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length;
929
+ const warningCount = findings.filter(f => f.severity === 'warning' || f.severity === 'WARN').length;
930
+
931
+ const verdict = criticalCount > 0 ? 'BLOCK' : warningCount > 0 ? 'WARN' : 'SHIP';
932
+
933
+ // Use enhanced output formatter for legacy fallback
934
+ const severityCounts = {
935
+ critical: criticalCount,
936
+ high: 0,
937
+ medium: warningCount,
938
+ low: findings.length - criticalCount - warningCount,
939
+ };
940
+ const score = calculateScore(severityCounts);
941
+
942
+ const result = {
943
+ verdict: { verdict, score },
944
+ findings: findings.map(f => ({
945
+ severity: f.severity === 'critical' || f.severity === 'BLOCK' ? 'critical' :
946
+ f.severity === 'warning' || f.severity === 'WARN' ? 'medium' : 'low',
947
+ category: f.category || 'ROUTE',
948
+ title: f.title || f.message,
949
+ message: f.message || f.title,
950
+ file: f.file,
951
+ line: f.line,
952
+ fix: f.fixSuggestion,
953
+ })),
954
+ layers: [],
955
+ timings,
956
+ };
957
+
958
+ console.log(formatScanOutput(result, { verbose: opts.verbose }));
959
+
960
+ // Emit audit event
961
+ emitScanComplete(projectPath, verdict === 'SHIP' ? 'success' : 'failure', {
962
+ score: verdict === 'SHIP' ? 100 : verdict === 'WARN' ? 70 : 40,
963
+ issueCount: criticalCount + warningCount,
964
+ durationMs: timings.total,
965
+ });
966
+
967
+ return verdict === 'SHIP' ? 0 : verdict === 'WARN' ? 1 : 2;
968
+ }
365
969
 
366
970
  } catch (error) {
367
971
  if (spinner) {
368
972
  spinner.fail(`Scan failed: ${error.message}`);
369
973
  }
370
974
 
371
- console.error(`\n ${colors.error}✗${ansi.reset} ${ansi.bold}Error:${ansi.reset} ${error.message}`);
372
- if (opts.verbose) {
373
- console.error(` ${ansi.dim}${error.stack}${ansi.reset}`);
374
- }
975
+ // Use enhanced error handling
976
+ const exitCode = printError(error, 'Scan');
375
977
 
978
+ // Emit audit event for scan error
376
979
  emitScanComplete(projectPath, 'error', {
377
980
  errorCode: error.code || 'SCAN_ERROR',
378
981
  errorMessage: error.message,
379
982
  durationMs: Date.now() - startTime,
380
983
  });
381
984
 
382
- return EXIT.INTERNAL_ERROR;
985
+ return exitCode;
383
986
  }
384
987
  }
385
988
 
386
- // Helper to normalize severity
387
- function normalizeSeverity(sev) {
388
- if (!sev) return 'info';
389
- const s = String(sev).toLowerCase();
390
- if (s === 'block' || s === 'critical' || s === 'high') return 'critical';
391
- if (s === 'warn' || s === 'warning' || s === 'medium') return 'warning';
392
- return 'info';
989
+ // Helper function to find source files for cache hash
990
+ async function findSourceFiles(projectPath) {
991
+ const files = [];
992
+ const fs = require('fs');
993
+ const path = require('path');
994
+
995
+ async function walk(dir) {
996
+ try {
997
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
998
+ for (const entry of entries) {
999
+ const fullPath = path.join(dir, entry.name);
1000
+ if (entry.isDirectory()) {
1001
+ if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
1002
+ await walk(fullPath);
1003
+ }
1004
+ } else if (entry.isFile()) {
1005
+ const ext = path.extname(entry.name).toLowerCase();
1006
+ if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
1007
+ files.push(fullPath);
1008
+ }
1009
+ }
1010
+ }
1011
+ } catch {
1012
+ // Skip inaccessible directories
1013
+ }
1014
+ }
1015
+
1016
+ await walk(projectPath);
1017
+ return files;
393
1018
  }
394
1019
 
395
- // Export
1020
+ // Export with error handling wrapper
396
1021
  module.exports = {
397
1022
  runScan: withErrorHandling(runScan, "Scan failed"),
398
1023
  };