@reticlehq/server 3.7.0 → 3.8.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 (578) hide show
  1. package/SKILL.md +59 -68
  2. package/bin/reticle.js +9 -2
  3. package/dist/command/cli/adhoc-suite.js +1 -1
  4. package/dist/command/cli/adhoc-verdict.d.ts +12 -0
  5. package/dist/command/cli/adhoc-verdict.js +62 -4
  6. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  7. package/dist/command/cli/answers/gate-hook-message.js +5 -1
  8. package/dist/command/cli/auth/cloud-session.d.ts +11 -19
  9. package/dist/command/cli/auth/cloud-session.js +23 -0
  10. package/dist/command/cli/auth/device-flow.d.ts +3 -37
  11. package/dist/command/cli/cli-doctor.js +66 -21
  12. package/dist/command/cli/cli-flow-commands.js +53 -11
  13. package/dist/command/cli/cli-kill.d.ts +8 -0
  14. package/dist/command/cli/cli-kill.js +43 -3
  15. package/dist/command/cli/cli-parse-feedback.d.ts +4 -0
  16. package/dist/command/cli/cli-parse-feedback.js +13 -1
  17. package/dist/command/cli/cli-parse-grammar.d.ts +1 -2
  18. package/dist/command/cli/cli-parse-grammar.js +1 -6
  19. package/dist/command/cli/cli-parse-serve.d.ts +24 -0
  20. package/dist/command/cli/cli-parse-serve.js +124 -0
  21. package/dist/command/cli/cli-parse.d.ts +12 -11
  22. package/dist/command/cli/cli-parse.js +100 -123
  23. package/dist/command/cli/cli-usage.d.ts +0 -11
  24. package/dist/command/cli/cli-usage.js +43 -16
  25. package/dist/command/cli/cli-verify.d.ts +1 -1
  26. package/dist/command/cli/cli-verify.js +29 -14
  27. package/dist/command/cli/cloud-cli.js +27 -9
  28. package/dist/command/cli/cloud-kit.js +3 -3
  29. package/dist/command/cli/cloud-link.d.ts +1 -20
  30. package/dist/command/cli/cloud-link.js +2 -0
  31. package/dist/command/cli/cloud-login.d.ts +6 -2
  32. package/dist/command/cli/cloud-login.js +13 -41
  33. package/dist/command/cli/config/checkout.d.ts +10 -0
  34. package/dist/command/cli/config/checkout.js +78 -0
  35. package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
  36. package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
  37. package/dist/command/cli/doctor/doctor-coverage-line.js +1 -7
  38. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  39. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  40. package/dist/command/cli/drive/drive-attach.d.ts +5 -0
  41. package/dist/command/cli/drive/drive-attach.js +21 -1
  42. package/dist/command/cli/hook/codex-transcript.d.ts +20 -0
  43. package/dist/command/cli/hook/codex-transcript.js +224 -0
  44. package/dist/command/cli/hook/episode-structure.d.ts +24 -0
  45. package/dist/command/cli/hook/episode-structure.js +138 -0
  46. package/dist/command/cli/hook/hook-command.d.ts +9 -0
  47. package/dist/command/cli/hook/hook-command.js +211 -0
  48. package/dist/command/cli/hook/hook-fast.d.ts +31 -0
  49. package/dist/command/cli/hook/hook-fast.js +136 -0
  50. package/dist/command/cli/hook/prompt-hook.d.ts +30 -0
  51. package/dist/command/cli/hook/prompt-hook.js +199 -0
  52. package/dist/command/cli/hook/tool-hook.d.ts +21 -0
  53. package/dist/command/cli/hook/tool-hook.js +259 -0
  54. package/dist/command/cli/launch/cli-launch.d.ts +2 -0
  55. package/dist/command/cli/launch/cli-launch.js +22 -2
  56. package/dist/command/cli/lifecycle/daemon-lifecycle.js +28 -2
  57. package/dist/command/cli/mcp-command.d.ts +9 -0
  58. package/dist/command/cli/mcp-command.js +127 -41
  59. package/dist/command/cli/open-navigate.d.ts +43 -0
  60. package/dist/command/cli/open-navigate.js +85 -0
  61. package/dist/command/cli/ports/port-holder.d.ts +16 -4
  62. package/dist/command/cli/ports/port-holder.js +58 -12
  63. package/dist/command/cli/ports/resolve/cli-port.js +18 -2
  64. package/dist/command/cli/project-name.d.ts +1 -7
  65. package/dist/command/cli/project-name.js +1 -0
  66. package/dist/command/cli/report-command.d.ts +8 -2
  67. package/dist/command/cli/report-command.js +95 -18
  68. package/dist/command/cli/status-fields.d.ts +9 -2
  69. package/dist/command/cli/status-fields.js +148 -4
  70. package/dist/command/cli/try-command.d.ts +3 -4
  71. package/dist/command/cli/try-command.js +6 -11
  72. package/dist/command/cli/verify-results.d.ts +4 -1
  73. package/dist/command/cli/verify-results.js +3 -1
  74. package/dist/command/cli.d.ts +1 -7
  75. package/dist/command/cli.js +79 -160
  76. package/dist/command/daemon/daemon-resilience.d.ts +6 -1
  77. package/dist/command/daemon/daemon-resilience.js +31 -18
  78. package/dist/command/daemon/daemon.d.ts +11 -0
  79. package/dist/command/daemon/daemon.js +70 -2
  80. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +2 -0
  81. package/dist/command/daemon/lifetime/daemon-usefulness.js +13 -2
  82. package/dist/command/daemon/wake-decision.js +4 -2
  83. package/dist/command/daemon/win32-escape.d.ts +32 -0
  84. package/dist/command/daemon/win32-escape.js +119 -0
  85. package/dist/command/setup/agent-configs.d.ts +9 -0
  86. package/dist/command/setup/agent-configs.js +9 -0
  87. package/dist/command/setup/auto-approve.d.ts +10 -3
  88. package/dist/command/setup/auto-approve.js +51 -3
  89. package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
  90. package/dist/command/setup/bringup/dev-server-wait.js +5 -1
  91. package/dist/command/setup/bringup/ensure-daemon.js +4 -7
  92. package/dist/command/setup/init/init-host.js +6 -0
  93. package/dist/command/setup/init/init-port.js +5 -4
  94. package/dist/command/setup/init/init-runtime.js +7 -1
  95. package/dist/command/setup/node-effects.d.ts +8 -0
  96. package/dist/command/setup/node-effects.js +36 -4
  97. package/dist/command/setup/probe/dev-port.d.ts +26 -0
  98. package/dist/command/setup/probe/dev-port.js +117 -0
  99. package/dist/command/setup/probe/page-probe.d.ts +1 -1
  100. package/dist/command/setup/probe/page-probe.js +11 -3
  101. package/dist/command/setup/remaining-steps.d.ts +0 -7
  102. package/dist/command/setup/remaining-steps.js +3 -2
  103. package/dist/command/setup/run-setup.d.ts +11 -0
  104. package/dist/command/setup/run-setup.js +33 -5
  105. package/dist/command/setup/setup-command.d.ts +2 -0
  106. package/dist/command/setup/setup-command.js +22 -4
  107. package/dist/command/update/updater.d.ts +1 -6
  108. package/dist/command/update/updater.js +4 -5
  109. package/dist/command/update/version-seen.d.ts +24 -0
  110. package/dist/command/update/version-seen.js +65 -0
  111. package/dist/command/version/peer-announce.d.ts +9 -1
  112. package/dist/command/version/peer-announce.js +25 -2
  113. package/dist/command/version/sdk-fix.d.ts +5 -6
  114. package/dist/command/version/sdk-fix.js +17 -32
  115. package/dist/command/version/version-skew.d.ts +16 -1
  116. package/dist/command/version/version-skew.js +84 -10
  117. package/dist/features/crawl/crawl-tools.js +2 -2
  118. package/dist/features/crawl/crawl.js +2 -0
  119. package/dist/features/exhaust/ledger.d.ts +15 -96
  120. package/dist/features/exhaust/ledger.js +3 -3
  121. package/dist/features/harness/drive-runs.d.ts +28 -4
  122. package/dist/features/harness/drive-runs.js +99 -7
  123. package/dist/features/harness/drivers.d.ts +1 -1
  124. package/dist/features/harness/drivers.js +1 -1
  125. package/dist/features/harness/harness.d.ts +5 -0
  126. package/dist/features/harness/harness.js +24 -12
  127. package/dist/features/harness/platform/platform-drives.d.ts +7 -1
  128. package/dist/features/harness/platform/platform-drives.js +12 -7
  129. package/dist/features/harness/platform/refusal.d.ts +1 -1
  130. package/dist/features/harness/platform/refusal.js +4 -4
  131. package/dist/features/harness/platform/remote-drive.js +70 -28
  132. package/dist/features/harness/platform/server-driver.js +22 -1
  133. package/dist/features/harness/platform-config.d.ts +1 -10
  134. package/dist/features/harness/platform-config.js +0 -20
  135. package/dist/features/license/license.d.ts +2 -14
  136. package/dist/features/visual/step-shots.d.ts +33 -1
  137. package/dist/features/visual/step-shots.js +155 -1
  138. package/dist/features/visual/visual-tools.js +12 -4
  139. package/dist/hooks/coding-agents.d.ts +6 -0
  140. package/dist/hooks/coding-agents.js +24 -3
  141. package/dist/hooks/episode-stamp.d.ts +32 -0
  142. package/dist/hooks/episode-stamp.js +61 -0
  143. package/dist/hooks/hook-commands.js +19 -9
  144. package/dist/index.d.ts +5 -1
  145. package/dist/index.js +66 -24
  146. package/dist/judgement/app-graph/journal-steps.d.ts +8 -0
  147. package/dist/judgement/app-graph/journal-steps.js +92 -0
  148. package/dist/judgement/capsule/capsule-store.d.ts +2 -39
  149. package/dist/judgement/domain/domain-model.js +1 -10
  150. package/dist/judgement/domain/domain-tools.js +4 -2
  151. package/dist/judgement/runs/artifact/run-store.d.ts +28 -8
  152. package/dist/judgement/runs/artifact/run-store.js +153 -28
  153. package/dist/judgement/runs/default-run-id.d.ts +3 -1
  154. package/dist/judgement/runs/default-run-id.js +15 -3
  155. package/dist/judgement/runs/drive-run.d.ts +3 -3
  156. package/dist/judgement/runs/drive-run.js +11 -7
  157. package/dist/judgement/runs/run-ledger.d.ts +21 -0
  158. package/dist/judgement/runs/run-ledger.js +143 -0
  159. package/dist/judgement/runs/runner-port.d.ts +1 -1
  160. package/dist/judgement/runs/runner-port.js +8 -2
  161. package/dist/judgement/runs/verification-sync.d.ts +4 -3
  162. package/dist/judgement/runs/verification-sync.js +27 -5
  163. package/dist/language/flows/affected-tools.js +2 -1
  164. package/dist/language/flows/annotate-notes/annotate.js +8 -3
  165. package/dist/language/flows/annotate-tools.js +1 -1
  166. package/dist/language/flows/change/affected.d.ts +8 -0
  167. package/dist/language/flows/change/affected.js +20 -7
  168. package/dist/language/flows/decision.js +19 -4
  169. package/dist/language/flows/fields/flow-secret-field.d.ts +9 -0
  170. package/dist/language/flows/fields/flow-secret-field.js +67 -5
  171. package/dist/language/flows/flow-anchor.d.ts +5 -0
  172. package/dist/language/flows/flow-anchor.js +4 -0
  173. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  174. package/dist/language/flows/flow-cross-step.js +9 -0
  175. package/dist/language/flows/flow-expect-grammar.js +15 -10
  176. package/dist/language/flows/flow-intent.js +6 -1
  177. package/dist/language/flows/flow-journey.d.ts +8 -0
  178. package/dist/language/flows/flow-journey.js +13 -0
  179. package/dist/language/flows/flow-lease.d.ts +27 -0
  180. package/dist/language/flows/flow-lease.js +59 -0
  181. package/dist/language/flows/flow-replay-run.d.ts +1 -87
  182. package/dist/language/flows/flow-replay-run.js +111 -310
  183. package/dist/language/flows/flow-replay.js +21 -19
  184. package/dist/language/flows/flow-replayable.d.ts +38 -0
  185. package/dist/language/flows/flow-replayable.js +179 -0
  186. package/dist/language/flows/flow-start-path.d.ts +108 -0
  187. package/dist/language/flows/flow-start-path.js +313 -0
  188. package/dist/language/flows/flow-step-runners.d.ts +23 -2
  189. package/dist/language/flows/flow-step-runners.js +191 -32
  190. package/dist/language/flows/flow-store-for-session.d.ts +8 -1
  191. package/dist/language/flows/flow-store-for-session.js +6 -0
  192. package/dist/language/flows/flow-tools.d.ts +2 -23
  193. package/dist/language/flows/flow-tools.js +63 -113
  194. package/dist/language/flows/flows.d.ts +4 -6
  195. package/dist/language/flows/flows.js +90 -15
  196. package/dist/language/flows/heal-run.d.ts +10 -4
  197. package/dist/language/flows/heal-run.js +26 -9
  198. package/dist/language/flows/recording/flake.d.ts +3 -27
  199. package/dist/language/flows/recording/flake.js +1 -1
  200. package/dist/language/flows/recording/recording-backtrack.js +1 -4
  201. package/dist/language/flows/recording/tape/recordings.d.ts +11 -1
  202. package/dist/language/flows/recording/tape/recordings.js +9 -3
  203. package/dist/language/flows/replay.d.ts +25 -5
  204. package/dist/language/flows/replay.js +83 -16
  205. package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
  206. package/dist/language/flows/suite/coverage.d.ts +9 -0
  207. package/dist/language/flows/suite/coverage.js +12 -1
  208. package/dist/language/flows/suite-selection.d.ts +25 -0
  209. package/dist/language/flows/suite-selection.js +41 -9
  210. package/dist/machine/git-tracks.d.ts +6 -0
  211. package/dist/machine/git-tracks.js +36 -0
  212. package/dist/memory/cloud/background-sync.d.ts +25 -0
  213. package/dist/memory/cloud/background-sync.js +79 -0
  214. package/dist/memory/cloud/cloud-config.d.ts +12 -1
  215. package/dist/memory/cloud/cloud-config.js +39 -13
  216. package/dist/memory/cloud/cloud-sync.d.ts +3 -24
  217. package/dist/memory/cloud/cloud-sync.js +5 -2
  218. package/dist/memory/cloud/harness-config.d.ts +0 -7
  219. package/dist/memory/cloud/memory-scope.d.ts +19 -0
  220. package/dist/memory/cloud/memory-scope.js +48 -0
  221. package/dist/memory/cloud/sync-answer.d.ts +43 -0
  222. package/dist/memory/cloud/sync-answer.js +42 -0
  223. package/dist/memory/cloud/sync-cycle.d.ts +40 -10
  224. package/dist/memory/cloud/sync-cycle.js +97 -144
  225. package/dist/memory/cloud/sync-daemon.d.ts +50 -3
  226. package/dist/memory/cloud/sync-daemon.js +473 -83
  227. package/dist/memory/cloud/sync-describe.d.ts +28 -0
  228. package/dist/memory/cloud/sync-describe.js +92 -0
  229. package/dist/memory/cloud/sync-disk.d.ts +9 -1
  230. package/dist/memory/cloud/sync-disk.js +137 -33
  231. package/dist/memory/cloud/sync-flow-versions.d.ts +26 -0
  232. package/dist/memory/cloud/sync-flow-versions.js +42 -0
  233. package/dist/memory/impact/impact-store.js +32 -15
  234. package/dist/memory/intent/intent-shard.d.ts +41 -266
  235. package/dist/memory/intent/intent-tools.js +20 -61
  236. package/dist/memory/intent/record-request.d.ts +27 -0
  237. package/dist/memory/intent/record-request.js +88 -0
  238. package/dist/memory/intent/settled-words.js +9 -1
  239. package/dist/memory/journal/drive-flow.d.ts +3 -0
  240. package/dist/memory/journal/drive-flow.js +11 -41
  241. package/dist/memory/journal/envelope-store.js +1 -1
  242. package/dist/memory/journal/envelope.d.ts +6 -154
  243. package/dist/memory/journal/episode-log.d.ts +107 -0
  244. package/dist/memory/journal/episode-log.js +302 -0
  245. package/dist/memory/journal/journal-recorder.d.ts +7 -0
  246. package/dist/memory/journal/journal-recorder.js +22 -9
  247. package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
  248. package/dist/memory/journal/on-disk/ambient-file.js +1 -1
  249. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +24 -3
  250. package/dist/memory/journal/on-disk/workspace-gitignore.js +38 -7
  251. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -0
  252. package/dist/memory/journal/self-observation.js +3 -1
  253. package/dist/memory/journal/session-end.js +23 -9
  254. package/dist/memory/journal/session-journal.js +39 -20
  255. package/dist/memory/project/artifact-root-resolver.d.ts +14 -1
  256. package/dist/memory/project/artifact-root-resolver.js +68 -23
  257. package/dist/memory/project/artifact-root.d.ts +17 -3
  258. package/dist/memory/project/artifact-root.js +47 -9
  259. package/dist/memory/project/fs/fs-port.d.ts +5 -0
  260. package/dist/memory/project/fs/fs-port.js +13 -0
  261. package/dist/memory/project/learned-routes.d.ts +3 -4
  262. package/dist/memory/project/learned-routes.js +3 -11
  263. package/dist/memory/project/link-directory.js +5 -2
  264. package/dist/memory/project/root-claims.d.ts +3 -1
  265. package/dist/memory/project/root-claims.js +41 -7
  266. package/dist/memory/project/session-root.d.ts +1 -1
  267. package/dist/memory/project/session-root.js +1 -15
  268. package/dist/memory/project/sync-status.js +5 -6
  269. package/dist/memory/recall/memory-tools.js +2 -2
  270. package/dist/memory/recall/platform-memory.d.ts +3 -1
  271. package/dist/memory/recall/platform-memory.js +9 -1
  272. package/dist/memory/recall/project-memory.js +15 -19
  273. package/dist/portal/bridge/auth-failure-reason.d.ts +13 -0
  274. package/dist/portal/bridge/auth-failure-reason.js +19 -0
  275. package/dist/portal/bridge/bridge.d.ts +19 -4
  276. package/dist/portal/bridge/bridge.js +150 -177
  277. package/dist/portal/bridge/harness-request.d.ts +15 -0
  278. package/dist/portal/bridge/harness-request.js +19 -0
  279. package/dist/portal/bridge/origin/origin-follow.d.ts +46 -0
  280. package/dist/portal/bridge/origin/origin-follow.js +110 -0
  281. package/dist/portal/bridge/pairing-token.d.ts +6 -0
  282. package/dist/portal/bridge/pairing-token.js +47 -16
  283. package/dist/portal/bridge/protocol-skew.d.ts +27 -2
  284. package/dist/portal/bridge/protocol-skew.js +66 -2
  285. package/dist/portal/bridge/session-nonces.d.ts +46 -0
  286. package/dist/portal/bridge/session-nonces.js +72 -0
  287. package/dist/portal/input/viewport-tools.d.ts +31 -0
  288. package/dist/portal/input/viewport-tools.js +106 -15
  289. package/dist/portal/pool/bounded-close.d.ts +23 -0
  290. package/dist/portal/pool/bounded-close.js +38 -0
  291. package/dist/portal/pool/browser-pool.d.ts +44 -39
  292. package/dist/portal/pool/browser-pool.js +247 -95
  293. package/dist/portal/pool/context-permissions.d.ts +9 -1
  294. package/dist/portal/pool/context-permissions.js +26 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +37 -0
  296. package/dist/portal/pool/lease-emulation.js +29 -0
  297. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  298. package/dist/portal/pool/playwright-launcher.js +74 -7
  299. package/dist/portal/pool/pool-contract.d.ts +23 -1
  300. package/dist/portal/pool/pool-lease.d.ts +128 -0
  301. package/dist/portal/pool/pool-lease.js +49 -0
  302. package/dist/portal/pool/zero-install.d.ts +8 -1
  303. package/dist/portal/pool/zero-install.js +24 -2
  304. package/dist/portal/production/production-activation.d.ts +136 -0
  305. package/dist/portal/production/production-activation.js +173 -0
  306. package/dist/portal/production/production-drive.d.ts +52 -0
  307. package/dist/portal/production/production-drive.js +301 -0
  308. package/dist/portal/production/production-hello.d.ts +23 -0
  309. package/dist/portal/production/production-hello.js +23 -0
  310. package/dist/portal/realm/web-realm.js +42 -15
  311. package/dist/portal/session/control-envelope.d.ts +1 -1
  312. package/dist/portal/session/dev-server/served-document.d.ts +0 -5
  313. package/dist/portal/session/dev-server/served-document.js +6 -8
  314. package/dist/portal/session/facts/handshake-facts.d.ts +8 -1
  315. package/dist/portal/session/first-run-wiring.d.ts +3 -36
  316. package/dist/portal/session/first-run-wiring.js +10 -103
  317. package/dist/portal/session/live-control-tools.js +61 -19
  318. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  319. package/dist/portal/session/no-session-next-action.js +40 -15
  320. package/dist/portal/session/no-session-watch.d.ts +7 -0
  321. package/dist/portal/session/no-session-watch.js +67 -23
  322. package/dist/portal/session/output-budget.d.ts +10 -0
  323. package/dist/portal/session/output-budget.js +16 -0
  324. package/dist/portal/session/presence/session-lease.d.ts +2 -0
  325. package/dist/portal/session/presence/session-lease.js +8 -0
  326. package/dist/portal/session/recorded-gaps.d.ts +15 -11
  327. package/dist/portal/session/recorded-gaps.js +39 -18
  328. package/dist/portal/session/serving-directory.d.ts +24 -2
  329. package/dist/portal/session/serving-directory.js +93 -5
  330. package/dist/portal/session/session-info.d.ts +26 -0
  331. package/dist/portal/session/session-info.js +20 -0
  332. package/dist/portal/session/session-manager.d.ts +35 -3
  333. package/dist/portal/session/session-manager.js +69 -14
  334. package/dist/portal/session/session-successor.js +1 -9
  335. package/dist/portal/session/session-tools.js +22 -4
  336. package/dist/portal/session/session.d.ts +10 -10
  337. package/dist/portal/session/session.js +37 -29
  338. package/dist/portal/session/zero-install-first-call.d.ts +7 -2
  339. package/dist/portal/session/zero-install-first-call.js +43 -20
  340. package/dist/start-wiring.d.ts +2 -2
  341. package/dist/start-wiring.js +17 -3
  342. package/dist/status-payload.d.ts +11 -1
  343. package/dist/status-payload.js +7 -1
  344. package/dist/surface/agent-presence-grace.d.ts +11 -0
  345. package/dist/surface/agent-presence-grace.js +34 -0
  346. package/dist/surface/http-server.d.ts +12 -1
  347. package/dist/surface/http-server.js +64 -15
  348. package/dist/surface/mcp/faults/mcp-outage.d.ts +12 -2
  349. package/dist/surface/mcp/faults/mcp-outage.js +19 -1
  350. package/dist/surface/mcp/mcp-proxy.d.ts +17 -74
  351. package/dist/surface/mcp/mcp-proxy.js +194 -267
  352. package/dist/surface/mcp/mcp.d.ts +20 -3
  353. package/dist/surface/mcp/mcp.js +88 -25
  354. package/dist/surface/mcp/proxy/drive-calls.d.ts +44 -0
  355. package/dist/surface/mcp/proxy/drive-calls.js +114 -5
  356. package/dist/surface/mcp/proxy/proxy-boot.d.ts +11 -0
  357. package/dist/surface/mcp/proxy/proxy-boot.js +1 -0
  358. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +6 -0
  359. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +14 -1
  360. package/dist/surface/mcp/proxy/proxy-handshake.d.ts +34 -2
  361. package/dist/surface/mcp/proxy/proxy-handshake.js +76 -8
  362. package/dist/surface/mcp/server-instructions.js +35 -16
  363. package/dist/surface/mcp/shared-params.d.ts +8 -0
  364. package/dist/surface/mcp/shared-params.js +15 -1
  365. package/dist/surface/mcp/sse-frame-parser.d.ts +11 -0
  366. package/dist/surface/mcp/sse-frame-parser.js +18 -0
  367. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  368. package/dist/surface/mcp/surface-vocabulary.js +13 -0
  369. package/dist/surface/mcp/tool-catalog-cache.d.ts +2 -0
  370. package/dist/surface/mcp/tool-catalog-cache.js +14 -4
  371. package/dist/surface/tools/account-tool.d.ts +4 -3
  372. package/dist/surface/tools/account-tool.js +20 -31
  373. package/dist/surface/tools/act/act-danger.d.ts +2 -0
  374. package/dist/surface/tools/act/act-danger.js +41 -14
  375. package/dist/surface/tools/act/act-observation.d.ts +11 -0
  376. package/dist/surface/tools/act/act-observation.js +33 -0
  377. package/dist/surface/tools/act/act-preflight.js +10 -3
  378. package/dist/surface/tools/act/act-sequence-retry.js +2 -1
  379. package/dist/surface/tools/act/act-target.js +19 -5
  380. package/dist/surface/tools/act/durable.js +1 -1
  381. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +9 -7
  382. package/dist/surface/tools/act/navigation/navigate-arrival.js +11 -14
  383. package/dist/surface/tools/act/unsettled-find.d.ts +36 -0
  384. package/dist/surface/tools/act/unsettled-find.js +71 -0
  385. package/dist/surface/tools/act-sequence-tool.js +53 -24
  386. package/dist/surface/tools/act-tools.d.ts +2 -3
  387. package/dist/surface/tools/act-tools.js +49 -47
  388. package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
  389. package/dist/surface/tools/agent/for-the-agent.js +41 -3
  390. package/dist/surface/tools/assert/assert-grade.js +8 -3
  391. package/dist/surface/tools/assert/assert-verdict.js +4 -2
  392. package/dist/surface/tools/browser-tools.js +7 -4
  393. package/dist/surface/tools/chat-drives.js +50 -25
  394. package/dist/surface/tools/coverage-tools.js +1 -1
  395. package/dist/surface/tools/dynamic-tools.d.ts +8 -0
  396. package/dist/surface/tools/dynamic-tools.js +89 -34
  397. package/dist/surface/tools/empty-session-list.d.ts +18 -25
  398. package/dist/surface/tools/empty-session-list.js +67 -38
  399. package/dist/surface/tools/error-recovery.d.ts +17 -7
  400. package/dist/surface/tools/error-recovery.js +53 -20
  401. package/dist/surface/tools/explore-tools.d.ts +10 -0
  402. package/dist/surface/tools/explore-tools.js +72 -19
  403. package/dist/surface/tools/feedback-tools.js +23 -2
  404. package/dist/surface/tools/gap-novelty.d.ts +6 -0
  405. package/dist/surface/tools/gap-novelty.js +9 -0
  406. package/dist/surface/tools/gaps/capability-absences.js +4 -3
  407. package/dist/surface/tools/harness-explore.d.ts +1 -1
  408. package/dist/surface/tools/harness-explore.js +6 -4
  409. package/dist/surface/tools/harness-gate.d.ts +43 -21
  410. package/dist/surface/tools/harness-gate.js +55 -81
  411. package/dist/surface/tools/harness-script.d.ts +2 -0
  412. package/dist/surface/tools/harness-script.js +11 -6
  413. package/dist/surface/tools/harness-toolset.js +3 -4
  414. package/dist/surface/tools/invoke-tool.d.ts +0 -8
  415. package/dist/surface/tools/invoke-tool.js +164 -178
  416. package/dist/surface/tools/lease-hint.d.ts +9 -0
  417. package/dist/surface/tools/lease-hint.js +22 -3
  418. package/dist/surface/tools/lease-permissions.d.ts +1 -1
  419. package/dist/surface/tools/lease-readiness.d.ts +55 -0
  420. package/dist/surface/tools/lease-readiness.js +150 -2
  421. package/dist/surface/tools/lease-tools.d.ts +3 -75
  422. package/dist/surface/tools/lease-tools.js +124 -188
  423. package/dist/surface/tools/map-tools.d.ts +20 -0
  424. package/dist/surface/tools/map-tools.js +145 -0
  425. package/dist/surface/tools/merge-tools.js +11 -7
  426. package/dist/surface/tools/next-step.js +6 -3
  427. package/dist/surface/tools/observe-tools.js +46 -35
  428. package/dist/surface/tools/platform-moment.d.ts +3 -12
  429. package/dist/surface/tools/platform-moment.js +12 -24
  430. package/dist/surface/tools/read/complete-snapshot.d.ts +7 -1
  431. package/dist/surface/tools/read/complete-snapshot.js +13 -2
  432. package/dist/surface/tools/read/query-paginate.js +6 -0
  433. package/dist/surface/tools/read/query-shape.d.ts +9 -0
  434. package/dist/surface/tools/read/query-shape.js +15 -0
  435. package/dist/surface/tools/read/snapshot-unmounted-note.d.ts +1 -0
  436. package/dist/surface/tools/read/snapshot-unmounted-note.js +73 -0
  437. package/dist/surface/tools/read-tools.js +5 -5
  438. package/dist/surface/tools/real-input-attempt.d.ts +11 -0
  439. package/dist/surface/tools/real-input-attempt.js +206 -38
  440. package/dist/surface/tools/reconcile-tools.js +21 -8
  441. package/dist/surface/tools/reticle-result.d.ts +3 -0
  442. package/dist/surface/tools/reticle-result.js +8 -1
  443. package/dist/surface/tools/run-report.d.ts +22 -0
  444. package/dist/surface/tools/run-report.js +141 -0
  445. package/dist/surface/tools/session-ports.d.ts +57 -0
  446. package/dist/surface/tools/session-ports.js +102 -0
  447. package/dist/surface/tools/tool-kit.d.ts +58 -4
  448. package/dist/surface/tools/tool-kit.js +140 -6
  449. package/dist/surface/tools/tool-surface.d.ts +2 -0
  450. package/dist/surface/tools/tool-surface.js +45 -0
  451. package/dist/surface/tools/tools.js +49 -87
  452. package/dist/telemetry/automation-hint.d.ts +5 -0
  453. package/dist/telemetry/automation-hint.js +7 -0
  454. package/dist/telemetry/cli-telemetry.d.ts +22 -0
  455. package/dist/telemetry/cli-telemetry.js +80 -11
  456. package/dist/telemetry/daemon-telemetry.d.ts +3 -1
  457. package/dist/telemetry/daemon-telemetry.js +9 -2
  458. package/dist/telemetry/feedback-cli.d.ts +1 -1
  459. package/dist/telemetry/feedback-cli.js +8 -3
  460. package/dist/telemetry/feedback.d.ts +1 -1
  461. package/dist/telemetry/feedback.js +51 -4
  462. package/dist/telemetry/harness-run.d.ts +3 -3
  463. package/dist/telemetry/harness-run.js +18 -7
  464. package/dist/telemetry/hud-events.d.ts +1 -1
  465. package/dist/telemetry/hud-metrics.d.ts +6 -3
  466. package/dist/telemetry/hud-metrics.js +20 -8
  467. package/dist/telemetry/install-source.js +1 -1
  468. package/dist/telemetry/mcp-connection.d.ts +1 -1
  469. package/dist/telemetry/mcp-connection.js +21 -2
  470. package/dist/telemetry/onboarding-firsts.d.ts +6 -1
  471. package/dist/telemetry/onboarding-firsts.js +41 -1
  472. package/dist/telemetry/proxy-telemetry.d.ts +42 -0
  473. package/dist/telemetry/proxy-telemetry.js +70 -1
  474. package/dist/telemetry/session-metrics.d.ts +1 -3
  475. package/dist/telemetry/session-metrics.js +16 -12
  476. package/dist/telemetry/telemetry.d.ts +9 -1
  477. package/dist/telemetry/telemetry.js +16 -1
  478. package/dist/telemetry/verification-of.d.ts +3 -1
  479. package/dist/telemetry/verification-of.js +7 -1
  480. package/dist/wire-journal.js +12 -2
  481. package/docs/README.md +4 -2
  482. package/docs/actions.mdx +7 -6
  483. package/docs/adapters.md +2 -1
  484. package/docs/agent-cheatsheet.md +19 -5
  485. package/docs/architecture.md +13 -10
  486. package/docs/autodrive.md +6 -5
  487. package/docs/benchmarks.md +2 -2
  488. package/docs/capabilities.mdx +1 -1
  489. package/docs/cli/affected.mdx +1 -1
  490. package/docs/cli/cloud.mdx +6 -6
  491. package/docs/cli/doctor.mdx +3 -1
  492. package/docs/cli/feedback.mdx +11 -7
  493. package/docs/cli/gate.mdx +1 -1
  494. package/docs/cli/help.mdx +1 -1
  495. package/docs/cli/init.mdx +20 -16
  496. package/docs/cli/mcp.mdx +5 -2
  497. package/docs/cli/open.mdx +5 -2
  498. package/docs/cli/report.mdx +1 -1
  499. package/docs/cli/restart.mdx +1 -1
  500. package/docs/cli/rollback.mdx +4 -4
  501. package/docs/cli/serve.mdx +7 -5
  502. package/docs/cli/setup.mdx +1 -1
  503. package/docs/cli/status.mdx +6 -2
  504. package/docs/cli/stop.mdx +3 -3
  505. package/docs/cli/telemetry.mdx +5 -1
  506. package/docs/cli/try.mdx +8 -8
  507. package/docs/cli/update.mdx +8 -5
  508. package/docs/cli/verify.mdx +12 -5
  509. package/docs/cli/version.mdx +1 -1
  510. package/docs/cli.mdx +10 -6
  511. package/docs/debugging.md +1 -1
  512. package/docs/deploy-checks.md +8 -6
  513. package/docs/desktop.mdx +14 -18
  514. package/docs/enterprise.md +6 -6
  515. package/docs/environment.mdx +5 -3
  516. package/docs/faq.mdx +9 -6
  517. package/docs/first-drive.md +19 -9
  518. package/docs/flows.md +3 -4
  519. package/docs/for-agents.md +1 -1
  520. package/docs/frameworks.mdx +34 -11
  521. package/docs/getting-started.md +46 -29
  522. package/docs/http-transport.md +2 -2
  523. package/docs/human-control.md +8 -8
  524. package/docs/index.mdx +2 -2
  525. package/docs/install-agentic.mdx +18 -19
  526. package/docs/install-manual.mdx +8 -4
  527. package/docs/integration-patterns.md +5 -5
  528. package/docs/multi-agent-testing.md +7 -5
  529. package/docs/packages/browser.mdx +34 -2
  530. package/docs/packages/core.mdx +2 -1
  531. package/docs/packages/electron.mdx +15 -3
  532. package/docs/packages/eslint-plugin.mdx +1 -1
  533. package/docs/packages/next.mdx +17 -5
  534. package/docs/packages/react.mdx +8 -1
  535. package/docs/packages/server.mdx +3 -3
  536. package/docs/packages/tauri.mdx +3 -3
  537. package/docs/packages/vite-plugin.mdx +1 -1
  538. package/docs/packages.mdx +19 -6
  539. package/docs/platform-integration.md +12 -4
  540. package/docs/predicates.mdx +18 -7
  541. package/docs/production-mode.md +108 -0
  542. package/docs/prompt-capture.md +99 -0
  543. package/docs/quickstart.mdx +14 -12
  544. package/docs/recipes.mdx +9 -8
  545. package/docs/required-checks.md +3 -2
  546. package/docs/skill-file.mdx +4 -3
  547. package/docs/skill-setup.md +2 -2
  548. package/docs/skill.md +62 -70
  549. package/docs/state-files.md +1 -0
  550. package/docs/state-management.mdx +11 -9
  551. package/docs/system-map.md +14 -13
  552. package/docs/telemetry.md +32 -24
  553. package/docs/testing.md +9 -5
  554. package/docs/tools/act-and-wait.mdx +17 -13
  555. package/docs/tools/act-sequence.mdx +40 -66
  556. package/docs/tools/act.mdx +7 -5
  557. package/docs/tools/assert.mdx +1 -1
  558. package/docs/tools/look.mdx +1 -1
  559. package/docs/tools/navigate.mdx +1 -1
  560. package/docs/tools/network.mdx +13 -11
  561. package/docs/tools/overview.mdx +3 -3
  562. package/docs/tools/query.mdx +3 -3
  563. package/docs/tools/session-and-feedback.mdx +9 -6
  564. package/docs/tools/sessions.mdx +7 -0
  565. package/docs/tools/snapshot.mdx +9 -7
  566. package/docs/tools/tools-and-run.mdx +11 -10
  567. package/docs/tools/verify.mdx +1 -1
  568. package/docs/troubleshooting.mdx +21 -9
  569. package/docs/usage.md +58 -43
  570. package/docs/use-cases.mdx +2 -2
  571. package/docs/vs-chrome-devtools-mcp.mdx +5 -5
  572. package/docs/vs-playwright-mcp.mdx +5 -4
  573. package/docs/what-is-recorded.md +12 -7
  574. package/docs/what-is-reticle.mdx +2 -2
  575. package/docs/why-reticle.mdx +1 -1
  576. package/package.json +9 -12
  577. package/dist/command/dev/stale-issue-guard.d.ts +0 -34
  578. package/dist/command/dev/stale-issue-guard.js +0 -69
package/docs/usage.md CHANGED
@@ -31,7 +31,7 @@ If you haven't set up Reticle yet, start with [Getting Started](getting-started.
31
31
  1. [How Reticle helps you](#1-how-reticle-helps-you)
32
32
  2. [Core concepts](#2-core-concepts)
33
33
  3. [The tools: full reference](#3-the-tools-full-reference)
34
- 4. [The predicate DSL: full reference](#4-the-predicate-dsl-full-reference)
34
+ 4. [The predicate DSL](#4-the-predicate-dsl)
35
35
  5. [Actions: full list](#5-actions-full-list)
36
36
  6. [Snapshot modes & scoping](#6-snapshot-modes--scoping)
37
37
  7. [Cookbook: real situations](#7-cookbook-real-situations)
@@ -72,11 +72,11 @@ Who benefits most: anyone shipping **dashboards, internal tools, SaaS apps**. Be
72
72
  3. **Observe** with `reticle_observe({ since })`: everything the app did _after_ that action.
73
73
  4. **Assert** with `reticle_assert({ predicate })`. Verify it, get evidence.
74
74
 
75
- **Refs.** Elements are addressed by stable handles like `e7`. You get them from `reticle_look { action: "page" }` or `reticle_look { action: "find" }`, then pass them to `act`/`inspect`. A ref re-resolves to its element across re-renders; if the element is gone, you get a clear error.
75
+ **Refs.** Elements are addressed by stable handles like `e7`. You get them from `reticle_look { action: "page" }` or `reticle_look { action: "find" }`, then pass them to `reticle_act` or `reticle_look { action: "element" }`. A ref re-resolves to its element across re-renders; if the element is gone, you get a clear error.
76
76
 
77
- **Evidence, not prose.** Every tool returns structured data (counts, the matching network call, the snapshot delta) so the agent reasons over facts, not a vibe.
77
+ **Evidence, not prose.** Every tool returns structured data (counts, the matching network call, the snapshot delta), so the agent reasons over facts.
78
78
 
79
- **Sessions.** Each connected browser tab is a session (named via `reticle.connect({ session })`). With one tab open you never specify it; with several, pass `sessionId`.
79
+ **Sessions.** Each connected browser tab is a session (named via `reticle.connect({ session })`). Omit `sessionId` unless you mean a specific tab. Reticle scopes to your project, prefers the active tab, and refuses when the choice is ambiguous.
80
80
 
81
81
  ---
82
82
 
@@ -84,14 +84,14 @@ Who benefits most: anyone shipping **dashboards, internal tools, SaaS apps**. Be
84
84
 
85
85
  ### `reticle_session { action: "list" }`
86
86
 
87
- List connected tabs. → `{ sessions: [{ sessionId, url, title, lastSeenMs, hidden, focused, throttled }] }`. `lastSeenMs` is the silence since the tab last reported (not time-since-connect); `throttled` is `true` when the tab is hidden **or** has merely gone quiet, and the two are not the same finding: a hidden tab clamps timers/rAF/pointer so an action can land on a page that never advances, while a quiet-but-visible tab is usually still driveable. The accompanying `recommendation` says which case you are in; treat `throttled` as a reason to read it, not as a reason to lease.
87
+ List connected tabs. → `{ sessions: [{ sessionId, url, title, lastSeenMs, hidden, focused, throttled, sdkVersion?, sdkBehind?, pendingMarks?, … }], next_step? }`. `sdkBehind` is present when the page's SDK is a minor release or more behind the daemon, and it names the update command. On a project that was never wired, this call changes no file: `next_step` names the zero-install lease, and the question to ask the user before you run `init`. `lastSeenMs` is the silence since the tab last reported (not time-since-connect); `throttled` is `true` when the tab is hidden **or** has merely gone quiet, and the two are not the same finding: a hidden tab clamps timers/rAF/pointer so an action can land on a page that never advances, while a quiet-but-visible tab is usually still driveable. The accompanying `recommendation` says which case you are in; treat `throttled` as a reason to read it, not as a reason to lease.
88
88
 
89
89
  ### `reticle_look { action: "page" }`
90
90
 
91
91
  A semantic, accessibility-tree view of the page.
92
92
 
93
- - **args:** `mode?: 'full' | 'interactive' | 'status'` (default `full`), `scope?` (CSS selector or ref), `diff?: boolean`, `sessionId?`.
94
- - **returns:** `{ tree, status: { route, title, visibleDialogs, overlayHidingPage }, nodes, truncated, cost: { bytes, tokens } }`.
93
+ - **args:** `mode?: 'full' | 'interactive' | 'status'` (default `full`), `scope?` (CSS selector or ref), `diff?: boolean`, `ids?: boolean` (add `[testid=…]` to each control that has one), `audit?: boolean` (also report text cut off with no ellipsis, controls drawn over each other, and text under WCAG contrast, as `audit: [{ kind, ref, what, detail }]`), `sessionId?`.
94
+ - **returns:** `{ tree, status: { route, title, visibleDialogs?, overlayHidingPage? }, nodes, truncated, cost: { bytes, tokens } }`.
95
95
  - **`diff: true`** returns only what changed since your last snapshot of the same scope/mode: `{ mode: 'delta', delta: { added, removed, addedCount, removedCount } }` or `{ mode: 'unchanged' }` (no full tree). The first call (and any call after a route change) still returns the full tree. ~99% fewer tokens to re-look after an action; see [token-efficiency.md](token-efficiency.md).
96
96
  - **`cost`** is an estimated size of the result. Re-scope (`mode`/`scope`) before reading if large.
97
97
 
@@ -99,7 +99,7 @@ A semantic, accessibility-tree view of the page.
99
99
  reticle_look({ action: "page", mode: "interactive" })
100
100
  // - tab "Overview" (ref=e2)
101
101
  // - button "Add item" (ref=e5)
102
- // status: { route: "/dashboard", visibleDialogs: [] }
102
+ // status: { route: "/dashboard", title: "Dashboard" } // visibleDialogs appears only when a dialog is open
103
103
 
104
104
  reticle_look({ action: "page", diff: true }) // after an action: only the change set
105
105
  // { mode: "delta", delta: { added: ['- alert "Saved!"'], removed: [], addedCount: 1, removedCount: 0 } }
@@ -109,7 +109,7 @@ reticle_look({ action: "page", diff: true }) // after an action: only the change
109
109
 
110
110
  Find elements (Testing-Library semantics).
111
111
 
112
- - **args:** `by: 'role'|'text'|'label'|'placeholder'|'testid'|'alt'`, `value`, `name?` (for role), `scope?`, `sessionId?`.
112
+ - **args:** `by: 'role'|'text'|'label'|'placeholder'|'testid'|'alt'|'component'`, `value`, `name?` (for role), `scope?`, `limit?`, `sessionId?`.
113
113
  - **returns:** `{ elements: [{ ref, role, name, value?, states, visible, text? }] }`.
114
114
 
115
115
  ```jsonc
@@ -129,7 +129,7 @@ Deep detail on one element, including the signals a snapshot/a11y tree omits, so
129
129
  Perform one action / several in order.
130
130
 
131
131
  - **`reticle_act` args:** `ref`, `action`, `args?`, `refuseWhenThrottled?`, `sessionId?`. → `{ since, dispatched, settled, settleReason, result, session, warning? }` where `result = { ok, ref, action, dispatched, settled, settleReason, effect }`. The `session` block `{ lastSeenMs, throttled, focused }` reports tab health on every act; when `throttled` is true a `warning` string is also attached. Pass `refuseWhenThrottled: true` to hard-fail instead of warning (opt-in; default is warn-only so background testing never breaks).
132
- - **`reticle_act { steps: [...] }` args:** `steps: [{ ref | target, action, args? }]`. Each step takes `ref` (from a snapshot/query) or `target` (`{ testid }` / `{ label }` / `{ role, name }` / `{ text }`). That is the same locator `reticle_act` accepts. → `{ since, dispatched, result }` where `result = { ok, count, effects: [...], steps: [...] }` (one `effect` per step; each step carries its own `dispatched`/`settled`/`settleReason`).
132
+ - **`reticle_act { steps: [...] }` args:** `steps: [{ ref | target, action, args?, expect? }]`. `expect` takes the same predicate as `reticle_act_and_wait` `until`. A step with `expect` proves its consequence. The call stops at the first `expect` that fails and returns `verified` and `because`. Each step takes `ref` (from a snapshot/query) or `target` (`{ testid }` / `{ label }` / `{ role, name }` / `{ text }`). That is the same locator `reticle_act` accepts. → `{ since, dispatched, result }` where `result = { ok, count, effects: [...], steps: [...] }` (one `effect` per step; each step carries its own `dispatched`/`settled`/`settleReason`).
133
133
  - See [§5](#5-actions-full-list) for the action list.
134
134
 
135
135
  **Dispatch vs settle.** (Settle means the page has stopped changing for now.) The action is two phases: the **dispatch** (the synchronous click/fill, which is what can fail) and the **settle** (waiting one animation frame so React's commit lands before we return). The settle is **bounded** (~200ms): in a throttled/background tab `requestAnimationFrame` never fires, so Reticle falls back to a timer and resolves anyway. A settle timeout is therefore **never an error**: `reticle_act` resolves with `settled:false, settleReason:"timeout"` and the dispatch (the click) has still landed. Only a real dispatch failure (stale ref, wrong element type) throws.
@@ -155,6 +155,11 @@ Perform one action / several in order.
155
155
  | `occluded` | `click`/`dblclick` only: the click point hit-tested to a _foreign_ element (an overlay is on top). Synthetic dispatch still delivered the event, but **a real user could not click it**, so treat the target as visually blocked. `false` when not click-like or not hit-testable |
156
156
  | `occludedBy` | the ref of the element actually on top at the click point when `occluded`, else `null` |
157
157
  | `scrolledIntoView` | `click`/`dblclick` only: the target was off-viewport, so Reticle scrolled it into view before dispatch |
158
+ | `occluderName` | when `occluded`: what covers the target, in words (its test id, else its role and name, else its tag). Omitted otherwise |
159
+ | `noSize` | click-like only: the target is laid out with no size, so a person has nothing to click. Omitted otherwise |
160
+ | `offViewport` | click-like only: the target is still outside the viewport after it was scrolled into view. Omitted otherwise |
161
+
162
+ A verdict drawn over a click with `occluded`, `noSize` or `offViewport` is `unknown` (`verifiedReason: "inconclusive"`), never `yes`: synthetic dispatch reached the target, so the consequence can hold, but no person could have made that click. `because` names the cause and the fix. A replayed flow step reads the same rule: the step drifts with `target_unreachable`, so the replay is `unknown`, never green.
158
163
 
159
164
  Use it to distinguish failure modes: `visible:false`/`enabled:false`/`targetMatched:false` → your action missed; the tool throwing → it never dispatched; `occluded:true` → the control is covered by something (a real user is blocked even though the synthetic event landed); `defaultPrevented:true` or all of `valueChanged:false`/`focusMoved:null`/`domMutatedWithin:0` → the app didn't react.
160
165
 
@@ -183,15 +188,15 @@ The timeline + summary of what happened.
183
188
 
184
189
  Act, then wait for a predicate: the whole act→observe→assert loop in one hop.
185
190
 
186
- - **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `intent?`, `durable?` (after a yes, reload and require `until`'s element/text/state parts to hold again), `sessionId?`.
187
- - **returns:** `{ verified, because, effect, verdict, trace, summary, honesty, since, session, … }`. **Gate on `verified`** (`yes` / `no` / `unknown` / `no-fault`; only `yes` is a pass) and read `because` for the deciding evidence. `effect` is the action result (`{ ok, ref, action }`), `verdict` is `{ pass, evidence?, failureReason? }` plus `verifiedReason`, the clause that decided it, `trace` is the reaction report of everything the app did after the action, and `session` is the tab-health block `{ lastSeenMs, throttled, focused }` (with a `warning` when throttled). A failing `verdict` still returns `effect` + `trace` so you can see what _did_ happen. The predicate is automatically floored at this act's cursor, so it only matches events the action actually caused.
191
+ - **args:** `ref` or `target` (`{ testid }` / `{ label }` / `{ role, name }` / `{ text }`), `action`, `args?`, `until?: <predicate>` (omit it to wait only for the page to settle), `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `intent?`, `durable?` (after a yes, reload and require `until`'s element/text/state parts to hold again), `sessionId?`.
192
+ - **returns:** `{ verified, because, effect, verdict, trace, summary, honesty, engineBlindSpots?, since, session, … }`. `engineBlindSpots` names what no channel could see in this run: `pixels-unobserved` (a `<canvas>` was on screen) and `claim-arithmetic-unchecked` (the claim states an amount and no verdict ran a `compare`). **Gate on `verified`** (`yes` / `no` / `unknown` / `no-fault`; only `yes` is a pass) and read `because` for the deciding evidence. `effect` is the action result (`{ ok, ref, action }`), `verdict` is `{ pass, evidence?, failureReason? }` plus `verifiedReason`, the clause that decided it, `trace` is the reaction report of everything the app did after the action, and `session` is the tab-health block `{ lastSeenMs, throttled, focused }` (with a `warning` when throttled). A failing `verdict` still returns `effect` + `trace` so you can see what _did_ happen. The predicate is automatically floored at this act's cursor, so it only matches events the action actually caused.
188
193
 
189
194
  ### `reticle_assert { action: "wait" }`
190
195
 
191
- Block until a predicate holds (or time out). Looks both backward (recent buffer) and forward. It returns `pass` and evidence with no `verified`: a wait is not a verdict. Put the consequence on `reticle_act_and_wait` or `reticle_assert` to get one.
196
+ Block until a predicate holds, or time out. It looks back at the recent buffer and forward. It returns `verified`, `because` and `pass`, graded as `reticle_assert` grades them. Gate on `verified`. One call waits for a limited time. When it stops before the predicate holds, it returns `resume_ms`: call again with `timeout_ms: resume_ms` and the same `since` to keep waiting. A wait is not saved to the journal. Use it to let the page settle, and put the proof on `reticle_act_and_wait` or `reticle_assert`.
192
197
 
193
- - **args:** `predicate`, `timeout_ms?` (default 4000), `since?`, `sessionId?`.
194
- - **No stale-signal false passes.** By default the evaluation window is floored at your **last act's cursor**, so a signal/network/console/animation event buffered _before_ the action can never satisfy the predicate (the report's "validation 68 == 68 was a lie" footgun). Pass an explicit `since` (an act/observe cursor) to widen or narrow the window deliberately. Element/text predicates query the live DOM and are unaffected by `since`.
198
+ - **args:** `predicate`, `timeout_ms?` (default 4000, up to 600000), `since?`, `sessionId?`.
199
+ - **No stale-signal false passes.** By default the evaluation window is floored at your **last act's cursor**, so a signal, network, console or animation event buffered _before_ the action can never satisfy the predicate. Pass an explicit `since` (an act/observe cursor) to widen or narrow the window deliberately. Element/text predicates query the live DOM and are unaffected by `since`.
195
200
 
196
201
  ### `reticle_assert`
197
202
 
@@ -199,7 +204,7 @@ Verify a predicate; optionally wait for it.
199
204
 
200
205
  - **args:** `predicate`, `timeout_ms?` (0 = evaluate once), `since?`, `intent?`, `sessionId?`.
201
206
  - Same `since` default as `reticle_assert { action: "wait" }`: scoped to your last act so a stale buffered event can't fake a pass; override with an explicit `since`.
202
- - **returns:** `{ verified, because, pass, evidence, contradictions?, coverage?, failureReason?, session, warning? }`. On failure includes a **near-miss** (e.g. "found the dialog but not visible", or "no button named 'Submit'; saw: Cancel"). The `session` block `{ lastSeenMs, throttled, focused }` reports tab health on every assert; when throttled a `warning` is attached so you never assert against a tab that is silently no-oping.
207
+ - **returns:** `{ verified, because, pass, evidence, contradictions?, coverage?, engineBlindSpots?, failureReason?, session, warning? }`. On failure includes a **near-miss** (e.g. "found the dialog but not visible", or "no button named 'Submit'; saw: Cancel"). The `session` block `{ lastSeenMs, throttled, focused }` reports tab health on every assert; when throttled a `warning` is attached so you never assert against a tab that is silently no-oping.
203
208
  - **`intent` declares what the change was FOR, inline.** Pass a sentence in your own words and it lands in `.reticle/intent/` (one directory per subject, a flow's name being its subject), the same git-checked ledger `reticle_intent` writes, before the verdict is drawn; a green verdict then marks it proved and names itself as the proof. Pass the **id** of an intent you already declared instead, to point several verdicts at one statement rather than restating it. `reticle_act_and_wait` takes the same argument. Omit it and nothing is written.
204
209
  - **Read `verified`, not `pass`.** `pass` says the predicate held; `verified` says whether that means anything. It is `"no"` when a channel contradicts the assertion (a failed write under a green screen, a batch whose body reports per-item failures, a request still in flight), and `"unknown"` when the outcome could not be known yet: a `202 Accepted` that has not reconciled, or a write whose response body was never recorded. `because` names the deciding evidence in one sentence.
205
210
 
@@ -207,8 +212,9 @@ Verify a predicate; optionally wait for it.
207
212
 
208
213
  Compare what the API **returned** against what the page **renders**.
209
214
 
210
- - **args:** `since?`, `urlContains?`, `sessionId?`.
211
- - **returns:** `{ mismatches, compared, note? }`.
215
+ - **args:** `since?`, `urlContains?`, `scope?`, `witness?`, `sessionId?`.
216
+ - **returns:** `{ mismatches, compared, note?, witness? }`.
217
+ - **`scope`** is a ref or CSS selector, for example `"#status"`. The tool then compares only against that subtree. Use it when the whole page is too large to read completely, for example a dashboard of large native selects, and the API feeds one region. Every cut branch under the scope is still read. A scope that matches no element is refused, never read as an empty page.
212
218
  - Catches the class no status code and no assertion can reach: a `USD 7997` amount rendered as `₹79.97`, or a record the API calls `on_hold` displayed as `"pending"`. Both sides agree on the digits; only the meaning differs, so every other channel reports success.
213
219
  - Needs response bodies: `connect({ captureNetworkBodies: true })`. When nothing could be compared it says so in `note` rather than returning an empty, clean-looking result over data it never read.
214
220
 
@@ -304,7 +310,7 @@ The dev clicks **"Flag a bug"** in the running app, points at the element that l
304
310
  reticle_session({ action: "review", sessionId })
305
311
  → { marks: [{ id: "m1", note: "this button is misaligned", label: "button \"Pay\"",
306
312
  source: { file: "src/Checkout.tsx", line: 42 },
307
- fix: "Open src/Checkout.tsx:42 and fix: this button is misaligned. Then reticle_session { action: "review" } { resolve: \"m1\" }" }],
313
+ fix: "Open src/Checkout.tsx:42 and fix: this button is misaligned. Then call reticle_session { action: \"review\", resolve: \"m1\" }." }],
308
314
  pendingCount: 1 }
309
315
  ```
310
316
 
@@ -325,24 +331,24 @@ First matching rule wins (`urlContains` + optional case-insensitive `method`). N
325
331
 
326
332
  ### `reticle_viewport`: reproducible visual baselines (driven mode)
327
333
 
328
- Pin the driven page to a fixed viewport so a screenshot baseline is reproducible across machines:
334
+ Pin the driven page, or a leased tab named by `sessionId`, to a fixed viewport so a screenshot baseline is the same on every machine:
329
335
 
330
336
  ```
331
337
  reticle_run({ tool: "reticle_viewport", args: { width: 1280, height: 800 } }) // set once, before reticle_screenshot / reticle_visual_diff
332
- → { applied: true, width: 1280, height: 800 }
338
+ → { applied: true, width: 1280, height: 800 } // on a lease also `observed`, the size the page reports; `applied` is false when it does not match
333
339
  ```
334
340
 
335
341
  This is one of three knobs for **CI-stable visual regression**. Set them together:
336
342
 
337
343
  1. **`reticle_run({ tool: "reticle_viewport", args: { width, height } })`** gives the same dimensions on every machine.
338
- 2. **`reticle_run({ tool: "reticle_clock", args: { freeze: true } })`** kills animation/time jitter so the pixels are stable.
344
+ 2. **`reticle_run({ tool: "reticle_clock", args: { freeze: true } })`** stops timer and date jitter (clocks, countdowns). For animations, also pass `reducedMotion: "reduce"` to `reticle_viewport`.
339
345
  3. **`reticle_run({ tool: "reticle_visual_diff", args: { baseline, masks: [{ x, y, width, height }] } })`** neutralizes volatile regions (clocks, avatars, ids) so only real changes fail.
340
346
 
341
347
  ---
342
348
 
343
- ## 4. The predicate DSL: full reference
349
+ ## 4. The predicate DSL
344
350
 
345
- A **predicate** declares what should be true. `reticle_assert` / `reticle_assert { action: "wait" }` evaluate it against the live DOM + the event buffer.
351
+ A **predicate** declares what should be true. `reticle_assert` / `reticle_assert { action: "wait" }` evaluate it against the live DOM and the event buffer. This section shows the common kinds. [Predicates](/predicates) is the full reference, including `settled`, `compare`, and `count` on `net` and `signal` (exact; `count: 0` means never).
346
352
 
347
353
  ### Leaf predicates
348
354
 
@@ -381,7 +387,7 @@ A **predicate** declares what should be true. `reticle_assert` / `reticle_assert
381
387
  { "kind": "state", "store": "app", "path": "deployments.0.status", "equals": "live" }
382
388
  ```
383
389
 
384
- `visible` excludes elements whose boxes are fully clipped by an ancestor's `overflow:hidden` or `overflow:clip`; a partly clipped element stays visible. Expanding a clamped card can therefore prove that its previously hidden paragraph became visible. `overflow:auto` and `overflow:scroll` keep their existing visibility semantics, including content beyond the scrollport. Ordinary document content outside the window remains visible; `inViewport` checks the window intersection after any hidden/clip ancestor clipping, without introducing scrollport clipping.
390
+ `visible` excludes elements whose boxes are fully clipped by an ancestor's `overflow:hidden` or `overflow:clip`; a partly clipped element stays visible. It also excludes screen-reader-only text: a positioned box with a `clip: rect()` of no area, a `clip-path: inset()` whose edges meet, or a 1×1 box that clips its own overflow. That text stays `present` and still names its control. Expanding a clamped card can therefore prove that its previously hidden paragraph became visible. `overflow:auto` and `overflow:scroll` keep their existing visibility semantics, including content beyond the scrollport. Ordinary document content outside the window remains visible; `inViewport` checks the window intersection after any hidden/clip ancestor clipping, without introducing scrollport clipping.
385
391
 
386
392
  A `state` assertion is graded as a **consequence** (a wrong element or stale render cannot fake it), and is usable the same three ways anywhere predicates flow: ad-hoc (`reticle_assert` / `reticle_act_and_wait` `until`), as a flow step invariant (`reticle_annotate { kind: "assert-state", statePath, store?, equals? }`), and as a flow's golden end-condition (`reticle_annotate { kind: "success-state", statePath, … }`). On a miss it names the real store value and the keys that were available: legible, not a blind fail.
387
393
 
@@ -408,7 +414,7 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
408
414
 
409
415
  | action | args | notes |
410
416
  | --- | --- | --- |
411
- | `click` / `dblclick` | n/a | dispatches a real click |
417
+ | `click` / `dblclick` | `{ native? }` | dispatches the full pointer and mouse sequence on the element; `native: true` forces a trusted click through CDP |
412
418
  | `hover` | n/a | `mouseover`+`mouseenter` (triggers JS hover state) |
413
419
  | `focus` / `blur` | n/a | |
414
420
  | `fill` | `{ value }` | sets value via React-safe native setter + `input`/`change` |
@@ -431,7 +437,7 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
431
437
 
432
438
  `reticle_look { action: "page" }` has three modes. Pick the cheapest that answers your question:
433
439
 
434
- - **`status`** (~30 tokens): route, visible dialogs, counters. "Where am I, is a modal open?"
440
+ - **`status`** (~30 tokens): the route, the title, and the open dialogs if there are any. "Where am I, is a modal open?"
435
441
  - **`interactive`** (~100 tokens): only actionable elements (buttons, inputs, tabs…). "What can I click?" Non-interactive content (e.g. 1,000 list rows) is skipped.
436
442
  - **`full`**: the whole semantic tree. Use only when you truly need everything.
437
443
 
@@ -496,7 +502,7 @@ reticle_assert({ timeout_ms: 3000, predicate: { kind: "element",
496
502
  query: { text: "Invoice #4821", scope: "[data-testid=item-list]" }, state: "visible" } })
497
503
  ```
498
504
 
499
- > Note: if your list is **virtualized** (react-window/virtuoso), an off-screen row is not in the DOM at all, so `reticle_look { action: "find" }` correctly finds nothing. Use **`reticle_scroll_to`** to scroll the windowed container until the row renders, then query it: `reticle_run({ tool: "reticle_scroll_to", args: { by: "text", value: "Invoice #4821", container: "[data-testid=item-list]" } })`. Asserting against the data with `reticle.signal` or `reticle_look { action: "state" }` also works and is cheaper.
505
+ > Note: if your list is **virtualized** (react-window/virtuoso), an off-screen row is not in the DOM at all, so `reticle_look { action: "find" }` correctly finds nothing. Use **`reticle_scroll_to`** to scroll the windowed container until the row renders. First get the container's ref, for example with `reticle_look({ action: "find", by: "testid", value: "item-list" })`, then pass that ref: `reticle_run({ tool: "reticle_scroll_to", args: { by: "text", value: "Invoice #4821", container: "e12" } })`. `container` takes a ref, not a CSS selector. Asserting against the data with `reticle.signal` or `reticle_look { action: "state" }` also works and is cheaper.
500
506
 
501
507
  ### "Login form: does it actually authorize?"
502
508
 
@@ -552,7 +558,7 @@ const after = reticle_look({ action: "element", ref }).styles.backgroundColor
552
558
  // assert before !== after
553
559
  ```
554
560
 
555
- > Pure CSS `:hover` styling needs a real pointer; drive hover effects from JS state (or use a Playwright real-hover) if you need pixel-exact `:hover`. Reticle reads computed style after the JS state change.
561
+ > Pure CSS `:hover` styling needs a real pointer. Enable real input ([§18](#18-real-input-mode-native-hover--drag)) or use a page from `reticle drive`, and `hover` then moves the real pointer. Without it, `hover` fires the JS events only, and Reticle reads the computed style after the JS state change.
556
562
 
557
563
  ### "Something off-DOM happened: a webhook arrived, a store changed"
558
564
 
@@ -627,7 +633,7 @@ The semantic `reticle_run { tool: "reticle_baseline", args: { action: "diff" } }
627
633
 
628
634
  ```jsonc
629
635
  reticle_run({ tool: "reticle_viewport", args: { width: 1280, height: 800 } }) // 1. same size on every machine
630
- reticle_run({ tool: "reticle_clock", args: { freeze: true } }) // 2. no animation/time jitter
636
+ reticle_run({ tool: "reticle_clock", args: { freeze: true } }) // 2. no timer or date jitter (pass reducedMotion: "reduce" to reticle_viewport for animations)
631
637
  reticle_run({ tool: "reticle_screenshot", args: { name: "checkout-ok" } }) // capture the baseline
632
638
  // …later, after a change, at the same viewport + frozen clock:
633
639
  reticle_run({ tool: "reticle_visual_diff", args: { baseline: "checkout-ok", masks: [{ x: 0, y: 0, width: 200, height: 24 }] } })
@@ -649,9 +655,11 @@ reticle_run({ tool: "reticle_record", args: { action: "stop", recordingName: "ch
649
655
  // → {
650
656
  // recordingName,
651
657
  // program: { version, steps: [{ tool, args: { by:"testid", value, action, args }, stable }] },
652
- // events: [...ordered timeline...],
653
- // summary: { network, domAdded, … },
654
- // warning? // present when some steps could not be bound to a testid
658
+ // window_ms, summary: { total, network?, domAdded?, … }, // zero counters are left out
659
+ // timeline_omitted?, // the raw events are not included; it names the reticle_observe { since } call that reads them
660
+ // proposedConsequences?, // ranked checks you can keep as the flow's success condition
661
+ // warning?, // a step has no testid, or the recording crossed a reload or left the page
662
+ // cost
655
663
  // }
656
664
  ```
657
665
 
@@ -697,7 +705,7 @@ A practical workflow:
697
705
 
698
706
  > "Here are our 12 dashboard test cases. For each, drive the app with Reticle and tell me pass/fail with evidence. For any failure, show the source file to fix."
699
707
 
700
- This is the sweet spot: the **manual cases you never automated** become things the agent runs in seconds, on every change. It **complements** your CI Playwright/Cypress suite (which gates releases); Reticle is the in-loop checklist while you build.
708
+ This is where Reticle helps most: the agent runs the **manual cases you never automated** in seconds, on every change. It **complements** your CI Playwright/Cypress suite (which gates releases); Reticle is the in-loop checklist while you build.
701
709
 
702
710
  ---
703
711
 
@@ -729,7 +737,7 @@ Reticle is cheap by design ([benchmark](token-efficiency.md)), but keep it that
729
737
 
730
738
  ### Does this run in production?
731
739
 
732
- No. Keep `reticle.connect()` behind a dev guard. The SDK is side-effect-free and tree-shakes out of prod builds.
740
+ Not by default. Keep `reticle.connect()` behind a development guard. The SDK tree-shakes out of production builds. To let an agent drive a staging or production site, install the production-mode loader. It loads nothing until a signed activation ticket is in the URL. See [Production mode](/production-mode).
733
741
 
734
742
  ### Do I have to change my components?
735
743
 
@@ -753,14 +761,14 @@ Those let an agent drive/ inspect a _separate_ browser; Reticle verifies your _o
753
761
 
754
762
  ### Multiple tabs/apps?
755
763
 
756
- Each is a session; pass `sessionId` to any tool when more than one is connected (`reticle_session { action: "list" }` lists them).
764
+ Each tab is a session. Omit `sessionId`, and Reticle picks the active tab of your project. It refuses when the choice is ambiguous. Then pass a `sessionId` from `reticle_session { action: "list" }`.
757
765
 
758
766
  ---
759
767
 
760
768
  ## 15. Security & privacy
761
769
 
762
770
  - **Dev-only, localhost-only by default.** The bridge binds `127.0.0.1`; the SDK is meant for dev builds.
763
- - **No app data leaves your machine until you link a project.** Nothing from your app leaves the machine until you link a project (`reticle connect`, or `RETICLE_API_KEY` in CI). After that, what syncs is set by `reticle config`, and when the Harness (Reticle's model-driven explorer, which runs on the Reticle platform) drives, the platform sees the steps it drives. Secret values never leave. Baselines/recordings are local. The CLI sends anonymous, opt-out usage metrics only (random id + event names, no code and no PII; see [telemetry](telemetry.md)); opt out with `reticle telemetry disable`, `RETICLE_TELEMETRY=0`, or `DO_NOT_TRACK=1`. Feedback you or your agent deliberately send (`reticle feedback` / `reticle_session { action: "feedback" }`) is the only free text that ever leaves the machine: never passive, redacted first, and separately disabled with `RETICLE_FEEDBACK=0`.
771
+ - **No app data leaves your machine until you link a project.** Nothing from your app leaves the machine until you link a project (`reticle connect`, or `RETICLE_API_KEY` in CI). After that, what syncs is set by `reticle config`, and when the Harness (Reticle's model-driven explorer, which runs on the Reticle platform) drives, the platform sees the steps it drives. Secret values never leave. Baselines/recordings are local. The CLI sends opt-out usage metrics: a random install id, event names and counts, and no code, no name and no IP address. Two fields can connect events to an organization: the workspace id of a linked project, and the licence id of a licensed build (see [telemetry](telemetry.md)). Opt out with `reticle telemetry disable`, `RETICLE_TELEMETRY=0` or `DO_NOT_TRACK=1`. Feedback that you or your agent send on purpose (`reticle feedback` / `reticle_session { action: "feedback" }`) is the only free text that leaves the machine. It is never sent passively, it is redacted first, and `RETICLE_FEEDBACK=0` turns it off. A reply address goes with it only when you give one with `--contact`.
764
772
  - **Network bodies aren't captured by default:** only method/url/status/timing. Body capture is opt-in and runs through a redactor (drop `password`/`token`/`secret`/… + your patterns).
765
773
  - **Additive & reversible.** Reticle patches `fetch`/History/console defensively and restores them on disconnect; it will not break the app under test.
766
774
 
@@ -815,9 +823,9 @@ All presenter DOM uses `data-reticle-*` and is excluded from snapshots/observers
815
823
 
816
824
  #### Session liveness: the HUD never gets stuck "running"
817
825
 
818
- A session starts on the agent's first activity and must reliably end even when the agent misbehaves. Reticle is an MCP tool, so the agent (Claude) can crash, disconnect, or simply forget to call `reticle_session { action: "end" }`, and a backgrounded tab's own timers are throttled by the browser. So **the Node server owns liveness, not the browser tab:**
826
+ A session starts on the agent's first activity and must reliably end even when the agent misbehaves. Reticle is an MCP tool, so the agent can crash, disconnect, or forget to hand control back. The agent must call `reticle_session { action: "yield" }` before it stops. Also, the browser throttles the timers of a background tab. So **the Node server owns liveness, not the browser tab:**
819
827
 
820
- - **Agent goes idle / forgets to end** → a server-side reaper (immune to tab throttling) ends the session after `idleEndMs` of no agent commands and pushes the end to the browser. A backgrounded tab still receives that push, so you can switch windows and come back to a correctly-ended HUD.
828
+ - **Agent goes idle or forgets to yield** → a server-side timer (not affected by tab throttling) ends the session after `idleEndMs` (default 5 minutes) with no agent command. It shows the human that the agent is WAITING (your turn), and pushes that to the browser. A backgrounded tab still receives that push, so you can switch windows and come back to a correctly-ended HUD.
821
829
  - **Agent (MCP client) disconnects cleanly** → every active session ends at once.
822
830
  - **Agent kills the Reticle server process** (so no push can arrive) → the SDK self-ends the session after it can't reach the bridge for `BRIDGE_LOST_MS` (~15s), showing "lost connection to Reticle."
823
831
  - **Slow-but-alive agent** → if it goes quiet long enough to auto-end and then acts again, the session **revives** automatically (an explicit `reticle_session { action: "end" }` stays terminal).
@@ -911,6 +919,7 @@ registerStore('workspace', useWorkspace); // pass the store itself → auto STAT
911
919
 
912
920
  ```ts
913
921
  import {
922
+ registerStore,
914
923
  tanstackQueryStore,
915
924
  jotaiStore,
916
925
  xstateStore,
@@ -939,7 +948,10 @@ A Svelte store has **no pull side at all**: `{ subscribe }` is the entire contra
939
948
  **Recoil** has no enumerable registry of live atoms and no per-atom subscription outside React, so it takes an atom map (like Jotai) plus the transaction stream from a small bridge component:
940
949
 
941
950
  ```tsx
951
+ import { useEffect, useRef } from 'react';
942
952
  import { snapshot_UNSTABLE, useRecoilTransactionObserver_UNSTABLE } from 'recoil';
953
+ import { registerStore, recoilStore } from '@reticlehq/browser';
954
+ import { cartAtom, userAtom } from './atoms';
943
955
 
944
956
  function ReticleRecoilBridge() {
945
957
  const latest = useRef(snapshot_UNSTABLE());
@@ -967,7 +979,7 @@ function ReticleRecoilBridge() {
967
979
 
968
980
  Each atom comes back as `{ status, value, error }` rather than a bare value, because Recoil atoms can be async: calling `getValue()` on a pending selector **throws the pending promise**, which would lose the whole state read over one slow atom. A loading atom reports `status: 'loading'` instead of silently reading as empty.
969
981
 
970
- **TanStack Query is worth registering even if you register nothing else.** Its cache holds the state most likely to be wrong in a way nothing else can observe: a stale value served as fresh, a mutation that never invalidated its query, an optimistic update never rolled back. None of those fire a network request, so a network log shows silence and the DOM shows a plausible number. The cache is the only witness. The adapter exposes `status`, `fetchStatus`, `isStale` and `dataUpdatedAt` per query key, so an agent can assert the stronger property: not "the number rendered is 42" but "the number rendered came from fresh data".
982
+ **TanStack Query is worth registering even if you register nothing else.** Its cache holds the state most likely to be wrong in a way nothing else can observe: a stale value served as fresh, a mutation that never invalidated its query, an optimistic update never rolled back. None of those fire a network request, so a network log shows silence and the DOM shows a plausible number. The cache is the only witness. The adapter exposes `status`, `fetchStatus`, `isStale`, `isInvalidated` and `dataUpdatedAt` per query key (`isInvalidated: true` after `invalidateQueries` marks the query), so an agent can assert the stronger property: not "the number rendered is 42" but "the number rendered came from fresh data".
971
983
 
972
984
  **React Context / `useState` / `useReducer`** have no store object to adapt: the value lives in the fiber tree and the only subscription is a re-render. Invert it with the hook:
973
985
 
@@ -1041,7 +1053,7 @@ reticle_run({ tool: "reticle_capabilities", args: {} }) // → { testids, sign
1041
1053
 
1042
1054
  Reticle drives actions by dispatching JS events from inside the page. That covers click, fill, type, select, submit, press, and HTML5 drag, but it **cannot** trigger browser-native pointer behavior: `onMouseEnter`/`onMouseLeave`, hover-gated reveals, and pointer-library drags rely on the browser's real hit-testing, which synthetic events don't drive.
1043
1055
 
1044
- **Clicks are synthetic by default, on purpose.** Even with real input configured, `click`/`dblclick` run the occlusion-honest synthetic path (full `pointerdown→…→click` sequence + a `occluded` hit-test + off-viewport auto-scroll), reporting `inputModeReason:"synthetic-click-preferred"`. There's no coordinate gesture for the presenter HUD to intercept or to miss off-screen, and synthetic dispatch reaches the resolved element directly. Reserve native clicks for the rare `isTrusted`-gated case (native file picker, clipboard) with `args:{ native:true }`. Real input remains the path for `hover`/`drag`, which genuinely need the browser's hit-testing. Every `reticle_act` result tells you which path ran:
1056
+ **Clicks are synthetic by default, on purpose.** Even with real input configured, `click`/`dblclick` run the occlusion-honest synthetic path (full `pointerdown→…→click` sequence + an `occluded` hit-test + off-viewport auto-scroll), reporting `inputModeReason:"synthetic-click-preferred"`. There's no coordinate gesture for the presenter HUD to intercept or to miss off-screen, and synthetic dispatch reaches the resolved element directly. Reserve native clicks for the rare `isTrusted`-gated case (native file picker, clipboard) with `args:{ native:true }`. Real input remains the path for `hover`/`drag`, which genuinely need the browser's hit-testing. Every `reticle_act` result tells you which path ran:
1045
1057
 
1046
1058
  ```jsonc
1047
1059
  { since, dispatched, settled, inputMode: "synthetic" | "real", inputModeReason?, result, session, warning? }
@@ -1062,8 +1074,11 @@ When `inputMode` is `"synthetic"` and the target has hover/enter handlers, the r
1062
1074
  | `synthetic-element-press-preferred` | the `press` named a `ref`/`target`, so it stayed synthetic on purpose: dispatching AT that element reaches it exactly, while a real keyboard sends the key to whatever holds focus. A `press` with NO ref is a document key (Escape, Tab, a modifier shortcut) and does run through the real keyboard when one is configured |
1063
1075
  | `synthetic-multi-key-press-preferred` | the `press` named `keys`: several keys held down **together**, pressed in order and released in reverse. Only the in-page dispatcher implements that sequence; a real keyboard chord cannot express it |
1064
1076
  | `synthetic-key-code-press-preferred` | the `press` named an explicit `code`, which the real driver cannot send: it presses by KEY name, so `{ text: "z", code: "KeyY" }` would strike a different key than the one asked for |
1077
+ | `real-input-not-configured-for-this-session` | you passed `args:{ native:true }`, but no real-input provider is configured (no `RETICLE_CDP_URL`, no driven or leased browser). Stop asking for native input in this session |
1078
+ | `target-cannot-hold-focus` | on a lease, the `press` named an element that did not take focus, so the key stayed in the page |
1079
+ | `production-drive-presses-in-page` | a production drive runs every `press` in the page, so the production policy can judge the control that the key reaches |
1065
1080
 
1066
- (No `inputModeReason` is set when real input simply isn't configured; synthetic is the expected default there.)
1081
+ (When real input is not configured, an act sets no `inputModeReason`, because synthetic is the default. The exception is an explicit `native: true`, which gets `real-input-not-configured-for-this-session`.)
1067
1082
 
1068
1083
  ### Enable real input (optional, opt-in)
1069
1084
 
@@ -1093,7 +1108,7 @@ Point Reticle's server at a Chrome DevTools (CDP) endpoint; it then drives **rea
1093
1108
 
1094
1109
  That's it. Reticle correlates the CDP page to your SDK session by URL; pointer actions now fire native hover/enter so hover-gated suggestion panels, tooltips, and pointer-based drag become drivable. Everything else is unchanged, and with no `RETICLE_CDP_URL` set, Reticle stays in the synthetic (zero-dependency, in-page) mode. Playwright is an optional dependency loaded only when you opt in.
1095
1110
 
1096
- > **SPA navigation is handled.** The URL correlation tracks client-side route changes (`pushState`/`replaceState`/`popstate`), so real input keeps working after your app navigates into a sub-route, e.g. the hover/quick-edit cluster on a `/workspace` view stays drivable. (If you see `inputModeReason:"page-not-correlated-to-a-cdp-target"`, the reported session URL isn't correlated to a CDP target and real input silently falls back to synthetic.)
1111
+ > **SPA navigation is handled.** The URL correlation tracks client-side route changes (`pushState`/`replaceState`/`popstate`), so real input keeps working after your app navigates into a sub-route, e.g. the hover/quick-edit cluster on a `/workspace` view stays drivable. (If you see `inputModeReason:"page-not-correlated-to-a-cdp-target"`, no CDP target matches the session URL, and the act ran synthetic. The field tells you so.)
1097
1112
 
1098
1113
  > **Watching the agent (presenter).** With `present: true` the activity border now glows once while the agent is busy and fades when idle (no per-action strobe); the HUD docks **bottom-right** by default, shows a **READING** vs **ACTING** chip so you can tell observation from action at a glance, and `reticle_session { action: "narrate" }` lines are **queued** with a minimum on-screen dwell so none flash by unread.
1099
1114
 
@@ -91,7 +91,7 @@ Open the "Delete project" dialog with `press` (`args: { "text": "Enter" }`) and
91
91
  - **Page performance entries.** The SDK records largest-contentful-paint, a running layout-shift total and long tasks as `perf` events, which you read with `reticle_observe { filters: ["perf"] }`. The layout-shift figure is a running sum without the windowing a Core Web Vitals CLS score uses, so it works for "no layout shift on load" and trends, not as an exact CWV number.
92
92
  - **Request timing.** Every captured request carries its duration in `reticle_observe { action: "network" }`.
93
93
  - **React render rate.** With the React adapter, the commit count is readable as the `__reticle_renders` store, so a component that re-renders continuously while the screen looks idle shows up.
94
- - **Recurring re-verification.** `npx @reticlehq/server verify <url>` replays your saved flows against a dev server or a staging/preview deployment and exits non-zero on a failure, so it runs in CI or on a schedule. The SDK refuses to connect in a production build, so this covers environments you control, not live production traffic.
94
+ - **Recurring re-verification.** `npx @reticlehq/server verify <url>` replays your saved flows against a dev server or a staging/preview deployment and exits non-zero on a failure, so it runs in CI or on a schedule. By default the SDK does not connect in a production build, so this covers environments you control. To drive a live site, see [Production mode](/production-mode).
95
95
 
96
96
  Type "shoes" into a debounced search box and require exactly one request:
97
97
 
@@ -122,7 +122,7 @@ After navigating to the old `/plans` URL, require the redirect and the page's ma
122
122
 
123
123
  ## Personas and simulation
124
124
 
125
- - **Drive the app as a user.** `explore` takes a persona in plain words, drives that whole journey with the Harness, Reticle's model-driven explorer, which runs on the Reticle platform (run `reticle connect` first; a new account gets 10 free credits), and saves what it drove as a flow that later replays with no model.
125
+ - **Drive the app as a user.** `explore` takes a persona in plain words, drives that whole journey with the Harness, Reticle's model-driven explorer, which runs on the Reticle platform (run `reticle connect` first; a new account gets free credits), and saves what it drove as a flow that later replays with no model.
126
126
  - **One context per role.** `reticle_run { tool: "reticle_lease", args: { action: "acquire" } }` opens an isolated browser context (its own cookies, storage and DOM) and can seed storage or cookies first, so an admin and a viewer drive the same app side by side. Reach it with `reticle_run`, or use `reticle verify <url> --storage-state <file>` from a terminal.
127
127
  - **Reachability testing.** Signed in as a role, a control that role must not have is absent, and the call behind it never fires. `reticle_verify { action: "coverage" }` lists the controls you have and have not driven this session.
128
128
  - **Multi-agent runs.** Several agents lease contexts from one shared headless Chromium, capped and queued, instead of starting a browser each. See [Multi-agent](/multi-agent-testing).
@@ -40,7 +40,7 @@ Note the flip. DevTools is cheaper on a small page and more expensive on a large
40
40
 
41
41
  - **Cheaper on simple pages**, as above.
42
42
  - **It is Chrome's own tooling**, which means the performance and tracing surface is deep and authoritative in a way third-party instrumentation is not.
43
- - **Nothing to install in your app.** No SDK, no plugin, no build-time step. If instrumenting is off the table, this matters a lot.
43
+ - **Nothing to install in your app, at full depth.** Reticle can also check a page with nothing installed, through a leased tab, but its state, store and source-file reads need the SDK. DevTools MCP gives its full surface with no change to the app.
44
44
  - **Real pixels.** Actual rendered frames, with everything that implies.
45
45
 
46
46
  ## The two bugs
@@ -111,10 +111,10 @@ Neither tool forbids the other. A reasonable setup:
111
111
  </Accordion>
112
112
 
113
113
  <Accordion title="Do I have to instrument my app to use Reticle?">
114
- You have to add the dev-only SDK, which is what `npx @reticlehq/server init` does. Beyond that,
115
- look, act and observe work with zero component changes. Signals and registered stores are
116
- optional, and they are what buys the assertions DevTools MCP structurally cannot make. If
117
- instrumenting is off the table entirely, DevTools MCP is the correct choice.
114
+ No. With nothing installed, a leased tab checks the running app: the DOM, network, console and
115
+ routes. `npx @reticlehq/server init` adds the dev-only SDK, which adds component state, registered
116
+ stores and the source `file:line`. Signals and registered stores are optional, and they are what
117
+ buys the assertions DevTools MCP structurally cannot make.
118
118
  </Accordion>
119
119
 
120
120
  <Accordion title="Does Reticle give me performance traces?">
@@ -41,7 +41,7 @@ Reticle is about 2.1x leaner on a large page. The gap comes from what each tool
41
41
 
42
42
  This is not a courtesy section. There are real cases where Playwright is the correct choice and Reticle is not.
43
43
 
44
- - **Sites you do not own.** Reticle needs a dev-only SDK in the app. No SDK, no Reticle. Playwright drives anything with a URL, which is the entire point of it.
44
+ - **Sites you do not own.** Reticle is built for an app you own. A leased tab can read the DOM, network, console and routes of any page with nothing installed, but component state, store diffs and the source `file:line` need the SDK in your build. Playwright drives anything with a URL, and that is the entire point of it.
45
45
  - **Cross-browser.** Firefox and WebKit are first-class in Playwright. Reticle's real-input and screenshot paths are Chromium-oriented.
46
46
  - **True pixels.** A screenshot is the actual rendered frame. A font that failed to load or a compositing glitch shows up there and can be missed by structural reads.
47
47
  - **Maturity and ecosystem.** Playwright is a large, battle-tested project with an enormous community. We are not going to pretend otherwise.
@@ -98,9 +98,10 @@ Reticle also has a driven mode (`reticle drive`) that provides real CDP input an
98
98
  </Accordion>
99
99
 
100
100
  <Accordion title="Does Reticle work on a site I do not own?">
101
- No. Reticle reads the program from inside the page, which requires a dev-only SDK in the app. No
102
- SDK, no Reticle. That is the single hardest boundary between these two tools, and it is why
103
- Playwright MCP is the right answer for anything you cannot instrument.
101
+ Partly. A leased tab gets an injected SDK, so Reticle can read the DOM, network, console and
102
+ routes of a page with nothing installed. It cannot read component state or registered stores, and
103
+ it cannot map a node to its source file, because those come from the SDK and the build plugin in
104
+ your own app. For a site you cannot instrument, Playwright MCP is still the stronger tool.
104
105
  </Accordion>
105
106
 
106
107
  <Accordion title="Is Reticle actually cheaper, or only on the pages you chose?">
@@ -5,24 +5,27 @@ description: What Reticle keeps on your machine, what is redacted, and what it t
5
5
 
6
6
  Reticle drives your app and reads what it did. That means it holds real content from real pages, so this page says exactly what it keeps, where it keeps it, and what it takes to make any of it leave.
7
7
 
8
- If you read one line: **a verdict is entirely local and needs no account.** Nothing from your app leaves the machine until you link a project (`reticle connect`, or `RETICLE_API_KEY` in CI). After that, what syncs is set by `reticle config`, and when the Harness drives, the platform sees the steps it drives. Secret values never leave. (Anonymous usage counts, which carry nothing from your app, are separate and described in [Telemetry](telemetry.md).)
8
+ If you read one line: **a verdict is entirely local and needs no account.** Nothing from your app leaves the machine until you link a project (`reticle connect`, or `RETICLE_API_KEY` in CI). After that, what syncs is set by `reticle config`, and when the Harness drives, the platform sees the steps it drives. A linked project also sends each prompt you typed, and the message the agent closed its turn with, with the runs that verify it, unless `"shareRequests": false`; see [Prompt capture](prompt-capture.md). Secret values never leave. (Usage counts, which carry nothing from your app, are separate and described in [Telemetry](telemetry.md).)
9
9
 
10
10
  ## On your machine
11
11
 
12
- Everything lands in `.reticle/` in your project, and it is yours. Three kinds of thing:
12
+ In a wired project, everything lands in `.reticle/` in your project, and it is yours. Reticle writes nothing into a project that you did not wire. A run that matches no project goes to `~/.reticle/unmatched/`. Four kinds of thing:
13
13
 
14
14
  | | what it is | what is in it |
15
15
  | --- | --- | --- |
16
16
  | **runs** | a verdict and its evidence | what was claimed, whether it held, the request or state change that decided it, and the `file:line` of the element driven |
17
17
  | **flows** | a journey you drove, saved so it can be replayed | the steps, the anchors they resolve by, and the values that were typed |
18
- | **memory** | what this project has learned across sessions | intents you declared, envelopes of normal behaviour, notes about anchors that drift, the coverage ledger (`coverage.json`), the notes people pinned in the HUD (`notes.json`), and the user's latest request as the agent relayed it (`request.json`, local only) |
18
+ | **memory** | what this project has learned across sessions | intents you declared, envelopes of normal behaviour, notes about anchors that drift, the coverage ledger (`coverage.json`), the notes people pinned in the HUD (`notes.json`), the newest prompt (`request.json`) and every prompt (`requests.jsonl`, rotated to `requests.1.jsonl`) that the prompt hook captured or the agent relayed, redacted |
19
+ | **episodes** | every tool call behind the work, in `episodes/` | each call to Reticle's tools with its arguments (typed values removed), its whole result and the page events it caused; and, from the agent hooks, the agent's own shell commands, edits and web calls, its model and its closing messages, redacted ([Prompt capture](prompt-capture.md)) |
19
20
 
20
- `.reticle/` splits into a part meant for git and a part meant to stay local. Flows are the part worth committing: a saved flow is a regression test. Evidence is the part that is not.
21
+ `.reticle/` splits into a part meant for git and a part meant to stay local. Flows are the part worth committing: a saved flow is a regression test. Evidence, the episodes with it, is the part that is not.
21
22
 
22
23
  ## What is redacted, and what is not
23
24
 
24
25
  **Credential-shaped fields are redacted when a flow is saved.** A value typed into a field whose name looks like a secret is replaced with `<redacted: supply at replay>`, and replay reads the real value from `RETICLE_SECRET_<FIELD>` at run time. It keys off the field's name across every anchor kind (a testid, an accessible name, a signal), so an app without test ids is covered too.
25
26
 
27
+ New compiled recordings that fill the same secret field more than once give each fill its own variable: `RETICLE_SECRET_PASSWORD`, `RETICLE_SECRET_PASSWORD_2`, and so on. The variable name is stored in the fill's `secret` argument; the `value` stays `<redacted: supply at replay>`. Sequence children share this ordering, and suffixes skip other fields' keys. Supply those names in a replay call's `secrets` map or in the daemon environment; per-call values take precedence. Existing saved flows keep their original unsuffixed supply when loaded, including repeated legacy placeholders, without rewriting the file. Re-saving an already anchored flow, including an in-page recording, preserves its supplied secret names.
28
+
26
29
  **It is name-based, and that is the limit worth knowing.** It redacts a password. It does not redact a customer's name typed into a search box, because nothing about that field says "secret". A flow records the journey you actually drove.
27
30
 
28
31
  So: **drive staging.** Not because the risk is large, but because it is real and avoiding it is free.
@@ -39,7 +42,9 @@ Once linked, you choose what syncs:
39
42
  npx @reticlehq/server config --runs on|off --flows on|off --memory on|off
40
43
  ```
41
44
 
42
- `--memory` covers the coverage ledger and the HUD notes as well, and the notes are the words people typed. The user's request (`request.json`, the prompt the agent relayed, redacted) goes to the platform with the runs from a linked project, so the dashboard can show what each run was for; `"shareRequests": false` in `.reticle.json` keeps it on the machine. An unlinked project sends it nowhere, and it is kept out of git either way.
45
+ `--memory` covers the coverage ledger and the HUD notes as well, and the notes are the words people typed. **Prompts are shared by default from a linked project.** `reticle init` installs a hook that keeps each prompt you send, redacted, in `.reticle/` ([Prompt capture](prompt-capture.md)). A run written soon after a prompt carries it to the platform, so the dashboard can show what each run was for, and the Harness sends the newest one to the platform planner as a goal. A key in the environment (`RETICLE_API_KEY`) counts as linked. `"shareRequests": false` in `.reticle.json` keeps every request on the machine. An unlinked project sends it nowhere. The prompt files are in `.reticle/.gitignore` either way.
46
+
47
+ A run also carries the agent's model and, under the same `shareRequests` rule as the prompt, the message the agent closed its turn with. Nothing else from `episodes/` leaves: the files stay on the machine. `"promptCapture": false` in `.reticle.json`, or `RETICLE_PROMPT_CAPTURE=0`, stops all that the agent hooks keep: the prompts, the tool calls, the model and the closing messages. `RETICLE_JOURNAL=0`, or `"journal": false` in `.reticle.json`, stops the episode log.
43
48
 
44
49
  The Harness runs on the platform, so the platform sees what it drives: each step's result goes there so it can choose the next one. A secret field is sent by name only; its value is typed in on your machine.
45
50
 
@@ -49,7 +54,7 @@ The Harness runs on the platform, so the platform sees what it drives: each step
49
54
 
50
55
  ## Telemetry, which is separate from all of the above
51
56
 
52
- The CLI sends anonymous usage events: event names and a random installation id. No code, no page content, no URLs from your app. It is how we know which parts of the product are reached at all.
57
+ The CLI sends usage events: event names, a random installation id, and a hash of the project's git origin. A linked project adds its workspace id, and a licensed build adds its licence id, so those events can be connected to an organization. No code, no page content, no URLs from your app. No IP address and no location: each event tells the analytics service not to store the address or look up a location from it. It is how we know which parts of the product are reached at all.
53
58
 
54
59
  ```bash
55
60
  RETICLE_TELEMETRY=0 # this shell
@@ -57,7 +62,7 @@ DO_NOT_TRACK=1 # the convention, honoured
57
62
  npx @reticlehq/server telemetry disable # this machine, permanently
58
63
  ```
59
64
 
60
- Telemetry carries no free text. Free text leaves in three ways, each one something you chose: a feedback report (never collected passively; `RETICLE_FEEDBACK=0` disables it), HUD notes on a linked project with `--memory on`, and the user's request from a linked project unless `"shareRequests": false`.
65
+ Telemetry carries no free text. Free text leaves in three ways: a feedback report (never collected passively; `RETICLE_FEEDBACK=0` disables it; a reply address is sent only when you give it with `--contact`), HUD notes on a linked project with `--memory on`, and your prompts and the agent's closing messages from a linked project. Prompts and closing messages are on by default once a project is linked; `"shareRequests": false` stops them.
61
66
 
62
67
  The daemon also fetches a small public file, `https://reticle.sh/hud/notices.v1.json`, for the notices in the HUD's rail. The request carries nothing about you or your project. `RETICLE_TELEMETRY=0` or `DO_NOT_TRACK=1` stops it, and `RETICLE_HUD_NOTICES_URL` points it elsewhere.
63
68
 
@@ -11,7 +11,7 @@ It exists because an agent that writes code cannot otherwise tell whether that c
11
11
 
12
12
  ## How it works, in three parts
13
13
 
14
- 1. **An SDK inside the app.** Dev-only, stripped from production builds, added by a build plugin. It instruments the DOM, network, routing, console, animations, and your state library.
14
+ 1. **An SDK inside the app.** Dev-only by default, stripped from production builds, added by a build plugin. It instruments the DOM, network, routing, console, animations, and your state library.
15
15
  2. **A local bridge.** The SDK connects over a WebSocket to a daemon on your machine, bound to `127.0.0.1`. The daemon never manages your dev server; it attaches to the app that is running. On first setup, `npx @reticlehq/server init` starts the dev server with your project's own dev script and opens the app.
16
16
  3. **An MCP server.** The daemon exposes the app to your agent as tools. `reticle_look` to look, `reticle_act_and_wait` to act and prove in one call, `reticle_observe` to read what the app did, `reticle_assert` to check.
17
17
 
@@ -29,7 +29,7 @@ Reticle sits next to several familiar categories, and it works alongside each of
29
29
 
30
30
  **Security, accessibility, performance and SEO.** Reticle checks how your running app behaves in each: access control by role, calls that must never fire, focus and keyboard behaviour, request counts and long tasks, titles, links and redirects. It pairs with a vulnerability scanner, a full WCAG audit tool such as axe, and an SEO crawler. [Use cases](/use-cases) has what each check covers.
31
31
 
32
- **Monitoring.** Reticle runs against environments you control (a dev server, a staging or preview deployment) and replays saved flows in CI. The SDK refuses to connect in a production build, so it complements an APM or error tracker rather than watching live traffic.
32
+ **Monitoring.** Reticle runs against environments you control (a dev server, a staging or preview deployment) and replays saved flows in CI. By default the SDK does not connect in a production build. [Production mode](/production-mode) lets an agent drive a live site for one activated drive, but Reticle never watches your users' traffic, so it complements an APM or error tracker.
33
33
 
34
34
  **Not an accessibility auditor.** `reticle_look { action: "page" }` addresses elements by role and name, which is not the same as auditing contrast, focus order, or screen reader behaviour. Use a dedicated accessibility tool for that.
35
35
 
@@ -44,7 +44,7 @@ Reticle takes the other route. It runs **inside** your app and reads the program
44
44
 
45
45
  ## The evidence
46
46
 
47
- We built eleven scenarios: ten bugs, plus a control where nothing is wrong, so anything flagged on the control counts as a false positive. Each tool got the same app, the same moment, the same task.
47
+ We built eleven scenarios: ten bugs, plus a control where nothing is wrong, so anything flagged on the control counts as a false positive. Each tool got the same app, the same moment, the same task. This pass last ran on 2026-06-22, against `@playwright/mcp@0.0.76` and `chrome-devtools-mcp@1.3.0`.
48
48
 
49
49
  | Tool | Bugs caught | Detection accuracy | Avg tokens per look | Verification efficiency |
50
50
  | --- | --- | --- | --- | --- |