@reticlehq/server 3.8.2 → 3.10.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 (477) hide show
  1. package/LICENSE +201 -104
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/SKILL.md +13 -12
  5. package/bin/launch.js +13 -0
  6. package/bin/package.json +3 -0
  7. package/bin/reticle.js +66 -8
  8. package/dist/chromium-launch-options.d.ts +25 -0
  9. package/dist/chromium-launch-options.js +21 -1
  10. package/dist/command/cli/adhoc-suite.js +2 -0
  11. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  12. package/dist/command/cli/adhoc-verdict.js +50 -160
  13. package/dist/command/cli/answers/gate-exit.d.ts +6 -2
  14. package/dist/command/cli/answers/gate-exit.js +12 -2
  15. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  16. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  17. package/dist/command/cli/ci-hint.d.ts +44 -0
  18. package/dist/command/cli/ci-hint.js +72 -0
  19. package/dist/command/cli/cli-doctor.d.ts +6 -1
  20. package/dist/command/cli/cli-doctor.js +46 -5
  21. package/dist/command/cli/cli-flow-commands.d.ts +7 -1
  22. package/dist/command/cli/cli-flow-commands.js +39 -24
  23. package/dist/command/cli/cli-kill.d.ts +21 -0
  24. package/dist/command/cli/cli-kill.js +47 -4
  25. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  26. package/dist/command/cli/cli-parse-compare.js +75 -0
  27. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  28. package/dist/command/cli/cli-parse-grade.js +75 -0
  29. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  30. package/dist/command/cli/cli-parse-grammar.js +18 -20
  31. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  32. package/dist/command/cli/cli-parse-verify.js +63 -14
  33. package/dist/command/cli/cli-parse.d.ts +22 -17
  34. package/dist/command/cli/cli-parse.js +54 -32
  35. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  36. package/dist/command/cli/cli-update-commands.js +29 -20
  37. package/dist/command/cli/cli-usage.js +61 -31
  38. package/dist/command/cli/cli-verify.d.ts +17 -3
  39. package/dist/command/cli/cli-verify.js +59 -21
  40. package/dist/command/cli/cloud-cli.js +2 -119
  41. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  42. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  43. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
  44. package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
  45. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  46. package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
  47. package/dist/command/cli/doctor/doctor-fix.js +89 -0
  48. package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
  49. package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
  50. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  51. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  52. package/dist/command/cli/doctor-repairs.d.ts +64 -0
  53. package/dist/command/cli/doctor-repairs.js +214 -0
  54. package/dist/command/cli/drive/drive-command.js +3 -7
  55. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  56. package/dist/command/cli/ensure-daemon.js +57 -0
  57. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  58. package/dist/command/cli/finished-run/grade-command.js +180 -0
  59. package/dist/command/cli/first-use-checks.d.ts +41 -0
  60. package/dist/command/cli/first-use-checks.js +127 -0
  61. package/dist/command/cli/first-verdict-node.d.ts +3 -0
  62. package/dist/command/cli/first-verdict-node.js +189 -0
  63. package/dist/command/cli/first-verdict.d.ts +126 -0
  64. package/dist/command/cli/first-verdict.js +267 -0
  65. package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
  66. package/dist/command/cli/launch/sdk-version-match.js +3 -2
  67. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  68. package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
  69. package/dist/command/cli/mcp-command.js +13 -4
  70. package/dist/command/cli/ports/port-holder.d.ts +11 -0
  71. package/dist/command/cli/ports/port-holder.js +36 -4
  72. package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
  73. package/dist/command/cli/setup-mcp-cli.js +98 -23
  74. package/dist/command/cli/status/status-lines.d.ts +2 -0
  75. package/dist/command/cli/status/status-lines.js +72 -17
  76. package/dist/command/cli/status-fields.d.ts +5 -1
  77. package/dist/command/cli/status-fields.js +24 -6
  78. package/dist/command/cli/try-command.d.ts +7 -0
  79. package/dist/command/cli/try-command.js +11 -3
  80. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  81. package/dist/command/cli/two-builds/compare-command.js +314 -0
  82. package/dist/command/cli/verify-against.d.ts +61 -0
  83. package/dist/command/cli/verify-against.js +187 -0
  84. package/dist/command/cli/verify-no-session.d.ts +2 -0
  85. package/dist/command/cli/verify-no-session.js +13 -0
  86. package/dist/command/cli/verify-results.d.ts +35 -2
  87. package/dist/command/cli/verify-results.js +76 -10
  88. package/dist/command/cli.js +112 -70
  89. package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
  90. package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
  91. package/dist/command/daemon/daemon-argv.d.ts +9 -0
  92. package/dist/command/daemon/daemon-argv.js +11 -0
  93. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  94. package/dist/command/daemon/daemon-resolve.js +27 -1
  95. package/dist/command/daemon/daemon.d.ts +22 -4
  96. package/dist/command/daemon/daemon.js +52 -10
  97. package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
  98. package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
  99. package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
  100. package/dist/command/daemon/lifetime/death-cause.js +149 -0
  101. package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
  102. package/dist/command/daemon/lifetime/heartbeat.js +11 -15
  103. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  104. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  105. package/dist/command/daemon/port-state.d.ts +14 -0
  106. package/dist/command/daemon/port-state.js +31 -0
  107. package/dist/command/setup/agent-configs.d.ts +2 -0
  108. package/dist/command/setup/agent-configs.js +42 -15
  109. package/dist/command/setup/agent-next-steps.d.ts +77 -0
  110. package/dist/command/setup/agent-next-steps.js +284 -0
  111. package/dist/command/setup/agent-writer.js +11 -6
  112. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  113. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  114. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  115. package/dist/command/setup/bringup/static-page-server.js +43 -9
  116. package/dist/command/setup/init/init-host.js +2 -0
  117. package/dist/command/setup/init/init-runtime.d.ts +6 -1
  118. package/dist/command/setup/init/init-runtime.js +63 -21
  119. package/dist/command/setup/mcp-launcher.d.ts +3 -0
  120. package/dist/command/setup/mcp-launcher.js +11 -0
  121. package/dist/command/setup/node-effects.d.ts +3 -2
  122. package/dist/command/setup/node-effects.js +9 -3
  123. package/dist/command/setup/remaining-steps.d.ts +8 -0
  124. package/dist/command/setup/remaining-steps.js +10 -0
  125. package/dist/command/setup/run-setup.d.ts +2 -0
  126. package/dist/command/setup/run-setup.js +9 -1
  127. package/dist/command/setup/setup-command.js +36 -15
  128. package/dist/command/setup/setup-mcp.d.ts +8 -0
  129. package/dist/command/setup/setup-mcp.js +47 -20
  130. package/dist/command/setup/start-dev-server.d.ts +8 -0
  131. package/dist/command/setup/start-dev-server.js +43 -0
  132. package/dist/command/update/updater.js +6 -0
  133. package/dist/command/version/identity/server-version.d.ts +2 -0
  134. package/dist/command/version/identity/server-version.js +2 -0
  135. package/dist/command/version/version-skew.d.ts +7 -0
  136. package/dist/command/version/version-skew.js +14 -3
  137. package/dist/features/compare/action-plan.d.ts +260 -0
  138. package/dist/features/compare/action-plan.js +543 -0
  139. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  140. package/dist/features/compare/behaviour-compare.js +85 -0
  141. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  142. package/dist/features/compare/behaviour-diff.js +202 -0
  143. package/dist/features/compare/behaviour-report.d.ts +40 -0
  144. package/dist/features/compare/behaviour-report.js +128 -0
  145. package/dist/features/compare/build-readings.d.ts +106 -0
  146. package/dist/features/compare/build-readings.js +250 -0
  147. package/dist/features/compare/compare-diff.d.ts +62 -0
  148. package/dist/features/compare/compare-diff.js +177 -0
  149. package/dist/features/compare/compare-report-html.d.ts +6 -0
  150. package/dist/features/compare/compare-report-html.js +102 -0
  151. package/dist/features/compare/compare-report.d.ts +15 -0
  152. package/dist/features/compare/compare-report.js +73 -0
  153. package/dist/features/compare/compare-run.d.ts +60 -0
  154. package/dist/features/compare/compare-run.js +123 -0
  155. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  156. package/dist/features/compare/outside/effect-lines.js +103 -0
  157. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  158. package/dist/features/compare/outside/state-sources.js +164 -0
  159. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  160. package/dist/features/compare/outside/value-dictionary.js +227 -0
  161. package/dist/features/compare/visible-content.d.ts +20 -0
  162. package/dist/features/compare/visible-content.js +125 -0
  163. package/dist/features/crawl/crawl-empty.js +2 -1
  164. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  165. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  166. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  167. package/dist/features/crawl/crawl-tools.js +28 -12
  168. package/dist/features/crawl/crawl.d.ts +8 -9
  169. package/dist/features/crawl/crawl.js +61 -65
  170. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  171. package/dist/features/exhaust/app-coverage.js +3 -1
  172. package/dist/features/exhaust/code-coverage.js +8 -4
  173. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  174. package/dist/features/exhaust/edge-cases.js +310 -0
  175. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  176. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  177. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  178. package/dist/features/exhaust/edges/edge-run.js +169 -0
  179. package/dist/features/exhaust/explorer.d.ts +87 -14
  180. package/dist/features/exhaust/explorer.js +159 -25
  181. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  182. package/dist/features/exhaust/failure-branches.js +29 -11
  183. package/dist/features/exhaust/invariants.d.ts +112 -0
  184. package/dist/features/exhaust/invariants.js +188 -0
  185. package/dist/features/exhaust/ledger.d.ts +68 -0
  186. package/dist/features/exhaust/ledger.js +121 -13
  187. package/dist/features/exhaust/session-fold.d.ts +10 -0
  188. package/dist/features/exhaust/session-fold.js +46 -3
  189. package/dist/features/exhaust/session-port.d.ts +3 -1
  190. package/dist/features/exhaust/session-port.js +97 -28
  191. package/dist/features/grade/grade-answer.d.ts +99 -0
  192. package/dist/features/grade/grade-answer.js +112 -0
  193. package/dist/features/grade/grade-noise.d.ts +27 -0
  194. package/dist/features/grade/grade-noise.js +55 -0
  195. package/dist/features/grade/grade-renumber.d.ts +12 -0
  196. package/dist/features/grade/grade-renumber.js +86 -0
  197. package/dist/features/grade/grade-state.d.ts +50 -0
  198. package/dist/features/grade/grade-state.js +93 -0
  199. package/dist/features/grade/grade-suggest.d.ts +33 -0
  200. package/dist/features/grade/grade-suggest.js +98 -0
  201. package/dist/features/license/license.d.ts +11 -71
  202. package/dist/features/license/license.js +43 -136
  203. package/dist/features/visual/desktop-capture.d.ts +33 -0
  204. package/dist/features/visual/desktop-capture.js +94 -0
  205. package/dist/features/visual/step-shots.js +139 -35
  206. package/dist/features/visual/visual-tools.d.ts +0 -18
  207. package/dist/features/visual/visual-tools.js +67 -99
  208. package/dist/import-graph.js +5 -6
  209. package/dist/index.d.ts +3 -1
  210. package/dist/index.js +19 -7
  211. package/dist/judgement/capsule/causal-summary.js +9 -2
  212. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  213. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  214. package/dist/judgement/runs/reticle-runner.js +16 -2
  215. package/dist/judgement/runs/run-ledger.d.ts +7 -5
  216. package/dist/judgement/runs/run-ledger.js +98 -69
  217. package/dist/judgement/runs/runner-port.d.ts +2 -0
  218. package/dist/judgement/runs/runner-port.js +23 -5
  219. package/dist/judgement/runs/verification-sync.d.ts +6 -3
  220. package/dist/judgement/runs/verification-sync.js +21 -10
  221. package/dist/language/flows/affected-tools.d.ts +1 -1
  222. package/dist/language/flows/affected-tools.js +49 -30
  223. package/dist/language/flows/change/git-changed.d.ts +2 -1
  224. package/dist/language/flows/change/git-changed.js +19 -2
  225. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  226. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  227. package/dist/language/flows/flow-anchor.d.ts +60 -2
  228. package/dist/language/flows/flow-anchor.js +125 -20
  229. package/dist/language/flows/flow-classify.d.ts +20 -0
  230. package/dist/language/flows/flow-classify.js +90 -12
  231. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  232. package/dist/language/flows/flow-cross-step.js +25 -1
  233. package/dist/language/flows/flow-journey.d.ts +38 -4
  234. package/dist/language/flows/flow-journey.js +138 -8
  235. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  236. package/dist/language/flows/flow-replay-run.js +76 -14
  237. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  238. package/dist/language/flows/flow-replay.d.ts +19 -1
  239. package/dist/language/flows/flow-replay.js +169 -52
  240. package/dist/language/flows/flow-replayable.js +7 -1
  241. package/dist/language/flows/flow-start-path.d.ts +34 -5
  242. package/dist/language/flows/flow-start-path.js +202 -15
  243. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  244. package/dist/language/flows/flow-step-runners.js +184 -109
  245. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  246. package/dist/language/flows/flow-store-for-session.js +51 -5
  247. package/dist/language/flows/flow-tools.js +46 -33
  248. package/dist/language/flows/flows.js +176 -76
  249. package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
  250. package/dist/language/flows/recording/tape/recordings.js +112 -49
  251. package/dist/language/flows/replay.d.ts +36 -12
  252. package/dist/language/flows/replay.js +193 -50
  253. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  254. package/dist/language/flows/steps/page-steps.js +198 -0
  255. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  256. package/dist/language/flows/suite/sequential-run.js +96 -0
  257. package/dist/language/flows/verify-change-tools.js +26 -15
  258. package/dist/launch-chromium.d.ts +1 -0
  259. package/dist/launch-chromium.js +49 -12
  260. package/dist/memory/cloud/background-sync.d.ts +13 -0
  261. package/dist/memory/cloud/background-sync.js +24 -3
  262. package/dist/memory/cloud/sync-daemon.js +26 -27
  263. package/dist/memory/cloud/sync-screenshots.js +6 -1
  264. package/dist/memory/journal/attach-journal.js +2 -1
  265. package/dist/memory/journal/drive-flow.d.ts +7 -0
  266. package/dist/memory/journal/drive-flow.js +44 -13
  267. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  268. package/dist/memory/journal/session-journal.d.ts +7 -0
  269. package/dist/memory/journal/session-journal.js +58 -10
  270. package/dist/memory/project/root-claims.d.ts +9 -0
  271. package/dist/memory/project/root-claims.js +9 -4
  272. package/dist/portal/bridge/bridge.d.ts +13 -4
  273. package/dist/portal/bridge/bridge.js +10 -9
  274. package/dist/portal/input/download-capture.d.ts +7 -0
  275. package/dist/portal/input/download-capture.js +92 -0
  276. package/dist/portal/input/network-detail.d.ts +6 -0
  277. package/dist/portal/input/network-detail.js +12 -1
  278. package/dist/portal/input/network-mock-tools.js +49 -13
  279. package/dist/portal/input/network-mock.d.ts +8 -0
  280. package/dist/portal/input/network-mock.js +30 -10
  281. package/dist/portal/input/page-capture.d.ts +24 -0
  282. package/dist/portal/input/page-capture.js +33 -0
  283. package/dist/portal/input/real-input.d.ts +35 -18
  284. package/dist/portal/input/real-input.js +99 -33
  285. package/dist/portal/input/viewport-tools.d.ts +8 -0
  286. package/dist/portal/input/viewport-tools.js +81 -21
  287. package/dist/portal/pool/browser-pool.d.ts +16 -30
  288. package/dist/portal/pool/browser-pool.js +64 -52
  289. package/dist/portal/pool/context-permissions.d.ts +34 -17
  290. package/dist/portal/pool/context-permissions.js +94 -1
  291. package/dist/portal/pool/csp-widen.d.ts +62 -0
  292. package/dist/portal/pool/csp-widen.js +132 -0
  293. package/dist/portal/pool/determinism.d.ts +52 -0
  294. package/dist/portal/pool/determinism.js +259 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  296. package/dist/portal/pool/lease-emulation.js +18 -0
  297. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  298. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  299. package/dist/portal/pool/lease-page-error.d.ts +56 -0
  300. package/dist/portal/pool/lease-page-error.js +160 -0
  301. package/dist/portal/pool/page-error-journal.d.ts +63 -0
  302. package/dist/portal/pool/page-error-journal.js +208 -0
  303. package/dist/portal/pool/playwright-launcher.js +71 -15
  304. package/dist/portal/pool/pool-contract.d.ts +49 -1
  305. package/dist/portal/pool/pool-lease.d.ts +70 -2
  306. package/dist/portal/pool/pool-lease.js +23 -0
  307. package/dist/portal/pool/zero-install.d.ts +8 -0
  308. package/dist/portal/pool/zero-install.js +91 -1
  309. package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
  310. package/dist/portal/session/dev-server/dev-command.js +110 -16
  311. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  312. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  313. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  314. package/dist/portal/session/dev-server/project-ports.js +62 -0
  315. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  316. package/dist/portal/session/facts/departed-network.js +142 -0
  317. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  318. package/dist/portal/session/no-session-diagnosis.js +76 -27
  319. package/dist/portal/session/no-session-next-action.d.ts +14 -0
  320. package/dist/portal/session/no-session-next-action.js +56 -7
  321. package/dist/portal/session/no-session-watch.d.ts +6 -0
  322. package/dist/portal/session/no-session-watch.js +41 -4
  323. package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
  324. package/dist/portal/session/page-commands/undeclared-command.js +16 -7
  325. package/dist/portal/session/recorded-gaps.d.ts +1 -1
  326. package/dist/portal/session/recorded-gaps.js +5 -2
  327. package/dist/portal/session/serving-directory.d.ts +10 -0
  328. package/dist/portal/session/serving-directory.js +39 -8
  329. package/dist/portal/session/session-manager.d.ts +10 -0
  330. package/dist/portal/session/session-manager.js +211 -81
  331. package/dist/portal/session/session.js +4 -3
  332. package/dist/portal/session/zero-install-first-call.d.ts +20 -1
  333. package/dist/portal/session/zero-install-first-call.js +34 -2
  334. package/dist/start-wiring.d.ts +30 -2
  335. package/dist/start-wiring.js +74 -9
  336. package/dist/surface/mcp/mcp-proxy.js +10 -2
  337. package/dist/surface/mcp/mcp.js +8 -5
  338. package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
  339. package/dist/surface/mcp/peer/agents-connected.js +58 -0
  340. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
  341. package/dist/surface/mcp/shared-params.d.ts +2 -0
  342. package/dist/surface/mcp/shared-params.js +31 -0
  343. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  344. package/dist/surface/tools/act/act-observation.js +118 -12
  345. package/dist/surface/tools/act/act-preflight.js +38 -18
  346. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  347. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  348. package/dist/surface/tools/act/act-target.js +3 -1
  349. package/dist/surface/tools/act/after-match.d.ts +10 -0
  350. package/dist/surface/tools/act/after-match.js +40 -0
  351. package/dist/surface/tools/act/already-true.js +1 -1
  352. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  353. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  354. package/dist/surface/tools/act/resolve-target.d.ts +17 -1
  355. package/dist/surface/tools/act/resolve-target.js +59 -9
  356. package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
  357. package/dist/surface/tools/act/sequence-grade.js +45 -27
  358. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  359. package/dist/surface/tools/act-command.d.ts +23 -0
  360. package/dist/surface/tools/act-command.js +50 -0
  361. package/dist/surface/tools/act-sequence-tool.js +81 -50
  362. package/dist/surface/tools/act-tools.d.ts +1 -13
  363. package/dist/surface/tools/act-tools.js +83 -95
  364. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  365. package/dist/surface/tools/agent/build-staleness.js +1 -1
  366. package/dist/surface/tools/assert/assert-verdict.js +18 -12
  367. package/dist/surface/tools/browser-tools.js +87 -13
  368. package/dist/surface/tools/compare-tools.d.ts +11 -0
  369. package/dist/surface/tools/compare-tools.js +94 -0
  370. package/dist/surface/tools/coverage-tools.js +1 -1
  371. package/dist/surface/tools/dynamic-tools.js +11 -3
  372. package/dist/surface/tools/empty-session-list.d.ts +2 -0
  373. package/dist/surface/tools/empty-session-list.js +4 -2
  374. package/dist/surface/tools/error-recovery.d.ts +12 -1
  375. package/dist/surface/tools/error-recovery.js +131 -40
  376. package/dist/surface/tools/explore-tools.js +1 -1
  377. package/dist/surface/tools/invoke-tool.js +29 -21
  378. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  379. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  380. package/dist/surface/tools/lease-hint.d.ts +42 -1
  381. package/dist/surface/tools/lease-hint.js +52 -3
  382. package/dist/surface/tools/lease-readiness.d.ts +47 -0
  383. package/dist/surface/tools/lease-readiness.js +126 -0
  384. package/dist/surface/tools/lease-seed.d.ts +16 -1
  385. package/dist/surface/tools/lease-seed.js +37 -4
  386. package/dist/surface/tools/lease-tools.d.ts +3 -41
  387. package/dist/surface/tools/lease-tools.js +104 -124
  388. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  389. package/dist/surface/tools/lease-url-identity.js +97 -0
  390. package/dist/surface/tools/merge-tools.js +99 -2
  391. package/dist/surface/tools/merged-name-redirect.js +5 -1
  392. package/dist/surface/tools/observe-tools.js +15 -9
  393. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  394. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  395. package/dist/surface/tools/read-tools.js +33 -30
  396. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  397. package/dist/surface/tools/real-input-attempt.js +143 -28
  398. package/dist/surface/tools/repeat-hint.d.ts +40 -0
  399. package/dist/surface/tools/repeat-hint.js +171 -0
  400. package/dist/surface/tools/run-report.d.ts +2 -2
  401. package/dist/surface/tools/run-report.js +27 -15
  402. package/dist/surface/tools/session-ports.d.ts +14 -0
  403. package/dist/surface/tools/session-ports.js +53 -0
  404. package/dist/surface/tools/tool-kit.d.ts +18 -0
  405. package/dist/surface/tools/tool-kit.js +9 -3
  406. package/dist/surface/tools/tool-surface.js +0 -1
  407. package/dist/surface/tools/tools.js +54 -18
  408. package/dist/surface/tools/type-spelling.d.ts +13 -0
  409. package/dist/surface/tools/type-spelling.js +36 -0
  410. package/dist/telemetry/agent-client.d.ts +6 -0
  411. package/dist/telemetry/agent-client.js +10 -0
  412. package/dist/telemetry/argument-shape.js +17 -7
  413. package/dist/telemetry/cli-telemetry.d.ts +10 -1
  414. package/dist/telemetry/cli-telemetry.js +15 -0
  415. package/dist/telemetry/init-telemetry.d.ts +2 -1
  416. package/dist/telemetry/init-telemetry.js +47 -2
  417. package/dist/telemetry/install-trace.js +23 -4
  418. package/dist/telemetry/onboarding-funnel.d.ts +19 -2
  419. package/dist/telemetry/onboarding-funnel.js +25 -3
  420. package/dist/telemetry/proxy-telemetry.d.ts +2 -1
  421. package/dist/telemetry/proxy-telemetry.js +2 -2
  422. package/dist/telemetry/session-metrics.d.ts +1 -1
  423. package/dist/telemetry/session-metrics.js +67 -27
  424. package/dist/telemetry/telemetry.d.ts +3 -1
  425. package/dist/telemetry/telemetry.js +60 -28
  426. package/dist/telemetry/tool-refused.d.ts +6 -3
  427. package/dist/telemetry/tool-refused.js +15 -3
  428. package/docs/actions.mdx +3 -3
  429. package/docs/agent-cheatsheet.md +1 -1
  430. package/docs/architecture.md +3 -6
  431. package/docs/capabilities.mdx +61 -1
  432. package/docs/cli/affected.mdx +9 -7
  433. package/docs/cli/cloud.mdx +3 -3
  434. package/docs/cli/compare.mdx +94 -0
  435. package/docs/cli/doctor.mdx +41 -2
  436. package/docs/cli/grade.mdx +97 -0
  437. package/docs/cli/help.mdx +4 -2
  438. package/docs/cli/license.mdx +13 -12
  439. package/docs/cli/restart.mdx +7 -5
  440. package/docs/cli/rollback.mdx +5 -5
  441. package/docs/cli/setup.mdx +13 -2
  442. package/docs/cli/status.mdx +4 -0
  443. package/docs/cli/stop.mdx +3 -3
  444. package/docs/cli/update.mdx +2 -2
  445. package/docs/cli/verify.mdx +23 -1
  446. package/docs/cli.mdx +34 -24
  447. package/docs/deploy-checks.md +25 -0
  448. package/docs/desktop.mdx +1 -1
  449. package/docs/enterprise.md +17 -22
  450. package/docs/environment.mdx +1 -0
  451. package/docs/first-drive.md +1 -1
  452. package/docs/flows.md +11 -1
  453. package/docs/frameworks.mdx +9 -9
  454. package/docs/getting-started.md +1 -1
  455. package/docs/install-manual.mdx +16 -1
  456. package/docs/license-activation.mdx +3 -3
  457. package/docs/packages/engine.mdx +1 -1
  458. package/docs/packages/server.mdx +1 -1
  459. package/docs/packages/tauri.mdx +1 -1
  460. package/docs/packages/test.mdx +1 -1
  461. package/docs/packages.mdx +1 -1
  462. package/docs/platform-integration.md +1 -1
  463. package/docs/predicates.mdx +1 -1
  464. package/docs/quickstart.mdx +68 -49
  465. package/docs/skill-setup.md +1 -1
  466. package/docs/skill.md +14 -13
  467. package/docs/telemetry.md +6 -4
  468. package/docs/tools/navigate.mdx +1 -1
  469. package/docs/tools/verify.mdx +4 -1
  470. package/docs/troubleshooting.mdx +1 -1
  471. package/docs/usage.md +17 -14
  472. package/package.json +18 -14
  473. package/LICENSE-ENTERPRISE +0 -37
  474. package/dist/features/ee/audit-log.d.ts +0 -14
  475. package/dist/features/ee/audit-log.js +0 -11
  476. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  477. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Behaviour checks that need no reference answer.
3
+ *
4
+ * A test needs someone to write down what the app should do. These do not: each one compares the app
5
+ * with itself — the page before an action with the page after its undo, the first press with the
6
+ * second, the value sent with the value shown, one run with an identical run. Each is a pure
7
+ * function over observations the explorer already takes, and each returns findings with the steps
8
+ * that show the fault and, when the app gives it, the source line of the element.
9
+ *
10
+ * A check that cannot decide returns nothing. A false finding costs more than a missed one, so every
11
+ * check below names the case it lets pass on purpose.
12
+ */
13
+ import { BehaviourFindingKind } from '@reticlehq/core';
14
+ import { describeStep } from './edge-cases.js';
15
+ /** The rule each check holds the app to, in words, for the report. */
16
+ export const Invariant = {
17
+ UNDO_RESTORES: 'undo or cancel returns the previous page',
18
+ REPEAT_IS_STABLE: 'a repeated action does not change state again',
19
+ RELOAD_KEEPS: 'a saved value is still shown after reload',
20
+ SHOWN_IS_SENT: 'the value shown after a write is the value the write sent',
21
+ DETERMINISTIC: 'two identical drives give the same result',
22
+ FAILURE_HANDLED: 'no success over a failed write',
23
+ NO_FAULT: 'an edge case gets an answer, not an error',
24
+ RESPONDS: 'a control that is pressed does something',
25
+ };
26
+ const REF = /\s*\(ref=e\d+\)/g;
27
+ /** Focus moves with every action and is not part of what the page shows. */
28
+ const FOCUSED = /\[focused\]|,focused\b|\bfocused,/g;
29
+ const DIGITS = /\d+/g;
30
+ /** How many changed lines a finding quotes. */
31
+ const MAX_QUOTED = 3;
32
+ /** Writes that create something new each time they are sent. PUT and DELETE are idempotent. */
33
+ const CREATES = 'POST ';
34
+ const OK_MIN = 200;
35
+ const OK_MAX = 299;
36
+ function visibleLines(page) {
37
+ const lines = page.tree
38
+ .split('\n')
39
+ .map((line) => line
40
+ .replace(REF, '')
41
+ .replace(FOCUSED, '')
42
+ .replace(/\s*\[\]/g, '')
43
+ .trimEnd())
44
+ .filter((line) => line.trim().length > 0);
45
+ return [`route ${page.route ?? ''}`, ...lines];
46
+ }
47
+ const same = (a, b) => a.length === b.length && a.every((line, i) => line === b[i]);
48
+ /** The two pages show the same thing: refs and focus aside, every line and the route are equal. */
49
+ export const samePage = (a, b) => same(visibleLines(a), visibleLines(b));
50
+ /** The lines one page has and the other does not, quoted for a finding. */
51
+ function difference(before, after) {
52
+ const gone = before.filter((line) => !after.includes(line)).slice(0, MAX_QUOTED);
53
+ const added = after.filter((line) => !before.includes(line)).slice(0, MAX_QUOTED);
54
+ const parts = [
55
+ ...(gone.length > 0 ? [`missing: ${gone.map((l) => l.trim()).join('; ')}`] : []),
56
+ ...(added.length > 0 ? [`new: ${added.map((l) => l.trim()).join('; ')}`] : []),
57
+ ];
58
+ return parts.length > 0 ? parts.join(' | ') : 'the lines are in a different order';
59
+ }
60
+ function finding(kind, invariant, detail, ctx, extraSteps = []) {
61
+ const src = ctx.source;
62
+ return {
63
+ kind,
64
+ invariant,
65
+ detail,
66
+ sequence: [...ctx.steps.map(describeStep), ...extraSteps],
67
+ ...(src === undefined ? {} : { source: `${src.file}:${String(src.line)}` }),
68
+ };
69
+ }
70
+ /**
71
+ * (a) After an action and its undo or cancel, the page shows what it showed before the action.
72
+ * `after` is the page after the undo. Refs and focus are ignored; every other line counts.
73
+ */
74
+ export function checkUndo(input) {
75
+ const before = visibleLines(input.before);
76
+ const after = visibleLines(input.after);
77
+ if (same(before, after)) {
78
+ return [];
79
+ }
80
+ return [
81
+ finding(BehaviourFindingKind.UNDO_NOT_RESTORED, Invariant.UNDO_RESTORES, `the page after undo is not the page before the action (${difference(before, after)})`, input),
82
+ ];
83
+ }
84
+ const isOk = (w) => w.status !== undefined && w.status >= OK_MIN && w.status <= OK_MAX;
85
+ /**
86
+ * (b) An action pressed twice. Two faults:
87
+ * - the page after the second press is the page after the first, but the second press sent a POST
88
+ * the server accepted and answered differently: it created a second thing the screen hides;
89
+ * - the first press changed nothing on the page and the second changed it.
90
+ * Lets pass: a toggle or a counter (each press shows its own change), and a POST the server answers
91
+ * the same way twice (an upsert).
92
+ */
93
+ export function checkRepeat(input) {
94
+ const before = visibleLines(input.before);
95
+ const first = visibleLines(input.afterFirst);
96
+ const second = visibleLines(input.afterSecond);
97
+ if (same(first, second)) {
98
+ const firstWrites = input.firstWrites ?? [];
99
+ const hidden = (input.secondWrites ?? []).find((w) => w.key.startsWith(CREATES) &&
100
+ isOk(w) &&
101
+ !firstWrites.some((f) => f.key === w.key && f.response === w.response));
102
+ if (hidden === undefined) {
103
+ return [];
104
+ }
105
+ return [
106
+ finding(BehaviourFindingKind.REPEAT_CHANGED_STATE, Invariant.REPEAT_IS_STABLE, `the second press sent ${hidden.key} and the server accepted it with a new answer, but the page shows the same result as after the first press`, input),
107
+ ];
108
+ }
109
+ if (!same(before, first)) {
110
+ return [];
111
+ }
112
+ return [
113
+ finding(BehaviourFindingKind.REPEAT_CHANGED_STATE, Invariant.REPEAT_IS_STABLE, `the first press changed nothing and the second press changed the page (${difference(first, second)})`, input),
114
+ ];
115
+ }
116
+ /**
117
+ * (c) A value the app said it saved is still shown after a reload. Lets pass: an empty value (there
118
+ * is nothing to look for), and an app that said the value was not saved.
119
+ */
120
+ export function checkReloadKeeps(input) {
121
+ const value = input.value.trim();
122
+ if (0 === value.length ||
123
+ true === input.appSaidNotSaved ||
124
+ input.afterReload.tree.includes(value)) {
125
+ return [];
126
+ }
127
+ return [
128
+ finding(BehaviourFindingKind.LOST_AFTER_RELOAD, Invariant.RELOAD_KEEPS, `the app saved ${JSON.stringify(value)}, and after a reload the page does not show it`, input, ['reload']),
129
+ ];
130
+ }
131
+ /** Padding, case and digit grouping are presentation, not a different value. */
132
+ function normalized(value) {
133
+ const text = value.trim().replace(/\s+/g, ' ').toLowerCase();
134
+ return /^-?[\d,]+(?:\.\d+)?$/.test(text) ? text.replace(/,/g, '') : text;
135
+ }
136
+ /**
137
+ * (d) The value a field shows after a write is the value the write request sent for it. Lets pass:
138
+ * a field that shows nothing (it was cleared or closed), and differences of padding, case and digit
139
+ * grouping.
140
+ */
141
+ export function checkShownMatchesSent(input) {
142
+ if (input.shown === undefined || 0 === input.shown.trim().length) {
143
+ return [];
144
+ }
145
+ if (normalized(input.shown) === normalized(input.sent)) {
146
+ return [];
147
+ }
148
+ return [
149
+ finding(BehaviourFindingKind.SHOWN_DIFFERS_FROM_SENT, Invariant.SHOWN_IS_SENT, `${input.field} shows ${JSON.stringify(input.shown)}, but the write sent ${JSON.stringify(input.sent)}`, input),
150
+ ];
151
+ }
152
+ /**
153
+ * (e) Two identical drives from a fresh page show the same page. A difference is reported as
154
+ * intermittent: one run does not say which result is the fault.
155
+ *
156
+ * ponytail: every digit is masked before the compare, so a clock, an id or a relative time does not
157
+ * read as a different result. The ceiling: two runs that differ only in a number ("3 items" against
158
+ * "4 items") pass. Mask only time-like and id-like tokens if that proves too loose.
159
+ */
160
+ export function checkDeterministic(input) {
161
+ const shape = (page) => visibleLines(page).map((line) => line.replace(DIGITS, '#'));
162
+ const first = shape(input.first);
163
+ const second = shape(input.second);
164
+ if (same(first, second)) {
165
+ return [];
166
+ }
167
+ return [
168
+ {
169
+ ...finding(BehaviourFindingKind.INTERMITTENT_RESULT, Invariant.DETERMINISTIC, `two identical drives showed different pages (${difference(first, second)})`, input),
170
+ intermittent: true,
171
+ },
172
+ ];
173
+ }
174
+ /**
175
+ * (f) No success over a failed write. The judgement is `driveFailureBranches`'s own; this only gives
176
+ * each unhandled write the steps that fire it, so it reads like every other finding.
177
+ */
178
+ export function failureFindings(branches, writes) {
179
+ return branches.unhandled.map((u) => ({
180
+ kind: BehaviourFindingKind.FAILURE_UNHANDLED,
181
+ invariant: Invariant.FAILURE_HANDLED,
182
+ detail: u.detail,
183
+ sequence: [
184
+ ...(writes.find((w) => w.key === u.key)?.path ?? []).map(describeStep),
185
+ `fail ${u.key}`,
186
+ ],
187
+ }));
188
+ }
@@ -30,6 +30,18 @@ export declare const CoverageLevel: {
30
30
  readonly EXECUTED: "executed";
31
31
  };
32
32
  export type CoverageLevel = (typeof CoverageLevel)[keyof typeof CoverageLevel];
33
+ /** How many missing items a level names before it only counts them. */
34
+ export declare const MAX_MISSING = 50;
35
+ /** A behaviour fault the explorer found. See `invariants.ts`, which produces these. */
36
+ export declare const BehaviourFindingSchema: z.ZodObject<{
37
+ kind: z.ZodString;
38
+ invariant: z.ZodString;
39
+ detail: z.ZodString;
40
+ sequence: z.ZodArray<z.ZodString>;
41
+ source: z.ZodOptional<z.ZodString>;
42
+ intermittent: z.ZodOptional<z.ZodBoolean>;
43
+ }, z.core.$strip>;
44
+ export type BehaviourFinding = z.infer<typeof BehaviourFindingSchema>;
33
45
  declare const LedgerSchema: z.ZodObject<{
34
46
  routes: z.ZodObject<{
35
47
  discovered: z.ZodArray<z.ZodString>;
@@ -48,6 +60,17 @@ declare const LedgerSchema: z.ZodObject<{
48
60
  branched: z.ZodArray<z.ZodString>;
49
61
  unhandled: z.ZodArray<z.ZodString>;
50
62
  }, z.core.$strip>;
63
+ edges: z.ZodDefault<z.ZodObject<{
64
+ probed: z.ZodArray<z.ZodString>;
65
+ }, z.core.$strip>>;
66
+ findings: z.ZodDefault<z.ZodArray<z.ZodObject<{
67
+ kind: z.ZodString;
68
+ invariant: z.ZodString;
69
+ detail: z.ZodString;
70
+ sequence: z.ZodArray<z.ZodString>;
71
+ source: z.ZodOptional<z.ZodString>;
72
+ intermittent: z.ZodOptional<z.ZodBoolean>;
73
+ }, z.core.$strip>>>;
51
74
  code: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
52
75
  name: z.ZodString;
53
76
  executed: z.ZodBoolean;
@@ -62,6 +85,8 @@ export interface LedgerDelta {
62
85
  controls?: Partial<AppLedger['controls']>;
63
86
  journeys?: Partial<AppLedger['journeys']>;
64
87
  writes?: Partial<AppLedger['writes']>;
88
+ edges?: Partial<AppLedger['edges']>;
89
+ findings?: readonly BehaviourFinding[];
65
90
  code?: CodeCoverage;
66
91
  /** When `code` was taken, so a file edited since reads as not measured rather than as covered. */
67
92
  codeTakenAt?: number;
@@ -87,6 +112,49 @@ export declare function coveragePercents(reticleRoot: string): Record<string, nu
87
112
  export declare function emptyLedger(): AppLedger;
88
113
  export declare function mergeLedger(into: AppLedger, delta: LedgerDelta): AppLedger;
89
114
  export declare function levelsOf(ledger: AppLedger): LevelReport[];
115
+ /**
116
+ * What was never reached, as named items: the holes in the exploration.
117
+ *
118
+ * The levels above give a number per level. A number does not say where to go next, and it hides a
119
+ * level that was never measured behind an empty list. `holesOf` names every gap by kind, says when
120
+ * a kind was not measured at all, and counts the items past its cap. Nothing is left out in silence.
121
+ */
122
+ export declare const HoleKind: {
123
+ /** Routes discovered and never visited. */
124
+ readonly ROUTES: "routes";
125
+ /** Controls seen and never driven. */
126
+ readonly CONTROLS: "controls";
127
+ /** Controls seen whose edge cases were never driven. */
128
+ readonly EDGES: "edges";
129
+ /** Writes seen whose failure path was never driven. */
130
+ readonly WRITES: "writes";
131
+ /** App functions loaded and never executed, as `file: function`. */
132
+ readonly FUNCTIONS: "functions";
133
+ /** Components the app has and never rendered. */
134
+ readonly COMPONENTS: "components";
135
+ };
136
+ export type HoleKind = (typeof HoleKind)[keyof typeof HoleKind];
137
+ export interface HoleList {
138
+ /** False when nothing could measure this kind: an empty list then means "unknown", not "none". */
139
+ measured: boolean;
140
+ items: string[];
141
+ /** How many more items there are than `items` names. */
142
+ overflow?: number;
143
+ /** Why the kind is not measured, or what to do to measure it. */
144
+ note?: string;
145
+ }
146
+ export type HolesReport = Record<HoleKind, HoleList>;
147
+ /** The React adapter's component inventory: every component it knows, and the ones it rendered. */
148
+ export interface ComponentInventory {
149
+ known: readonly string[];
150
+ rendered: readonly string[];
151
+ }
152
+ export declare const HoleNote: {
153
+ readonly CODE: "Not measured: code coverage needs a browser Reticle drives (`reticle drive`, or RETICLE_CDP_URL).";
154
+ readonly COMPONENTS: "Not measured: no component inventory is available. A component that never rendered still shows in `functions` when code coverage is on.";
155
+ readonly EDGES: "No edge cases were driven yet. Run `reticle_crawl { exhaustive: true }`; every control listed here is still a hole.";
156
+ };
157
+ export declare function holesOf(ledger: AppLedger, components?: ComponentInventory): HolesReport;
90
158
  /** The best percentage each level has reached, raised to today's where today is higher. */
91
159
  export declare function raiseBest(ledger: AppLedger): AppLedger;
92
160
  /**
@@ -34,13 +34,31 @@ export const CoverageLevel = {
34
34
  };
35
35
  const LEDGER_VERSION = 1;
36
36
  /** How many missing items a level names before it only counts them. */
37
- const MAX_MISSING = 50;
37
+ export const MAX_MISSING = 50;
38
38
  const names = z.array(z.string());
39
+ /** A behaviour fault the explorer found. See `invariants.ts`, which produces these. */
40
+ export const BehaviourFindingSchema = z.object({
41
+ /** A `BehaviourFindingKind` from core, or the kind of a fault an edge case caused (`console-error`). */
42
+ kind: z.string(),
43
+ /** The rule that did not hold, in words. */
44
+ invariant: z.string(),
45
+ detail: z.string(),
46
+ /** The steps that show it, from a fresh page. */
47
+ sequence: z.array(z.string()),
48
+ /** `file:line` of the element, when the app gives source attribution. */
49
+ source: z.string().optional(),
50
+ /** True when the fault did not occur on every run. */
51
+ intermittent: z.boolean().optional(),
52
+ });
39
53
  const LedgerSchema = z.object({
40
54
  routes: z.object({ discovered: names, reached: names }),
41
55
  controls: z.object({ seen: names, touched: names, proved: names }),
42
56
  journeys: z.object({ completed: names }),
43
57
  writes: z.object({ seen: names, branched: names, unhandled: names }),
58
+ /** Controls whose edge cases (edge inputs, edge interactions) were driven. */
59
+ edges: z.object({ probed: names }).default({ probed: [] }),
60
+ /** Behaviour faults, one copy each. Absent in a ledger from before they existed. */
61
+ findings: z.array(BehaviourFindingSchema).default([]),
44
62
  code: z.record(z.string(), z.record(z.string(), z.object({ name: z.string(), executed: z.boolean() }))),
45
63
  /** Per file, when its code coverage was last taken. Absent in a ledger from before it existed. */
46
64
  codeTaken: z.record(z.string(), z.number()).default({}),
@@ -74,8 +92,9 @@ export function coveragePercents(reticleRoot) {
74
92
  }
75
93
  const out = {};
76
94
  for (const report of levelsOf(parseLedger(text))) {
77
- if (report.pct !== undefined)
95
+ if (report.pct !== undefined) {
78
96
  out[report.level] = report.pct;
97
+ }
79
98
  }
80
99
  return 0 < Object.keys(out).length ? out : undefined;
81
100
  }
@@ -85,12 +104,27 @@ export function emptyLedger() {
85
104
  controls: { seen: [], touched: [], proved: [] },
86
105
  journeys: { completed: [] },
87
106
  writes: { seen: [], branched: [], unhandled: [] },
107
+ edges: { probed: [] },
108
+ findings: [],
88
109
  code: {},
89
110
  codeTaken: {},
90
111
  best: {},
91
112
  };
92
113
  }
93
114
  const union = (a, b) => b === undefined ? [...a] : [...new Set([...a, ...b])];
115
+ /** The same fault found again by the same steps is one finding; the first copy is kept. */
116
+ const findingKey = (f) => `${f.kind}\n${f.sequence.join('\n')}`;
117
+ function unionFindings(into, add) {
118
+ const out = [...into];
119
+ const keys = new Set(out.map(findingKey));
120
+ for (const f of add ?? []) {
121
+ if (!keys.has(findingKey(f))) {
122
+ keys.add(findingKey(f));
123
+ out.push(f);
124
+ }
125
+ }
126
+ return out;
127
+ }
94
128
  export function mergeLedger(into, delta) {
95
129
  const code = { ...into.code };
96
130
  const codeTaken = { ...into.codeTaken };
@@ -100,12 +134,15 @@ export function mergeLedger(into, delta) {
100
134
  merged[key] = { name: fn.name, executed: fn.executed || true === merged[key]?.executed };
101
135
  }
102
136
  code[file] = merged;
103
- if (delta.codeTakenAt !== undefined)
137
+ if (delta.codeTakenAt !== undefined) {
104
138
  codeTaken[file] = delta.codeTakenAt;
139
+ }
105
140
  }
106
- // Reaching a route discovers it; touching or proving a control means it was seen.
141
+ // Reaching a route discovers it; touching or proving a control means it was seen. Probing a
142
+ // control's edge cases drove it, so it was touched.
107
143
  const reached = union(into.routes.reached, delta.routes?.reached);
108
- const touched = union(into.controls.touched, delta.controls?.touched);
144
+ const probed = union(into.edges.probed, delta.edges?.probed);
145
+ const touched = union(union(into.controls.touched, delta.controls?.touched), probed);
109
146
  const proved = union(into.controls.proved, delta.controls?.proved);
110
147
  const branched = union(into.writes.branched, delta.writes?.branched);
111
148
  return {
@@ -124,6 +161,8 @@ export function mergeLedger(into, delta) {
124
161
  branched,
125
162
  unhandled: union(into.writes.unhandled, delta.writes?.unhandled),
126
163
  },
164
+ edges: { probed },
165
+ findings: unionFindings(into.findings, delta.findings),
127
166
  code,
128
167
  codeTaken,
129
168
  best: { ...into.best },
@@ -166,12 +205,74 @@ export function levelsOf(ledger) {
166
205
  executedLevel(ledger.code),
167
206
  ];
168
207
  }
208
+ /**
209
+ * What was never reached, as named items: the holes in the exploration.
210
+ *
211
+ * The levels above give a number per level. A number does not say where to go next, and it hides a
212
+ * level that was never measured behind an empty list. `holesOf` names every gap by kind, says when
213
+ * a kind was not measured at all, and counts the items past its cap. Nothing is left out in silence.
214
+ */
215
+ export const HoleKind = {
216
+ /** Routes discovered and never visited. */
217
+ ROUTES: 'routes',
218
+ /** Controls seen and never driven. */
219
+ CONTROLS: 'controls',
220
+ /** Controls seen whose edge cases were never driven. */
221
+ EDGES: 'edges',
222
+ /** Writes seen whose failure path was never driven. */
223
+ WRITES: 'writes',
224
+ /** App functions loaded and never executed, as `file: function`. */
225
+ FUNCTIONS: 'functions',
226
+ /** Components the app has and never rendered. */
227
+ COMPONENTS: 'components',
228
+ };
229
+ export const HoleNote = {
230
+ CODE: 'Not measured: code coverage needs a browser Reticle drives (`reticle drive`, or RETICLE_CDP_URL).',
231
+ COMPONENTS: 'Not measured: no component inventory is available. A component that never rendered still shows in `functions` when code coverage is on.',
232
+ EDGES: 'No edge cases were driven yet. Run `reticle_crawl { exhaustive: true }`; every control listed here is still a hole.',
233
+ };
234
+ function named(universe, covered) {
235
+ const done = new Set(covered);
236
+ const missing = [...new Set(universe)].filter((item) => !done.has(item));
237
+ return {
238
+ measured: true,
239
+ items: missing.slice(0, MAX_MISSING),
240
+ ...(missing.length > MAX_MISSING ? { overflow: missing.length - MAX_MISSING } : {}),
241
+ };
242
+ }
243
+ /** The same named list a coverage level reports, so the two can never disagree. */
244
+ function fromLevel(ledger, level) {
245
+ const report = levelsOf(ledger).find((l) => l.level === level);
246
+ return {
247
+ measured: true,
248
+ items: report?.missing ?? [],
249
+ ...(report?.missingOverflow === undefined ? {} : { overflow: report.missingOverflow }),
250
+ };
251
+ }
252
+ export function holesOf(ledger, components) {
253
+ const edges = named(ledger.controls.seen, ledger.edges.probed);
254
+ return {
255
+ [HoleKind.ROUTES]: fromLevel(ledger, CoverageLevel.REACHED),
256
+ [HoleKind.CONTROLS]: fromLevel(ledger, CoverageLevel.TOUCHED),
257
+ [HoleKind.EDGES]: 0 === ledger.edges.probed.length
258
+ ? { ...edges, measured: false, note: HoleNote.EDGES }
259
+ : edges,
260
+ [HoleKind.WRITES]: fromLevel(ledger, CoverageLevel.BRANCHED),
261
+ [HoleKind.FUNCTIONS]: 0 === Object.keys(ledger.code).length
262
+ ? { measured: false, items: [], note: HoleNote.CODE }
263
+ : fromLevel(ledger, CoverageLevel.EXECUTED),
264
+ [HoleKind.COMPONENTS]: components === undefined
265
+ ? { measured: false, items: [], note: HoleNote.COMPONENTS }
266
+ : named(components.known, components.rendered),
267
+ };
268
+ }
169
269
  /** The best percentage each level has reached, raised to today's where today is higher. */
170
270
  export function raiseBest(ledger) {
171
271
  const best = { ...ledger.best };
172
272
  for (const l of levelsOf(ledger)) {
173
- if (l.pct !== undefined && l.pct > (best[l.level] ?? -1))
273
+ if (l.pct !== undefined && l.pct > (best[l.level] ?? -1)) {
174
274
  best[l.level] = l.pct;
275
+ }
175
276
  }
176
277
  return { ...ledger, best };
177
278
  }
@@ -181,9 +282,11 @@ export function raiseBest(ledger) {
181
282
  */
182
283
  export function acceptCurrent(ledger) {
183
284
  const best = {};
184
- for (const l of levelsOf(ledger))
185
- if (l.pct !== undefined)
285
+ for (const l of levelsOf(ledger)) {
286
+ if (l.pct !== undefined) {
186
287
  best[l.level] = l.pct;
288
+ }
289
+ }
187
290
  return { ...ledger, best };
188
291
  }
189
292
  /** Levels now below the best they ever reached — something new appeared and nothing covered it. */
@@ -191,8 +294,9 @@ export function regressions(ledger) {
191
294
  const out = [];
192
295
  for (const l of levelsOf(ledger)) {
193
296
  const was = ledger.best[l.level];
194
- if (l.pct !== undefined && was !== undefined && l.pct < was)
297
+ if (l.pct !== undefined && was !== undefined && l.pct < was) {
195
298
  out.push({ level: l.level, was, now: l.pct });
299
+ }
196
300
  }
197
301
  return out;
198
302
  }
@@ -207,8 +311,9 @@ function servedAs(code, file) {
207
311
  export function unexecutedChanged(code, changed) {
208
312
  return changed.filter((file) => {
209
313
  const served = servedAs(code, file);
210
- if (served === undefined)
314
+ if (served === undefined) {
211
315
  return false;
316
+ }
212
317
  const fns = Object.values(code[served] ?? {});
213
318
  return fns.length > 0 && fns.every((fn) => !fn.executed);
214
319
  });
@@ -223,8 +328,9 @@ export function staleChanged(ledger, changed, modifiedAt) {
223
328
  return changed.filter((file) => {
224
329
  const served = servedAs(ledger.code, file);
225
330
  const modified = modifiedAt(file);
226
- if (served === undefined || modified === undefined)
331
+ if (served === undefined || modified === undefined) {
227
332
  return false;
333
+ }
228
334
  const taken = ledger.codeTaken[served];
229
335
  return taken === undefined || modified > taken;
230
336
  });
@@ -243,8 +349,9 @@ export class LedgerStore {
243
349
  text = await this.#fs.readFile(this.#path);
244
350
  }
245
351
  catch (error) {
246
- if (this.#fs.isNotFound(error))
352
+ if (this.#fs.isNotFound(error)) {
247
353
  return emptyLedger();
354
+ }
248
355
  throw error;
249
356
  }
250
357
  return parseLedger(text);
@@ -264,8 +371,9 @@ export class LedgerStore {
264
371
  return withFileLock(this.#path, async () => {
265
372
  const ledger = await this.load();
266
373
  const accepted = regressions(ledger);
267
- if (accepted.length > 0)
374
+ if (accepted.length > 0) {
268
375
  await this.#save(acceptCurrent(ledger));
376
+ }
269
377
  return accepted;
270
378
  });
271
379
  }
@@ -29,3 +29,13 @@ export declare function sessionDelta(input: {
29
29
  export declare function isConsoleError(e: ReticleEvent): boolean;
30
30
  /** Requests that answered at or above `floor`. */
31
31
  export declare function failedRequests(events: ReticleEvent[], floor: number): ReticleEvent[];
32
+ /** Any buffer event that proves the app reacted to a click (vs a dead/no-op control). */
33
+ export declare function isActivity(e: ReticleEvent): boolean;
34
+ /**
35
+ * Controls whose correct behaviour IS to do nothing when clicked.
36
+ *
37
+ * Read off the same descriptor the crawler already has, so no extra round trip: `[disabled]` is inert
38
+ * by definition, an already `[checked]` radio/checkbox is idempotent, and clicking a text field
39
+ * focuses it rather than changing anything.
40
+ */
41
+ export declare function legitimatelyInert(desc: string): boolean;
@@ -14,12 +14,14 @@ const ID_SEGMENT = /^(?:\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9
14
14
  * refund of order 41 and of order 42 are one endpoint with one failure path to drive, not two.
15
15
  */
16
16
  export function writeKeyOf(event) {
17
- if (EventType.NET_REQUEST !== event.type)
17
+ if (EventType.NET_REQUEST !== event.type) {
18
18
  return undefined;
19
+ }
19
20
  const method = asString(event.data['method'])?.toUpperCase();
20
21
  const url = asString(event.data['url']);
21
- if (method === undefined || url === undefined || !MUTATING_METHODS.includes(method))
22
+ if (method === undefined || url === undefined || !MUTATING_METHODS.includes(method)) {
22
23
  return undefined;
24
+ }
23
25
  let path;
24
26
  try {
25
27
  path = new URL(url, 'http://localhost').pathname;
@@ -71,9 +73,50 @@ export function isConsoleError(e) {
71
73
  /** Requests that answered at or above `floor`. */
72
74
  export function failedRequests(events, floor) {
73
75
  return events.filter((e) => {
74
- if (e.type !== EventType.NET_REQUEST)
76
+ if (e.type !== EventType.NET_REQUEST) {
75
77
  return false;
78
+ }
76
79
  const status = asNumber(e.data['status']);
77
80
  return status !== undefined && status >= floor;
78
81
  });
79
82
  }
83
+ /** Any buffer event that proves the app reacted to a click (vs a dead/no-op control). */
84
+ export function isActivity(e) {
85
+ return (e.type === EventType.NET_REQUEST ||
86
+ e.type === EventType.DOM_ADDED ||
87
+ e.type === EventType.DOM_REMOVED ||
88
+ e.type === EventType.DOM_ATTR ||
89
+ e.type === EventType.DOM_TEXT ||
90
+ e.type === EventType.ROUTE_CHANGE ||
91
+ e.type === EventType.SIGNAL ||
92
+ e.type === EventType.ANIM_START ||
93
+ e.type === EventType.ANIM_END);
94
+ }
95
+ /**
96
+ * Controls whose correct behaviour IS to do nothing when clicked.
97
+ *
98
+ * Read off the same descriptor the crawler already has, so no extra round trip: `[disabled]` is inert
99
+ * by definition, an already `[checked]` radio/checkbox is idempotent, and clicking a text field
100
+ * focuses it rather than changing anything.
101
+ */
102
+ export function legitimatelyInert(desc) {
103
+ if (desc.includes('[disabled]')) {
104
+ return true;
105
+ }
106
+ if (desc.includes('[checked]')) {
107
+ return true;
108
+ }
109
+ // Inputs, and the ARIA LIVE-REGION roles.
110
+ //
111
+ // `alert`, `status`, `log`, `timer` and `marquee` exist to be READ — a screen reader announces
112
+ // them and nothing is meant to happen when one is clicked. Clicking twice and seeing nothing is
113
+ // therefore correct behaviour, and reporting it as `dead-control` accuses a working app.
114
+ //
115
+ // Found by driving a real app: the demo reported `dead-control — alert "Invalid email or
116
+ // password"`, which is a login form correctly telling somebody their password was wrong. That is
117
+ // the most expensive kind of false positive there is — the strongest claim the crawler makes,
118
+ // about an app that was working, and on the path a new user meets first.
119
+ //
120
+ // `alertdialog` is deliberately NOT here: it is interactive, and a word boundary keeps it out.
121
+ return /\b(textbox|searchbox|combobox|spinbutton|alert|status|log|timer|marquee)\b/.test(desc);
122
+ }
@@ -7,7 +7,7 @@
7
7
  * the successor before it answers — the same wait `navigate { reload }` does.
8
8
  */
9
9
  import { type CommandResult, type ReticleEvent } from '@reticlehq/core';
10
- import type { ExplorePort } from './explorer.js';
10
+ import { type ExplorePort } from './explorer.js';
11
11
  export interface PortSession {
12
12
  readonly id: string;
13
13
  readonly url: string;
@@ -17,6 +17,8 @@ export interface PortSession {
17
17
  beginAction?(tool: string, args: Record<string, unknown>): unknown;
18
18
  finishAction?(): void;
19
19
  }
20
+ /** How long a press waits for the requests it started to answer. */
21
+ export declare const PENDING_WAIT_MS = 10000;
20
22
  export declare function sessionPort(input: {
21
23
  sessions: {
22
24
  get(id: string): PortSession | undefined;