@reticlehq/server 2.7.0 → 2.8.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 (236) hide show
  1. package/README.md +14 -5
  2. package/SKILL.md +64 -29
  3. package/dist/capsule/causal-summary.d.ts +20 -0
  4. package/dist/capsule/causal-summary.js +31 -6
  5. package/dist/capsule/divergence.js +7 -1
  6. package/dist/cli/chromium-hint.d.ts +33 -5
  7. package/dist/cli/chromium-hint.js +82 -12
  8. package/dist/cli/cli-doctor.js +28 -25
  9. package/dist/cli/cli-kill.d.ts +58 -0
  10. package/dist/cli/cli-kill.js +162 -0
  11. package/dist/cli/cli-launch.d.ts +17 -0
  12. package/dist/cli/cli-launch.js +28 -0
  13. package/dist/cli/cli-parse.d.ts +16 -1
  14. package/dist/cli/cli-parse.js +32 -9
  15. package/dist/cli/cli-port.d.ts +8 -0
  16. package/dist/cli/cli-port.js +16 -0
  17. package/dist/cli/cli-verify.js +2 -2
  18. package/dist/cli/doctor-sessions-line.d.ts +12 -0
  19. package/dist/cli/doctor-sessions-line.js +66 -0
  20. package/dist/cli/port-holder.d.ts +10 -0
  21. package/dist/cli/port-holder.js +26 -2
  22. package/dist/cli.js +99 -14
  23. package/dist/crawl/crawl-tools.js +12 -2
  24. package/dist/crawl/crawl.d.ts +13 -0
  25. package/dist/crawl/crawl.js +50 -2
  26. package/dist/events/contradictions.d.ts +14 -0
  27. package/dist/events/contradictions.js +26 -17
  28. package/dist/events/predicate-eval.d.ts +20 -1
  29. package/dist/events/predicate-eval.js +125 -3
  30. package/dist/events/predicate.js +170 -18
  31. package/dist/flows/flow-classify.js +1 -1
  32. package/dist/flows/flow-tools.js +12 -4
  33. package/dist/flows/heal-run.js +1 -1
  34. package/dist/honesty/blind-spots.js +6 -2
  35. package/dist/honesty/unsettled.d.ts +40 -0
  36. package/dist/honesty/unsettled.js +78 -0
  37. package/dist/honesty/verified.d.ts +7 -0
  38. package/dist/honesty/verified.js +3 -3
  39. package/dist/index.js +3 -0
  40. package/dist/init/agent-rules.d.ts +16 -0
  41. package/dist/init/agent-rules.js +96 -22
  42. package/dist/init/astro-patch.d.ts +2 -1
  43. package/dist/init/astro-patch.js +11 -5
  44. package/dist/init/capabilities.d.ts +22 -0
  45. package/dist/init/capabilities.js +76 -0
  46. package/dist/init/confirm.d.ts +96 -0
  47. package/dist/init/confirm.js +138 -0
  48. package/dist/init/cra.js +3 -2
  49. package/dist/init/non-js-project.js +29 -5
  50. package/dist/init/plan-framework.js +53 -15
  51. package/dist/init/plan.d.ts +40 -4
  52. package/dist/init/plan.js +163 -40
  53. package/dist/init/run.d.ts +24 -2
  54. package/dist/init/run.js +148 -42
  55. package/dist/init/slash-command.d.ts +1 -1
  56. package/dist/init/slash-command.js +46 -6
  57. package/dist/init/snippets.d.ts +44 -4
  58. package/dist/init/snippets.js +161 -37
  59. package/dist/init/vite-config.js +29 -3
  60. package/dist/input/network-mock-tools.d.ts +19 -0
  61. package/dist/input/network-mock-tools.js +29 -4
  62. package/dist/license/license.js +1 -1
  63. package/dist/mcp/mcp-proxy.d.ts +69 -3
  64. package/dist/mcp/mcp-proxy.js +205 -20
  65. package/dist/mcp/mcp.js +23 -7
  66. package/dist/mcp/proxy-lifecycle.d.ts +1 -1
  67. package/dist/mcp/proxy-lifecycle.js +1 -1
  68. package/dist/pool/browser-pool.d.ts +22 -2
  69. package/dist/pool/browser-pool.js +44 -4
  70. package/dist/project/learned-routes.d.ts +24 -0
  71. package/dist/project/learned-routes.js +63 -0
  72. package/dist/project/project-store.d.ts +6 -0
  73. package/dist/project/project-store.js +25 -1
  74. package/dist/session/no-session-diagnosis.d.ts +19 -0
  75. package/dist/session/no-session-diagnosis.js +71 -18
  76. package/dist/session/no-session-watch.js +12 -0
  77. package/dist/session/ref-provenance.d.ts +54 -0
  78. package/dist/session/ref-provenance.js +61 -0
  79. package/dist/session/session-lease.js +1 -1
  80. package/dist/telemetry/automation-hint.d.ts +17 -0
  81. package/dist/telemetry/automation-hint.js +71 -0
  82. package/dist/telemetry/bug-found.d.ts +0 -20
  83. package/dist/telemetry/bug-found.js +38 -21
  84. package/dist/telemetry/cli-telemetry.js +7 -1
  85. package/dist/telemetry/feedback.js +1 -1
  86. package/dist/telemetry/init-telemetry.js +9 -1
  87. package/dist/telemetry/install-source.d.ts +36 -0
  88. package/dist/telemetry/install-source.js +40 -0
  89. package/dist/telemetry/run-telemetry.js +4 -1
  90. package/dist/telemetry/session-metrics.js +5 -0
  91. package/dist/telemetry/telemetry.d.ts +5 -1
  92. package/dist/telemetry/telemetry.js +15 -2
  93. package/dist/telemetry/tool-refused.d.ts +19 -0
  94. package/dist/telemetry/tool-refused.js +55 -0
  95. package/dist/telemetry/verification-of.js +46 -7
  96. package/dist/temp-dir.d.ts +27 -0
  97. package/dist/temp-dir.js +46 -0
  98. package/dist/tools/act-tools.js +45 -1
  99. package/dist/tools/assert-grade.d.ts +1 -1
  100. package/dist/tools/assert-grade.js +27 -5
  101. package/dist/tools/assert-verdict.js +18 -1
  102. package/dist/tools/contract-tools.js +4 -0
  103. package/dist/tools/dynamic-tools.js +1 -1
  104. package/dist/tools/error-recovery.d.ts +10 -0
  105. package/dist/tools/error-recovery.js +64 -4
  106. package/dist/tools/feedback-invite.js +1 -1
  107. package/dist/tools/invoke-tool.js +139 -23
  108. package/dist/tools/lease-tools.d.ts +29 -1
  109. package/dist/tools/lease-tools.js +58 -5
  110. package/dist/tools/navigate-result.js +1 -1
  111. package/dist/tools/observe-tools.js +1 -1
  112. package/dist/tools/observed-nothing.js +1 -1
  113. package/dist/tools/react-grace.d.ts +48 -0
  114. package/dist/tools/react-grace.js +87 -0
  115. package/dist/tools/read-tools.js +20 -4
  116. package/dist/tools/settle-in-flight.d.ts +79 -0
  117. package/dist/tools/settle-in-flight.js +106 -0
  118. package/dist/tools/snapshot-delta.d.ts +7 -0
  119. package/dist/tools/snapshot-delta.js +130 -6
  120. package/dist/tools/tool-surface.js +19 -0
  121. package/dist/tools/tools.js +16 -4
  122. package/docs/README.md +56 -11
  123. package/docs/actions.mdx +87 -0
  124. package/docs/agent-cheatsheet.md +93 -29
  125. package/docs/architecture.md +46 -35
  126. package/docs/benchmarks.md +76 -72
  127. package/docs/best-practices.mdx +211 -0
  128. package/docs/capabilities.mdx +387 -0
  129. package/docs/cli/affected.mdx +51 -0
  130. package/docs/cli/capsules.mdx +69 -0
  131. package/docs/cli/cloud.mdx +127 -0
  132. package/docs/cli/doctor.mdx +76 -0
  133. package/docs/cli/drive.mdx +55 -0
  134. package/docs/cli/feedback.mdx +67 -0
  135. package/docs/cli/gate.mdx +64 -0
  136. package/docs/cli/help.mdx +92 -0
  137. package/docs/cli/hunt.mdx +57 -0
  138. package/docs/cli/identify.mdx +62 -0
  139. package/docs/cli/init.mdx +94 -0
  140. package/docs/cli/kill.mdx +91 -0
  141. package/docs/cli/license.mdx +48 -0
  142. package/docs/cli/mcp.mdx +61 -0
  143. package/docs/cli/open.mdx +68 -0
  144. package/docs/cli/restart.mdx +60 -0
  145. package/docs/cli/rollback.mdx +35 -0
  146. package/docs/cli/serve.mdx +62 -0
  147. package/docs/cli/status.mdx +64 -0
  148. package/docs/cli/stop.mdx +56 -0
  149. package/docs/cli/telemetry.mdx +57 -0
  150. package/docs/cli/update.mdx +52 -0
  151. package/docs/cli/verify.mdx +57 -0
  152. package/docs/cli/version.mdx +39 -0
  153. package/docs/cli/watch.mdx +55 -0
  154. package/docs/cli.mdx +219 -0
  155. package/docs/debugging.md +13 -9
  156. package/docs/deploy-checks.md +15 -11
  157. package/docs/desktop-apps.md +49 -40
  158. package/docs/desktop.mdx +188 -0
  159. package/docs/docs.json +363 -0
  160. package/docs/enterprise.md +20 -16
  161. package/docs/faq.mdx +88 -0
  162. package/docs/fixtures.md +11 -7
  163. package/docs/flows.md +26 -22
  164. package/docs/for-agents.md +71 -0
  165. package/docs/frameworks.mdx +199 -0
  166. package/docs/gate-plan.md +45 -41
  167. package/docs/gates.md +22 -18
  168. package/docs/getting-started.md +76 -65
  169. package/docs/human-control.md +22 -18
  170. package/docs/index.mdx +101 -0
  171. package/docs/install-agentic.mdx +215 -0
  172. package/docs/install-manual.mdx +194 -0
  173. package/docs/instrumentation.mdx +232 -0
  174. package/docs/integration-patterns.md +38 -34
  175. package/docs/local-registry.md +16 -12
  176. package/docs/multi-agent-testing.md +16 -12
  177. package/docs/packages/babel-plugin.mdx +70 -0
  178. package/docs/packages/browser.mdx +111 -0
  179. package/docs/packages/core.mdx +106 -0
  180. package/docs/packages/electron.mdx +87 -0
  181. package/docs/packages/eslint-plugin.mdx +84 -0
  182. package/docs/packages/next.mdx +91 -0
  183. package/docs/packages/react.mdx +91 -0
  184. package/docs/packages/server.mdx +98 -0
  185. package/docs/packages/tauri.mdx +63 -0
  186. package/docs/packages/test.mdx +108 -0
  187. package/docs/packages/vite-plugin.mdx +96 -0
  188. package/docs/packages.mdx +217 -0
  189. package/docs/platform-integration.md +33 -26
  190. package/docs/predicates.mdx +179 -0
  191. package/docs/quickstart.mdx +211 -0
  192. package/docs/recipes.mdx +242 -0
  193. package/docs/skill-file.mdx +105 -0
  194. package/docs/state-management.mdx +197 -0
  195. package/docs/style.css +14 -0
  196. package/docs/system-map.md +47 -32
  197. package/docs/telemetry-contract.md +93 -50
  198. package/docs/telemetry-events.mdx +430 -0
  199. package/docs/telemetry.md +57 -52
  200. package/docs/testing.md +10 -6
  201. package/docs/token-efficiency.md +30 -26
  202. package/docs/tools-act-and-wait.mdx +123 -0
  203. package/docs/tools-act-sequence.mdx +120 -0
  204. package/docs/tools-act.mdx +82 -0
  205. package/docs/tools-assert.mdx +70 -0
  206. package/docs/tools-console.mdx +59 -0
  207. package/docs/tools-inspect.mdx +85 -0
  208. package/docs/tools-navigate.mdx +63 -0
  209. package/docs/tools-network.mdx +86 -0
  210. package/docs/tools-observe.mdx +89 -0
  211. package/docs/tools-overview.mdx +157 -0
  212. package/docs/tools-query.mdx +82 -0
  213. package/docs/tools-session-and-feedback.mdx +91 -0
  214. package/docs/tools-sessions.mdx +65 -0
  215. package/docs/tools-snapshot.mdx +75 -0
  216. package/docs/tools-state.mdx +81 -0
  217. package/docs/tools-tools-and-run.mdx +125 -0
  218. package/docs/tools-wait-for.mdx +66 -0
  219. package/docs/troubleshooting.mdx +193 -0
  220. package/docs/usage.md +175 -153
  221. package/docs/vs-chrome-devtools-mcp.mdx +82 -0
  222. package/docs/vs-playwright-mcp.mdx +85 -0
  223. package/docs/vs-screenshots.mdx +96 -0
  224. package/docs/what-is-reticle.mdx +63 -0
  225. package/docs/why-reticle.mdx +107 -0
  226. package/package.json +2 -2
  227. package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
  228. package/docs/matrix/2.5.0/codex-machine.json +0 -20
  229. package/docs/matrix/2.5.0/cursor-machine.json +0 -22
  230. package/docs/matrix/2.5.0/gemini-machine.json +0 -22
  231. package/docs/matrix/2.5.0/opencode-machine.json +0 -22
  232. package/docs/matrix/2.5.0/vscode-machine.json +0 -22
  233. package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
  234. package/docs/matrix/MATRIX.md +0 -26
  235. package/docs/matrix/README.md +0 -59
  236. package/docs/mint.json +0 -61
@@ -0,0 +1,179 @@
1
+ ---
2
+ title: Predicate reference: the grammar of green
3
+ sidebarTitle: Predicates
4
+ description: The grammar for declaring what should be true. Every kind, every field, and which ones actually prove something.
5
+ icon: filter
6
+ ---
7
+
8
+ A predicate declares what should be true. It is the argument to `until` on [`reticle_act_and_wait`](/tools-act-and-wait), and to `predicate` on [`reticle_assert`](/tools-assert) and [`reticle_wait_for`](/tools-wait-for).
9
+
10
+ Every predicate is `{ kind, ...fields }`.
11
+
12
+ ## Not all predicates prove the same thing
13
+
14
+ Reticle grades them, and it will tell you when you have chosen a weak one. This is a real response from a `text` assertion:
15
+
16
+ ```json
17
+ {
18
+ "verified": "yes",
19
+ "pass": true,
20
+ "advice": "This predicate only checks element/text presence, not an observable consequence. A locator healed to the wrong element (or a stale render) can satisfy it while the feature is broken. Prefer a { signal } or { net } assertion (or allOf it with one) so green means the feature actually worked."
21
+ }
22
+ ```
23
+
24
+ Take the advice. `element` and `text` are convenient and weak; `signal`, `state` and `net` are what make a green trustworthy.
25
+
26
+ | Kind | Grade | Proves |
27
+ | ------------------ | --------- | ------------------------------------------------------ |
28
+ | `signal` | strongest | The app declared success in its own words |
29
+ | `state` | strong | A registered store actually changed |
30
+ | `net` | strong | A specific request fired, with a countable cardinality |
31
+ | `route` | medium | Navigation happened |
32
+ | `console` | medium | Something logged, or nothing did |
33
+ | `element` / `text` | weak | Something rendered. A mock renders too |
34
+
35
+ ## The leaves
36
+
37
+ ### element
38
+
39
+ ```json
40
+ {
41
+ "kind": "element",
42
+ "query": { "role": "dialog", "name": "Confirm" },
43
+ "state": "visible"
44
+ }
45
+ ```
46
+
47
+ `query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `scope`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `focused`, `present`.
48
+
49
+ Add `"absent": true` to assert something is **gone**. The predicate for a removal, a dismissed toast, or a regression check.
50
+
51
+ ### text
52
+
53
+ ```json
54
+ { "kind": "text", "contains": "Saved successfully", "visible": true }
55
+ ```
56
+
57
+ Both `contains` and `value` are accepted. `absent: true` asserts the text is not present.
58
+
59
+ ### net
60
+
61
+ ```json
62
+ { "kind": "net", "method": "POST", "urlContains": "/api/order", "status": 200 }
63
+ ```
64
+
65
+ The one that catches the expensive bug:
66
+
67
+ ```json
68
+ { "kind": "net", "method": "POST", "urlContains": "/api/order", "count": 1 }
69
+ ```
70
+
71
+ **Exactly one.** Double-submit fails at two, and nothing on screen would have told you.
72
+
73
+ ### route
74
+
75
+ ```json
76
+ { "kind": "route", "pathname": "/success" }
77
+ ```
78
+
79
+ `contains` also works.
80
+
81
+ <Warning>
82
+ `route` asserts the route **changed**. Asserting the route you are already on always fails, and
83
+ the response says so: `expected: "a route change to /"`, `assertion: "route.changed"`.
84
+ </Warning>
85
+
86
+ ### console
87
+
88
+ ```json
89
+ { "kind": "console", "level": "error", "absent": true }
90
+ ```
91
+
92
+ "The flow completed and logged nothing." Worth attaching to most actions. Plenty of features work while quietly throwing.
93
+
94
+ ### signal
95
+
96
+ ```json
97
+ { "kind": "signal", "name": "webhook:received", "dataMatches": { "provider": "stripe", "id": "*" } }
98
+ ```
99
+
100
+ The app emitted a named signal via `reticle.signal()`. `dataMatches` is shallow JSON matching, and `*` means "present, any value".
101
+
102
+ Nothing outranks this. A `200` proves the server was reachable; a rendered row proves React ran. A signal is the application itself saying the thing succeeded. See [instrumentation](/instrumentation) for how to emit them.
103
+
104
+ ### state
105
+
106
+ ```json
107
+ { "kind": "state", "store": "app", "path": "deployments.0.status", "equals": "live" }
108
+ ```
109
+
110
+ Walks a dot-path, with numeric array indices. `equals` takes a literal, or an operator pattern:
111
+
112
+ | Operator | Meaning |
113
+ | ------------------------- | ----------------------------- |
114
+ | `$gte` `$lte` `$gt` `$lt` | numeric comparison |
115
+ | `$contains` | substring or array membership |
116
+ | `$length` | collection size |
117
+
118
+ Omit `equals` entirely to assert presence.
119
+
120
+ `store` is optional. Leave it out and the path picks the store: if exactly one registered store exposes it, that one is read. Registering several is normal, so this is the common case rather than a shortcut. Name a store when two of them genuinely expose the same path, which is the only situation Reticle cannot resolve on its own. If no store exposes it, that is a failure rather than a question, and the miss names the stores it searched.
121
+
122
+ This is the predicate that catches a UI-versus-store desync. A deploy that only _looks_ shipped. Deterministically, in one call, with no model involved. On a miss it names the real store value and the keys that were available, so a failure is legible rather than a blind "no".
123
+
124
+ ### animation
125
+
126
+ ```json
127
+ { "kind": "animation", "name": "dialog-in", "completed": true }
128
+ ```
129
+
130
+ ## Combinators
131
+
132
+ ```json
133
+ {
134
+ "kind": "allOf",
135
+ "predicates": [
136
+ { "kind": "signal", "name": "auth:granted" },
137
+ { "kind": "net", "method": "POST", "urlContains": "/api/login", "count": 1 },
138
+ { "kind": "console", "level": "error", "absent": true }
139
+ ]
140
+ }
141
+ ```
142
+
143
+ ```json
144
+ { "kind": "anyOf", "predicates": [ … ] }
145
+ { "kind": "not", "predicate": { … } }
146
+ ```
147
+
148
+ <Warning>
149
+ A combinator needs its own `kind`, and the children go in `predicates`. A bare
150
+ `{ "allOf": [ … ] }` does not parse, and neither does `{ "kind": "allOf", "allOf": [ … ] }`.
151
+
152
+ Reticle refuses the call rather than running half of it: _"Nothing ran, the predicate was not evaluated, so no verdict was produced."_ Better to be told than to get a green from an assertion that never executed.
153
+
154
+ </Warning>
155
+
156
+ `allOf` is the workhorse. "The signal fired **and** exactly one request went out **and** the console stayed clean" is a genuinely strong check, and it is one call.
157
+
158
+ Each child reports its own evidence:
159
+
160
+ ```json
161
+ "evidence": [
162
+ { "name": "auth:granted", "data": { "email": "admin@reticle.dev" } },
163
+ { "matched": 1 },
164
+ { "absent": true }
165
+ ]
166
+ ```
167
+
168
+ ## Timing and scoping
169
+
170
+ | Field | Where | What it does |
171
+ | --- | --- | --- |
172
+ | `timeout_ms` | `assert`, `wait_for`, `act_and_wait` | Wait up to N ms for it to become true |
173
+ | `since` | `net` and `console` leaves, and the call itself | Only consider events after this cursor |
174
+
175
+ The `since` cursor comes from a prior `reticle_act` response. Use it whenever you are asserting something an action was supposed to cause. Without it, an assertion can pass on an event from two clicks ago.
176
+
177
+ <Card title="Where predicates get used" icon="circle-check" href="/tools-act-and-wait">
178
+ Naming the consequence before the action is the whole point.
179
+ </Card>
@@ -0,0 +1,211 @@
1
+ ---
2
+ title: Quickstart: proof in five minutes
3
+ sidebarTitle: Quickstart
4
+ description: Install Reticle, connect your coding agent, and get a real verdict with evidence in about five minutes.
5
+ icon: rocket
6
+ ---
7
+
8
+ By the end of this page your agent will click a button in your app and prove what happened: the request that fired, the state that changed, and the file to open if it didn't.
9
+
10
+ <Note>
11
+ Every response on this page is real output, captured against a running app while writing it.
12
+ Nothing here is illustrative.
13
+ </Note>
14
+
15
+ ## Prerequisites
16
+
17
+ - Node 20 or newer
18
+ - A web app you can run locally, and its dev server
19
+ - A coding agent that speaks MCP (Claude Code, Cursor, Codex, OpenCode, Windsurf, VS Code)
20
+
21
+ ## Step 1. Let your agent install it
22
+
23
+ The fastest path is to hand the job to the agent that will use it. In your project directory:
24
+
25
+ ```bash
26
+ npx @reticlehq/server init
27
+ ```
28
+
29
+ This registers the MCP server, installs the SDK, wires it into your app's entry point, and writes the agent rule files. It is idempotent. Run it twice and the second run reports what was already done rather than doing it again.
30
+
31
+ Prefer to install the skill and let the agent run setup itself? On Claude Code the plugin registers the MCP server and the skill in one step:
32
+
33
+ ```text
34
+ /plugin marketplace add reticlehq/reticle
35
+ /plugin install reticle@reticlehq
36
+ ```
37
+
38
+ On Cursor, Codex, Copilot, Gemini and anything else the skills CLI supports:
39
+
40
+ ```bash
41
+ npx skills add reticlehq/reticle
42
+ ```
43
+
44
+ Then type `/reticle` and the agent runs the install from the skill.
45
+
46
+ Useful flags, from `reticle init` itself:
47
+
48
+ ```
49
+ reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
50
+ --app picks WHICH app in a monorepo, when several are found
51
+ --no-mcp skips MORE than the server registration: also the agent rule files
52
+ (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three
53
+ only make sense once the tools are reachable.
54
+ ```
55
+
56
+ Start with `--dry-run` if you want to see the plan before anything is written.
57
+
58
+ <Tip>
59
+ In a monorepo, `init` will find several apps and ask which one you mean. `--app packages/web`
60
+ skips the question.
61
+ </Tip>
62
+
63
+ Prefer to wire it yourself? See [Manual install](/install-manual). It is about ten lines, and worth reading once even if you let the agent do it, because it explains what the agent actually changed.
64
+
65
+ ## Step 2. Check the setup
66
+
67
+ ```bash
68
+ npx @reticlehq/server doctor
69
+ ```
70
+
71
+ `doctor` diagnoses the whole setup in one command: Chromium, the daemon, the port. If something is wrong this is the page you want before you start debugging your app instead.
72
+
73
+ ## Step 3. Start your app and open it
74
+
75
+ Start your dev server as usual, then:
76
+
77
+ ```bash
78
+ npx @reticlehq/server open
79
+ ```
80
+
81
+ This reuses the tab you already have open, or opens one if you don't. The SDK connects to the local daemon over a WebSocket, and your agent can now see the app.
82
+
83
+ <Warning>
84
+ If the browser console says it "could not reach the bridge", your app and the daemon disagree
85
+ about the port. Set it explicitly: `VITE_RETICLE_WS_URL=ws://localhost:4400/reticle` for Vite, or
86
+ `reticle.connect({url})`. This is the single most common setup problem, and the console message
87
+ tells you the exact port it tried.
88
+ </Warning>
89
+
90
+ ## Step 4. Look at the page
91
+
92
+ Ask your agent to take a snapshot. This is what came back from a real app:
93
+
94
+ ```json
95
+ {
96
+ "tree": "- textbox \"Email\" (ref=e3) [value=\"admin@reticle.dev\"]\n- textbox \"Password\" (ref=e4) [value=\"[REDACTED]\"]\n- button \"Sign in\" (ref=e5)",
97
+ "nodes": 3,
98
+ "truncated": false,
99
+ "status": { "route": "/", "title": "Reticle · Mission Control" },
100
+ "cost": { "bytes": 243, "tokens": 61 }
101
+ }
102
+ ```
103
+
104
+ Three things to notice.
105
+
106
+ The password is already `[REDACTED]`. Reticle redacts credential-shaped values before they leave the page, so your agent's context never holds your test password, let alone your real one.
107
+
108
+ The whole snapshot cost **61 tokens**. This is `mode: "interactive"`, which returns only the controls. A full accessibility-tree dump of a comparable page runs to thousands. Reticle is built to ask narrow questions, because the answer gets re-sent to the model on every turn.
109
+
110
+ Each control has a `ref` like `e5`. That's the handle for the next step, and it stays valid until the element leaves the DOM, so you don't re-snapshot between actions.
111
+
112
+ ## Step 5. Act, and prove it
113
+
114
+ Here is the whole idea of Reticle in one call. Your agent names the expected consequence **before** it acts:
115
+
116
+ ```json
117
+ {
118
+ "ref": "e5",
119
+ "action": "click",
120
+ "until": { "kind": "net", "method": "POST" }
121
+ }
122
+ ```
123
+
124
+ Stating the expectation up front is the difference between a check and a rationalisation. An agent that clicks first and then decides what counts as success will always find something that counts.
125
+
126
+ The real response, trimmed for length:
127
+
128
+ ```json
129
+ {
130
+ "verified": "yes",
131
+ "verifiedReason": "proved",
132
+ "because": "assertion held at presence grade over a clean capture with no channel disagreeing",
133
+ "effect": {
134
+ "ok": true,
135
+ "testid": "login-submit",
136
+ "component": "Login",
137
+ "name": "Sign in",
138
+ "effect": { "domMutatedWithin": 7, "appeared": "Signing in…" },
139
+ "source": { "file": "src/components/Login.tsx", "line": 81, "column": 8 }
140
+ },
141
+ "verdict": {
142
+ "pass": true,
143
+ "evidence": {
144
+ "method": "POST",
145
+ "url": "http://localhost:8787/api/login",
146
+ "status": 200,
147
+ "durationMs": 67,
148
+ "requestBody": "{\"email\":\"admin@reticle.dev\",\"password\":\"[REDACTED]\"}",
149
+ "responseBody": "{\"token\":\"[REDACTED]\",\"user\":{\"email\":\"admin@reticle.dev\"}}"
150
+ }
151
+ },
152
+ "summary": {
153
+ "net": { "total": 1, "errors": 0 },
154
+ "consoleErrors": 0,
155
+ "stateDiffs": [{ "path": "auth", "from": null, "to": "{\"email\":\"admin@reticle.dev\"}" }],
156
+ "signals": ["auth:granted"]
157
+ },
158
+ "honesty": {
159
+ "grade": "presence",
160
+ "coverage": { "pct": 100, "partial": false },
161
+ "integrity": { "clean": true, "issues": [] }
162
+ }
163
+ }
164
+ ```
165
+
166
+ ![A real act_and_wait verdict, annotated. The verdict, the source pointer, the request evidence, the request count, the state diff, and the app's own signal](/images/verdict-annotated.png)
167
+
168
+ Read what that actually proves:
169
+
170
+ | Field | What it tells you |
171
+ | --- | --- |
172
+ | `verified: "yes"` | The named consequence happened. Not "the page looks different", the specific thing. |
173
+ | `source.file` / `line` | The button lives at `src/components/Login.tsx:81`. If this had failed, your agent knows which file to open. |
174
+ | `verdict.evidence` | The exact request: one `POST /api/login`, `200`, 67ms. Not a summary of it. The request. |
175
+ | `stateDiffs` | Application state moved from `null` to a real user. A mock that returns 200 without changing state is caught right here. |
176
+ | `signals` | The app's own `auth:granted` fired. This is the strongest signal available, because the app declared success itself. |
177
+ | `net.total: 1` | Exactly one request. Double-submit bugs show up as `2`. |
178
+ | `integrity.clean` | Nothing interfered with the capture, so the verdict is trustworthy. |
179
+
180
+ ## What "verified" can say
181
+
182
+ <CardGroup cols={3}>
183
+ <Card title="yes" icon="circle-check">
184
+ The consequence you named actually happened, and the evidence is attached.
185
+ </Card>
186
+ <Card title="no" icon="circle-xmark">
187
+ It did not happen. This is a finding, with the source pointer to go fix it.
188
+ </Card>
189
+ <Card title="unknown" icon="circle-question">
190
+ Reticle drove the app and could not tell. **This is not a pass.** Report it as unknown.
191
+ </Card>
192
+ </CardGroup>
193
+
194
+ That third state is the one that matters. Most tools have two outcomes and quietly file "I couldn't tell" under "fine". A verification layer that cannot admit uncertainty is just a very confident random number generator.
195
+
196
+ ## Where to go next
197
+
198
+ <CardGroup cols={2}>
199
+ <Card title="Instrument your app" icon="wrench" href="/instrumentation">
200
+ Emit signals like `auth:granted` so your verdicts get stronger than "the DOM changed".
201
+ </Card>
202
+ <Card title="Every tool" icon="list" href="/tools-overview">
203
+ The 18 tools your agent sees by default, and the 30 more it can reach on demand.
204
+ </Card>
205
+ <Card title="Run it in CI" icon="circle-check" href="/testing">
206
+ Turn this session into a suite that blocks a bad merge.
207
+ </Card>
208
+ <Card title="Why Reticle" icon="lightbulb" href="/why-reticle">
209
+ The false-green problem, and the measured case that this fixes it.
210
+ </Card>
211
+ </CardGroup>
@@ -0,0 +1,242 @@
1
+ ---
2
+ title: Recipes: copy, paste, verify
3
+ sidebarTitle: Recipes
4
+ description: Worked examples for the situations you are actually in, with the real response each one returned.
5
+ icon: book-open
6
+ ---
7
+
8
+ Each recipe is a situation, the call that verifies it, and what came back. Every response on this page was captured from a running app.
9
+
10
+ ## "Did the login actually work?"
11
+
12
+ One call, three conditions. The signal fired, exactly one request went out, and nothing was logged.
13
+
14
+ ```json
15
+ {
16
+ "ref": "e5",
17
+ "action": "click",
18
+ "until": {
19
+ "kind": "allOf",
20
+ "predicates": [
21
+ { "kind": "signal", "name": "auth:granted" },
22
+ { "kind": "net", "method": "POST", "urlContains": "/api/login", "count": 1 },
23
+ { "kind": "console", "level": "error", "absent": true }
24
+ ]
25
+ }
26
+ }
27
+ ```
28
+
29
+ ```json
30
+ {
31
+ "verified": "yes",
32
+ "because": "assertion held at signal grade over a clean capture with no channel disagreeing",
33
+ "verdict": {
34
+ "pass": true,
35
+ "evidence": [
36
+ { "name": "auth:granted", "data": { "email": "admin@reticle.dev" } },
37
+ { "matched": 1 },
38
+ { "absent": true }
39
+ ]
40
+ },
41
+ "summary": {
42
+ "stateDiffs": [{ "path": "auth", "from": null, "to": "{…}" }],
43
+ "storageKeysChanged": ["reticle.bench.authToken", "reticle.bench.sessionId"]
44
+ },
45
+ "honesty": { "grade": "signal", "coverage": { "pct": 100, "partial": false } }
46
+ }
47
+ ```
48
+
49
+ Each child predicate reports its own evidence, in order. `count: 1` is the part that catches double-submit, and it costs nothing to ask for.
50
+
51
+ ## "Did navigating actually change the view?"
52
+
53
+ ```json
54
+ { "ref": "e17", "action": "click", "until": { "kind": "signal", "name": "nav:changed" } }
55
+ ```
56
+
57
+ ```json
58
+ {
59
+ "verified": "yes",
60
+ "verdict": {
61
+ "pass": true,
62
+ "evidence": { "name": "nav:changed", "data": { "view": "deployments" } }
63
+ },
64
+ "summary": {
65
+ "stateDiffs": [{ "path": "view", "from": "overview", "to": "deployments" }],
66
+ "route": "/deployments"
67
+ }
68
+ }
69
+ ```
70
+
71
+ The signal carries the destination in its payload, so one predicate proves both that navigation happened and that it went to the right place.
72
+
73
+ ## "Did the modal open?" and the lesson in getting it wrong
74
+
75
+ Here is a real failure, kept because it teaches more than another pass.
76
+
77
+ ```json
78
+ {
79
+ "ref": "e23",
80
+ "action": "click",
81
+ "until": {
82
+ "kind": "allOf",
83
+ "predicates": [
84
+ { "kind": "element", "query": { "role": "dialog" }, "state": "visible" },
85
+ { "kind": "console", "level": "error", "absent": true }
86
+ ]
87
+ }
88
+ }
89
+ ```
90
+
91
+ ```json
92
+ {
93
+ "verified": "no",
94
+ "verdict": {
95
+ "pass": false,
96
+ "evidence": [
97
+ {
98
+ "pass": false,
99
+ "expected": "an element matching {\"role\":\"dialog\"} in state 'visible'",
100
+ "observed": "no matching element on the page",
101
+ "evidence": { "presentTestids": ["deploy-table", "filter-search", "env-filter", "…"] }
102
+ },
103
+ { "pass": true, "evidence": { "absent": true } }
104
+ ]
105
+ },
106
+ "summary": {
107
+ "stateDiffs": [{ "path": "newDeployOpen", "from": false, "to": true }],
108
+ "signals": ["modal:opened"]
109
+ }
110
+ }
111
+ ```
112
+
113
+ Read the `summary`. The modal **did** open. `newDeployOpen` went `false → true` and the app fired `modal:opened`.
114
+
115
+ The assertion was wrong, not the app. That modal is a `div` with no `role="dialog"`, so a role query could never match it. (Which is also an accessibility bug worth fixing, found for free.)
116
+
117
+ The right predicate was sitting in the response the whole time:
118
+
119
+ ```json
120
+ { "until": { "kind": "signal", "name": "modal:opened" } }
121
+ ```
122
+
123
+ <Tip>
124
+ When an assertion fails, read `summary.signals` and `summary.stateDiffs` before you assume the
125
+ feature is broken. They tell you what actually happened, and usually hand you the predicate you
126
+ should have written.
127
+ </Tip>
128
+
129
+ ## "Nothing matched, and I do not know why"
130
+
131
+ An empty `reticle_query` result tells you what _is_ there:
132
+
133
+ ```json
134
+ {
135
+ "count": 0,
136
+ "elements": [],
137
+ "hint": {
138
+ "route": "/",
139
+ "knownEmptyState": true,
140
+ "presentTestids": ["nav-overview", "nav-deployments", "kpi-deploys", "…"]
141
+ }
142
+ }
143
+ ```
144
+
145
+ In this case the testid existed, on another route. The hint saved a snapshot and a guess.
146
+
147
+ ## "Is this button destructive?"
148
+
149
+ Reticle refuses controls that look destructive, before acting:
150
+
151
+ ```json
152
+ {
153
+ "error": "potentially destructive action blocked; retry with args.confirmDangerous=true",
154
+ "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."
155
+ }
156
+ ```
157
+
158
+ Confirm it explicitly when you mean it:
159
+
160
+ ```json
161
+ { "ref": "e23", "action": "click", "args": { "confirmDangerous": true }, "until": { … } }
162
+ ```
163
+
164
+ <Note>
165
+ The matcher is conservative and will occasionally stop something harmless. A button labelled "New
166
+ deploy" trips it. That is the trade: a false stop costs you one argument, and a false start costs
167
+ you a deleted record.
168
+ </Note>
169
+
170
+ ## "Did my change break anything else?"
171
+
172
+ ```json
173
+ { "tool": "reticle_verify_change", "args": { "since": "HEAD~1" } }
174
+ ```
175
+
176
+ Give it the files you edited, or a git ref. It works out which saved flows cover them and replays only those.
177
+
178
+ ## "What is even testable in this app?"
179
+
180
+ The best first call on an unfamiliar codebase:
181
+
182
+ ```json
183
+ { "tool": "reticle_capabilities" }
184
+ ```
185
+
186
+ ```json
187
+ {
188
+ "testids": ["login-email", "login-submit", "new-deploy", "deploy-submit", "…"],
189
+ "signals": ["auth:granted", "auth:denied", "deploy:created", "modal:opened", "…"],
190
+ "stores": ["app"],
191
+ "flows": [
192
+ {
193
+ "name": "ship-a-deploy",
194
+ "steps": ["nav-deployments", "new-deploy", "deploy-name", "deploy-submit"]
195
+ }
196
+ ],
197
+ "source": "live"
198
+ }
199
+ ```
200
+
201
+ That is the app stating what it considers testable, which beats snapshotting the DOM and inferring it from element names.
202
+
203
+ ## "The error state has never been tested"
204
+
205
+ Most error states have never run. Force one:
206
+
207
+ ```json
208
+ {
209
+ "tool": "reticle_network_mock",
210
+ "args": { "mocks": [{ "urlContains": "/api/deploys", "status": 500 }] }
211
+ }
212
+ ```
213
+
214
+ Then drive the flow and assert the error UI appears. Pass `clear: true` to turn mocking off.
215
+
216
+ <Warning>
217
+ This one needs a **Reticle-driven browser**, because mocks are applied through CDP and the always-on
218
+ SDK cannot do that. A pooled lease is **not** enough. Verified: `reticle_lease` still returns
219
+ `{ "ok": false, "reason": "no-cdp-provider" }`.
220
+
221
+ Your route is `RETICLE_CDP_URL` pointed at a Chrome started with `--remote-debugging-port`. The same applies to `reticle_screenshot`, `reticle_visual_diff` and `reticle_viewport`.
222
+
223
+ </Warning>
224
+
225
+ ## "I want this to run on every PR"
226
+
227
+ Record it once, replay it forever:
228
+
229
+ ```json
230
+ { "tool": "reticle_flow_verify" }
231
+ ```
232
+
233
+ Replays every saved flow and returns one consolidated verdict.
234
+
235
+ <CardGroup cols={2}>
236
+ <Card title="The predicate grammar" icon="filter" href="/predicates">
237
+ Every kind, every field, and which ones actually prove something.
238
+ </Card>
239
+ <Card title="Best practices" icon="star" href="/best-practices">
240
+ The habits behind these recipes, and the mistakes that produce a confident wrong pass.
241
+ </Card>
242
+ </CardGroup>