@reticlehq/server 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +12 -14
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +22 -0
  7. package/dist/command/cli/adhoc-suite.js +90 -19
  8. package/dist/command/cli/adhoc-verdict.d.ts +35 -0
  9. package/dist/command/cli/adhoc-verdict.js +90 -3
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +4 -0
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/cli-doctor.js +7 -5
  17. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  18. package/dist/command/cli/cli-flow-commands.js +67 -4
  19. package/dist/command/cli/cli-parse.d.ts +8 -1
  20. package/dist/command/cli/cli-parse.js +21 -3
  21. package/dist/command/cli/cli-update-commands.js +8 -1
  22. package/dist/command/cli/cli-verify.d.ts +24 -3
  23. package/dist/command/cli/cli-verify.js +54 -11
  24. package/dist/command/cli/cloud-cli.js +5 -2
  25. package/dist/command/cli/cloud-kit.js +3 -5
  26. package/dist/command/cli/cloud-login.js +21 -3
  27. package/dist/command/cli/demo/demo-page.js +6 -3
  28. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  29. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  30. package/dist/command/cli/launch/cli-launch.d.ts +8 -0
  31. package/dist/command/cli/launch/cli-launch.js +21 -9
  32. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  33. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  34. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  35. package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
  36. package/dist/command/cli/ports/resolve/cli-port.js +125 -15
  37. package/dist/command/cli/setup-mcp-cli.js +13 -13
  38. package/dist/command/cli/tutorial.d.ts +2 -2
  39. package/dist/command/cli/tutorial.js +21 -7
  40. package/dist/command/cli.js +62 -10
  41. package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
  42. package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
  43. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  44. package/dist/command/daemon/daemon-resolve.js +1 -1
  45. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  46. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  47. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  48. package/dist/command/dev/stale-issue-guard.js +8 -8
  49. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  50. package/dist/command/setup/bringup/connect-lease.js +38 -0
  51. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  52. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  53. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  54. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  55. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  56. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  57. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  58. package/dist/command/setup/bringup/static-page-server.js +151 -0
  59. package/dist/command/setup/desktop-shape.d.ts +4 -0
  60. package/dist/command/setup/desktop-shape.js +18 -2
  61. package/dist/command/setup/init/init-port.d.ts +15 -0
  62. package/dist/command/setup/init/init-port.js +44 -0
  63. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  64. package/dist/command/setup/init/init-runtime.js +39 -2
  65. package/dist/command/setup/node-effects.d.ts +27 -14
  66. package/dist/command/setup/node-effects.js +80 -44
  67. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  68. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  69. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  70. package/dist/command/setup/probe/page-probe.js +18 -3
  71. package/dist/command/setup/run-setup.d.ts +26 -1
  72. package/dist/command/setup/run-setup.js +96 -13
  73. package/dist/command/setup/session-pick.d.ts +13 -0
  74. package/dist/command/setup/session-pick.js +20 -0
  75. package/dist/command/setup/setup-command.d.ts +2 -14
  76. package/dist/command/setup/setup-command.js +105 -31
  77. package/dist/command/setup/setup-mcp.js +3 -3
  78. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  79. package/dist/command/setup/terminal/wait-progress.js +17 -0
  80. package/dist/features/crawl/crawl-empty.js +1 -1
  81. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  82. package/dist/features/crawl/crawl-tools.js +57 -0
  83. package/dist/features/crawl/crawl.js +171 -157
  84. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  85. package/dist/features/exhaust/app-coverage.js +31 -0
  86. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  87. package/dist/features/exhaust/code-coverage.js +77 -0
  88. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  89. package/dist/features/exhaust/exhaust-run.js +82 -0
  90. package/dist/features/exhaust/explorer.d.ts +103 -0
  91. package/dist/features/exhaust/explorer.js +195 -0
  92. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  93. package/dist/features/exhaust/failure-branches.js +92 -0
  94. package/dist/features/exhaust/ledger.d.ts +202 -0
  95. package/dist/features/exhaust/ledger.js +252 -0
  96. package/dist/features/exhaust/session-fold.d.ts +31 -0
  97. package/dist/features/exhaust/session-fold.js +79 -0
  98. package/dist/features/exhaust/session-port.d.ts +36 -0
  99. package/dist/features/exhaust/session-port.js +102 -0
  100. package/dist/features/harness/drive-report.d.ts +0 -9
  101. package/dist/features/harness/drive-report.js +18 -0
  102. package/dist/features/harness/driver.js +4 -7
  103. package/dist/features/harness/fill-values.d.ts +6 -0
  104. package/dist/features/harness/fill-values.js +3 -0
  105. package/dist/features/harness/jev-driver.js +30 -13
  106. package/dist/features/harness/openai-driver.js +4 -7
  107. package/dist/features/harness/platform-config.js +5 -6
  108. package/dist/index.d.ts +9 -0
  109. package/dist/index.js +40 -11
  110. package/dist/judgement/domain/domain-model.js +3 -1
  111. package/dist/judgement/oracles/self-instrument.js +4 -2
  112. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  113. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  114. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  115. package/dist/judgement/runs/verification-sync.js +1 -1
  116. package/dist/language/flows/change/affected.d.ts +9 -0
  117. package/dist/language/flows/change/affected.js +35 -0
  118. package/dist/language/flows/change/gate.d.ts +20 -0
  119. package/dist/language/flows/change/gate.js +12 -1
  120. package/dist/language/flows/flow-author.d.ts +7 -0
  121. package/dist/language/flows/flow-author.js +19 -0
  122. package/dist/language/flows/flow-expect-grammar.js +1 -1
  123. package/dist/language/flows/flow-journey.d.ts +54 -0
  124. package/dist/language/flows/flow-journey.js +119 -0
  125. package/dist/language/flows/flow-replay-run.d.ts +2 -3
  126. package/dist/language/flows/flow-replay-run.js +13 -47
  127. package/dist/language/flows/flow-replay.d.ts +22 -8
  128. package/dist/language/flows/flow-replay.js +55 -9
  129. package/dist/language/flows/flow-result.d.ts +2 -0
  130. package/dist/language/flows/flow-result.js +25 -0
  131. package/dist/language/flows/flow-tools.js +17 -5
  132. package/dist/language/flows/flows.d.ts +10 -1
  133. package/dist/language/flows/flows.js +90 -14
  134. package/dist/language/flows/recording/replay-halt.js +3 -1
  135. package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
  136. package/dist/language/flows/recording/tape/recordings.js +55 -1
  137. package/dist/language/flows/replay.d.ts +19 -0
  138. package/dist/language/flows/replay.js +42 -6
  139. package/dist/language/flows/verify-change-tools.js +5 -2
  140. package/dist/launch-chromium.d.ts +80 -0
  141. package/dist/launch-chromium.js +218 -0
  142. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  143. package/dist/memory/cloud/cloud-config.js +47 -7
  144. package/dist/memory/cloud/cloud-sync.d.ts +4 -1
  145. package/dist/memory/cloud/cloud-sync.js +6 -9
  146. package/dist/memory/cloud/harness-offer.js +5 -6
  147. package/dist/memory/cloud/harness-switch.js +5 -6
  148. package/dist/memory/cloud/sync-cycle.d.ts +68 -3
  149. package/dist/memory/cloud/sync-cycle.js +298 -55
  150. package/dist/memory/cloud/sync-daemon.js +24 -20
  151. package/dist/memory/cloud/sync-disk.js +16 -2
  152. package/dist/memory/intent/intent-shard.d.ts +10 -32
  153. package/dist/memory/intent/intent-shard.js +0 -10
  154. package/dist/memory/journal/drive-flow.d.ts +18 -13
  155. package/dist/memory/journal/drive-flow.js +45 -37
  156. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  157. package/dist/memory/journal/session-end.d.ts +9 -1
  158. package/dist/memory/journal/session-end.js +24 -12
  159. package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
  160. package/dist/memory/project/dir/reticle-dir.js +1 -0
  161. package/dist/memory/project/project-tools.js +3 -1
  162. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  163. package/dist/portal/bridge/auth-failure-reason.js +4 -2
  164. package/dist/portal/bridge/bridge.d.ts +8 -0
  165. package/dist/portal/bridge/bridge.js +43 -2
  166. package/dist/portal/input/js-coverage.d.ts +25 -0
  167. package/dist/portal/input/js-coverage.js +30 -0
  168. package/dist/portal/input/real-input.d.ts +11 -0
  169. package/dist/portal/input/real-input.js +29 -4
  170. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  171. package/dist/portal/pool/playwright-launcher.js +20 -13
  172. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  173. package/dist/portal/session/dev-server/dev-command.js +30 -12
  174. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  175. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  176. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  177. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  178. package/dist/portal/session/facts/observed-state.js +34 -0
  179. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  180. package/dist/portal/session/no-session-diagnosis.js +62 -5
  181. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  182. package/dist/portal/session/no-session-next-action.js +48 -9
  183. package/dist/portal/session/no-session-watch.d.ts +18 -0
  184. package/dist/portal/session/no-session-watch.js +88 -2
  185. package/dist/portal/session/session-health.js +3 -2
  186. package/dist/portal/session/session-manager.d.ts +20 -6
  187. package/dist/portal/session/session-manager.js +19 -3
  188. package/dist/portal/session/session-reaper.d.ts +28 -0
  189. package/dist/portal/session/session-reaper.js +49 -1
  190. package/dist/portal/session/session.d.ts +5 -0
  191. package/dist/portal/session/session.js +15 -19
  192. package/dist/surface/http-server.d.ts +2 -1
  193. package/dist/surface/http-server.js +4 -2
  194. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  195. package/dist/surface/mcp/mcp-proxy.js +2 -2
  196. package/dist/surface/mcp/mcp.js +4 -2
  197. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  198. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  199. package/dist/surface/mcp/server-instructions.js +9 -7
  200. package/dist/surface/tools/act/after-match.d.ts +17 -0
  201. package/dist/surface/tools/act/after-match.js +30 -0
  202. package/dist/surface/tools/act/durable.d.ts +69 -0
  203. package/dist/surface/tools/act/durable.js +139 -0
  204. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
  205. package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
  206. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  207. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  208. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  209. package/dist/surface/tools/act/proved-controls.js +11 -0
  210. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  211. package/dist/surface/tools/act/sequence-grade.js +32 -1
  212. package/dist/surface/tools/act-capsule.d.ts +7 -0
  213. package/dist/surface/tools/act-capsule.js +46 -1
  214. package/dist/surface/tools/act-sequence-tool.js +25 -8
  215. package/dist/surface/tools/act-tools.js +26 -50
  216. package/dist/surface/tools/assert/assert-grade.js +12 -1
  217. package/dist/surface/tools/assert/assert-verdict.js +2 -2
  218. package/dist/surface/tools/browser-tools.js +14 -2
  219. package/dist/surface/tools/coverage-tools.js +27 -1
  220. package/dist/surface/tools/error-recovery.js +4 -4
  221. package/dist/surface/tools/feedback-invite.js +6 -4
  222. package/dist/surface/tools/harness-explore.d.ts +3 -2
  223. package/dist/surface/tools/harness-explore.js +7 -6
  224. package/dist/surface/tools/lease-tools.js +15 -6
  225. package/dist/surface/tools/live-call-text.js +4 -1
  226. package/dist/surface/tools/observe-tools.js +9 -1
  227. package/dist/surface/tools/tools.js +4 -0
  228. package/dist/telemetry/argument-shape.d.ts +0 -2
  229. package/dist/telemetry/argument-shape.js +0 -4
  230. package/dist/telemetry/error-class.d.ts +2 -3
  231. package/dist/telemetry/error-class.js +2 -3
  232. package/dist/telemetry/harness-drive.d.ts +0 -2
  233. package/dist/telemetry/harness-drive.js +0 -4
  234. package/dist/wire-journal.d.ts +3 -0
  235. package/dist/wire-journal.js +4 -0
  236. package/dist/workspace-packages.d.ts +0 -2
  237. package/dist/workspace-packages.js +1 -9
  238. package/docs/README.md +4 -2
  239. package/docs/actions.mdx +3 -3
  240. package/docs/agent-cheatsheet.md +4 -4
  241. package/docs/architecture.md +6 -6
  242. package/docs/best-practices.mdx +1 -1
  243. package/docs/capabilities.mdx +32 -1
  244. package/docs/cli/cloud.mdx +14 -4
  245. package/docs/cli/doctor.mdx +4 -0
  246. package/docs/cli/feedback.mdx +2 -2
  247. package/docs/cli/gate.mdx +9 -4
  248. package/docs/cli/update.mdx +10 -0
  249. package/docs/cli.mdx +9 -0
  250. package/docs/desktop.mdx +14 -10
  251. package/docs/environment.mdx +87 -0
  252. package/docs/faq.mdx +18 -16
  253. package/docs/first-drive.md +5 -13
  254. package/docs/flows.md +21 -2
  255. package/docs/frameworks.mdx +29 -10
  256. package/docs/getting-started.md +3 -3
  257. package/docs/http-transport.md +6 -3
  258. package/docs/human-control.md +2 -2
  259. package/docs/index.mdx +16 -6
  260. package/docs/install-agentic.mdx +1 -1
  261. package/docs/install-manual.mdx +1 -1
  262. package/docs/integration-patterns.md +3 -3
  263. package/docs/multi-agent-testing.md +1 -1
  264. package/docs/packages/babel-plugin.mdx +1 -1
  265. package/docs/packages/browser.mdx +2 -2
  266. package/docs/packages/react.mdx +2 -2
  267. package/docs/packages/tauri.mdx +1 -1
  268. package/docs/packages/vite-plugin.mdx +2 -2
  269. package/docs/packages.mdx +9 -9
  270. package/docs/predicates.mdx +50 -5
  271. package/docs/quickstart.mdx +2 -2
  272. package/docs/recipes.mdx +2 -2
  273. package/docs/state-management.mdx +2 -2
  274. package/docs/system-map.md +1 -1
  275. package/docs/telemetry.md +1 -1
  276. package/docs/token-efficiency.md +3 -3
  277. package/docs/tools/act-and-wait.mdx +1 -0
  278. package/docs/tools/act-sequence.mdx +1 -1
  279. package/docs/tools/console.mdx +1 -1
  280. package/docs/tools/inspect.mdx +1 -1
  281. package/docs/tools/network.mdx +1 -1
  282. package/docs/tools/query.mdx +1 -1
  283. package/docs/tools/sessions.mdx +1 -1
  284. package/docs/tools/snapshot.mdx +1 -1
  285. package/docs/tools/state.mdx +1 -1
  286. package/docs/tools/wait-for.mdx +1 -1
  287. package/docs/usage.md +7 -6
  288. package/docs/use-cases.mdx +157 -0
  289. package/docs/vs-screenshots.mdx +3 -3
  290. package/docs/what-is-recorded.md +2 -2
  291. package/docs/what-is-reticle.mdx +17 -20
  292. package/docs/why-reticle.mdx +14 -21
  293. package/package.json +10 -9
  294. package/dist/judgement/capsule/minimize.d.ts +0 -12
  295. package/dist/judgement/capsule/minimize.js +0 -22
  296. package/dist/judgement/phenomena/phenomena.d.ts +0 -27
  297. package/dist/judgement/phenomena/phenomena.js +0 -101
  298. package/dist/language/flows/flow-report.d.ts +0 -22
  299. package/dist/language/flows/flow-report.js +0 -106
@@ -196,7 +196,7 @@ Sessions accumulate. Four stale ones pointing at dev servers you restarted an ho
196
196
 
197
197
  ## Confirm a navigation landed
198
198
 
199
- `reticle_navigate` returns `confirmed: false` by design. A navigation destroys the document the SDK lives in, so it can only report dispatch. Check [`reticle_sessions`](/tools/sessions) for a session at the new URL before acting, and re-snapshot: refs do not survive a new document.
199
+ `reticle_navigate` returns `confirmed: false` by design. A navigation destroys the document the SDK lives in, so it can only report dispatch. Check [`reticle_session { action: "list" }`](/tools/sessions) for a session at the new URL before acting, and re-snapshot: refs do not survive a new document.
200
200
 
201
201
  ## Start with what the app says about itself
202
202
 
@@ -55,7 +55,7 @@ Useful at the end of a drive, when the agent is about to report success. "I veri
55
55
  }
56
56
  ```
57
57
 
58
- Note the token redaction. Credential-shaped values are stripped here exactly as they are in [`reticle_network`](/tools/network), so reading storage never puts a session token in your context.
58
+ Note the token redaction. Credential-shaped values are stripped here exactly as they are in [`reticle_observe { action: "network" }`](/tools/network), so reading storage never puts a session token in your context.
59
59
 
60
60
  localStorage, sessionStorage and readable cookies.
61
61
 
@@ -87,6 +87,24 @@ Toasts that auto-dismiss, debounced search, polling, session timeouts, retry bac
87
87
 
88
88
  Freeze the clock, advance it by exactly the interval, assert the consequence. No sleeping, no guessing, and the same result on a fast laptop and a loaded CI runner.
89
89
 
90
+ <Warning>
91
+ Freeze the clock **before** the action that schedules the timer. Reticle replaces the page's
92
+ `setTimeout` and `setInterval` when the clock is frozen, so timers that already exist keep running
93
+ on the browser's real clock and are not fired by `advanceMs`.
94
+
95
+ If a timer is created during page mount, freeze the clock first and then reload the page so the mount-time timer is created under the fake clock.
96
+
97
+ </Warning>
98
+
99
+ The clock virtualizes `Date.now()`, but not every time API. `new Date()` and `performance.now()` continue to use real time. `performance.now()` is intentionally left real because React's scheduler depends on it.
100
+
101
+ The reliable pattern is:
102
+
103
+ 1. Freeze the clock.
104
+ 2. Perform the action that schedules the timer, or reload the page if the timer is created during mount.
105
+ 3. Advance by the required interval.
106
+ 4. Assert the consequence.
107
+
90
108
  ### Crawl: click everything, report anomalies
91
109
 
92
110
  ```json
@@ -123,6 +141,19 @@ This is the tool for an app you did not write and do not understand yet.
123
141
  25.
124
142
  </Warning>
125
143
 
144
+ Controls Reticle judges destructive are blocked unless you pass `confirmDangerous: true`. That covers the controls on the page the crawl started on. A control one of its clicks revealed, such as the "Yes, delete" behind a "Delete", stays blocked, so one crawl cannot finish a deletion you did not look at.
145
+
146
+ ### Covering everything: `exhaustive`
147
+
148
+ `{ "action": "crawl", "exhaustive": true }` goes past one page. It walks every state reachable from where you start, reaching each one by replaying the path that found it from a fresh load, so it never depends on "back" working. It fills text fields before it tries a form's buttons (from `RETICLE_SECRET_<FIELD>` when set, so it gets past a login, and only on the origin the crawl started on), and treats controls that differ only by a number, like table rows, as one. A control with the same name on a different screen is tried again there. Every write it finds is then driven again with the request failing. A write counts as **branched** only when the request was actually sent and broken. With the write failing, the app must not end up where it goes when the write succeeds; if it does, you get `failure-unhandled`. That part needs a browser Reticle drives (`reticle drive`).
149
+
150
+ The run folds into the app-wide coverage ledger, which `reticle_verify { action: "coverage", app: true }` also reports. It has six levels, each with what is missing: routes **reached**, controls **touched**, controls **proved** (an `act_and_wait` came back `yes`), journeys **completed**, writes **branched** (failure path driven), and app functions **executed** (V8 coverage, driven browser only, collected from the first coverage request or exhaustive crawl onward, so an ordinary drive pays nothing for it). `frontier` is the number of states found and not yet explored. Zero means every state the explorer could tell apart was explored, not that every reachable state was: a state is its route plus the names of its controls, so two screens that differ only in field values, in how many of a control they show, or in what the same controls do count as one, and the second is never visited. `reticle gate` blocks when a level falls below the best it has reached, and when a changed file never ran. A changed file whose coverage was taken before the edit is reported as `coverageStale` instead, because its counts describe the old code. When a drop was intended, `reticle gate --accept-coverage` records today's levels as the new best.
151
+
152
+ <Warning>
153
+ Exhaustive really does click everything, repeatedly, and submits forms. `maxSteps` bounds it,
154
+ defaulting to 400 actions.
155
+ </Warning>
156
+
126
157
  ### Reconcile: what the API said versus what rendered
127
158
 
128
159
  ```json
@@ -20,7 +20,13 @@ These are thin clients over the `/v1` API. The logic is server-side; these verbs
20
20
  | `~/.reticle/credentials.json` | Project-scoped API keys from `reticle link`, keyed by `projectId` | Yes |
21
21
  | `<repo>/.reticle/cloud.json` | `{ projectId, projectName, url, sync, verify }`. The non-secret binding | No, commit it |
22
22
 
23
- Auth for any command is `RETICLE_API_KEY` from the environment (the agent path) if set, otherwise the login token. `RETICLE_CLOUD_URL` overrides the endpoint; the default is `http://localhost:8890`. When a command falls back to that default without `RETICLE_CLOUD_URL` (or a saved session URL), it prints a one-line notice on stderr; stdout stays pure JSON for agents.
23
+ Auth for any command is `RETICLE_API_KEY` from the environment (the agent path) if set, otherwise the login token. `RETICLE_CLOUD_URL` (or its short form `RETICLE_URL`) overrides the endpoint; without it, a command uses the saved session's host, and otherwise the hosted service, `https://app.reticle.sh`.
24
+
25
+ ## In CI
26
+
27
+ CI needs `RETICLE_API_KEY` and nothing else: no `login`, no `link`. Commit `.reticle/cloud.json`, store the key as a secret, and the daemon and `reticle push` sync to the project the binding names. A key stored in `credentials.json` wins over the environment when both exist.
28
+
29
+ The environment key is only sent to the host it belongs to: `RETICLE_CLOUD_URL` when set, otherwise the hosted service. A repo linked to a self-hosted install therefore also needs `RETICLE_CLOUD_URL` set to that install in CI. A repo with no `cloud.json` and a key in the environment syncs to the hosted service, or to `RETICLE_CLOUD_URL`.
24
30
 
25
31
  ## The commands
26
32
 
@@ -101,9 +107,13 @@ reticle: no .reticle/cloud.json here ... run `reticle link` first
101
107
 
102
108
  ### `reticle push`
103
109
 
104
- Sends local run artifacts from `.reticle` to the linked project, honoring the sync policy. With `sync.runs` off it does nothing and says so. Prints `{ ok, project, sent, pulled }`, where `sent` carries the accepted counts and, when the server refused anything, a `rejected` array naming each artifact's index and reason.
110
+ Sends local run artifacts from `.reticle` to the linked project, honoring the sync policy. With `sync.runs` off it does nothing and says so. Prints `{ ok, project, sent, pulled }`, where `sent` carries the accepted counts and, when the server refused anything, a `rejected` array naming each run's index and reason and a `refused` array for flows, capsules and derived records. `sent.records` lists only the records the server accepted.
111
+
112
+ Runs go up in batches, so a large backlog is never one oversized request.
113
+
114
+ What the platform cannot read is not sent at all, and a `notSent` array says why: a record kind an older platform does not know yet (this one alone leaves `ok` true), or a flow or run in a file version it does not read. Sending those would only be refused again on every cycle.
105
115
 
106
- **A rejection is a failed push.** If the server refuses any artifact, `ok` is `false` and the command exits `1`, even when other artifacts in the same bundle landed. A refused artifact never reaches the dashboard and is re-offered on every cycle, so partial loss is still loss. `ok: true` and exit `0` mean everything offered was accepted, which includes the common case of a repo that was already up to date. The accepted counts still report what did land, so a partial push is visible as one.
116
+ **A rejection is a failed push.** If the server refuses any artifact, `ok` is `false` and the command exits `1`, even when other artifacts in the same bundle landed. The reason is kept as `sync.lastError` in `reticle whoami`, and the background daemon logs it once, until a cycle refuses nothing. A refused run is not offered again until its file changes or the platform reports that it reads more: until then it is listed under `notRetried`, and the summary reads `refused, not retried: N run(s) (reason)`. A refused artifact never reaches the dashboard and is re-offered on every cycle, so partial loss is still loss. `ok: true` and exit `0` mean everything offered was accepted, which includes the common case of a repo that was already up to date. The accepted counts still report what did land, so a partial push is visible as one.
107
117
 
108
118
  ### `reticle runs`
109
119
 
@@ -130,7 +140,7 @@ reticle: usage: reticle share <runId>
130
140
  `push`, `runs` and `regression` all need a project to talk to. Without one they exit `1` with the same line on stderr, measured:
131
141
 
132
142
  ```
133
- reticle: cloud not attached here ... run `reticle link` (or set RETICLE_CLOUD_URL/KEY)
143
+ reticle: cloud not attached here ... run `reticle link` (or set RETICLE_API_KEY)
134
144
  ```
135
145
 
136
146
  ## Exit codes
@@ -72,6 +72,10 @@ All three captures below are real, taken by pointing `PLAYWRIGHT_BROWSERS_PATH`
72
72
  chromium ✓ installed (chromium-1234)
73
73
  ```
74
74
 
75
+ **`✓ using the installed Google Chrome`** (or Microsoft Edge) means the pinned build is absent and Reticle drives the browser you already have instead, through Playwright's channel. Nothing to do: the line also names the command that adds Playwright's own build, if you want it.
76
+
77
+ When neither is there, Reticle does not refuse. The first command that needs a browser (a lease, `verify`, a drive) runs the bundled Playwright's own pinned install for you, announced on stderr so `reticle mcp`'s stdout stays clean. The two `✗` lines below are what you see when that has not happened yet, or failed.
78
+
75
79
  **`✗ wrong revision`** means builds are installed and none of them is the wanted one. One pinned download away. The line names the revision Playwright wants, then every revision the root actually holds, then the exact command:
76
80
 
77
81
  ```
@@ -11,7 +11,7 @@ npx @reticlehq/server feedback [--rating 1-5] [--bug] "what worked, what didn't"
11
11
  npx @reticlehq/server feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened"
12
12
  ```
13
13
 
14
- The `--agent` form is for an agent that cannot reach the `reticle_feedback` tool: during `init`, while instrumentation is half wired, or when the daemon will not start. Those are the reports that are hardest to hear and most needed, because every other path requires the tool surface to already work.
14
+ The `--agent` form is for an agent that cannot reach the `reticle_session { action: "feedback" }` tool: during `init`, while instrumentation is half wired, or when the daemon will not start. Those are the reports that are hardest to hear and most needed, because every other path requires the tool surface to already work.
15
15
 
16
16
  ## Flags
17
17
 
@@ -63,5 +63,5 @@ npx @reticlehq/server feedback --agent --kind gap \
63
63
  icon="comments"
64
64
  href="/tools/session-and-feedback"
65
65
  >
66
- `reticle_feedback` when the tools are reachable.
66
+ `reticle_session { action: "feedback" }` when the tools are reachable.
67
67
  </Card>
package/docs/cli/gate.mdx CHANGED
@@ -7,7 +7,7 @@ icon: shield-halved
7
7
  `reticle gate` is what makes verification unavoidable. It computes the [affected](/cli/affected) flows, looks for a passing artifact for each, and fails the command if any is missing. An agent that edits a covered file cannot call itself finished without re-verifying.
8
8
 
9
9
  ```bash
10
- npx @reticlehq/server gate [--since <ref>] [file...]
10
+ npx @reticlehq/server gate [--since <ref>] [--accept-coverage] [file...]
11
11
  ```
12
12
 
13
13
  ## Arguments and flags
@@ -16,6 +16,7 @@ npx @reticlehq/server gate [--since <ref>] [file...]
16
16
  | --- | --- | --- | --- |
17
17
  | `file...` | strings, positional | none | Explicit changed files |
18
18
  | `--since <ref>` | string | `HEAD` when no files are given | Also include files git reports as changed since this ref |
19
+ | `--accept-coverage` | boolean | off | Accept today's coverage levels as the new best before judging. Use it when a drop was intended, such as a control you removed on purpose. The run reports what it accepted as `coverageAccepted` |
19
20
 
20
21
  ## What blocks and what does not
21
22
 
@@ -24,6 +25,10 @@ npx @reticlehq/server gate [--since <ref>] [file...]
24
25
  | `uncovered` | Yes | An affected flow with no passing artifact |
25
26
  | `downgraded` | Yes | A flow whose assertions were weakened since its last passing run. A green bought by weakening the test is the gaming move this exists to stop |
26
27
  | `deletedCoverage` | Yes | A flow that covered a changed file and has since vanished. Coverage deleted, not satisfied |
28
+ | `unflowed` | Yes | A changed interactive component that no saved flow drives. Only once the project has saved flows, so a first day never blocks |
29
+ | `coverageRegressed` | Yes | A [coverage](/capabilities) level fell below the best it has reached: something new appeared that nothing covers. Cover it, or accept the drop with `--accept-coverage` |
30
+ | `unexecuted` | Yes | A changed file the browser loaded during a drive and never ran a function of |
31
+ | `coverageStale` | No | A changed file whose code coverage was taken before its last edit. Its counts describe the old code, so `unexecuted` cannot judge it either way. Take coverage again after driving the change to measure it |
27
32
  | `quarantined` | No | Flows the flake ledger has quarantined. Surfaced, never blocking, because one unexplained flake that blocks a merge destroys trust in the red |
28
33
 
29
34
  ## What it prints
@@ -41,14 +46,14 @@ One `reticle_gate` line. Real capture from this repository, with the flow lists
41
46
  }
42
47
  ```
43
48
 
44
- `downgraded` and `deletedCoverage` appear only when they are non-empty, and neither was present in that run. The CLI prints this as a single line of JSON; it is pretty-printed here to be readable.
49
+ `downgraded`, `deletedCoverage`, `unflowed`, `coverageRegressed`, `unexecuted`, `coverageStale` and `coverageAccepted` appear only when they are non-empty, and none was present in that run. The coverage categories need a coverage ledger, which a project gets from `reticle_verify { action: "crawl", exhaustive: true }` or `reticle_verify { action: "coverage", app: true }`; without one they never fire. The CLI prints this as a single line of JSON; it is pretty-printed here to be readable.
45
50
 
46
51
  ## Exit codes
47
52
 
48
53
  | Code | Meaning |
49
54
  | --- | --- |
50
- | `0` | Every affected, non-flaky flow has a passing artifact and nothing was weakened or deleted |
51
- | `1` | Something is uncovered, downgraded or deleted. Also `1` on any internal fault: the gate fails closed |
55
+ | `0` | Every affected, non-flaky flow has a passing artifact, nothing was weakened or deleted, and coverage held |
56
+ | `1` | Something is uncovered, downgraded, deleted, unflowed, unexecuted, or coverage fell. Also `1` on any internal fault: the gate fails closed |
52
57
 
53
58
  Measured on this repository, `npx @reticlehq/server gate --since HEAD~1` exited `1` with the output above: 0 of 47 flows covered, 39 uncovered and 8 quarantined.
54
59
 
@@ -31,6 +31,16 @@ No flags.
31
31
 
32
32
  The SDK sync is best effort and non-fatal on purpose. This command gets run from wherever the human happens to be standing, which is often not an app at all.
33
33
 
34
+ ## Stuck on an older Windows release
35
+
36
+ Reticle 3.1.0 and older cannot apply this update on Windows and may fail with `spawn EINVAL`. Those releases run their own outdated updater, so retrying the command will not help. Run the installer directly from PowerShell instead:
37
+
38
+ ```powershell
39
+ irm https://raw.githubusercontent.com/reticlehq/reticle/main/install/install.ps1 | iex
40
+ ```
41
+
42
+ This installs the current server without going through the old updater.
43
+
34
44
  ## Exit codes
35
45
 
36
46
  | Code | Meaning |
package/docs/cli.mdx CHANGED
@@ -112,6 +112,15 @@ policy https://github.com/reticlehq/reticle/blob/main/docs/telemetry.md
112
112
 
113
113
  A bare `reticle` with no arguments is not help. It is `reticle serve`.
114
114
 
115
+ <Note>
116
+ When the Reticle SDK installed in the project is a different **major** version from the CLI you ran,
117
+ the CLI hands the same command to `npx @reticlehq/server@<that version>`, so an unpinned
118
+ `npx @reticlehq/server` never pairs a daemon with an SDK it cannot talk to. `init`, `update`,
119
+ `rollback`, `setup install`, `version` and `help` always run as typed, and so does a command whose
120
+ version you pinned yourself (`npx @reticlehq/server@<version> …`). Set `RETICLE_NO_VERSION_MATCH=1` to
121
+ turn it off.
122
+ </Note>
123
+
115
124
  <Warning>
116
125
  `reticle verify` binds the bridge port itself, so it fails if a daemon is already listening, which
117
126
  is the normal state once your agent has started one. Stop the daemon first, or point it elsewhere
package/docs/desktop.mdx CHANGED
@@ -88,7 +88,7 @@ The rows in **bold** are re-proven by a committed battery on every change, `pnpm
88
88
  | Capability | Electron | Tauri | Note |
89
89
  | --- | --- | --- | --- |
90
90
  | sessions, snapshot, query, inspect | yes | yes | `inspect` returns `src/App.tsx:104` in a dev build; a packaged production renderer has no source map, so it reports `n/a` |
91
- | capabilities, state (live store) | yes | yes | `reticle_state` reads the real store |
91
+ | capabilities, state (live store) | yes | yes | `reticle_look { action: "state" }` reads the real store |
92
92
  | act (click/fill/type/select) | yes | yes | |
93
93
  | **act_and_wait, wait_for, assert** | yes | yes | signal / state / route / net / console predicates |
94
94
  | console errors | yes | yes | catches what the UI never shows |
@@ -133,7 +133,7 @@ export default defineConfig({
133
133
  });
134
134
  ```
135
135
 
136
- `desktop: true` does the two things a desktop shell needs and a web app must never get: the plugin also runs for `vite build` (a packaged renderer is a production build with **no dev server**, so the default serve-only gating would ship an app with no `connect()` at all), and `connect()` is called with `allowInProduction` so the SDK's production backstop does not refuse to start. Keep it behind your own dev-only build so an instrumented bundle can never reach a release binary.
136
+ `desktop: true` does two things a desktop shell needs: `connect()` is injected into the renderer's entry module, so a renderer loaded with no dev server still resolves it, and it is called with `allowInProduction`, because a packaged renderer reports `NODE_ENV=production` and the SDK's production backstop would otherwise refuse to start. It instruments the dev server, and a `vite build` **only when that build runs in a non-production mode**: `vite build --mode development`, for a packaged renderer you want to drive with no dev server. A production-mode build, which is what `vite build` and `electron-vite build` do by default, is stubbed exactly like a web bundle: it carries no SDK and never dials `ws://localhost`, so a release binary built the normal way is clean.
137
137
 
138
138
  Nothing to add in your app code. You can still call `reticle.connect()` by hand and pass `inject: false` if you want control.
139
139
 
@@ -157,10 +157,12 @@ That line is what makes your main-process calls visible. It has to live in the p
157
157
 
158
158
  **Preload sandboxing.** A sandboxed preload cannot resolve `node_modules`, so the bare `require` above fails with `module not found: @reticlehq/electron/preload`. Either bundle your preload (electron-vite and Electron Forge do this by default; the require is inlined at build time and sandboxing stays on), or set `sandbox: false` in `webPreferences`.
159
159
 
160
- **Packaged renderers.** An app that loads its renderer with `loadFile` runs on `file://`, which is a production Vite build. Pass `allowInProduction: true` to `connect()` for that mode, or keep the SDK gated behind `import.meta.env.DEV` so it never enters the shipped binary at all.
160
+ **Packaged renderers.** An app that loads its renderer with `loadFile` runs on `file://` with no dev server. To drive one, build its renderer with `vite build --mode development` (the plugin then instruments it, `allowInProduction` included). The default production-mode build ships no Reticle code at all.
161
161
 
162
162
  Working example: [`apps/electron-smoke`](https://github.com/reticlehq/reticle/tree/main/apps/electron-smoke).
163
163
 
164
+ **Electron Forge.** `reticle init` recognises Forge's Vite template (`vite.renderer.config.*` and no `vite.config.*`) and wires it the same three ways: `reticle()` in the renderer config, the preload shim, and `installReticleCapture` in main. The plugin goes in **without** `desktop: true`, because `electron-forge start` serves the renderer through a real Vite dev server, so the ordinary serve-time injection reaches the window and `electron-forge package` ships a renderer with no Reticle code. `init` starts the app with the script that runs `electron-forge start`, not the renderer's dev script. A vanilla renderer gets `@reticlehq/browser` rather than the React kit.
165
+
164
166
  ## Tauri setup
165
167
 
166
168
  Frontend side, nothing desktop-specific:
@@ -214,7 +216,7 @@ Alternatively, since an Electron renderer _is_ Chromium, `--remote-debugging-por
214
216
 
215
217
  ```toml
216
218
  [dependencies]
217
- reticle-tauri = "3.3"
219
+ reticle-tauri = "3.4"
218
220
  ```
219
221
 
220
222
  ```rust
@@ -283,12 +285,12 @@ A packaged renderer runs on `file://`, where `pushState('/settings')` rewrites t
283
285
 
284
286
  ## What IPC looks like to an agent
285
287
 
286
- A desktop app reaches its backend over IPC, not HTTP. `fetch`/`XHR` patching cannot see that, so without the IPC observer every backend call in your app is a blind spot: `reticle_network` returns nothing, `act_and_wait` has no in-flight request to settle on, and `assert { net }` is vacuously true. That is a false green by construction.
288
+ A desktop app reaches its backend over IPC, not HTTP. `fetch`/`XHR` patching cannot see that, so without the IPC observer every backend call in your app is a blind spot: `reticle_observe { action: "network" }` returns nothing, `act_and_wait` has no in-flight request to settle on, and `assert { net }` is vacuously true. That is a false green by construction.
287
289
 
288
290
  Reticle records each IPC call as an ordinary request, so the tools you already use work unchanged:
289
291
 
290
292
  ```jsonc
291
- // reticle_network { urlContains: "ipc://" }
293
+ // reticle_observe { action: "network", urlContains: "ipc://" }
292
294
  {
293
295
  "calls": [
294
296
  { "method": "ipc", "url": "ipc://todos:load", "status": 200, "ms": 134 },
@@ -297,7 +299,7 @@ Reticle records each IPC call as an ordinary request, so the tools you already u
297
299
  }
298
300
  ```
299
301
 
300
- IPC has no status code. `200`/`500` are synthetic, mapped from whether the call succeeded or failed, precisely so that `reticle_network { status: 500 }` and `assert { kind: "net", status: 500 }` keep working. On Tauri you will see `status: 500` next to `statusText: "OK"`. That is not a bug: the transport really did answer 200, and the 500 is the command's own verdict. `ok` is authoritative, and on Electron `error` carries the message your main process returned:
302
+ IPC has no status code. `200`/`500` are synthetic, mapped from whether the call succeeded or failed, precisely so that `reticle_observe { action: "network", status: 500 }` and `assert { kind: "net", status: 500 }` keep working. On Tauri you will see `status: 500` next to `statusText: "OK"`. That is not a bug: the transport really did answer 200, and the 500 is the command's own verdict. `ok` is authoritative, and on Electron `error` carries the message your main process returned:
301
303
 
302
304
  ```jsonc
303
305
  // reticle_assert { predicate: { kind: "net", urlContains: "ipc://todos:archive", status: 500 } }
@@ -363,9 +365,11 @@ Playwright MCP is faster. It is also structurally unable to see an IPC failure,
363
365
  Tauri, no: WKWebView exposes no CDP endpoint, so Playwright cannot attach at all.
364
366
  </Accordion>
365
367
  <Accordion title="Does the SDK end up in my shipped binary?">
366
- Only if you put it there. Keep the plugin behind a dev-only build, or gate `connect()` on
367
- `import.meta.env.DEV`. `desktop: true` exists because a packaged renderer is a production build,
368
- so it opts that one build into instrumentation deliberately.
368
+ No, not from a normal build. With `desktop: true` a production-mode `vite build` or
369
+ `electron-vite build` is stubbed like a web bundle: no SDK, no `ws://localhost` dial. Only a
370
+ build you run in another mode (`vite build --mode development`) is instrumented, which is how
371
+ you drive a packaged renderer with no dev server. A `connect()` you wrote by hand is yours to
372
+ gate on `import.meta.env.DEV`.
369
373
  </Accordion>
370
374
  <Accordion title="Why does a Tauri response show status 500 with statusText OK?">
371
375
  The IPC transport really did answer HTTP 200. The 500 is Reticle translating the Rust command's
@@ -0,0 +1,87 @@
1
+ ---
2
+ title: Environment variables
3
+ description: Every environment variable Reticle reads, what it changes, and which process reads it.
4
+ icon: sliders
5
+ ---
6
+
7
+ Reticle works with none of these set. Each one changes a single behaviour, and most are read once, by the process named in the table: the daemon at startup, the CLI command you run, or a build plugin when your dev server starts. A daemon that is already running keeps the values it started with, so after changing a daemon setting run `npx @reticlehq/server restart`.
8
+
9
+ ## Connection and ports
10
+
11
+ | Variable | Read by | What it does |
12
+ | --- | --- | --- |
13
+ | `RETICLE_PORT` | daemon, CLI, build plugins | The bridge and daemon port. `.reticle.json`'s `port` sets it per project; this overrides it. |
14
+ | `RETICLE_HOST` | daemon | The bridge bind host. Loopback by default. Anything else exposes the bridge beyond this machine, so it is opt-in. |
15
+ | `RETICLE_TOKEN` | daemon | A shared secret the page must present when it connects. The pairing token covers this automatically; set it only for a hand-wired setup. |
16
+ | `RETICLE_PAIRING_TOKEN_DIR` | daemon, `init`, build plugins | Where the auto-provisioned pairing token lives. Defaults to `~/.reticle`. Relocate it for CI or a read-only home directory. |
17
+ | `RETICLE_ALLOWED_ORIGINS` | daemon | Comma-separated WebSocket origins the bridge accepts. |
18
+ | `RETICLE_STATE_DIR` | daemon, CLI | Daemon state: pid files, the discovery registry and logs. Defaults to `~/.reticle`. |
19
+ | `RETICLE_DAEMON_READY_TIMEOUT_MS` | MCP proxy | How long the MCP proxy waits for a daemon to come up before reporting it unreachable. |
20
+ | `RETICLE_DEV` | `@reticlehq/next` | Instrument even when `NODE_ENV` says production, for `NODE_ENV=production next dev`. |
21
+
22
+ ## Daemon lifetime
23
+
24
+ | Variable | Read by | What it does |
25
+ | --- | --- | --- |
26
+ | `RETICLE_IDLE_SHUTDOWN_MS` | daemon | How long the daemon waits with no agent, no page and no lease before it exits. `0` keeps it running. |
27
+ | `RETICLE_IDLE_ATTACHED_MS` | daemon | The same wait while an agent is attached, which is longer so a slow install or a thinking human is not cut off. Derived from the base when unset. |
28
+ | `RETICLE_MCP_PROXY_IDLE_MS` | MCP proxy | How long an abandoned MCP stdio proxy waits before it exits. `0` disables the watcher. |
29
+ | `RETICLE_JOURNAL` | daemon | Turns the causal journal off (`0`, `false`, `off`) or on. On by default. |
30
+
31
+ ## Browser
32
+
33
+ | Variable | Read by | What it does |
34
+ | --- | --- | --- |
35
+ | `RETICLE_CDP_URL` | daemon | Attach to a browser that is already running, over CDP, instead of launching one. |
36
+ | `RETICLE_MAX_CONTEXTS` | daemon | The most leased headless browser contexts the pool runs at once. |
37
+ | `RETICLE_MAX_MESSAGES_PER_SECOND` | daemon | Events per second a page may send before the bridge starts sampling them. It never disconnects. Raise it for a legitimately busy app, such as a streaming dashboard. |
38
+
39
+ ## Tools and output
40
+
41
+ | Variable | Read by | What it does |
42
+ | --- | --- | --- |
43
+ | `RETICLE_ADVERTISE_ALL_TOOLS` | daemon | Advertise every tool directly instead of the default surface. Everything is reachable either way, through `reticle_run`. |
44
+ | `RETICLE_VERIFY_SURFACE` | daemon | Advertise the smallest surface that can still produce a verdict. |
45
+ | `RETICLE_TOOL_PROFILE` | daemon | Retired. Any value it used to accept still resolves to a sensible surface, and the daemon says which one it used instead. |
46
+ | `RETICLE_ENCODING` | daemon | How tool results are written for the agent. Compact JSON by default; `pretty` indents it, and `toon` is a denser tabular form. The typed result is the same in every encoding. |
47
+ | `RETICLE_NO_UPSELL` | daemon | Silences every upgrade hint. |
48
+
49
+ ## Gate and CI
50
+
51
+ | Variable | Read by | What it does |
52
+ | --- | --- | --- |
53
+ | `RETICLE_SKIP_GATE` | `reticle gate --hook` | Lets an agent's stop hook proceed past a failing gate. The skip is printed and recorded, never silent. It does not affect `reticle gate` without `--hook`, which is what CI runs. |
54
+ | `RETICLE_VERIFY_TOKEN` | `reticle serve --http` | The bearer token the optional HTTP verify endpoint requires. |
55
+ | `RETICLE_SECRET_<FIELD>` | replay, verify, crawl | The value typed into a password or secret field at replay. A recorded flow never stores it. See [what is recorded](/what-is-recorded). |
56
+
57
+ ## Cloud and the autonomous drive
58
+
59
+ | Variable | Read by | What it does |
60
+ | --- | --- | --- |
61
+ | `RETICLE_API_KEY` | CLI, daemon | The API key minted on the platform. |
62
+ | `RETICLE_CLOUD_KEY` | CLI, daemon | The key's earlier name. Still honoured. |
63
+ | `RETICLE_CLOUD_URL`, `RETICLE_URL` | CLI, daemon | The platform host. `RETICLE_CLOUD_URL` wins when both are set. |
64
+ | `RETICLE_SYNC_INTERVAL_MS` | `reticle sync --watch` | How often the watching sync runs. Every 60 seconds by default. |
65
+ | `ANTHROPIC_API_KEY` | autonomous drive | The key the drive's model uses when no coding agent is in the loop. Without it, the drive is unavailable and everything else is unaffected. |
66
+ | `RETICLE_HARNESS_MODEL` | autonomous drive | The model the drive uses. A small one by default. |
67
+ | `RETICLE_HARNESS_BASE_URL` | autonomous drive | A proxy or gateway in front of the model API. |
68
+ | `RETICLE_HARNESS_MAX_STEPS` | autonomous drive | A hard ceiling on model turns in one drive. It bounds cost. |
69
+ | `RETICLE_HARNESS_DRIVER` | autonomous drive | Which model drives: `anthropic` (the default) or `jev`. |
70
+ | `JEV_API_KEY`, `RETICLE_HARNESS_JEV_URL` | autonomous drive | A direct key and base URL for the `jev` driver. The usual path is `RETICLE_API_KEY` through the platform. |
71
+ | `OPENAI_API_KEY`, `RETICLE_HARNESS_OPENAI_MODEL` | autonomous drive | A direct key and model for the OpenAI driver. |
72
+
73
+ ## Licensing
74
+
75
+ | Variable | Read by | What it does |
76
+ | --- | --- | --- |
77
+ | `RETICLE_LICENSE_KEY` | daemon | The Enterprise licence key. See [licence activation](/license-activation). |
78
+
79
+ ## Telemetry and feedback
80
+
81
+ | Variable | Read by | What it does |
82
+ | --- | --- | --- |
83
+ | `RETICLE_TELEMETRY` | CLI, daemon | `0`, `false` or `off` turns anonymous usage telemetry off. `DO_NOT_TRACK` is honoured too. See [telemetry](/telemetry). |
84
+ | `RETICLE_TELEMETRY_FILE` | CLI, daemon | Write telemetry to a local JSONL file and send nothing, so you can read exactly what would be sent. |
85
+ | `RETICLE_TELEMETRY_URL`, `RETICLE_TELEMETRY_KEY` | CLI, daemon | Send telemetry to a different host or project, such as an EU region or your own instance. |
86
+ | `RETICLE_FEEDBACK` | daemon | Turns off the feedback channel only. Usage telemetry follows `RETICLE_TELEMETRY`. |
87
+ | `RETICLE_TRACE` | daemon, CLI | Verbose internal tracing for people working on Reticle itself. Off by default. |
package/docs/faq.mdx CHANGED
@@ -23,22 +23,16 @@ You get considerably better results by adding `data-testid` attributes and by [e
23
23
  </Accordion>
24
24
 
25
25
  <Accordion title="Does it work without React?">
26
- The core is framework-agnostic: DOM, network, routing, console, animations, snapshots and actions are gated against a vanilla TypeScript app.
26
+ Yes. The core is framework-agnostic: DOM, network, routing, console, animations, snapshots and actions are gated against a vanilla TypeScript app. What varies by framework is how `connect()` gets in and whether you get a source pointer on top.
27
27
 
28
- Support beyond that is uneven, and we would rather say so than let you find out:
29
-
30
- | Framework | Status |
31
- | --- | --- |
32
- | React, Next.js, Remix, Astro | Supported, each with an app and a CI gate |
33
- | SvelteKit | Wired end to end. `init` writes the client hook, the plugin stamps `.svelte` source. But **no CI gate**, so unverified rather than supported |
34
- | Vue | A Pinia store adapter and nothing else. No detection, no `.vue` stamping, no gate |
28
+ [Frameworks](/frameworks) is the one list of what is proven for each, from "driven to a verdict in CI" to "install gated" to "wired, unverified".
35
29
 
36
30
  </Accordion>
37
31
 
38
32
  <Accordion title="Can it tell me whether my UI looks good?">
39
- No. Reticle verifies behaviour, not aesthetics. Visual correctness and "does this feel right" remain a human job, or a visual-diff tool's.
33
+ Reticle verifies behaviour, not aesthetics. Visual correctness and "does this feel right" remain a human job, or a visual-diff tool's, and Reticle pairs well with one.
40
34
 
41
- It does read design tokens. [`reticle_inspect`](/tools/inspect) reports whether a colour came from your theme or from someone's clipboard. But that is a consistency check, not taste.
35
+ It does read design tokens. [`reticle_look { action: "element" }`](/tools/inspect) reports whether a colour came from your theme or from someone's clipboard. But that is a consistency check, not taste.
42
36
 
43
37
  </Accordion>
44
38
 
@@ -58,7 +52,7 @@ Measured on the same app: Reticle caught 10 of 10 injected bugs, Playwright MCP
58
52
  </Accordion>
59
53
 
60
54
  <Accordion title="Can I run several apps or tabs at once?">
61
- Yes. Each is a session. Pass `sessionId` to any tool when more than one is connected, and [`reticle_sessions`](/tools/sessions) lists them.
55
+ Yes. Each is a session. Pass `sessionId` to any tool when more than one is connected, and [`reticle_session { action: "list" }`](/tools/sessions) lists them.
62
56
 
63
57
  Reticle scopes to your project and prefers the active tab, and it **refuses rather than guesses** when the choice is ambiguous. A refusal you can act on beats a silent pick of the wrong tab.
64
58
 
@@ -74,9 +68,9 @@ If you believe an `unknown` is wrong, report it. Reticle's own response says as
74
68
  </Accordion>
75
69
 
76
70
  <Accordion title="Does my data leave the machine?">
77
- Your app's data, no. Reticle is dev-only, localhost-only and offline. Credential-shaped values are redacted before they leave the page. You will see `[REDACTED]` in request bodies without configuring anything.
71
+ Your app's data, no. Reticle is dev-only and localhost-only, and nothing from the app under test (DOM, network bodies, console, state, source) is sent. Credential-shaped values are redacted before they leave the page. You will see `[REDACTED]` in request bodies without configuring anything.
78
72
 
79
- Anonymous usage telemetry is sent separately and can be turned off with `reticle telemetry disable`.
73
+ Anonymous usage counts are sent by default, separately, and carry nothing from your app. `reticle telemetry disable`, `RETICLE_TELEMETRY=0` or `DO_NOT_TRACK=1` turns them off.
80
74
 
81
75
  <Card title="Exactly what is collected" icon="chart-line" href="/telemetry">
82
76
  The complete list. If it is not on that page, it is not sent.
@@ -85,7 +79,7 @@ Anonymous usage telemetry is sent separately and can be turned off with `reticle
85
79
 
86
80
  <Accordion title="Does it work with WebGL / react-three-fiber?">
87
81
  Partially. Reticle's model of an app is DOM + store + network. A WebGL or react-three-fiber
88
- subtree is a single `<canvas>` to `reticle_query` / `reticle_snapshot`: Reticle cannot pick faces
82
+ subtree is a single `<canvas>` to `reticle_look { action: "find" }` / `reticle_look { action: "page" }`: Reticle cannot pick faces
89
83
  or drive the camera inside the scene.
90
84
 
91
85
  DOM, registered store state, and network **around** the canvas still work, and that is often where the consequential bugs live (import counts, mesh stats, solver results that contradict the UI). `init` prints this limit when it detects react-three-fiber so an adopting team does not discover it only after trying to verify the feature they care about.
@@ -93,9 +87,9 @@ DOM, registered store state, and network **around** the canvas still work, and t
93
87
  </Accordion>
94
88
 
95
89
  <Accordion title="Is Reticle proven to make agents fix more bugs?">
96
- No, and we are not going to claim it. A controlled test of fix-rate did **not** show an improvement. The benchmark was confounded in ways we can point at, and we consider the question open.
90
+ No. A fix-loop test on a small fixture found the same fix rate with and without Reticle, at two model tiers, and the Reticle arm made more tool calls. The fixture was small enough to read whole and the pass check looked for a string rather than behaviour, so the question is open. [Why Reticle](/why-reticle#what-the-fix-rate-test-measured) has the details.
97
91
 
98
- What is measured is detection: 10 of 10 injected regressions caught, with zero false alarms on a known-good control, against 9 and 8 for the alternatives.
92
+ What Reticle is built to improve is the verdict: in the 88-bug head-to-head against a Playwright script it reported one false green to the script's 29, and on a known-good control it raised no false alarms.
99
93
 
100
94
  </Accordion>
101
95
 
@@ -108,4 +102,12 @@ What is measured is detection: 10 of 10 injected regressions caught, with zero f
108
102
  Which tools are advertised directly, and why each earned its slot.
109
103
  </Card>
110
104
  </Accordion>
105
+
106
+ <Accordion title="Can I use it for security, accessibility, performance or SEO checks?">
107
+ Yes, for how your own running app behaves: access control by role, calls that must never fire, CSP violations, roles and focus, request counts and long tasks, titles, headings, links and redirects. It pairs with a vulnerability scanner, an accessibility audit tool such as axe and an SEO crawler, which cover what an in-app check does not.
108
+
109
+ <Card title="Use cases" icon="list-check" href="/use-cases">
110
+ What each one checks, with a call you can run.
111
+ </Card>
112
+ </Accordion>
111
113
  </AccordionGroup>
@@ -46,18 +46,10 @@ If `~/.reticle/pairing-token` does not exist yet, run any `reticle` command once
46
46
 
47
47
  This is the shortest path to seeing Reticle work, and it needs no MCP client at all.
48
48
 
49
- Open the app in a browser. Use a real one, or:
49
+ Ask for a verdict, naming the app's url. With no tab open on that origin, Reticle opens one in a browser it drives (Playwright's Chromium, or an installed Chrome or Edge when that is missing) and closes it afterwards; a tab you already have open is used instead:
50
50
 
51
51
  ```bash
52
- node -e "require('playwright').chromium.launch({headless:true})
53
- .then(b=>b.newPage().then(p=>p.goto('http://localhost:4310/')
54
- .then(()=>new Promise(r=>setTimeout(r,60000)))))" &
55
- ```
56
-
57
- Then ask for a verdict:
58
-
59
- ```bash
60
- node server/dist/command/cli.js verify "" --port 4400 \
52
+ node server/dist/command/cli.js verify http://localhost:4310/ --port 4400 \
61
53
  --expect '{"kind":"text","contains":"Reticle"}'
62
54
  ```
63
55
 
@@ -72,7 +64,7 @@ That is the whole product in one line: a claim, checked against a running page,
72
64
  Try making it fail, because a tool that only ever says yes is not telling you anything:
73
65
 
74
66
  ```bash
75
- node server/dist/command/cli.js verify "" --port 4400 \
67
+ node server/dist/command/cli.js verify http://localhost:4310/ --port 4400 \
76
68
  --expect '{"kind":"text","contains":"definitely not on this page"}'
77
69
  ```
78
70
 
@@ -80,8 +72,8 @@ node server/dist/command/cli.js verify "" --port 4400 \
80
72
 
81
73
  If your editor has the Reticle MCP server, the same app is now drivable. The four calls worth knowing, in the order you actually use them:
82
74
 
83
- - `reticle_snapshot`: what is on the page
84
- - `reticle_query { by: "testid", value: "..." }`: find one thing, cheaply
75
+ - `reticle_look { action: "page" }`: what is on the page
76
+ - `reticle_look { action: "find", by: "testid", value: "..." }`: find one thing, cheaply
85
77
  - `reticle_act_and_wait { ref, action, until }` (**the one that produces a verdict**)
86
78
  - `reticle_observe`: everything the page did, when you need the evidence
87
79
 
package/docs/flows.md CHANGED
@@ -87,6 +87,25 @@ reticle_flow {action:"load"}({ flowName: "create-task" }) // → the flow JSON
87
87
  reticle_flow_replay({ flowName: "create-task" }) // re-resolve each anchor against the LIVE DOM, run it
88
88
  ```
89
89
 
90
+ ### Resume from a step
91
+
92
+ ```jsonc
93
+ reticle_flow_replay({ flowName: "create-task", from: 3 }) // by 0-based index
94
+ reticle_flow_replay({ flowName: "create-task", from: "submit" }) // or by a step's `id`
95
+ ```
96
+
97
+ There is no state to restore, so the steps before `from` run again quickly as setup: their actions run, their `expect`s are not checked, and they are not reported. Replay is then checked and reported from `from` on. If a setup step fails, it is reported, because the resume never reached the step you asked for. A setup step declared `"effect": "commits"` refuses the resume and the whole flow replays, so resuming never re-sends a payment or a message silently. A `from` that names no step is an error, not a full replay.
98
+
99
+ ## Flows saved from a drive
100
+
101
+ Every session's driving is saved as a flow at teardown when it declared a consequence. These flows:
102
+
103
+ - are named from the `intent` you pass to `reticle_act_and_wait` (`drive-close-an-issue`), and carry it as the flow's `intent`;
104
+ - merge: another drive that starts on the same page and takes the same steps updates the existing file instead of adding a copy. The existing flow keeps its name and everything it already says; the new drive only fills gaps. Flows you named yourself are never merged;
105
+ - record, on every step, the `page` it ran on and the `endPage` it led to;
106
+ - declare `requires` (the start page) and `ensures` (the end page) as route claims, so `canFollow` can tell which flows chain. Replay treats a required start page as met by navigating there;
107
+ - carry `author: { agent, person }`: the MCP client that drove it and the email of whoever ran `reticle login` on that machine. Either is left out when it is not known.
108
+
90
109
  ## Delete a flow
91
110
 
92
111
  ```jsonc
@@ -105,7 +124,7 @@ A renamed or obsolete flow otherwise lingers in `reticle_flow {action:"list"}` a
105
124
  - `drift`: an anchor missed (a testid was renamed, or a signal never fired). The result is **legible**, never a blind failure: `{ step, anchor, drift: { reasonKind: "testid_not_found", nearest: "send-message" } }`. (This is the "whose fault is it" principle.)
106
125
  - `error`: the flow file is missing/invalid, or a resolved action failed. Runtime failures include the failed step and a top-level error envelope.
107
126
 
108
- A testid-_preserving_ refactor (you moved markup but kept the testids) still replays green. A step whose element has **no testid** is anchored on its component + source location (`{ kind: "component", component, source: { file, line } }`), an auto-derived stable anchor, so a flow records cleanly with zero hand-added testids and replay re-resolves it via `reticle_query by:'component'`.
127
+ A testid-_preserving_ refactor (you moved markup but kept the testids) still replays green. A step whose element has **no testid** is anchored on its component + source location (`{ kind: "component", component, source: { file, line } }`), an auto-derived stable anchor, so a flow records cleanly with zero hand-added testids and replay re-resolves it via `reticle_look { action: "find" } by:'component'`.
109
128
 
110
129
  ### The decision envelope: what to do next, not just pass/fail
111
130
 
@@ -175,7 +194,7 @@ With `apply: false` the flow file is **never modified**; you get the proposed di
175
194
  | `reticle_flow {action:"list"}` | `{}` | flows on disk |
176
195
  | `reticle_flow {action:"load"}` | `{ flowName }` | the flow JSON |
177
196
  | `reticle_flow {action:"delete"}` | `{ flowName }` | `{ deleted: true }`, or `{ error, code }` (`not_found` when no such flow) |
178
- | `reticle_flow_replay` | `{ flowName }` | `{ status, steps, decision? }` (decision on drift/fail) |
197
+ | `reticle_flow_replay` | `{ flowName, from?, sweep? }` | `{ status, steps, decision? }` (decision on drift/fail); `from` resumes at a step |
179
198
  | `reticle_verify {action:"flows"}` | `{ names?, sessionId? }` | suite verdict `{ status, passed, failed, failures[] }` |
180
199
  | `reticle_verify { action: "heal" }` | `{ flowName, apply? }` | propose / apply nearest-match rebind |
181
200
  | `reticle_annotate` | `{ kind, … }` | compile a structured annotation into the flow |