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,1153 @@
1
+ /**
2
+ * Ship Manifest - Deterministic Artifact System
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * THE SHIP GATE ARTIFACT - TRUSTWORTHY, FINAL, DETERMINISTIC
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Creates a cryptographically signed manifest that guarantees:
9
+ * - Same repo state → Same verdict (deterministic)
10
+ * - Evidence-backed decisions (traceable)
11
+ * - CI-friendly outputs (badges, receipts)
12
+ *
13
+ * Used by `vibecheck ship` and `vibecheck seal` commands.
14
+ *
15
+ * @module ship-manifest
16
+ * @version 1.1.0 - Hardened
17
+ */
18
+
19
+ "use strict";
20
+
21
+ const fs = require("fs");
22
+ const path = require("path");
23
+ const crypto = require("crypto");
24
+ const { execSync } = require("child_process");
25
+ const os = require("os");
26
+
27
+ // ═══════════════════════════════════════════════════════════════════════════════
28
+ // ERROR TYPES
29
+ // ═══════════════════════════════════════════════════════════════════════════════
30
+
31
+ class ManifestError extends Error {
32
+ constructor(message, code, details = {}) {
33
+ super(message);
34
+ this.name = "ManifestError";
35
+ this.code = code;
36
+ this.details = details;
37
+ }
38
+ }
39
+
40
+ class ValidationError extends ManifestError {
41
+ constructor(message, field, value) {
42
+ super(message, "VALIDATION_ERROR", { field, value });
43
+ this.name = "ValidationError";
44
+ }
45
+ }
46
+
47
+ class FileSystemError extends ManifestError {
48
+ constructor(message, operation, path) {
49
+ super(message, "FS_ERROR", { operation, path });
50
+ this.name = "FileSystemError";
51
+ }
52
+ }
53
+
54
+ // ═══════════════════════════════════════════════════════════════════════════════
55
+ // CONSTANTS
56
+ // ═══════════════════════════════════════════════════════════════════════════════
57
+
58
+ const MANIFEST_VERSION = "1.1.0";
59
+ const MANIFEST_SCHEMA = "vibecheck/ship-manifest/v1";
60
+
61
+ // Maximum file size to read (prevent DoS on large files)
62
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
63
+
64
+ // Maximum findings to include in manifest
65
+ const MAX_FINDINGS = 1000;
66
+
67
+ // Git command timeout
68
+ const GIT_TIMEOUT = 10000; // 10 seconds
69
+
70
+ const VERDICTS = Object.freeze({
71
+ SHIP: Object.freeze({ code: 0, badge: "passing", color: "#22c55e", ci: "success" }),
72
+ WARN: Object.freeze({ code: 1, badge: "warnings", color: "#eab308", ci: "warning" }),
73
+ BLOCK: Object.freeze({ code: 2, badge: "blocked", color: "#ef4444", ci: "failure" }),
74
+ });
75
+
76
+ const VALID_VERDICTS = new Set(["SHIP", "WARN", "BLOCK"]);
77
+
78
+ // Files used for repo fingerprint (critical config)
79
+ const FINGERPRINT_FILES = Object.freeze([
80
+ "package.json",
81
+ "pnpm-lock.yaml",
82
+ "package-lock.json",
83
+ "yarn.lock",
84
+ "prisma/schema.prisma",
85
+ ".vibecheckrc",
86
+ ".vibecheck/contracts/routes.json",
87
+ ".vibecheck/contracts/env.json",
88
+ ".vibecheck/contracts/auth.json",
89
+ ]);
90
+
91
+ // ═══════════════════════════════════════════════════════════════════════════════
92
+ // INPUT VALIDATION
93
+ // ═══════════════════════════════════════════════════════════════════════════════
94
+
95
+ /**
96
+ * Validate that a path is safe (no traversal, exists)
97
+ * @param {string} inputPath - Path to validate
98
+ * @param {string} context - Context for error messages
99
+ * @returns {string} Validated absolute path
100
+ */
101
+ function validatePath(inputPath, context = "path") {
102
+ if (typeof inputPath !== "string" || !inputPath.trim()) {
103
+ throw new ValidationError(`${context} must be a non-empty string`, context, inputPath);
104
+ }
105
+
106
+ // Prevent null byte injection
107
+ if (inputPath.includes("\0")) {
108
+ throw new ValidationError(`${context} contains invalid characters`, context, "[contains null byte]");
109
+ }
110
+
111
+ const resolved = path.resolve(inputPath);
112
+
113
+ // Prevent traversal outside workspace (basic check)
114
+ const normalized = path.normalize(resolved);
115
+ if (normalized !== resolved) {
116
+ // Path contained .. or other traversal
117
+ // This is OK as long as it resolves to a valid absolute path
118
+ }
119
+
120
+ return resolved;
121
+ }
122
+
123
+ /**
124
+ * Validate verdict string
125
+ * @param {string} verdict - Verdict to validate
126
+ * @returns {string} Validated verdict
127
+ */
128
+ function validateVerdict(verdict) {
129
+ const v = String(verdict || "").toUpperCase();
130
+
131
+ if (!VALID_VERDICTS.has(v)) {
132
+ throw new ValidationError(
133
+ `Invalid verdict: ${verdict}. Must be one of: SHIP, WARN, BLOCK`,
134
+ "verdict",
135
+ verdict
136
+ );
137
+ }
138
+
139
+ return v;
140
+ }
141
+
142
+ /**
143
+ * Validate score is in range
144
+ * @param {number} score - Score to validate
145
+ * @returns {number} Validated score (clamped to 0-100)
146
+ */
147
+ function validateScore(score) {
148
+ const num = Number(score);
149
+
150
+ if (isNaN(num)) {
151
+ return 0;
152
+ }
153
+
154
+ return Math.max(0, Math.min(100, Math.round(num)));
155
+ }
156
+
157
+ /**
158
+ * Validate and sanitize findings array
159
+ * @param {Array} findings - Findings to validate
160
+ * @returns {Array} Sanitized findings
161
+ */
162
+ function validateFindings(findings) {
163
+ if (!Array.isArray(findings)) {
164
+ return [];
165
+ }
166
+
167
+ // Limit number of findings
168
+ const limited = findings.slice(0, MAX_FINDINGS);
169
+
170
+ return limited.map((f, index) => {
171
+ if (!f || typeof f !== "object") {
172
+ return null;
173
+ }
174
+
175
+ return {
176
+ id: sanitizeString(f.id || f.fingerprint || `finding-${index}`),
177
+ category: sanitizeString(f.category || "Unknown"),
178
+ severity: sanitizeSeverity(f.severity),
179
+ title: sanitizeString(f.title || f.message || "Untitled finding", 500),
180
+ why: sanitizeString(f.why || f.explanation, 1000),
181
+ confidence: sanitizeConfidence(f.confidence),
182
+ evidence: sanitizeEvidence(f.evidence),
183
+ fixHints: sanitizeFixHints(f.fixHints),
184
+ fingerprint: sanitizeString(f.fingerprint, 64),
185
+ };
186
+ }).filter(Boolean);
187
+ }
188
+
189
+ /**
190
+ * Sanitize a string (prevent injection, limit length)
191
+ */
192
+ function sanitizeString(str, maxLength = 200) {
193
+ if (str === null || str === undefined) {
194
+ return null;
195
+ }
196
+
197
+ const s = String(str)
198
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "") // Remove control chars
199
+ .trim();
200
+
201
+ return s.slice(0, maxLength) || null;
202
+ }
203
+
204
+ /**
205
+ * Sanitize severity
206
+ */
207
+ function sanitizeSeverity(severity) {
208
+ const valid = ["BLOCK", "WARN", "INFO"];
209
+ const s = String(severity || "").toUpperCase();
210
+ return valid.includes(s) ? s : "INFO";
211
+ }
212
+
213
+ /**
214
+ * Sanitize confidence
215
+ */
216
+ function sanitizeConfidence(confidence) {
217
+ const valid = ["high", "medium", "low"];
218
+ const c = String(confidence || "").toLowerCase();
219
+ return valid.includes(c) ? c : "medium";
220
+ }
221
+
222
+ /**
223
+ * Sanitize evidence array
224
+ */
225
+ function sanitizeEvidence(evidence) {
226
+ if (!Array.isArray(evidence)) {
227
+ return [];
228
+ }
229
+
230
+ return evidence.slice(0, 5).map(e => {
231
+ if (!e || typeof e !== "object") return null;
232
+ return {
233
+ file: sanitizeString(e.file || e.path, 500),
234
+ lines: sanitizeString(e.lines, 20),
235
+ snippet: sanitizeString(e.snippet, 200),
236
+ kind: sanitizeString(e.kind, 20) || "file",
237
+ };
238
+ }).filter(Boolean);
239
+ }
240
+
241
+ /**
242
+ * Sanitize fix hints
243
+ */
244
+ function sanitizeFixHints(hints) {
245
+ if (!Array.isArray(hints)) {
246
+ return [];
247
+ }
248
+ return hints.slice(0, 5).map(h => sanitizeString(h, 500)).filter(Boolean);
249
+ }
250
+
251
+ // ═══════════════════════════════════════════════════════════════════════════════
252
+ // REPO FINGERPRINT - DETERMINISTIC STATE HASH
253
+ // ═══════════════════════════════════════════════════════════════════════════════
254
+
255
+ /**
256
+ * Generate a deterministic fingerprint for the current repo state.
257
+ * Same repo state always produces the same fingerprint.
258
+ *
259
+ * @param {string} repoRoot - Repository root path
260
+ * @returns {Object} Fingerprint with components
261
+ * @throws {ValidationError} If repoRoot is invalid
262
+ */
263
+ function generateRepoFingerprint(repoRoot) {
264
+ // Validate input
265
+ const validatedRoot = validatePath(repoRoot, "repoRoot");
266
+
267
+ // Verify directory exists
268
+ if (!fs.existsSync(validatedRoot)) {
269
+ throw new FileSystemError(
270
+ `Repository root does not exist: ${validatedRoot}`,
271
+ "stat",
272
+ validatedRoot
273
+ );
274
+ }
275
+
276
+ const stats = safeStatSync(validatedRoot);
277
+ if (!stats || !stats.isDirectory()) {
278
+ throw new FileSystemError(
279
+ `Repository root is not a directory: ${validatedRoot}`,
280
+ "stat",
281
+ validatedRoot
282
+ );
283
+ }
284
+
285
+ const components = {
286
+ commitHash: getGitCommitHash(validatedRoot),
287
+ headRef: getGitHeadRef(validatedRoot),
288
+ treeHash: getGitTreeHash(validatedRoot),
289
+ configHashes: {},
290
+ timestamp: null, // Not included in hash for determinism
291
+ gitAvailable: false,
292
+ };
293
+
294
+ // Check if git is available
295
+ components.gitAvailable = components.commitHash !== "unknown" ||
296
+ fs.existsSync(path.join(validatedRoot, ".git"));
297
+
298
+ // Hash critical config files
299
+ for (const file of FINGERPRINT_FILES) {
300
+ const filePath = path.join(validatedRoot, file);
301
+ const content = safeReadFile(filePath, MAX_FILE_SIZE);
302
+
303
+ if (content !== null) {
304
+ components.configHashes[file] = sha256(content).slice(0, 16);
305
+ }
306
+ }
307
+
308
+ // Compute combined fingerprint
309
+ // Use deterministic sorting to ensure same output
310
+ const sortedHashes = Object.entries(components.configHashes)
311
+ .sort((a, b) => a[0].localeCompare(b[0]))
312
+ .map(([k, v]) => `${k}:${v}`);
313
+
314
+ const fingerprintInput = [
315
+ components.commitHash || "no-commit",
316
+ components.treeHash || "no-tree",
317
+ ...sortedHashes,
318
+ ].join("|");
319
+
320
+ return {
321
+ ...components,
322
+ fingerprint: sha256(fingerprintInput).slice(0, 32),
323
+ isDirty: isGitDirty(validatedRoot),
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Safe stat that returns null on error
329
+ */
330
+ function safeStatSync(filePath) {
331
+ try {
332
+ return fs.statSync(filePath);
333
+ } catch {
334
+ return null;
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Safe file read with size limit
340
+ */
341
+ function safeReadFile(filePath, maxSize = MAX_FILE_SIZE) {
342
+ try {
343
+ // Check if file exists and get size
344
+ const stats = fs.statSync(filePath);
345
+
346
+ if (!stats.isFile()) {
347
+ return null;
348
+ }
349
+
350
+ // Prevent reading huge files
351
+ if (stats.size > maxSize) {
352
+ // For large files, hash first 1MB + size
353
+ const fd = fs.openSync(filePath, "r");
354
+ try {
355
+ const buffer = Buffer.alloc(1024 * 1024);
356
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
357
+ return buffer.slice(0, bytesRead).toString("utf8") + `|size:${stats.size}`;
358
+ } finally {
359
+ fs.closeSync(fd);
360
+ }
361
+ }
362
+
363
+ return fs.readFileSync(filePath, "utf8");
364
+ } catch {
365
+ return null;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Execute a git command safely with timeout
371
+ * @param {string} command - Git command (without "git" prefix)
372
+ * @param {string} cwd - Working directory
373
+ * @returns {string|null} Command output or null on failure
374
+ */
375
+ function safeGitExec(command, cwd) {
376
+ try {
377
+ return execSync(`git ${command}`, {
378
+ cwd,
379
+ encoding: "utf8",
380
+ stdio: ["pipe", "pipe", "pipe"],
381
+ timeout: GIT_TIMEOUT,
382
+ maxBuffer: 1024 * 1024, // 1MB max output
383
+ windowsHide: true,
384
+ }).trim();
385
+ } catch (err) {
386
+ // Log in debug mode
387
+ if (process.env.DEBUG || process.env.VIBECHECK_DEBUG) {
388
+ console.error(`Git command failed: git ${command}`, err.message);
389
+ }
390
+ return null;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Get Git commit hash
396
+ * @param {string} repoRoot - Repository root
397
+ * @returns {string} Commit hash (12 chars) or "unknown"
398
+ */
399
+ function getGitCommitHash(repoRoot) {
400
+ const result = safeGitExec("rev-parse HEAD", repoRoot);
401
+
402
+ if (result && /^[a-f0-9]{40}$/i.test(result)) {
403
+ return result.slice(0, 12);
404
+ }
405
+
406
+ return "unknown";
407
+ }
408
+
409
+ /**
410
+ * Get Git HEAD ref (branch name or detached HEAD)
411
+ * @param {string} repoRoot - Repository root
412
+ * @returns {string} Branch name or "HEAD" or "unknown"
413
+ */
414
+ function getGitHeadRef(repoRoot) {
415
+ // Try reading HEAD file directly (faster, no subprocess)
416
+ const headPath = path.join(repoRoot, ".git", "HEAD");
417
+ const content = safeReadFile(headPath, 1024);
418
+
419
+ if (content) {
420
+ const head = content.trim();
421
+ if (head.startsWith("ref: refs/heads/")) {
422
+ const branch = head.slice("ref: refs/heads/".length);
423
+ // Sanitize branch name
424
+ if (/^[\w\-./]+$/.test(branch)) {
425
+ return branch;
426
+ }
427
+ }
428
+ // Detached HEAD
429
+ if (/^[a-f0-9]{40}$/i.test(head)) {
430
+ return "HEAD";
431
+ }
432
+ }
433
+
434
+ // Fallback to git command
435
+ const branch = safeGitExec("rev-parse --abbrev-ref HEAD", repoRoot);
436
+ if (branch && branch !== "HEAD" && /^[\w\-./]+$/.test(branch)) {
437
+ return branch;
438
+ }
439
+
440
+ return "unknown";
441
+ }
442
+
443
+ /**
444
+ * Get Git tree hash (content-addressable hash of working tree)
445
+ * @param {string} repoRoot - Repository root
446
+ * @returns {string} Tree hash (12 chars) or "unknown"
447
+ */
448
+ function getGitTreeHash(repoRoot) {
449
+ const result = safeGitExec("write-tree", repoRoot);
450
+
451
+ if (result && /^[a-f0-9]{40}$/i.test(result)) {
452
+ return result.slice(0, 12);
453
+ }
454
+
455
+ // Fallback: hash of HEAD tree
456
+ const treeRef = safeGitExec("rev-parse HEAD^{tree}", repoRoot);
457
+ if (treeRef && /^[a-f0-9]{40}$/i.test(treeRef)) {
458
+ return treeRef.slice(0, 12);
459
+ }
460
+
461
+ return "unknown";
462
+ }
463
+
464
+ /**
465
+ * Check if Git working tree is dirty
466
+ * @param {string} repoRoot - Repository root
467
+ * @returns {boolean} True if dirty, false if clean or unknown
468
+ */
469
+ function isGitDirty(repoRoot) {
470
+ const status = safeGitExec("status --porcelain", repoRoot);
471
+
472
+ // null means git failed (not a repo, etc.) - treat as not dirty
473
+ if (status === null) {
474
+ return false;
475
+ }
476
+
477
+ return status.length > 0;
478
+ }
479
+
480
+ // ═══════════════════════════════════════════════════════════════════════════════
481
+ // SHIP MANIFEST GENERATION
482
+ // ═══════════════════════════════════════════════════════════════════════════════
483
+
484
+ /**
485
+ * Generate a ship manifest from verdict data.
486
+ *
487
+ * The manifest is the authoritative artifact that proves:
488
+ * - What was checked
489
+ * - What verdict was reached
490
+ * - Why (evidence chain)
491
+ * - Who/when (audit trail)
492
+ *
493
+ * @param {Object} options - Manifest options
494
+ * @returns {Object} Ship manifest
495
+ * @throws {ValidationError} If required options are invalid
496
+ */
497
+ function generateShipManifest(options = {}) {
498
+ // Validate and extract options with defaults
499
+ const {
500
+ repoRoot = process.cwd(),
501
+ verdict: rawVerdict = "BLOCK",
502
+ score: rawScore = 0,
503
+ findings: rawFindings = [],
504
+ truthpack = null,
505
+ realityReport = null,
506
+ shieldStatus = null,
507
+ coverage = null,
508
+ durationMs = 0,
509
+ } = options || {};
510
+
511
+ // Validate inputs
512
+ const validatedRoot = validatePath(repoRoot, "repoRoot");
513
+ const verdict = validateVerdict(rawVerdict);
514
+ const score = validateScore(rawScore);
515
+ const findings = validateFindings(rawFindings);
516
+ const duration = Math.max(0, Math.round(Number(durationMs) || 0));
517
+
518
+ // Generate repo fingerprint (may throw on invalid path)
519
+ let repoFP;
520
+ try {
521
+ repoFP = generateRepoFingerprint(validatedRoot);
522
+ } catch (err) {
523
+ // If fingerprint fails, create a degraded one
524
+ repoFP = {
525
+ fingerprint: sha256(validatedRoot + Date.now()).slice(0, 32),
526
+ commitHash: "unknown",
527
+ headRef: "unknown",
528
+ treeHash: "unknown",
529
+ isDirty: false,
530
+ gitAvailable: false,
531
+ configHashes: {},
532
+ };
533
+
534
+ if (process.env.DEBUG || process.env.VIBECHECK_DEBUG) {
535
+ console.error("Fingerprint generation failed:", err.message);
536
+ }
537
+ }
538
+
539
+ const now = new Date().toISOString();
540
+
541
+ // Build findings summary with safe counting
542
+ const findingsSummary = {
543
+ total: findings.length,
544
+ blockers: findings.filter(f => f.severity === "BLOCK").length,
545
+ warnings: findings.filter(f => f.severity === "WARN").length,
546
+ info: findings.filter(f => f.severity === "INFO").length,
547
+ };
548
+
549
+ // Build evidence map (top 3 blockers with evidence)
550
+ const whyTree = buildWhyTree(findings);
551
+
552
+ // Build coverage metrics with null safety
553
+ const coverageMetrics = sanitizeCoverage(coverage, truthpack, realityReport);
554
+
555
+ // Build manifest with all validated data
556
+ const manifest = {
557
+ version: MANIFEST_VERSION,
558
+ schema: MANIFEST_SCHEMA,
559
+
560
+ // Verdict (the final word)
561
+ verdict: {
562
+ status: verdict,
563
+ exitCode: VERDICTS[verdict]?.code ?? 2,
564
+ score,
565
+ ciStatus: VERDICTS[verdict]?.ci ?? "failure",
566
+ canShip: verdict === "SHIP",
567
+ },
568
+
569
+ // Repo state (for determinism)
570
+ repo: {
571
+ root: validatedRoot,
572
+ name: sanitizeString(path.basename(validatedRoot), 100) || "unknown",
573
+ fingerprint: repoFP.fingerprint,
574
+ commit: repoFP.commitHash,
575
+ branch: repoFP.headRef,
576
+ treeHash: repoFP.treeHash,
577
+ isDirty: Boolean(repoFP.isDirty),
578
+ gitAvailable: Boolean(repoFP.gitAvailable),
579
+ },
580
+
581
+ // Evidence (why this verdict)
582
+ evidence: {
583
+ summary: findingsSummary,
584
+ whyTree,
585
+ coverage: coverageMetrics,
586
+ },
587
+
588
+ // Source checks performed
589
+ checks: {
590
+ audit: {
591
+ ran: findings.length > 0 || truthpack !== null,
592
+ findingsCount: findings.length,
593
+ truthpackHash: sanitizeString(truthpack?.index?.hashes?.truthpackHash, 64),
594
+ },
595
+ reality: {
596
+ ran: realityReport !== null,
597
+ actionsCount: Math.max(0, Number(realityReport?.actions?.length) || 0),
598
+ requestsCount: Math.max(0, Number(realityReport?.requests?.length) || 0),
599
+ },
600
+ shield: {
601
+ ran: shieldStatus !== null,
602
+ mode: sanitizeString(shieldStatus?.mode, 20) || "unknown",
603
+ enforcing: shieldStatus?.mode === "enforce",
604
+ locked: Boolean(shieldStatus?.locked),
605
+ },
606
+ },
607
+
608
+ // Metadata
609
+ meta: {
610
+ generatedAt: now,
611
+ durationMs: duration,
612
+ tool: "vibecheck",
613
+ toolVersion: getVibeCheckVersion(),
614
+ platform: `${os.platform()}-${os.arch()}`,
615
+ nodeVersion: process.version,
616
+ },
617
+
618
+ // Signature (for verification)
619
+ signature: null, // Will be computed below
620
+ };
621
+
622
+ // Compute manifest signature
623
+ manifest.signature = computeManifestSignature(manifest);
624
+
625
+ // Freeze the manifest to prevent accidental mutation
626
+ return deepFreeze(manifest);
627
+ }
628
+
629
+ /**
630
+ * Sanitize coverage metrics
631
+ */
632
+ function sanitizeCoverage(coverage, truthpack, realityReport) {
633
+ if (coverage && typeof coverage === "object") {
634
+ return {
635
+ routeCoverage: validateScore(coverage.routeCoverage),
636
+ envCoverage: validateScore(coverage.envCoverage),
637
+ authCoverage: coverage.authCoverage === null ? null : validateScore(coverage.authCoverage),
638
+ runtimeCoverage: coverage.runtimeCoverage === null ? null : Math.max(0, Number(coverage.runtimeCoverage) || 0),
639
+ };
640
+ }
641
+
642
+ // Compute from truthpack/reality
643
+ const serverRoutes = Array.isArray(truthpack?.routes?.server) ? truthpack.routes.server.length : 0;
644
+ const envVars = Array.isArray(truthpack?.env?.vars) ? truthpack.env.vars.length : 0;
645
+ const envDeclared = Array.isArray(truthpack?.env?.declared) ? truthpack.env.declared.length : 0;
646
+
647
+ return {
648
+ routeCoverage: serverRoutes > 0 ? 100 : 0,
649
+ envCoverage: envVars > 0 ? Math.round((envDeclared / envVars) * 100) : 100,
650
+ authCoverage: null,
651
+ runtimeCoverage: realityReport ? Math.max(0, Number(realityReport.requests?.length) || 0) : null,
652
+ };
653
+ }
654
+
655
+ /**
656
+ * Deep freeze an object to prevent mutation
657
+ */
658
+ function deepFreeze(obj) {
659
+ if (obj === null || typeof obj !== "object") {
660
+ return obj;
661
+ }
662
+
663
+ Object.freeze(obj);
664
+
665
+ for (const key of Object.keys(obj)) {
666
+ const value = obj[key];
667
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
668
+ deepFreeze(value);
669
+ }
670
+ }
671
+
672
+ return obj;
673
+ }
674
+
675
+ /**
676
+ * Build the "why tree" - top blockers with evidence chains.
677
+ * This is the key artifact for understanding WHY a verdict was reached.
678
+ */
679
+ function buildWhyTree(findings) {
680
+ const blockers = findings
681
+ .filter(f => f.severity === "BLOCK")
682
+ .sort((a, b) => {
683
+ // Sort by confidence (high first), then by category importance
684
+ const confA = a.confidence === "high" ? 3 : a.confidence === "medium" ? 2 : 1;
685
+ const confB = b.confidence === "high" ? 3 : b.confidence === "medium" ? 2 : 1;
686
+ return confB - confA;
687
+ })
688
+ .slice(0, 3);
689
+
690
+ if (blockers.length === 0) {
691
+ // No blockers - verdict should be SHIP or WARN
692
+ const warnings = findings
693
+ .filter(f => f.severity === "WARN")
694
+ .slice(0, 3);
695
+
696
+ if (warnings.length === 0) {
697
+ return {
698
+ summary: "All checks passed",
699
+ topIssues: [],
700
+ fixMissions: [],
701
+ };
702
+ }
703
+
704
+ return {
705
+ summary: `${warnings.length} warning(s) to review`,
706
+ topIssues: warnings.map(w => ({
707
+ id: w.id || w.fingerprint || sha256(w.title || "").slice(0, 8),
708
+ category: w.category,
709
+ title: w.title || w.message,
710
+ evidence: formatEvidence(w),
711
+ fixHint: w.fixHints?.[0] || null,
712
+ })),
713
+ fixMissions: [],
714
+ };
715
+ }
716
+
717
+ return {
718
+ summary: `${blockers.length} blocker(s) must be fixed`,
719
+ topIssues: blockers.map(b => ({
720
+ id: b.id || b.fingerprint || sha256(b.title || "").slice(0, 8),
721
+ category: b.category,
722
+ title: b.title || b.message,
723
+ why: b.why || null,
724
+ evidence: formatEvidence(b),
725
+ fixHint: b.fixHints?.[0] || null,
726
+ })),
727
+ fixMissions: blockers.map(b => ({
728
+ target: b.evidence?.[0]?.file || "unknown",
729
+ action: b.fixHints?.[0] || `Fix ${b.category} issue`,
730
+ priority: "critical",
731
+ })),
732
+ };
733
+ }
734
+
735
+ /**
736
+ * Format evidence for display
737
+ */
738
+ function formatEvidence(finding) {
739
+ if (!finding.evidence?.length) {
740
+ return null;
741
+ }
742
+
743
+ const ev = finding.evidence[0];
744
+ return {
745
+ file: ev.file || ev.path || null,
746
+ lines: ev.lines || (ev.line ? `${ev.line}` : null),
747
+ snippet: ev.snippet?.slice(0, 100) || null,
748
+ kind: ev.kind || "file",
749
+ };
750
+ }
751
+
752
+ // ═══════════════════════════════════════════════════════════════════════════════
753
+ // MANIFEST SIGNATURE
754
+ // ═══════════════════════════════════════════════════════════════════════════════
755
+
756
+ /**
757
+ * Compute a signature for the manifest.
758
+ * This allows verification that the manifest hasn't been tampered with.
759
+ */
760
+ function computeManifestSignature(manifest) {
761
+ // Create a canonical representation (excluding signature field)
762
+ const signableContent = {
763
+ version: manifest.version,
764
+ verdict: manifest.verdict,
765
+ repo: manifest.repo,
766
+ evidence: manifest.evidence,
767
+ checks: manifest.checks,
768
+ meta: {
769
+ generatedAt: manifest.meta.generatedAt,
770
+ tool: manifest.meta.tool,
771
+ toolVersion: manifest.meta.toolVersion,
772
+ },
773
+ };
774
+
775
+ const canonical = JSON.stringify(signableContent, Object.keys(signableContent).sort());
776
+ const hash = sha256(canonical);
777
+
778
+ return {
779
+ algorithm: "sha256",
780
+ digest: hash.slice(0, 32),
781
+ // In a full implementation, this would include cryptographic signing
782
+ // For now, we use a deterministic hash that can be verified
783
+ verifiable: true,
784
+ };
785
+ }
786
+
787
+ /**
788
+ * Verify a manifest signature
789
+ */
790
+ function verifyManifestSignature(manifest) {
791
+ if (!manifest.signature) {
792
+ return { valid: false, reason: "No signature present" };
793
+ }
794
+
795
+ const recomputed = computeManifestSignature({
796
+ ...manifest,
797
+ signature: null, // Exclude signature for recomputation
798
+ });
799
+
800
+ const matches = recomputed.digest === manifest.signature.digest;
801
+
802
+ return {
803
+ valid: matches,
804
+ reason: matches ? "Signature valid" : "Signature mismatch - manifest may have been modified",
805
+ recomputedDigest: recomputed.digest,
806
+ storedDigest: manifest.signature.digest,
807
+ };
808
+ }
809
+
810
+ // ═══════════════════════════════════════════════════════════════════════════════
811
+ // CI OUTPUT FORMATS
812
+ // ═══════════════════════════════════════════════════════════════════════════════
813
+
814
+ /**
815
+ * Generate a CI-friendly receipt (single line, parseable)
816
+ */
817
+ function generateCIReceipt(manifest) {
818
+ const parts = [
819
+ `VERDICT=${manifest.verdict.status}`,
820
+ `EXIT_CODE=${manifest.verdict.exitCode}`,
821
+ `SCORE=${manifest.verdict.score}`,
822
+ `BLOCKERS=${manifest.evidence.summary.blockers}`,
823
+ `WARNINGS=${manifest.evidence.summary.warnings}`,
824
+ `COMMIT=${manifest.repo.commit}`,
825
+ `FINGERPRINT=${manifest.repo.fingerprint}`,
826
+ `DIRTY=${manifest.repo.isDirty}`,
827
+ `SIGNATURE=${manifest.signature.digest}`,
828
+ ];
829
+
830
+ return parts.join(" ");
831
+ }
832
+
833
+ /**
834
+ * Generate environment variables for CI
835
+ */
836
+ function generateCIEnvVars(manifest) {
837
+ return {
838
+ VIBECHECK_VERDICT: manifest.verdict.status,
839
+ VIBECHECK_EXIT_CODE: String(manifest.verdict.exitCode),
840
+ VIBECHECK_SCORE: String(manifest.verdict.score),
841
+ VIBECHECK_CAN_SHIP: String(manifest.verdict.canShip),
842
+ VIBECHECK_BLOCKERS: String(manifest.evidence.summary.blockers),
843
+ VIBECHECK_WARNINGS: String(manifest.evidence.summary.warnings),
844
+ VIBECHECK_COMMIT: manifest.repo.commit,
845
+ VIBECHECK_BRANCH: manifest.repo.branch,
846
+ VIBECHECK_FINGERPRINT: manifest.repo.fingerprint,
847
+ VIBECHECK_SIGNATURE: manifest.signature.digest,
848
+ VIBECHECK_DIRTY: String(manifest.repo.isDirty),
849
+ };
850
+ }
851
+
852
+ /**
853
+ * Generate GitHub Actions output format
854
+ */
855
+ function generateGitHubActionsOutput(manifest) {
856
+ const outputs = generateCIEnvVars(manifest);
857
+ const lines = [];
858
+
859
+ for (const [key, value] of Object.entries(outputs)) {
860
+ const name = key.replace("VIBECHECK_", "").toLowerCase();
861
+ lines.push(`${name}=${value}`);
862
+ }
863
+
864
+ return lines.join("\n");
865
+ }
866
+
867
+ // ═══════════════════════════════════════════════════════════════════════════════
868
+ // BADGE GENERATION
869
+ // ═══════════════════════════════════════════════════════════════════════════════
870
+
871
+ /**
872
+ * Generate badge info for embedding in README
873
+ */
874
+ function generateBadgeInfo(manifest) {
875
+ const verdictConfig = VERDICTS[manifest.verdict.status] || VERDICTS.BLOCK;
876
+
877
+ return {
878
+ label: "vibecheck",
879
+ message: manifest.verdict.status,
880
+ color: verdictConfig.color.replace("#", ""),
881
+ score: manifest.verdict.score,
882
+ url: {
883
+ shields: `https://img.shields.io/badge/vibecheck-${manifest.verdict.status}-${verdictConfig.color.replace("#", "")}?style=flat-square`,
884
+ badgen: `https://badgen.net/badge/vibecheck/${manifest.verdict.status}/${verdictConfig.color.replace("#", "")}`,
885
+ },
886
+ markdown: `[![Vibecheck](https://img.shields.io/badge/vibecheck-${manifest.verdict.status}-${verdictConfig.color.replace("#", "")}?style=flat-square)](https://vibecheckai.dev)`,
887
+ };
888
+ }
889
+
890
+ // ═══════════════════════════════════════════════════════════════════════════════
891
+ // PERSISTENCE
892
+ // ═══════════════════════════════════════════════════════════════════════════════
893
+
894
+ /**
895
+ * Atomically write a file (write to temp, then rename)
896
+ * @param {string} targetPath - Final destination path
897
+ * @param {string} content - Content to write
898
+ */
899
+ function atomicWriteFile(targetPath, content) {
900
+ const dir = path.dirname(targetPath);
901
+ const tempPath = path.join(dir, `.${path.basename(targetPath)}.tmp.${process.pid}`);
902
+
903
+ try {
904
+ // Ensure directory exists
905
+ fs.mkdirSync(dir, { recursive: true });
906
+
907
+ // Write to temp file
908
+ fs.writeFileSync(tempPath, content, { encoding: "utf8", mode: 0o644 });
909
+
910
+ // Atomically rename (overwrites existing)
911
+ fs.renameSync(tempPath, targetPath);
912
+ } catch (err) {
913
+ // Clean up temp file on error
914
+ try {
915
+ if (fs.existsSync(tempPath)) {
916
+ fs.unlinkSync(tempPath);
917
+ }
918
+ } catch {
919
+ // Ignore cleanup errors
920
+ }
921
+ throw new FileSystemError(
922
+ `Failed to write file: ${err.message}`,
923
+ "write",
924
+ targetPath
925
+ );
926
+ }
927
+ }
928
+
929
+ /**
930
+ * Write manifest to disk with atomic writes.
931
+ *
932
+ * @param {string} repoRoot - Repository root path
933
+ * @param {Object} manifest - Ship manifest to write
934
+ * @returns {Object} Paths to written files
935
+ * @throws {FileSystemError} If write fails
936
+ */
937
+ function writeShipManifest(repoRoot, manifest) {
938
+ // Validate inputs
939
+ const validatedRoot = validatePath(repoRoot, "repoRoot");
940
+
941
+ if (!manifest || typeof manifest !== "object") {
942
+ throw new ValidationError("manifest must be an object", "manifest", manifest);
943
+ }
944
+
945
+ const dir = path.join(validatedRoot, ".vibecheck", "ship");
946
+
947
+ // Prepare all content first (fail early if serialization fails)
948
+ let manifestJson, receipt, ghOutput;
949
+
950
+ try {
951
+ manifestJson = JSON.stringify(manifest, null, 2);
952
+ } catch (err) {
953
+ throw new ManifestError(
954
+ `Failed to serialize manifest: ${err.message}`,
955
+ "SERIALIZATION_ERROR"
956
+ );
957
+ }
958
+
959
+ receipt = generateCIReceipt(manifest);
960
+ ghOutput = generateGitHubActionsOutput(manifest);
961
+
962
+ // Ensure directory exists
963
+ try {
964
+ fs.mkdirSync(dir, { recursive: true, mode: 0o755 });
965
+ } catch (err) {
966
+ throw new FileSystemError(
967
+ `Failed to create directory: ${err.message}`,
968
+ "mkdir",
969
+ dir
970
+ );
971
+ }
972
+
973
+ // Write all files atomically
974
+ const paths = {
975
+ manifest: path.join(dir, "manifest.json"),
976
+ archive: null,
977
+ receipt: path.join(dir, "receipt.txt"),
978
+ githubOutput: path.join(dir, "github-output.txt"),
979
+ };
980
+
981
+ // Write main manifest
982
+ atomicWriteFile(paths.manifest, manifestJson);
983
+
984
+ // Write timestamped archive (best effort, don't fail on this)
985
+ try {
986
+ const timestamp = (manifest.meta?.generatedAt || new Date().toISOString())
987
+ .replace(/[:.]/g, "-")
988
+ .slice(0, 19);
989
+
990
+ // Validate timestamp format
991
+ if (/^[\d-T]+$/.test(timestamp)) {
992
+ paths.archive = path.join(dir, `manifest-${timestamp}.json`);
993
+ atomicWriteFile(paths.archive, manifestJson);
994
+
995
+ // Clean up old archives (keep last 10)
996
+ cleanupOldArchives(dir, 10);
997
+ }
998
+ } catch {
999
+ // Archive write failure is non-fatal
1000
+ paths.archive = null;
1001
+ }
1002
+
1003
+ // Write CI receipt
1004
+ atomicWriteFile(paths.receipt, receipt);
1005
+
1006
+ // Write GitHub Actions output
1007
+ atomicWriteFile(paths.githubOutput, ghOutput);
1008
+
1009
+ return paths;
1010
+ }
1011
+
1012
+ /**
1013
+ * Clean up old archive files, keeping the most recent N
1014
+ */
1015
+ function cleanupOldArchives(dir, keepCount = 10) {
1016
+ try {
1017
+ const files = fs.readdirSync(dir)
1018
+ .filter(f => f.startsWith("manifest-") && f.endsWith(".json"))
1019
+ .map(f => ({
1020
+ name: f,
1021
+ path: path.join(dir, f),
1022
+ mtime: fs.statSync(path.join(dir, f)).mtimeMs,
1023
+ }))
1024
+ .sort((a, b) => b.mtime - a.mtime);
1025
+
1026
+ // Delete files beyond keepCount
1027
+ for (const file of files.slice(keepCount)) {
1028
+ try {
1029
+ fs.unlinkSync(file.path);
1030
+ } catch {
1031
+ // Ignore deletion errors
1032
+ }
1033
+ }
1034
+ } catch {
1035
+ // Ignore cleanup errors
1036
+ }
1037
+ }
1038
+
1039
+ /**
1040
+ * Load latest manifest from disk with validation.
1041
+ *
1042
+ * @param {string} repoRoot - Repository root path
1043
+ * @returns {Object|null} Manifest or null if not found/invalid
1044
+ */
1045
+ function loadShipManifest(repoRoot) {
1046
+ let validatedRoot;
1047
+ try {
1048
+ validatedRoot = validatePath(repoRoot, "repoRoot");
1049
+ } catch {
1050
+ return null;
1051
+ }
1052
+
1053
+ const manifestPath = path.join(validatedRoot, ".vibecheck", "ship", "manifest.json");
1054
+
1055
+ const content = safeReadFile(manifestPath, MAX_FILE_SIZE);
1056
+ if (!content) {
1057
+ return null;
1058
+ }
1059
+
1060
+ try {
1061
+ const manifest = JSON.parse(content);
1062
+
1063
+ // Basic validation
1064
+ if (!manifest || typeof manifest !== "object") {
1065
+ return null;
1066
+ }
1067
+
1068
+ // Check schema version
1069
+ if (manifest.schema && !manifest.schema.startsWith("vibecheck/ship-manifest/")) {
1070
+ return null;
1071
+ }
1072
+
1073
+ // Check required fields
1074
+ if (!manifest.verdict?.status || !manifest.repo?.fingerprint) {
1075
+ return null;
1076
+ }
1077
+
1078
+ return manifest;
1079
+ } catch {
1080
+ // Invalid JSON
1081
+ return null;
1082
+ }
1083
+ }
1084
+
1085
+ // ═══════════════════════════════════════════════════════════════════════════════
1086
+ // HELPERS
1087
+ // ═══════════════════════════════════════════════════════════════════════════════
1088
+
1089
+ function sha256(input) {
1090
+ return crypto.createHash("sha256").update(input).digest("hex");
1091
+ }
1092
+
1093
+ function getVibeCheckVersion() {
1094
+ try {
1095
+ const pkgPath = path.join(__dirname, "../../../package.json");
1096
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
1097
+ return pkg.version || "unknown";
1098
+ } catch {
1099
+ return "unknown";
1100
+ }
1101
+ }
1102
+
1103
+ // ═══════════════════════════════════════════════════════════════════════════════
1104
+ // EXPORTS
1105
+ // ═══════════════════════════════════════════════════════════════════════════════
1106
+
1107
+ module.exports = {
1108
+ // Core
1109
+ generateShipManifest,
1110
+ generateRepoFingerprint,
1111
+ buildWhyTree,
1112
+
1113
+ // Signature
1114
+ computeManifestSignature,
1115
+ verifyManifestSignature,
1116
+
1117
+ // CI outputs
1118
+ generateCIReceipt,
1119
+ generateCIEnvVars,
1120
+ generateGitHubActionsOutput,
1121
+
1122
+ // Badge
1123
+ generateBadgeInfo,
1124
+
1125
+ // Persistence
1126
+ writeShipManifest,
1127
+ loadShipManifest,
1128
+
1129
+ // Validation (for external use)
1130
+ validatePath,
1131
+ validateVerdict,
1132
+ validateScore,
1133
+ validateFindings,
1134
+
1135
+ // Utility
1136
+ safeReadFile,
1137
+ safeGitExec,
1138
+ atomicWriteFile,
1139
+
1140
+ // Error classes
1141
+ ManifestError,
1142
+ ValidationError,
1143
+ FileSystemError,
1144
+
1145
+ // Constants
1146
+ MANIFEST_VERSION,
1147
+ MANIFEST_SCHEMA,
1148
+ VERDICTS,
1149
+ FINGERPRINT_FILES,
1150
+ MAX_FILE_SIZE,
1151
+ MAX_FINDINGS,
1152
+ GIT_TIMEOUT,
1153
+ };