@reticlehq/server 3.3.0 → 3.5.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.
- package/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +12 -14
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +22 -0
- package/dist/command/cli/adhoc-suite.js +90 -19
- package/dist/command/cli/adhoc-verdict.d.ts +35 -0
- package/dist/command/cli/adhoc-verdict.js +90 -3
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +6 -2
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +7 -5
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +24 -4
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +24 -3
- package/dist/command/cli/cli-verify.js +54 -11
- package/dist/command/cli/cloud-cli.js +64 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.d.ts +1 -1
- package/dist/command/cli/cloud-login.js +27 -8
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/launch/cli-launch.d.ts +8 -0
- package/dist/command/cli/launch/cli-launch.js +21 -9
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
- package/dist/command/cli/ports/resolve/cli-port.js +125 -15
- package/dist/command/cli/setup-mcp-cli.js +21 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +79 -11
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
- package/dist/command/setup/bringup/dev-server-wait.js +1 -1
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +91 -9
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +86 -45
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +28 -1
- package/dist/command/setup/run-setup.js +103 -15
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +17 -8
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.js +171 -157
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +10 -0
- package/dist/features/harness/fill-values.js +50 -11
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.d.ts +12 -0
- package/dist/features/harness/openai-driver.js +21 -12
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/features/license/license.d.ts +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +56 -19
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/domain/domain-model.js +3 -1
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-replay-run.d.ts +2 -3
- package/dist/language/flows/flow-replay-run.js +13 -47
- package/dist/language/flows/flow-replay.d.ts +22 -8
- package/dist/language/flows/flow-replay.js +55 -9
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +10 -1
- package/dist/language/flows/flows.js +90 -14
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +42 -6
- package/dist/language/flows/verify-change-tools.js +5 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -1
- package/dist/memory/cloud/cloud-sync.js +6 -9
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +77 -3
- package/dist/memory/cloud/sync-cycle.js +325 -55
- package/dist/memory/cloud/sync-daemon.js +24 -20
- package/dist/memory/cloud/sync-disk.js +16 -2
- package/dist/memory/intent/intent-shard.d.ts +10 -32
- package/dist/memory/intent/intent-shard.js +0 -10
- package/dist/memory/intent/intent-tools.js +8 -3
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +9 -1
- package/dist/memory/journal/session-end.js +24 -12
- package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
- package/dist/memory/project/artifact-root-resolver.js +46 -19
- package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +4 -2
- package/dist/portal/bridge/bridge.d.ts +12 -3
- package/dist/portal/bridge/bridge.js +49 -3
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +29 -4
- package/dist/portal/input/viewport-tools.js +16 -6
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +20 -13
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/session-health.js +3 -2
- package/dist/portal/session/session-manager.d.ts +20 -6
- package/dist/portal/session/session-manager.js +19 -3
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session.d.ts +10 -8
- package/dist/portal/session/session.js +24 -32
- package/dist/surface/http-server.d.ts +3 -2
- package/dist/surface/http-server.js +8 -5
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +2 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.js +9 -7
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +46 -1
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +28 -52
- package/dist/surface/tools/assert/assert-grade.js +12 -1
- package/dist/surface/tools/assert/assert-verdict.js +2 -2
- package/dist/surface/tools/browser-tools.js +14 -2
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/error-recovery.js +5 -6
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +12 -7
- package/dist/surface/tools/invoke-tool.js +12 -8
- package/dist/surface/tools/lease-tools.js +15 -6
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +9 -1
- package/dist/surface/tools/tool-kit.d.ts +10 -0
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +1 -5
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/wire-journal.d.ts +3 -0
- package/dist/wire-journal.js +4 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +4 -2
- package/docs/actions.mdx +3 -4
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +20 -8
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +22 -7
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +21 -2
- package/docs/frameworks.mdx +29 -10
- package/docs/getting-started.md +15 -5
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +2 -2
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +4 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +12 -10
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +1 -1
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +7 -7
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +2 -2
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +10 -9
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/phenomena/phenomena.d.ts +0 -27
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
package/docs/predicates.mdx
CHANGED
|
@@ -7,7 +7,7 @@ icon: filter
|
|
|
7
7
|
|
|
8
8
|
A predicate declares what should be true, as `{ kind, ...fields }`. Reticle has twelve kinds: `signal`, `state`, `net`, `route`, `element`, `text`, `console`, `animation`, `settled`, and the combinators `allOf`, `anyOf` and `not`. Which one you pick decides whether a green means anything.
|
|
9
9
|
|
|
10
|
-
It is the argument to `until` on [`reticle_act_and_wait`](/tools/act-and-wait), and to `predicate` on [`reticle_assert`](/tools/assert) and [`
|
|
10
|
+
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_assert { action: "wait" }`](/tools/wait-for).
|
|
11
11
|
|
|
12
12
|
Every predicate is `{ kind, ...fields }`.
|
|
13
13
|
|
|
@@ -35,6 +35,7 @@ Take the advice. `element` and `text` are convenient and weak; `signal`, `state`
|
|
|
35
35
|
| `element` / `text` | weak | Something rendered. A mock renders too |
|
|
36
36
|
| `animation` | weak | An animation ran or finished |
|
|
37
37
|
| `settled` | timing | The page went quiet. A precondition, not a proof |
|
|
38
|
+
| `compare` | its sides | Two readings agree: the stronger side's grade |
|
|
38
39
|
|
|
39
40
|
## The leaves
|
|
40
41
|
|
|
@@ -48,13 +49,13 @@ Take the advice. `element` and `text` are convenient and weak; `signal`, `state`
|
|
|
48
49
|
}
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
`query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `value`, `by`, `component`, `source`, `scope`, `self`, `attrs`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `focused`, `inViewport`, `present`.
|
|
52
|
+
`query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `value`, `by`, `component`, `source`, `scope`, `self`, `attrs`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `pressed`, `focused`, `inViewport`, `present`.
|
|
52
53
|
|
|
53
54
|
`value` is the one worth knowing about. `{ "role": "textbox", "name": "Amount" }` collapses to "a textbox called Amount exists", which passes against an empty field; adding `value` is what makes it an assertion about what the user will actually send. `by` selects the locator strategy explicitly, `component` and `source` match React component identity and source location, and `self` restricts a scoped match to the scope element itself rather than its descendants.
|
|
54
55
|
|
|
55
56
|
`attrs` is a list of attribute **names**, such as `["href", "data-status"]`. It **projects** those attributes onto each match so you can read them back, and it does not filter. `["data-status=complete"]` is refused: it would otherwise look up an attribute literally named `data-status=complete`, find none, and leave the predicate resting on the locator alone, which is a green for any value the element happens to hold.
|
|
56
57
|
|
|
57
|
-
To READ the values, call `
|
|
58
|
+
To READ the values, call `reticle_look { action: "find" }` with `attrs` and look at the `attrs` map on each match. To ASSERT one: a form field's value goes in the query as `value`, while a state expressed as a data attribute is better asserted as the consequence it stands for, such as a `signal`, a `net` call, or the text the user actually sees.
|
|
58
59
|
|
|
59
60
|
```json
|
|
60
61
|
{ "kind": "element", "query": { "role": "textbox", "name": "Amount", "value": "274.58" } }
|
|
@@ -118,7 +119,7 @@ Two fields read the RESPONSE BODY, so a request that succeeded with the wrong nu
|
|
|
118
119
|
{ "kind": "net", "urlContains": "/api/job", "bodyMatches": { "status": "completed" } }
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
`bodyMatches` is a
|
|
122
|
+
`bodyMatches` is a field match, keyed like `signal.dataMatches` and taking the same operators (`*`, `$gte`, `$contains`). A key can be a dotted path into a wrapped answer: `{ "data.status": "completed" }`, `{ "items.0.id": "a1" }`, `{ "items.length": 3 }`. A nested object written out as a value is compared whole, so use the path to match one field inside it. Reach for it whenever the value is an enum, because that is the case a substring cannot decide: `bodyContains: "completed"` passes on a body carrying `"completedAt": null`, where the needle matched the KEY and the job was still queued. `completed`/`completedAt`, `success`/`successRate`, `sent`/`unsent` all have that shape.
|
|
122
123
|
|
|
123
124
|
Both require body capture, `reticle({ captureNetworkBodies: true })`, and say so when the body was never recorded rather than reporting an ordinary mismatch.
|
|
124
125
|
|
|
@@ -161,7 +162,7 @@ A passing route verdict quotes the transition itself:
|
|
|
161
162
|
{ "kind": "signal", "name": "webhook:received", "dataMatches": { "provider": "stripe", "id": "*" } }
|
|
162
163
|
```
|
|
163
164
|
|
|
164
|
-
The app emitted a named signal via `reticle.signal()`. `dataMatches`
|
|
165
|
+
The app emitted a named signal via `reticle.signal()`. `dataMatches` matches field by field, a key can be a dotted path (`"order.total"`), and `*` means "present, any value".
|
|
165
166
|
|
|
166
167
|
`count` works here exactly as it does on `net`:
|
|
167
168
|
|
|
@@ -303,6 +304,50 @@ This is the predicate that catches a UI-versus-store desync. A deploy that only
|
|
|
303
304
|
|
|
304
305
|
Network and DOM idle. This is the deterministic replacement for a fixed sleep, not a proof of anything: combine it with a real consequence rather than asserting it alone. Omitting `until` on [`reticle_act_and_wait`](/tools/act-and-wait) waits for exactly this.
|
|
305
306
|
|
|
307
|
+
### compare
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"kind": "compare",
|
|
312
|
+
"left": { "from": "text", "scope": "#refunded" },
|
|
313
|
+
"right": { "from": "net", "urlContains": "/api/refund", "path": "refunded" },
|
|
314
|
+
"as": "number"
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Two observed values must agree, and you do not have to know either one. Every other predicate checks one reading against a value you write down. That cannot catch a page that echoes the user's input instead of the server's answer unless you already know the right answer: a refund of `1187.01` that the server read as paise answered `{"refunded":11.87}`, the page showed `₹1,187.01`, and every other channel was green.
|
|
319
|
+
|
|
320
|
+
Each side reads one value from a source:
|
|
321
|
+
|
|
322
|
+
| `from` | Fields | Reads |
|
|
323
|
+
| --- | --- | --- |
|
|
324
|
+
| `net` | `urlContains`, `path`, `method?`, `body?` | A field of the LAST matching call's body. `body` is `response` (default) or `request` |
|
|
325
|
+
| `signal` | `name`, `path` | A field of the last `name` signal's payload |
|
|
326
|
+
| `state` | `path`, `store?` | A store value, as `state` reads it |
|
|
327
|
+
| `text` | `scope` | The whole text of the element `scope` names (a CSS selector) |
|
|
328
|
+
|
|
329
|
+
A `path` is a field name or a dotted path into nested data (`data.total`, `items.0.price`), and it must end on a single value, not an object.
|
|
330
|
+
|
|
331
|
+
`as` is `value` (the default) or `number`:
|
|
332
|
+
|
|
333
|
+
- `value` is strict equality. The text `"11.87"` and the number `11.87` are different, and the failure says so and suggests `as: "number"`.
|
|
334
|
+
- `number` reads one number from each side. A JSON number is used as it is; a string contributes its only number, so `"Refunded ₹1,187.01"` reads as `1187.01`. A string holding two numbers is unknown rather than guessed at, so scope the text to the one value. Add `tolerance` for a band.
|
|
335
|
+
|
|
336
|
+
A side nobody could read is never a pass. A call that never happened, a missing element or a missing field fails. A body that was not recorded, was truncated before the field, or had the field redacted is unknown. Comparing a source with itself is refused before it runs, because it would agree on every page.
|
|
337
|
+
|
|
338
|
+
Two live sides (`text`, `state`) are also read before the action. If they already agreed, the result is `already_true`, because their agreement says nothing about what the action did. Put a `net` or `signal` side in the comparison to tie it to the action.
|
|
339
|
+
|
|
340
|
+
Comparing request with response on the same call is legitimate, and it is the direct check for a unit mismatch:
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{
|
|
344
|
+
"kind": "compare",
|
|
345
|
+
"left": { "from": "net", "urlContains": "/api/refund", "body": "request", "path": "amount" },
|
|
346
|
+
"right": { "from": "net", "urlContains": "/api/refund", "path": "refunded" },
|
|
347
|
+
"as": "number"
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
306
351
|
## Asserting a failure on purpose
|
|
307
352
|
|
|
308
353
|
A fault-injection test (mock a 500, check the error UI) is asserting that a request **fails**. Say so in the predicate, or the verdict will disagree with you:
|
package/docs/quickstart.mdx
CHANGED
|
@@ -5,7 +5,7 @@ description: Install Reticle, connect your coding agent, and get a real verdict
|
|
|
5
5
|
icon: rocket
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
**Run the installer
|
|
8
|
+
**Run the installer once, then run `reticle connect --project "My App"` in your project.** Reticle embeds a dev-only SDK in your running app, signs you in through browser approval when needed, and syncs your checks to the project you name.
|
|
9
9
|
|
|
10
10
|
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.
|
|
11
11
|
|
|
@@ -53,18 +53,20 @@ npm install -g @reticlehq/server
|
|
|
53
53
|
npx @reticlehq/server setup mcp
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
## Step 2.
|
|
56
|
+
## Step 2. Connect this project
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
In your project directory, before opening your coding agent:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
npx @reticlehq/server
|
|
61
|
+
npx @reticlehq/server connect --project "My App"
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
This installs the SDK, wires your app, starts the dev server, and checks that a browser session connected. It then opens browser sign-in approval if needed, links this folder to the named cloud project, and sends existing local history. Approve the short code shown in the browser and terminal; the command resumes by itself. A saved sign-in is reused after the cloud accepts it. Open your coding agent once this command finishes; if it was already open during installation, restart it to load the new tools.
|
|
65
65
|
|
|
66
66
|
Step 1 is once per machine. This step is once per project.
|
|
67
67
|
|
|
68
|
+
Want local-only verification without an account? Run `reticle init` in this step instead. You can run `reticle connect` later to sync your history.
|
|
69
|
+
|
|
68
70
|
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:
|
|
69
71
|
|
|
70
72
|
```text
|
|
@@ -80,7 +82,7 @@ npx skills add reticlehq/reticle
|
|
|
80
82
|
|
|
81
83
|
Then type `/reticle` and the agent runs the install from the skill.
|
|
82
84
|
|
|
83
|
-
Useful flags
|
|
85
|
+
Useful flags for the local-only `reticle init` path:
|
|
84
86
|
|
|
85
87
|
```
|
|
86
88
|
reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
|
|
@@ -90,7 +92,7 @@ reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
|
|
|
90
92
|
only make sense once the tools are reachable.
|
|
91
93
|
```
|
|
92
94
|
|
|
93
|
-
Start with
|
|
95
|
+
Start with `reticle init --dry-run` if you want to see the app changes before anything is written.
|
|
94
96
|
|
|
95
97
|
<Tip>
|
|
96
98
|
In a monorepo, `init` will find several apps and ask which one you mean. `--app packages/web`
|
|
@@ -107,9 +109,9 @@ npx @reticlehq/server doctor
|
|
|
107
109
|
|
|
108
110
|
`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.
|
|
109
111
|
|
|
110
|
-
## Step 4.
|
|
112
|
+
## Step 4. Open your app again when you need it
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
`connect` already started the dev server and opened the app. On a later day, start your dev server as usual, then:
|
|
113
115
|
|
|
114
116
|
```bash
|
|
115
117
|
npx @reticlehq/server open
|
|
@@ -236,7 +238,7 @@ That third state is the one that matters. Most tools have two outcomes and quiet
|
|
|
236
238
|
<Accordion title="How long does it actually take to install Reticle?">
|
|
237
239
|
Two commands, and no restart if you run them in order. The installer registers the MCP server
|
|
238
240
|
with every agent on the machine, so opening your agent afterwards is all that is needed. Then
|
|
239
|
-
`npx @reticlehq/server init` wires the project
|
|
241
|
+
`npx @reticlehq/server init` wires the project, starts your dev server and opens the app for you. The MCP
|
|
240
242
|
registration is global, so every later project starts with the tools already there.
|
|
241
243
|
</Accordion>
|
|
242
244
|
|
package/docs/recipes.mdx
CHANGED
|
@@ -5,7 +5,7 @@ description: Worked examples for the situations you are actually in, with the re
|
|
|
5
5
|
icon: book-open
|
|
6
6
|
---
|
|
7
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 on 2026-08-16. Refs like `e605` are session-specific: take your own from [`
|
|
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 on 2026-08-16. Refs like `e605` are session-specific: take your own from [`reticle_look { action: "find" }`](/tools/query) before copying a call.
|
|
9
9
|
|
|
10
10
|
## "Did the login actually work?"
|
|
11
11
|
|
|
@@ -135,7 +135,7 @@ The right predicate was sitting in the response the whole time:
|
|
|
135
135
|
|
|
136
136
|
## "Nothing matched, and I do not know why"
|
|
137
137
|
|
|
138
|
-
An empty `
|
|
138
|
+
An empty `reticle_look { action: "find" }` result tells you what _is_ there:
|
|
139
139
|
|
|
140
140
|
```json
|
|
141
141
|
{
|
|
@@ -179,7 +179,7 @@ worker.onmessage = (e) => push(e.data);
|
|
|
179
179
|
{ "store": "app", "path": "cart.items.0.qty" }
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
`
|
|
182
|
+
`reticle_look { action: "state" }` walks a dot-path with numeric array indices. `depth` collapses anything deeper to a size marker, which is the difference between a cheap look and a very expensive one on a large store.
|
|
183
183
|
|
|
184
184
|
You can also assert on it directly, which is stronger than reading it afterwards:
|
|
185
185
|
|
|
@@ -190,7 +190,7 @@ You can also assert on it directly, which is stronger than reading it afterwards
|
|
|
190
190
|
That catches a UI-versus-store desync, a deploy that only looks shipped, deterministically and with no model involved.
|
|
191
191
|
|
|
192
192
|
<CardGroup cols={2}>
|
|
193
|
-
<Card title="
|
|
193
|
+
<Card title='reticle_look { action: "state" }' icon="database" href="/tools/state">
|
|
194
194
|
Arguments, depth control, and reading a component's hook state.
|
|
195
195
|
</Card>
|
|
196
196
|
<Card title="Instrument your app" icon="wrench" href="/instrumentation">
|
package/docs/system-map.md
CHANGED
|
@@ -66,7 +66,7 @@ These three were each wrong by the time somebody checked: the table read 68 / 48
|
|
|
66
66
|
|
|
67
67
|
| Token | Produced by | Consumed by | Invalidated by |
|
|
68
68
|
| --- | --- | --- | --- |
|
|
69
|
-
| `sessionId` | `
|
|
69
|
+
| `sessionId` | `reticle_session { action: "list" }`, HELLO | ~every tool | tab close; **survives reload** via `sessionStorage` |
|
|
70
70
|
| `ref` (`e115`) | `snapshot`, `query`, `scroll_to` | `inspect`, `act`, `act_and_wait`, `state` | any re-render; refuses cleanly rather than clicking the new occupant |
|
|
71
71
|
| `actionId` (`a2`) | `act`, `act_and_wait` | `observe`, `network`, `console` filters | window expiry |
|
|
72
72
|
| `since` cursor | every read | every read | ring-buffer eviction |
|
package/docs/telemetry.md
CHANGED
|
@@ -122,7 +122,7 @@ Reticle verifies apps for AI agents, and for a long time it had no way to hear w
|
|
|
122
122
|
|
|
123
123
|
| Who | How | What it carries |
|
|
124
124
|
| --- | --- | --- |
|
|
125
|
-
| Your agent | Calls the `
|
|
125
|
+
| Your agent | Calls the `reticle_session { action: "feedback" }` MCP tool (after a failure, **or to ask for a feature**) | What it wrote: an analysis, or a request with the goal behind it, what would improve, and how it works around the gap today. Plus the model it is running, which it tells us because MCP cannot |
|
|
126
126
|
| You | Run `npx @reticlehq/server feedback [--rating 1-5] [--bug] "your words"` | Your words and your rating |
|
|
127
127
|
| Your agent, before Reticle works | Runs `reticle feedback --agent --kind <bug\|gap\|ambiguity\|feature_request\|improvement> "what happened"` | The same words, filed from the shell. This exists for the phase where there is no daemon and no MCP tools (a failed `init`, half-finished wiring), which is the failure we are otherwise never told about |
|
|
128
128
|
|
package/docs/token-efficiency.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Why asking narrow questions costs a fraction of feeding the whole a
|
|
|
4
4
|
icon: coins
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
A full Reticle verify loop costs about **100 tokens**, against roughly **7,300** for a full-tree accessibility snapshot on the same page: about 73x leaner, because Reticle asks a narrow question instead of returning the page. If you re-look after an action with `
|
|
7
|
+
A full Reticle verify loop costs about **100 tokens**, against roughly **7,300** for a full-tree accessibility snapshot on the same page: about 73x leaner, because Reticle asks a narrow question instead of returning the page. If you re-look after an action with `reticle_look({ action: "page", diff: true })`, the re-read costs about 99% less again.
|
|
8
8
|
|
|
9
9
|
Agent browser tools that feed the **whole accessibility tree** to the model every step get expensive fast. Playwright MCP's own ecosystem notes its snapshots _"can exceed 50,000 tokens on complex pages,"_ with a _typical task ~114,000 tokens through MCP._ Reticle is built to ask **narrow questions** instead, so the per-interaction cost stays tiny.
|
|
10
10
|
|
|
@@ -28,7 +28,7 @@ Measured against the bench dashboard (`apps/bench-app`) **with a 1,000-item list
|
|
|
28
28
|
|
|
29
29
|
## Diffed snapshots: pay once, then only for changes
|
|
30
30
|
|
|
31
|
-
After the first snapshot, pass `
|
|
31
|
+
After the first snapshot, pass `reticle_look({ action: "page", diff: true })` to get back **only what changed** since your last look of the same scope/mode (`mode:delta` with added/removed lines, or `mode:unchanged`). A route change auto-resets to a full snapshot, so you never read a misleading cross-page diff.
|
|
32
32
|
|
|
33
33
|
Measured on a representative 150-row dashboard (the shipped regression benchmark `server/src/surface/tools/snapshot-cost.test.ts`, char/4 proxy):
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ Measured on a representative 150-row dashboard (the shipped regression benchmark
|
|
|
40
40
|
|
|
41
41
|
**~99% fewer tokens** to re-look after an action, and because a `delta` carries no stale full tree, it also removes the 60K to 80K-token stale-context buildup that makes long-running agents start hallucinating selectors that no longer exist.
|
|
42
42
|
|
|
43
|
-
Every `
|
|
43
|
+
Every `reticle_look { action: "page" }`/`reticle_look { action: "find" }` result also carries `cost:{ bytes, tokens }` (estimated) so you can **re-scope before reading** a large body (`mode:interactive`/`status`, a tighter `scope`, or a narrower `query`) instead of paying for it first.
|
|
44
44
|
|
|
45
45
|
## The other tax: tool schemas, paid on every request
|
|
46
46
|
|
|
@@ -162,6 +162,7 @@ A verification tool that cannot describe the quality of its own evidence is aski
|
|
|
162
162
|
| `predicate` | Alias for `until`, the name `assert` and `wait_for` use |
|
|
163
163
|
| `timeout_ms` | Maximum wait, defaulting to 4000. `0` evaluates the predicate once, no wait |
|
|
164
164
|
| `refuseWhenThrottled` | Throw rather than act on a throttled tab |
|
|
165
|
+
| `durable` | After a `yes`, reload the page and require the element, text and state parts of `until` to hold again. A consequence that is gone after the reload turns the verdict into `no`: the UI advanced, the change did not persist |
|
|
165
166
|
|
|
166
167
|
Omitting `until` waits for network and DOM idle, which is the deterministic replacement for a fixed sleep. To assert a consequence **and** settle, combine them:
|
|
167
168
|
|
|
@@ -5,7 +5,7 @@ icon: layer-group
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_act_sequence is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_act_sequence is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_act { steps: [...] }`** instead: the same call, routed on the steps array. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/act.mdx
CHANGED
|
@@ -59,7 +59,7 @@ Real response:
|
|
|
59
59
|
|
|
60
60
|
## Actions
|
|
61
61
|
|
|
62
|
-
`click` · `dblclick` · `hover` · `focus` · `blur` · `fill` · `type` · `clear` · `select` · `check` · `uncheck` · `submit` · `press` · `upload` · `scrollIntoView` · `drag`
|
|
62
|
+
`click` · `dblclick` · `hover` · `focus` · `blur` · `fill` · `type` · `clear` · `select` · `check` · `uncheck` · `submit` · `press` · `upload` · `scrollIntoView` · `drag`
|
|
63
63
|
|
|
64
64
|
| Action | Arguments |
|
|
65
65
|
| ---------------- | --------------------------------------------------------------- |
|
package/docs/tools/console.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: terminal
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_console is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_console is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_observe { action: "console" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/inspect.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: crosshairs
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_inspect is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_inspect is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_look { action: "element" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/network.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: satellite-dish
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_network is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_network is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_observe { action: "network" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/query.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: magnifying-glass
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_query is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_query is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_look { action: "find" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/sessions.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: list-check
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_sessions is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_sessions is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_session { action: "list" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/snapshot.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: camera
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_snapshot is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_snapshot is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_look { action: "page" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/state.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: database
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_state is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_state is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_look { action: "state" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/tools/wait-for.mdx
CHANGED
|
@@ -5,7 +5,7 @@ icon: hourglass
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Warning>
|
|
8
|
-
**reticle_wait_for is not advertised.** The default surface is the merged
|
|
8
|
+
**reticle_wait_for is not advertised.** The default surface is the merged ten, so an agent does not see
|
|
9
9
|
this name. Call **`reticle_assert { action: "wait" }`** instead. Everything below describes what that call does;
|
|
10
10
|
only the spelling changed. A call to the old name is answered with the new one, but an
|
|
11
11
|
`allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
|
package/docs/usage.md
CHANGED
|
@@ -4,7 +4,7 @@ description: 'The full reference and cookbook: every tool, flag, and workflow, w
|
|
|
4
4
|
icon: book
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Every Reticle drive is the same four steps: **look** (`reticle_look { action: "page" }` / `reticle_look { action: "find" }`), **act** (`reticle_act` / `reticle_act { steps: [...] }`), **observe** (`reticle_observe` / `reticle_observe { action: "network" }` / `reticle_look { action: "state" }`), **assert** (`reticle_assert`).
|
|
7
|
+
Every Reticle drive is the same four steps: **look** (`reticle_look { action: "page" }` / `reticle_look { action: "find" }`), **act** (`reticle_act` / `reticle_act { steps: [...] }`), **observe** (`reticle_observe` / `reticle_observe { action: "network" }` / `reticle_look { action: "state" }`), **assert** (`reticle_assert`). Verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`, so a run that ends anywhere else proved nothing. This page is the full reference for every tool, predicate, action and flag in that loop.
|
|
8
8
|
|
|
9
9
|
If you haven't set up Reticle yet, start with [Getting Started](getting-started.md).
|
|
10
10
|
|
|
@@ -183,7 +183,7 @@ The timeline + summary of what happened.
|
|
|
183
183
|
|
|
184
184
|
Act, then wait for a predicate: the whole act→observe→assert loop in one hop.
|
|
185
185
|
|
|
186
|
-
- **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `intent?`, `sessionId?`.
|
|
186
|
+
- **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `intent?`, `durable?` (after a yes, reload and require `until`'s element/text/state parts to hold again), `sessionId?`.
|
|
187
187
|
- **returns:** `{ effect, verdict, trace, session, warning? }`. `effect` is the action result (`{ ok, ref, action }`), `verdict` is `{ pass, evidence?, failureReason? }`, `trace` is the reaction report of everything the app did after the action, and `session` is the tab-health block `{ lastSeenMs, throttled, focused }` (with a `warning` when throttled). A failing `verdict` still returns `effect` + `trace` so you can see what _did_ happen. The predicate is automatically floored at this act's cursor, so it only matches events the action actually caused.
|
|
188
188
|
|
|
189
189
|
### `reticle_assert { action: "wait" }`
|
|
@@ -350,7 +350,7 @@ A **predicate** declares what should be true. `reticle_assert` / `reticle_assert
|
|
|
350
350
|
// An element exists / is in a state
|
|
351
351
|
{ "kind": "element", "query": { "role": "dialog", "name": "Confirm" }, "state": "visible" }
|
|
352
352
|
// query supports: role, name, text, label, placeholder, testid, alt, scope
|
|
353
|
-
// state: visible | hidden | enabled | disabled | checked | expanded | focused | present | inViewport
|
|
353
|
+
// state: visible | hidden | enabled | disabled | checked | expanded | pressed | focused | present | inViewport
|
|
354
354
|
// inViewport asserts the element is in the viewport NOW (not just in the DOM), so a scrollIntoView is gradeable
|
|
355
355
|
// add "absent": true to assert it is NOT there (regression / removal)
|
|
356
356
|
|
|
@@ -396,7 +396,7 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
|
|
|
396
396
|
- `timeout_ms` (on `assert`/`wait_for`): wait up to N ms for it to become true.
|
|
397
397
|
- `since` (on `net`/`console` leaves): only consider events after this cursor (from `act`).
|
|
398
398
|
|
|
399
|
-
`dataMatches`
|
|
399
|
+
`dataMatches` matches field by field; a key can be a dotted path (`"order.total"`, `"items.0.id"`), and `*` means "present, any value".
|
|
400
400
|
|
|
401
401
|
---
|
|
402
402
|
|
|
@@ -419,7 +419,6 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
|
|
|
419
419
|
| `scrollIntoView` | n/a | |
|
|
420
420
|
| `upload` | `{ name, content?, type? }` | sets a file on `<input type=file>` |
|
|
421
421
|
| `drag` | `{ toRef }` | pointer-based drag (dnd-kit / rbd) + HTML5 DnD |
|
|
422
|
-
| `webmcp` | `{ tool, params }` | calls a `navigator.modelContext` tool if the site exposes one |
|
|
423
422
|
|
|
424
423
|
---
|
|
425
424
|
|
|
@@ -640,7 +639,7 @@ Capture everything that happens across a span. Useful for "run my whole checkout
|
|
|
640
639
|
|
|
641
640
|
```jsonc
|
|
642
641
|
reticle_record {action:"start"}({ recordingName: "checkout" })
|
|
643
|
-
// …agent performs the flow (reticle_act
|
|
642
|
+
// …agent performs the flow (reticle_act, single or with steps)…
|
|
644
643
|
reticle_record {action:"stop"}({ recordingName: "checkout" })
|
|
645
644
|
// → {
|
|
646
645
|
// recordingName,
|
|
@@ -732,7 +731,7 @@ No, for basic look/act/observe. You'll get better results by adding `data-testid
|
|
|
732
731
|
|
|
733
732
|
### Does it work without React?
|
|
734
733
|
|
|
735
|
-
The core (DOM/network/route/console/animation/snapshot/actions) is framework-agnostic and is gated against a vanilla-TS app.
|
|
734
|
+
The core (DOM/network/route/console/animation/snapshot/actions) is framework-agnostic and is gated against a vanilla-TS app. What is proven for each framework, from driven to a verdict in CI to install-gated to wired but unverified, is listed once in [Frameworks](frameworks.mdx). See also [what Svelte support is and is not](getting-started.md#what-svelte-support-is-and-what-it-is-not).
|
|
736
735
|
|
|
737
736
|
### Can it judge whether my UI looks good?
|
|
738
737
|
|
|
@@ -858,6 +857,7 @@ It does **not** freeze `requestAnimationFrame`/microtasks (React's scheduler kee
|
|
|
858
857
|
"dataMatches": { "count": { "$gte": 1 }, "sections": { "$contains": "hook" } },
|
|
859
858
|
}
|
|
860
859
|
// operators: $gte $lte $gt $lt $contains (array/substring) $length ; "*" = present
|
|
860
|
+
// keys: a field name, or a dotted path into nested data ("order.total", "items.0.id")
|
|
861
861
|
```
|
|
862
862
|
|
|
863
863
|
On a failed signal assert, the result includes a **near-miss**: the signals that _did_ fire with that name + their data. And `reticle_observe`'s summary now includes `domChanged` (in-place text/attribute re-renders, not just added/removed nodes).
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Use cases: what people verify with Reticle'
|
|
3
|
+
sidebarTitle: Use cases
|
|
4
|
+
description: 'Verifying agent-built changes, security behaviour, accessibility and UX, performance, SEO basics, and driving the app as different users. What Reticle checks in each, with a call you can run.'
|
|
5
|
+
icon: list-check
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Reticle runs inside your app while it is running, so anything the app does (a request, a state change, a console line, a focus move, a route change, a performance entry) is something an agent can check and get a verdict on. People use it for more than "did my change work". This page covers the main uses. Each section lists what Reticle checks and gives one call you can run.
|
|
9
|
+
|
|
10
|
+
Every predicate below is a `json` value you pass as `until` to `reticle_act_and_wait` (the consequence of an action) or as `predicate` to `reticle_assert` (a check with no action). The URLs, names and store paths are placeholders for your own. The full grammar is in [Predicates](/predicates).
|
|
11
|
+
|
|
12
|
+
## Verifying agent-built changes
|
|
13
|
+
|
|
14
|
+
This is what Reticle was built for: an agent says a change works, and Reticle checks it against the running app.
|
|
15
|
+
|
|
16
|
+
- The request fired, returned the status you expected, and fired **once** (a double-submit is a `count` of 2).
|
|
17
|
+
- Application state moved the way the UI says it did. A total on screen that disagrees with the store is a failure.
|
|
18
|
+
- No console error appeared during the action.
|
|
19
|
+
- A failure names the `file:line` to open (on React, and on any framework whose build plugin stamps source; see [Frameworks](/frameworks)).
|
|
20
|
+
|
|
21
|
+
Click "Place order", then check all three at once:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"kind": "allOf",
|
|
26
|
+
"predicates": [
|
|
27
|
+
{ "kind": "net", "method": "POST", "urlContains": "/api/orders", "status": 201, "count": 1 },
|
|
28
|
+
{ "kind": "state", "store": "cart", "path": "count", "equals": 0 },
|
|
29
|
+
{ "kind": "console", "level": "error", "absent": true }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Once a journey has been driven, it is saved as a flow and replays with no model in the loop. `npx @reticlehq/server gate --since HEAD~1` fails unless a passing run covers every saved flow your edit touched.
|
|
35
|
+
|
|
36
|
+
## Security checks
|
|
37
|
+
|
|
38
|
+
This proves how your app behaves around security. It does not search for vulnerabilities, so it complements a vulnerability scanner rather than replacing one.
|
|
39
|
+
|
|
40
|
+
- **Access control.** Signed in as a role that should be refused, the protected call returns `403` and the app shows the denial, not a success toast.
|
|
41
|
+
- **Forbidden calls.** An endpoint that must not fire (an analytics beacon before consent, an admin API from a viewer's page) fires zero times: `{ "kind": "net", "urlContains": "/collect", "count": 0 }`.
|
|
42
|
+
- **Secrets not rendered.** A value that must never reach the page is absent from its text. Separately, the SDK redacts credential-shaped values (tokens, API keys, passwords, card numbers) from captured request and response bodies, storage and state before they reach the agent, and shows them as `[REDACTED]`.
|
|
43
|
+
- **CSP violations.** A Content Security Policy violation shows up as a console error (as a warning for report-only policies), so a CSP check needs nothing extra.
|
|
44
|
+
|
|
45
|
+
As a viewer, click "Delete user" and require the refusal:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"kind": "allOf",
|
|
50
|
+
"predicates": [
|
|
51
|
+
{ "kind": "net", "method": "DELETE", "urlContains": "/api/admin/users", "status": 403 },
|
|
52
|
+
{ "kind": "element", "query": { "role": "alert" }, "state": "visible" },
|
|
53
|
+
{ "kind": "text", "contains": "sk_live_", "absent": true },
|
|
54
|
+
{ "kind": "console", "contains": "Content Security Policy", "absent": true }
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To check reachability for each role, see [Personas and simulation](#personas-and-simulation) below.
|
|
60
|
+
|
|
61
|
+
## Accessibility and UX
|
|
62
|
+
|
|
63
|
+
Reticle finds elements the way assistive technology does: by role and accessible name. That makes some accessibility behaviour directly checkable. For contrast, full screen-reader behaviour and complete WCAG coverage, pair it with an audit tool such as axe.
|
|
64
|
+
|
|
65
|
+
- A control is reachable by its **role and accessible name**. An icon button with no name cannot be found with `{ "role": "button", "name": "Close" }`.
|
|
66
|
+
- **Focus lands where it should.** When a dialog opens, focus moves into it (`state: "focused"`), and Reticle records focus dropping to `<body>` when a dialog closes.
|
|
67
|
+
- **The keyboard works.** `press` with `Enter` or `Escape` fires the control's action, not just a mouse click. The [verify-keyboard-access](https://github.com/reticlehq/reticle/tree/main/skills/verify-keyboard-access) skill walks through it.
|
|
68
|
+
- **Visible, enabled, checked, expanded, pressed and in-viewport** are all element states you can assert.
|
|
69
|
+
|
|
70
|
+
A synthetic Tab key does not move focus in a browser, so Reticle cannot verify Tab order, and it reports that as unknown rather than as a pass.
|
|
71
|
+
|
|
72
|
+
Open the "Delete project" dialog with `press` (`args: { "text": "Enter" }`) and require focus to move into it:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"kind": "allOf",
|
|
77
|
+
"predicates": [
|
|
78
|
+
{
|
|
79
|
+
"kind": "element",
|
|
80
|
+
"query": { "role": "dialog", "name": "Delete project" },
|
|
81
|
+
"state": "visible"
|
|
82
|
+
},
|
|
83
|
+
{ "kind": "element", "query": { "role": "button", "name": "Cancel" }, "state": "focused" }
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Performance and monitoring
|
|
89
|
+
|
|
90
|
+
- **Request cardinality.** One keystroke in a debounced search sends one request, not one per character. The `duplicate-request` finding flags a write that fired more than once in a single action.
|
|
91
|
+
- **Page performance entries.** The SDK records largest-contentful-paint, a running layout-shift total and long tasks as `perf` events, which you read with `reticle_observe { filters: ["perf"] }`. The layout-shift figure is a running sum without the windowing a Core Web Vitals CLS score uses, so it works for "no layout shift on load" and trends, not as an exact CWV number.
|
|
92
|
+
- **Request timing.** Every captured request carries its duration in `reticle_observe { action: "network" }`.
|
|
93
|
+
- **React render rate.** With the React adapter, the commit count is readable as the `__reticle_renders` store, so a component that re-renders continuously while the screen looks idle shows up.
|
|
94
|
+
- **Recurring re-verification.** `npx @reticlehq/server verify <url>` replays your saved flows against a dev server or a staging/preview deployment and exits non-zero on a failure, so it runs in CI or on a schedule. The SDK refuses to connect in a production build, so this covers environments you control, not live production traffic.
|
|
95
|
+
|
|
96
|
+
Type "shoes" into a debounced search box and require exactly one request:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{ "kind": "net", "method": "GET", "urlContains": "/api/search", "count": 1 }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## SEO checks
|
|
103
|
+
|
|
104
|
+
Reticle sees the rendered page and the routes, so it covers the behavioural half of SEO. It does not read `<head>` meta tags or score a page, so pair it with an SEO crawler for those.
|
|
105
|
+
|
|
106
|
+
- `reticle_look { action: "page" }` reports the document `title` and the current route.
|
|
107
|
+
- **Headings and links.** The page's heading exists under its accessible name, and `reticle_look { action: "find", by: "role", value: "link", attrs: ["href"] }` lists every link with its `href`.
|
|
108
|
+
- **Redirects and routing.** A legacy URL lands on the right path: `{ "kind": "route", "pathname": "/pricing" }`.
|
|
109
|
+
- **Broken links and dead controls.** `reticle_verify { action: "crawl" }` drives every reachable control and reports failed requests, console errors and controls that do nothing.
|
|
110
|
+
|
|
111
|
+
After navigating to the old `/plans` URL, require the redirect and the page's main heading:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"kind": "allOf",
|
|
116
|
+
"predicates": [
|
|
117
|
+
{ "kind": "route", "pathname": "/pricing" },
|
|
118
|
+
{ "kind": "element", "query": { "role": "heading", "name": "Pricing" }, "state": "visible" }
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Personas and simulation
|
|
124
|
+
|
|
125
|
+
- **Drive the app as a user.** `explore` takes a persona in plain words, drives that whole journey with a model inside the daemon, and saves what it drove as a flow that later replays with no model.
|
|
126
|
+
- **One context per role.** `reticle_lease { action: "acquire" }` opens an isolated browser context (its own cookies, storage and DOM) and can seed storage or cookies first, so an admin and a viewer drive the same app side by side. Reach it with `reticle_run`, or use `reticle verify <url> --storage-state <file>` from a terminal.
|
|
127
|
+
- **Reachability testing.** Signed in as a role, a control that role must not have is absent, and the call behind it never fires. `reticle_verify { action: "coverage" }` lists the controls you have and have not driven this session.
|
|
128
|
+
- **Multi-agent runs.** Several agents lease contexts from one shared headless Chromium, capped and queued, instead of starting a browser each. See [Multi-agent](/multi-agent-testing).
|
|
129
|
+
|
|
130
|
+
Have Reticle drive a first-time user's journey and record it:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx @reticlehq/server verify http://localhost:3000 --explore --persona "a new user who signs up and creates a first project"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Then, signed in as a viewer, check what that role can reach:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"kind": "allOf",
|
|
141
|
+
"predicates": [
|
|
142
|
+
{ "kind": "element", "query": { "role": "button", "name": "Invite member" }, "absent": true },
|
|
143
|
+
{ "kind": "net", "urlContains": "/api/billing", "count": 0 }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Pairs well with
|
|
149
|
+
|
|
150
|
+
Reticle checks what your app does from inside it. Some jobs belong to other tools, and it works alongside them:
|
|
151
|
+
|
|
152
|
+
- **Pixel-level visual diffs:** a visual testing tool. Reticle can take screenshots, but its verdicts come from behaviour, not pixels.
|
|
153
|
+
- **Driving sites you don't own, or a cross-browser matrix:** Playwright. Reticle needs its dev-only SDK in the app, and its driven browser is Chromium.
|
|
154
|
+
- **Full WCAG audits:** axe or a similar audit tool.
|
|
155
|
+
- **Vulnerability discovery:** a security scanner.
|
|
156
|
+
|
|
157
|
+
What Reticle cannot see yet: IndexedDB, Web Workers, closed shadow roots and cross-origin iframes. When part of the page was out of reach, the verdict carries a `coverage` note, so a pass reads as "nothing failed in the part I could see". When the evidence cannot decide, the verdict is `unknown`, which is never counted as a pass.
|