vibecheck-ai 2.0.1 → 5.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 (456) hide show
  1. package/bin/.generated +25 -0
  2. package/bin/_deprecations.js +463 -0
  3. package/bin/_router.js +46 -0
  4. package/bin/cli-hygiene.js +241 -0
  5. package/bin/dev/run-v2-torture.js +30 -0
  6. package/bin/registry.js +656 -0
  7. package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
  8. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
  9. package/bin/runners/REPORT_AUDIT.md +64 -0
  10. package/bin/runners/cli-utils.js +1070 -0
  11. package/bin/runners/context/ai-task-decomposer.js +337 -0
  12. package/bin/runners/context/analyzer.js +513 -0
  13. package/bin/runners/context/api-contracts.js +427 -0
  14. package/bin/runners/context/context-diff.js +342 -0
  15. package/bin/runners/context/context-pruner.js +291 -0
  16. package/bin/runners/context/dependency-graph.js +414 -0
  17. package/bin/runners/context/generators/claude.js +107 -0
  18. package/bin/runners/context/generators/codex.js +108 -0
  19. package/bin/runners/context/generators/copilot.js +119 -0
  20. package/bin/runners/context/generators/cursor-enhanced.js +2525 -0
  21. package/bin/runners/context/generators/cursor.js +514 -0
  22. package/bin/runners/context/generators/mcp.js +169 -0
  23. package/bin/runners/context/generators/windsurf.js +180 -0
  24. package/bin/runners/context/git-context.js +304 -0
  25. package/bin/runners/context/index.js +1110 -0
  26. package/bin/runners/context/insights.js +173 -0
  27. package/bin/runners/context/mcp-server/generate-rules.js +337 -0
  28. package/bin/runners/context/mcp-server/index.js +1176 -0
  29. package/bin/runners/context/mcp-server/package.json +24 -0
  30. package/bin/runners/context/memory.js +200 -0
  31. package/bin/runners/context/monorepo.js +215 -0
  32. package/bin/runners/context/multi-repo-federation.js +404 -0
  33. package/bin/runners/context/patterns.js +253 -0
  34. package/bin/runners/context/proof-context.js +1264 -0
  35. package/bin/runners/context/security-scanner.js +541 -0
  36. package/bin/runners/context/semantic-search.js +350 -0
  37. package/bin/runners/context/shared.js +264 -0
  38. package/bin/runners/context/team-conventions.js +336 -0
  39. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
  40. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  41. package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
  42. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  43. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  44. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  45. package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
  46. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  47. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  48. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  49. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  50. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  51. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  52. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  53. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  54. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  55. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  56. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  57. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  58. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  59. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  60. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  61. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
  62. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  63. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
  64. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  65. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  66. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  67. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  68. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  69. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  70. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  71. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  72. package/bin/runners/lib/agent-firewall/index.js +200 -0
  73. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  74. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  75. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
  76. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  77. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  78. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  79. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  80. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  81. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  82. package/bin/runners/lib/agent-firewall/interceptor/base.js +308 -0
  83. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  84. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  85. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  86. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  87. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  88. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  89. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  90. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  91. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  92. package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
  93. package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
  94. package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
  95. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  96. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  97. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +79 -0
  98. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +227 -0
  99. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +191 -0
  100. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  101. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  102. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  103. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  104. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  105. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  106. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  107. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  108. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  109. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  110. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  111. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  112. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  113. package/bin/runners/lib/agent-firewall/risk/thresholds.js +322 -0
  114. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  115. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  116. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  117. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  118. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  119. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  120. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  121. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  122. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  123. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  124. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  125. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  126. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  127. package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
  128. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  129. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  130. package/bin/runners/lib/ai-bridge.js +416 -0
  131. package/bin/runners/lib/analysis-core.js +309 -0
  132. package/bin/runners/lib/analyzers.js +2500 -0
  133. package/bin/runners/lib/api-client.js +269 -0
  134. package/bin/runners/lib/approve-output.js +235 -0
  135. package/bin/runners/lib/artifact-envelope.js +540 -0
  136. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  137. package/bin/runners/lib/audit-bridge.js +391 -0
  138. package/bin/runners/lib/auth-shared.js +977 -0
  139. package/bin/runners/lib/auth-truth.js +193 -0
  140. package/bin/runners/lib/auth.js +215 -0
  141. package/bin/runners/lib/authority-badge.js +425 -0
  142. package/bin/runners/lib/backup.js +62 -0
  143. package/bin/runners/lib/billing.js +107 -0
  144. package/bin/runners/lib/checkpoint.js +941 -0
  145. package/bin/runners/lib/claims.js +118 -0
  146. package/bin/runners/lib/classify-output.js +204 -0
  147. package/bin/runners/lib/cleanup/engine.js +571 -0
  148. package/bin/runners/lib/cleanup/index.js +53 -0
  149. package/bin/runners/lib/cleanup/output.js +375 -0
  150. package/bin/runners/lib/cleanup/rules.js +1060 -0
  151. package/bin/runners/lib/cli-output.js +400 -0
  152. package/bin/runners/lib/cli-ui.js +540 -0
  153. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  154. package/bin/runners/lib/compliance-bridge.js +165 -0
  155. package/bin/runners/lib/contracts/auth-contract.js +202 -0
  156. package/bin/runners/lib/contracts/env-contract.js +181 -0
  157. package/bin/runners/lib/contracts/external-contract.js +206 -0
  158. package/bin/runners/lib/contracts/guard.js +168 -0
  159. package/bin/runners/lib/contracts/index.js +89 -0
  160. package/bin/runners/lib/contracts/plan-validator.js +311 -0
  161. package/bin/runners/lib/contracts/route-contract.js +199 -0
  162. package/bin/runners/lib/contracts.js +804 -0
  163. package/bin/runners/lib/default-config.js +127 -0
  164. package/bin/runners/lib/detect.js +89 -0
  165. package/bin/runners/lib/detectors-v2.js +622 -0
  166. package/bin/runners/lib/doctor/autofix.js +254 -0
  167. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  168. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  169. package/bin/runners/lib/doctor/fix-script.js +336 -0
  170. package/bin/runners/lib/doctor/index.js +37 -0
  171. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  172. package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
  173. package/bin/runners/lib/doctor/modules/index.js +105 -0
  174. package/bin/runners/lib/doctor/modules/network.js +250 -0
  175. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  176. package/bin/runners/lib/doctor/modules/project.js +312 -0
  177. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  178. package/bin/runners/lib/doctor/modules/runtime.js +224 -0
  179. package/bin/runners/lib/doctor/modules/security.js +350 -0
  180. package/bin/runners/lib/doctor/modules/system.js +213 -0
  181. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
  182. package/bin/runners/lib/doctor/reporter.js +262 -0
  183. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  184. package/bin/runners/lib/doctor/service.js +262 -0
  185. package/bin/runners/lib/doctor/types.js +113 -0
  186. package/bin/runners/lib/doctor/ui.js +263 -0
  187. package/bin/runners/lib/doctor-enhanced.js +233 -0
  188. package/bin/runners/lib/doctor-output.js +226 -0
  189. package/bin/runners/lib/doctor-v2.js +608 -0
  190. package/bin/runners/lib/drift.js +425 -0
  191. package/bin/runners/lib/enforcement.js +72 -0
  192. package/bin/runners/lib/engine/ast-cache.js +210 -0
  193. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  194. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  195. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  196. package/bin/runners/lib/engine/env-extractor.js +207 -0
  197. package/bin/runners/lib/engine/express-extractor.js +208 -0
  198. package/bin/runners/lib/engine/extractors.js +849 -0
  199. package/bin/runners/lib/engine/index.js +207 -0
  200. package/bin/runners/lib/engine/repo-index.js +514 -0
  201. package/bin/runners/lib/engine/types.js +124 -0
  202. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  203. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  204. package/bin/runners/lib/engines/ast-cache.js +99 -0
  205. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  206. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  207. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  208. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  209. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  210. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  211. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  212. package/bin/runners/lib/engines/file-filter.js +131 -0
  213. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  214. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  215. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  216. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  217. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  218. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  219. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  220. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  221. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  222. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  223. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  224. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  225. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  226. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  227. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  228. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  229. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  230. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  231. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  232. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  233. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  234. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  235. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  236. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  237. package/bin/runners/lib/enterprise-detect.js +603 -0
  238. package/bin/runners/lib/enterprise-init.js +942 -0
  239. package/bin/runners/lib/entitlements-v2.js +265 -0
  240. package/bin/runners/lib/entitlements.generated.js +0 -0
  241. package/bin/runners/lib/entitlements.js +340 -0
  242. package/bin/runners/lib/env-resolver.js +417 -0
  243. package/bin/runners/lib/env-template.js +66 -0
  244. package/bin/runners/lib/env.js +189 -0
  245. package/bin/runners/lib/error-handler.js +368 -0
  246. package/bin/runners/lib/error-messages.js +289 -0
  247. package/bin/runners/lib/evidence-pack.js +684 -0
  248. package/bin/runners/lib/exit-codes.js +275 -0
  249. package/bin/runners/lib/extractors/client-calls.js +990 -0
  250. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  251. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  252. package/bin/runners/lib/extractors/index.js +363 -0
  253. package/bin/runners/lib/extractors/next-routes.js +524 -0
  254. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  255. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  256. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  257. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  258. package/bin/runners/lib/finding-id.js +69 -0
  259. package/bin/runners/lib/finding-sorter.js +89 -0
  260. package/bin/runners/lib/findings-schema.js +281 -0
  261. package/bin/runners/lib/fingerprint.js +377 -0
  262. package/bin/runners/lib/firewall-prompt.js +50 -0
  263. package/bin/runners/lib/fix-output.js +228 -0
  264. package/bin/runners/lib/global-flags.js +250 -0
  265. package/bin/runners/lib/graph/graph-builder.js +265 -0
  266. package/bin/runners/lib/graph/html-renderer.js +413 -0
  267. package/bin/runners/lib/graph/index.js +32 -0
  268. package/bin/runners/lib/graph/runtime-collector.js +215 -0
  269. package/bin/runners/lib/graph/static-extractor.js +518 -0
  270. package/bin/runners/lib/help-formatter.js +413 -0
  271. package/bin/runners/lib/html-proof-report.js +913 -0
  272. package/bin/runners/lib/html-report.js +650 -0
  273. package/bin/runners/lib/init-wizard.js +601 -0
  274. package/bin/runners/lib/interactive-menu.js +1496 -0
  275. package/bin/runners/lib/json-output.js +76 -0
  276. package/bin/runners/lib/llm.js +75 -0
  277. package/bin/runners/lib/logger.js +38 -0
  278. package/bin/runners/lib/meter.js +61 -0
  279. package/bin/runners/lib/missions/briefing.js +427 -0
  280. package/bin/runners/lib/missions/checkpoint.js +753 -0
  281. package/bin/runners/lib/missions/evidence.js +126 -0
  282. package/bin/runners/lib/missions/hardening.js +851 -0
  283. package/bin/runners/lib/missions/plan.js +648 -0
  284. package/bin/runners/lib/missions/safety-gates.js +645 -0
  285. package/bin/runners/lib/missions/schema.js +478 -0
  286. package/bin/runners/lib/missions/templates.js +317 -0
  287. package/bin/runners/lib/next-action.js +560 -0
  288. package/bin/runners/lib/packs/bundle.js +675 -0
  289. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  290. package/bin/runners/lib/packs/pack-factory.js +837 -0
  291. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  292. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  293. package/bin/runners/lib/patch.js +40 -0
  294. package/bin/runners/lib/permissions/auth-model.js +213 -0
  295. package/bin/runners/lib/permissions/idor-prover.js +205 -0
  296. package/bin/runners/lib/permissions/index.js +45 -0
  297. package/bin/runners/lib/permissions/matrix-builder.js +198 -0
  298. package/bin/runners/lib/pkgjson.js +28 -0
  299. package/bin/runners/lib/policy.js +295 -0
  300. package/bin/runners/lib/polish/accessibility.js +62 -0
  301. package/bin/runners/lib/polish/analyzer.js +93 -0
  302. package/bin/runners/lib/polish/backend.js +87 -0
  303. package/bin/runners/lib/polish/configuration.js +83 -0
  304. package/bin/runners/lib/polish/documentation.js +83 -0
  305. package/bin/runners/lib/polish/frontend.js +817 -0
  306. package/bin/runners/lib/polish/index.js +27 -0
  307. package/bin/runners/lib/polish/infrastructure.js +80 -0
  308. package/bin/runners/lib/polish/internationalization.js +85 -0
  309. package/bin/runners/lib/polish/libraries.js +180 -0
  310. package/bin/runners/lib/polish/observability.js +75 -0
  311. package/bin/runners/lib/polish/performance.js +64 -0
  312. package/bin/runners/lib/polish/privacy.js +110 -0
  313. package/bin/runners/lib/polish/resilience.js +92 -0
  314. package/bin/runners/lib/polish/security.js +78 -0
  315. package/bin/runners/lib/polish/seo.js +71 -0
  316. package/bin/runners/lib/polish/styles.js +62 -0
  317. package/bin/runners/lib/polish/utils.js +104 -0
  318. package/bin/runners/lib/preflight.js +142 -0
  319. package/bin/runners/lib/prerequisites.js +149 -0
  320. package/bin/runners/lib/prove-output.js +220 -0
  321. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  322. package/bin/runners/lib/reality/index.js +318 -0
  323. package/bin/runners/lib/reality/request-hashing.js +416 -0
  324. package/bin/runners/lib/reality/request-mapper.js +453 -0
  325. package/bin/runners/lib/reality/safety-rails.js +463 -0
  326. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  327. package/bin/runners/lib/reality/toast-detector.js +393 -0
  328. package/bin/runners/lib/reality-findings.js +84 -0
  329. package/bin/runners/lib/reality-output.js +231 -0
  330. package/bin/runners/lib/receipts.js +179 -0
  331. package/bin/runners/lib/redact.js +29 -0
  332. package/bin/runners/lib/replay/capsule-manager.js +154 -0
  333. package/bin/runners/lib/replay/index.js +263 -0
  334. package/bin/runners/lib/replay/player.js +348 -0
  335. package/bin/runners/lib/replay/recorder.js +331 -0
  336. package/bin/runners/lib/report-engine.js +626 -0
  337. package/bin/runners/lib/report-html.js +1233 -0
  338. package/bin/runners/lib/report-output.js +366 -0
  339. package/bin/runners/lib/report-templates.js +967 -0
  340. package/bin/runners/lib/report.js +135 -0
  341. package/bin/runners/lib/route-detection.js +1209 -0
  342. package/bin/runners/lib/route-truth.js +1322 -0
  343. package/bin/runners/lib/safelist/index.js +96 -0
  344. package/bin/runners/lib/safelist/integration.js +334 -0
  345. package/bin/runners/lib/safelist/matcher.js +696 -0
  346. package/bin/runners/lib/safelist/schema.js +948 -0
  347. package/bin/runners/lib/safelist/store.js +438 -0
  348. package/bin/runners/lib/sandbox/index.js +59 -0
  349. package/bin/runners/lib/sandbox/proof-chain.js +399 -0
  350. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
  351. package/bin/runners/lib/sandbox/worktree.js +174 -0
  352. package/bin/runners/lib/scan-cache.js +330 -0
  353. package/bin/runners/lib/scan-output-schema.js +344 -0
  354. package/bin/runners/lib/scan-output.js +631 -0
  355. package/bin/runners/lib/scan-runner.js +135 -0
  356. package/bin/runners/lib/schema-validator.js +350 -0
  357. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  358. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  359. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  360. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  361. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  362. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  363. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  364. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  365. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  366. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  367. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  368. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  369. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  370. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  371. package/bin/runners/lib/schemas/validator.js +465 -0
  372. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  373. package/bin/runners/lib/score-history.js +282 -0
  374. package/bin/runners/lib/security-bridge.js +249 -0
  375. package/bin/runners/lib/server-usage.js +513 -0
  376. package/bin/runners/lib/share-pack.js +239 -0
  377. package/bin/runners/lib/ship-gate.js +832 -0
  378. package/bin/runners/lib/ship-manifest.js +1153 -0
  379. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  380. package/bin/runners/lib/ship-output.js +1128 -0
  381. package/bin/runners/lib/snippets.js +67 -0
  382. package/bin/runners/lib/status-output.js +340 -0
  383. package/bin/runners/lib/terminal-ui.js +356 -0
  384. package/bin/runners/lib/truth.js +1691 -0
  385. package/bin/runners/lib/ui.js +562 -0
  386. package/bin/runners/lib/unified-cli-output.js +947 -0
  387. package/bin/runners/lib/unified-output.js +197 -0
  388. package/bin/runners/lib/upsell.js +410 -0
  389. package/bin/runners/lib/usage.js +153 -0
  390. package/bin/runners/lib/validate-patch.js +156 -0
  391. package/bin/runners/lib/verdict-engine.js +628 -0
  392. package/bin/runners/lib/verification.js +345 -0
  393. package/bin/runners/lib/why-tree.js +650 -0
  394. package/bin/runners/reality/engine.js +917 -0
  395. package/bin/runners/reality/flows.js +122 -0
  396. package/bin/runners/reality/report.js +378 -0
  397. package/bin/runners/reality/session.js +193 -0
  398. package/bin/runners/runAIAgent.js +229 -0
  399. package/bin/runners/runAgent.d.ts +5 -0
  400. package/bin/runners/runAgent.js +161 -0
  401. package/bin/runners/runAllowlist.js +418 -0
  402. package/bin/runners/runApprove.js +320 -0
  403. package/bin/runners/runAudit.js +692 -0
  404. package/bin/runners/runAuth.js +731 -0
  405. package/bin/runners/runCI.js +353 -0
  406. package/bin/runners/runCheckpoint.js +530 -0
  407. package/bin/runners/runClassify.js +928 -0
  408. package/bin/runners/runCleanup.js +343 -0
  409. package/bin/runners/runContext.d.ts +4 -0
  410. package/bin/runners/runContext.js +175 -0
  411. package/bin/runners/runDoctor.js +877 -0
  412. package/bin/runners/runEvidencePack.js +362 -0
  413. package/bin/runners/runFirewall.d.ts +5 -0
  414. package/bin/runners/runFirewall.js +134 -0
  415. package/bin/runners/runFirewallHook.d.ts +5 -0
  416. package/bin/runners/runFirewallHook.js +56 -0
  417. package/bin/runners/runFix.js +1355 -0
  418. package/bin/runners/runForge.js +451 -0
  419. package/bin/runners/runGuard.js +262 -0
  420. package/bin/runners/runInit.js +1927 -0
  421. package/bin/runners/runIntent.js +906 -0
  422. package/bin/runners/runKickoff.js +878 -0
  423. package/bin/runners/runLabs.js +424 -0
  424. package/bin/runners/runLaunch.js +2000 -0
  425. package/bin/runners/runLink.js +785 -0
  426. package/bin/runners/runMcp.js +1875 -0
  427. package/bin/runners/runPacks.js +2089 -0
  428. package/bin/runners/runPolish.d.ts +4 -0
  429. package/bin/runners/runPolish.js +390 -0
  430. package/bin/runners/runPromptFirewall.js +211 -0
  431. package/bin/runners/runProve.js +1411 -0
  432. package/bin/runners/runQuickstart.js +531 -0
  433. package/bin/runners/runReality.js +2260 -0
  434. package/bin/runners/runReport.js +726 -0
  435. package/bin/runners/runRuntime.js +110 -0
  436. package/bin/runners/runSafelist.js +1190 -0
  437. package/bin/runners/runScan.js +688 -0
  438. package/bin/runners/runShield.js +1282 -0
  439. package/bin/runners/runShip.js +1660 -0
  440. package/bin/runners/runTruth.d.ts +5 -0
  441. package/bin/runners/runTruth.js +101 -0
  442. package/bin/runners/runValidate.js +179 -0
  443. package/bin/runners/runWatch.js +478 -0
  444. package/bin/runners/utils.js +360 -0
  445. package/bin/scan.js +617 -0
  446. package/bin/vibecheck.js +1617 -0
  447. package/dist/guardrail/index.d.ts +2405 -0
  448. package/dist/guardrail/index.js +9747 -0
  449. package/dist/guardrail/index.js.map +1 -0
  450. package/dist/scanner/index.d.ts +282 -0
  451. package/dist/scanner/index.js +3395 -0
  452. package/dist/scanner/index.js.map +1 -0
  453. package/package.json +123 -104
  454. package/README.md +0 -491
  455. package/dist/index.js +0 -99711
  456. package/dist/index.js.map +0 -1
@@ -0,0 +1,1660 @@
1
+ /**
2
+ * vibecheck ship - The Vibe Coder's Best Friend
3
+ * Zero config. Plain English. One command to ship with confidence.
4
+ *
5
+ * ═══════════════════════════════════════════════════════════════════════════════
6
+ * ENTERPRISE EDITION - World-Class Terminal Experience
7
+ * ═══════════════════════════════════════════════════════════════════════════════
8
+ */
9
+
10
+ const path = require("path");
11
+ const fs = require("fs");
12
+ const { withErrorHandling } = require("./lib/error-handler");
13
+ const { ensureOutputDir, detectProjectFeatures } = require("./utils");
14
+ const { enforceLimit, enforceFeature, trackUsage, getCurrentTier } = require("./lib/entitlements");
15
+ const { emitShipCheck } = require("./lib/audit-bridge");
16
+ const { parseGlobalFlags, shouldShowBanner } = require("./lib/global-flags");
17
+ const {
18
+ generateRunId,
19
+ createJsonOutput,
20
+ writeJsonOutput,
21
+ saveArtifact
22
+ } = require("./lib/cli-output");
23
+ const { EXIT, verdictToExitCode, exitCodeToVerdict } = require("./lib/exit-codes");
24
+
25
+ // ═══════════════════════════════════════════════════════════════════════════════
26
+ // NEW: Ship Gate System (v2) - Deterministic, Evidence-backed
27
+ // ═══════════════════════════════════════════════════════════════════════════════
28
+ let _shipGate = null;
29
+ let _shipManifest = null;
30
+ let _whyTree = null;
31
+
32
+ function getShipGate() {
33
+ if (!_shipGate) {
34
+ try {
35
+ _shipGate = require("./lib/ship-gate");
36
+ } catch {
37
+ _shipGate = false;
38
+ }
39
+ }
40
+ return _shipGate;
41
+ }
42
+
43
+ function getShipManifest() {
44
+ if (!_shipManifest) {
45
+ try {
46
+ _shipManifest = require("./lib/ship-manifest");
47
+ } catch {
48
+ _shipManifest = false;
49
+ }
50
+ }
51
+ return _shipManifest;
52
+ }
53
+
54
+ function getWhyTree() {
55
+ if (!_whyTree) {
56
+ try {
57
+ _whyTree = require("./lib/why-tree");
58
+ } catch {
59
+ _whyTree = false;
60
+ }
61
+ }
62
+ return _whyTree;
63
+ }
64
+
65
+ // Route Truth - Fake endpoint detection (V2 engine-aware)
66
+ const { buildTruthpackSmart, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
67
+ const {
68
+ findMissingRoutes,
69
+ findEnvGaps,
70
+ findFakeSuccess,
71
+ findGhostAuth,
72
+ findStripeWebhookViolations,
73
+ findPaidSurfaceNotEnforced,
74
+ findOwnerModeBypass,
75
+ // NEW: AI Hallucination Detectors
76
+ findOptimisticNoRollback,
77
+ findSilentCatch,
78
+ findMethodMismatch,
79
+ findDeadUI,
80
+ } = require("./lib/analyzers");
81
+ const { findingsFromReality } = require("./lib/reality-findings");
82
+ const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = require("./lib/drift");
83
+ const upsell = require("./lib/upsell");
84
+ const entitlements = require("./lib/entitlements-v2");
85
+
86
+ // ═══════════════════════════════════════════════════════════════════════════════
87
+ // ENHANCED TERMINAL UI & OUTPUT MODULES
88
+ // ═══════════════════════════════════════════════════════════════════════════════
89
+
90
+ const {
91
+ ansi,
92
+ colors,
93
+ icons,
94
+ Spinner,
95
+ renderBanner,
96
+ renderSection,
97
+ formatDuration,
98
+ } = require("./lib/terminal-ui");
99
+
100
+ const {
101
+ formatShipOutput: formatShipOutputLegacy,
102
+ renderVerdictCard,
103
+ renderFixModeHeader,
104
+ renderFixResults,
105
+ renderBadgeOutput,
106
+ getExitCode,
107
+ EXIT_CODES,
108
+ shipIcons,
109
+ } = require("./lib/ship-output");
110
+
111
+ const {
112
+ formatShipOutput,
113
+ } = require("./lib/ship-output-enterprise");
114
+
115
+ // ═══════════════════════════════════════════════════════════════════════════════
116
+ // UNIFIED CLI OUTPUT
117
+ // ═══════════════════════════════════════════════════════════════════════════════
118
+
119
+ let _cliOutput = null;
120
+ function getCliOutput() {
121
+ if (!_cliOutput) _cliOutput = require("./lib/unified-cli-output");
122
+ return _cliOutput;
123
+ }
124
+
125
+ // Fallback banner if unified not available
126
+ const BANNER_FALLBACK = `
127
+ ${ansi.rgb(0, 255, 200)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${ansi.reset}
128
+ ${ansi.rgb(0, 230, 220)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${ansi.reset}
129
+ ${ansi.rgb(0, 200, 255)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${ansi.reset}
130
+ ${ansi.rgb(50, 150, 255)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${ansi.reset}
131
+ ${ansi.rgb(100, 100, 255)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${ansi.reset}
132
+ ${ansi.rgb(150, 50, 255)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${ansi.reset}
133
+ `;
134
+
135
+ // ═══════════════════════════════════════════════════════════════════════════════
136
+ // TERMINAL UTILITIES
137
+ // ═══════════════════════════════════════════════════════════════════════════════
138
+
139
+ const BOX = {
140
+ topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯',
141
+ horizontal: '─', vertical: '│',
142
+ teeRight: '├', teeLeft: '┤', teeDown: '┬', teeUp: '┴',
143
+ cross: '┼',
144
+ // Double line variants
145
+ dTopLeft: '╔', dTopRight: '╗', dBottomLeft: '╚', dBottomRight: '╝',
146
+ dHorizontal: '═', dVertical: '║',
147
+ };
148
+
149
+ const ICONS = {
150
+ ship: '🚀',
151
+ check: '✓',
152
+ cross: '✗',
153
+ warning: '⚠',
154
+ error: '✗',
155
+ info: 'ℹ',
156
+ arrow: '→',
157
+ bullet: '•',
158
+ star: '★',
159
+ sparkle: '✨',
160
+ fire: '🔥',
161
+ lock: '🔐',
162
+ key: '🔑',
163
+ link: '🔗',
164
+ graph: '📊',
165
+ map: '🗺️',
166
+ doc: '📄',
167
+ folder: '📁',
168
+ clock: '⏱',
169
+ target: '🎯',
170
+ shield: '🛡️',
171
+ bug: '🐛',
172
+ wrench: '🔧',
173
+ lightning: '⚡',
174
+ package: '📦',
175
+ route: '🛤️',
176
+ env: '🌍',
177
+ auth: '🔒',
178
+ money: '💰',
179
+ ghost: '👻',
180
+ dead: '💀',
181
+ };
182
+
183
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
184
+ const SPINNER_DOTS = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
185
+ const SPINNER_ARROWS = ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'];
186
+
187
+ let spinnerIndex = 0;
188
+ let spinnerInterval = null;
189
+ let spinnerStartTime = null;
190
+
191
+ // formatDuration is imported from terminal-ui.js
192
+
193
+ function formatNumber(num) {
194
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
195
+ }
196
+
197
+ function truncate(str, len) {
198
+ if (!str) return '';
199
+ if (str.length <= len) return str;
200
+ return str.slice(0, len - 3) + '...';
201
+ }
202
+
203
+ function padCenter(str, width) {
204
+ const padding = Math.max(0, width - str.length);
205
+ const left = Math.floor(padding / 2);
206
+ const right = padding - left;
207
+ return ' '.repeat(left) + str + ' '.repeat(right);
208
+ }
209
+
210
+ function progressBar(percent, width = 30, opts = {}) {
211
+ const filled = Math.round((percent / 100) * width);
212
+ const empty = width - filled;
213
+
214
+ let filledColor;
215
+ if (opts.color) {
216
+ filledColor = opts.color;
217
+ } else if (percent >= 80) {
218
+ filledColor = colors.shipGreen;
219
+ } else if (percent >= 50) {
220
+ filledColor = colors.warnAmber;
221
+ } else {
222
+ filledColor = colors.blockRed;
223
+ }
224
+
225
+ const filledChar = opts.filled || '█';
226
+ const emptyChar = opts.empty || '░';
227
+
228
+ return `${filledColor}${filledChar.repeat(filled)}${c.dim}${emptyChar.repeat(empty)}${c.reset}`;
229
+ }
230
+
231
+ function startSpinner(message, style = 'dots') {
232
+ const frames = style === 'arrows' ? SPINNER_ARROWS :
233
+ style === 'dots' ? SPINNER_DOTS : SPINNER_FRAMES;
234
+ spinnerStartTime = Date.now();
235
+ process.stdout.write(c.hideCursor);
236
+
237
+ spinnerInterval = setInterval(() => {
238
+ const elapsed = formatDuration(Date.now() - spinnerStartTime);
239
+ process.stdout.write(`\r${c.clearLine} ${colors.accent}${frames[spinnerIndex]}${c.reset} ${message} ${c.dim}${elapsed}${c.reset}`);
240
+ spinnerIndex = (spinnerIndex + 1) % frames.length;
241
+ }, 80);
242
+ }
243
+
244
+ function stopSpinner(message, success = true) {
245
+ if (spinnerInterval) {
246
+ clearInterval(spinnerInterval);
247
+ spinnerInterval = null;
248
+ }
249
+ const elapsed = spinnerStartTime ? formatDuration(Date.now() - spinnerStartTime) : '';
250
+ const icon = success ? `${colors.shipGreen}${ICONS.check}${c.reset}` : `${colors.blockRed}${ICONS.cross}${c.reset}`;
251
+ process.stdout.write(`\r${c.clearLine} ${icon} ${message} ${c.dim}${elapsed}${c.reset}\n`);
252
+ process.stdout.write(c.showCursor);
253
+ spinnerStartTime = null;
254
+ }
255
+
256
+ // Compact banner for fix mode (fallback)
257
+ const SHIP_BANNER_FALLBACK = `
258
+ ${ansi.rgb(0, 255, 200)} ███████╗██╗ ██╗██╗██████╗ ${ansi.reset}
259
+ ${ansi.rgb(0, 230, 220)} ██╔════╝██║ ██║██║██╔══██╗${ansi.reset}
260
+ ${ansi.rgb(0, 200, 255)} ███████╗███████║██║██████╔╝${ansi.reset}
261
+ ${ansi.rgb(50, 150, 255)} ╚════██║██╔══██║██║██╔═══╝ ${ansi.reset}
262
+ ${ansi.rgb(100, 100, 255)} ███████║██║ ██║██║██║ ${ansi.reset}
263
+ ${ansi.rgb(150, 50, 255)} ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ${ansi.reset}
264
+ `;
265
+
266
+ function printBanner(compact = false, projectRoot = process.cwd()) {
267
+ if (compact) {
268
+ console.log(SHIP_BANNER_FALLBACK);
269
+ return;
270
+ }
271
+
272
+ try {
273
+ const cli = getCliOutput();
274
+ console.log(cli.renderFullHeader("ship", {
275
+ version: "4.0.0",
276
+ tier: "FREE",
277
+ target: projectRoot,
278
+ sessionId: cli.generateSessionId(),
279
+ }));
280
+ } catch {
281
+ console.log(BANNER_FALLBACK);
282
+ }
283
+ }
284
+
285
+ function printDivider(char = '─', width = 69, color = c.dim) {
286
+ console.log(`${color} ${char.repeat(width)}${c.reset}`);
287
+ }
288
+
289
+ function printSection(title, icon = '◆') {
290
+ console.log();
291
+ console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
292
+ printDivider();
293
+ }
294
+
295
+ function printSubSection(title) {
296
+ console.log();
297
+ console.log(` ${c.dim}${BOX.teeRight}${BOX.horizontal}${c.reset} ${c.bold}${title}${c.reset}`);
298
+ }
299
+
300
+ // ═══════════════════════════════════════════════════════════════════════════════
301
+ // VERDICT DISPLAY - THE HERO MOMENT
302
+ // ═══════════════════════════════════════════════════════════════════════════════
303
+
304
+ function getVerdictConfig(verdict, score, blockers, warnings) {
305
+ if (verdict === 'SHIP' || (score >= 90 && blockers === 0)) {
306
+ return {
307
+ verdict: 'SHIP',
308
+ icon: '🚀',
309
+ headline: 'CLEAR TO SHIP',
310
+ tagline: 'Your app is production ready!',
311
+ color: colors.shipGreen,
312
+ bgColor: bgRgb(0, 80, 50),
313
+ borderColor: rgb(0, 200, 120),
314
+ glow: rgb(0, 255, 150),
315
+ };
316
+ }
317
+
318
+ if (verdict === 'WARN' || (score >= 50 && blockers <= 2)) {
319
+ return {
320
+ verdict: 'WARN',
321
+ icon: '⚠️',
322
+ headline: 'REVIEW BEFORE SHIP',
323
+ tagline: `${warnings} warning${warnings !== 1 ? 's' : ''} to address`,
324
+ color: colors.warnAmber,
325
+ bgColor: bgRgb(80, 60, 0),
326
+ borderColor: rgb(200, 160, 0),
327
+ glow: rgb(255, 200, 0),
328
+ };
329
+ }
330
+
331
+ return {
332
+ verdict: 'BLOCK',
333
+ icon: '🛑',
334
+ headline: 'NOT SHIP READY',
335
+ tagline: `${blockers} blocker${blockers !== 1 ? 's' : ''} must be fixed`,
336
+ color: colors.blockRed,
337
+ bgColor: bgRgb(80, 20, 20),
338
+ borderColor: rgb(200, 60, 60),
339
+ glow: rgb(255, 80, 80),
340
+ };
341
+ }
342
+
343
+ function printVerdictCard(verdict, score, blockers, warnings, duration) {
344
+ const config = getVerdictConfig(verdict, score, blockers, warnings);
345
+ const w = 68; // Inner width
346
+
347
+ console.log();
348
+ console.log();
349
+
350
+ // Top border with glow effect
351
+ console.log(` ${config.borderColor}${BOX.dTopLeft}${BOX.dHorizontal.repeat(w)}${BOX.dTopRight}${c.reset}`);
352
+
353
+ // Empty line
354
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
355
+
356
+ // Verdict icon and headline
357
+ const headlineText = `${config.icon} ${config.headline}`;
358
+ const headlinePadded = padCenter(headlineText, w);
359
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${config.color}${c.bold}${headlinePadded}${c.reset}${config.borderColor}${BOX.dVertical}${c.reset}`);
360
+
361
+ // Tagline
362
+ const taglinePadded = padCenter(config.tagline, w);
363
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${c.dim}${taglinePadded}${c.reset}${config.borderColor}${BOX.dVertical}${c.reset}`);
364
+
365
+ // Empty line
366
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
367
+
368
+ // Score bar
369
+ const scoreLabel = `VIBE SCORE`;
370
+ const scoreValue = `${score}/100`;
371
+ const scoreBar = progressBar(score, 35, { color: config.color });
372
+ const scoreLine = ` ${scoreLabel} ${scoreBar} ${config.color}${c.bold}${scoreValue}${c.reset}`;
373
+ const scoreLinePadded = scoreLine + ' '.repeat(Math.max(0, w - 60));
374
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${scoreLinePadded}${config.borderColor}${BOX.dVertical}${c.reset}`);
375
+
376
+ // Empty line
377
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
378
+
379
+ // Stats row
380
+ const stats = [
381
+ { label: 'Blockers', value: blockers, color: blockers > 0 ? colors.blockRed : colors.shipGreen },
382
+ { label: 'Warnings', value: warnings, color: warnings > 0 ? colors.warnAmber : colors.shipGreen },
383
+ { label: 'Duration', value: formatDuration(duration), color: colors.accent },
384
+ ];
385
+
386
+ let statsLine = ' ';
387
+ for (const stat of stats) {
388
+ statsLine += `${c.dim}${stat.label}:${c.reset} ${stat.color}${c.bold}${stat.value}${c.reset} `;
389
+ }
390
+ const statsLinePadded = statsLine + ' '.repeat(Math.max(0, w - statsLine.length + 20));
391
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${statsLinePadded}${config.borderColor}${BOX.dVertical}${c.reset}`);
392
+
393
+ // Empty line
394
+ console.log(` ${config.borderColor}${BOX.dVertical}${c.reset}${' '.repeat(w)}${config.borderColor}${BOX.dVertical}${c.reset}`);
395
+
396
+ // Bottom border
397
+ console.log(` ${config.borderColor}${BOX.dBottomLeft}${BOX.dHorizontal.repeat(w)}${BOX.dBottomRight}${c.reset}`);
398
+
399
+ console.log();
400
+ }
401
+
402
+ // ═══════════════════════════════════════════════════════════════════════════════
403
+ // FINDINGS DISPLAY
404
+ // ═══════════════════════════════════════════════════════════════════════════════
405
+
406
+ function getSeverityStyle(severity) {
407
+ const styles = {
408
+ BLOCK: { color: colors.critical, bg: bgRgb(80, 20, 20), icon: '●', label: 'BLOCKER' },
409
+ critical: { color: colors.critical, bg: bgRgb(80, 20, 20), icon: '●', label: 'CRITICAL' },
410
+ WARN: { color: colors.warnAmber, bg: bgRgb(80, 60, 0), icon: '◐', label: 'WARNING' },
411
+ warning: { color: colors.warnAmber, bg: bgRgb(80, 60, 0), icon: '◐', label: 'WARNING' },
412
+ high: { color: colors.high, bg: bgRgb(80, 40, 20), icon: '○', label: 'HIGH' },
413
+ medium: { color: colors.medium, bg: bgRgb(60, 50, 0), icon: '○', label: 'MEDIUM' },
414
+ low: { color: colors.low, bg: bgRgb(20, 40, 60), icon: '○', label: 'LOW' },
415
+ info: { color: colors.info, bg: bgRgb(40, 40, 50), icon: '○', label: 'INFO' },
416
+ };
417
+ return styles[severity] || styles.info;
418
+ }
419
+
420
+ function getCategoryIcon(category) {
421
+ const icons = {
422
+ 'MissingRoute': ICONS.route,
423
+ 'EnvContract': ICONS.env,
424
+ 'EnvGap': ICONS.env,
425
+ 'FakeSuccess': ICONS.ghost,
426
+ 'GhostAuth': ICONS.auth,
427
+ 'StripeWebhook': ICONS.money,
428
+ 'PaidSurface': ICONS.money,
429
+ 'OwnerModeBypass': ICONS.lock,
430
+ 'DeadUI': ICONS.dead,
431
+ 'ContractDrift': ICONS.warning,
432
+ 'Security': ICONS.shield,
433
+ 'Auth': ICONS.lock,
434
+ 'Fake Code': ICONS.ghost,
435
+ };
436
+ return icons[category] || ICONS.bug;
437
+ }
438
+
439
+ function printFindingsBreakdown(findings) {
440
+ if (!findings || findings.length === 0) {
441
+ printSection('FINDINGS', ICONS.check);
442
+ console.log();
443
+ console.log(` ${colors.shipGreen}${c.bold}${ICONS.sparkle} No issues found! Your code is clean.${c.reset}`);
444
+ return;
445
+ }
446
+
447
+ // Group by category
448
+ const byCategory = {};
449
+ for (const f of findings) {
450
+ const cat = f.category || 'Other';
451
+ if (!byCategory[cat]) byCategory[cat] = [];
452
+ byCategory[cat].push(f);
453
+ }
454
+
455
+ const blockers = findings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
456
+ const warnings = findings.filter(f => f.severity === 'WARN' || f.severity === 'warning');
457
+
458
+ printSection(`FINDINGS (${blockers.length} blockers, ${warnings.length} warnings)`, ICONS.graph);
459
+ console.log();
460
+
461
+ // Summary by category
462
+ const categories = Object.entries(byCategory).sort((a, b) => {
463
+ const aBlockers = a[1].filter(f => f.severity === 'BLOCK').length;
464
+ const bBlockers = b[1].filter(f => f.severity === 'BLOCK').length;
465
+ return bBlockers - aBlockers;
466
+ });
467
+
468
+ for (const [category, catFindings] of categories) {
469
+ const catBlockers = catFindings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical').length;
470
+ const catWarnings = catFindings.filter(f => f.severity === 'WARN' || f.severity === 'warning').length;
471
+ const icon = getCategoryIcon(category);
472
+
473
+ const statusColor = catBlockers > 0 ? colors.blockRed : catWarnings > 0 ? colors.warnAmber : colors.shipGreen;
474
+ const statusIcon = catBlockers > 0 ? ICONS.cross : catWarnings > 0 ? ICONS.warning : ICONS.check;
475
+
476
+ console.log(` ${statusColor}${statusIcon}${c.reset} ${icon} ${c.bold}${category.padEnd(20)}${c.reset} ${catBlockers > 0 ? `${colors.blockRed}${catBlockers} blockers${c.reset}` : ''}${catWarnings > 0 ? ` ${colors.warnAmber}${catWarnings} warnings${c.reset}` : ''}`);
477
+ }
478
+ }
479
+
480
+ function printBlockerDetails(findings, maxShow = 8) {
481
+ const blockers = findings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
482
+
483
+ if (blockers.length === 0) return;
484
+
485
+ printSection(`BLOCKERS (${blockers.length})`, '🚨');
486
+ console.log();
487
+
488
+ for (const blocker of blockers.slice(0, maxShow)) {
489
+ const style = getSeverityStyle(blocker.severity);
490
+ const icon = getCategoryIcon(blocker.category);
491
+
492
+ // Severity badge
493
+ console.log(` ${style.bg}${c.bold} ${style.label} ${c.reset} ${icon} ${c.bold}${truncate(blocker.title, 50)}${c.reset}`);
494
+
495
+ // Details
496
+ if (blocker.why) {
497
+ console.log(` ${' '.repeat(10)} ${c.dim}${truncate(blocker.why, 55)}${c.reset}`);
498
+ }
499
+
500
+ // File location
501
+ if (blocker.evidence && blocker.evidence.length > 0) {
502
+ const ev = blocker.evidence[0];
503
+ const fileDisplay = `${path.basename(ev.file || '')}${ev.lines ? `:${ev.lines}` : ''}`;
504
+ console.log(` ${' '.repeat(10)} ${colors.accent}${ICONS.doc} ${fileDisplay}${c.reset}`);
505
+ }
506
+
507
+ // Fix hint
508
+ if (blocker.fixHints && blocker.fixHints.length > 0) {
509
+ console.log(` ${' '.repeat(10)} ${colors.shipGreen}${ICONS.arrow} ${truncate(blocker.fixHints[0], 50)}${c.reset}`);
510
+ }
511
+
512
+ console.log();
513
+ }
514
+
515
+ if (blockers.length > maxShow) {
516
+ console.log(` ${c.dim}... and ${blockers.length - maxShow} more blockers (see full report)${c.reset}`);
517
+ console.log();
518
+ }
519
+ }
520
+
521
+ // ═══════════════════════════════════════════════════════════════════════════════
522
+ // ROUTE TRUTH VISUALIZATION
523
+ // ═══════════════════════════════════════════════════════════════════════════════
524
+
525
+ function printRouteTruthMap(truthpack) {
526
+ if (!truthpack) return;
527
+
528
+ printSection('ROUTE TRUTH MAP', ICONS.map);
529
+ console.log();
530
+
531
+ const serverRoutes = truthpack.routes?.server?.length || 0;
532
+ const clientRefs = truthpack.routes?.clientRefs?.length || 0;
533
+ const envVars = truthpack.env?.vars?.length || 0;
534
+ const envDeclared = truthpack.env?.declared?.length || 0;
535
+
536
+ // Routes coverage
537
+ const routeCoverage = serverRoutes > 0 ? Math.round((clientRefs / serverRoutes) * 100) : 100;
538
+ const routeColor = routeCoverage >= 80 ? colors.shipGreen : routeCoverage >= 50 ? colors.warnAmber : colors.blockRed;
539
+
540
+ console.log(` ${ICONS.route} ${c.bold}Routes${c.reset}`);
541
+ console.log(` Server: ${colors.accent}${serverRoutes}${c.reset} defined`);
542
+ console.log(` Client: ${colors.accent}${clientRefs}${c.reset} references`);
543
+ console.log(` Coverage: ${progressBar(routeCoverage, 20)} ${routeColor}${routeCoverage}%${c.reset}`);
544
+ console.log();
545
+
546
+ // Env coverage
547
+ const envCoverage = envVars > 0 ? Math.round((envDeclared / envVars) * 100) : 100;
548
+ const envColor = envCoverage >= 80 ? colors.shipGreen : envCoverage >= 50 ? colors.warnAmber : colors.blockRed;
549
+
550
+ console.log(` ${ICONS.env} ${c.bold}Environment${c.reset}`);
551
+ console.log(` Used: ${colors.accent}${envVars}${c.reset} variables`);
552
+ console.log(` Declared: ${colors.accent}${envDeclared}${c.reset} in .env`);
553
+ console.log(` Coverage: ${progressBar(envCoverage, 20)} ${envColor}${envCoverage}%${c.reset}`);
554
+ }
555
+
556
+ // ═══════════════════════════════════════════════════════════════════════════════
557
+ // PROOF GRAPH VISUALIZATION
558
+ // ═══════════════════════════════════════════════════════════════════════════════
559
+
560
+ function printProofGraph(proofGraph) {
561
+ if (!proofGraph || !proofGraph.summary) return;
562
+
563
+ printSection('PROOF GRAPH', ICONS.graph);
564
+ console.log();
565
+
566
+ const { summary } = proofGraph;
567
+
568
+ // Confidence gauge
569
+ const confidence = Math.round((summary.confidence || 0) * 100);
570
+ const confColor = confidence >= 80 ? colors.shipGreen : confidence >= 50 ? colors.warnAmber : colors.blockRed;
571
+
572
+ console.log(` ${c.bold}Analysis Confidence${c.reset}`);
573
+ console.log(` ${progressBar(confidence, 40)} ${confColor}${c.bold}${confidence}%${c.reset}`);
574
+ console.log();
575
+
576
+ // Claims summary
577
+ console.log(` ${c.dim}Claims:${c.reset} ${colors.shipGreen}${summary.verifiedClaims || 0}${c.reset} verified ${c.dim}/${c.reset} ${colors.blockRed}${summary.failedClaims || 0}${c.reset} failed ${c.dim}of${c.reset} ${summary.totalClaims || 0} total`);
578
+ console.log(` ${c.dim}Gaps:${c.reset} ${summary.gaps || 0} identified`);
579
+ console.log(` ${c.dim}Risk Score:${c.reset} ${summary.riskScore || 0}/100`);
580
+
581
+ // Top blockers from proof graph
582
+ if (proofGraph.topBlockers && proofGraph.topBlockers.length > 0) {
583
+ console.log();
584
+ console.log(` ${c.bold}Top Unverified Claims:${c.reset}`);
585
+ for (const blocker of proofGraph.topBlockers.slice(0, 3)) {
586
+ console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} ${truncate(blocker.assertion, 50)}`);
587
+ }
588
+ }
589
+ }
590
+
591
+ // ═══════════════════════════════════════════════════════════════════════════════
592
+ // BADGE DISPLAY
593
+ // ═══════════════════════════════════════════════════════════════════════════════
594
+
595
+ function printBadgeOutput(projectPath, verdict, score) {
596
+ const projectName = path.basename(projectPath);
597
+ const projectId = projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
598
+
599
+ const config = getVerdictConfig(verdict, score, verdict === 'BLOCK' ? 1 : 0, verdict === 'WARN' ? 1 : 0);
600
+
601
+ printSection('SHIP BADGE', '📛');
602
+ console.log();
603
+
604
+ // Badge preview box
605
+ const badgeText = `vibecheck | ${verdict} | ${score}`;
606
+ console.log(` ${config.bgColor}${c.bold} ${badgeText} ${c.reset}`);
607
+ console.log();
608
+
609
+ const badgeUrl = `https://vibecheck.dev/badge/${projectId}.svg`;
610
+ const reportUrl = `https://vibecheck.dev/report/${projectId}`;
611
+ const markdown = `[![Vibecheck](${badgeUrl})](${reportUrl})`;
612
+
613
+ console.log(` ${c.dim}Badge URL:${c.reset}`);
614
+ console.log(` ${colors.accent}${badgeUrl}${c.reset}`);
615
+ console.log();
616
+ console.log(` ${c.dim}Report URL:${c.reset}`);
617
+ console.log(` ${colors.accent}${reportUrl}${c.reset}`);
618
+ console.log();
619
+ console.log(` ${c.dim}Add to README.md:${c.reset}`);
620
+ console.log(` ${colors.shipGreen}${markdown}${c.reset}`);
621
+
622
+ return { projectId, badgeUrl, reportUrl, markdown };
623
+ }
624
+
625
+ // ═══════════════════════════════════════════════════════════════════════════════
626
+ // FIX MODE DISPLAY
627
+ // ═══════════════════════════════════════════════════════════════════════════════
628
+
629
+ function printFixModeHeader() {
630
+ console.log();
631
+ console.log(` ${bgRgb(40, 80, 120)}${c.bold} ${ICONS.wrench} AUTO-FIX MODE ${c.reset}`);
632
+ console.log();
633
+ }
634
+
635
+ function printFixResults(fixResults) {
636
+ if (!fixResults) return;
637
+
638
+ printSection('FIX RESULTS', ICONS.wrench);
639
+ console.log();
640
+
641
+ if (fixResults.errors && fixResults.errors.length > 0) {
642
+ for (const err of fixResults.errors) {
643
+ console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} ${err}`);
644
+ }
645
+ console.log();
646
+ }
647
+
648
+ const actions = [
649
+ { done: fixResults.envExampleCreated, label: 'Created .env.example', icon: ICONS.env },
650
+ { done: fixResults.gitignoreUpdated, label: 'Updated .gitignore', icon: ICONS.shield },
651
+ { done: fixResults.fixesMdCreated, label: 'Generated fixes.md', icon: ICONS.doc },
652
+ ];
653
+
654
+ for (const action of actions) {
655
+ const icon = action.done ? `${colors.shipGreen}${ICONS.check}` : `${c.dim}${ICONS.bullet}`;
656
+ const label = action.done ? c.reset + action.label : c.dim + action.label + c.reset;
657
+ console.log(` ${icon}${c.reset} ${action.icon} ${label}`);
658
+ }
659
+
660
+ if (fixResults.secretsFound && fixResults.secretsFound.length > 0) {
661
+ console.log();
662
+ console.log(` ${c.bold}Secrets to migrate:${c.reset}`);
663
+ for (const secret of fixResults.secretsFound.slice(0, 5)) {
664
+ console.log(` ${ICONS.key} ${secret.varName} ${c.dim}(${secret.type})${c.reset}`);
665
+ }
666
+ }
667
+
668
+ console.log();
669
+ console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${c.bold}Safe fixes applied!${c.reset}`);
670
+ console.log(` ${c.dim}Review changes and follow instructions in ${colors.accent}.vibecheck/fixes.md${c.reset}`);
671
+ }
672
+
673
+ // ═══════════════════════════════════════════════════════════════════════════════
674
+ // HELP DISPLAY
675
+ // ═══════════════════════════════════════════════════════════════════════════════
676
+
677
+ function printHelp(showBanner = true) {
678
+ if (showBanner && shouldShowBanner({})) {
679
+ console.log(BANNER);
680
+ }
681
+ console.log(`
682
+ ${ansi.bold}Usage:${ansi.reset} vibecheck ship ${ansi.dim}(go)${ansi.reset} [options]
683
+
684
+ ${ansi.bold}Aliases:${ansi.reset} ${ansi.dim}go${ansi.reset}
685
+
686
+ ${ansi.bold}The One Command${ansi.reset} — Get a ship verdict: ${colors.success}SHIP${ansi.reset} | ${colors.warning}WARN${ansi.reset} | ${colors.error}BLOCK${ansi.reset}
687
+
688
+ ${ansi.bold}Verdict Options:${ansi.reset}
689
+ ${colors.accent}--strict${ansi.reset} Treat warnings as blockers
690
+ ${colors.accent}--why${ansi.reset} Show detailed "why tree" (top blockers + evidence)
691
+ ${colors.accent}--cached${ansi.reset} Use cached verdict if repo unchanged
692
+
693
+ ${ansi.bold}Output Options:${ansi.reset}
694
+ ${colors.accent}--json${ansi.reset} Output full manifest as JSON
695
+ ${colors.accent}--ci${ansi.reset} Machine output (KEY=VALUE format for CI/CD)
696
+ ${colors.accent}--receipt${ansi.reset} Single-line receipt (parseable)
697
+ ${colors.accent}--manifest${ansi.reset} Output full signed manifest
698
+ ${colors.accent}--badge, -b${ansi.reset} Generate embeddable badge for README
699
+
700
+ ${ansi.bold}Fix Options:${ansi.reset}
701
+ ${colors.accent}--fix, -f${ansi.reset} Try safe mechanical fixes ${ansi.dim}(shows plan first)${ansi.reset}
702
+ ${colors.accent}--assist${ansi.reset} Generate AI mission prompts for complex issues
703
+
704
+ ${ansi.bold}General Options:${ansi.reset}
705
+ ${colors.accent}--path, -p${ansi.reset} Project path ${ansi.dim}(default: current directory)${ansi.reset}
706
+ ${colors.accent}--verbose, -v${ansi.reset} Show detailed progress
707
+ ${colors.accent}--legacy${ansi.reset} Use legacy (non-gate) evaluation
708
+ ${colors.accent}--help, -h${ansi.reset} Show this help
709
+
710
+ ${ansi.bold}Exit Codes:${ansi.reset}
711
+ ${colors.success}0${ansi.reset} SHIP — Ready to ship
712
+ ${colors.warning}1${ansi.reset} WARN — Warnings found, review recommended
713
+ ${colors.error}2${ansi.reset} BLOCK — Blockers found, must fix before shipping
714
+
715
+ ${ansi.bold}CI Integration:${ansi.reset}
716
+ ${ansi.dim}# GitHub Actions - set output variables${ansi.reset}
717
+ vibecheck ship --ci >> $$GITHUB_OUTPUT
718
+
719
+ ${ansi.dim}# Get receipt for artifact storage${ansi.reset}
720
+ vibecheck ship --receipt > .vibecheck/receipt.txt
721
+
722
+ ${ansi.dim}# Cache-aware evaluation (fast re-runs)${ansi.reset}
723
+ vibecheck ship --cached
724
+
725
+ ${ansi.bold}Examples:${ansi.reset}
726
+ ${ansi.dim}# Quick ship check${ansi.reset}
727
+ vibecheck ship
728
+
729
+ ${ansi.dim}# Show why a verdict was reached${ansi.reset}
730
+ vibecheck ship --why
731
+
732
+ ${ansi.dim}# Auto-fix what can be fixed${ansi.reset}
733
+ vibecheck ship --fix
734
+
735
+ ${ansi.dim}# Generate README badge${ansi.reset}
736
+ vibecheck ship --badge
737
+
738
+ ${ansi.dim}# Strict CI mode (warnings = failure)${ansi.reset}
739
+ vibecheck ship --strict --ci
740
+
741
+ ${ansi.bold}Artifacts Generated:${ansi.reset}
742
+ ${ansi.dim}.vibecheck/ship/manifest.json${ansi.reset} Full signed manifest
743
+ ${ansi.dim}.vibecheck/ship/receipt.txt${ansi.reset} CI-friendly receipt
744
+ ${ansi.dim}.vibecheck/ship/github-output.txt${ansi.reset} GitHub Actions output
745
+ `);
746
+ }
747
+
748
+ // ═══════════════════════════════════════════════════════════════════════════════
749
+ // PROOF GRAPH BUILDER
750
+ // ═══════════════════════════════════════════════════════════════════════════════
751
+
752
+ function buildProofGraph(findings, truthpack, root) {
753
+ const claims = [];
754
+ let claimId = 0;
755
+
756
+ for (const finding of findings) {
757
+ const claim = {
758
+ id: `claim-${++claimId}`,
759
+ type: getClaimType(finding.category),
760
+ assertion: finding.title || finding.message,
761
+ verified: finding.severity !== 'BLOCK',
762
+ confidence: finding.confidence === 'high' ? 0.9 : finding.confidence === 'medium' ? 0.7 : 0.5,
763
+ evidence: (finding.evidence || []).map((e, i) => ({
764
+ id: `evidence-${claimId}-${i}`,
765
+ type: 'file_citation',
766
+ file: e.file,
767
+ line: parseInt(e.lines?.split('-')[0]) || e.line || 1,
768
+ snippet: e.snippetHash || '',
769
+ strength: 0.8,
770
+ verifiedAt: new Date().toISOString(),
771
+ method: 'static'
772
+ })),
773
+ gaps: [{
774
+ id: `gap-${claimId}`,
775
+ type: getGapType(finding.category),
776
+ description: finding.why || finding.title,
777
+ severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
778
+ suggestion: (finding.fixHints || [])[0] || ''
779
+ }],
780
+ severity: finding.severity === 'BLOCK' ? 'critical' : finding.severity === 'WARN' ? 'medium' : 'low',
781
+ file: finding.evidence?.[0]?.file || '',
782
+ line: parseInt(finding.evidence?.[0]?.lines?.split('-')[0]) || 1
783
+ };
784
+ claims.push(claim);
785
+ }
786
+
787
+ const verifiedClaims = claims.filter(c => c.verified);
788
+ const failedClaims = claims.filter(c => !c.verified);
789
+ const allGaps = claims.flatMap(c => c.gaps);
790
+
791
+ const riskScore = Math.min(100, failedClaims.reduce((sum, c) => {
792
+ if (c.severity === 'critical') return sum + 30;
793
+ if (c.severity === 'high') return sum + 20;
794
+ if (c.severity === 'medium') return sum + 10;
795
+ return sum + 5;
796
+ }, 0));
797
+
798
+ const confidence = claims.length > 0
799
+ ? claims.reduce((sum, c) => sum + c.confidence, 0) / claims.length
800
+ : 1.0;
801
+
802
+ return {
803
+ version: '1.0.0',
804
+ generatedAt: new Date().toISOString(),
805
+ projectPath: root,
806
+ claims,
807
+ summary: {
808
+ totalClaims: claims.length,
809
+ verifiedClaims: verifiedClaims.length,
810
+ failedClaims: failedClaims.length,
811
+ gaps: allGaps.length,
812
+ riskScore,
813
+ confidence
814
+ },
815
+ verdict: findings.some(f => f.severity === 'BLOCK') ? 'BLOCK' :
816
+ findings.some(f => f.severity === 'WARN') ? 'WARN' : 'SHIP',
817
+ topBlockers: failedClaims.filter(c => c.severity === 'critical' || c.severity === 'high').slice(0, 5),
818
+ topGaps: allGaps.filter(g => g.severity === 'critical' || g.severity === 'high').slice(0, 5)
819
+ };
820
+ }
821
+
822
+ function getClaimType(category) {
823
+ const map = {
824
+ 'MissingRoute': 'route_exists',
825
+ 'EnvContract': 'env_declared',
826
+ 'FakeSuccess': 'success_verified',
827
+ 'GhostAuth': 'auth_protected',
828
+ 'StripeWebhook': 'billing_enforced',
829
+ 'PaidSurface': 'billing_enforced',
830
+ 'OwnerModeBypass': 'billing_enforced',
831
+ 'DeadUI': 'ui_wired',
832
+ 'ContractDrift': 'contract_satisfied'
833
+ };
834
+ return map[category] || 'ui_wired';
835
+ }
836
+
837
+ function getGapType(category) {
838
+ const map = {
839
+ 'MissingRoute': 'missing_handler',
840
+ 'EnvContract': 'missing_verification',
841
+ 'FakeSuccess': 'missing_verification',
842
+ 'GhostAuth': 'missing_gate',
843
+ 'StripeWebhook': 'missing_verification',
844
+ 'PaidSurface': 'missing_gate',
845
+ 'OwnerModeBypass': 'missing_gate',
846
+ 'DeadUI': 'missing_handler',
847
+ 'ContractDrift': 'contract_drift'
848
+ };
849
+ return map[category] || 'untested_path';
850
+ }
851
+
852
+ // ═══════════════════════════════════════════════════════════════════════════════
853
+ // ARGS PARSER
854
+ // ═══════════════════════════════════════════════════════════════════════════════
855
+
856
+ function parseArgs(args) {
857
+ // Parse global flags first
858
+ const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
859
+
860
+ const opts = {
861
+ fix: false,
862
+ path: globalFlags.path || ".",
863
+ verbose: globalFlags.verbose || false,
864
+ json: globalFlags.json || false,
865
+ badge: false,
866
+ assist: false,
867
+ strict: globalFlags.strict || false,
868
+ ci: globalFlags.ci || false,
869
+ mode: null, // "scan" for scan mode
870
+ withRuntime: false, // merge runtime results
871
+ help: globalFlags.help || false,
872
+ noBanner: globalFlags.noBanner || false,
873
+ quiet: globalFlags.quiet || false,
874
+ // NEW: Gate mode options
875
+ useGate: true, // Use new gate system by default
876
+ cached: false, // Use cached verdict if valid
877
+ manifest: false, // Output full manifest
878
+ receipt: false, // Output CI receipt
879
+ whyTree: false, // Show detailed why tree
880
+ };
881
+
882
+ // Parse command-specific args
883
+ for (let i = 0; i < cleanArgs.length; i++) {
884
+ const a = cleanArgs[i];
885
+ if (a === "--fix" || a === "-f") opts.fix = true;
886
+ else if (a === "--badge" || a === "-b") opts.badge = true;
887
+ else if (a === "--assist") opts.assist = true;
888
+ else if (a === "--mode") opts.mode = cleanArgs[++i];
889
+ else if (a === "--with") {
890
+ const next = cleanArgs[++i];
891
+ if (next === "runtime") opts.withRuntime = true;
892
+ }
893
+ else if (a.startsWith("--path=")) opts.path = a.split("=")[1];
894
+ else if (a === "--path" || a === "-p") opts.path = args[++i];
895
+ // NEW: Gate options
896
+ else if (a === "--legacy") opts.useGate = false;
897
+ else if (a === "--cached") opts.cached = true;
898
+ else if (a === "--manifest") opts.manifest = true;
899
+ else if (a === "--receipt") opts.receipt = true;
900
+ else if (a === "--why") opts.whyTree = true;
901
+ }
902
+
903
+ return opts;
904
+ }
905
+
906
+ // ═══════════════════════════════════════════════════════════════════════════════
907
+ // MAIN SHIP FUNCTION
908
+ // ═══════════════════════════════════════════════════════════════════════════════
909
+
910
+ async function runShip(args, context = {}) {
911
+ // Extract runId from context or generate new one
912
+ const runId = context.runId || generateRunId();
913
+ const startTime = context.startTime || new Date().toISOString();
914
+
915
+ const opts = parseArgs(args);
916
+ const executionStart = Date.now();
917
+
918
+ // Show help if requested
919
+ if (opts.help) {
920
+ printHelp(shouldShowBanner(opts));
921
+ return 0;
922
+ }
923
+
924
+ // ═══════════════════════════════════════════════════════════════════════════
925
+ // NEW: Use Ship Gate (v2) system for deterministic, evidence-backed verdicts
926
+ // ═══════════════════════════════════════════════════════════════════════════
927
+ if (opts.useGate && !opts.fix && !opts.assist) {
928
+ try {
929
+ const gateResult = await runShipGate(opts, context);
930
+ if (gateResult !== null) {
931
+ return gateResult;
932
+ }
933
+ // If null returned, fall through to legacy mode
934
+ } catch (err) {
935
+ if (opts.verbose) {
936
+ console.error(` Gate evaluation failed: ${err.message}, using legacy mode`);
937
+ }
938
+ // Fall through to legacy mode
939
+ }
940
+ }
941
+
942
+ // Entitlement check
943
+ try {
944
+ await enforceLimit('scans');
945
+ await enforceFeature('ship');
946
+ if (opts.fix) {
947
+ await enforceFeature('fix');
948
+ }
949
+ } catch (err) {
950
+ if (err.code === 'LIMIT_EXCEEDED' || err.code === 'FEATURE_NOT_AVAILABLE') {
951
+ console.error(`\n ${colors.error}${icons.error}${ansi.reset} ${err.upgradePrompt || err.message}\n`);
952
+ return EXIT.TIER_REQUIRED;
953
+ }
954
+ throw err;
955
+ }
956
+
957
+ await trackUsage('scans');
958
+
959
+ const projectPath = path.resolve(opts.path);
960
+ const outputDir = path.join(projectPath, ".vibecheck");
961
+ const projectName = path.basename(projectPath);
962
+
963
+ // Print banner (respects --no-banner, VIBECHECK_NO_BANNER, --ci, --quiet, --json)
964
+ if (shouldShowBanner(opts)) {
965
+ printBanner();
966
+ console.log(` ${ansi.dim}Project:${ansi.reset} ${ansi.bold}${projectName}${ansi.reset}`);
967
+ console.log(` ${ansi.dim}Path:${ansi.reset} ${projectPath}`);
968
+ console.log();
969
+ }
970
+
971
+ let results = {
972
+ score: 100,
973
+ grade: "A",
974
+ canShip: true,
975
+ deductions: [],
976
+ blockers: [],
977
+ warnings: [],
978
+ findings: [],
979
+ truthpack: null,
980
+ proofGraph: null,
981
+ };
982
+
983
+ // Initialize spinner outside try block so it's in scope for catch
984
+ let spinner;
985
+ if (!opts.json && !opts.ci) {
986
+ spinner = new Spinner({ color: colors.accent });
987
+ }
988
+
989
+ try {
990
+
991
+ // Phase 1: Production Integrity Check
992
+ if (spinner) spinner.start('Checking production integrity...');
993
+
994
+ try {
995
+ const { auditProductionIntegrity } = require(
996
+ path.join(__dirname, "../../scripts/audit-production-integrity.js"),
997
+ );
998
+ const { results: integrityResults, integrity } = await auditProductionIntegrity(projectPath);
999
+ results.score = integrity.score;
1000
+ results.grade = integrity.grade;
1001
+ results.canShip = integrity.canShip;
1002
+ results.deductions = integrity.deductions;
1003
+ results.integrity = integrityResults;
1004
+ } catch (err) {
1005
+ if (opts.verbose) console.warn(` ${ansi.dim}Integrity check skipped: ${err.message}${ansi.reset}`);
1006
+ }
1007
+
1008
+ if (spinner) spinner.succeed('Production integrity checked');
1009
+
1010
+ // Phase 2: Route Truth Analysis
1011
+ if (spinner) spinner.start('Building route truth map...');
1012
+
1013
+ const fastifyEntry = detectFastifyEntry(projectPath);
1014
+ const truthpack = await buildTruthpackSmart({ repoRoot: projectPath, fastifyEntry });
1015
+ writeTruthpack(projectPath, truthpack);
1016
+ results.truthpack = truthpack;
1017
+
1018
+ // Run all analyzers in parallel for performance (independent analyzers)
1019
+ // Group 1: File-based analyzers (can run fully in parallel)
1020
+ const fileAnalyzerPromises = [
1021
+ Promise.resolve(findFakeSuccess(projectPath)),
1022
+ Promise.resolve(findOptimisticNoRollback(projectPath)),
1023
+ Promise.resolve(findSilentCatch(projectPath)),
1024
+ Promise.resolve(findDeadUI(projectPath)),
1025
+ Promise.resolve(findOwnerModeBypass(projectPath)),
1026
+ ];
1027
+
1028
+ // Group 2: Truthpack-based analyzers (depend on truthpack but not each other)
1029
+ const truthpackAnalyzerPromises = [
1030
+ Promise.resolve(findMissingRoutes(truthpack)),
1031
+ Promise.resolve(findMethodMismatch(truthpack)),
1032
+ Promise.resolve(findEnvGaps(truthpack)),
1033
+ Promise.resolve(findGhostAuth(truthpack, projectPath)),
1034
+ Promise.resolve(findStripeWebhookViolations(truthpack)),
1035
+ Promise.resolve(findPaidSurfaceNotEnforced(truthpack)),
1036
+ ];
1037
+
1038
+ // Run both groups in parallel
1039
+ const [fileResults, truthpackResults] = await Promise.all([
1040
+ Promise.all(fileAnalyzerPromises),
1041
+ Promise.all(truthpackAnalyzerPromises),
1042
+ ]);
1043
+
1044
+ // Flatten results
1045
+ const allFindings = [
1046
+ ...fileResults.flat(),
1047
+ ...truthpackResults.flat(),
1048
+ // Runtime findings (if requested)
1049
+ ...(opts.withRuntime ? findingsFromReality(projectPath) : [])
1050
+ ];
1051
+
1052
+ // Contract drift detection
1053
+ if (hasContracts(projectPath)) {
1054
+ const contracts = loadContracts(projectPath);
1055
+ const driftFindings = findContractDrift(contracts, truthpack);
1056
+ allFindings.push(...driftFindings);
1057
+ }
1058
+
1059
+ results.findings = allFindings;
1060
+
1061
+ if (spinner) spinner.succeed(`Route truth mapped (${truthpack.routes?.server?.length || 0} routes)`);
1062
+
1063
+ // Phase 3: Build Proof Graph
1064
+ if (spinner) spinner.start('Building proof graph...');
1065
+
1066
+ const proofGraph = buildProofGraph(allFindings, truthpack, projectPath);
1067
+ results.proofGraph = proofGraph;
1068
+
1069
+ if (spinner) spinner.succeed(`Proof graph built (${proofGraph.summary.totalClaims} claims)`);
1070
+
1071
+ // Calculate final verdict
1072
+ const blockers = allFindings.filter(f => f.severity === 'BLOCK' || f.severity === 'critical');
1073
+ const warnings = allFindings.filter(f => f.severity === 'WARN' || f.severity === 'warning');
1074
+
1075
+ results.blockers = blockers;
1076
+ results.warnings = warnings;
1077
+
1078
+ // Apply strict mode
1079
+ if (opts.strict && warnings.length > 0) {
1080
+ results.canShip = false;
1081
+ }
1082
+
1083
+ if (blockers.length > 0) {
1084
+ results.canShip = false;
1085
+ }
1086
+
1087
+ // Deduct score for findings
1088
+ for (const finding of allFindings) {
1089
+ if (finding.severity === 'BLOCK') {
1090
+ results.score = Math.max(0, results.score - 15);
1091
+ } else if (finding.severity === 'WARN') {
1092
+ results.score = Math.max(0, results.score - 5);
1093
+ }
1094
+ }
1095
+
1096
+ const verdict = results.canShip ? 'SHIP' : blockers.length > 0 ? 'BLOCK' : 'WARN';
1097
+ const duration = Date.now() - startTime;
1098
+
1099
+ // ═══════════════════════════════════════════════════════════════════════════
1100
+ // OUTPUT
1101
+ // ═══════════════════════════════════════════════════════════════════════════
1102
+
1103
+ // JSON output mode
1104
+ if (opts.json) {
1105
+ const output = createJsonOutput({
1106
+ runId,
1107
+ command: "ship",
1108
+ startTime,
1109
+ exitCode: getExitCode(verdict),
1110
+ verdict,
1111
+ result: {
1112
+ verdict,
1113
+ score: results.score,
1114
+ grade: results.grade,
1115
+ canShip: results.canShip,
1116
+ summary: {
1117
+ blockers: blockers.length,
1118
+ warnings: warnings.length,
1119
+ total: allFindings.length,
1120
+ },
1121
+ findings: allFindings,
1122
+ proofGraph: proofGraph.summary,
1123
+ duration: Date.now() - executionStart,
1124
+ },
1125
+ tier: getCurrentTier(),
1126
+ version: require("../../package.json").version,
1127
+ artifacts: [
1128
+ {
1129
+ type: "report",
1130
+ path: path.join(outputDir, "report.json"),
1131
+ description: "Ship report with findings"
1132
+ },
1133
+ {
1134
+ type: "proof",
1135
+ path: path.join(outputDir, "proof-graph.json"),
1136
+ description: "Proof graph analysis"
1137
+ }
1138
+ ]
1139
+ });
1140
+
1141
+ writeJsonOutput(output, opts.output);
1142
+
1143
+ // Save artifacts
1144
+ const reportPath = saveArtifact(runId, "report", {
1145
+ ...output.result,
1146
+ truthpack,
1147
+ integrity: results.integrity
1148
+ });
1149
+ const proofPath = saveArtifact(runId, "proof-graph", proofGraph);
1150
+
1151
+ return getExitCode(verdict);
1152
+ }
1153
+
1154
+ // CI output mode (minimal)
1155
+ if (opts.ci) {
1156
+ console.log(`VERDICT=${verdict}`);
1157
+ console.log(`SCORE=${results.score}`);
1158
+ console.log(`BLOCKERS=${blockers.length}`);
1159
+ console.log(`WARNINGS=${warnings.length}`);
1160
+
1161
+ // Save CI artifacts
1162
+ saveArtifact(runId, "ci-summary", {
1163
+ verdict,
1164
+ score: results.score,
1165
+ blockers: blockers.length,
1166
+ warnings: warnings.length,
1167
+ timestamp: new Date().toISOString()
1168
+ });
1169
+
1170
+ return getExitCode(verdict);
1171
+ }
1172
+
1173
+ // Fix mode
1174
+ let fixResults = null;
1175
+ if (opts.fix) {
1176
+ if (spinner) spinner.start('Applying safe fixes...');
1177
+ fixResults = await runAutoFix(projectPath, results, outputDir, allFindings);
1178
+ if (spinner) spinner.succeed('Safe fixes applied');
1179
+ }
1180
+
1181
+ // Human-readable output using enterprise ship-output module
1182
+ const result = {
1183
+ verdict,
1184
+ score: results.score,
1185
+ findings: allFindings,
1186
+ blockers,
1187
+ warnings,
1188
+ truthpack,
1189
+ proofGraph,
1190
+ fixResults,
1191
+ duration: Date.now() - executionStart,
1192
+ };
1193
+
1194
+ // Get current tier for output formatting
1195
+ const currentTier = context?.authInfo?.access?.tier || getCurrentTier() || "free";
1196
+
1197
+ // Use enterprise format
1198
+ console.log(formatShipOutput(result, {
1199
+ tier: currentTier,
1200
+ }));
1201
+
1202
+ // Badge file generation (STARTER+ only)
1203
+ if (opts.badge) {
1204
+ const isVerified = opts.withRuntime && (currentTier === 'pro' || currentTier === 'compliance');
1205
+ const { data: badgeData } = renderBadgeOutput(projectPath, verdict, results.score, {
1206
+ tier: currentTier,
1207
+ isVerified
1208
+ });
1209
+
1210
+ // Save badge info
1211
+ fs.mkdirSync(outputDir, { recursive: true });
1212
+ fs.writeFileSync(
1213
+ path.join(outputDir, 'badge.json'),
1214
+ JSON.stringify({
1215
+ ...badgeData,
1216
+ verdict,
1217
+ score: results.score,
1218
+ tier: currentTier,
1219
+ isVerified,
1220
+ generatedAt: new Date().toISOString()
1221
+ }, null, 2)
1222
+ );
1223
+ }
1224
+
1225
+ // Earned upsell: Badge withheld when verdict != SHIP
1226
+ if (!results.canShip) {
1227
+ const currentTier = context?.authInfo?.access?.tier || "free";
1228
+ if (entitlements.tierMeetsMinimum(currentTier, "starter")) {
1229
+ // User has badge access but verdict prevents it
1230
+ console.log(upsell.formatEarnedUpsell({
1231
+ cmd: "ship",
1232
+ verdict,
1233
+ topIssues: blockers.slice(0, 3),
1234
+ withheldArtifact: "badge",
1235
+ currentTier,
1236
+ suggestedCmd: currentTier === "free" ? "vibecheck fix --plan-only" : "vibecheck fix",
1237
+ }));
1238
+ }
1239
+ }
1240
+
1241
+ // Emit audit event
1242
+ emitShipCheck(projectPath, results.canShip ? 'success' : 'failure', {
1243
+ score: results.score,
1244
+ grade: results.grade,
1245
+ canShip: results.canShip,
1246
+ issueCount: allFindings.length,
1247
+ });
1248
+
1249
+ // Write artifacts (no HTML report - use 'vibecheck report' command instead)
1250
+ try {
1251
+ const { writeArtifacts } = require("./utils");
1252
+ writeArtifacts(outputDir, results, { skipHtmlReport: true });
1253
+ } catch {}
1254
+
1255
+ // Exit code: 0=SHIP, 1=WARN, 2=BLOCK
1256
+ const exitCode = getExitCode(verdict);
1257
+
1258
+ // Save final results
1259
+ saveArtifact(runId, "summary", {
1260
+ verdict,
1261
+ score: results.score,
1262
+ canShip: results.canShip,
1263
+ exitCode,
1264
+ timestamp: new Date().toISOString()
1265
+ });
1266
+
1267
+ return exitCode;
1268
+
1269
+ } catch (error) {
1270
+ if (spinner) spinner.fail(`Ship check failed: ${error.message}`);
1271
+
1272
+ console.error(`\n ${colors.error}${icons.error}${ansi.reset} ${ansi.bold}Error:${ansi.reset} ${error.message}`);
1273
+ if (opts.verbose) {
1274
+ console.error(` ${ansi.dim}${error.stack}${ansi.reset}`);
1275
+ }
1276
+
1277
+ return EXIT.INTERNAL_ERROR;
1278
+ }
1279
+ }
1280
+
1281
+ // ═══════════════════════════════════════════════════════════════════════════════
1282
+ // AUTO-FIX (Placeholder - would import from original)
1283
+ // ═══════════════════════════════════════════════════════════════════════════════
1284
+
1285
+ async function runAutoFix(projectPath, results, outputDir, findings) {
1286
+ // This would be the full auto-fix implementation from the original
1287
+ // Keeping it as a placeholder for now
1288
+ const fixResults = {
1289
+ envExampleCreated: false,
1290
+ gitignoreUpdated: false,
1291
+ fixesMdCreated: false,
1292
+ secretsFound: [],
1293
+ errors: [],
1294
+ };
1295
+
1296
+ fs.mkdirSync(outputDir, { recursive: true });
1297
+
1298
+ // Generate fixes.md with modern findings
1299
+ const fixesMdPath = path.join(outputDir, "fixes.md");
1300
+ let md = "# 🔧 vibecheck Fix Guide\n\n";
1301
+ md += `Generated: ${new Date().toISOString()}\n\n`;
1302
+ md += `**${findings.length} issues found**\n\n---\n\n`;
1303
+
1304
+ for (const finding of findings) {
1305
+ md += `## ${finding.category}: ${finding.title}\n\n`;
1306
+ md += `**Severity:** ${finding.severity}\n\n`;
1307
+ if (finding.why) md += `**Why:** ${finding.why}\n\n`;
1308
+ if (finding.fixHints?.length) {
1309
+ md += "**Fix:**\n";
1310
+ for (const hint of finding.fixHints) {
1311
+ md += `- ${hint}\n`;
1312
+ }
1313
+ md += "\n";
1314
+ }
1315
+ md += "---\n\n";
1316
+ }
1317
+
1318
+ fs.writeFileSync(fixesMdPath, md);
1319
+ fixResults.fixesMdCreated = true;
1320
+
1321
+ return fixResults;
1322
+ }
1323
+
1324
+ // ═══════════════════════════════════════════════════════════════════════════════
1325
+ // SHIP GATE (v2) - Deterministic, Evidence-backed Verdict
1326
+ // ═══════════════════════════════════════════════════════════════════════════════
1327
+
1328
+ /**
1329
+ * Run ship using the new gate system.
1330
+ *
1331
+ * The gate system provides:
1332
+ * - Deterministic verdicts (same repo state → same verdict)
1333
+ * - Evidence-backed decisions (why tree)
1334
+ * - CI-ready artifacts (manifest, receipt, badge)
1335
+ *
1336
+ * @param {Object} opts - Options from parseArgs
1337
+ * @param {Object} context - Execution context
1338
+ * @returns {number} Exit code
1339
+ */
1340
+ async function runShipGate(opts, context = {}) {
1341
+ const shipGate = getShipGate();
1342
+ const shipManifest = getShipManifest();
1343
+ const whyTreeModule = getWhyTree();
1344
+
1345
+ if (!shipGate || !shipManifest) {
1346
+ // Fall back to legacy mode if gate modules not available
1347
+ console.error(" Ship gate modules not available, using legacy mode");
1348
+ return null; // Signal to use legacy
1349
+ }
1350
+
1351
+ const projectPath = path.resolve(opts.path);
1352
+ const startTime = Date.now();
1353
+
1354
+ // Check cache first if requested
1355
+ if (opts.cached) {
1356
+ const cacheResult = shipGate.checkVerdictCache(projectPath);
1357
+ if (cacheResult.valid) {
1358
+ const manifest = cacheResult.manifest;
1359
+
1360
+ if (opts.json || opts.manifest) {
1361
+ console.log(JSON.stringify(manifest, null, 2));
1362
+ } else if (opts.receipt) {
1363
+ console.log(shipManifest.generateCIReceipt(manifest));
1364
+ } else if (!opts.quiet) {
1365
+ console.log(` Using cached verdict: ${manifest.verdict.status} (${cacheResult.age}ms old)`);
1366
+ console.log(` Fingerprint: ${manifest.repo.fingerprint}`);
1367
+ }
1368
+
1369
+ return manifest.verdict.exitCode;
1370
+ }
1371
+
1372
+ if (!opts.quiet && !opts.json) {
1373
+ console.log(` Cache invalid: ${cacheResult.reason}`);
1374
+ }
1375
+ }
1376
+
1377
+ // Run full gate evaluation
1378
+ const gate = new shipGate.ShipGate(projectPath, {
1379
+ strict: opts.strict,
1380
+ });
1381
+
1382
+ const result = await gate.evaluate({
1383
+ skipReality: !opts.withRuntime,
1384
+ });
1385
+
1386
+ // Output based on mode
1387
+ if (opts.json || opts.manifest) {
1388
+ console.log(JSON.stringify(result.manifest, null, 2));
1389
+ return result.exitCode;
1390
+ }
1391
+
1392
+ if (opts.receipt) {
1393
+ console.log(shipManifest.generateCIReceipt(result.manifest));
1394
+ return result.exitCode;
1395
+ }
1396
+
1397
+ if (opts.ci) {
1398
+ // CI mode: minimal key=value output
1399
+ const envVars = shipManifest.generateCIEnvVars(result.manifest);
1400
+ for (const [key, value] of Object.entries(envVars)) {
1401
+ console.log(`${key}=${value}`);
1402
+ }
1403
+ return result.exitCode;
1404
+ }
1405
+
1406
+ // Human-readable output
1407
+ if (!opts.quiet) {
1408
+ // Print verdict card
1409
+ printVerdictCard(
1410
+ result.verdict,
1411
+ result.score,
1412
+ result.evidence.audit.blockers,
1413
+ result.evidence.audit.warnings,
1414
+ result.durationMs
1415
+ );
1416
+
1417
+ // Print why tree if requested or if blocked
1418
+ if (opts.whyTree || result.verdict !== "SHIP") {
1419
+ if (whyTreeModule) {
1420
+ const whyTree = whyTreeModule.buildWhyTree(result.findings);
1421
+ console.log(whyTreeModule.renderWhyTreeTerminal(whyTree));
1422
+ } else {
1423
+ // Fallback: simple why tree from manifest
1424
+ const why = result.manifest.evidence.whyTree;
1425
+ console.log(`\n ${ansi.bold}WHY: ${result.verdict}${ansi.reset}`);
1426
+ console.log(` ${ansi.dim}${why.summary}${ansi.reset}\n`);
1427
+
1428
+ for (const issue of why.topIssues || []) {
1429
+ console.log(` ${colors.blockRed}•${ansi.reset} ${issue.title}`);
1430
+ if (issue.evidence?.file) {
1431
+ console.log(` ${ansi.dim}→ ${issue.evidence.file}:${issue.evidence.lines || ""}${ansi.reset}`);
1432
+ }
1433
+ if (issue.fixHint) {
1434
+ console.log(` ${colors.shipGreen}Fix: ${issue.fixHint}${ansi.reset}`);
1435
+ }
1436
+ console.log();
1437
+ }
1438
+ }
1439
+ }
1440
+
1441
+ // Print artifact paths
1442
+ console.log(` ${ansi.dim}Artifacts:${ansi.reset}`);
1443
+ console.log(` ${ansi.dim}Manifest: ${result.artifactPaths.manifest}${ansi.reset}`);
1444
+ console.log(` ${ansi.dim}Receipt: ${result.artifactPaths.receipt}${ansi.reset}`);
1445
+ console.log();
1446
+
1447
+ // Badge info if requested
1448
+ if (opts.badge) {
1449
+ const badgeInfo = shipManifest.generateBadgeInfo(result.manifest);
1450
+ console.log(` ${ansi.bold}Badge:${ansi.reset}`);
1451
+ console.log(` ${badgeInfo.markdown}`);
1452
+ console.log();
1453
+ }
1454
+ }
1455
+
1456
+ return result.exitCode;
1457
+ }
1458
+
1459
+ // ═══════════════════════════════════════════════════════════════════════════════
1460
+ // SHIP CORE (for programmatic use)
1461
+ // ═══════════════════════════════════════════════════════════════════════════════
1462
+
1463
+ async function shipCore({ repoRoot, fastifyEntry, jsonOut, noWrite } = {}) {
1464
+ const root = repoRoot || process.cwd();
1465
+ const fastEntry = fastifyEntry || detectFastifyEntry(root);
1466
+
1467
+ const truthpack = await buildTruthpackSmart({ repoRoot: root, fastifyEntry: fastEntry });
1468
+ if (!noWrite) writeTruthpack(root, truthpack);
1469
+
1470
+ // Run analyzers in parallel for performance
1471
+ const [fileResults, truthpackResults] = await Promise.all([
1472
+ Promise.all([
1473
+ Promise.resolve(findFakeSuccess(root)),
1474
+ Promise.resolve(findOptimisticNoRollback(root)),
1475
+ Promise.resolve(findSilentCatch(root)),
1476
+ Promise.resolve(findDeadUI(root)),
1477
+ Promise.resolve(findOwnerModeBypass(root)),
1478
+ ]),
1479
+ Promise.all([
1480
+ Promise.resolve(findMissingRoutes(truthpack)),
1481
+ Promise.resolve(findMethodMismatch(truthpack)),
1482
+ Promise.resolve(findEnvGaps(truthpack)),
1483
+ Promise.resolve(findGhostAuth(truthpack, root)),
1484
+ Promise.resolve(findStripeWebhookViolations(truthpack)),
1485
+ Promise.resolve(findPaidSurfaceNotEnforced(truthpack)),
1486
+ ]),
1487
+ ]);
1488
+
1489
+ const allFindings = [
1490
+ ...fileResults.flat(),
1491
+ ...truthpackResults.flat(),
1492
+ // Runtime findings
1493
+ ...findingsFromReality(root)
1494
+ ];
1495
+
1496
+ const verdict = allFindings.some(f => f.severity === "BLOCK") ? "BLOCK" :
1497
+ allFindings.some(f => f.severity === "WARN") ? "WARN" : "SHIP";
1498
+
1499
+ const proofGraph = buildProofGraph(allFindings, truthpack, root);
1500
+
1501
+ const report = {
1502
+ meta: { generatedAt: new Date().toISOString(), verdict },
1503
+ truthpackHash: truthpack.index?.hashes?.truthpackHash,
1504
+ findings: allFindings,
1505
+ proofGraph: {
1506
+ summary: proofGraph.summary,
1507
+ topBlockers: proofGraph.topBlockers.slice(0, 5),
1508
+ topGaps: proofGraph.topGaps.slice(0, 5)
1509
+ }
1510
+ };
1511
+
1512
+ const outDir = path.join(root, ".vibecheck");
1513
+ fs.mkdirSync(outDir, { recursive: true });
1514
+ fs.writeFileSync(path.join(outDir, "last_ship.json"), JSON.stringify(report, null, 2));
1515
+ fs.writeFileSync(path.join(outDir, "proof-graph.json"), JSON.stringify(proofGraph, null, 2));
1516
+
1517
+ // Note: HTML reports are generated by 'vibecheck report' command, not here
1518
+
1519
+ if (jsonOut) {
1520
+ fs.writeFileSync(path.isAbsolute(jsonOut) ? jsonOut : path.join(root, jsonOut), JSON.stringify(report, null, 2));
1521
+ }
1522
+
1523
+ return { report, truthpack, verdict };
1524
+ }
1525
+
1526
+ // ═══════════════════════════════════════════════════════════════════════════════
1527
+ // EXPORTS
1528
+ // ═══════════════════════════════════════════════════════════════════════════════
1529
+
1530
+ // ═══════════════════════════════════════════════════════════════════════════════
1531
+ // SEAL COMMAND - Badge and Attestation Generator
1532
+ // ═══════════════════════════════════════════════════════════════════════════════
1533
+
1534
+ /**
1535
+ * Generate seal (badge) and attestation from the latest ship manifest.
1536
+ *
1537
+ * @param {string[]} args - Command arguments
1538
+ * @param {Object} context - Execution context
1539
+ * @returns {number} Exit code
1540
+ */
1541
+ async function runSeal(args = [], context = {}) {
1542
+ const { parseGlobalFlags } = require("./lib/global-flags");
1543
+ const { EXIT } = require("./lib/exit-codes");
1544
+
1545
+ const { flags: globalFlags } = parseGlobalFlags(args);
1546
+ const quiet = globalFlags.quiet || args.includes("--quiet") || args.includes("-q");
1547
+ const json = globalFlags.json || args.includes("--json");
1548
+ const projectRoot = context.repoRoot || globalFlags.path || process.cwd();
1549
+
1550
+ const shipManifest = getShipManifest();
1551
+
1552
+ if (!shipManifest) {
1553
+ if (!quiet) {
1554
+ console.error(" Seal module not available");
1555
+ }
1556
+ return EXIT.INTERNAL_ERROR;
1557
+ }
1558
+
1559
+ // Load latest manifest
1560
+ const manifest = shipManifest.loadShipManifest(projectRoot);
1561
+
1562
+ if (!manifest) {
1563
+ if (json) {
1564
+ console.log(JSON.stringify({
1565
+ success: false,
1566
+ error: "No ship manifest found. Run 'vibecheck ship' first."
1567
+ }));
1568
+ } else if (!quiet) {
1569
+ console.error(" No ship manifest found. Run 'vibecheck ship' first.");
1570
+ }
1571
+ return EXIT.NOT_FOUND;
1572
+ }
1573
+
1574
+ // Verify signature
1575
+ const verification = shipManifest.verifyManifestSignature(manifest);
1576
+
1577
+ // Generate badge info
1578
+ const badge = shipManifest.generateBadgeInfo(manifest);
1579
+
1580
+ // Build seal output
1581
+ const seal = {
1582
+ verdict: manifest.verdict.status,
1583
+ score: manifest.verdict.score,
1584
+ canShip: manifest.verdict.canShip,
1585
+
1586
+ // Repo info
1587
+ repo: {
1588
+ name: manifest.repo.name,
1589
+ commit: manifest.repo.commit,
1590
+ branch: manifest.repo.branch,
1591
+ fingerprint: manifest.repo.fingerprint,
1592
+ },
1593
+
1594
+ // Badge URLs
1595
+ badge: {
1596
+ shields: badge.url.shields,
1597
+ badgen: badge.url.badgen,
1598
+ markdown: badge.markdown,
1599
+ },
1600
+
1601
+ // Attestation
1602
+ attestation: {
1603
+ signatureValid: verification.valid,
1604
+ digest: manifest.signature.digest,
1605
+ algorithm: manifest.signature.algorithm,
1606
+ generatedAt: manifest.meta.generatedAt,
1607
+ },
1608
+
1609
+ // Evidence summary
1610
+ evidence: {
1611
+ blockers: manifest.evidence.summary.blockers,
1612
+ warnings: manifest.evidence.summary.warnings,
1613
+ totalFindings: manifest.evidence.summary.total,
1614
+ },
1615
+ };
1616
+
1617
+ if (json) {
1618
+ console.log(JSON.stringify(seal, null, 2));
1619
+ } else if (!quiet) {
1620
+ console.log(`
1621
+ ${ansi.bold}${ansi.cyan}╔═══════════════════════════════════════════════════════════════════╗
1622
+ ║ ║
1623
+ ║ SHIP SEAL ║
1624
+ ║ ║
1625
+ ╚═══════════════════════════════════════════════════════════════════╝${ansi.reset}
1626
+
1627
+ ${ansi.bold}Verdict:${ansi.reset} ${seal.verdict === "SHIP" ? colors.shipGreen : seal.verdict === "WARN" ? colors.warnAmber : colors.blockRed}${seal.verdict}${ansi.reset}
1628
+ ${ansi.bold}Score:${ansi.reset} ${seal.score}/100
1629
+
1630
+ ${ansi.bold}Repository${ansi.reset}
1631
+ ${ansi.dim}Name:${ansi.reset} ${seal.repo.name}
1632
+ ${ansi.dim}Commit:${ansi.reset} ${seal.repo.commit}
1633
+ ${ansi.dim}Branch:${ansi.reset} ${seal.repo.branch}
1634
+ ${ansi.dim}Fingerprint:${ansi.reset} ${seal.repo.fingerprint}
1635
+
1636
+ ${ansi.bold}Attestation${ansi.reset}
1637
+ ${ansi.dim}Signature:${ansi.reset} ${verification.valid ? colors.shipGreen + "✓ Valid" : colors.blockRed + "✗ Invalid"}${ansi.reset}
1638
+ ${ansi.dim}Digest:${ansi.reset} ${seal.attestation.digest}
1639
+ ${ansi.dim}Generated:${ansi.reset} ${seal.attestation.generatedAt}
1640
+
1641
+ ${ansi.bold}Badge (copy to README.md):${ansi.reset}
1642
+ ${colors.shipGreen}${seal.badge.markdown}${ansi.reset}
1643
+
1644
+ ${ansi.dim}Evidence: ${seal.evidence.blockers} blockers, ${seal.evidence.warnings} warnings${ansi.reset}
1645
+ `);
1646
+ }
1647
+
1648
+ return manifest.verdict.exitCode;
1649
+ }
1650
+
1651
+ // ═══════════════════════════════════════════════════════════════════════════════
1652
+ // EXPORTS
1653
+ // ═══════════════════════════════════════════════════════════════════════════════
1654
+
1655
+ module.exports = {
1656
+ runShip: withErrorHandling(runShip, "Ship check failed"),
1657
+ runSeal: withErrorHandling(runSeal, "Seal generation failed"),
1658
+ runShipGate,
1659
+ shipCore,
1660
+ };