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,948 @@
1
+ /**
2
+ * vibecheck safelist - Schema & Validation
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * A SCALPEL, NOT A TRASH CAN
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Responsible suppression with:
9
+ * - Required justification
10
+ * - Owner accountability
11
+ * - Optional expiration
12
+ * - Audit trail
13
+ * - Scope control (repo-wide vs local)
14
+ * - Abuse prevention limits
15
+ * - Conflict detection
16
+ */
17
+
18
+ "use strict";
19
+
20
+ // ═══════════════════════════════════════════════════════════════════════════════
21
+ // SCHEMA VERSION - Increment on breaking changes
22
+ // ═══════════════════════════════════════════════════════════════════════════════
23
+
24
+ const SCHEMA_VERSION = 2;
25
+
26
+ // ═══════════════════════════════════════════════════════════════════════════════
27
+ // LIMITS - Prevent safelist abuse
28
+ // ═══════════════════════════════════════════════════════════════════════════════
29
+
30
+ const LIMITS = {
31
+ MAX_ENTRIES_PER_FILE: 500, // Max entries in a single safelist file
32
+ MAX_REASON_LENGTH: 500, // Max characters in reason
33
+ MAX_PATTERN_LENGTH: 200, // Max regex pattern length
34
+ MAX_FILE_PATH_LENGTH: 300, // Max file path length
35
+ MIN_REASON_LENGTH: 10, // Min characters in reason
36
+ MAX_LINE_RANGE: 1000, // Max lines in a line range
37
+ MAX_OWNER_NAME_LENGTH: 100, // Max owner name length
38
+ MAX_TICKET_URL_LENGTH: 500, // Max ticket URL length
39
+ WARN_ENTRIES_THRESHOLD: 100, // Warn when exceeding this many entries
40
+ STALE_DAYS_THRESHOLD: 90, // Days without match before considered stale
41
+ };
42
+
43
+ // ═══════════════════════════════════════════════════════════════════════════════
44
+ // DANGEROUS PATTERNS - Patterns that suppress too much
45
+ // ═══════════════════════════════════════════════════════════════════════════════
46
+
47
+ const DANGEROUS_PATTERNS = [
48
+ /^\.\*$/, // Matches everything
49
+ /^\.\+$/, // Matches everything non-empty
50
+ /^\[^\\s\]\*$/i, // Matches any non-whitespace
51
+ /^\\w\*$/i, // Matches any word
52
+ /^\\S\*$/i, // Matches any non-whitespace
53
+ /^\.\{0,\}$/, // Matches everything (alternate)
54
+ ];
55
+
56
+ /**
57
+ * Check if a pattern is dangerously broad
58
+ */
59
+ function isDangerousPattern(pattern) {
60
+ if (!pattern || pattern.length < 3) return true; // Too short
61
+
62
+ for (const dangerous of DANGEROUS_PATTERNS) {
63
+ if (dangerous.test(pattern)) return true;
64
+ }
65
+
66
+ // Check for patterns that would match almost anything
67
+ try {
68
+ const regex = new RegExp(pattern, "i");
69
+ const testStrings = ["a", "test", "ERROR", "123", "foo_bar"];
70
+ const matchCount = testStrings.filter(s => regex.test(s)).length;
71
+ if (matchCount >= 4) return true; // Matches too many test strings
72
+ } catch {
73
+ // Invalid regex - will be caught by other validation
74
+ }
75
+
76
+ return false;
77
+ }
78
+
79
+ // ═══════════════════════════════════════════════════════════════════════════════
80
+ // ENTRY SCHEMA
81
+ // ═══════════════════════════════════════════════════════════════════════════════
82
+
83
+ /**
84
+ * Safelist entry schema
85
+ * @typedef {Object} SafelistEntry
86
+ * @property {string} id - Unique entry ID (e.g., "SL_abc123")
87
+ * @property {string} type - Entry type: "finding" | "pattern" | "rule" | "file"
88
+ * @property {Object} target - What to suppress
89
+ * @property {string} [target.findingId] - Specific finding ID
90
+ * @property {string} [target.pattern] - Regex pattern to match
91
+ * @property {string} [target.ruleId] - Rule ID to suppress
92
+ * @property {string} [target.file] - File path (glob supported)
93
+ * @property {number[]} [target.lines] - Line range [start, end]
94
+ * @property {Object} justification - Required justification
95
+ * @property {string} justification.reason - Why this is suppressed
96
+ * @property {string} justification.category - Category of justification
97
+ * @property {string} [justification.ticket] - Link to issue/ticket
98
+ * @property {Object} owner - Who is responsible
99
+ * @property {string} owner.name - Person/team name
100
+ * @property {string} [owner.email] - Contact email
101
+ * @property {string} [owner.team] - Team name
102
+ * @property {Object} scope - Where this applies
103
+ * @property {string} scope.type - "repo" | "local" | "branch"
104
+ * @property {string[]} [scope.commands] - Commands this applies to (default: all)
105
+ * @property {Object} lifecycle - Temporal properties
106
+ * @property {string} lifecycle.createdAt - ISO timestamp
107
+ * @property {string} lifecycle.createdBy - Who created (CLI user, CI, etc.)
108
+ * @property {string} [lifecycle.expiresAt] - ISO timestamp for expiry
109
+ * @property {string} [lifecycle.reviewAt] - ISO timestamp for review reminder
110
+ * @property {string} [lifecycle.lastMatchedAt] - Last time this matched a finding
111
+ * @property {number} lifecycle.matchCount - How many times this has matched
112
+ * @property {Object} [audit] - Audit trail
113
+ * @property {Array} [audit.history] - Change history
114
+ */
115
+
116
+ /**
117
+ * Justification categories - must pick one
118
+ */
119
+ const JUSTIFICATION_CATEGORIES = {
120
+ "false-positive": {
121
+ name: "False Positive",
122
+ description: "The finding is incorrect - code is actually fine",
123
+ requiresTicket: false,
124
+ maxExpiry: null, // Can be permanent
125
+ },
126
+ "accepted-risk": {
127
+ name: "Accepted Risk",
128
+ description: "Risk acknowledged and accepted by team",
129
+ requiresTicket: true, // Should have a ticket
130
+ maxExpiry: 365, // Max 1 year, then review
131
+ },
132
+ "test-fixture": {
133
+ name: "Test Fixture",
134
+ description: "Intentional test data/mock",
135
+ requiresTicket: false,
136
+ maxExpiry: null,
137
+ },
138
+ "legacy-code": {
139
+ name: "Legacy Code",
140
+ description: "Known issue in legacy code pending refactor",
141
+ requiresTicket: true,
142
+ maxExpiry: 180, // Max 6 months
143
+ },
144
+ "third-party": {
145
+ name: "Third-Party Code",
146
+ description: "Issue in vendored/third-party code we don't control",
147
+ requiresTicket: false,
148
+ maxExpiry: null,
149
+ },
150
+ "documentation": {
151
+ name: "Documentation/Example",
152
+ description: "Intentional example code in docs",
153
+ requiresTicket: false,
154
+ maxExpiry: null,
155
+ },
156
+ "temporary": {
157
+ name: "Temporary Suppression",
158
+ description: "Short-term suppression during development",
159
+ requiresTicket: false,
160
+ maxExpiry: 30, // Max 30 days
161
+ },
162
+ };
163
+
164
+ /**
165
+ * Scope types
166
+ */
167
+ const SCOPE_TYPES = {
168
+ "repo": {
169
+ name: "Repository-Wide",
170
+ description: "Applies to entire repo, committed to version control",
171
+ file: ".vibecheck/safelist.json",
172
+ gitIgnore: false,
173
+ },
174
+ "local": {
175
+ name: "Local Only",
176
+ description: "Only applies to this machine, not committed",
177
+ file: ".vibecheck/safelist.local.json",
178
+ gitIgnore: true,
179
+ },
180
+ "branch": {
181
+ name: "Branch-Specific",
182
+ description: "Only applies to specific branches",
183
+ file: ".vibecheck/safelist.json",
184
+ gitIgnore: false,
185
+ },
186
+ };
187
+
188
+ /**
189
+ * Commands that respect the safelist
190
+ */
191
+ const SAFELIST_COMMANDS = [
192
+ "audit", // vibecheck audit
193
+ "scan", // vibecheck scan (alias)
194
+ "ship", // vibecheck ship
195
+ "polish", // vibecheck polish
196
+ "prove", // vibecheck prove
197
+ "reality", // vibecheck reality
198
+ "guard", // vibecheck guard
199
+ ];
200
+
201
+ // ═══════════════════════════════════════════════════════════════════════════════
202
+ // VALIDATION
203
+ // ═══════════════════════════════════════════════════════════════════════════════
204
+
205
+ /**
206
+ * Validation error
207
+ */
208
+ class SafelistValidationError extends Error {
209
+ constructor(message, field, value) {
210
+ super(message);
211
+ this.name = "SafelistValidationError";
212
+ this.field = field;
213
+ this.value = value;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Validate a safelist entry
219
+ * @param {SafelistEntry} entry - Entry to validate
220
+ * @param {Object} opts - Validation options
221
+ * @param {boolean} opts.strict - Enable strict validation (default: true)
222
+ * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
223
+ */
224
+ function validateEntry(entry, opts = {}) {
225
+ const { strict = true } = opts;
226
+ const errors = [];
227
+ const warnings = [];
228
+
229
+ // ═══════════════════════════════════════════════════════════════════════════
230
+ // ID VALIDATION
231
+ // ═══════════════════════════════════════════════════════════════════════════
232
+
233
+ if (!entry.id || typeof entry.id !== "string") {
234
+ errors.push("Missing or invalid 'id' field");
235
+ } else {
236
+ if (!entry.id.startsWith("SL_")) {
237
+ errors.push("Entry ID must start with 'SL_'");
238
+ }
239
+ if (entry.id.length > 50) {
240
+ errors.push("Entry ID too long (max 50 characters)");
241
+ }
242
+ if (!/^SL_[a-zA-Z0-9_]+$/.test(entry.id)) {
243
+ errors.push("Entry ID contains invalid characters (only alphanumeric and underscore allowed)");
244
+ }
245
+ }
246
+
247
+ // ═══════════════════════════════════════════════════════════════════════════
248
+ // TYPE VALIDATION
249
+ // ═══════════════════════════════════════════════════════════════════════════
250
+
251
+ if (!entry.type || !["finding", "pattern", "rule", "file"].includes(entry.type)) {
252
+ errors.push("Invalid 'type' - must be: finding, pattern, rule, or file");
253
+ }
254
+
255
+ // ═══════════════════════════════════════════════════════════════════════════
256
+ // TARGET VALIDATION
257
+ // ═══════════════════════════════════════════════════════════════════════════
258
+
259
+ if (!entry.target || typeof entry.target !== "object") {
260
+ errors.push("Missing 'target' object");
261
+ } else {
262
+ const hasTarget = entry.target.findingId || entry.target.pattern ||
263
+ entry.target.ruleId || entry.target.file;
264
+ if (!hasTarget) {
265
+ errors.push("Target must have at least one of: findingId, pattern, ruleId, file");
266
+ }
267
+
268
+ // Validate finding ID format
269
+ if (entry.target.findingId) {
270
+ if (typeof entry.target.findingId !== "string") {
271
+ errors.push("Finding ID must be a string");
272
+ } else if (entry.target.findingId.length > 200) {
273
+ errors.push("Finding ID too long (max 200 characters)");
274
+ }
275
+ }
276
+
277
+ // Validate pattern
278
+ if (entry.target.pattern) {
279
+ if (typeof entry.target.pattern !== "string") {
280
+ errors.push("Pattern must be a string");
281
+ } else {
282
+ if (entry.target.pattern.length > LIMITS.MAX_PATTERN_LENGTH) {
283
+ errors.push(`Pattern too long (max ${LIMITS.MAX_PATTERN_LENGTH} characters)`);
284
+ }
285
+
286
+ // Validate regex syntax
287
+ try {
288
+ new RegExp(entry.target.pattern);
289
+ } catch (e) {
290
+ errors.push(`Invalid regex pattern: ${e.message}`);
291
+ }
292
+
293
+ // Check for dangerous patterns
294
+ if (strict && isDangerousPattern(entry.target.pattern)) {
295
+ errors.push("Pattern is too broad and would match too many findings. Be more specific.");
296
+ }
297
+
298
+ // Check for common regex mistakes
299
+ if (entry.target.pattern.includes("\\\\") && !entry.target.pattern.includes("\\\\\\\\")) {
300
+ warnings.push("Pattern contains double backslash - did you mean to escape a backslash?");
301
+ }
302
+ }
303
+ }
304
+
305
+ // Validate rule ID
306
+ if (entry.target.ruleId) {
307
+ if (typeof entry.target.ruleId !== "string") {
308
+ errors.push("Rule ID must be a string");
309
+ } else if (!/^[A-Z][A-Z0-9_]*$/i.test(entry.target.ruleId)) {
310
+ warnings.push("Rule ID should be uppercase with underscores (e.g., MOCK_DATA, HARDCODED_SECRET)");
311
+ }
312
+ }
313
+
314
+ // Validate file path
315
+ if (entry.target.file) {
316
+ if (typeof entry.target.file !== "string") {
317
+ errors.push("File path must be a string");
318
+ } else {
319
+ if (entry.target.file.length > LIMITS.MAX_FILE_PATH_LENGTH) {
320
+ errors.push(`File path too long (max ${LIMITS.MAX_FILE_PATH_LENGTH} characters)`);
321
+ }
322
+
323
+ // Check for suspicious file patterns
324
+ if (entry.target.file === "*" || entry.target.file === "**" || entry.target.file === "**/*") {
325
+ errors.push("File pattern is too broad - would match all files");
326
+ }
327
+
328
+ // Normalize path separators warning
329
+ if (entry.target.file.includes("\\")) {
330
+ warnings.push("File path contains backslashes - use forward slashes for cross-platform compatibility");
331
+ }
332
+
333
+ // Check for absolute paths
334
+ if (/^[A-Za-z]:/.test(entry.target.file) || entry.target.file.startsWith("/")) {
335
+ warnings.push("File path appears to be absolute - consider using relative paths");
336
+ }
337
+ }
338
+ }
339
+
340
+ // Validate line range
341
+ if (entry.target.lines) {
342
+ if (!Array.isArray(entry.target.lines) || entry.target.lines.length !== 2) {
343
+ errors.push("Lines must be an array of [start, end]");
344
+ } else {
345
+ const [start, end] = entry.target.lines;
346
+
347
+ if (!Number.isInteger(start) || !Number.isInteger(end)) {
348
+ errors.push("Line numbers must be integers");
349
+ } else {
350
+ if (start < 1) {
351
+ errors.push("Line numbers must be >= 1");
352
+ }
353
+ if (start > end) {
354
+ errors.push("Line range start must be <= end");
355
+ }
356
+ if (end - start > LIMITS.MAX_LINE_RANGE) {
357
+ errors.push(`Line range too large (max ${LIMITS.MAX_LINE_RANGE} lines)`);
358
+ }
359
+ if (end > 100000) {
360
+ warnings.push("Line range end is very large - are you sure this is correct?");
361
+ }
362
+ }
363
+ }
364
+
365
+ // Lines require file
366
+ if (!entry.target.file) {
367
+ errors.push("Line range requires a file path");
368
+ }
369
+ }
370
+ }
371
+
372
+ // ═══════════════════════════════════════════════════════════════════════════
373
+ // JUSTIFICATION VALIDATION (REQUIRED - this is a scalpel!)
374
+ // ═══════════════════════════════════════════════════════════════════════════
375
+
376
+ if (!entry.justification || typeof entry.justification !== "object") {
377
+ errors.push("Missing 'justification' object - reason is REQUIRED");
378
+ } else {
379
+ // Reason validation
380
+ if (!entry.justification.reason) {
381
+ errors.push("Justification reason is REQUIRED");
382
+ } else if (typeof entry.justification.reason !== "string") {
383
+ errors.push("Justification reason must be a string");
384
+ } else {
385
+ if (entry.justification.reason.length < LIMITS.MIN_REASON_LENGTH) {
386
+ errors.push(`Justification reason must be at least ${LIMITS.MIN_REASON_LENGTH} characters`);
387
+ }
388
+ if (entry.justification.reason.length > LIMITS.MAX_REASON_LENGTH) {
389
+ errors.push(`Justification reason too long (max ${LIMITS.MAX_REASON_LENGTH} characters)`);
390
+ }
391
+
392
+ // Check for low-effort reasons
393
+ const lowEffortReasons = [
394
+ /^false positive$/i,
395
+ /^ignore$/i,
396
+ /^suppress$/i,
397
+ /^skip$/i,
398
+ /^not an issue$/i,
399
+ /^test$/i,
400
+ /^asdf/i,
401
+ /^xxx/i,
402
+ /^todo/i,
403
+ ];
404
+ if (strict && lowEffortReasons.some(r => r.test(entry.justification.reason.trim()))) {
405
+ errors.push("Justification reason is too vague. Explain WHY this should be suppressed.");
406
+ }
407
+ }
408
+
409
+ // Category validation
410
+ if (!entry.justification.category) {
411
+ errors.push("Justification category is REQUIRED");
412
+ } else if (!JUSTIFICATION_CATEGORIES[entry.justification.category]) {
413
+ errors.push(`Invalid justification category. Must be one of: ${Object.keys(JUSTIFICATION_CATEGORIES).join(", ")}`);
414
+ } else {
415
+ const category = JUSTIFICATION_CATEGORIES[entry.justification.category];
416
+ if (category.requiresTicket && !entry.justification.ticket) {
417
+ errors.push(`Category '${entry.justification.category}' requires a ticket/issue link`);
418
+ }
419
+ }
420
+
421
+ // Ticket validation
422
+ if (entry.justification.ticket) {
423
+ if (typeof entry.justification.ticket !== "string") {
424
+ errors.push("Ticket must be a string");
425
+ } else {
426
+ if (entry.justification.ticket.length > LIMITS.MAX_TICKET_URL_LENGTH) {
427
+ errors.push(`Ticket URL too long (max ${LIMITS.MAX_TICKET_URL_LENGTH} characters)`);
428
+ }
429
+
430
+ // Validate URL format
431
+ if (!isValidUrl(entry.justification.ticket)) {
432
+ warnings.push("Ticket doesn't appear to be a valid URL");
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ // ═══════════════════════════════════════════════════════════════════════════
439
+ // OWNER VALIDATION (REQUIRED - accountability)
440
+ // ═══════════════════════════════════════════════════════════════════════════
441
+
442
+ if (!entry.owner || typeof entry.owner !== "object") {
443
+ errors.push("Missing 'owner' object - accountability is REQUIRED");
444
+ } else {
445
+ if (!entry.owner.name) {
446
+ errors.push("Owner name is REQUIRED");
447
+ } else if (typeof entry.owner.name !== "string") {
448
+ errors.push("Owner name must be a string");
449
+ } else {
450
+ if (entry.owner.name.length < 2) {
451
+ errors.push("Owner name must be at least 2 characters");
452
+ }
453
+ if (entry.owner.name.length > LIMITS.MAX_OWNER_NAME_LENGTH) {
454
+ errors.push(`Owner name too long (max ${LIMITS.MAX_OWNER_NAME_LENGTH} characters)`);
455
+ }
456
+
457
+ // Check for placeholder names
458
+ const placeholderNames = ["test", "user", "admin", "me", "todo", "asdf", "xxx"];
459
+ if (strict && placeholderNames.includes(entry.owner.name.toLowerCase())) {
460
+ warnings.push("Owner name appears to be a placeholder - use a real name or team");
461
+ }
462
+ }
463
+
464
+ // Email validation
465
+ if (entry.owner.email) {
466
+ if (typeof entry.owner.email !== "string") {
467
+ errors.push("Owner email must be a string");
468
+ } else if (!isValidEmail(entry.owner.email)) {
469
+ warnings.push("Owner email doesn't appear to be valid");
470
+ }
471
+ }
472
+
473
+ // Team validation
474
+ if (entry.owner.team) {
475
+ if (typeof entry.owner.team !== "string") {
476
+ errors.push("Owner team must be a string");
477
+ } else if (entry.owner.team.length > 100) {
478
+ errors.push("Owner team name too long (max 100 characters)");
479
+ }
480
+ }
481
+ }
482
+
483
+ // ═══════════════════════════════════════════════════════════════════════════
484
+ // SCOPE VALIDATION
485
+ // ═══════════════════════════════════════════════════════════════════════════
486
+
487
+ if (!entry.scope || typeof entry.scope !== "object") {
488
+ errors.push("Missing 'scope' object");
489
+ } else {
490
+ if (!entry.scope.type || !SCOPE_TYPES[entry.scope.type]) {
491
+ errors.push(`Invalid scope type. Must be one of: ${Object.keys(SCOPE_TYPES).join(", ")}`);
492
+ }
493
+
494
+ // Validate commands if specified
495
+ if (entry.scope.commands) {
496
+ if (!Array.isArray(entry.scope.commands)) {
497
+ errors.push("Scope commands must be an array");
498
+ } else {
499
+ const invalidCommands = entry.scope.commands.filter(c => !SAFELIST_COMMANDS.includes(c));
500
+ if (invalidCommands.length > 0) {
501
+ errors.push(`Invalid commands in scope: ${invalidCommands.join(", ")}`);
502
+ }
503
+ if (entry.scope.commands.length === 0) {
504
+ warnings.push("Empty commands array - entry won't apply to any commands");
505
+ }
506
+ }
507
+ }
508
+
509
+ // Validate branch if specified
510
+ if (entry.scope.type === "branch" && !entry.scope.branches) {
511
+ warnings.push("Branch scope type but no branches specified");
512
+ }
513
+ if (entry.scope.branches) {
514
+ if (!Array.isArray(entry.scope.branches)) {
515
+ errors.push("Scope branches must be an array");
516
+ } else if (entry.scope.branches.length === 0) {
517
+ warnings.push("Empty branches array");
518
+ }
519
+ }
520
+ }
521
+
522
+ // ═══════════════════════════════════════════════════════════════════════════
523
+ // LIFECYCLE VALIDATION
524
+ // ═══════════════════════════════════════════════════════════════════════════
525
+
526
+ if (!entry.lifecycle || typeof entry.lifecycle !== "object") {
527
+ errors.push("Missing 'lifecycle' object");
528
+ } else {
529
+ // Created timestamp
530
+ if (!entry.lifecycle.createdAt) {
531
+ errors.push("Missing lifecycle.createdAt timestamp");
532
+ } else if (!isValidISODate(entry.lifecycle.createdAt)) {
533
+ errors.push("Invalid lifecycle.createdAt timestamp (must be ISO 8601)");
534
+ }
535
+
536
+ // Expiry validation
537
+ if (entry.lifecycle.expiresAt) {
538
+ if (!isValidISODate(entry.lifecycle.expiresAt)) {
539
+ errors.push("Invalid lifecycle.expiresAt timestamp (must be ISO 8601)");
540
+ } else if (entry.lifecycle.createdAt) {
541
+ const expiresAt = new Date(entry.lifecycle.expiresAt);
542
+ const createdAt = new Date(entry.lifecycle.createdAt);
543
+
544
+ if (expiresAt <= createdAt) {
545
+ errors.push("Expiry date must be after creation date");
546
+ }
547
+ }
548
+ }
549
+
550
+ // Check expiry constraints based on category
551
+ if (entry.justification?.category) {
552
+ const category = JUSTIFICATION_CATEGORIES[entry.justification.category];
553
+ if (category?.maxExpiry) {
554
+ if (!entry.lifecycle.expiresAt) {
555
+ errors.push(`Category '${entry.justification.category}' requires an expiration date (max ${category.maxExpiry} days)`);
556
+ } else if (entry.lifecycle.createdAt) {
557
+ const expiresAt = new Date(entry.lifecycle.expiresAt);
558
+ const createdAt = new Date(entry.lifecycle.createdAt);
559
+ const daysDiff = (expiresAt - createdAt) / (1000 * 60 * 60 * 24);
560
+ if (daysDiff > category.maxExpiry) {
561
+ errors.push(`Category '${entry.justification.category}' max expiry is ${category.maxExpiry} days, got ${Math.round(daysDiff)} days`);
562
+ }
563
+ }
564
+ }
565
+ }
566
+
567
+ // Review date validation
568
+ if (entry.lifecycle.reviewAt) {
569
+ if (!isValidISODate(entry.lifecycle.reviewAt)) {
570
+ errors.push("Invalid lifecycle.reviewAt timestamp (must be ISO 8601)");
571
+ }
572
+ }
573
+
574
+ // Match count validation
575
+ if (entry.lifecycle.matchCount !== undefined) {
576
+ if (!Number.isInteger(entry.lifecycle.matchCount) || entry.lifecycle.matchCount < 0) {
577
+ errors.push("Match count must be a non-negative integer");
578
+ }
579
+ }
580
+ }
581
+
582
+ return {
583
+ valid: errors.length === 0,
584
+ errors,
585
+ warnings,
586
+ };
587
+ }
588
+
589
+ // ═══════════════════════════════════════════════════════════════════════════════
590
+ // VALIDATION HELPERS
591
+ // ═══════════════════════════════════════════════════════════════════════════════
592
+
593
+ /**
594
+ * Check if string is a valid ISO 8601 date
595
+ */
596
+ function isValidISODate(str) {
597
+ if (typeof str !== "string") return false;
598
+ const date = new Date(str);
599
+ return !isNaN(date.getTime()) && str.includes("T");
600
+ }
601
+
602
+ /**
603
+ * Check if string is a valid email
604
+ */
605
+ function isValidEmail(str) {
606
+ if (typeof str !== "string") return false;
607
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
608
+ }
609
+
610
+ /**
611
+ * Check if string is a valid URL
612
+ */
613
+ function isValidUrl(str) {
614
+ if (typeof str !== "string") return false;
615
+ try {
616
+ new URL(str);
617
+ return true;
618
+ } catch {
619
+ // Allow ticket IDs like JIRA-123
620
+ return /^[A-Z]+-\d+$/i.test(str);
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Validate entire safelist file
626
+ * @param {Object} safelist - Safelist object
627
+ * @param {Object} opts - Validation options
628
+ * @returns {{ valid: boolean, errors: string[], warnings: string[], stats: Object }}
629
+ */
630
+ function validateSafelist(safelist, opts = {}) {
631
+ const errors = [];
632
+ const warnings = [];
633
+ const stats = {
634
+ total: 0,
635
+ valid: 0,
636
+ invalid: 0,
637
+ expired: 0,
638
+ expiringSoon: 0,
639
+ dueForReview: 0,
640
+ stale: 0,
641
+ conflicts: [],
642
+ };
643
+
644
+ if (!safelist || typeof safelist !== "object") {
645
+ errors.push("Invalid safelist format");
646
+ return { valid: false, errors, warnings, stats };
647
+ }
648
+
649
+ // Version check
650
+ if (safelist.version !== SCHEMA_VERSION) {
651
+ if (!safelist.version) {
652
+ errors.push("Missing schema version");
653
+ } else if (safelist.version < SCHEMA_VERSION) {
654
+ warnings.push(`Safelist version ${safelist.version} is outdated (current: ${SCHEMA_VERSION}). Run 'vibecheck safelist migrate'.`);
655
+ } else {
656
+ errors.push(`Safelist version ${safelist.version} is newer than supported (${SCHEMA_VERSION}). Update vibecheck CLI.`);
657
+ }
658
+ }
659
+
660
+ if (!Array.isArray(safelist.entries)) {
661
+ errors.push("Safelist must have 'entries' array");
662
+ return { valid: false, errors, warnings, stats };
663
+ }
664
+
665
+ stats.total = safelist.entries.length;
666
+
667
+ // Check entry count limits
668
+ if (safelist.entries.length > LIMITS.MAX_ENTRIES_PER_FILE) {
669
+ errors.push(`Too many entries (${safelist.entries.length}). Max: ${LIMITS.MAX_ENTRIES_PER_FILE}. Consider cleaning up expired/unused entries.`);
670
+ } else if (safelist.entries.length > LIMITS.WARN_ENTRIES_THRESHOLD) {
671
+ warnings.push(`High number of entries (${safelist.entries.length}). Consider reviewing and cleaning up.`);
672
+ }
673
+
674
+ const now = Date.now();
675
+ const sevenDays = 7 * 24 * 60 * 60 * 1000;
676
+ const staleDays = LIMITS.STALE_DAYS_THRESHOLD * 24 * 60 * 60 * 1000;
677
+
678
+ // Validate each entry
679
+ const ids = new Set();
680
+ const patternMap = new Map(); // For conflict detection
681
+ const fileMap = new Map(); // For conflict detection
682
+
683
+ for (let i = 0; i < safelist.entries.length; i++) {
684
+ const entry = safelist.entries[i];
685
+ const validation = validateEntry(entry, opts);
686
+
687
+ if (!validation.valid) {
688
+ stats.invalid++;
689
+ errors.push(`Entry ${i} (${entry.id || "no-id"}): ${validation.errors.join("; ")}`);
690
+ } else {
691
+ stats.valid++;
692
+ }
693
+
694
+ // Add entry warnings
695
+ if (validation.warnings) {
696
+ warnings.push(...validation.warnings.map(w => `Entry ${entry.id}: ${w}`));
697
+ }
698
+
699
+ // Check for duplicate IDs
700
+ if (entry.id) {
701
+ if (ids.has(entry.id)) {
702
+ errors.push(`Duplicate entry ID: ${entry.id}`);
703
+ }
704
+ ids.add(entry.id);
705
+ }
706
+
707
+ // Check for expired entries
708
+ if (entry.lifecycle?.expiresAt) {
709
+ const expiresAt = new Date(entry.lifecycle.expiresAt).getTime();
710
+ if (expiresAt < now) {
711
+ stats.expired++;
712
+ warnings.push(`Entry ${entry.id} has expired (${new Date(expiresAt).toLocaleDateString()})`);
713
+ } else if (expiresAt < now + sevenDays) {
714
+ stats.expiringSoon++;
715
+ }
716
+ }
717
+
718
+ // Check for entries due for review
719
+ if (entry.lifecycle?.reviewAt) {
720
+ const reviewAt = new Date(entry.lifecycle.reviewAt).getTime();
721
+ if (reviewAt < now) {
722
+ stats.dueForReview++;
723
+ warnings.push(`Entry ${entry.id} is due for review`);
724
+ }
725
+ }
726
+
727
+ // Check for stale entries (never matched after creation period)
728
+ if (entry.lifecycle?.createdAt) {
729
+ const createdAt = new Date(entry.lifecycle.createdAt).getTime();
730
+ const matchCount = entry.lifecycle?.matchCount || 0;
731
+ if (createdAt < now - staleDays && matchCount === 0) {
732
+ stats.stale++;
733
+ warnings.push(`Entry ${entry.id} has never matched any findings (${LIMITS.STALE_DAYS_THRESHOLD}+ days old)`);
734
+ }
735
+ }
736
+
737
+ // Conflict detection - overlapping patterns
738
+ if (entry.target?.pattern) {
739
+ for (const [existingId, existingPattern] of patternMap) {
740
+ if (patternsOverlap(entry.target.pattern, existingPattern)) {
741
+ stats.conflicts.push({ entry1: existingId, entry2: entry.id, type: "pattern" });
742
+ warnings.push(`Potential overlap: ${existingId} and ${entry.id} have similar patterns`);
743
+ }
744
+ }
745
+ patternMap.set(entry.id, entry.target.pattern);
746
+ }
747
+
748
+ // Conflict detection - same file
749
+ if (entry.target?.file) {
750
+ for (const [existingId, existingFile] of fileMap) {
751
+ if (filesOverlap(entry.target.file, existingFile)) {
752
+ // Only warn if they don't have different line ranges
753
+ const existingEntry = safelist.entries.find(e => e.id === existingId);
754
+ if (!entry.target.lines || !existingEntry?.target?.lines) {
755
+ stats.conflicts.push({ entry1: existingId, entry2: entry.id, type: "file" });
756
+ warnings.push(`Potential overlap: ${existingId} and ${entry.id} target same file`);
757
+ }
758
+ }
759
+ }
760
+ fileMap.set(entry.id, entry.target.file);
761
+ }
762
+ }
763
+
764
+ return {
765
+ valid: errors.length === 0,
766
+ errors,
767
+ warnings,
768
+ stats,
769
+ };
770
+ }
771
+
772
+ /**
773
+ * Check if two patterns might overlap
774
+ */
775
+ function patternsOverlap(pattern1, pattern2) {
776
+ if (pattern1 === pattern2) return true;
777
+
778
+ // Check if one contains the other
779
+ if (pattern1.includes(pattern2) || pattern2.includes(pattern1)) return true;
780
+
781
+ // Try to detect regex subset relationships
782
+ try {
783
+ const testStrings = ["test", "error", "mock_data", "console.log", "TODO", "FIXME"];
784
+ const regex1 = new RegExp(pattern1, "i");
785
+ const regex2 = new RegExp(pattern2, "i");
786
+
787
+ const matches1 = testStrings.filter(s => regex1.test(s));
788
+ const matches2 = testStrings.filter(s => regex2.test(s));
789
+
790
+ // Check for overlap in matches
791
+ const overlap = matches1.filter(m => matches2.includes(m));
792
+ return overlap.length > 0 && (overlap.length === matches1.length || overlap.length === matches2.length);
793
+ } catch {
794
+ return false;
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Check if two file patterns might overlap
800
+ */
801
+ function filesOverlap(file1, file2) {
802
+ if (file1 === file2) return true;
803
+
804
+ // Normalize paths
805
+ const norm1 = file1.replace(/\\/g, "/");
806
+ const norm2 = file2.replace(/\\/g, "/");
807
+
808
+ // Check if one is a prefix of the other
809
+ if (norm1.startsWith(norm2) || norm2.startsWith(norm1)) return true;
810
+
811
+ // Check glob overlap
812
+ if (file1.includes("*") || file2.includes("*")) {
813
+ // Both are globs - check if they're the same base pattern
814
+ const base1 = norm1.replace(/\*+/g, "*");
815
+ const base2 = norm2.replace(/\*+/g, "*");
816
+ if (base1 === base2) return true;
817
+ }
818
+
819
+ return false;
820
+ }
821
+
822
+ // ═══════════════════════════════════════════════════════════════════════════════
823
+ // FACTORY FUNCTIONS
824
+ // ═══════════════════════════════════════════════════════════════════════════════
825
+
826
+ /**
827
+ * Generate a new safelist entry ID
828
+ */
829
+ function generateEntryId() {
830
+ const timestamp = Date.now().toString(36);
831
+ const random = Math.random().toString(36).substring(2, 8);
832
+ return `SL_${timestamp}_${random}`;
833
+ }
834
+
835
+ /**
836
+ * Create a new safelist entry with defaults
837
+ * @param {Object} opts - Entry options
838
+ * @returns {SafelistEntry}
839
+ */
840
+ function createEntry(opts) {
841
+ const {
842
+ type = "finding",
843
+ findingId,
844
+ pattern,
845
+ ruleId,
846
+ file,
847
+ lines,
848
+ reason,
849
+ category = "false-positive",
850
+ ticket,
851
+ ownerName,
852
+ ownerEmail,
853
+ ownerTeam,
854
+ scopeType = "repo",
855
+ commands,
856
+ expiresIn, // Days from now
857
+ reviewIn, // Days from now
858
+ } = opts;
859
+
860
+ const now = new Date().toISOString();
861
+
862
+ const entry = {
863
+ id: generateEntryId(),
864
+ type,
865
+ target: {},
866
+ justification: {
867
+ reason,
868
+ category,
869
+ },
870
+ owner: {
871
+ name: ownerName,
872
+ },
873
+ scope: {
874
+ type: scopeType,
875
+ },
876
+ lifecycle: {
877
+ createdAt: now,
878
+ createdBy: process.env.USER || process.env.USERNAME || "cli",
879
+ matchCount: 0,
880
+ },
881
+ };
882
+
883
+ // Target
884
+ if (findingId) entry.target.findingId = findingId;
885
+ if (pattern) entry.target.pattern = pattern;
886
+ if (ruleId) entry.target.ruleId = ruleId;
887
+ if (file) entry.target.file = file;
888
+ if (lines) entry.target.lines = lines;
889
+
890
+ // Justification
891
+ if (ticket) entry.justification.ticket = ticket;
892
+
893
+ // Owner
894
+ if (ownerEmail) entry.owner.email = ownerEmail;
895
+ if (ownerTeam) entry.owner.team = ownerTeam;
896
+
897
+ // Scope
898
+ if (commands) entry.scope.commands = commands;
899
+
900
+ // Lifecycle
901
+ if (expiresIn) {
902
+ entry.lifecycle.expiresAt = new Date(Date.now() + expiresIn * 24 * 60 * 60 * 1000).toISOString();
903
+ }
904
+ if (reviewIn) {
905
+ entry.lifecycle.reviewAt = new Date(Date.now() + reviewIn * 24 * 60 * 60 * 1000).toISOString();
906
+ }
907
+
908
+ return entry;
909
+ }
910
+
911
+ /**
912
+ * Create empty safelist
913
+ */
914
+ function createEmptySafelist() {
915
+ return {
916
+ version: SCHEMA_VERSION,
917
+ metadata: {
918
+ createdAt: new Date().toISOString(),
919
+ description: "vibecheck safelist - Responsible finding suppression",
920
+ },
921
+ entries: [],
922
+ };
923
+ }
924
+
925
+ // ═══════════════════════════════════════════════════════════════════════════════
926
+ // EXPORTS
927
+ // ═══════════════════════════════════════════════════════════════════════════════
928
+
929
+ module.exports = {
930
+ SCHEMA_VERSION,
931
+ LIMITS,
932
+ JUSTIFICATION_CATEGORIES,
933
+ SCOPE_TYPES,
934
+ SAFELIST_COMMANDS,
935
+ DANGEROUS_PATTERNS,
936
+ SafelistValidationError,
937
+ isDangerousPattern,
938
+ isValidISODate,
939
+ isValidEmail,
940
+ isValidUrl,
941
+ validateEntry,
942
+ validateSafelist,
943
+ patternsOverlap,
944
+ filesOverlap,
945
+ generateEntryId,
946
+ createEntry,
947
+ createEmptySafelist,
948
+ };