@vibecheckai/cli 2.8.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/README.md +8 -8
  2. package/bin/_deprecations.js +35 -0
  3. package/bin/_router.js +46 -0
  4. package/bin/cli-hygiene.js +241 -0
  5. package/bin/guardrail.js +834 -0
  6. package/bin/runners/cli-utils.js +1070 -0
  7. package/bin/runners/context/ai-task-decomposer.js +337 -0
  8. package/bin/runners/context/analyzer.js +462 -0
  9. package/bin/runners/context/api-contracts.js +427 -0
  10. package/bin/runners/context/context-diff.js +342 -0
  11. package/bin/runners/context/context-pruner.js +291 -0
  12. package/bin/runners/context/dependency-graph.js +414 -0
  13. package/bin/runners/context/generators/claude.js +107 -0
  14. package/bin/runners/context/generators/codex.js +108 -0
  15. package/bin/runners/context/generators/copilot.js +119 -0
  16. package/bin/runners/context/generators/cursor.js +514 -0
  17. package/bin/runners/context/generators/mcp.js +151 -0
  18. package/bin/runners/context/generators/windsurf.js +180 -0
  19. package/bin/runners/context/git-context.js +302 -0
  20. package/bin/runners/context/index.js +1042 -0
  21. package/bin/runners/context/insights.js +173 -0
  22. package/bin/runners/context/mcp-server/generate-rules.js +337 -0
  23. package/bin/runners/context/mcp-server/index.js +1176 -0
  24. package/bin/runners/context/mcp-server/package.json +24 -0
  25. package/bin/runners/context/memory.js +200 -0
  26. package/bin/runners/context/monorepo.js +215 -0
  27. package/bin/runners/context/multi-repo-federation.js +404 -0
  28. package/bin/runners/context/patterns.js +253 -0
  29. package/bin/runners/context/proof-context.js +972 -0
  30. package/bin/runners/context/security-scanner.js +303 -0
  31. package/bin/runners/context/semantic-search.js +350 -0
  32. package/bin/runners/context/shared.js +264 -0
  33. package/bin/runners/context/team-conventions.js +310 -0
  34. package/bin/runners/lib/ai-bridge.js +416 -0
  35. package/bin/runners/lib/analysis-core.js +271 -0
  36. package/bin/runners/lib/analyzers.js +541 -0
  37. package/bin/runners/lib/audit-bridge.js +391 -0
  38. package/bin/runners/lib/auth-truth.js +193 -0
  39. package/bin/runners/lib/auth.js +215 -0
  40. package/bin/runners/lib/backup.js +62 -0
  41. package/bin/runners/lib/billing.js +107 -0
  42. package/bin/runners/lib/claims.js +118 -0
  43. package/bin/runners/lib/cli-ui.js +540 -0
  44. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  45. package/bin/runners/lib/compliance-bridge.js +165 -0
  46. package/bin/runners/lib/contracts/auth-contract.js +194 -0
  47. package/bin/runners/lib/contracts/env-contract.js +178 -0
  48. package/bin/runners/lib/contracts/external-contract.js +198 -0
  49. package/bin/runners/lib/contracts/guard.js +168 -0
  50. package/bin/runners/lib/contracts/index.js +89 -0
  51. package/bin/runners/lib/contracts/plan-validator.js +311 -0
  52. package/bin/runners/lib/contracts/route-contract.js +192 -0
  53. package/bin/runners/lib/detect.js +89 -0
  54. package/bin/runners/lib/doctor/autofix.js +254 -0
  55. package/bin/runners/lib/doctor/index.js +37 -0
  56. package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
  57. package/bin/runners/lib/doctor/modules/index.js +46 -0
  58. package/bin/runners/lib/doctor/modules/network.js +250 -0
  59. package/bin/runners/lib/doctor/modules/project.js +312 -0
  60. package/bin/runners/lib/doctor/modules/runtime.js +224 -0
  61. package/bin/runners/lib/doctor/modules/security.js +348 -0
  62. package/bin/runners/lib/doctor/modules/system.js +213 -0
  63. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
  64. package/bin/runners/lib/doctor/reporter.js +262 -0
  65. package/bin/runners/lib/doctor/service.js +262 -0
  66. package/bin/runners/lib/doctor/types.js +113 -0
  67. package/bin/runners/lib/doctor/ui.js +263 -0
  68. package/bin/runners/lib/doctor-enhanced.js +233 -0
  69. package/bin/runners/lib/doctor-v2.js +608 -0
  70. package/bin/runners/lib/enforcement.js +72 -0
  71. package/bin/runners/lib/enterprise-detect.js +603 -0
  72. package/bin/runners/lib/enterprise-init.js +942 -0
  73. package/bin/runners/lib/entitlements-v2.js +381 -0
  74. package/bin/runners/lib/entitlements.generated.js +0 -0
  75. package/bin/runners/lib/entitlements.js +332 -0
  76. package/bin/runners/lib/env-template.js +66 -0
  77. package/bin/runners/lib/env.js +189 -0
  78. package/bin/runners/lib/error-handler.js +320 -0
  79. package/bin/runners/lib/firewall-prompt.js +50 -0
  80. package/bin/runners/lib/graph/graph-builder.js +265 -0
  81. package/bin/runners/lib/graph/html-renderer.js +413 -0
  82. package/bin/runners/lib/graph/index.js +32 -0
  83. package/bin/runners/lib/graph/runtime-collector.js +215 -0
  84. package/bin/runners/lib/graph/static-extractor.js +518 -0
  85. package/bin/runners/lib/init-wizard.js +308 -0
  86. package/bin/runners/lib/json-output.js +76 -0
  87. package/bin/runners/lib/llm.js +75 -0
  88. package/bin/runners/lib/meter.js +61 -0
  89. package/bin/runners/lib/missions/evidence.js +126 -0
  90. package/bin/runners/lib/missions/plan.js +69 -0
  91. package/bin/runners/lib/missions/templates.js +147 -0
  92. package/bin/runners/lib/patch.js +40 -0
  93. package/bin/runners/lib/permissions/auth-model.js +213 -0
  94. package/bin/runners/lib/permissions/idor-prover.js +205 -0
  95. package/bin/runners/lib/permissions/index.js +45 -0
  96. package/bin/runners/lib/permissions/matrix-builder.js +198 -0
  97. package/bin/runners/lib/pkgjson.js +28 -0
  98. package/bin/runners/lib/preflight.js +142 -0
  99. package/bin/runners/lib/reality-findings.js +84 -0
  100. package/bin/runners/lib/redact.js +29 -0
  101. package/bin/runners/lib/replay/capsule-manager.js +154 -0
  102. package/bin/runners/lib/replay/index.js +263 -0
  103. package/bin/runners/lib/replay/player.js +348 -0
  104. package/bin/runners/lib/replay/recorder.js +331 -0
  105. package/bin/runners/lib/report-engine.js +447 -0
  106. package/bin/runners/lib/report-html.js +1117 -0
  107. package/bin/runners/lib/report-templates.js +964 -0
  108. package/bin/runners/lib/route-detection.js +1140 -0
  109. package/bin/runners/lib/route-truth.js +477 -0
  110. package/bin/runners/lib/sandbox/index.js +59 -0
  111. package/bin/runners/lib/sandbox/proof-chain.js +399 -0
  112. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
  113. package/bin/runners/lib/sandbox/worktree.js +174 -0
  114. package/bin/runners/lib/scan-cache.js +330 -0
  115. package/bin/runners/lib/scan-output-schema.js +344 -0
  116. package/bin/runners/lib/score-history.js +282 -0
  117. package/bin/runners/lib/security-bridge.js +249 -0
  118. package/bin/runners/lib/server-usage.js +513 -0
  119. package/bin/runners/lib/share-pack.js +239 -0
  120. package/bin/runners/lib/snippets.js +67 -0
  121. package/bin/runners/lib/truth.js +667 -0
  122. package/bin/runners/lib/unified-output.js +189 -0
  123. package/bin/runners/lib/validate-patch.js +156 -0
  124. package/bin/runners/lib/verification.js +345 -0
  125. package/bin/runners/reality/engine.js +917 -0
  126. package/bin/runners/reality/flows.js +122 -0
  127. package/bin/runners/reality/report.js +378 -0
  128. package/bin/runners/reality/session.js +193 -0
  129. package/bin/runners/runAIAgent.js +2 -0
  130. package/bin/runners/runAudit.js +2 -0
  131. package/bin/runners/runAuth.js +106 -0
  132. package/bin/runners/runAutopilot.js +2 -0
  133. package/bin/runners/runBadge.js +2 -0
  134. package/bin/runners/runCertify.js +2 -0
  135. package/bin/runners/runClaimVerifier.js +483 -0
  136. package/bin/runners/runContext.js +56 -0
  137. package/bin/runners/runContextCompiler.js +385 -0
  138. package/bin/runners/runCtx.js +187 -0
  139. package/bin/runners/runCtxGuard.js +176 -0
  140. package/bin/runners/runCtxSync.js +116 -0
  141. package/bin/runners/runDashboard.js +10 -0
  142. package/bin/runners/runDoctor.js +245 -0
  143. package/bin/runners/runEnhancedShip.js +2 -0
  144. package/bin/runners/runFix.js +735 -0
  145. package/bin/runners/runFixPacks.js +2 -0
  146. package/bin/runners/runGate.js +17 -0
  147. package/bin/runners/runGraph.js +283 -0
  148. package/bin/runners/runInit.js +260 -0
  149. package/bin/runners/runInitGha.js +101 -0
  150. package/bin/runners/runInstall.js +76 -0
  151. package/bin/runners/runInteractive.js +388 -0
  152. package/bin/runners/runLaunch.js +2 -0
  153. package/bin/runners/runMcp.js +19 -0
  154. package/bin/runners/runMdc.js +2 -0
  155. package/bin/runners/runMissionGenerator.js +282 -0
  156. package/bin/runners/runNaturalLanguage.js +3 -0
  157. package/bin/runners/runPR.js +96 -0
  158. package/bin/runners/runPermissions.js +290 -0
  159. package/bin/runners/runPromptFirewall.js +211 -0
  160. package/bin/runners/runProof.js +2 -0
  161. package/bin/runners/runProve.js +392 -0
  162. package/bin/runners/runReality.js +489 -0
  163. package/bin/runners/runRealitySniff.js +2 -0
  164. package/bin/runners/runReplay.js +469 -0
  165. package/bin/runners/runReport.js +478 -0
  166. package/bin/runners/runScan.js +835 -0
  167. package/bin/runners/runShare.js +34 -0
  168. package/bin/runners/runShip.js +1062 -0
  169. package/bin/runners/runStatus.js +136 -0
  170. package/bin/runners/runTruthpack.js +634 -0
  171. package/bin/runners/runUpgrade.js +2 -0
  172. package/bin/runners/runValidate.js +2 -0
  173. package/bin/runners/runVerifyAgentOutput.js +2 -0
  174. package/bin/runners/runWatch.js +230 -0
  175. package/bin/runners/utils.js +360 -0
  176. package/bin/scan.js +612 -0
  177. package/bin/vibecheck.js +834 -0
  178. package/package.json +11 -11
  179. package/dist/autopatch/verified-autopatch.d.ts +0 -111
  180. package/dist/autopatch/verified-autopatch.d.ts.map +0 -1
  181. package/dist/autopatch/verified-autopatch.js +0 -503
  182. package/dist/autopatch/verified-autopatch.js.map +0 -1
  183. package/dist/bundles/index.js +0 -8
  184. package/dist/bundles/vibecheck-core.js +0 -25799
  185. package/dist/bundles/vibecheck-security.js +0 -208693
  186. package/dist/bundles/vibecheck-ship.js +0 -2318
  187. package/dist/commands/baseline.d.ts +0 -7
  188. package/dist/commands/baseline.d.ts.map +0 -1
  189. package/dist/commands/baseline.js +0 -79
  190. package/dist/commands/baseline.js.map +0 -1
  191. package/dist/commands/cache.d.ts +0 -13
  192. package/dist/commands/cache.d.ts.map +0 -1
  193. package/dist/commands/cache.js +0 -165
  194. package/dist/commands/cache.js.map +0 -1
  195. package/dist/commands/checkpoint.d.ts +0 -8
  196. package/dist/commands/checkpoint.d.ts.map +0 -1
  197. package/dist/commands/checkpoint.js +0 -35
  198. package/dist/commands/checkpoint.js.map +0 -1
  199. package/dist/commands/doctor.d.ts +0 -17
  200. package/dist/commands/doctor.d.ts.map +0 -1
  201. package/dist/commands/doctor.js +0 -226
  202. package/dist/commands/doctor.js.map +0 -1
  203. package/dist/commands/evidence.d.ts +0 -45
  204. package/dist/commands/evidence.d.ts.map +0 -1
  205. package/dist/commands/evidence.js +0 -197
  206. package/dist/commands/evidence.js.map +0 -1
  207. package/dist/commands/explain.d.ts +0 -8
  208. package/dist/commands/explain.d.ts.map +0 -1
  209. package/dist/commands/explain.js +0 -52
  210. package/dist/commands/explain.js.map +0 -1
  211. package/dist/commands/fix-consolidated.d.ts +0 -19
  212. package/dist/commands/fix-consolidated.d.ts.map +0 -1
  213. package/dist/commands/fix-consolidated.js +0 -165
  214. package/dist/commands/fix-consolidated.js.map +0 -1
  215. package/dist/commands/index.d.ts +0 -8
  216. package/dist/commands/index.d.ts.map +0 -1
  217. package/dist/commands/index.js +0 -15
  218. package/dist/commands/index.js.map +0 -1
  219. package/dist/commands/init.d.ts +0 -8
  220. package/dist/commands/init.d.ts.map +0 -1
  221. package/dist/commands/init.js +0 -125
  222. package/dist/commands/init.js.map +0 -1
  223. package/dist/commands/launcher.d.ts +0 -10
  224. package/dist/commands/launcher.d.ts.map +0 -1
  225. package/dist/commands/launcher.js +0 -174
  226. package/dist/commands/launcher.js.map +0 -1
  227. package/dist/commands/on.d.ts +0 -8
  228. package/dist/commands/on.d.ts.map +0 -1
  229. package/dist/commands/on.js +0 -123
  230. package/dist/commands/on.js.map +0 -1
  231. package/dist/commands/replay.d.ts +0 -8
  232. package/dist/commands/replay.d.ts.map +0 -1
  233. package/dist/commands/replay.js +0 -52
  234. package/dist/commands/replay.js.map +0 -1
  235. package/dist/commands/scan-consolidated.d.ts +0 -61
  236. package/dist/commands/scan-consolidated.d.ts.map +0 -1
  237. package/dist/commands/scan-consolidated.js +0 -243
  238. package/dist/commands/scan-consolidated.js.map +0 -1
  239. package/dist/commands/scan-secrets.d.ts +0 -47
  240. package/dist/commands/scan-secrets.d.ts.map +0 -1
  241. package/dist/commands/scan-secrets.js +0 -225
  242. package/dist/commands/scan-secrets.js.map +0 -1
  243. package/dist/commands/scan-vulnerabilities-enhanced.d.ts +0 -41
  244. package/dist/commands/scan-vulnerabilities-enhanced.d.ts.map +0 -1
  245. package/dist/commands/scan-vulnerabilities-enhanced.js +0 -368
  246. package/dist/commands/scan-vulnerabilities-enhanced.js.map +0 -1
  247. package/dist/commands/scan-vulnerabilities-osv.d.ts +0 -58
  248. package/dist/commands/scan-vulnerabilities-osv.d.ts.map +0 -1
  249. package/dist/commands/scan-vulnerabilities-osv.js +0 -722
  250. package/dist/commands/scan-vulnerabilities-osv.js.map +0 -1
  251. package/dist/commands/scan-vulnerabilities.d.ts +0 -32
  252. package/dist/commands/scan-vulnerabilities.d.ts.map +0 -1
  253. package/dist/commands/scan-vulnerabilities.js +0 -283
  254. package/dist/commands/scan-vulnerabilities.js.map +0 -1
  255. package/dist/commands/secrets-allowlist.d.ts +0 -7
  256. package/dist/commands/secrets-allowlist.d.ts.map +0 -1
  257. package/dist/commands/secrets-allowlist.js +0 -85
  258. package/dist/commands/secrets-allowlist.js.map +0 -1
  259. package/dist/commands/ship-consolidated.d.ts +0 -58
  260. package/dist/commands/ship-consolidated.d.ts.map +0 -1
  261. package/dist/commands/ship-consolidated.js +0 -515
  262. package/dist/commands/ship-consolidated.js.map +0 -1
  263. package/dist/commands/stats.d.ts +0 -8
  264. package/dist/commands/stats.d.ts.map +0 -1
  265. package/dist/commands/stats.js +0 -134
  266. package/dist/commands/stats.js.map +0 -1
  267. package/dist/commands/upgrade.d.ts +0 -8
  268. package/dist/commands/upgrade.d.ts.map +0 -1
  269. package/dist/commands/upgrade.js +0 -30
  270. package/dist/commands/upgrade.js.map +0 -1
  271. package/dist/fix/applicator.d.ts +0 -44
  272. package/dist/fix/applicator.d.ts.map +0 -1
  273. package/dist/fix/applicator.js +0 -144
  274. package/dist/fix/applicator.js.map +0 -1
  275. package/dist/fix/backup.d.ts +0 -38
  276. package/dist/fix/backup.d.ts.map +0 -1
  277. package/dist/fix/backup.js +0 -154
  278. package/dist/fix/backup.js.map +0 -1
  279. package/dist/fix/engine.d.ts +0 -55
  280. package/dist/fix/engine.d.ts.map +0 -1
  281. package/dist/fix/engine.js +0 -285
  282. package/dist/fix/engine.js.map +0 -1
  283. package/dist/fix/index.d.ts +0 -5
  284. package/dist/fix/index.d.ts.map +0 -1
  285. package/dist/fix/index.js +0 -12
  286. package/dist/fix/index.js.map +0 -1
  287. package/dist/fix/interactive.d.ts +0 -22
  288. package/dist/fix/interactive.d.ts.map +0 -1
  289. package/dist/fix/interactive.js +0 -172
  290. package/dist/fix/interactive.js.map +0 -1
  291. package/dist/formatters/index.d.ts +0 -6
  292. package/dist/formatters/index.d.ts.map +0 -1
  293. package/dist/formatters/index.js +0 -11
  294. package/dist/formatters/index.js.map +0 -1
  295. package/dist/formatters/sarif-enhanced.d.ts +0 -78
  296. package/dist/formatters/sarif-enhanced.d.ts.map +0 -1
  297. package/dist/formatters/sarif-enhanced.js +0 -144
  298. package/dist/formatters/sarif-enhanced.js.map +0 -1
  299. package/dist/formatters/sarif-v2.d.ts +0 -121
  300. package/dist/formatters/sarif-v2.d.ts.map +0 -1
  301. package/dist/formatters/sarif-v2.js +0 -356
  302. package/dist/formatters/sarif-v2.js.map +0 -1
  303. package/dist/formatters/sarif.d.ts +0 -72
  304. package/dist/formatters/sarif.d.ts.map +0 -1
  305. package/dist/formatters/sarif.js +0 -146
  306. package/dist/formatters/sarif.js.map +0 -1
  307. package/dist/index.d.ts +0 -61
  308. package/dist/index.d.ts.map +0 -1
  309. package/dist/index.js +0 -4388
  310. package/dist/index.js.map +0 -1
  311. package/dist/init/ci-generator.d.ts +0 -18
  312. package/dist/init/ci-generator.d.ts.map +0 -1
  313. package/dist/init/ci-generator.js +0 -317
  314. package/dist/init/ci-generator.js.map +0 -1
  315. package/dist/init/detect-framework.d.ts +0 -15
  316. package/dist/init/detect-framework.d.ts.map +0 -1
  317. package/dist/init/detect-framework.js +0 -301
  318. package/dist/init/detect-framework.js.map +0 -1
  319. package/dist/init/hooks-installer.d.ts +0 -22
  320. package/dist/init/hooks-installer.d.ts.map +0 -1
  321. package/dist/init/hooks-installer.js +0 -310
  322. package/dist/init/hooks-installer.js.map +0 -1
  323. package/dist/init/index.d.ts +0 -8
  324. package/dist/init/index.d.ts.map +0 -1
  325. package/dist/init/index.js +0 -22
  326. package/dist/init/index.js.map +0 -1
  327. package/dist/init/templates.d.ts +0 -402
  328. package/dist/init/templates.d.ts.map +0 -1
  329. package/dist/init/templates.js +0 -240
  330. package/dist/init/templates.js.map +0 -1
  331. package/dist/mcp/server.d.ts +0 -12
  332. package/dist/mcp/server.d.ts.map +0 -1
  333. package/dist/mcp/server.js +0 -42
  334. package/dist/mcp/server.js.map +0 -1
  335. package/dist/mcp/telemetry.d.ts +0 -40
  336. package/dist/mcp/telemetry.d.ts.map +0 -1
  337. package/dist/mcp/telemetry.js +0 -98
  338. package/dist/mcp/telemetry.js.map +0 -1
  339. package/dist/reality/no-dead-buttons/button-sweep-generator.d.ts +0 -32
  340. package/dist/reality/no-dead-buttons/button-sweep-generator.d.ts.map +0 -1
  341. package/dist/reality/no-dead-buttons/button-sweep-generator.js +0 -236
  342. package/dist/reality/no-dead-buttons/button-sweep-generator.js.map +0 -1
  343. package/dist/reality/no-dead-buttons/index.d.ts +0 -11
  344. package/dist/reality/no-dead-buttons/index.d.ts.map +0 -1
  345. package/dist/reality/no-dead-buttons/index.js +0 -18
  346. package/dist/reality/no-dead-buttons/index.js.map +0 -1
  347. package/dist/reality/no-dead-buttons/static-scanner.d.ts +0 -34
  348. package/dist/reality/no-dead-buttons/static-scanner.d.ts.map +0 -1
  349. package/dist/reality/no-dead-buttons/static-scanner.js +0 -230
  350. package/dist/reality/no-dead-buttons/static-scanner.js.map +0 -1
  351. package/dist/reality/reality-graph.d.ts +0 -192
  352. package/dist/reality/reality-graph.d.ts.map +0 -1
  353. package/dist/reality/reality-graph.js +0 -600
  354. package/dist/reality/reality-graph.js.map +0 -1
  355. package/dist/reality/reality-runner.d.ts +0 -89
  356. package/dist/reality/reality-runner.d.ts.map +0 -1
  357. package/dist/reality/reality-runner.js +0 -540
  358. package/dist/reality/reality-runner.js.map +0 -1
  359. package/dist/reality/receipt-generator.d.ts +0 -152
  360. package/dist/reality/receipt-generator.d.ts.map +0 -1
  361. package/dist/reality/receipt-generator.js +0 -495
  362. package/dist/reality/receipt-generator.js.map +0 -1
  363. package/dist/reality/runtime-tracer.d.ts +0 -75
  364. package/dist/reality/runtime-tracer.d.ts.map +0 -1
  365. package/dist/reality/runtime-tracer.js +0 -109
  366. package/dist/reality/runtime-tracer.js.map +0 -1
  367. package/dist/runtime/auth-utils.d.ts +0 -43
  368. package/dist/runtime/auth-utils.d.ts.map +0 -1
  369. package/dist/runtime/auth-utils.js +0 -130
  370. package/dist/runtime/auth-utils.js.map +0 -1
  371. package/dist/runtime/client.d.ts +0 -74
  372. package/dist/runtime/client.d.ts.map +0 -1
  373. package/dist/runtime/client.js +0 -222
  374. package/dist/runtime/client.js.map +0 -1
  375. package/dist/runtime/creds.d.ts +0 -48
  376. package/dist/runtime/creds.d.ts.map +0 -1
  377. package/dist/runtime/creds.js +0 -245
  378. package/dist/runtime/creds.js.map +0 -1
  379. package/dist/runtime/exit-codes.d.ts +0 -49
  380. package/dist/runtime/exit-codes.d.ts.map +0 -1
  381. package/dist/runtime/exit-codes.js +0 -93
  382. package/dist/runtime/exit-codes.js.map +0 -1
  383. package/dist/runtime/index.d.ts +0 -9
  384. package/dist/runtime/index.d.ts.map +0 -1
  385. package/dist/runtime/index.js +0 -25
  386. package/dist/runtime/index.js.map +0 -1
  387. package/dist/runtime/json-output.d.ts +0 -42
  388. package/dist/runtime/json-output.d.ts.map +0 -1
  389. package/dist/runtime/json-output.js +0 -59
  390. package/dist/runtime/json-output.js.map +0 -1
  391. package/dist/runtime/semver.d.ts +0 -37
  392. package/dist/runtime/semver.d.ts.map +0 -1
  393. package/dist/runtime/semver.js +0 -110
  394. package/dist/runtime/semver.js.map +0 -1
  395. package/dist/scan/dead-ui-detector.d.ts +0 -48
  396. package/dist/scan/dead-ui-detector.d.ts.map +0 -1
  397. package/dist/scan/dead-ui-detector.js +0 -170
  398. package/dist/scan/dead-ui-detector.js.map +0 -1
  399. package/dist/scan/playwright-sweep.d.ts +0 -40
  400. package/dist/scan/playwright-sweep.d.ts.map +0 -1
  401. package/dist/scan/playwright-sweep.js +0 -216
  402. package/dist/scan/playwright-sweep.js.map +0 -1
  403. package/dist/scan/proof-bundle.d.ts +0 -25
  404. package/dist/scan/proof-bundle.d.ts.map +0 -1
  405. package/dist/scan/proof-bundle.js +0 -203
  406. package/dist/scan/proof-bundle.js.map +0 -1
  407. package/dist/scan/proof-graph.d.ts +0 -59
  408. package/dist/scan/proof-graph.d.ts.map +0 -1
  409. package/dist/scan/proof-graph.js +0 -64
  410. package/dist/scan/proof-graph.js.map +0 -1
  411. package/dist/scan/reality-sniff.d.ts +0 -56
  412. package/dist/scan/reality-sniff.d.ts.map +0 -1
  413. package/dist/scan/reality-sniff.js +0 -200
  414. package/dist/scan/reality-sniff.js.map +0 -1
  415. package/dist/scan/structural-verifier.d.ts +0 -20
  416. package/dist/scan/structural-verifier.d.ts.map +0 -1
  417. package/dist/scan/structural-verifier.js +0 -112
  418. package/dist/scan/structural-verifier.js.map +0 -1
  419. package/dist/scan/verification-engine.d.ts +0 -47
  420. package/dist/scan/verification-engine.d.ts.map +0 -1
  421. package/dist/scan/verification-engine.js +0 -141
  422. package/dist/scan/verification-engine.js.map +0 -1
  423. package/dist/scanner/baseline.d.ts +0 -52
  424. package/dist/scanner/baseline.d.ts.map +0 -1
  425. package/dist/scanner/baseline.js +0 -85
  426. package/dist/scanner/baseline.js.map +0 -1
  427. package/dist/scanner/incremental.d.ts +0 -30
  428. package/dist/scanner/incremental.d.ts.map +0 -1
  429. package/dist/scanner/incremental.js +0 -82
  430. package/dist/scanner/incremental.js.map +0 -1
  431. package/dist/scanner/parallel.d.ts +0 -43
  432. package/dist/scanner/parallel.d.ts.map +0 -1
  433. package/dist/scanner/parallel.js +0 -99
  434. package/dist/scanner/parallel.js.map +0 -1
  435. package/dist/standalone.d.ts +0 -1
  436. package/dist/standalone.d.ts.map +0 -1
  437. package/dist/standalone.js +0 -1
  438. package/dist/standalone.js.map +0 -1
  439. package/dist/truth-pack/index.d.ts +0 -102
  440. package/dist/truth-pack/index.d.ts.map +0 -1
  441. package/dist/truth-pack/index.js +0 -694
  442. package/dist/truth-pack/index.js.map +0 -1
  443. package/dist/ui/frame.d.ts +0 -68
  444. package/dist/ui/frame.d.ts.map +0 -1
  445. package/dist/ui/frame.js +0 -165
  446. package/dist/ui/frame.js.map +0 -1
  447. package/dist/ui/index.d.ts +0 -5
  448. package/dist/ui/index.d.ts.map +0 -1
  449. package/dist/ui/index.js +0 -16
  450. package/dist/ui/index.js.map +0 -1
  451. package/dist/ui.d.ts +0 -36
  452. package/dist/ui.d.ts.map +0 -1
  453. package/dist/ui.js +0 -45
  454. package/dist/ui.js.map +0 -1
@@ -0,0 +1,1062 @@
1
+ /**
2
+ * vibecheck ship - The Vibe Coder's Best Friend
3
+ * Zero config. Plain English. One command to ship with confidence.
4
+ */
5
+
6
+ const path = require("path");
7
+ const fs = require("fs");
8
+ const { withErrorHandling } = require("./lib/error-handler");
9
+ const { ensureOutputDir, detectProjectFeatures } = require("./utils");
10
+ const { enforceLimit, enforceFeature, trackUsage, getCurrentTier } = require("./lib/entitlements");
11
+ const { emitShipCheck } = require("./lib/audit-bridge");
12
+
13
+ // Route Truth v1 - Fake endpoint detection
14
+ const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
15
+ const {
16
+ findMissingRoutes,
17
+ findEnvGaps,
18
+ findFakeSuccess,
19
+ findGhostAuth,
20
+ findStripeWebhookViolations,
21
+ findPaidSurfaceNotEnforced,
22
+ findOwnerModeBypass
23
+ } = require("./lib/analyzers");
24
+ const { findingsFromReality } = require("./lib/reality-findings");
25
+
26
+ // Build proof graph from findings for evidence-backed verdicts
27
+ function buildProofGraph(findings, truthpack, root) {
28
+ const claims = [];
29
+ let claimId = 0;
30
+
31
+ for (const finding of findings) {
32
+ const claim = {
33
+ id: `claim-${++claimId}`,
34
+ type: getClaimType(finding.category),
35
+ assertion: finding.title || finding.message,
36
+ verified: finding.severity !== 'BLOCK',
37
+ confidence: finding.confidence === 'high' ? 0.9 : finding.confidence === 'medium' ? 0.7 : 0.5,
38
+ evidence: (finding.evidence || []).map((e, i) => ({
39
+ id: `evidence-${claimId}-${i}`,
40
+ type: 'file_citation',
41
+ file: e.file,
42
+ line: parseInt(e.lines?.split('-')[0]) || e.line || 1,
43
+ snippet: e.snippetHash || '',
44
+ strength: 0.8,
45
+ verifiedAt: new Date().toISOString(),
46
+ method: 'static'
47
+ })),
48
+ gaps: [{
49
+ id: `gap-${claimId}`,
50
+ type: getGapType(finding.category),
51
+ description: finding.why || finding.title,
52
+ severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
53
+ suggestion: (finding.fixHints || [])[0] || ''
54
+ }],
55
+ severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
56
+ file: finding.evidence?.[0]?.file || '',
57
+ line: parseInt(finding.evidence?.[0]?.lines?.split('-')[0]) || 1
58
+ };
59
+ claims.push(claim);
60
+ }
61
+
62
+ const verifiedClaims = claims.filter(c => c.verified);
63
+ const failedClaims = claims.filter(c => !c.verified);
64
+ const allGaps = claims.flatMap(c => c.gaps);
65
+
66
+ const riskScore = Math.min(100, failedClaims.reduce((sum, c) => {
67
+ if (c.severity === 'critical') return sum + 30;
68
+ if (c.severity === 'high') return sum + 20;
69
+ if (c.severity === 'medium') return sum + 10;
70
+ return sum + 5;
71
+ }, 0));
72
+
73
+ const confidence = claims.length > 0
74
+ ? claims.reduce((sum, c) => sum + c.confidence, 0) / claims.length
75
+ : 1.0;
76
+
77
+ return {
78
+ version: '1.0.0',
79
+ generatedAt: new Date().toISOString(),
80
+ projectPath: root,
81
+ claims,
82
+ summary: {
83
+ totalClaims: claims.length,
84
+ verifiedClaims: verifiedClaims.length,
85
+ failedClaims: failedClaims.length,
86
+ gaps: allGaps.length,
87
+ riskScore,
88
+ confidence
89
+ },
90
+ verdict: findings.some(f => f.severity === 'BLOCK') ? 'BLOCK' :
91
+ findings.some(f => f.severity === 'WARN') ? 'WARN' : 'SHIP',
92
+ topBlockers: failedClaims.filter(c => c.severity === 'critical' || c.severity === 'high').slice(0, 5),
93
+ topGaps: allGaps.filter(g => g.severity === 'critical' || g.severity === 'high').slice(0, 5)
94
+ };
95
+ }
96
+
97
+ function getClaimType(category) {
98
+ const map = {
99
+ 'MissingRoute': 'route_exists',
100
+ 'EnvContract': 'env_declared',
101
+ 'FakeSuccess': 'success_verified',
102
+ 'GhostAuth': 'auth_protected',
103
+ 'StripeWebhook': 'billing_enforced',
104
+ 'PaidSurface': 'billing_enforced',
105
+ 'OwnerModeBypass': 'billing_enforced',
106
+ 'DeadUI': 'ui_wired'
107
+ };
108
+ return map[category] || 'ui_wired';
109
+ }
110
+
111
+ function getGapType(category) {
112
+ const map = {
113
+ 'MissingRoute': 'missing_handler',
114
+ 'EnvContract': 'missing_verification',
115
+ 'FakeSuccess': 'missing_verification',
116
+ 'GhostAuth': 'missing_gate',
117
+ 'StripeWebhook': 'missing_verification',
118
+ 'PaidSurface': 'missing_gate',
119
+ 'OwnerModeBypass': 'missing_gate',
120
+ 'DeadUI': 'missing_handler'
121
+ };
122
+ return map[category] || 'untested_path';
123
+ }
124
+
125
+ // ANSI color codes
126
+ const c = {
127
+ reset: "\x1b[0m",
128
+ bold: "\x1b[1m",
129
+ dim: "\x1b[2m",
130
+ red: "\x1b[31m",
131
+ green: "\x1b[32m",
132
+ yellow: "\x1b[33m",
133
+ blue: "\x1b[34m",
134
+ magenta: "\x1b[35m",
135
+ cyan: "\x1b[36m",
136
+ white: "\x1b[37m",
137
+ bgRed: "\x1b[41m",
138
+ bgGreen: "\x1b[42m",
139
+ bgYellow: "\x1b[43m",
140
+ };
141
+
142
+ const PLAIN_ENGLISH = {
143
+ secretExposed: (type) => ({
144
+ message: `🔑 Your ${type} is visible in the code - hackers can steal it`,
145
+ why: `If this code is pushed to GitHub or deployed, anyone can see your ${type} and use it maliciously.`,
146
+ fix: `Move this to a .env file and use process.env.${type.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`,
147
+ }),
148
+ adminExposed: (route) => ({
149
+ message: `🔐 Anyone can access ${route} without logging in`,
150
+ why: `This endpoint has no authentication. Attackers can access admin features directly.`,
151
+ fix: `Add authentication middleware to protect this route.`,
152
+ }),
153
+ mockInProd: (detail) => ({
154
+ message: `🎭 Your code uses fake data instead of real data`,
155
+ why: `Mock/test code in production means users see fake data or features don't work.`,
156
+ fix: `Remove or conditionally disable this mock code for production builds.`,
157
+ }),
158
+ endpointMissing: (route) => ({
159
+ message: `🔗 Button calls ${route} but that endpoint doesn't exist`,
160
+ why: `Your frontend calls an API that doesn't exist - this will cause errors for users.`,
161
+ fix: `Either create the missing endpoint or update the frontend to use the correct URL.`,
162
+ }),
163
+ };
164
+
165
+ function getTrafficLight(score) {
166
+ if (score >= 80) return "🟢";
167
+ if (score >= 50) return "🟡";
168
+ return "🔴";
169
+ }
170
+
171
+ function getVerdict(score, blockers) {
172
+ if (score >= 90 && blockers.length === 0) {
173
+ return {
174
+ emoji: "🚀",
175
+ headline: "Ready to ship!",
176
+ detail: "Your app looks solid. Ship it!",
177
+ };
178
+ }
179
+ if (score >= 70 && blockers.length <= 2) {
180
+ return {
181
+ emoji: "⚠️",
182
+ headline: "Almost ready",
183
+ detail: "A few things to fix, but you're close.",
184
+ };
185
+ }
186
+ if (score >= 50) {
187
+ return {
188
+ emoji: "🛑",
189
+ headline: "Not ready yet",
190
+ detail: "Some important issues need your attention.",
191
+ };
192
+ }
193
+ return {
194
+ emoji: "🚨",
195
+ headline: "Don't ship this!",
196
+ detail: "Critical problems found. Fix these first.",
197
+ };
198
+ }
199
+
200
+ function translateToPlainEnglish(results) {
201
+ const problems = [],
202
+ warnings = [],
203
+ passes = [];
204
+
205
+ if (results.integrity?.env?.secrets) {
206
+ for (const secret of results.integrity.env.secrets) {
207
+ if (secret.severity === "critical") {
208
+ const info = PLAIN_ENGLISH.secretExposed(secret.type);
209
+ problems.push({
210
+ category: "Security",
211
+ type: secret.type,
212
+ message: info.message,
213
+ why: info.why,
214
+ fix: info.fix,
215
+ file: `${secret.file}:${secret.line}`,
216
+ line: secret.line,
217
+ rawFile: secret.file,
218
+ fixable: true,
219
+ fixAction: "move-to-env",
220
+ });
221
+ }
222
+ }
223
+ }
224
+
225
+ if (results.integrity?.auth?.analysis?.adminExposed?.length > 0) {
226
+ for (const route of results.integrity.auth.analysis.adminExposed) {
227
+ const info = PLAIN_ENGLISH.adminExposed(`${route.method} ${route.path}`);
228
+ problems.push({
229
+ category: "Auth",
230
+ message: info.message,
231
+ why: info.why,
232
+ fix: info.fix,
233
+ file: route.file,
234
+ fixable: false,
235
+ fixAction: "add-auth-middleware",
236
+ });
237
+ }
238
+ }
239
+
240
+ if (results.integrity?.mocks?.issues) {
241
+ for (const issue of results.integrity.mocks.issues) {
242
+ if (issue.severity === "critical" || issue.severity === "high") {
243
+ const info = PLAIN_ENGLISH.mockInProd(issue.type);
244
+ problems.push({
245
+ category: "Fake Code",
246
+ message: info.message,
247
+ why: info.why,
248
+ fix: info.fix,
249
+ detail: issue.evidence || issue.type,
250
+ file: `${issue.file}:${issue.line}`,
251
+ fixable: false,
252
+ fixAction: "remove-mock",
253
+ });
254
+ }
255
+ }
256
+ }
257
+
258
+ if (!results.integrity?.env?.secrets?.length)
259
+ passes.push({
260
+ category: "Secrets",
261
+ message: "✅ No exposed secrets found",
262
+ });
263
+ if (!results.integrity?.mocks?.issues?.length)
264
+ passes.push({ category: "Code", message: "✅ No fake/mock code found" });
265
+
266
+ return { problems, warnings, passes };
267
+ }
268
+
269
+ function printVibeCoderResults(results, translated, outputDir) {
270
+ const { problems, warnings, passes } = translated;
271
+ const score = results.score || 0;
272
+ const light = getTrafficLight(score);
273
+ const verdict = getVerdict(score, problems);
274
+
275
+ // Get colors based on score
276
+ const boxColor = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;
277
+ const scoreColor = score >= 80 ? c.green : score >= 50 ? c.yellow : c.red;
278
+
279
+ console.log("");
280
+ console.log(
281
+ ` ${boxColor}╔═════════════════════════════════════════════════════════════════╗${c.reset}`,
282
+ );
283
+ console.log(
284
+ ` ${boxColor}║${c.reset} ${boxColor}║${c.reset}`,
285
+ );
286
+ console.log(
287
+ ` ${boxColor}║${c.reset} ${light} ${c.bold}${verdict.headline}${c.reset} ${boxColor}║${c.reset}`,
288
+ );
289
+ console.log(
290
+ ` ${boxColor}║${c.reset} ${c.dim}${verdict.detail}${c.reset} ${boxColor}║${c.reset}`,
291
+ );
292
+ console.log(
293
+ ` ${boxColor}║${c.reset} ${boxColor}║${c.reset}`,
294
+ );
295
+ console.log(
296
+ ` ${boxColor}╚═════════════════════════════════════════════════════════════════╝${c.reset}`,
297
+ );
298
+ console.log("");
299
+
300
+ if (problems.length > 0) {
301
+ console.log(
302
+ ` ${c.bold}${c.red}🚨 PROBLEMS${c.reset} ${c.dim}(${problems.length} found)${c.reset}\n`,
303
+ );
304
+ for (const p of problems.slice(0, 8)) {
305
+ console.log(` ${c.red}❌${c.reset} ${c.bold}${p.message}${c.reset}`);
306
+ if (p.why) console.log(` ${c.dim}Why: ${p.why}${c.reset}`);
307
+ if (p.file) console.log(` ${c.cyan}📍 ${p.file}${c.reset}`);
308
+ if (p.fix) console.log(` ${c.green}💡 Fix: ${p.fix}${c.reset}`);
309
+ console.log("");
310
+ }
311
+ }
312
+
313
+ if (passes.length > 0) {
314
+ console.log(` ${c.bold}${c.green}✅ WHAT'S WORKING${c.reset}\n`);
315
+ for (const p of passes) console.log(` ${c.green}${p.message}${c.reset}`);
316
+ console.log("");
317
+ }
318
+
319
+ // Score summary box
320
+ console.log(
321
+ ` ${c.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`,
322
+ );
323
+ console.log(
324
+ ` ${c.bold}Score:${c.reset} ${scoreColor}${c.bold}${score}${c.reset}/100 ${light}`,
325
+ );
326
+ console.log("");
327
+ if (problems.length > 0) {
328
+ const fixable = problems.filter((p) => p.fixable).length;
329
+ console.log(
330
+ ` ${c.dim}${problems.length} problems found (${fixable} auto-fixable)${c.reset}`,
331
+ );
332
+ console.log(
333
+ ` ${c.dim}Run:${c.reset} ${c.cyan}${c.bold}vibecheck ship --fix${c.reset}`,
334
+ );
335
+ } else {
336
+ console.log(` ${c.green}${c.bold}No critical problems! 🎉${c.reset}`);
337
+ }
338
+ console.log(
339
+ ` ${c.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`,
340
+ );
341
+ console.log("");
342
+ console.log(
343
+ ` ${c.dim}📄 Full report:${c.reset} ${c.cyan}${outputDir}/report.html${c.reset}\n`,
344
+ );
345
+ }
346
+
347
+ function parseArgs(args) {
348
+ const opts = {
349
+ fix: false,
350
+ path: ".",
351
+ verbose: false,
352
+ json: false,
353
+ badge: false,
354
+ assist: false,
355
+ strict: false,
356
+ ci: false,
357
+ };
358
+ for (let i = 0; i < args.length; i++) {
359
+ const a = args[i];
360
+ if (a === "--fix" || a === "-f") opts.fix = true;
361
+ if (a === "--verbose" || a === "-v") opts.verbose = true;
362
+ if (a === "--json") opts.json = true;
363
+ if (a === "--badge" || a === "-b") opts.badge = true;
364
+ if (a === "--assist") opts.assist = true;
365
+ if (a === "--strict") opts.strict = true;
366
+ if (a === "--ci") opts.ci = true;
367
+ if (a.startsWith("--path=")) opts.path = a.split("=")[1];
368
+ if (a === "--path" || a === "-p") opts.path = args[++i];
369
+ if (a === "--help" || a === "-h") opts.help = true;
370
+ }
371
+ return opts;
372
+ }
373
+
374
+ async function runShip(args) {
375
+ const opts = parseArgs(args);
376
+
377
+ if (opts.help) {
378
+ console.log(`
379
+ ${c.bold}${c.cyan}vibecheck ship${c.reset} — The One Command
380
+
381
+ ${c.dim}Get a ship verdict: SHIP | WARN | BLOCK${c.reset}
382
+
383
+ ${c.bold}USAGE${c.reset}
384
+ vibecheck ship Get ship verdict + Vibe Score
385
+ vibecheck ship --fix Try safe mechanical fixes
386
+ vibecheck ship --assist Generate AI mission packs
387
+ vibecheck ship --badge Generate embeddable badge
388
+
389
+ ${c.bold}OPTIONS${c.reset}
390
+ --fix, -f Try safe mechanical fixes (shows plan first)
391
+ --assist Generate AI mission prompts for complex issues
392
+ --badge, -b Generate embeddable badge for README
393
+ --strict Treat warnings as blockers
394
+ --ci Machine output for CI/CD
395
+ --json Output as JSON
396
+ --path, -p Project path (default: current directory)
397
+ --help, -h Show this help
398
+
399
+ ${c.bold}EXIT CODES${c.reset}
400
+ ${c.green}0${c.reset} SHIP — Ready to ship
401
+ ${c.yellow}1${c.reset} WARN — Warnings found
402
+ ${c.red}2${c.reset} BLOCK — Blockers found
403
+
404
+ ${c.bold}EXAMPLES${c.reset}
405
+ vibecheck ship # Get verdict
406
+ vibecheck ship --fix # Fix what can be fixed
407
+ vibecheck ship --badge # Get README badge
408
+ vibecheck ship --strict --ci # Strict CI mode
409
+ `);
410
+ return 0;
411
+ }
412
+
413
+ // ═══════════════════════════════════════════════════════════════════════════
414
+ // ENTITLEMENT CHECK
415
+ // ═══════════════════════════════════════════════════════════════════════════
416
+ try {
417
+ await enforceLimit('scans');
418
+ await enforceFeature('ship');
419
+
420
+ // Check for fix feature (premium)
421
+ if (opts.fix) {
422
+ await enforceFeature('fix');
423
+ }
424
+ } catch (err) {
425
+ if (err.code === 'LIMIT_EXCEEDED' || err.code === 'FEATURE_NOT_AVAILABLE') {
426
+ console.error(err.upgradePrompt || err.message);
427
+ const { EXIT_CODES } = require('./lib/error-handler');
428
+ process.exit(EXIT_CODES.AUTH_FAILURE);
429
+ }
430
+ throw err;
431
+ }
432
+
433
+ // Track usage
434
+ await trackUsage('scans');
435
+
436
+ const projectPath = path.resolve(opts.path);
437
+ const outputDir = path.join(projectPath, ".vibecheck");
438
+
439
+ console.log(`\n ${c.bold}${c.magenta}🚀 vibecheck SHIP${c.reset}\n`);
440
+ console.log(
441
+ ` ${c.dim}Scanning your app for production readiness...${c.reset}\n`,
442
+ );
443
+
444
+ let results = {
445
+ score: 100,
446
+ grade: "A",
447
+ canShip: true,
448
+ deductions: [],
449
+ blockers: [],
450
+ counts: {},
451
+ checks: {},
452
+ outputDir,
453
+ };
454
+
455
+ try {
456
+ console.log(" 🔍 Checking for problems...");
457
+ const { auditProductionIntegrity } = require(
458
+ path.join(__dirname, "../../scripts/audit-production-integrity.js"),
459
+ );
460
+ const { results: integrityResults, integrity } =
461
+ await auditProductionIntegrity(projectPath);
462
+ results.score = integrity.score;
463
+ results.grade = integrity.grade;
464
+ results.canShip = integrity.canShip;
465
+ results.deductions = integrity.deductions;
466
+ results.integrity = integrityResults;
467
+ } catch (err) {
468
+ if (opts.verbose) console.error(" ⚠️ Integrity check error:", err.message);
469
+ }
470
+
471
+ // ═══════════════════════════════════════════════════════════════════════════
472
+ // ROUTE TRUTH v1 - Fake Endpoint Detection
473
+ // ═══════════════════════════════════════════════════════════════════════════
474
+ try {
475
+ console.log(" 🗺️ Building route truth map...");
476
+ const fastifyEntry = detectFastifyEntry(projectPath);
477
+ const truthpack = await buildTruthpack({ repoRoot: projectPath, fastifyEntry });
478
+ writeTruthpack(projectPath, truthpack);
479
+
480
+ // Run all v1 analyzers
481
+ const allFindings = [
482
+ ...findMissingRoutes(truthpack),
483
+ ...findEnvGaps(truthpack),
484
+ ...findFakeSuccess(projectPath),
485
+ ...findGhostAuth(truthpack, projectPath),
486
+ ...findStripeWebhookViolations(truthpack),
487
+ ...findPaidSurfaceNotEnforced(truthpack),
488
+ ...findOwnerModeBypass(projectPath),
489
+ // Runtime UI reality (Dead UI detection)
490
+ ...findingsFromReality(projectPath)
491
+ ];
492
+
493
+ results.routeTruth = {
494
+ serverRoutes: truthpack.routes.server.length,
495
+ clientRefs: truthpack.routes.clientRefs.length,
496
+ envVars: truthpack.env?.vars?.length || 0,
497
+ envDeclared: truthpack.env?.declared?.length || 0,
498
+ findings: allFindings,
499
+ };
500
+
501
+ // Add blocking findings to results
502
+ for (const finding of allFindings) {
503
+ if (finding.severity === "BLOCK") {
504
+ results.canShip = false;
505
+ results.blockers.push({
506
+ type: finding.category,
507
+ message: finding.title,
508
+ evidence: finding.evidence,
509
+ fix: finding.fixHints?.join(" ") || "",
510
+ });
511
+ results.deductions.push({
512
+ reason: finding.title,
513
+ points: 15,
514
+ severity: "critical",
515
+ });
516
+ results.score = Math.max(0, results.score - 15);
517
+ }
518
+ }
519
+
520
+ // Print summary
521
+ console.log(` Routes: ${truthpack.routes.server.length} server, ${truthpack.routes.clientRefs.length} client refs`);
522
+ console.log(` Env: ${truthpack.env?.vars?.length || 0} used, ${truthpack.env?.declared?.length || 0} declared`);
523
+
524
+ const blockers = allFindings.filter(f => f.severity === "BLOCK");
525
+ const warns = allFindings.filter(f => f.severity === "WARN");
526
+
527
+ if (blockers.length) {
528
+ console.log(` ${c.red}🛑 ${blockers.length} BLOCKERS detected${c.reset}`);
529
+ for (const b of blockers.slice(0, 5)) {
530
+ console.log(` - ${b.title}`);
531
+ }
532
+ if (blockers.length > 5) console.log(` ...and ${blockers.length - 5} more`);
533
+ }
534
+ if (warns.length) {
535
+ console.log(` ${c.yellow}⚠️ ${warns.length} WARNINGS${c.reset}`);
536
+ }
537
+ } catch (err) {
538
+ if (opts.verbose) console.error(" ⚠️ Route truth check error:", err.message);
539
+ }
540
+
541
+ console.log(" ✅ Scan complete!");
542
+
543
+ const translated = translateToPlainEnglish(results);
544
+
545
+ // Run auto-fix if requested
546
+ if (opts.fix) {
547
+ console.log(`\n ${c.bold}${c.cyan}🔧 AUTO-FIX MODE${c.reset}\n`);
548
+ const fixResults = await runAutoFix(
549
+ projectPath,
550
+ translated,
551
+ results,
552
+ outputDir,
553
+ );
554
+ printFixResults(fixResults);
555
+ }
556
+
557
+ printVibeCoderResults(results, translated, outputDir);
558
+
559
+ try {
560
+ const { writeArtifacts } = require("./utils");
561
+ writeArtifacts(outputDir, results);
562
+ } catch (err) {
563
+ // Log but don't fail - artifact writing is non-critical
564
+ console.warn(`${c.yellow}⚠${c.reset} Failed to write artifacts: ${err.message}`);
565
+ if (process.env.DEBUG || process.env.VIBECHECK_DEBUG) {
566
+ console.error(err.stack);
567
+ }
568
+ }
569
+
570
+ // Emit audit event for ship check
571
+ emitShipCheck(projectPath, results.canShip ? 'success' : 'failure', {
572
+ score: results.score,
573
+ grade: results.grade,
574
+ canShip: results.canShip,
575
+ issueCount: translated.problems?.length || 0,
576
+ });
577
+
578
+ // Badge generation mode
579
+ if (opts.badge) {
580
+ const projectName = path.basename(projectPath);
581
+ const projectId = projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
582
+ const verdict = results.canShip ? 'SHIP' : (results.score >= 50 ? 'WARN' : 'BLOCK');
583
+ const score = results.score || 0;
584
+
585
+ console.log(`\n${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`);
586
+ console.log(`${c.bold}📛 Ship Badge Generated${c.reset}\n`);
587
+
588
+ const badgeUrl = `https://vibecheck.dev/badge/${projectId}.svg`;
589
+ const reportUrl = `https://vibecheck.dev/report/${projectId}`;
590
+ const markdown = `[![Vibecheck](${badgeUrl})](${reportUrl})`;
591
+
592
+ const verdictColor = verdict === 'SHIP' ? c.green : verdict === 'WARN' ? c.yellow : c.red;
593
+ const verdictIcon = verdict === 'SHIP' ? '✅' : verdict === 'WARN' ? '⚠️' : '🚫';
594
+
595
+ console.log(` ${verdictIcon} ${verdictColor}${c.bold}${verdict}${c.reset} · Score: ${c.bold}${score}${c.reset}`);
596
+ console.log(`\n ${c.dim}Badge URL:${c.reset} ${c.cyan}${badgeUrl}${c.reset}`);
597
+ console.log(` ${c.dim}Report URL:${c.reset} ${c.cyan}${reportUrl}${c.reset}`);
598
+ console.log(`\n ${c.dim}Add to README.md:${c.reset}`);
599
+ console.log(` ${c.green}${markdown}${c.reset}`);
600
+ console.log(`\n${c.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}\n`);
601
+
602
+ // Save badge info to .vibecheck directory
603
+ const badgeInfo = {
604
+ projectId,
605
+ score,
606
+ verdict,
607
+ badgeUrl,
608
+ reportUrl,
609
+ markdown,
610
+ generatedAt: new Date().toISOString(),
611
+ };
612
+
613
+ try {
614
+ fs.writeFileSync(
615
+ path.join(outputDir, 'badge.json'),
616
+ JSON.stringify(badgeInfo, null, 2)
617
+ );
618
+ } catch {}
619
+ }
620
+
621
+ // JSON output mode - use standardized schema
622
+ if (opts.json) {
623
+ const { createScanResult, validateScanResult } = require('./lib/scan-output-schema');
624
+
625
+ // Convert results to standardized format
626
+ const findings = (translated.problems || []).map((p, idx) => ({
627
+ id: `finding_${idx}`,
628
+ type: p.type || 'ship_blocker',
629
+ severity: p.severity || 'high',
630
+ message: p.message || p.title || '',
631
+ file: p.file || null,
632
+ line: p.line || null,
633
+ confidence: 0.9,
634
+ blocksShip: !results.canShip,
635
+ suggestedFix: p.fix || null,
636
+ }));
637
+
638
+ const standardizedResult = createScanResult({
639
+ findings,
640
+ projectPath,
641
+ scanId: `ship_${Date.now()}`,
642
+ startTime: Date.now(),
643
+ });
644
+
645
+ // Validate before output
646
+ const validation = validateScanResult(standardizedResult);
647
+ if (!validation.valid) {
648
+ console.error(JSON.stringify({
649
+ schemaVersion: "1.0.0",
650
+ success: false,
651
+ error: {
652
+ code: "SCHEMA_VALIDATION_FAILED",
653
+ message: "JSON output validation failed",
654
+ nextSteps: validation.errors,
655
+ },
656
+ }, null, 2));
657
+ const { EXIT_CODES } = require('./lib/error-handler');
658
+ return EXIT_CODES.SYSTEM_ERROR;
659
+ }
660
+
661
+ console.log(JSON.stringify(standardizedResult, null, 2));
662
+ // Exit codes per spec: 0=SHIP, 1=WARN, 2=BLOCK
663
+ if (results.canShip) return 0; // SHIP
664
+ if (results.hasWarnings && !results.hasBlockers) return 1; // WARN
665
+ return 2; // BLOCK
666
+ }
667
+
668
+ // Exit codes per spec: 0=SHIP, 1=WARN, 2=BLOCK
669
+ if (results.canShip) return 0; // SHIP
670
+ if (results.hasWarnings && !results.hasBlockers) return 1; // WARN
671
+ return 2; // BLOCK
672
+ }
673
+
674
+ /**
675
+ * Safe auto-fix implementation
676
+ * Only performs non-destructive fixes:
677
+ * 1. Creates .env.example with detected secrets as placeholders
678
+ * 2. Updates .gitignore to protect sensitive files
679
+ * 3. Generates fixes.md with detailed manual fix instructions
680
+ */
681
+ async function runAutoFix(projectPath, translated, results, outputDir) {
682
+ const fixResults = {
683
+ envExampleCreated: false,
684
+ gitignoreUpdated: false,
685
+ fixesMdCreated: false,
686
+ secretsFound: [],
687
+ errors: [],
688
+ };
689
+
690
+ const { ensureOutputDir } = require("./utils");
691
+ ensureOutputDir(outputDir);
692
+
693
+ // 1. Create .env.example with detected secrets
694
+ const secretProblems = translated.problems.filter(
695
+ (p) => p.fixAction === "move-to-env",
696
+ );
697
+ if (secretProblems.length > 0) {
698
+ try {
699
+ const envExamplePath = path.join(projectPath, ".env.example");
700
+ const envVars = new Set();
701
+
702
+ for (const problem of secretProblems) {
703
+ const varName = problem.type.toUpperCase().replace(/[^A-Z0-9]/g, "_");
704
+ envVars.add(varName);
705
+ fixResults.secretsFound.push({
706
+ type: problem.type,
707
+ varName,
708
+ file: problem.rawFile,
709
+ });
710
+ }
711
+
712
+ let envContent = "# Environment Variables Template\n";
713
+ envContent += "# Copy this file to .env and fill in your actual values\n";
714
+ envContent += "# NEVER commit .env to version control!\n\n";
715
+
716
+ for (const varName of envVars) {
717
+ envContent += `${varName}=your_${varName.toLowerCase()}_here\n`;
718
+ }
719
+
720
+ // Append to existing .env.example or create new
721
+ if (fs.existsSync(envExamplePath)) {
722
+ const existing = fs.readFileSync(envExamplePath, "utf8");
723
+ for (const varName of envVars) {
724
+ if (!existing.includes(varName)) {
725
+ fs.appendFileSync(
726
+ envExamplePath,
727
+ `${varName}=your_${varName.toLowerCase()}_here\n`,
728
+ );
729
+ }
730
+ }
731
+ console.log(
732
+ ` ${c.green}✓${c.reset} Updated ${c.cyan}.env.example${c.reset} with ${envVars.size} variables`,
733
+ );
734
+ } else {
735
+ fs.writeFileSync(envExamplePath, envContent);
736
+ console.log(
737
+ ` ${c.green}✓${c.reset} Created ${c.cyan}.env.example${c.reset} with ${envVars.size} variables`,
738
+ );
739
+ }
740
+ fixResults.envExampleCreated = true;
741
+ } catch (err) {
742
+ fixResults.errors.push(`Failed to create .env.example: ${err.message}`);
743
+ }
744
+ }
745
+
746
+ // 2. Update .gitignore to protect sensitive files
747
+ try {
748
+ const gitignorePath = path.join(projectPath, ".gitignore");
749
+ const sensitivePatterns = [
750
+ ".env",
751
+ ".env.local",
752
+ ".env.*.local",
753
+ "*.pem",
754
+ "*.key",
755
+ ".vibecheck/artifacts/",
756
+ ];
757
+
758
+ let gitignoreContent = "";
759
+ if (fs.existsSync(gitignorePath)) {
760
+ gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
761
+ }
762
+
763
+ const patternsToAdd = sensitivePatterns.filter(
764
+ (p) => !gitignoreContent.includes(p),
765
+ );
766
+
767
+ if (patternsToAdd.length > 0) {
768
+ const addition =
769
+ "\n# vibecheck: Protect sensitive files\n" +
770
+ patternsToAdd.join("\n") +
771
+ "\n";
772
+ fs.appendFileSync(gitignorePath, addition);
773
+ console.log(
774
+ ` ${c.green}✓${c.reset} Updated ${c.cyan}.gitignore${c.reset} with ${patternsToAdd.length} patterns`,
775
+ );
776
+ fixResults.gitignoreUpdated = true;
777
+ } else {
778
+ console.log(
779
+ ` ${c.dim}✓ .gitignore already protects sensitive files${c.reset}`,
780
+ );
781
+ }
782
+ } catch (err) {
783
+ fixResults.errors.push(`Failed to update .gitignore: ${err.message}`);
784
+ }
785
+
786
+ // 3. Generate detailed fixes.md with manual instructions AND AI agent prompt
787
+ try {
788
+ const fixesMdPath = path.join(outputDir, "fixes.md");
789
+ const aiPromptPath = path.join(outputDir, "ai-fix-prompt.md");
790
+
791
+ // Group problems by category and dedupe by file
792
+ const byCategory = {};
793
+ const seenFiles = new Set();
794
+ for (const p of translated.problems) {
795
+ // Dedupe: only show first issue per file for same category
796
+ const key = `${p.category}:${p.file}`;
797
+ if (seenFiles.has(key)) continue;
798
+ seenFiles.add(key);
799
+
800
+ if (!byCategory[p.category]) byCategory[p.category] = [];
801
+ byCategory[p.category].push(p);
802
+ }
803
+
804
+ // Read actual file content for context (keyed by file:line to handle multiple issues per file)
805
+ const fileContexts = {};
806
+ const fileContents = {}; // Cache file contents
807
+ for (const problems of Object.values(byCategory)) {
808
+ for (const p of problems) {
809
+ if (p.rawFile) {
810
+ const contextKey = `${p.rawFile}:${p.line || 1}`;
811
+ if (!fileContexts[contextKey]) {
812
+ try {
813
+ // Cache file content
814
+ if (!fileContents[p.rawFile]) {
815
+ const fullPath = path.join(projectPath, p.rawFile);
816
+ if (fs.existsSync(fullPath)) {
817
+ fileContents[p.rawFile] = fs
818
+ .readFileSync(fullPath, "utf8")
819
+ .split("\n");
820
+ }
821
+ }
822
+
823
+ if (fileContents[p.rawFile]) {
824
+ const lines = fileContents[p.rawFile];
825
+ const lineNum = p.line || 1;
826
+ const start = Math.max(0, lineNum - 3);
827
+ const end = Math.min(lines.length, lineNum + 3);
828
+ fileContexts[contextKey] = {
829
+ snippet: lines
830
+ .slice(start, end)
831
+ .map((l, i) => `${start + i + 1}: ${l}`)
832
+ .join("\n"),
833
+ line: lineNum,
834
+ };
835
+ }
836
+ } catch (e) {
837
+ /* ignore read errors */
838
+ }
839
+ }
840
+ // Store the context key on the problem for later lookup
841
+ p._contextKey = contextKey;
842
+ }
843
+ }
844
+ }
845
+
846
+ // Generate human-readable fixes.md
847
+ let md = "# 🔧 vibecheck Fix Guide\n\n";
848
+ md += `Generated: ${new Date().toISOString()}\n\n`;
849
+
850
+ const totalIssues = Object.values(byCategory).flat().length;
851
+ md += `**${totalIssues} unique issues found across ${Object.keys(byCategory).length} categories**\n\n`;
852
+ md += "---\n\n";
853
+
854
+ for (const [category, problems] of Object.entries(byCategory)) {
855
+ md += `## ${category} (${problems.length} files)\n\n`;
856
+
857
+ for (const p of problems) {
858
+ md += `### \`${p.file}\`\n\n`;
859
+ md += `**Problem:** ${p.message}\n\n`;
860
+ md += `**Risk:** ${p.why}\n\n`;
861
+
862
+ // Show actual code context if available
863
+ if (p._contextKey && fileContexts[p._contextKey]) {
864
+ md += "**Current code:**\n```\n";
865
+ md += fileContexts[p._contextKey].snippet;
866
+ md += "\n```\n\n";
867
+ }
868
+
869
+ md += `**Fix:** ${p.fix}\n\n`;
870
+
871
+ // Add specific code example for secrets
872
+ if (p.fixAction === "move-to-env" && p.type) {
873
+ const varName = p.type.toUpperCase().replace(/[^A-Z0-9]/g, "_");
874
+ md += "**Replace with:**\n```javascript\n";
875
+ md += `const ${p.type.toLowerCase().replace(/[^a-z0-9]/g, "")} = process.env.${varName};\n`;
876
+ md += "```\n\n";
877
+ }
878
+
879
+ md += "---\n\n";
880
+ }
881
+ }
882
+
883
+ md += "## Next Steps\n\n";
884
+ md += "1. Copy `.env.example` to `.env` and fill in real values\n";
885
+ md += "2. Apply the fixes above to each file\n";
886
+ md += "3. Run `vibecheck ship` again to verify\n\n";
887
+ md += "---\n\n";
888
+ md +=
889
+ "📋 **AI Agent prompt available at:** `.vibecheck/ai-fix-prompt.md`\n";
890
+
891
+ fs.writeFileSync(fixesMdPath, md);
892
+ console.log(
893
+ ` ${c.green}✓${c.reset} Created ${c.cyan}.vibecheck/fixes.md${c.reset} with detailed instructions`,
894
+ );
895
+
896
+ // 4. Generate AI agent prompt
897
+ let aiPrompt = "# AI Agent Fix Prompt\n\n";
898
+ aiPrompt +=
899
+ "> Copy this entire prompt to an AI coding assistant to fix these issues safely.\n\n";
900
+ aiPrompt += "---\n\n";
901
+ aiPrompt += "## Task\n\n";
902
+ aiPrompt +=
903
+ "Fix the following production security and code quality issues. ";
904
+ aiPrompt +=
905
+ "Follow the exact instructions for each fix. Do NOT break existing functionality.\n\n";
906
+
907
+ aiPrompt += "## Critical Rules\n\n";
908
+ aiPrompt += "1. **Never delete code** - only modify or comment out\n";
909
+ aiPrompt += "2. **Never change function signatures** - keep APIs stable\n";
910
+ aiPrompt += "3. **Test after each fix** - ensure the app still runs\n";
911
+ aiPrompt +=
912
+ "4. **Preserve comments** - don't remove existing documentation\n";
913
+ aiPrompt += "5. **Use environment variables** - never hardcode secrets\n\n";
914
+
915
+ aiPrompt += "## Fixes Required\n\n";
916
+
917
+ let fixNum = 1;
918
+ for (const [category, problems] of Object.entries(byCategory)) {
919
+ for (const p of problems) {
920
+ aiPrompt += `### Fix ${fixNum}: ${category}\n\n`;
921
+ aiPrompt += `**File:** \`${p.file}\`\n\n`;
922
+ aiPrompt += `**Problem:** ${p.message}\n\n`;
923
+
924
+ if (p._contextKey && fileContexts[p._contextKey]) {
925
+ aiPrompt +=
926
+ "**Current code (around line " +
927
+ fileContexts[p._contextKey].line +
928
+ "):**\n```\n";
929
+ aiPrompt += fileContexts[p._contextKey].snippet;
930
+ aiPrompt += "\n```\n\n";
931
+ }
932
+
933
+ aiPrompt += "**Action:**\n";
934
+
935
+ if (p.fixAction === "move-to-env") {
936
+ const varName = p.type.toUpperCase().replace(/[^A-Z0-9]/g, "_");
937
+ aiPrompt += `1. Find the hardcoded ${p.type} in this file\n`;
938
+ aiPrompt += `2. Replace the hardcoded value with \`process.env.${varName}\`\n`;
939
+ aiPrompt += `3. Add \`${varName}\` to \`.env.example\` if not present\n`;
940
+ aiPrompt += `4. Ensure the code handles undefined env var gracefully\n\n`;
941
+ aiPrompt += "**Example transformation:**\n```diff\n";
942
+ aiPrompt += `- const secret = "sk_live_xxxxx";\n`;
943
+ aiPrompt += `+ const secret = process.env.${varName};\n`;
944
+ aiPrompt += `+ if (!secret) throw new Error('${varName} is required');\n`;
945
+ aiPrompt += "```\n\n";
946
+ } else if (p.fixAction === "remove-mock") {
947
+ aiPrompt +=
948
+ "1. Check if this file is a test file (should be in __tests__, *.test.*, *.spec.*)\n";
949
+ aiPrompt +=
950
+ "2. If it's a test file, this is a FALSE POSITIVE - skip it\n";
951
+ aiPrompt += "3. If it's production code, either:\n";
952
+ aiPrompt += " - Remove the mock import/code entirely, OR\n";
953
+ aiPrompt +=
954
+ ' - Wrap it in `if (process.env.NODE_ENV !== "production")`\n\n';
955
+ } else if (p.fixAction === "add-auth-middleware") {
956
+ aiPrompt += "1. Identify the route handler for this endpoint\n";
957
+ aiPrompt += "2. Add authentication middleware before the handler\n";
958
+ aiPrompt +=
959
+ '3. Example: `router.get("/admin", authMiddleware, adminHandler)`\n\n';
960
+ }
961
+
962
+ fixNum++;
963
+ }
964
+ }
965
+
966
+ aiPrompt += "## Verification\n\n";
967
+ aiPrompt += "After applying fixes:\n";
968
+ aiPrompt += "1. Run `npm run build` or `pnpm build` to check for errors\n";
969
+ aiPrompt += "2. Run `vibecheck ship` to verify all issues are resolved\n";
970
+ aiPrompt += "3. Test the application manually to ensure it works\n";
971
+
972
+ fs.writeFileSync(aiPromptPath, aiPrompt);
973
+ console.log(
974
+ ` ${c.green}✓${c.reset} Created ${c.cyan}.vibecheck/ai-fix-prompt.md${c.reset} for AI agents`,
975
+ );
976
+
977
+ fixResults.fixesMdCreated = true;
978
+ } catch (err) {
979
+ fixResults.errors.push(`Failed to create fixes.md: ${err.message}`);
980
+ }
981
+
982
+ return fixResults;
983
+ }
984
+
985
+ function printFixResults(fixResults) {
986
+ console.log("");
987
+ if (fixResults.errors.length > 0) {
988
+ for (const err of fixResults.errors) {
989
+ console.log(` ${c.red}⚠️ ${err}${c.reset}`);
990
+ }
991
+ }
992
+
993
+ if (
994
+ fixResults.envExampleCreated ||
995
+ fixResults.gitignoreUpdated ||
996
+ fixResults.fixesMdCreated
997
+ ) {
998
+ console.log(`\n ${c.bold}${c.green}✅ Safe fixes applied!${c.reset}`);
999
+ console.log(
1000
+ ` ${c.dim}Review the changes and follow instructions in .vibecheck/fixes.md${c.reset}\n`,
1001
+ );
1002
+ }
1003
+ }
1004
+
1005
+ /**
1006
+ * shipCore: returns report, never consumes meter, never exits.
1007
+ * Used by fix/autopilot verification loops.
1008
+ */
1009
+ async function shipCore({ repoRoot, fastifyEntry, jsonOut, noWrite } = {}) {
1010
+ const root = repoRoot || process.cwd();
1011
+ const fastEntry = fastifyEntry || detectFastifyEntry(root);
1012
+
1013
+ const truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: fastEntry });
1014
+ if (!noWrite) writeTruthpack(root, truthpack);
1015
+
1016
+ const allFindings = [
1017
+ ...findMissingRoutes(truthpack),
1018
+ ...findEnvGaps(truthpack),
1019
+ ...findFakeSuccess(root),
1020
+ ...findGhostAuth(truthpack, root),
1021
+ ...findStripeWebhookViolations(truthpack),
1022
+ ...findPaidSurfaceNotEnforced(truthpack),
1023
+ ...findOwnerModeBypass(root),
1024
+ // Runtime UI reality (Dead UI detection)
1025
+ ...findingsFromReality(root)
1026
+ ];
1027
+
1028
+ const verdict = allFindings.some(f => f.severity === "BLOCK") ? "BLOCK" :
1029
+ allFindings.some(f => f.severity === "WARN") ? "WARN" : "SHIP";
1030
+
1031
+ // Build proof graph from findings
1032
+ const proofGraph = buildProofGraph(allFindings, truthpack, root);
1033
+
1034
+ const report = {
1035
+ meta: { generatedAt: new Date().toISOString(), verdict },
1036
+ truthpackHash: truthpack.index?.hashes?.truthpackHash,
1037
+ findings: allFindings,
1038
+ proofGraph: {
1039
+ summary: proofGraph.summary,
1040
+ topBlockers: proofGraph.topBlockers.slice(0, 5),
1041
+ topGaps: proofGraph.topGaps.slice(0, 5)
1042
+ }
1043
+ };
1044
+
1045
+ const outDir = path.join(root, ".vibecheck");
1046
+ fs.mkdirSync(outDir, { recursive: true });
1047
+ fs.writeFileSync(path.join(outDir, "last_ship.json"), JSON.stringify(report, null, 2));
1048
+
1049
+ // Write full proof graph separately
1050
+ fs.writeFileSync(path.join(outDir, "proof-graph.json"), JSON.stringify(proofGraph, null, 2));
1051
+
1052
+ if (jsonOut) {
1053
+ fs.writeFileSync(path.isAbsolute(jsonOut) ? jsonOut : path.join(root, jsonOut), JSON.stringify(report, null, 2));
1054
+ }
1055
+
1056
+ return { report, truthpack, verdict };
1057
+ }
1058
+
1059
+ module.exports = {
1060
+ runShip: withErrorHandling(runShip, "Ship check failed"),
1061
+ shipCore
1062
+ };