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,917 @@
1
+ /**
2
+ * Reality Mode Execution Engine
3
+ * Step execution, danger classification, assertions, surface discovery
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const crypto = require("crypto");
9
+
10
+ // ============================================================================
11
+ // Utilities
12
+ // ============================================================================
13
+
14
+ function uuid() {
15
+ try {
16
+ return crypto.randomUUID();
17
+ } catch {
18
+ return crypto.randomBytes(16).toString("hex");
19
+ }
20
+ }
21
+
22
+ function randomEmail() {
23
+ return `reality+${Date.now()}_${Math.floor(Math.random() * 1e6)}@example.com`;
24
+ }
25
+
26
+ function template(str, vars) {
27
+ if (typeof str !== "string") return str;
28
+ return str.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
29
+ const k = String(key || "").trim();
30
+ if (k === "$timestamp") return String(Date.now());
31
+ if (k === "$uuid") return uuid();
32
+ if (k === "$randomEmail") return randomEmail();
33
+ if (k === "$isoDate") return new Date().toISOString();
34
+ if (k.startsWith("env.")) return process.env[k.slice(4)] || "";
35
+ if (k.startsWith("stored.")) return vars?.[k] ?? "";
36
+ return vars?.[k] != null ? String(vars[k]) : "";
37
+ });
38
+ }
39
+
40
+ // ============================================================================
41
+ // Danger Classification System
42
+ // ============================================================================
43
+
44
+ const DANGER_TEXT_PATTERNS = [
45
+ /\bdelete\b/i,
46
+ /\bremove\b/i,
47
+ /\bdestroy\b/i,
48
+ /\bdeactivate\b/i,
49
+ /\bterminate\b/i,
50
+ /\bclose\s*account\b/i,
51
+ /\bcancel\s*subscription\b/i,
52
+ /\breset\s*all\b/i,
53
+ /\bwipe\b/i,
54
+ /\berase\b/i,
55
+ /\birreversible\b/i,
56
+ /\bpermanently\b/i,
57
+ /\bcannot\s*be\s*undone\b/i,
58
+ /\brevoke\b/i,
59
+ /\bdisconnect\b/i,
60
+ /\bunlink\b/i,
61
+ ];
62
+
63
+ const DANGER_CLASS_PATTERNS = [
64
+ /\bbtn-danger\b/,
65
+ /\bdestructive\b/,
66
+ /\bdelete-/,
67
+ /\bdanger-/,
68
+ /\bremove-/,
69
+ ];
70
+
71
+ function classifyDanger({ text, className, ariaLabel, role, url, method }) {
72
+ const signals = [];
73
+ let score = 0;
74
+
75
+ const t = `${text || ""}`.toLowerCase();
76
+ const cls = `${className || ""}`.toLowerCase();
77
+ const aria = `${ariaLabel || ""}`.toLowerCase();
78
+ const r = `${role || ""}`.toLowerCase();
79
+ const u = `${url || ""}`.toLowerCase();
80
+ const m = `${method || ""}`.toUpperCase();
81
+
82
+ // Text patterns
83
+ for (const pattern of DANGER_TEXT_PATTERNS) {
84
+ if (pattern.test(t) || pattern.test(aria)) {
85
+ signals.push({ type: "text", value: pattern.source, weight: 0.3 });
86
+ score += 0.3;
87
+ }
88
+ }
89
+
90
+ // Class patterns
91
+ for (const pattern of DANGER_CLASS_PATTERNS) {
92
+ if (pattern.test(cls)) {
93
+ signals.push({ type: "class", value: pattern.source, weight: 0.25 });
94
+ score += 0.25;
95
+ }
96
+ }
97
+
98
+ // ARIA patterns
99
+ if (r === "alertdialog") {
100
+ signals.push({ type: "aria", value: "role=alertdialog", weight: 0.2 });
101
+ score += 0.2;
102
+ }
103
+
104
+ // Network patterns (DELETE method or destructive endpoints)
105
+ if (m === "DELETE") {
106
+ signals.push({ type: "network", value: "DELETE method", weight: 0.4 });
107
+ score += 0.4;
108
+ }
109
+ if (/\/delete\b|\/destroy\b|\/deactivate\b|\/remove\b/.test(u)) {
110
+ signals.push({ type: "network", value: "destructive endpoint", weight: 0.35 });
111
+ score += 0.35;
112
+ }
113
+
114
+ const dangerous = score >= 0.25;
115
+ const reason = signals.length > 0 ? signals[0].type : "none";
116
+
117
+ return {
118
+ dangerous,
119
+ score: Math.min(1, score),
120
+ signals,
121
+ reason,
122
+ };
123
+ }
124
+
125
+ // ============================================================================
126
+ // Screenshots & DOM Snapshots
127
+ // ============================================================================
128
+
129
+ async function screenshot(page, outPath) {
130
+ try {
131
+ await page.screenshot({ path: outPath, fullPage: true });
132
+ } catch {
133
+ // Non-fatal
134
+ }
135
+ }
136
+
137
+ async function domSnapshot(page) {
138
+ try {
139
+ return await page.evaluate(() => {
140
+ const el = document.activeElement;
141
+ return {
142
+ url: location.href,
143
+ title: document.title,
144
+ active: el ? { tag: el.tagName, id: el.id, name: el.getAttribute("name") } : null,
145
+ bodyTextSample: document.body?.innerText?.slice(0, 500) || "",
146
+ };
147
+ });
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
153
+ // ============================================================================
154
+ // Navigation Helpers
155
+ // ============================================================================
156
+
157
+ async function safeGoto(page, url, timeout) {
158
+ const started = Date.now();
159
+ try {
160
+ const res = await page.goto(url, { waitUntil: "domcontentloaded", timeout });
161
+ await page.waitForLoadState("networkidle", { timeout: Math.min(timeout, 5000) }).catch(() => {});
162
+ const status = res ? res.status() : 0;
163
+ return { ok: status >= 200 && status < 400, status, ms: Date.now() - started };
164
+ } catch (e) {
165
+ return { ok: false, status: 0, ms: Date.now() - started, error: String(e?.message || e).slice(0, 180) };
166
+ }
167
+ }
168
+
169
+ async function findLocator(page, target) {
170
+ return page.locator(target).first();
171
+ }
172
+
173
+ // ============================================================================
174
+ // Surface Discovery
175
+ // ============================================================================
176
+
177
+ async function discoverSurface({ page, baseUrl, timeout, maxPages }) {
178
+ const discovered = {
179
+ routes: [],
180
+ elements: [],
181
+ forms: [],
182
+ };
183
+
184
+ // Visit base
185
+ const home = await safeGoto(page, baseUrl, timeout);
186
+ if (home.status) {
187
+ discovered.routes.push({ path: "/", status: home.ok ? "success" : "error", httpStatus: home.status });
188
+ }
189
+
190
+ // Extract routes
191
+ const routes = await page.evaluate(() => {
192
+ const out = new Set();
193
+ document.querySelectorAll("a[href]").forEach(a => {
194
+ const href = a.getAttribute("href") || "";
195
+ if (!href || href.startsWith("http") || href.startsWith("//") || href.includes("#")) return;
196
+ if (href.startsWith("/")) out.add(href);
197
+ });
198
+ // Common SPA routes
199
+ ["/dashboard", "/app", "/account", "/settings", "/profile", "/projects", "/analytics", "/billing"].forEach(r => out.add(r));
200
+ return Array.from(out);
201
+ }).catch(() => []);
202
+
203
+ const queue = routes.slice(0, maxPages * 2);
204
+ const seen = new Set(["/", baseUrl]);
205
+
206
+ for (const route of queue.slice(0, maxPages)) {
207
+ if (seen.has(route)) continue;
208
+ seen.add(route);
209
+
210
+ const res = await safeGoto(page, baseUrl + route, timeout);
211
+ discovered.routes.push({
212
+ path: route,
213
+ status: res.ok ? "success" : "error",
214
+ httpStatus: res.status || 0,
215
+ error: res.error,
216
+ });
217
+
218
+ // Discover elements on this page
219
+ const pageEls = await page.evaluate(() => {
220
+ const pickText = (el) => (el.textContent || el.getAttribute("aria-label") || "").trim().slice(0, 80);
221
+ const els = Array.from(document.querySelectorAll('button, [role="button"], input[type="submit"], a[href], [data-testid]'));
222
+ return els.slice(0, 100).map((el, idx) => ({
223
+ tag: el.tagName.toLowerCase(),
224
+ text: pickText(el),
225
+ id: el.id || "",
226
+ testid: el.getAttribute("data-testid") || "",
227
+ className: el.className || "",
228
+ ariaLabel: el.getAttribute("aria-label") || "",
229
+ href: el.getAttribute("href") || "",
230
+ index: idx,
231
+ }));
232
+ }).catch(() => []);
233
+
234
+ for (const e of pageEls) {
235
+ if (!e.text && !e.testid && !e.id) continue;
236
+ discovered.elements.push({ ...e, page: route });
237
+ }
238
+
239
+ // Discover forms
240
+ const forms = await page.evaluate(() => {
241
+ const forms = Array.from(document.querySelectorAll("form"));
242
+ return forms.slice(0, 20).map((form, idx) => {
243
+ const fields = Array.from(form.querySelectorAll("input, textarea, select")).slice(0, 30).map((f) => ({
244
+ name: f.getAttribute("name") || f.id || "",
245
+ type: f.getAttribute("type") || f.tagName.toLowerCase(),
246
+ required: !!f.required,
247
+ }));
248
+ return {
249
+ selector: form.id ? `#${form.id}` : form.getAttribute("data-testid") ? `[data-testid="${form.getAttribute("data-testid")}"]` : `form:nth-of-type(${idx + 1})`,
250
+ fields,
251
+ };
252
+ });
253
+ }).catch(() => []);
254
+
255
+ for (const f of forms) discovered.forms.push({ ...f, page: route });
256
+ }
257
+
258
+ // Dedupe
259
+ discovered.routes = dedupe(discovered.routes, r => r.path);
260
+ discovered.elements = dedupe(discovered.elements, e => `${e.page}|${e.tag}|${e.testid}|${e.id}|${e.text}`);
261
+ discovered.forms = dedupe(discovered.forms, f => `${f.page}|${f.selector}`);
262
+
263
+ return discovered;
264
+ }
265
+
266
+ function dedupe(arr, keyFn) {
267
+ const seen = new Set();
268
+ const out = [];
269
+ for (const x of arr) {
270
+ const k = keyFn(x);
271
+ if (seen.has(k)) continue;
272
+ seen.add(k);
273
+ out.push(x);
274
+ }
275
+ return out;
276
+ }
277
+
278
+ // ============================================================================
279
+ // Flow Plan Execution
280
+ // ============================================================================
281
+
282
+ async function runFlowPlan({ plan, page, context, telemetry }) {
283
+ const results = {
284
+ meta: {
285
+ baseUrl: plan.baseUrl,
286
+ startedAt: new Date().toISOString(),
287
+ danger: plan.danger,
288
+ maxPages: plan.maxPages,
289
+ timeout: plan.timeout,
290
+ },
291
+ discovery: null,
292
+ coverage: {
293
+ routesDiscovered: 0,
294
+ routesWorking: 0,
295
+ elementsDiscovered: 0,
296
+ elementsWorking: 0,
297
+ formsDiscovered: 0,
298
+ formsWorking: 0,
299
+ },
300
+ flows: [],
301
+ routes: [],
302
+ elements: [],
303
+ forms: [],
304
+ errors: [],
305
+ network: [],
306
+ console: [],
307
+ timeline: [],
308
+ score: 0,
309
+ duration: 0,
310
+ };
311
+
312
+ // Discovery pass
313
+ const discovery = await discoverSurface({
314
+ page,
315
+ baseUrl: plan.baseUrl,
316
+ timeout: plan.timeout,
317
+ maxPages: plan.maxPages,
318
+ });
319
+
320
+ results.discovery = {
321
+ routes: discovery.routes.length,
322
+ elements: discovery.elements.length,
323
+ forms: discovery.forms.length,
324
+ };
325
+
326
+ results.routes = discovery.routes;
327
+ results.elements = discovery.elements;
328
+ results.forms = discovery.forms;
329
+
330
+ results.coverage.routesDiscovered = discovery.routes.length;
331
+ results.coverage.routesWorking = discovery.routes.filter(r => r.status === "success").length;
332
+ results.coverage.elementsDiscovered = discovery.elements.length;
333
+ results.coverage.formsDiscovered = discovery.forms.length;
334
+
335
+ // Collect telemetry
336
+ results.console = telemetry.console.slice(-500);
337
+ results.errors = [
338
+ ...telemetry.pageErrors.map(e => ({ type: "uncaught", message: e.message, url: e.url, ts: e.ts })),
339
+ ...telemetry.console.filter(m => m.type === "error").map(m => ({ type: "console", message: m.text, url: m.url, ts: m.ts })),
340
+ ];
341
+ results.network = telemetry.responses.slice(-500);
342
+
343
+ // Run Flow Packs
344
+ const flowVarsBase = makeBaseVars(plan);
345
+ const flowsToRun = Array.isArray(plan.flows) ? plan.flows : [];
346
+
347
+ for (const flow of flowsToRun) {
348
+ const flowResult = await runSingleFlow({
349
+ flow,
350
+ plan,
351
+ page,
352
+ context,
353
+ telemetry,
354
+ varsBase: flowVarsBase,
355
+ results,
356
+ });
357
+ results.flows.push(flowResult);
358
+ }
359
+
360
+ // Count working elements from successful flow steps
361
+ let elementWorking = 0;
362
+ for (const f of results.flows) {
363
+ for (const s of (f.steps || [])) {
364
+ if ((s.action === "click" || s.action === "fill") && s.status === "success") elementWorking++;
365
+ }
366
+ }
367
+ results.coverage.elementsWorking = Math.min(elementWorking, results.coverage.elementsDiscovered);
368
+
369
+ // Calculate score
370
+ results.score = calculateScore(results);
371
+
372
+ return results;
373
+ }
374
+
375
+ function makeBaseVars(plan) {
376
+ const vars = {
377
+ baseUrl: plan.baseUrl,
378
+ timestamp: String(Date.now()),
379
+ $timestamp: String(Date.now()),
380
+ $uuid: uuid(),
381
+ $randomEmail: randomEmail(),
382
+ $isoDate: new Date().toISOString(),
383
+ };
384
+
385
+ if (plan.auth && typeof plan.auth === "string" && plan.auth.includes(":")) {
386
+ const [email, ...rest] = plan.auth.split(":");
387
+ vars.email = email;
388
+ vars.password = rest.join(":");
389
+ }
390
+
391
+ return vars;
392
+ }
393
+
394
+ // ============================================================================
395
+ // Single Flow Execution
396
+ // ============================================================================
397
+
398
+ async function runSingleFlow({ flow, plan, page, telemetry, varsBase, results }) {
399
+ const flowStart = Date.now();
400
+ const flowVars = { ...varsBase, ...(flow.vars || {}) };
401
+
402
+ // Template all vars
403
+ for (const k of Object.keys(flowVars)) {
404
+ flowVars[k] = template(flowVars[k], flowVars);
405
+ }
406
+
407
+ const flowOut = {
408
+ id: flow.id,
409
+ name: flow.name,
410
+ source: flow.__source,
411
+ required: !!flow.required,
412
+ status: "success",
413
+ startedAt: new Date(flowStart).toISOString(),
414
+ durationMs: 0,
415
+ steps: [],
416
+ assertions: [],
417
+ errors: [],
418
+ };
419
+
420
+ // Run steps
421
+ let stepIndex = 0;
422
+ for (const step of flow.steps || []) {
423
+ stepIndex++;
424
+ const stepId = `${flow.id}#${stepIndex}`;
425
+ const stepName = step.name || `${step.action || "step"}-${stepIndex}`;
426
+ const stepStart = Date.now();
427
+
428
+ const stepRec = {
429
+ id: stepId,
430
+ name: stepName,
431
+ action: step.action,
432
+ target: step.target || null,
433
+ status: "success",
434
+ error: null,
435
+ danger: null,
436
+ artifacts: {},
437
+ startedAt: new Date(stepStart).toISOString(),
438
+ durationMs: 0,
439
+ };
440
+
441
+ // Trace marker
442
+ try {
443
+ await page.evaluate((label) => console.log(`[reality:trace] ${label}`), `STEP ${stepId}`);
444
+ } catch {}
445
+
446
+ // Before screenshot
447
+ const beforeShot = path.join(plan.artifacts.screenshotsDir, `${flow.id}__${stepIndex}__before.png`);
448
+ await screenshot(page, beforeShot);
449
+ stepRec.artifacts.beforeScreenshot = path.relative(plan.outputDir, beforeShot);
450
+ stepRec.artifacts.beforeDom = await domSnapshot(page);
451
+
452
+ // Execute action
453
+ try {
454
+ await executeStep({ step, stepRec, plan, page, flowVars, telemetry });
455
+ } catch (e) {
456
+ stepRec.status = "error";
457
+ stepRec.error = String(e?.message || e).slice(0, 220);
458
+ flowOut.errors.push({ step: stepIndex, message: stepRec.error });
459
+ }
460
+
461
+ // After screenshot
462
+ const afterShot = path.join(plan.artifacts.screenshotsDir, `${flow.id}__${stepIndex}__after.png`);
463
+ await screenshot(page, afterShot);
464
+ stepRec.artifacts.afterScreenshot = path.relative(plan.outputDir, afterShot);
465
+ stepRec.artifacts.afterDom = await domSnapshot(page);
466
+
467
+ stepRec.durationMs = Date.now() - stepStart;
468
+
469
+ // Add to timeline
470
+ results.timeline.push({
471
+ flowId: flow.id,
472
+ step: stepIndex,
473
+ name: stepRec.name,
474
+ action: stepRec.action,
475
+ status: stepRec.status,
476
+ at: Date.now(),
477
+ url: page.url(),
478
+ before: stepRec.artifacts.beforeScreenshot,
479
+ after: stepRec.artifacts.afterScreenshot,
480
+ });
481
+
482
+ flowOut.steps.push(stepRec);
483
+ }
484
+
485
+ // Run assertions
486
+ for (const a of flow.assertions || []) {
487
+ const ar = await runAssertion({ assertion: a, plan, page, telemetry, vars: flowVars });
488
+ flowOut.assertions.push(ar);
489
+ if (ar.status === "fail" && a.critical) {
490
+ flowOut.status = "error";
491
+ }
492
+ }
493
+
494
+ // Cleanup (best-effort)
495
+ for (const step of flow.cleanup || []) {
496
+ try {
497
+ await executeStep({ step, stepRec: {}, plan, page, flowVars, telemetry });
498
+ } catch {}
499
+ }
500
+
501
+ flowOut.durationMs = Date.now() - flowStart;
502
+ if (flowOut.errors.length > 0) flowOut.status = "error";
503
+
504
+ return flowOut;
505
+ }
506
+
507
+ // ============================================================================
508
+ // Step Execution
509
+ // ============================================================================
510
+
511
+ async function executeStep({ step, stepRec, plan, page, flowVars, telemetry }) {
512
+ const action = String(step.action || "").toLowerCase();
513
+
514
+ if (action === "navigate") {
515
+ const target = template(step.target || "/", flowVars);
516
+ const url = target.startsWith("http") ? target : (plan.baseUrl + (target.startsWith("/") ? target : `/${target}`));
517
+ const res = await safeGoto(page, url, plan.timeout);
518
+ if (!res.ok) throw new Error(res.error || `Navigation failed: ${url}`);
519
+ }
520
+
521
+ else if (action === "wait") {
522
+ if (step.for === "selector" && step.target) {
523
+ const target = template(step.target, flowVars);
524
+ const state = step.state || "visible";
525
+ const loc = await findLocator(page, target);
526
+ await loc.waitFor({ state, timeout: Number(step.timeout || plan.timeout) });
527
+ } else if (step.for === "navigation" && step.match) {
528
+ const match = new RegExp(template(step.match, flowVars));
529
+ const timeout = Number(step.timeout || plan.timeout);
530
+ const end = Date.now() + timeout;
531
+ while (Date.now() < end) {
532
+ if (match.test(page.url())) break;
533
+ await page.waitForTimeout(200);
534
+ }
535
+ if (!match.test(page.url())) throw new Error(`URL did not match ${step.match}`);
536
+ } else if (step.for === "network" && step.match) {
537
+ const match = template(step.match, flowVars);
538
+ const timeout = Number(step.timeout || plan.timeout);
539
+ const since = Date.now();
540
+ const end = since + timeout;
541
+ let found = false;
542
+ while (Date.now() < end) {
543
+ const recent = telemetry.responses.filter(r => r.ts >= since);
544
+ if (recent.some(r => r.url.includes(match))) { found = true; break; }
545
+ await page.waitForTimeout(200);
546
+ }
547
+ if (!found) throw new Error(`No network call matching ${match}`);
548
+ } else {
549
+ const ms = Number(step.timeout || step.ms || 1000);
550
+ await page.waitForTimeout(ms);
551
+ }
552
+ }
553
+
554
+ else if (action === "fill") {
555
+ const target = template(step.target, flowVars);
556
+ const value = template(step.value, flowVars);
557
+ const loc = await findLocator(page, target);
558
+ await loc.waitFor({ state: "visible", timeout: plan.timeout }).catch(() => {});
559
+ if (step.clear !== false) await loc.clear().catch(() => {});
560
+ await loc.fill(String(value ?? ""), { timeout: plan.timeout });
561
+ }
562
+
563
+ else if (action === "click") {
564
+ const target = template(step.target, flowVars);
565
+ const loc = await findLocator(page, target);
566
+
567
+ // Danger detection
568
+ let meta = {};
569
+ try {
570
+ meta = await loc.evaluate((el) => ({
571
+ text: (el.textContent || "").trim().slice(0, 120),
572
+ className: el.className || "",
573
+ ariaLabel: el.getAttribute("aria-label") || "",
574
+ role: el.getAttribute("role") || "",
575
+ }));
576
+ } catch {}
577
+
578
+ const danger = classifyDanger({ ...meta, url: page.url() });
579
+ stepRec.danger = danger;
580
+
581
+ const policy = String(step.dangerPolicy || "skip").toLowerCase();
582
+ if (danger.dangerous && !plan.danger) {
583
+ if (policy === "block") {
584
+ stepRec.status = "blocked";
585
+ stepRec.error = `Blocked destructive action (${danger.reason}). Use --danger to allow.`;
586
+ return;
587
+ }
588
+ stepRec.status = "skipped";
589
+ stepRec.error = `Skipped destructive action (${danger.reason}). Use --danger to allow.`;
590
+ return;
591
+ }
592
+
593
+ await loc.waitFor({ state: "visible", timeout: plan.timeout }).catch(() => {});
594
+ try {
595
+ await loc.click({ timeout: plan.timeout });
596
+ } catch {
597
+ await loc.click({ timeout: plan.timeout, force: true });
598
+ }
599
+ await page.waitForTimeout(300);
600
+ }
601
+
602
+ else if (action === "check") {
603
+ const target = template(step.target, flowVars);
604
+ const loc = await findLocator(page, target);
605
+ await loc.check({ timeout: plan.timeout });
606
+ }
607
+
608
+ else if (action === "uncheck") {
609
+ const target = template(step.target, flowVars);
610
+ const loc = await findLocator(page, target);
611
+ await loc.uncheck({ timeout: plan.timeout });
612
+ }
613
+
614
+ else if (action === "select") {
615
+ const target = template(step.target, flowVars);
616
+ const loc = await findLocator(page, target);
617
+ if (step.value) {
618
+ await loc.selectOption({ value: template(step.value, flowVars) });
619
+ } else if (step.label) {
620
+ await loc.selectOption({ label: template(step.label, flowVars) });
621
+ } else if (step.index != null) {
622
+ await loc.selectOption({ index: Number(step.index) });
623
+ }
624
+ }
625
+
626
+ else if (action === "hover") {
627
+ const target = template(step.target, flowVars);
628
+ const loc = await findLocator(page, target);
629
+ await loc.hover({ timeout: plan.timeout });
630
+ }
631
+
632
+ else if (action === "press") {
633
+ const key = template(step.key, flowVars);
634
+ await page.keyboard.press(key);
635
+ }
636
+
637
+ else if (action === "screenshot") {
638
+ const name = template(step.name || `manual-${Date.now()}`, flowVars);
639
+ const outPath = path.join(plan.artifacts.screenshotsDir, `${name}.png`);
640
+ await screenshot(page, outPath);
641
+ stepRec.artifacts.manualScreenshot = path.relative(plan.outputDir, outPath);
642
+ }
643
+
644
+ else if (action === "scroll") {
645
+ if (step.to === "bottom") {
646
+ await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
647
+ } else if (step.to === "top") {
648
+ await page.evaluate(() => window.scrollTo(0, 0));
649
+ } else if (step.target) {
650
+ const target = template(step.target, flowVars);
651
+ const loc = await findLocator(page, target);
652
+ await loc.scrollIntoViewIfNeeded();
653
+ }
654
+ }
655
+
656
+ else if (action === "execute") {
657
+ if (step.script) {
658
+ await page.evaluate(step.script);
659
+ } else if (step.scriptFile) {
660
+ const scriptPath = path.resolve(step.scriptFile);
661
+ const script = fs.readFileSync(scriptPath, "utf8");
662
+ await page.evaluate(script);
663
+ }
664
+ }
665
+
666
+ else {
667
+ stepRec.status = "unknown";
668
+ stepRec.error = `Unknown action: ${step.action}`;
669
+ }
670
+ }
671
+
672
+ // ============================================================================
673
+ // Assertions
674
+ // ============================================================================
675
+
676
+ async function runAssertion({ assertion, plan, page, telemetry, vars }) {
677
+ const type = String(assertion.type || "").toLowerCase().replace(/-/g, "");
678
+ const critical = !!assertion.critical;
679
+
680
+ const out = {
681
+ type: assertion.type,
682
+ critical,
683
+ status: "pass",
684
+ message: "",
685
+ };
686
+
687
+ try {
688
+ if (type === "urlcontains") {
689
+ const v = template(assertion.value, vars);
690
+ if (!page.url().toLowerCase().includes(v.toLowerCase())) {
691
+ throw new Error(`URL does not contain: ${v} (got ${page.url()})`);
692
+ }
693
+ }
694
+
695
+ else if (type === "urlmatches") {
696
+ const v = template(assertion.pattern || assertion.value, vars);
697
+ const re = new RegExp(v);
698
+ if (!re.test(page.url())) throw new Error(`URL does not match: ${v}`);
699
+ }
700
+
701
+ else if (type === "urlnotcontains") {
702
+ const v = template(assertion.value, vars);
703
+ if (page.url().toLowerCase().includes(v.toLowerCase())) {
704
+ throw new Error(`URL should not contain: ${v}`);
705
+ }
706
+ }
707
+
708
+ else if (type === "routechanged") {
709
+ const within = Number(assertion.within || 3000);
710
+ const match = assertion.match ? new RegExp(template(assertion.match, vars)) : null;
711
+ const before = page.url();
712
+ await page.waitForTimeout(Math.min(within, 500));
713
+ const after = page.url();
714
+ if (after === before) throw new Error(`Route did not change`);
715
+ if (match && !match.test(after)) throw new Error(`Route changed but does not match: ${assertion.match}`);
716
+ }
717
+
718
+ else if (type === "elementvisible") {
719
+ const target = template(assertion.target, vars);
720
+ const loc = await findLocator(page, target);
721
+ await loc.waitFor({ state: "visible", timeout: Number(assertion.within || plan.timeout) });
722
+ }
723
+
724
+ else if (type === "elementhidden") {
725
+ const target = template(assertion.target, vars);
726
+ const loc = await findLocator(page, target);
727
+ await loc.waitFor({ state: "hidden", timeout: Number(assertion.within || plan.timeout) });
728
+ }
729
+
730
+ else if (type === "elementtext") {
731
+ const target = template(assertion.target, vars);
732
+ const loc = await findLocator(page, target);
733
+ const text = await loc.textContent({ timeout: plan.timeout });
734
+ if (assertion.contains) {
735
+ const expected = template(assertion.contains, vars);
736
+ if (!String(text || "").includes(expected)) {
737
+ throw new Error(`Element text does not contain "${expected}"`);
738
+ }
739
+ }
740
+ if (assertion.matches) {
741
+ const re = new RegExp(template(assertion.matches, vars));
742
+ if (!re.test(text || "")) {
743
+ throw new Error(`Element text does not match ${assertion.matches}`);
744
+ }
745
+ }
746
+ }
747
+
748
+ else if (type === "elementcount") {
749
+ const target = template(assertion.target, vars);
750
+ const count = await page.locator(target).count();
751
+ if (assertion.min != null && count < assertion.min) {
752
+ throw new Error(`Element count ${count} < min ${assertion.min}`);
753
+ }
754
+ if (assertion.max != null && count > assertion.max) {
755
+ throw new Error(`Element count ${count} > max ${assertion.max}`);
756
+ }
757
+ if (assertion.equals != null && count !== assertion.equals) {
758
+ throw new Error(`Element count ${count} !== ${assertion.equals}`);
759
+ }
760
+ }
761
+
762
+ else if (type === "toastcontains" || type === "notificationvisible") {
763
+ const v = template(assertion.text || assertion.contains || assertion.value, vars);
764
+ const within = Number(assertion.within || 5000);
765
+ const toastSel = assertion.target || '[role="status"], [role="alert"], .toast, .sonner-toast, [data-sonner-toast], .notification';
766
+ const loc = page.locator(toastSel).filter({ hasText: new RegExp(v, "i") }).first();
767
+ await loc.waitFor({ state: "visible", timeout: within });
768
+ }
769
+
770
+ else if (type === "toastnotcontains") {
771
+ const v = template(assertion.text || assertion.value, vars);
772
+ const within = Number(assertion.within || 3000);
773
+ const toastSel = assertion.target || '[role="status"], [role="alert"], .toast, .sonner-toast, [data-sonner-toast], .notification';
774
+ await page.waitForTimeout(Math.min(within, 1500));
775
+ const count = await page.locator(toastSel).filter({ hasText: new RegExp(v, "i") }).count();
776
+ if (count > 0) throw new Error(`Toast contains forbidden text: ${v}`);
777
+ }
778
+
779
+ else if (type === "networkcalled") {
780
+ const v = template(assertion.match || assertion.value, vars);
781
+ const within = Number(assertion.within || 10000);
782
+ const minTimes = assertion.times ?? assertion.minTimes ?? 1;
783
+ const since = Date.now() - within;
784
+ const matches = telemetry.responses.filter(r => r.ts >= since && r.url.includes(v));
785
+ if (matches.length < minTimes) {
786
+ throw new Error(`Expected ${minTimes} calls matching ${v}, got ${matches.length}`);
787
+ }
788
+ }
789
+
790
+ else if (type === "networkstatus") {
791
+ const v = template(assertion.match || assertion.value, vars);
792
+ const expectStatus = Number(assertion.status || 200);
793
+ const within = Number(assertion.within || 10000);
794
+ const since = Date.now() - within;
795
+ const match = telemetry.responses.find(r => r.ts >= since && r.url.includes(v));
796
+ if (!match) throw new Error(`No response matching: ${v}`);
797
+ if (match.status !== expectStatus) {
798
+ throw new Error(`Expected ${expectStatus}, got ${match.status} for ${match.url}`);
799
+ }
800
+ }
801
+
802
+ else if (type === "networktiming") {
803
+ const v = template(assertion.match || assertion.value, vars);
804
+ const maxDuration = Number(assertion.maxDuration || 5000);
805
+ // Check request/response pairs - simplified version
806
+ const match = telemetry.responses.find(r => r.url.includes(v));
807
+ if (!match) throw new Error(`No response matching: ${v}`);
808
+ // We don't have timing data in this simplified version
809
+ out.message = `Network timing check (simplified): ${v}`;
810
+ }
811
+
812
+ else if (type === "noconsoleerrors") {
813
+ const severity = String(assertion.severity || "error").toLowerCase();
814
+ const windowMs = Number(assertion.within || 60000);
815
+ const since = Date.now() - windowMs;
816
+
817
+ const ignorePatterns = (assertion.ignore || []).map(p => new RegExp(p));
818
+
819
+ const bad = telemetry.console
820
+ .filter(m => m.ts >= since)
821
+ .filter(m => {
822
+ if (severity === "error") return m.type === "error";
823
+ if (severity === "warning" || severity === "warn") return m.type === "error" || m.type === "warning";
824
+ return m.type === "error";
825
+ })
826
+ .filter(m => !ignorePatterns.some(re => re.test(m.text)));
827
+
828
+ if (bad.length > 0) {
829
+ throw new Error(`Console ${bad[0].type}: ${String(bad[0].text || "").slice(0, 140)}`);
830
+ }
831
+ }
832
+
833
+ else if (type === "consolecontains") {
834
+ const match = template(assertion.match || assertion.value, vars);
835
+ const severity = String(assertion.severity || "log").toLowerCase();
836
+ const found = telemetry.console.some(m => {
837
+ if (severity !== "all" && m.type !== severity) return false;
838
+ return m.text.includes(match);
839
+ });
840
+ if (!found) throw new Error(`Console does not contain: ${match}`);
841
+ }
842
+
843
+ else if (type === "localstorage") {
844
+ const key = template(assertion.key, vars);
845
+ const value = await page.evaluate((k) => localStorage.getItem(k), key);
846
+ if (assertion.exists === true && value === null) {
847
+ throw new Error(`localStorage key "${key}" does not exist`);
848
+ }
849
+ if (assertion.exists === false && value !== null) {
850
+ throw new Error(`localStorage key "${key}" should not exist`);
851
+ }
852
+ if (assertion.equals != null && value !== assertion.equals) {
853
+ throw new Error(`localStorage "${key}" !== "${assertion.equals}"`);
854
+ }
855
+ if (assertion.matches) {
856
+ const re = new RegExp(assertion.matches);
857
+ if (!re.test(value || "")) throw new Error(`localStorage "${key}" does not match ${assertion.matches}`);
858
+ }
859
+ }
860
+
861
+ else if (type === "sessionstorage") {
862
+ const key = template(assertion.key, vars);
863
+ const value = await page.evaluate((k) => sessionStorage.getItem(k), key);
864
+ if (assertion.exists === true && value === null) {
865
+ throw new Error(`sessionStorage key "${key}" does not exist`);
866
+ }
867
+ if (assertion.matches) {
868
+ const re = new RegExp(assertion.matches);
869
+ if (!re.test(value || "")) throw new Error(`sessionStorage "${key}" does not match ${assertion.matches}`);
870
+ }
871
+ }
872
+
873
+ else {
874
+ out.status = "skip";
875
+ out.message = `Assertion type not implemented: ${assertion.type}`;
876
+ }
877
+ } catch (e) {
878
+ out.status = "fail";
879
+ out.message = String(e?.message || e).slice(0, 220);
880
+ }
881
+
882
+ return out;
883
+ }
884
+
885
+ // ============================================================================
886
+ // Score Calculation
887
+ // ============================================================================
888
+
889
+ function calculateScore(results) {
890
+ const routesDiscovered = results.coverage?.routesDiscovered || 0;
891
+ const routesWorking = results.coverage?.routesWorking || 0;
892
+ const routePct = routesDiscovered ? (routesWorking / routesDiscovered) : 1;
893
+
894
+ const flows = results.flows || [];
895
+ const flowTotal = flows.length || 1;
896
+ const flowOk = flows.filter(f => f.status === "success").length;
897
+
898
+ let assertsTotal = 0;
899
+ let assertsPass = 0;
900
+ for (const f of flows) {
901
+ for (const a of (f.assertions || [])) {
902
+ if (a.status === "skip") continue;
903
+ assertsTotal++;
904
+ if (a.status === "pass") assertsPass++;
905
+ }
906
+ }
907
+ const assertPct = assertsTotal ? (assertsPass / assertsTotal) : 1;
908
+
909
+ const errors = results.errors?.length || 0;
910
+ const penalty = Math.min(errors * 3, 20);
911
+
912
+ const score = (routePct * 30) + ((flowOk / flowTotal) * 40) + (assertPct * 30) - penalty;
913
+
914
+ return Math.max(0, Math.min(100, Math.round(score)));
915
+ }
916
+
917
+ module.exports = { runFlowPlan, classifyDanger, discoverSurface };