@reticlehq/server 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/SKILL.md +130 -813
  2. package/dist/bridge/bridge.d.ts +23 -0
  3. package/dist/bridge/bridge.js +80 -2
  4. package/dist/bridge/bridge.test-harness.d.ts +5 -0
  5. package/dist/bridge/bridge.test-harness.js +6 -4
  6. package/dist/capsule/capsule-store.d.ts +2 -2
  7. package/dist/capsule/causal-summary.d.ts +16 -1
  8. package/dist/capsule/causal-summary.js +2 -1
  9. package/dist/cli/cli-doctor.js +41 -13
  10. package/dist/cli/cli-flow-commands.js +8 -8
  11. package/dist/cli/cli-launch.d.ts +0 -17
  12. package/dist/cli/cli-launch.js +0 -28
  13. package/dist/cli/cli-parse.d.ts +1 -1
  14. package/dist/cli/cli-parse.js +1 -1
  15. package/dist/cli/cli-port.d.ts +24 -0
  16. package/dist/cli/cli-port.js +66 -35
  17. package/dist/cli/cli-verify.d.ts +21 -0
  18. package/dist/cli/cli-verify.js +45 -1
  19. package/dist/cli/config-discovery.d.ts +35 -0
  20. package/dist/cli/config-discovery.js +163 -0
  21. package/dist/cli/doctor-daemon-line.js +4 -1
  22. package/dist/cli/doctor-project-line.d.ts +37 -0
  23. package/dist/cli/doctor-project-line.js +35 -0
  24. package/dist/cli/doctor-rows.d.ts +51 -0
  25. package/dist/cli/doctor-rows.js +54 -0
  26. package/dist/cli/doctor-sessions-line.js +6 -3
  27. package/dist/cli/drive-attach.d.ts +99 -0
  28. package/dist/cli/drive-attach.js +203 -0
  29. package/dist/cli/drive-command.d.ts +13 -0
  30. package/dist/cli/drive-command.js +103 -0
  31. package/dist/cli/open-note.d.ts +20 -0
  32. package/dist/cli/open-note.js +37 -0
  33. package/dist/cli/status-next-action.d.ts +37 -0
  34. package/dist/cli/status-next-action.js +61 -0
  35. package/dist/cli.js +65 -70
  36. package/dist/crawl/crawl.d.ts +7 -2
  37. package/dist/crawl/crawl.js +52 -9
  38. package/dist/ee/audit-log.js +2 -2
  39. package/dist/events/body-failures.d.ts +2 -2
  40. package/dist/events/contradiction-folds.d.ts +45 -0
  41. package/dist/events/contradiction-folds.js +42 -0
  42. package/dist/events/contradictions.d.ts +51 -1
  43. package/dist/events/contradictions.js +26 -7
  44. package/dist/events/declared.d.ts +45 -0
  45. package/dist/events/declared.js +73 -0
  46. package/dist/events/echo-mismatch.d.ts +9 -3
  47. package/dist/events/echo-mismatch.js +33 -5
  48. package/dist/events/event-filters.d.ts +1 -1
  49. package/dist/events/event-filters.js +7 -4
  50. package/dist/events/predicate-eval.d.ts +17 -91
  51. package/dist/events/predicate-eval.js +85 -326
  52. package/dist/events/predicate-parse.js +31 -3
  53. package/dist/events/predicate-route.d.ts +32 -0
  54. package/dist/events/predicate-route.js +102 -0
  55. package/dist/events/predicate-schema.d.ts +99 -0
  56. package/dist/events/predicate-schema.js +309 -0
  57. package/dist/events/predicate.d.ts +8 -0
  58. package/dist/events/predicate.js +127 -5
  59. package/dist/events/stale-response.d.ts +2 -3
  60. package/dist/events/stale-response.js +49 -2
  61. package/dist/events/unit-mismatch.d.ts +2 -2
  62. package/dist/flows/annotate-tools.js +3 -0
  63. package/dist/flows/annotate.js +18 -4
  64. package/dist/flows/coverage.d.ts +38 -5
  65. package/dist/flows/coverage.js +30 -4
  66. package/dist/flows/decision.d.ts +15 -0
  67. package/dist/flows/decision.js +1 -1
  68. package/dist/flows/flow-replay-run.js +8 -1
  69. package/dist/flows/flow-tools.js +9 -1
  70. package/dist/flows/replay.js +3 -0
  71. package/dist/flows/verify-change-tools.js +1 -1
  72. package/dist/honesty/accepted-write.d.ts +3 -0
  73. package/dist/honesty/accepted-write.js +8 -3
  74. package/dist/honesty/already-true.js +15 -0
  75. package/dist/honesty/blind-spots.js +4 -0
  76. package/dist/honesty/honesty.d.ts +11 -0
  77. package/dist/honesty/honesty.js +1 -0
  78. package/dist/honesty/uncaptured-bodies.d.ts +4 -2
  79. package/dist/honesty/uncaptured-bodies.js +9 -2
  80. package/dist/honesty/unread-outcome.d.ts +8 -0
  81. package/dist/honesty/unread-outcome.js +36 -9
  82. package/dist/honesty/unsettled.d.ts +9 -0
  83. package/dist/honesty/unsettled.js +7 -3
  84. package/dist/honesty/verified.d.ts +29 -3
  85. package/dist/honesty/verified.js +67 -8
  86. package/dist/http-server.d.ts +19 -0
  87. package/dist/http-server.js +202 -4
  88. package/dist/impact/impact-recorder.d.ts +23 -0
  89. package/dist/impact/impact-recorder.js +76 -0
  90. package/dist/impact/impact-store.d.ts +47 -0
  91. package/dist/impact/impact-store.js +171 -0
  92. package/dist/index.d.ts +25 -0
  93. package/dist/index.js +60 -10
  94. package/dist/init/agent-rules.d.ts +20 -0
  95. package/dist/init/agent-rules.js +36 -0
  96. package/dist/init/astro-patch.js +27 -1
  97. package/dist/init/confirm.d.ts +21 -3
  98. package/dist/init/confirm.js +32 -5
  99. package/dist/init/connect-steps.d.ts +2 -0
  100. package/dist/init/connect-steps.js +50 -0
  101. package/dist/init/csp-check.d.ts +38 -0
  102. package/dist/init/csp-check.js +92 -0
  103. package/dist/init/csp-doctor.d.ts +25 -0
  104. package/dist/init/csp-doctor.js +49 -0
  105. package/dist/init/cursor.js +6 -9
  106. package/dist/init/detect.d.ts +8 -0
  107. package/dist/init/detect.js +33 -3
  108. package/dist/init/mcp-clients.d.ts +1 -0
  109. package/dist/init/mcp-clients.js +51 -7
  110. package/dist/init/mcp.d.ts +16 -2
  111. package/dist/init/mcp.js +23 -1
  112. package/dist/init/plan-framework.d.ts +10 -0
  113. package/dist/init/plan-framework.js +25 -1
  114. package/dist/init/plan.d.ts +7 -2
  115. package/dist/init/plan.js +66 -30
  116. package/dist/init/run.js +11 -5
  117. package/dist/init/slash-command.d.ts +1 -1
  118. package/dist/init/slash-command.js +12 -4
  119. package/dist/init/snippets.d.ts +1 -1
  120. package/dist/init/snippets.js +35 -7
  121. package/dist/init/vite-config.js +24 -2
  122. package/dist/input/network-mock-tools.js +8 -1
  123. package/dist/input/scroll-find.d.ts +6 -0
  124. package/dist/input/scroll-find.js +17 -2
  125. package/dist/input/scroll-tools.js +4 -0
  126. package/dist/journal/attach-journal.d.ts +9 -1
  127. package/dist/journal/attach-journal.js +13 -2
  128. package/dist/journal/journal-query.d.ts +13 -0
  129. package/dist/journal/journal-query.js +28 -3
  130. package/dist/journal/workspace-gitignore.d.ts +10 -0
  131. package/dist/journal/workspace-gitignore.js +59 -0
  132. package/dist/license/license.d.ts +69 -4
  133. package/dist/license/license.js +84 -15
  134. package/dist/mcp/mcp-proxy.d.ts +7 -93
  135. package/dist/mcp/mcp-proxy.js +183 -250
  136. package/dist/mcp/mcp.d.ts +27 -2
  137. package/dist/mcp/mcp.js +77 -36
  138. package/dist/mcp/proxy-daemon-probe.d.ts +8 -0
  139. package/dist/mcp/proxy-daemon-probe.js +48 -0
  140. package/dist/mcp/proxy-handshake.d.ts +82 -1
  141. package/dist/mcp/proxy-handshake.js +110 -3
  142. package/dist/mcp/proxy-lifecycle.d.ts +10 -1
  143. package/dist/mcp/proxy-lifecycle.js +11 -2
  144. package/dist/mcp/proxy-log.d.ts +63 -0
  145. package/dist/mcp/proxy-log.js +132 -0
  146. package/dist/mcp/server-instructions.d.ts +17 -0
  147. package/dist/mcp/server-instructions.js +56 -0
  148. package/dist/mcp/shared-params.d.ts +27 -0
  149. package/dist/mcp/shared-params.js +36 -0
  150. package/dist/mcp/sse-frame-parser.d.ts +19 -0
  151. package/dist/mcp/sse-frame-parser.js +41 -0
  152. package/dist/project/reticle-dir.d.ts +2 -0
  153. package/dist/project/reticle-dir.js +1 -0
  154. package/dist/session/connection-memory.d.ts +59 -0
  155. package/dist/session/connection-memory.js +108 -0
  156. package/dist/session/dev-command.d.ts +26 -0
  157. package/dist/session/dev-command.js +89 -0
  158. package/dist/session/dev-server-probe.d.ts +68 -0
  159. package/dist/session/dev-server-probe.js +102 -7
  160. package/dist/session/last-act.d.ts +26 -0
  161. package/dist/session/last-act.js +35 -1
  162. package/dist/session/live-control-tools.js +42 -5
  163. package/dist/session/no-session-diagnosis.d.ts +55 -2
  164. package/dist/session/no-session-diagnosis.js +199 -22
  165. package/dist/session/no-session-next-action.d.ts +46 -0
  166. package/dist/session/no-session-next-action.js +85 -0
  167. package/dist/session/no-session-watch.d.ts +40 -1
  168. package/dist/session/no-session-watch.js +172 -11
  169. package/dist/session/pending-commands.d.ts +12 -0
  170. package/dist/session/pending-commands.js +13 -1
  171. package/dist/session/session-manager.d.ts +4 -0
  172. package/dist/session/session-manager.js +35 -5
  173. package/dist/session/session-readiness.js +1 -1
  174. package/dist/session/session.d.ts +10 -1
  175. package/dist/session/session.js +146 -5
  176. package/dist/telemetry/app-instrumented.d.ts +8 -0
  177. package/dist/telemetry/app-instrumented.js +10 -0
  178. package/dist/telemetry/bug-found.d.ts +18 -0
  179. package/dist/telemetry/bug-found.js +14 -0
  180. package/dist/telemetry/daemon-telemetry.js +29 -0
  181. package/dist/telemetry/install-source.d.ts +20 -0
  182. package/dist/telemetry/install-source.js +34 -0
  183. package/dist/telemetry/instrumentation-stall.d.ts +57 -0
  184. package/dist/telemetry/instrumentation-stall.js +103 -0
  185. package/dist/telemetry/license-activation.d.ts +48 -0
  186. package/dist/telemetry/license-activation.js +57 -0
  187. package/dist/telemetry/session-metrics.d.ts +8 -0
  188. package/dist/telemetry/session-metrics.js +10 -0
  189. package/dist/telemetry/telemetry.d.ts +3 -1
  190. package/dist/telemetry/telemetry.js +23 -3
  191. package/dist/tools/act-tools.js +166 -29
  192. package/dist/tools/assert-verdict.js +32 -7
  193. package/dist/tools/browser-tools.js +10 -0
  194. package/dist/tools/dynamic-tools.js +17 -8
  195. package/dist/tools/error-recovery.js +10 -6
  196. package/dist/tools/feedback-tools.js +31 -6
  197. package/dist/tools/invoke-tool.d.ts +1 -1
  198. package/dist/tools/invoke-tool.js +30 -8
  199. package/dist/tools/lease-hint.d.ts +30 -8
  200. package/dist/tools/lease-hint.js +65 -14
  201. package/dist/tools/lease-tools.d.ts +11 -0
  202. package/dist/tools/lease-tools.js +141 -96
  203. package/dist/tools/observe-tools.js +9 -3
  204. package/dist/tools/react-grace.d.ts +4 -0
  205. package/dist/tools/react-grace.js +6 -3
  206. package/dist/tools/read-tools.js +47 -1
  207. package/dist/tools/resolve-target.d.ts +33 -0
  208. package/dist/tools/resolve-target.js +53 -0
  209. package/dist/tools/settle-in-flight.d.ts +15 -0
  210. package/dist/tools/settle-in-flight.js +42 -2
  211. package/dist/tools/snapshot-delta.d.ts +3 -0
  212. package/dist/tools/snapshot-delta.js +67 -6
  213. package/dist/tools/tool-kit.d.ts +38 -3
  214. package/dist/tools/tool-names.d.ts +2 -0
  215. package/dist/tools/tool-names.js +2 -0
  216. package/dist/tools/tool-surface.d.ts +60 -0
  217. package/dist/tools/tool-surface.js +84 -19
  218. package/dist/tools/tools.js +61 -5
  219. package/dist/tools/unadvertised-help.js +9 -6
  220. package/docs/README.md +1 -2
  221. package/docs/actions.mdx +41 -8
  222. package/docs/agent-cheatsheet.md +6 -4
  223. package/docs/architecture.md +6 -0
  224. package/docs/benchmarks.md +60 -12
  225. package/docs/best-practices.mdx +11 -6
  226. package/docs/capabilities.mdx +204 -57
  227. package/docs/cli/affected.mdx +6 -4
  228. package/docs/cli/capsules.mdx +9 -8
  229. package/docs/cli/cloud.mdx +30 -4
  230. package/docs/cli/doctor.mdx +41 -8
  231. package/docs/cli/drive.mdx +34 -15
  232. package/docs/cli/gate.mdx +6 -6
  233. package/docs/cli/help.mdx +8 -6
  234. package/docs/cli/hunt.mdx +20 -4
  235. package/docs/cli/identify.mdx +16 -2
  236. package/docs/cli/init.mdx +35 -8
  237. package/docs/cli/kill.mdx +5 -5
  238. package/docs/cli/license.mdx +1 -1
  239. package/docs/cli/open.mdx +6 -0
  240. package/docs/cli/restart.mdx +6 -6
  241. package/docs/cli/serve.mdx +10 -3
  242. package/docs/cli/status.mdx +29 -5
  243. package/docs/cli/stop.mdx +8 -0
  244. package/docs/cli/verify.mdx +20 -1
  245. package/docs/cli/version.mdx +1 -1
  246. package/docs/cli/watch.mdx +9 -2
  247. package/docs/cli.mdx +44 -22
  248. package/docs/debugging.md +3 -1
  249. package/docs/deploy-checks.md +31 -2
  250. package/docs/desktop.mdx +276 -80
  251. package/docs/docs.json +13 -12
  252. package/docs/enterprise.md +9 -3
  253. package/docs/faq.mdx +49 -35
  254. package/docs/fixtures.md +2 -0
  255. package/docs/flows.md +7 -5
  256. package/docs/for-agents.md +3 -3
  257. package/docs/frameworks.mdx +46 -7
  258. package/docs/gate-plan.md +4 -2
  259. package/docs/gates.md +8 -6
  260. package/docs/getting-started.md +53 -12
  261. package/docs/human-control.md +2 -0
  262. package/docs/index.mdx +43 -1
  263. package/docs/install-agentic.mdx +13 -4
  264. package/docs/install-manual.mdx +6 -4
  265. package/docs/instrumentation.mdx +7 -1
  266. package/docs/integration-patterns.md +3 -1
  267. package/docs/local-registry.md +4 -2
  268. package/docs/multi-agent-testing.md +3 -1
  269. package/docs/packages/babel-plugin.mdx +15 -6
  270. package/docs/packages/browser.mdx +2 -2
  271. package/docs/packages/core.mdx +26 -16
  272. package/docs/packages/electron.mdx +5 -7
  273. package/docs/packages/eslint-plugin.mdx +5 -3
  274. package/docs/packages/next.mdx +3 -5
  275. package/docs/packages/react.mdx +3 -3
  276. package/docs/packages/server.mdx +2 -2
  277. package/docs/packages/tauri.mdx +6 -6
  278. package/docs/packages/test.mdx +18 -7
  279. package/docs/packages/vite-plugin.mdx +4 -6
  280. package/docs/packages.mdx +14 -13
  281. package/docs/platform-integration.md +2 -0
  282. package/docs/predicates.mdx +76 -7
  283. package/docs/quickstart.mdx +51 -5
  284. package/docs/recipes.mdx +63 -23
  285. package/docs/skill-file.mdx +3 -1
  286. package/docs/state-management.mdx +2 -0
  287. package/docs/system-map.md +3 -1
  288. package/docs/telemetry-contract.md +33 -5
  289. package/docs/telemetry-events.mdx +63 -18
  290. package/docs/telemetry.md +8 -5
  291. package/docs/testing.md +35 -1
  292. package/docs/token-efficiency.md +8 -2
  293. package/docs/tools-act-and-wait.mdx +64 -14
  294. package/docs/tools-act-sequence.mdx +34 -17
  295. package/docs/tools-act.mdx +24 -11
  296. package/docs/tools-assert.mdx +69 -10
  297. package/docs/tools-console.mdx +11 -7
  298. package/docs/tools-inspect.mdx +11 -8
  299. package/docs/tools-navigate.mdx +41 -5
  300. package/docs/tools-network.mdx +42 -9
  301. package/docs/tools-observe.mdx +66 -10
  302. package/docs/tools-overview.mdx +26 -23
  303. package/docs/tools-query.mdx +52 -8
  304. package/docs/tools-session-and-feedback.mdx +35 -9
  305. package/docs/tools-sessions.mdx +39 -10
  306. package/docs/tools-snapshot.mdx +57 -3
  307. package/docs/tools-state.mdx +24 -1
  308. package/docs/tools-tools-and-run.mdx +55 -24
  309. package/docs/tools-wait-for.mdx +34 -15
  310. package/docs/troubleshooting.mdx +80 -10
  311. package/docs/usage.md +46 -24
  312. package/docs/vs-chrome-devtools-mcp.mdx +41 -0
  313. package/docs/vs-playwright-mcp.mdx +42 -2
  314. package/docs/vs-screenshots.mdx +42 -1
  315. package/docs/what-is-reticle.mdx +1 -1
  316. package/docs/why-reticle.mdx +3 -1
  317. package/package.json +5 -5
  318. package/docs/desktop-apps.md +0 -265
@@ -40,6 +40,36 @@ export const TOOL_SURFACE = {
40
40
  DEFAULT: 'default',
41
41
  /** Every tool advertised directly, WITH output schemas. A verification switch — see above. */
42
42
  ALL: 'all',
43
+ /**
44
+ * The smallest surface that can still produce a VERDICT. Not a profile — a cost switch.
45
+ *
46
+ * Measured on the wire: a verification that names its own target costs one `act_and_wait` call,
47
+ * and 5,480 of its 5,909 tokens are the advertised surface re-sent for that single turn. The
48
+ * answers cost 430. So on the path where the caller already knows what to assert, almost the
49
+ * entire bill is the menu, and the menu is the thing to cut.
50
+ *
51
+ * Deliberately NOT the default, and now MEASURED rather than inherited. Run over the same 30-bug
52
+ * set that the default surface scores 23/27 detection and 2/29 false alarms on:
53
+ *
54
+ * detection 24/28 (held)
55
+ * FALSE ALARMS 7/30 (was 2/29 — TRIPLED)
56
+ * tokens 113,599 per run (was 179,959 — 37% cheaper)
57
+ *
58
+ * The five new false alarms are not scattered. They are `mutation-leak` and
59
+ * `generate-blast-filter` (state), `kpi-deploys-tamper` (business-logic), `debounce-broken`
60
+ * (timing) and `route-stuck-deployments` (routing) — precisely the classes whose evidence lives in
61
+ * `reticle_state`, `reticle_network` and `reticle_observe`, which this surface does not advertise.
62
+ * Strip the observation tools and the model stops observing: it reaches for the verdict without
63
+ * the evidence and calls a healthy build broken.
64
+ *
65
+ * So the retired `dynamic` finding is confirmed, with a sharper mechanism than "accuracy drops".
66
+ * 37% of the tokens is not worth trading for the one metric this product actually wins on, and
67
+ * this must not become the default on the strength of the token number alone.
68
+ *
69
+ * It remains correct for a caller who has ALREADY decided what to assert — there the assertion
70
+ * supplies the evidence the surface would otherwise have to go and find.
71
+ */
72
+ VERIFY: 'verify',
43
73
  };
44
74
  /**
45
75
  * The switch that turns on the full, output-schema-carrying surface. A boolean, not a menu.
@@ -48,6 +78,8 @@ export const TOOL_SURFACE = {
48
78
  * users to shop among alternatives that did not meaningfully differ.
49
79
  */
50
80
  export const ADVERTISE_ALL_ENV = 'RETICLE_ADVERTISE_ALL_TOOLS';
81
+ /** Opt into the smallest verdict-capable surface. Read by the DAEMON at startup, like the others. */
82
+ export const VERIFY_SURFACE_ENV = 'RETICLE_VERIFY_SURFACE';
51
83
  /**
52
84
  * The retired setting, still read so nobody's shell profile breaks.
53
85
  *
@@ -144,26 +176,51 @@ export const CORE_TOOL_NAMES = new Set([
144
176
  // deleting the instruction from the lease and the pause hint — deletes the handback protocol,
145
177
  // because there is nowhere else those two calls are ever named.
146
178
  ReticleTool.SESSION,
147
- // CAPABILITIES is the highest orientation-per-token tool on the surface: one call returns the app's
148
- // whole testable surface — its testids, signals, stores and named flows — and `{fromDisk:true}`
149
- // returns the same from a checked-in contract with NO browser attached, so an agent with zero
150
- // context starts oriented instead of exploring.
151
- //
152
- // Same argument as the four above, and it is the fifth time this file has made it: a tool an agent
153
- // must already know about, and reach through reticle_run, is a tool that never gets called.
154
- //
155
- // The cost is real and is NOT the bargain #262 claimed. That issue argued the marginal cost sits
156
- // "well below the 274-token average" because the input schema is small; measured, its description
157
- // plus schema is closer to 325 tokens, which is above the average rather than below it. This is
158
- // also a straight +1 rather than a swap: the tools that write-up proposed demoting (`project`,
159
- // `domain`) were never in the default set to begin with.
160
- //
161
- // So it is a deliberate bet, taken with that correction on the table: that orientation replaces
162
- // exploratory snapshots, which are among the most expensive calls an agent makes. The measurement
163
- // that settles it is whether sessions calling this early take fewer and cheaper snapshots than
164
- // those that do not. If that does not show up, this is the entry to revert.
179
+ ]);
180
+ /**
181
+ * The extended surface: what `all` advertises BEYOND the default set.
182
+ *
183
+ * `all` used to mean "every tool in the registry", and that is no longer allowed to be true. Cursor
184
+ * enforces a limit of 40 tools across every connected MCP server COMBINED, so a server advertising
185
+ * 48 by itself can push a user's other servers out or be dropped wholesale. The budget is a count,
186
+ * so no amount of trimming parameter prose buys anything back — only advertising fewer names does.
187
+ *
188
+ * Everything omitted stays in the registry, stays catalogued by `reticle_tools`, and stays callable
189
+ * by name through `reticle_run { tool, args }`. The cost is one discovery hop on the cold tail, which
190
+ * is the same trade the default surface has always made, applied one level further out.
191
+ *
192
+ * What it costs that is NOT free: `all` is the only surface that carries `outputSchema`, which is
193
+ * what makes the MCP layer validate tool OUTPUT — the check that once caught `reticle_verify_change`
194
+ * returning a payload its own schema rejected. The tools left off this list no longer get that
195
+ * validation from the wire. That defect class is now uncovered for them, and it is written down here
196
+ * rather than discovered later.
197
+ *
198
+ * Chosen as the capabilities an agent plausibly reaches for once the verify loop is not enough:
199
+ * orientation, the record/replay flow loop, visual evidence, and the three fault-injection controls.
200
+ */
201
+ export const EXTENDED_TOOL_NAMES = new Set([
202
+ // Demoted from the default set to make room under the cap. It was added as an explicit bet that
203
+ // orientation replaces exploratory snapshots, and the measurement that would have settled that
204
+ // bet was never run — so when the budget became a hard count, the unproven entry is the one that
205
+ // gives way. Still one `reticle_run` hop from any agent that wants it.
165
206
  ReticleTool.CAPABILITIES,
207
+ ReticleTool.FLOW_SAVE,
208
+ ReticleTool.FLOW_REPLAY,
209
+ ReticleTool.RECORD,
210
+ ReticleTool.SCREENSHOT,
211
+ ReticleTool.VISUAL_DIFF,
212
+ ReticleTool.CLOCK,
213
+ ReticleTool.NETWORK_MOCK,
214
+ ReticleTool.STORAGE,
215
+ // Merged change/flows/affected/coverage/crawl — one name where three of these used to sit.
216
+ ReticleTool.VERIFY,
166
217
  ]);
218
+ /**
219
+ * The verify surface: one acting tool that returns a verdict, plus the two meta-tools that reach
220
+ * everything else. `act_and_wait` can resolve its own target, so no query tool is needed to name an
221
+ * element — that round trip was half the token cost of a verification.
222
+ */
223
+ export const VERIFY_TOOL_NAMES = new Set([ReticleTool.ACT_AND_WAIT]);
167
224
  /** Is the truthy form of a boolean env var set? `1`, `true`, `yes` — anything else is off. */
168
225
  function envFlagOn(raw) {
169
226
  if (raw === undefined)
@@ -178,6 +235,8 @@ function envFlagOn(raw) {
178
235
  * switch decides, and the retired setting is honoured last so an old shell profile still works.
179
236
  */
180
237
  export function resolveToolSurface(explicit) {
238
+ if (explicit === TOOL_SURFACE.VERIFY)
239
+ return TOOL_SURFACE.VERIFY;
181
240
  if (explicit === TOOL_SURFACE.ALL)
182
241
  return TOOL_SURFACE.ALL;
183
242
  if (explicit === TOOL_SURFACE.DEFAULT)
@@ -185,6 +244,8 @@ export function resolveToolSurface(explicit) {
185
244
  const retiredExplicit = explicit === undefined ? undefined : RETIRED_PROFILE_VALUES[explicit];
186
245
  if (retiredExplicit !== undefined)
187
246
  return retiredExplicit;
247
+ if (envFlagOn(process.env[VERIFY_SURFACE_ENV]))
248
+ return TOOL_SURFACE.VERIFY;
188
249
  if (envFlagOn(process.env[ADVERTISE_ALL_ENV]))
189
250
  return TOOL_SURFACE.ALL;
190
251
  const retiredEnv = RETIRED_PROFILE_VALUES[process.env[TOOL_PROFILE_ENV] ?? ''];
@@ -230,7 +291,11 @@ export function describeToolSurface(active, requested) {
230
291
  export function filterTools(tools, surface) {
231
292
  // CORE_TOOL_NAMES is what it always really was: the set advertised directly. It was never the
232
293
  // interesting thing about the `core` PROFILE, whose only distinction was a second name for this.
294
+ if (surface === TOOL_SURFACE.VERIFY)
295
+ return tools.filter((t) => VERIFY_TOOL_NAMES.has(t.name));
233
296
  if (surface === TOOL_SURFACE.DEFAULT)
234
297
  return tools.filter((t) => CORE_TOOL_NAMES.has(t.name));
235
- return tools;
298
+ // `all` is the extended surface, not the whole registry — the cap is a hard budget shared with
299
+ // every other MCP server the user has connected. surface-sizes.test.ts enforces it.
300
+ return tools.filter((t) => CORE_TOOL_NAMES.has(t.name) || EXTENDED_TOOL_NAMES.has(t.name));
236
301
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
2
+ import { NoSessionAction, QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
3
3
  import { ReticleTool } from './tool-names.js';
4
4
  import { withSizeCost } from '../session/output-budget.js';
5
5
  import { applySnapshotDelta, SnapshotCache } from './snapshot-delta.js';
@@ -82,6 +82,15 @@ const RAW_TOOLS = [
82
82
  .string()
83
83
  .optional()
84
84
  .describe('Present ONLY when `sessions` is empty: why nothing is connected, and the next action that fixes it. An empty list is never the end of the road — read this before concluding the app cannot be driven.'),
85
+ next_action: z
86
+ .object({
87
+ action: z.nativeEnum(NoSessionAction),
88
+ command: z.string().optional(),
89
+ port: z.number().optional(),
90
+ reason: z.string(),
91
+ })
92
+ .optional()
93
+ .describe("Present ONLY when `sessions` is empty: the same answer as `why`, executable. `command` is the LITERAL command to run, sourced from this project's own package.json scripts and lockfile — it is absent, never guessed, when the project declares no dev script. `action` is one of start_dev_server | run_init | open_app | reopen_app."),
85
94
  },
86
95
  handler: async (deps) => {
87
96
  const provider = deps.realInput;
@@ -97,7 +106,14 @@ const RAW_TOOLS = [
97
106
  // rather than only when a later tool fails for want of a session.
98
107
  if (0 === sessions.length) {
99
108
  const why = deps.sessions.noSessionHint();
100
- return { sessions, ...(why === undefined ? {} : { why }) };
109
+ // The executable half. `why` is for the human reading the transcript; this is the one the
110
+ // agent acts on, so it never has to parse a paragraph to find a command inside it.
111
+ const next = deps.sessions.noSessionNextAction();
112
+ return {
113
+ sessions,
114
+ ...(why === undefined ? {} : { why }),
115
+ ...(next === undefined ? {} : { next_action: next }),
116
+ };
101
117
  }
102
118
  return { sessions };
103
119
  },
@@ -126,11 +142,18 @@ const RAW_TOOLS = [
126
142
  .string()
127
143
  .optional()
128
144
  .describe('Indented ARIA tree of every element on the page (or the scoped subtree).'),
129
- status: z.object({ route: z.string(), title: z.string().optional() }).optional(),
145
+ status: z
146
+ .object({
147
+ route: z.string(),
148
+ title: z.string().optional(),
149
+ visibleDialogs: z.array(z.string()).optional(),
150
+ overlayHidingPage: z.string().optional(),
151
+ })
152
+ .optional(),
130
153
  mode: z
131
154
  .string()
132
155
  .optional()
133
- .describe('delta | unchanged when diff:true returned a change set.'),
156
+ .describe('full | delta | unchanged — which kind of diff response this is.'),
134
157
  delta: z
135
158
  .object({
136
159
  added: z.array(z.string()),
@@ -140,6 +163,15 @@ const RAW_TOOLS = [
140
163
  })
141
164
  .optional()
142
165
  .describe('Only present on a diff:true call that found changes.'),
166
+ changed: z
167
+ .array(z.string())
168
+ .optional()
169
+ .describe('Elements whose value changed in place (same ref, different content) — not a structural add/remove.'),
170
+ changedCount: z.number().optional().describe('Number of elements in `changed`.'),
171
+ reason: z
172
+ .string()
173
+ .optional()
174
+ .describe('Why mode is "full": first snapshot for this route, or route changed.'),
143
175
  cost: z
144
176
  .object({ bytes: z.number(), tokens: z.number() })
145
177
  .optional()
@@ -380,6 +412,15 @@ const RAW_TOOLS = [
380
412
  * Reticle build plugin in dev; absent in production builds.
381
413
  */
382
414
  source: z.string().optional(),
415
+ /**
416
+ * Why `source` is missing, when it is.
417
+ *
418
+ * Distinguishes "this element has no stamp" from "nothing on this page has one, so the
419
+ * stamping loader is not running" — the second means `file:line` is unavailable for the
420
+ * whole session and the fix is a build-config change, which used to be discoverable only by
421
+ * reading the adapter's own source.
422
+ */
423
+ sourceUnavailable: z.string().optional(),
383
424
  /**
384
425
  * Component identity from the framework adapter (@reticlehq/react).
385
426
  *
@@ -467,7 +508,7 @@ export const MERGE_PLANS = [
467
508
  },
468
509
  {
469
510
  name: ReticleTool.FLOW,
470
- description: 'Saved-flow management: { action: "list" } names every saved flow, "load" reads+validates one by flowName, "delete" removes one. The hot verbs (flow_save, flow_replay, flow_verify, flow_heal) stay separate.',
511
+ description: 'Saved-flow management: { action: "list" } names every saved flow, "load" reads+validates one by flowName, "delete" removes one. The hot verbs (flow_save, flow_replay, flow_heal) stay separate; whole-suite replay is reticle_verify { action: "flows" }.',
471
512
  members: {
472
513
  list: ReticleTool.FLOW_LIST,
473
514
  load: ReticleTool.FLOW_LOAD,
@@ -490,6 +531,21 @@ export const MERGE_PLANS = [
490
531
  // is the one that must not need a schema lookup first.
491
532
  example: { action: 'yield', mode: 'waiting' },
492
533
  },
534
+ {
535
+ name: ReticleTool.VERIFY,
536
+ description: 'What is proved, and what is not — by action. Start from a change: { action: "affected" } names which saved flows must re-verify for the files you edited (pass `files`, and/or `since` for a git ref), and "change" goes further and actually replays them, answering with one `verified` plus `because`. `unknown` there is the honest answer when NO saved flow covers the change: nothing ran, so nothing was proved, and it is never reported as green. Without a change to start from: "flows" replays every saved flow for one consolidated suite verdict (deterministic, no LLM per flow), and "coverage" lists the interactive controls you have and have NOT driven this session — an untouched list still holding the controls your change affects means you are not done. "crawl" is the no-script option: it drives every reachable control itself and reports single-channel faults (console errors, failed requests, dead controls) and CONTRADICTIONS, two channels disagreeing about the same click — the false greens a human cannot see, because a human watches the screen and the screen looks correct. DESTRUCTIVE: crawl really clicks, and may navigate or mutate state.',
537
+ members: {
538
+ change: ReticleTool.VERIFY_CHANGE,
539
+ flows: ReticleTool.FLOW_VERIFY,
540
+ affected: ReticleTool.AFFECTED,
541
+ coverage: ReticleTool.COVERAGE,
542
+ crawl: ReticleTool.CRAWL,
543
+ },
544
+ // No default action ON PURPOSE. The name implies no single member, and one of them really
545
+ // clicks: a fallthrough that guessed wrong would drive the app rather than read it. Every action
546
+ // is explicit, which for `crawl` is the whole safety story.
547
+ example: { action: 'change', files: ['src/App.tsx'] },
548
+ },
493
549
  {
494
550
  name: ReticleTool.LEASE,
495
551
  description: 'Isolated headless contexts from the shared pool: { action: "acquire" } leases one and navigates it to the app URL, "release" closes it and frees the slot.',
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * What to say when an agent calls a real Reticle tool that this profile does not advertise.
3
3
  *
4
- * The default profile advertises 16 of 46 tools. That is deliberate — every advertised tool's schema
5
- * is re-sent on every turn, and the trim is most of why `hybrid` is cheap — and the other 30 stay
6
- * fully callable through `reticle_run`, which `profile-reachability.test.ts` guards.
4
+ * The default surface advertises 18 of 48 tools, and the extended one 30. That is deliberate — every
5
+ * advertised tool's schema is re-sent on every turn, and editors budget MCP tools as a COUNT shared
6
+ * across every connected server — and everything omitted stays fully callable through `reticle_run`,
7
+ * which `surface-reachability.test.ts` guards.
7
8
  *
8
9
  * The gap was in the ERROR. The MCP SDK answers `Tool <name> not found`, which is indistinguishable
9
10
  * from "this tool does not exist", so an agent that trusts it stops trying. Reported from a real
@@ -14,7 +15,7 @@
14
15
  * and the switch that makes it stop being necessary.
15
16
  */
16
17
  import { ReticleTool } from './tool-names.js';
17
- import { TOOL_SURFACE, TOOL_PROFILE_ENV } from './tool-surface.js';
18
+ import { ADVERTISE_ALL_ENV } from './tool-surface.js';
18
19
  import { mergedNameRedirect, mergedNameMessage } from './merged-name-redirect.js';
19
20
  /**
20
21
  * Guidance for `name`, or undefined when there is nothing useful to add — the tool IS advertised (so
@@ -34,6 +35,8 @@ export function unadvertisedToolHelp(name, advertised, known) {
34
35
  `away. It is NOT missing: invoke it with ` +
35
36
  `${ReticleTool.RUN} { tool: "${name}", args: { ... } }. ` +
36
37
  `Call ${ReticleTool.TOOLS} { names: ["${name}"] } for its parameters. ` +
37
- `If you need it repeatedly, set ${TOOL_PROFILE_ENV}=${TOOL_SURFACE.ALL} to advertise ` +
38
- `every tool directly.`);
38
+ `If you need it repeatedly, start the daemon with ${ADVERTISE_ALL_ENV}=1 for the extended ` +
39
+ `surface. That is read by the DAEMON at startup, so it takes effect on the next daemon, and it ` +
40
+ `advertises MORE tools rather than all of them — editors budget MCP tools as a shared count, so ` +
41
+ `${ReticleTool.RUN} stays the way to reach the rest.`);
39
42
  }
package/docs/README.md CHANGED
@@ -49,8 +49,7 @@ Everything here is published to [docs.reticle.sh](https://docs.reticle.sh) by `d
49
49
  | [for-agents.md](for-agents.md) | how to fetch these docs as Markdown or `llms.txt` |
50
50
  | [architecture.md](architecture.md) | how it works, and why it is built this way |
51
51
  | [platform-integration.md](platform-integration.md) | Vite, Next, Remix, Astro, plain HTML |
52
- | [desktop.mdx](desktop.mdx) | why desktop is different, and what only Reticle does there |
53
- | [desktop-apps.md](desktop-apps.md) | Electron and Tauri, in full |
52
+ | [desktop.mdx](desktop.mdx) | Electron and Tauri in full: setup, IPC, screenshots, headless, troubleshooting |
54
53
  | [frameworks.mdx](frameworks.mdx) | what is supported, what is wired-but-unverified, and the wiring |
55
54
  | [state-management.mdx](state-management.mdx) | zustand, Redux, and eight adapters |
56
55
  | [instrumentation.mdx](instrumentation.mdx) | stores, signals, testids, and how verdicts get stronger |
package/docs/actions.mdx CHANGED
@@ -1,12 +1,14 @@
1
1
  ---
2
- title: Action reference: lights, camera, click
2
+ title: 'Action reference: lights, camera, click'
3
3
  sidebarTitle: Actions
4
4
  description: Every action reticle_act and reticle_act_and_wait can perform, with its arguments and the one that has a history.
5
5
  icon: hand-pointer
6
6
  ---
7
7
 
8
+ Every action Reticle can perform takes the same shape: a `ref` from [`reticle_snapshot`](/tools-snapshot) or [`reticle_query`](/tools-query), an `action` name, and an optional `args` object. The same seventeen actions are available on [`reticle_act`](/tools-act), which proves nothing, and on [`reticle_act_and_wait`](/tools-act-and-wait), which returns a verdict.
9
+
8
10
  ```json
9
- { "ref": "e42", "action": "fill", "args": { "value": "hello" } }
11
+ { "ref": "e104", "action": "fill", "args": { "value": "reticle" } }
10
12
  ```
11
13
 
12
14
  | Action | Arguments | Notes |
@@ -22,10 +24,39 @@ icon: hand-pointer
22
24
  | `submit` | none | Submits the element's `<form>` |
23
25
  | `press` | `{ text }` | The key **name**: `Escape`, `Tab`, `Enter` |
24
26
  | `scrollIntoView` | none | |
25
- | `upload` | `{ name, content?, type? }` | Sets a file on `<input type="file">` |
26
- | `drag` | `{ toRef }` | Pointer-based drag (dnd-kit, react-beautiful-dnd) plus HTML5 DnD |
27
+ | `upload` | `{ content?, name?, type? }` | Sets a file on `<input type="file">`. All three default, to `"reticle test file"`, `"file.txt"` and `"text/plain"` |
28
+ | `drag` | `{ toRef }` | Pointer-based drag (dnd-kit, react-beautiful-dnd) plus HTML5 DnD. `target` is an alias |
27
29
  | `webmcp` | `{ tool, params }` | Calls a `navigator.modelContext` tool, if the site exposes one |
28
30
 
31
+ ### Arguments that are not actions
32
+
33
+ Three `args` keys apply across actions rather than belonging to one:
34
+
35
+ | Key | What it does |
36
+ | --- | --- |
37
+ | `native` | Forces a trusted native click instead of the occlusion-honest synthetic path. For file pickers and clipboard access |
38
+ | `holdMs` | Keeps the pointer **down** for that long between mousedown and mouseup. The only way to drive a hold-to-confirm control. `effect.heldMs` reports the hold actually achieved |
39
+ | `confirmDangerous` | A permission gate for a control Reticle judged destructive. It is **not** a duration |
40
+
41
+ <Warning>
42
+ `confirmDangerous` and `holdMs` are routinely confused because both show up around delete buttons.
43
+ `holdMs` is how long the button is held; `confirmDangerous` is permission to press it at all.
44
+ Passing the wrong one gets you either a refusal or a press that was never held.
45
+ </Warning>
46
+
47
+ ### The refusal you will meet first
48
+
49
+ Reticle blocks a control that looks destructive before acting on it:
50
+
51
+ ```json
52
+ {
53
+ "error": "potentially destructive action blocked; retry with args.confirmDangerous=true",
54
+ "recovery": "Reticle blocked a potentially destructive control (delete, remove, revoke…) on purpose. If you mean it, retry the same action with args.confirmDangerous set to true. This is a deliberate refusal, not a defect: there is nothing to report."
55
+ }
56
+ ```
57
+
58
+ A stale ref is refused the same way, with `"ref 'e303' no longer resolves to an element"` and a recovery note telling you to re-query. Both refusals cost a turn and prevent a false green, which is the trade.
59
+
29
60
  ## `fill` versus `type`
30
61
 
31
62
  `fill` replaces. `type` appends. Reaching for `type` when you meant `fill` is how a field ends up containing `hellohello`, and neither call will complain.
@@ -54,9 +85,9 @@ Several actions in one round trip:
54
85
  ```json
55
86
  {
56
87
  "steps": [
57
- { "ref": "e101", "action": "fill", "args": { "value": "admin@reticle.dev" } },
58
- { "ref": "e102", "action": "fill", "args": { "value": "hunter2" } },
59
- { "ref": "e103", "action": "click" }
88
+ { "ref": "e603", "action": "fill", "args": { "value": "admin@reticle.dev" } },
89
+ { "ref": "e604", "action": "fill", "args": { "value": "hunter2" } },
90
+ { "ref": "e605", "action": "click" }
60
91
  ]
61
92
  }
62
93
  ```
@@ -78,7 +109,9 @@ Several actions in one round trip:
78
109
 
79
110
  ## What every action reports
80
111
 
81
- `valueChanged`, `focusMoved`, `domMutatedWithin`, `defaultPrevented`, `occluded` and `occludedBy`. Plus the element's `testid`, `component` and `source` file and line.
112
+ `dispatched`, `targetMatched`, `visible`, `enabled`, `valueChanged`, `focusMoved`, `domMutatedWithin`, `defaultPrevented`, `occluded`, `occludedBy` and `scrolledIntoView`. Plus the element's `testid`, `component`, `role`, `name` and `source` file and line.
113
+
114
+ Fields sitting at their uninformative default are **omitted**, so a clean action collapses to its consequence. A missing `visible` means `true`, not `false`. Read an absence as "nothing to report" rather than as a failure.
82
115
 
83
116
  `occluded` is the answer to a large share of "the button doesn't work" reports: the click landed on the modal overlay sitting on top of it.
84
117
 
@@ -4,6 +4,8 @@ description: 'One screen to get fluent: the look → act → observe → assert
4
4
  icon: bolt
5
5
  ---
6
6
 
7
+ The loop is **look → act → observe → assert**, and only `reticle_act_and_wait` and `reticle_assert` produce a verdict. Reach for `reticle_act_and_wait({ ref, action, until })` first: it names the expected consequence before the action, which is the difference between a check and a rationalisation. `verified: "unknown"` is not a pass.
8
+
7
9
  One screen to get fluent. Reticle is the **proof layer for AI agents**: no screenshots, no vision model, evidence not prose. Everything below returns structured data. Full guide: [usage.md](usage.md).
8
10
 
9
11
  ## The core loop: look → act → observe → assert
@@ -109,11 +111,11 @@ A claim is real only when the layers agree. Check more than the UI:
109
111
 
110
112
  The tools advertised DIRECTLY, i.e. what you'll use 90% of the time. Everything else is one `reticle_run` away (see Token note below).
111
113
 
112
- `reticle_sessions` · `reticle_navigate` · `reticle_snapshot` · `reticle_query` · `reticle_act` · `reticle_act_sequence` · `reticle_act_and_wait` · `reticle_observe` · `reticle_network` · `reticle_console` · `reticle_wait_for` · `reticle_assert` · `reticle_state` · `reticle_inspect` (DOM node → `src/App.tsx:104`) · `reticle_capabilities` (the app's whole testable surface in one call; `{fromDisk:true}` needs no browser) · `reticle_feedback` (tell the maintainers what is missing) · `reticle_session` (hand back: `{action:"yield"}` the moment you stop driving, `{action:"resume"}` after a human pause).
114
+ `reticle_sessions` · `reticle_navigate` · `reticle_snapshot` · `reticle_query` · `reticle_act` · `reticle_act_sequence` · `reticle_act_and_wait` · `reticle_observe` · `reticle_network` · `reticle_console` · `reticle_wait_for` · `reticle_assert` · `reticle_state` · `reticle_inspect` (DOM node → `src/App.tsx:104`) · `reticle_feedback` (tell the maintainers what is missing) · `reticle_session` (hand back: `{action:"yield"}` the moment you stop driving, `{action:"resume"}` after a human pause).
113
115
 
114
- Frequently useful but NOT core, so reach them through `reticle_run({ tool, args })`: `reticle_domain` (learn the app + gaps), `reticle_capabilities` (the app's declared testable surface), `reticle_baseline {action:"diff"}`, `reticle_project` (run history).
116
+ Frequently useful but NOT core, so reach them through `reticle_run({ tool, args })`: `reticle_capabilities` (the app's whole testable surface in one call; `{fromDisk:true}` needs no browser), `reticle_domain` (learn the app + gaps), `reticle_baseline {action:"diff"}`, `reticle_project` (run history).
115
117
 
116
- **Reach past core when…** you need to record/replay a journey (`reticle_record {action:"start"}/stop`, `reticle_replay`), persist a self-healing golden flow (`reticle_flow_save*` / `reticle_flow_replay` / `reticle_flow_heal`), compile annotations (`reticle_annotate`), explore autonomously (`reticle_explore` lists controls; `reticle_crawl` clicks them all and reports anomalies, and is **destructive**), reveal a virtualized off-screen row (`reticle_scroll_to`, for when `reticle_query` finds nothing because a windowed list hasn't rendered it yet), visual-check (`reticle_screenshot` / `reticle_visual_diff`, pinned with `reticle_viewport` for reproducible baselines), test error/edge states by stubbing the network (`reticle_network_mock`: 500 / offline / delay, driven mode), control time for toasts/debounces/auto-dismiss (`reticle_clock { freeze | advanceMs | reset }`), or work with a human (`reticle_session {action:"end"}` / `reticle_session {action:"resume"}` / `reticle_session {action:"messages"}`, and **`reticle_session {action:"review"}`** to drain + fix the bugs the human flagged from the panel).
118
+ **Reach past core when…** you need to record/replay a journey (`reticle_record {action:"start"}/stop`, `reticle_replay`), persist a self-healing golden flow (`reticle_flow_save*` / `reticle_flow_replay` / `reticle_flow_heal`), compile annotations (`reticle_annotate`), explore autonomously (`reticle_explore` lists controls; `reticle_verify {action:"crawl"}` clicks them all and reports anomalies, and is **destructive**), reveal a virtualized off-screen row (`reticle_scroll_to`, for when `reticle_query` finds nothing because a windowed list hasn't rendered it yet), visual-check (`reticle_screenshot` / `reticle_visual_diff`, pinned with `reticle_viewport` for reproducible baselines), test error/edge states by stubbing the network (`reticle_network_mock`: 500 / offline / delay, driven mode), control time for toasts/debounces/auto-dismiss (`reticle_clock { freeze | advanceMs | reset }`), or work with a human (`reticle_session {action:"end"}` / `reticle_session {action:"resume"}` / `reticle_session {action:"messages"}`, and **`reticle_session {action:"review"}`** to drain + fix the bugs the human flagged from the panel).
117
119
 
118
120
  ## flows vs baselines vs project.json (the persistence layers)
119
121
 
@@ -158,4 +160,4 @@ Recent additions, each of which answers a question agents were previously asking
158
160
  - **Cap broad reads.** `reticle_query` takes `limit` (caps descriptors; reports `total`/`truncated`) and `count_only` (just the match count). `reticle_network` / `reticle_console` take `limit` (most-recent-N, reports `droppedOldest`) and carry the same `cost` hint, so a busy page or wide window never floods your context unnoticed.
159
161
  - **A saved flow tells you if it's a real test.** `reticle_flow_save` returns `assertions.grade` (`asserted` / `presence-only` / `assertion-free`); if it's not `asserted`, add a consequence (`reticle_annotate` assert-signal/assert-net or a success-state) so it can't pass while broken. On replay, an ambiguous heal (two testids tie) is surfaced, never auto-applied. And an `apply` heal re-replays the rebound flow and **refuses to write** if the success consequence no longer fires (`status:consequence_broken`): it heals the locator, never the intent.
160
162
  - **Predicate schema is not bloated.** The recursive predicate DSL used by `reticle_assert` / `reticle_wait_for` / `reticle_act_and_wait` is **factored, not inlined**: when converted to the JSON Schema MCP sends, the predicate body is emitted **once** (~2.7k chars ≈ **~685 tokens** per tool) and recursion is handled by self-`$ref` (`#/properties/predicate`), with no per-recursion duplication. No action needed.
161
- - **One tool surface.** Reticle advertises the core set directly (sessions/navigate/snapshot/query/act/act_and_wait/observe/network/console/wait_for/assert/state/inspect/feedback/session: the whole detect loop, plus the file-pointer, the feedback channel and the handback) PLUS two meta-tools that keep every other tool one call away: `reticle_tools` (discover; no args lists every tool name + summary, `names:[…]` loads full params on demand) and `reticle_run({ tool, args })` (invoke any tool by name; a top-level `sessionId` is forwarded to the target, so you need not nest it in `args`). So to record/replay/verify a flow, call `reticle_run({ tool:"reticle_flow_verify", sessionId })` (or `reticle_tools` first to see params). There is nothing to pick. `RETICLE_ADVERTISE_ALL_TOOLS=1` (read by the daemon at startup, so restart it) advertises everything with output schemas: a verification switch for suites, not a way to run agents. **Sizes are deliberately not quoted here.** A count in prose goes stale, and has three times already. `reticle_tools` reports the live surface, and SKILL.md carries the one gated table.
163
+ - **One tool surface.** Reticle advertises the core set directly (sessions/navigate/snapshot/query/act/act_and_wait/observe/network/console/wait_for/assert/state/inspect/feedback/session: the whole detect loop, plus the file-pointer, the feedback channel and the handback) PLUS two meta-tools that keep every other tool one call away: `reticle_tools` (discover; no args lists every tool name + summary, `names:[…]` loads full params on demand) and `reticle_run({ tool, args })` (invoke any tool by name; a top-level `sessionId` is forwarded to the target, so you need not nest it in `args`). So to record/replay/verify a flow, call `reticle_run({ tool:"reticle_verify", args:{ action:"flows" }, sessionId })` (or `reticle_tools` first to see params). There is nothing to pick. `RETICLE_ADVERTISE_ALL_TOOLS=1` (read by the daemon at startup, so restart it) advertises everything with output schemas: a verification switch for suites, not a way to run agents. **Sizes are deliberately not quoted here.** A count in prose goes stale, and has three times already. `reticle_tools` reports the live surface, and SKILL.md carries the one gated table.
@@ -4,6 +4,8 @@ description: 'The moving parts, the data flow between them, and the design decis
4
4
  icon: sitemap
5
5
  ---
6
6
 
7
+ Reticle has three moving parts: a dev-only **SDK** inside your page that instruments the DOM, network, console, routing and framework state; a local **server** that runs a WebSocket bridge to that SDK and an MCP server for your agent; and a git-checked **`.reticle/` workspace** holding flows, baselines and run artifacts. Your agent calls an MCP tool, the server turns it into a command over the WebSocket, the SDK runs it in the real page and streams structured events back.
8
+
7
9
  > For engineers evaluating Reticle, integrating it at scale, or contributing. It explains the moving parts, the data flow, and the design decisions behind them. If you just want to get running, start with [getting-started.md](getting-started.md); come back here when you want to know _why_.
8
10
 
9
11
  ---
@@ -40,6 +42,10 @@ Reticle is a pnpm + Turborepo monorepo. The split is not cosmetic: each boundary
40
42
  | `@reticlehq/server` | Node | The bridge, the MCP server, the `reticle` CLI, flow/run storage | Never imports DOM APIs |
41
43
  | `@reticlehq/react` | the browser | The SDK **kit** you install in a browser app: re-exports the browser sensor (so one install gives both `reticle` and `install`) and maps a DOM node → React component → source `file:line` | Core works without the source-mapping half |
42
44
  | `@reticlehq/babel-plugin`, `@reticlehq/next`, `@reticlehq/vite-plugin` | build time | Stamp `data-reticle-source` for source mapping (and, for Vite, inject `connect()`) | Plain tooling |
45
+ | `@reticlehq/electron` | Electron main process | Adapter for the desktop runtime: IPC observer, main-process capture | Desktop-only, outside the web gates |
46
+ | `packages/tauri` (`reticle-tauri`) | Tauri (Rust) | Capture backend for the Tauri desktop runtime | Rust, outside every JS gate; compiled by CI's `rust`/`rust-macos` jobs |
47
+ | `@reticlehq/test` | Node (dev dependency) | Spec runner + matchers used by CI's own test suites | Peer dependency on `vitest` |
48
+ | `@reticlehq/eslint-plugin` | dev tooling | Repo-internal lint rule: a state change must fire a signal | Dev-only, never shipped in a user's bundle |
43
49
 
44
50
  > Note: pre-2.0, `@reticlehq/core` was a single umbrella package that re-exported all of the above under subpaths. In 2.0 the umbrella was retired and `@reticlehq/core` became the bottom-of-graph wire contract.
45
51
 
@@ -4,6 +4,14 @@ description: 'How we measure Reticle, what each number actually means, and the p
4
4
  icon: chart-column
5
5
  ---
6
6
 
7
+ Two different measurements, because there are two different questions.
8
+
9
+ Against a **Playwright script** over an 88-bug registry, each tool running its own native check deterministically with no model in the loop, Reticle caught **85 of 88** and Playwright **59 of 88**, both with zero false alarms on the clean build. The gap is not spread evenly: it is 26 bugs Playwright **structurally cannot** see, and on everything observable from outside the app the two tie exactly. See Part 5a.
10
+
11
+ Against **Playwright MCP and Chrome DevTools MCP**, the agent-driven tools where cost per look matters, Reticle caught 10 of 10 injected regressions to Playwright MCP's 9 and DevTools' 8, averaging 815 tokens per look against 1,292 and 758. See Part 5b.
12
+
13
+ Where Reticle loses is stated plainly in Part 5, and it loses in more places than these two tables show.
14
+
7
15
  > This page assumes **zero** testing background. By the end you'll understand what software testing is, why AI coding agents made it urgent, how to tell a good verification tool from a bad one, and exactly how Reticle measures up against the main alternatives, including the places Reticle loses. If a term looks like jargon, it's defined the first time it appears.
8
16
  >
9
17
  > _Where the pictures come from, stated accurately: the **SVG charts** (`bench/artifacts/`) are generated from measured raws by `bench/harness/charts.mjs`. The **headline PNG cards** (`assets/readme/`) are screenshots of hand-authored HTML in `assets/benchmarks/src/`, rendered by `assets/benchmarks/render.mjs`; their figures are typed in by a human and are **not** read from `history.jsonl`, which this line previously claimed. Treat a card as a design asset that has to be updated by hand when a number moves, and `bench/SCORECARD.md` (plus its freshness banner) as the source of truth._
@@ -59,7 +67,9 @@ We compare Reticle against the two most credible agent-native browser tools, bec
59
67
  - **Chrome DevTools MCP** is Google's tool exposing Chrome's DevTools to an agent.
60
68
  - **(Baseline) screenshot agents**, the common "let the model look at a picture" approach.
61
69
 
62
- Why not compare to traditional test frameworks (Playwright the library, Cypress, etc.)? Because those are written and maintained by humans; they don't answer "can an _agent_ verify its own work in the loop." The three above do, so it's an apples-to-apples agent comparison.
70
+ Why not compare to traditional test frameworks (Playwright the library, Cypress, etc.) on the token passes? Because those are written and maintained by humans; they don't answer "can an _agent_ verify its own work in the loop," and a hand-written script has no token cost to compare. The three above do, so the cost comparison stays apples-to-apples.
71
+
72
+ There **is** a comparison against a Playwright script, and it answers a different question: not "what does it cost an agent to look" but "what can each tool structurally SEE, at all." That one is in Part 5a. It runs no model, so nothing about it depends on how well an agent drives, which is what makes it the more durable of the two results.
63
73
 
64
74
  Tool versions are pinned in `bench/raw/run-meta.json` so any run is reproducible.
65
75
 
@@ -101,7 +111,45 @@ We ran this two ways on purpose: a **controlled toy app** (where we can inject e
101
111
 
102
112
  ![One honest test, two apps: Reticle has the highest Verification Efficiency on the controlled app and the lowest observation cost on the real dashboard](/images/bench-two-apps.png)
103
113
 
104
- ### 5a. The controlled toy app (the demo)
114
+ ### 5a. The 88-bug head-to-head against a Playwright script
115
+
116
+ The largest and least model-dependent measurement here. An **88-bug registry** (`bench/pw-vs-reticle/bugs.mjs`), each bug injected into the same app, each tool running its NATIVE check: a Playwright script doing what a Playwright script does, Reticle doing what Reticle does. **No LLM in the loop**, so nothing depends on how well an agent happened to drive.
117
+
118
+ A tool "catches" a bug when its check correctly FAILS on the broken build **and** does not fail on the clean one. Failing on the clean build is a false alarm and is counted against the tool, not ignored.
119
+
120
+ | | Reticle | Playwright script |
121
+ | -------------------------------------- | ----------: | ----------------: |
122
+ | Bugs caught | **85 / 88** | 59 / 88 |
123
+ | Of what it can structurally catch | **85 / 86** | 57 / 60 |
124
+ | **False greens** (broken, reported OK) | **1** | **29** |
125
+ | False alarms on the clean build | 0 | 0 |
126
+ | Output bytes per bug | 9,261 | **5,849** |
127
+
128
+ Three things about that table are more important than the headline number.
129
+
130
+ **Two of Reticle's three non-catches are not misses.** They are the registry's `false-positive-trap` cases, builds that look broken and are not: flagging them would itself be a false alarm, so 0 of 2 is the correct score and a tool that "caught" them would be worse. The third, `iframe-stale-data` (deep-dom), is a **genuine miss** and is counted as a false green above rather than explained away.
131
+
132
+ **These figures were re-derived on the 2.9.0 branch and they moved.** The numbers first published here were recorded on 2026-07-24; 615 commits touched the harness, the fixture app, the SDK or the server before anybody re-ran it. Detection moved 86 → 85 and 60 → 59, and the per-bug output figure **inverted**: Reticle was published as the leaner of the two at 4,134 B against 7,899 B and measures 9,261 B against 5,849 B here. Reticle's own check being the more expensive one does not contradict the token-cost sections below. Those measure an AGENT-driven look against MCP tools, which is a different question. It does mean the earlier claim should not be repeated. Re-run `bench/pw-vs-reticle/run.mjs` before quoting any of this.
133
+
134
+ **Reticle does not win everything, and the ties are the honest part.** On everything observable from outside the app the two are exactly level: console 6/6 against 6/6, network 8/8 against 8/8, storage 5/5 against 5/5, visual UI 16/16 against 16/16, plus routing, timing, paint and chart bugs. Any evaluate-capable tool matches Reticle on what the DOM, the network buffer or a screenshot can show.
135
+
136
+ **The gap is one specific shape:** bugs where the screen is right and the app is wrong.
137
+
138
+ | Category | Reticle | Playwright | The bug |
139
+ | --- | --: | --: | --- |
140
+ | state | 8/8 | **0/8** | the UI renders a plausible value that contradicts the app's own store |
141
+ | business-logic | 6/6 | **0/6** | an action corrupts a field nothing renders. The record is wrong, the screen is fine |
142
+ | signal | 4/4 | **0/4** | an event the app emits about itself (hydration done, error boundary caught) |
143
+ | net-status | 4/4 | 1/4 | a swallowed 4xx/5xx. The request failed, a catch block ate it, the UI rendered |
144
+ | streams | 3/3 | 0/3 | an SSE or WebSocket frame anomaly a request-level view cannot see |
145
+ | perf | 3/3 | 1/3 | layout shift, or a render storm where the DOM is identical |
146
+ | deep-dom | 2/3 | 2/3 | a break deep in a subtree a snapshot elides. The one both now miss is `iframe-stale-data` |
147
+
148
+ That is the whole claim, stated as narrowly as it deserves: Reticle sees inside the running app, so it catches the class of bug where the outside looks correct. Outside that class it ties, and it says so.
149
+
150
+ Bug registry: `bench/pw-vs-reticle/bugs.mjs`. Runner: `bench/pw-vs-reticle/run.mjs`, which regenerates `results.json` locally (run artifacts are not committed). Scorecard: `bench/FALSE-GREEN-SCORECARD.md`.
151
+
152
+ ### 5b. The controlled toy app (the demo)
105
153
 
106
154
  Measured on the observation-cost pass (numbers regenerate from `bench/raw/`):
107
155
 
@@ -115,7 +163,7 @@ Measured on the observation-cost pass (numbers regenerate from `bench/raw/`):
115
163
  - DevTools is a hair cheaper per look (758 vs 815 tokens), **but it's cheaper because it catches less.** On the metric that combines both (Verification Efficiency), Reticle leads at 12.27 vs 10.55.
116
164
  - Playwright catches a lot but is ~1.6× more expensive per look, so its efficiency is lowest here.
117
165
 
118
- ### 5b. A real production app (the Reticle dashboard)
166
+ ### 5c. A real production app (the Reticle dashboard)
119
167
 
120
168
  A toy app is a fair lab, but it's small. The harder, more honest test is a **real, complex app**. Take the [Reticle](https://reticle.sh) dashboard itself: React 19, authentication, live data, ~15 routes, a node-graph view, virtualized lists. We embedded the SDK (the Vite plugin) and drove the authenticated app with all three tools. Observing it **once** (the primary snapshot + the network log):
121
169
 
@@ -129,13 +177,13 @@ A toy app is a fair lab, but it's small. The harder, more honest test is a **rea
129
177
 
130
178
  On a small page everything is cheap and the gap is modest. On a **big** page the structured read pulls ahead: Reticle is **2.1× leaner than Playwright MCP** and the cheapest overall. And only Reticle can read the app's own program state (`authenticated`, `userId`, `activeProjectId`) and assert the login _actually worked_ from a signal the app emits. The others can only look at the DOM and the network and **guess**.
131
179
 
132
- ### 5c. The kicker: a real bug, caught live
180
+ ### 5d. The kicker: a real bug, caught live
133
181
 
134
182
  Here's what makes the real-app test matter. On the **very first run, before we instrumented anything**, Reticle's network observation flagged two endpoints returning **`500`**: `GET /api/v1/projects` and `/recovery/incidents`, both failing with `column "deleted_at" does not exist` (a database migration that hadn't been applied). **The page rendered fine.** The sidebar loaded, nothing looked broken; a screenshot agent would have called it _"done."_ Reticle saw the broken backend underneath, in one look.
135
183
 
136
184
  That is the whole thesis, demonstrated on a real app we didn't cherry-pick: **"looks done" ≠ "is done," and the difference is usually non-visual.** (We then fixed the migration; all endpoints 200.)
137
185
 
138
- ### 5d. What each tool can actually do
186
+ ### 5e. What each tool can actually do
139
187
 
140
188
  Cost is half the story; capability is the other half. The marks below are what's **first-class and built-in** to each tool (all three are good tools, and they compose: drive with theirs, assert with Reticle):
141
189
 
@@ -153,10 +201,10 @@ Honesty is the point of this page, so here are the places Reticle does **not** w
153
201
 
154
202
  The numbers above are for **one** verification. But a test suite's real job is the **same** check, over and over, every commit, every CI run. Here the picture changes shape:
155
203
 
156
- - Reticle records a flow once, then **replays it with no AI model at all** (re-resolving each element and re-asserting the outcome) for **~175-210 tokens per run**.
204
+ - Reticle records a flow once, then **replays it with no AI model at all** (re-resolving each element and re-asserting the outcome) for **~239 tokens per run** (re-measured 2026-08-11; it was ~175 before each flow started carrying a success oracle).
157
205
  - Playwright MCP and DevTools MCP have no replay: re-checking means an agent **re-drives the whole flow with the model every time**, costing tens of thousands of tokens per run.
158
206
 
159
- That's a **~128-184× cost difference per re-run**, and it grows with how often you run. This is where the "much cheaper than screenshots" claim becomes dramatic rather than incremental.
207
+ That's a **~127× cost difference per re-run**, and it grows with how often you run. This is where the "much cheaper than screenshots" claim becomes dramatic rather than incremental.
160
208
 
161
209
  ![Re-run cost: Reticle replays with no model; competitors re-drive every time](/images/chart-avg-tokens.svg)
162
210
 
@@ -188,7 +236,7 @@ A fleet of agents (or a parallel suite) verifying the same app doesn't need a br
188
236
  ## Part 7: What this means for you
189
237
 
190
238
  - **If you're a developer using an AI agent:** Reticle lets the agent confirm its own change actually worked (across the network call, the console, the route, the state) for a few hundred tokens, not a screenshot's thousands. "Done" starts meaning done.
191
- - **If you run CI / a platform team:** the replay pass is the headline: deterministic re-verification with no model, so a known flow re-checks for ~200 tokens at ~0% flake, every commit.
239
+ - **If you run CI / a platform team:** the replay pass is the headline: deterministic re-verification with no model, so a known flow re-checks for a couple of hundred tokens at ~0% flake, every commit.
192
240
  - **If you're evaluating tools:** the axis that matters is _bugs caught per token, gated on catching them all_. Cheapness that misses regressions is a false economy.
193
241
 
194
242
  You are now equipped to read any verification benchmark critically: ask "what's the catch rate, what's the false-positive rate, and what's the cost _per re-run_?" Then be suspicious of any vendor that won't show you where they lose.
@@ -204,15 +252,15 @@ A fair question from a large engineering org (think a Datadog- or Salesforce-siz
204
252
  The win is **regression re-runs**, because that cost is paid over and over. Take a mid-size surface:
205
253
 
206
254
  - **50 golden flows**, re-verified on **every PR**, at **200 PRs/day**.
207
- - Reticle replays each flow **deterministically, with no model**: ~**200 tokens/flow/run** (measured).
255
+ - Reticle replays each flow **deterministically, with no model**: ~**240 tokens/flow/run** (measured).
208
256
  - The agent-driven alternative re-drives each flow with an LLM: ~**30,000 tokens/flow/run** (measured Layer-C comparison).
209
257
 
210
258
  | | Per flow / run | 50 flows × 200 PRs/day | Per year (~250 working days) |
211
259
  | --- | --- | --- | --- |
212
- | Reticle replay (no model) | ~200 tok | ~2.0 M tok/day | ~0.5 B tok/yr |
260
+ | Reticle replay (no model) | ~240 tok | ~2.4 M tok/day | ~0.6 B tok/yr |
213
261
  | Agent re-drive (LLM) | ~30,000 tok | ~300 M tok/day | ~75 B tok/yr |
214
262
 
215
- That's a **~150× difference on the recurring axis**, and it scales linearly with flows × runs, the two numbers an enterprise has _a lot_ of. The deterministic replay also means **~0% verdict flake** (measured over repeated identical runs), which at this scale is the difference between a trusted gate and one engineers learn to ignore (recall: flaky suites get abandoned; that's the documented failure mode Reticle is built to avoid). And because replay needs no model, it has **no per-run API spend and no rate-limit ceiling**. It runs in CI like any other deterministic check.
263
+ That's a **~125× difference on the recurring axis**, and it scales linearly with flows × runs, the two numbers an enterprise has _a lot_ of. The deterministic replay also means **~0% verdict flake** (measured over repeated identical runs), which at this scale is the difference between a trusted gate and one engineers learn to ignore (recall: flaky suites get abandoned; that's the documented failure mode Reticle is built to avoid). And because replay needs no model, it has **no per-run API spend and no rate-limit ceiling**. It runs in CI like any other deterministic check.
216
264
 
217
265
  ### Where it does NOT help (stated plainly)
218
266
 
@@ -256,4 +304,4 @@ The same page is loaded in one browser under three conditions (full SDK, observe
256
304
 
257
305
  **Result: instrumentation overhead is smaller than the method can resolve. Report it as `< 1.2 percentage points`, inside the 3% budget.** The HUD is measured and reported separately, because it is a developer-visible affordance you can turn off, not a cost of observing.
258
306
 
259
- Getting here required correcting the method and then fixing a real bug. The two-condition version of this bench compared the full SDK against Reticle being absent, so it charged instrumentation for the HUD too, and reported **+5.85 pp (FAIL)**. Decomposing it showed almost none of that was instrumentation: a single `backdrop-filter: blur(24px)` on the HUD panel was costing **+4.03 pp** on its own, more than every observer combined, because a backdrop blur re-samples everything behind it whenever that content changes, and this fixture repaints at 60fps. Removing it took total main-thread time down 22%. Full history, including a fix that measured nothing and was reverted rather than kept, is in `bench/overhead/README.md`. Reproduce it yourself with `node bench/overhead/measure.mjs` (see `bench/overhead/README.md`, which records the ruled-out causes). causes).
307
+ Getting here required correcting the method and then fixing a real bug. The two-condition version of this bench compared the full SDK against Reticle being absent, so it charged instrumentation for the HUD too, and reported **+5.85 pp (FAIL)**. Decomposing it showed almost none of that was instrumentation: a single `backdrop-filter: blur(24px)` on the HUD panel was costing **+4.03 pp** on its own, more than every observer combined, because a backdrop blur re-samples everything behind it whenever that content changes, and this fixture repaints at 60fps. Removing it took total main-thread time down 22%. Full history, including a fix that measured nothing and was reverted rather than kept, is in `bench/overhead/README.md`. Reproduce it yourself with `node bench/overhead/measure.mjs` (see `bench/overhead/README.md`, which records the ruled-out causes).