@reticlehq/server 3.9.0 → 3.10.1

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 (351) hide show
  1. package/SKILL.md +13 -12
  2. package/dist/command/cli/adhoc-suite.js +2 -0
  3. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  4. package/dist/command/cli/adhoc-verdict.js +50 -187
  5. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  6. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  7. package/dist/command/cli/cli-doctor.js +1 -1
  8. package/dist/command/cli/cli-flow-commands.js +2 -9
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  11. package/dist/command/cli/cli-parse-compare.js +75 -0
  12. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  13. package/dist/command/cli/cli-parse-grade.js +75 -0
  14. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  15. package/dist/command/cli/cli-parse-grammar.js +18 -20
  16. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  17. package/dist/command/cli/cli-parse-verify.js +63 -14
  18. package/dist/command/cli/cli-parse.d.ts +17 -15
  19. package/dist/command/cli/cli-parse.js +44 -27
  20. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  21. package/dist/command/cli/cli-update-commands.js +29 -20
  22. package/dist/command/cli/cli-usage.js +58 -29
  23. package/dist/command/cli/cli-verify.d.ts +11 -2
  24. package/dist/command/cli/cli-verify.js +27 -8
  25. package/dist/command/cli/cloud-cli.js +2 -119
  26. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  27. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  28. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  29. package/dist/command/cli/doctor-repairs.js +1 -1
  30. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  31. package/dist/command/cli/ensure-daemon.js +57 -0
  32. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  33. package/dist/command/cli/finished-run/grade-command.js +180 -0
  34. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  35. package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
  36. package/dist/command/cli/ports/port-holder.d.ts +9 -0
  37. package/dist/command/cli/ports/port-holder.js +27 -3
  38. package/dist/command/cli/status/status-lines.d.ts +1 -0
  39. package/dist/command/cli/status/status-lines.js +22 -0
  40. package/dist/command/cli/status-fields.d.ts +5 -1
  41. package/dist/command/cli/status-fields.js +7 -3
  42. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  43. package/dist/command/cli/two-builds/compare-command.js +314 -0
  44. package/dist/command/cli/verify-against.d.ts +61 -0
  45. package/dist/command/cli/verify-against.js +187 -0
  46. package/dist/command/cli/verify-no-session.d.ts +2 -0
  47. package/dist/command/cli/verify-no-session.js +13 -0
  48. package/dist/command/cli/verify-results.d.ts +35 -2
  49. package/dist/command/cli/verify-results.js +76 -10
  50. package/dist/command/cli.js +46 -41
  51. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  52. package/dist/command/daemon/daemon-resolve.js +27 -1
  53. package/dist/command/daemon/daemon.d.ts +12 -1
  54. package/dist/command/daemon/daemon.js +35 -4
  55. package/dist/command/daemon/lifetime/death-cause.js +21 -2
  56. package/dist/command/daemon/lifetime/heartbeat.js +7 -2
  57. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  58. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  59. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  60. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  61. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  62. package/dist/command/setup/bringup/static-page-server.js +43 -9
  63. package/dist/command/setup/init/init-runtime.js +43 -2
  64. package/dist/command/setup/node-effects.d.ts +3 -2
  65. package/dist/command/setup/node-effects.js +9 -3
  66. package/dist/command/setup/remaining-steps.d.ts +8 -0
  67. package/dist/command/setup/remaining-steps.js +10 -0
  68. package/dist/command/setup/run-setup.d.ts +2 -0
  69. package/dist/command/setup/run-setup.js +9 -1
  70. package/dist/command/setup/setup-command.js +6 -3
  71. package/dist/command/update/updater.js +6 -0
  72. package/dist/command/version/version-skew.js +1 -1
  73. package/dist/features/compare/action-plan.d.ts +260 -0
  74. package/dist/features/compare/action-plan.js +543 -0
  75. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  76. package/dist/features/compare/behaviour-compare.js +85 -0
  77. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  78. package/dist/features/compare/behaviour-diff.js +202 -0
  79. package/dist/features/compare/behaviour-report.d.ts +40 -0
  80. package/dist/features/compare/behaviour-report.js +128 -0
  81. package/dist/features/compare/build-readings.d.ts +106 -0
  82. package/dist/features/compare/build-readings.js +250 -0
  83. package/dist/features/compare/compare-diff.d.ts +62 -0
  84. package/dist/features/compare/compare-diff.js +177 -0
  85. package/dist/features/compare/compare-report-html.d.ts +6 -0
  86. package/dist/features/compare/compare-report-html.js +102 -0
  87. package/dist/features/compare/compare-report.d.ts +15 -0
  88. package/dist/features/compare/compare-report.js +73 -0
  89. package/dist/features/compare/compare-run.d.ts +60 -0
  90. package/dist/features/compare/compare-run.js +123 -0
  91. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  92. package/dist/features/compare/outside/effect-lines.js +103 -0
  93. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  94. package/dist/features/compare/outside/state-sources.js +164 -0
  95. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  96. package/dist/features/compare/outside/value-dictionary.js +227 -0
  97. package/dist/features/compare/visible-content.d.ts +20 -0
  98. package/dist/features/compare/visible-content.js +125 -0
  99. package/dist/features/crawl/crawl-empty.js +2 -1
  100. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  101. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  102. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  103. package/dist/features/crawl/crawl-tools.js +28 -12
  104. package/dist/features/crawl/crawl.d.ts +8 -9
  105. package/dist/features/crawl/crawl.js +61 -65
  106. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  107. package/dist/features/exhaust/app-coverage.js +3 -1
  108. package/dist/features/exhaust/code-coverage.js +8 -4
  109. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  110. package/dist/features/exhaust/edge-cases.js +310 -0
  111. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  112. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  113. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  114. package/dist/features/exhaust/edges/edge-run.js +169 -0
  115. package/dist/features/exhaust/explorer.d.ts +87 -14
  116. package/dist/features/exhaust/explorer.js +159 -25
  117. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  118. package/dist/features/exhaust/failure-branches.js +29 -11
  119. package/dist/features/exhaust/invariants.d.ts +112 -0
  120. package/dist/features/exhaust/invariants.js +188 -0
  121. package/dist/features/exhaust/ledger.d.ts +68 -0
  122. package/dist/features/exhaust/ledger.js +121 -13
  123. package/dist/features/exhaust/session-fold.d.ts +10 -0
  124. package/dist/features/exhaust/session-fold.js +46 -3
  125. package/dist/features/exhaust/session-port.d.ts +3 -1
  126. package/dist/features/exhaust/session-port.js +97 -28
  127. package/dist/features/grade/grade-answer.d.ts +99 -0
  128. package/dist/features/grade/grade-answer.js +112 -0
  129. package/dist/features/grade/grade-noise.d.ts +27 -0
  130. package/dist/features/grade/grade-noise.js +55 -0
  131. package/dist/features/grade/grade-renumber.d.ts +12 -0
  132. package/dist/features/grade/grade-renumber.js +86 -0
  133. package/dist/features/grade/grade-state.d.ts +50 -0
  134. package/dist/features/grade/grade-state.js +93 -0
  135. package/dist/features/grade/grade-suggest.d.ts +33 -0
  136. package/dist/features/grade/grade-suggest.js +98 -0
  137. package/dist/features/visual/visual-tools.js +66 -19
  138. package/dist/index.js +7 -4
  139. package/dist/judgement/capsule/causal-summary.js +9 -2
  140. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  141. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  142. package/dist/judgement/runs/reticle-runner.js +16 -2
  143. package/dist/judgement/runs/runner-port.d.ts +2 -0
  144. package/dist/judgement/runs/runner-port.js +23 -5
  145. package/dist/language/flows/affected-tools.d.ts +1 -1
  146. package/dist/language/flows/affected-tools.js +49 -30
  147. package/dist/language/flows/change/git-changed.d.ts +2 -1
  148. package/dist/language/flows/change/git-changed.js +19 -2
  149. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  150. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  151. package/dist/language/flows/flow-anchor.d.ts +60 -2
  152. package/dist/language/flows/flow-anchor.js +125 -20
  153. package/dist/language/flows/flow-classify.d.ts +20 -0
  154. package/dist/language/flows/flow-classify.js +90 -12
  155. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  156. package/dist/language/flows/flow-cross-step.js +25 -1
  157. package/dist/language/flows/flow-journey.d.ts +38 -4
  158. package/dist/language/flows/flow-journey.js +138 -8
  159. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  160. package/dist/language/flows/flow-replay-run.js +76 -14
  161. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  162. package/dist/language/flows/flow-replay.d.ts +19 -1
  163. package/dist/language/flows/flow-replay.js +169 -52
  164. package/dist/language/flows/flow-replayable.js +7 -1
  165. package/dist/language/flows/flow-start-path.d.ts +34 -5
  166. package/dist/language/flows/flow-start-path.js +202 -15
  167. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  168. package/dist/language/flows/flow-step-runners.js +184 -109
  169. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  170. package/dist/language/flows/flow-store-for-session.js +44 -0
  171. package/dist/language/flows/flow-tools.js +46 -33
  172. package/dist/language/flows/flows.js +176 -76
  173. package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
  174. package/dist/language/flows/recording/tape/recordings.js +71 -5
  175. package/dist/language/flows/replay.d.ts +19 -5
  176. package/dist/language/flows/replay.js +99 -7
  177. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  178. package/dist/language/flows/steps/page-steps.js +198 -0
  179. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  180. package/dist/language/flows/suite/sequential-run.js +96 -0
  181. package/dist/language/flows/verify-change-tools.js +26 -15
  182. package/dist/memory/cloud/background-sync.d.ts +13 -0
  183. package/dist/memory/cloud/background-sync.js +24 -3
  184. package/dist/memory/cloud/sync-daemon.js +26 -27
  185. package/dist/memory/journal/attach-journal.js +2 -1
  186. package/dist/memory/journal/drive-flow.d.ts +7 -0
  187. package/dist/memory/journal/drive-flow.js +44 -13
  188. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  189. package/dist/memory/journal/session-journal.d.ts +7 -0
  190. package/dist/memory/journal/session-journal.js +58 -10
  191. package/dist/memory/project/root-claims.d.ts +9 -0
  192. package/dist/memory/project/root-claims.js +9 -4
  193. package/dist/portal/input/download-capture.d.ts +7 -0
  194. package/dist/portal/input/download-capture.js +92 -0
  195. package/dist/portal/input/network-detail.d.ts +6 -0
  196. package/dist/portal/input/network-detail.js +12 -1
  197. package/dist/portal/input/network-mock-tools.js +49 -13
  198. package/dist/portal/input/network-mock.d.ts +8 -0
  199. package/dist/portal/input/network-mock.js +28 -9
  200. package/dist/portal/input/page-capture.d.ts +24 -0
  201. package/dist/portal/input/page-capture.js +33 -0
  202. package/dist/portal/input/real-input.d.ts +22 -20
  203. package/dist/portal/input/real-input.js +41 -31
  204. package/dist/portal/input/viewport-tools.d.ts +8 -0
  205. package/dist/portal/input/viewport-tools.js +75 -18
  206. package/dist/portal/pool/browser-pool.d.ts +11 -29
  207. package/dist/portal/pool/browser-pool.js +35 -17
  208. package/dist/portal/pool/context-permissions.d.ts +17 -3
  209. package/dist/portal/pool/context-permissions.js +36 -5
  210. package/dist/portal/pool/determinism.d.ts +52 -0
  211. package/dist/portal/pool/determinism.js +259 -0
  212. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  213. package/dist/portal/pool/lease-emulation.js +18 -0
  214. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  215. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  216. package/dist/portal/pool/lease-page-error.d.ts +44 -17
  217. package/dist/portal/pool/lease-page-error.js +147 -5
  218. package/dist/portal/pool/page-error-journal.d.ts +7 -1
  219. package/dist/portal/pool/page-error-journal.js +27 -0
  220. package/dist/portal/pool/playwright-launcher.js +51 -8
  221. package/dist/portal/pool/pool-contract.d.ts +27 -1
  222. package/dist/portal/pool/pool-lease.d.ts +36 -1
  223. package/dist/portal/pool/zero-install.d.ts +8 -0
  224. package/dist/portal/pool/zero-install.js +23 -1
  225. package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
  226. package/dist/portal/session/dev-server/dev-command.js +61 -15
  227. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  228. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  229. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  230. package/dist/portal/session/dev-server/project-ports.js +62 -0
  231. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  232. package/dist/portal/session/facts/departed-network.js +142 -0
  233. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  234. package/dist/portal/session/no-session-diagnosis.js +67 -25
  235. package/dist/portal/session/no-session-next-action.d.ts +2 -0
  236. package/dist/portal/session/no-session-next-action.js +12 -3
  237. package/dist/portal/session/no-session-watch.d.ts +6 -0
  238. package/dist/portal/session/no-session-watch.js +38 -3
  239. package/dist/portal/session/serving-directory.d.ts +10 -0
  240. package/dist/portal/session/serving-directory.js +39 -8
  241. package/dist/portal/session/session-manager.d.ts +10 -0
  242. package/dist/portal/session/session-manager.js +174 -63
  243. package/dist/start-wiring.d.ts +2 -1
  244. package/dist/start-wiring.js +2 -1
  245. package/dist/surface/mcp/mcp.js +3 -2
  246. package/dist/surface/mcp/shared-params.d.ts +2 -0
  247. package/dist/surface/mcp/shared-params.js +31 -0
  248. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  249. package/dist/surface/tools/act/act-observation.js +118 -12
  250. package/dist/surface/tools/act/act-preflight.js +1 -1
  251. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  252. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  253. package/dist/surface/tools/act/after-match.d.ts +4 -1
  254. package/dist/surface/tools/act/after-match.js +8 -1
  255. package/dist/surface/tools/act/already-true.js +1 -1
  256. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  257. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  258. package/dist/surface/tools/act/resolve-target.d.ts +15 -1
  259. package/dist/surface/tools/act/resolve-target.js +24 -1
  260. package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
  261. package/dist/surface/tools/act/sequence-grade.js +37 -26
  262. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  263. package/dist/surface/tools/act-command.d.ts +23 -0
  264. package/dist/surface/tools/act-command.js +50 -0
  265. package/dist/surface/tools/act-sequence-tool.js +78 -50
  266. package/dist/surface/tools/act-tools.d.ts +1 -13
  267. package/dist/surface/tools/act-tools.js +74 -86
  268. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  269. package/dist/surface/tools/agent/build-staleness.js +1 -1
  270. package/dist/surface/tools/assert/assert-verdict.js +16 -37
  271. package/dist/surface/tools/browser-tools.js +12 -6
  272. package/dist/surface/tools/compare-tools.d.ts +11 -0
  273. package/dist/surface/tools/compare-tools.js +94 -0
  274. package/dist/surface/tools/coverage-tools.js +1 -1
  275. package/dist/surface/tools/dynamic-tools.js +9 -2
  276. package/dist/surface/tools/error-recovery.d.ts +12 -1
  277. package/dist/surface/tools/error-recovery.js +91 -21
  278. package/dist/surface/tools/explore-tools.js +1 -1
  279. package/dist/surface/tools/invoke-tool.js +3 -2
  280. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  281. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  282. package/dist/surface/tools/lease-hint.d.ts +35 -1
  283. package/dist/surface/tools/lease-hint.js +42 -0
  284. package/dist/surface/tools/lease-readiness.d.ts +26 -0
  285. package/dist/surface/tools/lease-readiness.js +79 -0
  286. package/dist/surface/tools/lease-seed.d.ts +14 -1
  287. package/dist/surface/tools/lease-seed.js +35 -3
  288. package/dist/surface/tools/lease-tools.d.ts +3 -41
  289. package/dist/surface/tools/lease-tools.js +62 -110
  290. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  291. package/dist/surface/tools/lease-url-identity.js +97 -0
  292. package/dist/surface/tools/merge-tools.js +99 -2
  293. package/dist/surface/tools/merged-name-redirect.js +5 -1
  294. package/dist/surface/tools/observe-tools.js +15 -9
  295. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  296. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  297. package/dist/surface/tools/read-tools.js +33 -30
  298. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  299. package/dist/surface/tools/real-input-attempt.js +143 -28
  300. package/dist/surface/tools/session-ports.d.ts +14 -0
  301. package/dist/surface/tools/session-ports.js +53 -0
  302. package/dist/surface/tools/tool-kit.d.ts +6 -0
  303. package/dist/surface/tools/tool-kit.js +2 -3
  304. package/dist/surface/tools/tool-surface.js +0 -1
  305. package/dist/surface/tools/tools.js +28 -5
  306. package/dist/surface/tools/type-spelling.d.ts +13 -0
  307. package/dist/surface/tools/type-spelling.js +36 -0
  308. package/dist/telemetry/agent-client.d.ts +6 -0
  309. package/dist/telemetry/agent-client.js +10 -0
  310. package/dist/telemetry/argument-shape.js +1 -0
  311. package/dist/telemetry/tool-refused.d.ts +6 -3
  312. package/dist/telemetry/tool-refused.js +8 -3
  313. package/docs/actions.mdx +3 -3
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/capabilities.mdx +61 -1
  316. package/docs/cli/affected.mdx +9 -7
  317. package/docs/cli/cloud.mdx +3 -3
  318. package/docs/cli/compare.mdx +94 -0
  319. package/docs/cli/doctor.mdx +1 -1
  320. package/docs/cli/grade.mdx +97 -0
  321. package/docs/cli/init.mdx +2 -2
  322. package/docs/cli/license.mdx +9 -7
  323. package/docs/cli/restart.mdx +7 -5
  324. package/docs/cli/rollback.mdx +5 -5
  325. package/docs/cli/stop.mdx +3 -3
  326. package/docs/cli/update.mdx +2 -2
  327. package/docs/cli/verify.mdx +23 -1
  328. package/docs/cli.mdx +33 -23
  329. package/docs/deploy-checks.md +1 -1
  330. package/docs/desktop.mdx +1 -1
  331. package/docs/enterprise.md +2 -2
  332. package/docs/environment.mdx +1 -1
  333. package/docs/flows.md +11 -1
  334. package/docs/frameworks.mdx +9 -9
  335. package/docs/getting-started.md +1 -1
  336. package/docs/install-manual.mdx +1 -1
  337. package/docs/license-activation.mdx +3 -3
  338. package/docs/packages/tauri.mdx +1 -1
  339. package/docs/packages.mdx +1 -1
  340. package/docs/predicates.mdx +1 -1
  341. package/docs/quickstart.mdx +68 -49
  342. package/docs/skill-setup.md +1 -1
  343. package/docs/skill.md +14 -13
  344. package/docs/telemetry.md +2 -2
  345. package/docs/tools/navigate.mdx +1 -1
  346. package/docs/tools/verify.mdx +4 -1
  347. package/docs/troubleshooting.mdx +30 -1
  348. package/docs/usage.md +17 -14
  349. package/package.json +11 -14
  350. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  351. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Values the app compares its fields with, read from the scripts it serves. No instrumentation:
3
+ * the page HTML and its scripts are fetched the way a browser fetches them.
4
+ *
5
+ * In `values.route === "progress"` the field named route gets "progress". `switch (x.route)` cases
6
+ * and `["a", "b"].includes(x.route)` give their literals the same way. An explorer that does not
7
+ * know this never types the value, and the branch behind it (a conditional step, a special mode)
8
+ * stays unseen. A field matches a name when its folded name equals the name, or ends with a name of
9
+ * three or more characters.
10
+ *
11
+ * The same scripts give the url parameters the page reads and the values it compares each with
12
+ * (`urlParamValues`).
13
+ */
14
+ /** Values by folded name. */
15
+ export type ValueDictionary = ReadonlyMap<string, readonly string[]>;
16
+ /** The most script files read for one page. */
17
+ export declare const MAX_SCRIPTS = 40;
18
+ /** A name, lowercase, letters and digits only. */
19
+ export declare const foldName: (name: string) => string;
20
+ export declare const scriptSources: (html: string) => string[];
21
+ export declare const chunkImports: (js: string) => string[];
22
+ /** `[name, literal]` for each comparison of a named value with a string literal, in source order. */
23
+ export declare function comparedLiterals(js: string): [string, string][];
24
+ export declare function buildDictionary(pairs: readonly (readonly [string, string])[]): ValueDictionary;
25
+ /** The values for a field with this name, or none. */
26
+ export declare function magicValues(dictionary: ValueDictionary, name: string): readonly string[];
27
+ /**
28
+ * `[parameter, literal]` for each url parameter the page reads, such as
29
+ * `new URLSearchParams(location.search).get("scenario")`, with each literal compared in the code
30
+ * after that read. A state behind the url (`?scenario=deferred`) is then a start page of its own.
31
+ */
32
+ export declare function urlParamValues(js: string): [string, string][];
33
+ /**
34
+ * The keys the app listens for, in source order, each once: the literals its scripts compare a key
35
+ * event's `key` or `code` with, then its shortcut strings. A build that answers to the wrong key
36
+ * shows a difference only when that key is pressed.
37
+ */
38
+ export declare function appKeys(js: string): string[];
39
+ /** What the scripts of a page tell the plan. */
40
+ export interface ScriptHints {
41
+ dictionary: ValueDictionary;
42
+ /** The keys the app listens for (`appKeys`), each once. */
43
+ keys: readonly string[];
44
+ /** The values each url parameter is compared with, in source order, each value once. */
45
+ urlParams: ReadonlyMap<string, readonly string[]>;
46
+ }
47
+ /**
48
+ * Fetch the page and its scripts, following script src and imported chunks, up to `MAX_SCRIPTS`
49
+ * files. `fetchText` answers undefined for a file it could not get; that file adds nothing.
50
+ */
51
+ export declare function harvestScripts(pageUrl: string, fetchText: (url: string) => Promise<string | undefined>): Promise<ScriptHints>;
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Values the app compares its fields with, read from the scripts it serves. No instrumentation:
3
+ * the page HTML and its scripts are fetched the way a browser fetches them.
4
+ *
5
+ * In `values.route === "progress"` the field named route gets "progress". `switch (x.route)` cases
6
+ * and `["a", "b"].includes(x.route)` give their literals the same way. An explorer that does not
7
+ * know this never types the value, and the branch behind it (a conditional step, a special mode)
8
+ * stays unseen. A field matches a name when its folded name equals the name, or ends with a name of
9
+ * three or more characters.
10
+ *
11
+ * The same scripts give the url parameters the page reads and the values it compares each with
12
+ * (`urlParamValues`).
13
+ */
14
+ /** The most script files read for one page. */
15
+ export const MAX_SCRIPTS = 40;
16
+ /** The most values kept for one name. */
17
+ const MAX_VALUES = 4;
18
+ /** The longest literal kept: a longer one is text, not a value a field is compared with. */
19
+ const MAX_LITERAL = 40;
20
+ /** A name this short matches only exactly: `id` must not match every field that ends in "id". */
21
+ const MIN_SUFFIX = 3;
22
+ const SCRIPT_SRC = /<script[^>]+src=["']([^"']+)["']/g;
23
+ /** A module path: a built chunk, or a source module a dev server serves, with an optional query. */
24
+ const CHUNK = /["'](\.{0,2}\/[\w./@-]+\.(?:m?js|jsx|tsx?)(?:\?[^"']*)?)["']/g;
25
+ /** Library code and the dev server's own client: they hold no comparison of the app's fields. */
26
+ const NOT_APP = /\/node_modules\/|^\/@/;
27
+ const NAME_THEN_LITERAL = /\.([A-Za-z_$][\w$]*)\s*[!=]==?\s*["']([^"'\\]{1,40})["']/g;
28
+ const LITERAL_THEN_NAME = /["']([^"'\\]{1,40})["']\s*[!=]==?\s*[\w$.]*\.([A-Za-z_$][\w$]*)\b/g;
29
+ /** `switch (x.name) { ... }`: the body, up to a bound, holds the cases. */
30
+ const SWITCH = /switch\s*\(\s*[\w$.]*\.([A-Za-z_$][\w$]*)\s*\)\s*\{([\s\S]{0,3000}?)\}/g;
31
+ const CASE = /case\s*["']([^"'\\]{1,40})["']\s*:/g;
32
+ /**
33
+ * `["a", "b"].includes(x.name)`. Each repeated item must end in a comma and the count is bounded, so
34
+ * there is one way to match a list: an optional comma inside a repeated group backtracked exponentially
35
+ * on a hostile script, and compare reads scripts from any page.
36
+ */
37
+ const INCLUDES = /\[\s*((?:["'][^"'\\]{1,40}["']\s*,\s*){0,30}["'][^"'\\]{1,40}["'])\s*,?\s*\]\.includes\(\s*[\w$.]*\.([A-Za-z_$][\w$]*)\s*\)/g;
38
+ /**
39
+ * What `typeof` returns. A `typeof x.name === "string"` check compares a type, not a value the app expects in
40
+ * the field; bundled libraries are full of them, and they gave a field named Name the value "string".
41
+ */
42
+ const TYPE_NAMES = new Set([
43
+ 'string',
44
+ 'number',
45
+ 'boolean',
46
+ 'object',
47
+ 'function',
48
+ 'undefined',
49
+ 'symbol',
50
+ 'bigint',
51
+ ]);
52
+ const QUOTED = /["']([^"'\\]{1,40})["']/g;
53
+ /** A name, lowercase, letters and digits only. */
54
+ export const foldName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, '');
55
+ export const scriptSources = (html) => [...html.matchAll(SCRIPT_SRC)].map((m) => m[1] ?? '').filter((src) => src.length > 0);
56
+ export const chunkImports = (js) => [...js.matchAll(CHUNK)]
57
+ .map((m) => m[1] ?? '')
58
+ .filter((src) => src.length > 0 && !NOT_APP.test(src));
59
+ /** `[name, literal]` for each comparison of a named value with a string literal, in source order. */
60
+ export function comparedLiterals(js) {
61
+ const found = [];
62
+ for (const m of js.matchAll(NAME_THEN_LITERAL)) {
63
+ found.push({ at: m.index, pair: [m[1] ?? '', m[2] ?? ''] });
64
+ }
65
+ for (const m of js.matchAll(LITERAL_THEN_NAME)) {
66
+ found.push({ at: m.index, pair: [m[2] ?? '', m[1] ?? ''] });
67
+ }
68
+ for (const m of js.matchAll(SWITCH)) {
69
+ for (const c of (m[2] ?? '').matchAll(CASE)) {
70
+ found.push({ at: m.index + c.index, pair: [m[1] ?? '', c[1] ?? ''] });
71
+ }
72
+ }
73
+ for (const m of js.matchAll(INCLUDES)) {
74
+ for (const v of (m[1] ?? '').matchAll(QUOTED)) {
75
+ found.push({ at: m.index + v.index, pair: [m[2] ?? '', v[1] ?? ''] });
76
+ }
77
+ }
78
+ return found
79
+ .filter((f) => !TYPE_NAMES.has(f.pair[1]))
80
+ .sort((a, b) => a.at - b.at)
81
+ .map((f) => f.pair);
82
+ }
83
+ export function buildDictionary(pairs) {
84
+ const out = new Map();
85
+ for (const [name, value] of pairs) {
86
+ const key = foldName(name);
87
+ const values = out.get(key) ?? [];
88
+ if (key.length > 0 &&
89
+ value.length > 0 &&
90
+ value.length <= MAX_LITERAL &&
91
+ !values.includes(value)) {
92
+ out.set(key, [...values, value].slice(0, MAX_VALUES));
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ /** The values for a field with this name, or none. */
98
+ export function magicValues(dictionary, name) {
99
+ const folded = foldName(name);
100
+ if (0 === folded.length) {
101
+ return [];
102
+ }
103
+ const exact = dictionary.get(folded);
104
+ if (exact !== undefined) {
105
+ return exact;
106
+ }
107
+ for (const [key, values] of dictionary) {
108
+ if (key.length >= MIN_SUFFIX && folded.endsWith(key)) {
109
+ return values;
110
+ }
111
+ }
112
+ return [];
113
+ }
114
+ /** `src` as an absolute url against `base`, or `src` when it cannot be resolved. */
115
+ function resolve(src, base) {
116
+ try {
117
+ return new URL(src, base).href;
118
+ }
119
+ catch {
120
+ return src;
121
+ }
122
+ }
123
+ /** `.get("name")`: a read of a url parameter when the code before it names the url. */
124
+ const PARAM_GET = /\.get\(\s*["']([A-Za-z_][\w-]{0,30})["']\s*\)/g;
125
+ const NEAR_URL = /URLSearchParams|searchParams|location/;
126
+ /** How far before a `.get(...)` the url must be named. */
127
+ const URL_BEFORE_CHARS = 200;
128
+ /** How far after a `.get(...)` the compared literals are read. Minified code renames the variable. */
129
+ const PARAM_AFTER_CHARS = 3000;
130
+ const PARAM_LITERAL = /[!=]==?\s*["']([\w-]{1,30})["']|["']([\w-]{1,30})["']\s*[!=]==|case\s*["']([\w-]{1,30})["']\s*:/g;
131
+ /**
132
+ * `[parameter, literal]` for each url parameter the page reads, such as
133
+ * `new URLSearchParams(location.search).get("scenario")`, with each literal compared in the code
134
+ * after that read. A state behind the url (`?scenario=deferred`) is then a start page of its own.
135
+ */
136
+ export function urlParamValues(js) {
137
+ const out = [];
138
+ for (const m of js.matchAll(PARAM_GET)) {
139
+ const before = js.slice(Math.max(0, m.index - URL_BEFORE_CHARS), m.index);
140
+ if (!NEAR_URL.test(before)) {
141
+ continue;
142
+ }
143
+ for (const v of js.slice(m.index, m.index + PARAM_AFTER_CHARS).matchAll(PARAM_LITERAL)) {
144
+ out.push([m[1] ?? '', v[1] ?? v[2] ?? v[3] ?? '']);
145
+ }
146
+ }
147
+ return out;
148
+ }
149
+ /** The names a key event's key is read by: `e.key === "k"`, `e.code === "Slash"`. */
150
+ const KEY_NAMES = new Set(['key', 'code']);
151
+ /** A longer literal compared with a key is text, not a key name. */
152
+ const MAX_KEY_CHARS = 12;
153
+ /** A shortcut string in a hotkey table: "mod+k", "ctrl+shift+p". */
154
+ const SHORTCUT = /["']((?:(?:mod|ctrl|control|cmd|command|meta|alt|option|shift)\+){1,3}[A-Za-z0-9/?.,;=-])["']/gi;
155
+ const SHORTCUT_JOIN = '+';
156
+ /** "mod" is Meta on a Mac and Control elsewhere, so a "mod" shortcut is pressed both ways. */
157
+ const MOD = 'mod';
158
+ const MOD_KEYS = ['Meta', 'Control'];
159
+ const MODIFIER_KEY = {
160
+ ctrl: 'Control',
161
+ control: 'Control',
162
+ cmd: 'Meta',
163
+ command: 'Meta',
164
+ meta: 'Meta',
165
+ alt: 'Alt',
166
+ option: 'Alt',
167
+ shift: 'Shift',
168
+ };
169
+ /** The key names of one shortcut string, each modifier as the key a press names. */
170
+ function shortcutPresses(shortcut) {
171
+ const parts = shortcut.split(SHORTCUT_JOIN);
172
+ const last = parts.at(-1) ?? '';
173
+ const mods = parts.slice(0, -1).map((m) => m.toLowerCase());
174
+ const each = mods.includes(MOD) ? MOD_KEYS : [undefined];
175
+ return each.map((mod) => [...mods.map((m) => (MOD === m ? (mod ?? '') : (MODIFIER_KEY[m] ?? m))), last].join(SHORTCUT_JOIN));
176
+ }
177
+ /**
178
+ * The keys the app listens for, in source order, each once: the literals its scripts compare a key
179
+ * event's `key` or `code` with, then its shortcut strings. A build that answers to the wrong key
180
+ * shows a difference only when that key is pressed.
181
+ */
182
+ export function appKeys(js) {
183
+ const out = new Set();
184
+ for (const [name, value] of comparedLiterals(js)) {
185
+ if (KEY_NAMES.has(foldName(name)) && value.length <= MAX_KEY_CHARS) {
186
+ out.add(value);
187
+ }
188
+ }
189
+ for (const m of js.matchAll(SHORTCUT)) {
190
+ for (const press of shortcutPresses(m[1] ?? '')) {
191
+ out.add(press);
192
+ }
193
+ }
194
+ return [...out];
195
+ }
196
+ /**
197
+ * Fetch the page and its scripts, following script src and imported chunks, up to `MAX_SCRIPTS`
198
+ * files. `fetchText` answers undefined for a file it could not get; that file adds nothing.
199
+ */
200
+ export async function harvestScripts(pageUrl, fetchText) {
201
+ const html = (await fetchText(pageUrl)) ?? '';
202
+ const queue = scriptSources(html).map((src) => resolve(src, pageUrl));
203
+ const seen = new Set();
204
+ const pairs = [];
205
+ const urlParams = new Map();
206
+ const keys = new Set();
207
+ while (queue.length > 0 && seen.size < MAX_SCRIPTS) {
208
+ const url = queue.shift() ?? '';
209
+ if (seen.has(url)) {
210
+ continue;
211
+ }
212
+ seen.add(url);
213
+ const js = (await fetchText(url)) ?? '';
214
+ queue.push(...chunkImports(js).map((src) => resolve(src, url)));
215
+ pairs.push(...comparedLiterals(js));
216
+ for (const key of appKeys(js)) {
217
+ keys.add(key);
218
+ }
219
+ for (const [param, value] of urlParamValues(js)) {
220
+ const values = urlParams.get(param) ?? [];
221
+ if (!values.includes(value)) {
222
+ urlParams.set(param, [...values, value]);
223
+ }
224
+ }
225
+ }
226
+ return { dictionary: buildDictionary(pairs), keys: [...keys], urlParams };
227
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * What a person can read and use on a page, from its accessibility tree.
3
+ *
4
+ * Two builds of one app are compared on this, so it keeps only what a person sees: the accessible
5
+ * name, the value, the element's own text, and the checked, selected, disabled, expanded and pressed
6
+ * states. Refs, layout, test ids, source locations and component names change between builds with
7
+ * nothing visible changing, so they are removed. Focus is removed too: it moves with timing.
8
+ *
9
+ * One line for each element that shows something, in page order. Case is folded, spacing is folded,
10
+ * and an ellipsis is dropped, because a cut at another width is not a change in content.
11
+ */
12
+ /** The visible content of an accessibility tree, one line for each element that shows something. */
13
+ export declare function visibleLines(tree: string): string[];
14
+ /**
15
+ * The visible content with the role of each control and structure element kept: `cell`,
16
+ * `cell john doe`, `button save`. Text alone cannot see a change of structure: an empty cell that
17
+ * keeps the columns of a table aligned, a button that became plain text, or a checkbox removed while
18
+ * its label stays. A line with a text-only role is its text, and it is dropped when it has no text.
19
+ */
20
+ export declare function structureLines(tree: string): string[];
@@ -0,0 +1,125 @@
1
+ /**
2
+ * What a person can read and use on a page, from its accessibility tree.
3
+ *
4
+ * Two builds of one app are compared on this, so it keeps only what a person sees: the accessible
5
+ * name, the value, the element's own text, and the checked, selected, disabled, expanded and pressed
6
+ * states. Refs, layout, test ids, source locations and component names change between builds with
7
+ * nothing visible changing, so they are removed. Focus is removed too: it moves with timing.
8
+ *
9
+ * One line for each element that shows something, in page order. Case is folded, spacing is folded,
10
+ * and an ellipsis is dropped, because a cut at another width is not a change in content.
11
+ */
12
+ /** The accessible name: the first quoted part after the role, up to the next field. */
13
+ const NAME = /^\s*- \S+ "(.*?)"(?= \(ref=| \[|$)/;
14
+ /** A quoted field, up to the next field. */
15
+ const quotedField = (field) => new RegExp(`\\[${field}="(.*?)"\\](?= \\[| \\(|$)`);
16
+ const VALUE = quotedField('value');
17
+ const OWN_TEXT = quotedField('text');
18
+ /** The state group the snapshot writes, such as `[disabled,checked]`. */
19
+ const STATES = /\[((?:[a-z]+)(?:,[a-z]+)*)\]/g;
20
+ /** The states a person can see on a control. */
21
+ const SHOWN_STATES = new Set([
22
+ 'checked',
23
+ 'selected',
24
+ 'disabled',
25
+ 'expanded',
26
+ 'pressed',
27
+ ]);
28
+ /** A state the snapshot writes that moves with timing, not with the build. */
29
+ const UNSHOWN_STATE = 'focused';
30
+ const ELLIPSIS = '…';
31
+ const VALUE_JOIN = ' = ';
32
+ const fold = (text) => text.toLowerCase().replaceAll(ELLIPSIS, '').split(/\s+/).filter(Boolean).join(' ');
33
+ function statesOf(line) {
34
+ const found = [];
35
+ for (const match of line.matchAll(STATES)) {
36
+ const group = (match[1] ?? '').split(',');
37
+ // A group with any word that is not a state is another field, for example `[grid]`.
38
+ if (group.every((word) => SHOWN_STATES.has(word) || UNSHOWN_STATE === word)) {
39
+ found.push(...group.filter((word) => SHOWN_STATES.has(word)));
40
+ }
41
+ }
42
+ return found;
43
+ }
44
+ /** The visible line for one tree line, or '' when the element shows nothing. */
45
+ function visibleLine(line) {
46
+ const name = fold(NAME.exec(line)?.[1] ?? '');
47
+ const value = fold(VALUE.exec(line)?.[1] ?? '');
48
+ const text = fold(OWN_TEXT.exec(line)?.[1] ?? '');
49
+ const states = statesOf(line);
50
+ const parts = [];
51
+ if (name.length > 0) {
52
+ parts.push(name);
53
+ }
54
+ if (text.length > 0) {
55
+ parts.push(text);
56
+ }
57
+ let out = parts.join(' ');
58
+ if (value.length > 0) {
59
+ out = out.length > 0 ? `${out}${VALUE_JOIN}${value}` : value;
60
+ }
61
+ if (states.length > 0) {
62
+ const shown = `[${states.join(',')}]`;
63
+ out = out.length > 0 ? `${out} ${shown}` : shown;
64
+ }
65
+ return out;
66
+ }
67
+ /** The visible content of an accessibility tree, one line for each element that shows something. */
68
+ export function visibleLines(tree) {
69
+ return tree
70
+ .split('\n')
71
+ .map(visibleLine)
72
+ .filter((line) => line.length > 0);
73
+ }
74
+ /** The role at the start of a tree line. */
75
+ const ROLE = /^\s*- ([\w-]+)/;
76
+ /**
77
+ * Roles that only hold text. A correct build can show the same words in another container, so a
78
+ * line with one of these roles is compared as its text alone.
79
+ */
80
+ const TEXT_ROLES = new Set([
81
+ 'generic',
82
+ 'text',
83
+ 'none',
84
+ 'presentation',
85
+ 'group',
86
+ 'paragraph',
87
+ 'status',
88
+ 'note',
89
+ 'log',
90
+ 'strong',
91
+ 'emphasis',
92
+ 'code',
93
+ 'blockquote',
94
+ 'caption',
95
+ 'label',
96
+ 'time',
97
+ 'mark',
98
+ 'subscript',
99
+ 'superscript',
100
+ 'deletion',
101
+ 'insertion',
102
+ ]);
103
+ /**
104
+ * The visible content with the role of each control and structure element kept: `cell`,
105
+ * `cell john doe`, `button save`. Text alone cannot see a change of structure: an empty cell that
106
+ * keeps the columns of a table aligned, a button that became plain text, or a checkbox removed while
107
+ * its label stays. A line with a text-only role is its text, and it is dropped when it has no text.
108
+ */
109
+ export function structureLines(tree) {
110
+ const out = [];
111
+ for (const line of tree.split('\n')) {
112
+ const role = ROLE.exec(line)?.[1];
113
+ if (role === undefined) {
114
+ continue;
115
+ }
116
+ const shown = visibleLine(line);
117
+ if (!TEXT_ROLES.has(role)) {
118
+ out.push(shown.length > 0 ? `${role} ${shown}` : role);
119
+ }
120
+ else if (shown.length > 0) {
121
+ out.push(shown);
122
+ }
123
+ }
124
+ return out;
125
+ }
@@ -7,8 +7,9 @@
7
7
  * the zero carry its own diagnosis rather than guess at a cause.
8
8
  */
9
9
  export function crawlEmptyNote(outcome) {
10
- if (outcome.stepsRun > 0)
10
+ if (outcome.stepsRun > 0) {
11
11
  return undefined;
12
+ }
12
13
  if (outcome.maxSteps <= 0) {
13
14
  return 'nothing was clicked because maxSteps is 0 — raise it to crawl at all';
14
15
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `reticle_crawl { exhaustive: true }` — explore every reachable state, drive every write's failure
3
+ * path and the edge cases of every state, take the code coverage, fold all of it into the ledger,
4
+ * and say what is left.
5
+ *
6
+ * It answers in crawl's own shape, so every caller that reads a crawl keeps working, and adds one
7
+ * `exhaustive` block with what only this mode knows.
8
+ */
9
+ import type { FileSystemPort } from '../../memory/project/fs/fs-port.js';
10
+ import { type RawScriptCoverage } from '../exhaust/code-coverage.js';
11
+ import { type ExploreReport } from '../exhaust/explorer.js';
12
+ import { type PortSession } from '../exhaust/session-port.js';
13
+ /** The share of a GIVEN budget kept back for edge cases. What exploring leaves goes to them too. */
14
+ export declare const EDGE_SHARE = 0.25;
15
+ /**
16
+ * With no budget given, each phase gets what the crawl found, up to a ceiling. Exploring stops on
17
+ * its own when no state is left; the ceiling only bounds an app too large to finish. A fixed 400,
18
+ * split by share, ran out on bench-app before the edge drive reached its third page.
19
+ */
20
+ export declare const ExhaustBudget: {
21
+ readonly EXPLORE_CEILING: 600;
22
+ /** A failure path replays the write's path, breaks it and fires it. */
23
+ readonly FAILURE_PER_WRITE: 12;
24
+ readonly FAILURE_CEILING: 150;
25
+ /** Enough for the first few probes of one control, each a replay and a few presses. */
26
+ readonly EDGE_PER_CONTROL: 15;
27
+ readonly EDGE_CEILING: 450;
28
+ };
29
+ /** The failure and edge budgets for what exploring found. */
30
+ export declare function scaledBudget(found: Pick<ExploreReport, 'surfaces' | 'writes'>): {
31
+ failure: number;
32
+ edges: number;
33
+ };
34
+ export declare function runExhaustive(input: {
35
+ sessions: {
36
+ get(id: string): PortSession | undefined;
37
+ };
38
+ session: PortSession;
39
+ startUrl: string;
40
+ /** A fixed budget, split by share. Absent: each phase gets what the crawl found (`scaledBudget`). */
41
+ maxActions?: number;
42
+ settleMs: number;
43
+ /** What to type into a field, given the page it is on — a secret is only for its own origin. */
44
+ fillValue: (label: string, pageUrl: string) => string;
45
+ now: () => number;
46
+ sleep: (ms: number) => Promise<void>;
47
+ setMocks?: (sessionUrl: string, rules: {
48
+ urlContains: string;
49
+ method?: string;
50
+ abort?: boolean;
51
+ }[]) => Promise<boolean>;
52
+ takeCode?: (sessionUrl: string) => Promise<readonly RawScriptCoverage[] | undefined>;
53
+ fs: FileSystemPort;
54
+ reticleRoot: string;
55
+ }): Promise<Record<string, unknown>>;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * `reticle_crawl { exhaustive: true }` — explore every reachable state, drive every write's failure
3
+ * path and the edge cases of every state, take the code coverage, fold all of it into the ledger,
4
+ * and say what is left.
5
+ *
6
+ * It answers in crawl's own shape, so every caller that reads a crawl keeps working, and adds one
7
+ * `exhaustive` block with what only this mode knows.
8
+ */
9
+ import { BehaviourFindingKind } from '@reticlehq/core';
10
+ import { emptyCodeCoverage, foldCodeCoverage, } from '../exhaust/code-coverage.js';
11
+ import { driveEdges } from '../exhaust/edges/edge-drive.js';
12
+ import { explore } from '../exhaust/explorer.js';
13
+ import { driveFailureBranches } from '../exhaust/failure-branches.js';
14
+ import { failureFindings } from '../exhaust/invariants.js';
15
+ import { LedgerStore, levelsOf, regressions } from '../exhaust/ledger.js';
16
+ import { sessionPort } from '../exhaust/session-port.js';
17
+ /** The share of a GIVEN budget kept back for failure paths, so exploring cannot starve them. */
18
+ const FAILURE_SHARE = 0.25;
19
+ /** The share of a GIVEN budget kept back for edge cases. What exploring leaves goes to them too. */
20
+ export const EDGE_SHARE = 0.25;
21
+ /**
22
+ * With no budget given, each phase gets what the crawl found, up to a ceiling. Exploring stops on
23
+ * its own when no state is left; the ceiling only bounds an app too large to finish. A fixed 400,
24
+ * split by share, ran out on bench-app before the edge drive reached its third page.
25
+ */
26
+ export const ExhaustBudget = {
27
+ EXPLORE_CEILING: 600,
28
+ /** A failure path replays the write's path, breaks it and fires it. */
29
+ FAILURE_PER_WRITE: 12,
30
+ FAILURE_CEILING: 150,
31
+ /** Enough for the first few probes of one control, each a replay and a few presses. */
32
+ EDGE_PER_CONTROL: 15,
33
+ EDGE_CEILING: 450,
34
+ };
35
+ /** The failure and edge budgets for what exploring found. */
36
+ export function scaledBudget(found) {
37
+ const controls = new Set(found.surfaces.flatMap((s) => [...s.requests, ...s.fields, ...s.inert]));
38
+ return {
39
+ failure: Math.min(ExhaustBudget.FAILURE_CEILING, ExhaustBudget.FAILURE_PER_WRITE * found.writes.length),
40
+ edges: Math.min(ExhaustBudget.EDGE_CEILING, ExhaustBudget.EDGE_PER_CONTROL * controls.size),
41
+ };
42
+ }
43
+ export async function runExhaustive(input) {
44
+ const port = sessionPort({
45
+ sessions: input.sessions,
46
+ start: input.session,
47
+ startUrl: input.startUrl,
48
+ settleMs: input.settleMs,
49
+ now: input.now,
50
+ sleep: input.sleep,
51
+ ...(input.setMocks === undefined ? {} : { setMocks: input.setMocks }),
52
+ });
53
+ const given = input.maxActions;
54
+ const exploreBudget = given === undefined
55
+ ? ExhaustBudget.EXPLORE_CEILING
56
+ : given - Math.floor(given * FAILURE_SHARE) - Math.floor(given * EDGE_SHARE);
57
+ const report = await explore(port, {
58
+ maxActions: exploreBudget,
59
+ fillValue: (label) => input.fillValue(label, port.current().url),
60
+ });
61
+ const scaled = scaledBudget(report);
62
+ const failureBudget = given === undefined ? scaled.failure : Math.floor(given * FAILURE_SHARE);
63
+ const edgeBudget = given === undefined
64
+ ? scaled.edges
65
+ : Math.floor(given * EDGE_SHARE) + Math.max(0, exploreBudget - report.actions);
66
+ const branches = await driveFailureBranches(port, report.writes, { maxActions: failureBudget });
67
+ const edges = await driveEdges(port, report.surfaces, {
68
+ maxActions: edgeBudget,
69
+ now: input.now,
70
+ known: report.anomalies.map((a) => `${a.control}\n${a.kind}`),
71
+ });
72
+ const take = await input.takeCode?.(port.current().url);
73
+ const ledger = await new LedgerStore(input.fs, input.reticleRoot).merge({
74
+ routes: { reached: report.routes },
75
+ controls: { seen: report.seen, touched: report.touched },
76
+ writes: {
77
+ seen: report.writes.map((w) => w.key),
78
+ branched: branches.branched,
79
+ unhandled: branches.unhandled.map((u) => u.key),
80
+ },
81
+ edges: { probed: edges.probed },
82
+ findings: [...failureFindings(branches, report.writes), ...edges.found.map((f) => f.finding)],
83
+ ...(take === undefined
84
+ ? {}
85
+ : { code: foldCodeCoverage(emptyCodeCoverage(), take), codeTakenAt: input.now() }),
86
+ });
87
+ const anomalies = [
88
+ ...report.anomalies.map((a) => ({ kind: a.kind, ref: '', desc: a.control, detail: a.detail })),
89
+ ...branches.unhandled.map((u) => ({
90
+ kind: BehaviourFindingKind.FAILURE_UNHANDLED,
91
+ ref: '',
92
+ desc: u.key,
93
+ detail: u.detail,
94
+ })),
95
+ ...edges.found.map((f) => ({
96
+ kind: f.finding.kind,
97
+ ref: '',
98
+ desc: f.control,
99
+ detail: f.finding.detail,
100
+ ...(f.finding.source === undefined ? {} : { source: f.finding.source }),
101
+ })),
102
+ ];
103
+ const counts = {};
104
+ for (const a of anomalies) {
105
+ counts[a.kind] = (counts[a.kind] ?? 0) + 1;
106
+ }
107
+ return {
108
+ interactiveFound: report.seen.length,
109
+ stepsRun: report.actions,
110
+ anomalies,
111
+ counts,
112
+ visited: report.touched,
113
+ truncated: report.frontier > 0 || report.budgetExhausted,
114
+ exhaustive: {
115
+ states: report.states,
116
+ routes: report.routes,
117
+ frontier: report.frontier,
118
+ unreachable: report.unreachable,
119
+ budgetExhausted: report.budgetExhausted,
120
+ writes: report.writes.map((w) => w.key),
121
+ failureBranches: branches,
122
+ edges: {
123
+ probed: edges.probed.length,
124
+ findings: edges.found.length,
125
+ actions: edges.actions,
126
+ budgetExhausted: edges.budgetExhausted,
127
+ ...(edges.failed > 0 ? { failed: edges.failed } : {}),
128
+ },
129
+ levels: levelsOf(ledger),
130
+ regressed: regressions(ledger),
131
+ ...(take === undefined
132
+ ? {
133
+ codeNote: 'code coverage needs a browser Reticle drives (`reticle drive`, or RETICLE_CDP_URL)',
134
+ }
135
+ : {}),
136
+ },
137
+ };
138
+ }
@@ -1,12 +1,19 @@
1
- import type { ToolDef } from '../../surface/tools/tool-kit.js';
1
+ import type { MockRule } from '../../portal/input/network-mock.js';
2
+ import type { ToolDef, ToolDeps } from '../../surface/tools/tool-kit.js';
2
3
  /**
3
4
  * The configured secret for a field, only on the origin the crawl started on. The field name comes
4
5
  * from whatever page is on screen, so without the check a page on another origin could name an
5
6
  * empty textbox after a secret and have the crawl type it in.
6
7
  */
8
+ /**
9
+ * How the exhaustive crawl breaks a request: the driven browser first, then the leased page the
10
+ * session runs in. `reticle mcp --headless` drives a lease, and the mock tool already reaches it the
11
+ * same way. Undefined when neither exists, so the crawl says why its failure paths were skipped.
12
+ */
13
+ export declare function exhaustiveMocks(deps: ToolDeps, sessionId: string): ((url: string, rules: MockRule[]) => Promise<boolean>) | undefined;
7
14
  export declare function crawlSecret(startUrl: string, env: Readonly<Record<string, string | undefined>>): (name: string, pageUrl: string) => string | undefined;
8
15
  /**
9
- * The autonomous "smart monkey" tool. Builds on reticle_explore (which only LISTS) by
16
+ * The autonomous "smart monkey" tool. Builds on the interactive snapshot (which only LISTS) by
10
17
  * actually clicking each reachable control and classifying the reaction. DESTRUCTIVE by nature —
11
18
  * it drives the app — so it's an explicit, bounded tool, never part of a passive read.
12
19
  */