@reticlehq/server 2.7.0 → 2.9.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/README.md +14 -5
- package/SKILL.md +130 -778
- package/dist/bridge/bridge.d.ts +23 -0
- package/dist/bridge/bridge.js +80 -2
- package/dist/bridge/bridge.test-harness.d.ts +5 -0
- package/dist/bridge/bridge.test-harness.js +6 -4
- package/dist/capsule/causal-summary.d.ts +36 -1
- package/dist/capsule/causal-summary.js +33 -7
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +33 -5
- package/dist/cli/chromium-hint.js +82 -12
- package/dist/cli/cli-doctor.js +65 -34
- package/dist/cli/cli-flow-commands.js +8 -8
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +32 -0
- package/dist/cli/cli-port.js +82 -35
- package/dist/cli/cli-verify.d.ts +21 -0
- package/dist/cli/cli-verify.js +47 -3
- package/dist/cli/config-discovery.d.ts +35 -0
- package/dist/cli/config-discovery.js +163 -0
- package/dist/cli/doctor-daemon-line.js +4 -1
- package/dist/cli/doctor-project-line.d.ts +37 -0
- package/dist/cli/doctor-project-line.js +35 -0
- package/dist/cli/doctor-rows.d.ts +51 -0
- package/dist/cli/doctor-rows.js +54 -0
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +69 -0
- package/dist/cli/drive-attach.d.ts +99 -0
- package/dist/cli/drive-attach.js +203 -0
- package/dist/cli/drive-command.d.ts +13 -0
- package/dist/cli/drive-command.js +103 -0
- package/dist/cli/open-note.d.ts +20 -0
- package/dist/cli/open-note.js +37 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli/status-next-action.d.ts +37 -0
- package/dist/cli/status-next-action.js +61 -0
- package/dist/cli.js +119 -39
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/events/contradictions.d.ts +46 -0
- package/dist/events/contradictions.js +35 -22
- package/dist/events/declared.d.ts +45 -0
- package/dist/events/declared.js +73 -0
- package/dist/events/echo-mismatch.d.ts +8 -2
- package/dist/events/echo-mismatch.js +33 -5
- package/dist/events/event-filters.d.ts +1 -1
- package/dist/events/event-filters.js +7 -4
- package/dist/events/predicate-eval.d.ts +17 -72
- package/dist/events/predicate-eval.js +117 -236
- package/dist/events/predicate-parse.js +31 -3
- package/dist/events/predicate-route.d.ts +32 -0
- package/dist/events/predicate-route.js +102 -0
- package/dist/events/predicate-schema.d.ts +99 -0
- package/dist/events/predicate-schema.js +309 -0
- package/dist/events/predicate.d.ts +8 -0
- package/dist/events/predicate.js +296 -22
- package/dist/flows/annotate-tools.js +3 -0
- package/dist/flows/annotate.js +18 -4
- package/dist/flows/coverage.d.ts +38 -5
- package/dist/flows/coverage.js +30 -4
- package/dist/flows/decision.d.ts +15 -0
- package/dist/flows/decision.js +1 -1
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-replay-run.js +8 -1
- package/dist/flows/flow-tools.js +21 -5
- package/dist/flows/heal-run.js +1 -1
- package/dist/flows/replay.js +3 -0
- package/dist/honesty/accepted-write.d.ts +3 -0
- package/dist/honesty/accepted-write.js +8 -3
- package/dist/honesty/already-true.js +15 -0
- package/dist/honesty/blind-spots.js +10 -2
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +1 -0
- package/dist/honesty/uncaptured-bodies.d.ts +4 -2
- package/dist/honesty/uncaptured-bodies.js +9 -2
- package/dist/honesty/unread-outcome.d.ts +8 -0
- package/dist/honesty/unread-outcome.js +36 -9
- package/dist/honesty/unsettled.d.ts +49 -0
- package/dist/honesty/unsettled.js +82 -0
- package/dist/honesty/verified.d.ts +36 -3
- package/dist/honesty/verified.js +54 -11
- package/dist/http-server.d.ts +19 -0
- package/dist/http-server.js +202 -4
- package/dist/impact/impact-recorder.d.ts +23 -0
- package/dist/impact/impact-recorder.js +76 -0
- package/dist/impact/impact-store.d.ts +47 -0
- package/dist/impact/impact-store.js +171 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +47 -10
- package/dist/init/agent-rules.d.ts +36 -0
- package/dist/init/agent-rules.js +132 -22
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +38 -6
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +114 -0
- package/dist/init/confirm.js +165 -0
- package/dist/init/connect-steps.d.ts +2 -0
- package/dist/init/connect-steps.js +50 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/csp-check.d.ts +38 -0
- package/dist/init/csp-check.js +92 -0
- package/dist/init/csp-doctor.d.ts +25 -0
- package/dist/init/csp-doctor.js +49 -0
- package/dist/init/cursor.js +6 -9
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +33 -3
- package/dist/init/mcp-clients.d.ts +1 -0
- package/dist/init/mcp-clients.js +51 -7
- package/dist/init/mcp.d.ts +15 -0
- package/dist/init/mcp.js +19 -0
- package/dist/init/non-js-project.js +29 -5
- package/dist/init/plan-framework.d.ts +10 -0
- package/dist/init/plan-framework.js +78 -16
- package/dist/init/plan.d.ts +47 -6
- package/dist/init/plan.js +228 -69
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +157 -45
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +54 -6
- package/dist/init/snippets.d.ts +45 -5
- package/dist/init/snippets.js +194 -42
- package/dist/init/vite-config.js +53 -5
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +37 -5
- package/dist/input/scroll-find.d.ts +6 -0
- package/dist/input/scroll-find.js +17 -2
- package/dist/input/scroll-tools.js +4 -0
- package/dist/journal/attach-journal.d.ts +9 -1
- package/dist/journal/attach-journal.js +13 -2
- package/dist/journal/journal-query.d.ts +13 -0
- package/dist/journal/journal-query.js +28 -3
- package/dist/journal/workspace-gitignore.d.ts +10 -0
- package/dist/journal/workspace-gitignore.js +59 -0
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +35 -30
- package/dist/mcp/mcp-proxy.js +306 -104
- package/dist/mcp/mcp.d.ts +10 -1
- package/dist/mcp/mcp.js +70 -34
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +109 -2
- package/dist/mcp/proxy-lifecycle.d.ts +11 -2
- package/dist/mcp/proxy-lifecycle.js +12 -3
- package/dist/mcp/proxy-log.d.ts +63 -0
- package/dist/mcp/proxy-log.js +132 -0
- package/dist/mcp/server-instructions.d.ts +17 -0
- package/dist/mcp/server-instructions.js +56 -0
- package/dist/mcp/shared-params.d.ts +27 -0
- package/dist/mcp/shared-params.js +36 -0
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/session/connection-memory.d.ts +59 -0
- package/dist/session/connection-memory.js +108 -0
- package/dist/session/dev-command.d.ts +26 -0
- package/dist/session/dev-command.js +89 -0
- package/dist/session/dev-server-probe.d.ts +68 -0
- package/dist/session/dev-server-probe.js +102 -7
- package/dist/session/last-act.d.ts +26 -0
- package/dist/session/last-act.js +35 -1
- package/dist/session/live-control-tools.js +42 -5
- package/dist/session/no-session-diagnosis.d.ts +67 -2
- package/dist/session/no-session-diagnosis.js +236 -27
- package/dist/session/no-session-next-action.d.ts +46 -0
- package/dist/session/no-session-next-action.js +85 -0
- package/dist/session/no-session-watch.d.ts +40 -1
- package/dist/session/no-session-watch.js +174 -8
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +13 -1
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +4 -0
- package/dist/session/session-manager.js +35 -5
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.js +146 -5
- package/dist/telemetry/app-instrumented.d.ts +8 -0
- package/dist/telemetry/app-instrumented.js +10 -0
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +15 -17
- package/dist/telemetry/bug-found.js +52 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/daemon-telemetry.js +24 -0
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +56 -0
- package/dist/telemetry/install-source.js +74 -0
- package/dist/telemetry/instrumentation-stall.d.ts +50 -0
- package/dist/telemetry/instrumentation-stall.js +90 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +8 -0
- package/dist/telemetry/session-metrics.js +15 -0
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +26 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +46 -7
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +209 -28
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +49 -7
- package/dist/tools/browser-tools.js +10 -0
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +16 -7
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +73 -9
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +159 -26
- package/dist/tools/lease-hint.d.ts +30 -8
- package/dist/tools/lease-hint.js +65 -14
- package/dist/tools/lease-tools.d.ts +40 -1
- package/dist/tools/lease-tools.js +181 -83
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +10 -4
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +52 -0
- package/dist/tools/react-grace.js +90 -0
- package/dist/tools/read-tools.js +67 -5
- package/dist/tools/resolve-target.d.ts +33 -0
- package/dist/tools/resolve-target.js +53 -0
- package/dist/tools/settle-in-flight.d.ts +94 -0
- package/dist/tools/settle-in-flight.js +146 -0
- package/dist/tools/snapshot-delta.d.ts +10 -0
- package/dist/tools/snapshot-delta.js +194 -9
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +86 -1
- package/dist/tools/tools.js +61 -8
- package/dist/tools/unadvertised-help.js +9 -6
- package/docs/README.md +55 -11
- package/docs/actions.mdx +120 -0
- package/docs/agent-cheatsheet.md +95 -29
- package/docs/architecture.md +52 -35
- package/docs/benchmarks.md +127 -75
- package/docs/best-practices.mdx +216 -0
- package/docs/capabilities.mdx +534 -0
- package/docs/cli/affected.mdx +53 -0
- package/docs/cli/capsules.mdx +70 -0
- package/docs/cli/cloud.mdx +153 -0
- package/docs/cli/doctor.mdx +109 -0
- package/docs/cli/drive.mdx +74 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +94 -0
- package/docs/cli/hunt.mdx +73 -0
- package/docs/cli/identify.mdx +76 -0
- package/docs/cli/init.mdx +121 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +74 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +69 -0
- package/docs/cli/status.mdx +88 -0
- package/docs/cli/stop.mdx +64 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +76 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +62 -0
- package/docs/cli.mdx +241 -0
- package/docs/debugging.md +16 -10
- package/docs/deploy-checks.md +45 -12
- package/docs/desktop.mdx +384 -0
- package/docs/docs.json +364 -0
- package/docs/enterprise.md +22 -16
- package/docs/faq.mdx +102 -0
- package/docs/fixtures.md +13 -7
- package/docs/flows.md +29 -23
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +238 -0
- package/docs/gate-plan.md +48 -42
- package/docs/gates.md +28 -22
- package/docs/getting-started.md +129 -77
- package/docs/human-control.md +24 -18
- package/docs/index.mdx +143 -0
- package/docs/install-agentic.mdx +224 -0
- package/docs/install-manual.mdx +196 -0
- package/docs/instrumentation.mdx +238 -0
- package/docs/integration-patterns.md +41 -35
- package/docs/local-registry.md +19 -13
- package/docs/multi-agent-testing.md +19 -13
- package/docs/packages/babel-plugin.mdx +79 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +116 -0
- package/docs/packages/electron.mdx +85 -0
- package/docs/packages/eslint-plugin.mdx +86 -0
- package/docs/packages/next.mdx +89 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +119 -0
- package/docs/packages/vite-plugin.mdx +94 -0
- package/docs/packages.mdx +218 -0
- package/docs/platform-integration.md +35 -26
- package/docs/predicates.mdx +248 -0
- package/docs/quickstart.mdx +257 -0
- package/docs/recipes.mdx +282 -0
- package/docs/skill-file.mdx +107 -0
- package/docs/state-management.mdx +199 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +50 -33
- package/docs/telemetry-contract.md +106 -52
- package/docs/telemetry-events.mdx +475 -0
- package/docs/telemetry.md +63 -55
- package/docs/testing.md +44 -6
- package/docs/token-efficiency.md +37 -27
- package/docs/tools-act-and-wait.mdx +173 -0
- package/docs/tools-act-sequence.mdx +137 -0
- package/docs/tools-act.mdx +95 -0
- package/docs/tools-assert.mdx +129 -0
- package/docs/tools-console.mdx +63 -0
- package/docs/tools-inspect.mdx +88 -0
- package/docs/tools-navigate.mdx +99 -0
- package/docs/tools-network.mdx +119 -0
- package/docs/tools-observe.mdx +145 -0
- package/docs/tools-overview.mdx +160 -0
- package/docs/tools-query.mdx +126 -0
- package/docs/tools-session-and-feedback.mdx +112 -0
- package/docs/tools-sessions.mdx +94 -0
- package/docs/tools-snapshot.mdx +129 -0
- package/docs/tools-state.mdx +104 -0
- package/docs/tools-tools-and-run.mdx +156 -0
- package/docs/tools-wait-for.mdx +85 -0
- package/docs/troubleshooting.mdx +263 -0
- package/docs/usage.md +209 -165
- package/docs/vs-chrome-devtools-mcp.mdx +123 -0
- package/docs/vs-playwright-mcp.mdx +125 -0
- package/docs/vs-screenshots.mdx +137 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +109 -0
- package/package.json +5 -5
- package/docs/desktop-apps.md +0 -256
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- package/docs/mint.json +0 -61
package/docs/telemetry.md
CHANGED
|
@@ -1,70 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Telemetry
|
|
3
|
+
description: 'The complete description of the anonymous usage data Reticle collects, what it is for, and how to turn it off.'
|
|
4
|
+
icon: chart-line
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
Reticle
|
|
7
|
+
Reticle sends anonymous usage counters (which commands ran, which tools an agent called, whether a verification produced a verdict) and nothing from the app under test. To turn it off permanently, run `npx @reticlehq/server telemetry disable`.
|
|
4
8
|
|
|
5
9
|
This page is the complete description of what is collected. If something is not listed here, it is not sent.
|
|
6
10
|
|
|
7
11
|
## The short version
|
|
8
12
|
|
|
9
13
|
- **Anonymous.** We cannot tell who you are, and we do not try.
|
|
10
|
-
- **No code, no app data.** Nothing from the app under test
|
|
14
|
+
- **No code, no app data.** Nothing from the app under test (no DOM, no network traffic, no console output, no source, no file paths) ever leaves your machine.
|
|
11
15
|
- **The one exception is feedback you deliberately send us**, and it is never collected passively. See [Feedback](#feedback) below.
|
|
12
16
|
- **Opt out any time**, permanently, with one command:
|
|
13
17
|
|
|
14
18
|
```bash
|
|
15
|
-
|
|
19
|
+
npx @reticlehq/server telemetry disable
|
|
16
20
|
```
|
|
17
21
|
|
|
18
22
|
## What is sent
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
Eighteen kinds of events, each a single small JSON object. This is the whole list, and it is the same list the code enumerates:
|
|
21
25
|
|
|
22
26
|
| Event | When | Extra data |
|
|
23
27
|
| --- | --- | --- |
|
|
24
|
-
| `reticle_installed` | The first time Reticle runs on a machine |
|
|
28
|
+
| `reticle_installed` | The first time Reticle runs on a machine | n/a |
|
|
25
29
|
| `cli_command_run` | You run a `reticle` command | Which subcommand (`verify`, `status`, …) and which flags were present, by name |
|
|
26
|
-
| `daemon_started` | The local daemon starts |
|
|
27
|
-
| `daemon_stopped` | The local daemon stops | A summary of the session
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
+
| `daemon_started` | The local daemon starts | n/a |
|
|
31
|
+
| `daemon_stopped` | The local daemon stops | A summary of the session; see below |
|
|
32
|
+
| `session_progress` | Periodically, from a daemon that is still running | The same summary as `daemon_stopped`, marked `final: false`. It exists so a long session is not invisible until it exits |
|
|
33
|
+
| `app_instrumented` | The first time your app's SDK connects in a given daemon run | Nothing beyond the common fields. It is the second half of the install funnel: registering the MCP server is one thing, getting the SDK into a running page is another |
|
|
34
|
+
| `instrumentation_stalled` | Ten minutes into a daemon run in which your app has still never connected | The same three facts as `app_instrumented`, with how long it waited instead of how long it took. It fires at most once per daemon run, and a run that starts slowly and then works reports nothing at all. It carries no diagnosis: whether a dev server is listening would need a live probe, and probing your machine to enrich a metric is collecting for the metric's sake |
|
|
35
|
+
| `verification_completed` | A verification produces a verdict | Whether it passed, whether Reticle refused to call a passing check verified, and **why** the verdict came out that way; see below |
|
|
36
|
+
| `project_profiled` | Once per daemon start | The shape of the project; see below |
|
|
30
37
|
| `version_changed` | You update or roll back | The two version numbers, and which direction |
|
|
31
|
-
| `runtime_crashed` | The daemon hits an uncaught error | The error's type, **Reticle's own** stack frames, and the message with variables stripped
|
|
38
|
+
| `runtime_crashed` | The daemon hits an uncaught error | The error's type, **Reticle's own** stack frames, and the message with variables stripped; see below |
|
|
32
39
|
| `mcp_client_connected` | An agent attaches to the daemon | Whether it is a reconnect, and how long the daemon had been idle |
|
|
33
|
-
| `mcp_connection_lost` | The agent's MCP tools go away | Which stage (`first`, or `budget_spent` when it stopped retrying), the cause, and the attempt count. **At most twice per session
|
|
34
|
-
| `init_completed` | `reticle init` finishes | Whether it worked,
|
|
35
|
-
| `bug_found` | Reticle finds a defect in the app under test | The **kind** of defect (`signal-contradicted`, `console-error`, …) and how it was found
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
40
|
+
| `mcp_connection_lost` | The agent's MCP tools go away | Which stage (`first`, or `budget_spent` when it stopped retrying), the cause, and the attempt count. **At most twice per session**, because a reconnect loop can fire many times in a row and an event each would bill for the pathology instead of measuring it |
|
|
41
|
+
| `init_completed` | `reticle init` finishes | Whether it worked, a classified reason when it did not, and which published install route you came through, DECLARED by that route via `RETICLE_INSTALL_SOURCE` and never inferred, and `unknown` when nothing declared one |
|
|
42
|
+
| `bug_found` | Reticle finds a defect in the app under test | The **kind** of defect (`signal-contradicted`, `console-error`, …) and how it was found, never what it was found in |
|
|
43
|
+
| `tool_refused` | A tool cannot do what an agent asked | Which tool, why (one of six buckets: no session, no match, unsupported, bad arguments, not ready, other), and whether the same call was retried. **Never the message**, because a refusal interpolates whatever you asked for, so only the tool name and the bucket are sent. Capped at 50 per daemon run |
|
|
44
|
+
| `feedback_submitted` | **Only** when you or your agent explicitly send feedback | The report; see [Feedback](#feedback). The AGENT's call does not wait for the network: the receipt says `accepted` (validated, redacted, queued), never `sent`, and a delivery that then fails is reported back on the agent's next tool result. `reticle feedback` typed by a human still waits, because a person at a terminal is owed the real answer |
|
|
45
|
+
| `identified` | **Only** when you run `reticle identify` | What you chose to tell us; see [Telling us who you are](#telling-us-who-you-are) |
|
|
38
46
|
|
|
39
|
-
**There is no per-tool-call event.** Tool usage is counted in memory and leaves once, inside `daemon_stopped`, as a histogram like `{"reticle_act": 40, "reticle_assert": 12}`. That is counts of tool NAMES from a fixed list we define
|
|
47
|
+
**There is no per-tool-call event.** Tool usage is counted in memory and leaves once, inside `daemon_stopped`, as a histogram like `{"reticle_act": 40, "reticle_assert": 12}`. That is counts of tool NAMES from a fixed list we define, never arguments, results, selectors, or URLs.
|
|
40
48
|
|
|
41
|
-
**Parameter and flag NAMES are collected; their VALUES are not.** We record that `reticle_act` was called with `ref` and `action`, and that you ran `reticle serve --headed --port`. We do not record what you set them to. This distinction is the whole safety property, and it is absolute for CLI flags: `--http-token` holds a secret, `--drive` holds a URL, `--storage-state` holds a file path, so no flag value is ever sent. For tool parameters there is one narrow exception
|
|
49
|
+
**Parameter and flag NAMES are collected; their VALUES are not.** We record that `reticle_act` was called with `ref` and `action`, and that you ran `reticle serve --headed --port`. We do not record what you set them to. This distinction is the whole safety property, and it is absolute for CLI flags: `--http-token` holds a secret, `--drive` holds a URL, `--storage-state` holds a file path, so no flag value is ever sent. For tool parameters there is one narrow exception: a short, explicit allowlist of parameters whose values are enums _we_ defined (`action: "click"`), listed in [`argument-shape.ts`](../packages/server/src/telemetry/argument-shape.ts). A value outside that allowlist reports as `other`, so a future schema change cannot quietly start forwarding free text. `reticle_act`'s `args` (the text being typed into your app, which on a login form is a password) is **never** in that allowlist.
|
|
42
50
|
|
|
43
51
|
`daemon_stopped` carries: how long the session ran, how many tool calls and of which tools, how long each tool took (total and worst case), how many failed, how many verifications ran, browser/lease connection attempts with their failure causes, and which MCP clients connected (`claude-code`, `cursor`).
|
|
44
52
|
|
|
45
|
-
It also carries a snapshot of the **machine's** state
|
|
53
|
+
It also carries a snapshot of the **machine's** state (our own process's memory, free and total system RAM, load average and CPU count), taken at shutdown and again on any crash. This is what separates "your machine ran out of memory" from "Reticle has a bug", which otherwise produce identical-looking failures. No hostname, no username, no paths, no process list.
|
|
46
54
|
|
|
47
55
|
### Why a verdict came out that way
|
|
48
56
|
|
|
49
|
-
`verification_completed` carries the **clause** that decided the verdict, from a fixed list we define: `proved`, `contradicted`, `assertion_failed`, `already_true`, `unclean_capture`, `vacuous_grade`, `outcome_pending`, `outcome_unread`, `unsettled`, `observation_lost`, `inconclusive`.
|
|
57
|
+
`verification_completed` carries the **clause** that decided the verdict, from a fixed list we define: `proved`, `contradicted`, `assertion_failed`, `already_true`, `unclean_capture`, `vacuous_grade`, `outcome_pending`, `outcome_unread`, `unsettled`, `evidence_incomplete`, `observation_lost`, `inconclusive`.
|
|
50
58
|
|
|
51
|
-
It exists because `verified: "unknown"` covered seven different situations belonging to three different owners
|
|
59
|
+
It exists because `verified: "unknown"` covered seven different situations belonging to three different owners (your app, your agent, and Reticle's own blind spots), and they arrived as one value. It is a **name from our own vocabulary**, never a description of your app: `contradicted` says two channels disagreed, not which ones, about what, or on which page.
|
|
52
60
|
|
|
53
|
-
When the clause is `unclean_capture`, one further name says which of our three losses caused it
|
|
61
|
+
When the clause is `unclean_capture`, one further name says which of our three losses caused it: `buffer_loss` (our server's event buffer), `transport_gap` (our browser-side queue), `blind_spot` (a boundary in the page, such as a cross-origin frame), or `other`. All four are facts about **Reticle's** ability to observe, not about what it observed. This field is how we found that Reticle was refusing to answer over windows that were completely intact.
|
|
54
62
|
|
|
55
63
|
### Bugs Reticle finds
|
|
56
64
|
|
|
57
|
-
When Reticle catches a defect in the app it is verifying, it records **that a class of defect was found
|
|
65
|
+
When Reticle catches a defect in the app it is verifying, it records **that a class of defect was found**, never what it was found in. The event carries the kind (`signal-contradicted`, `duplicate-request`, `console-error`, …) and how it surfaced, and nothing else: no selector, no URL, no element, no description of your app or its behaviour.
|
|
58
66
|
|
|
59
|
-
This is the number we use to say whether Reticle works at all, and the one we would publish. It is counted conservatively on purpose
|
|
67
|
+
This is the number we use to say whether Reticle works at all, and the one we would publish. It is counted conservatively on purpose: a defect explained by a contradiction is not also counted as a failed assertion, because an inflated number would be worse than none.
|
|
60
68
|
|
|
61
69
|
### Errors and crashes
|
|
62
70
|
|
|
63
|
-
The SDK that runs inside your page reports its own failures too
|
|
71
|
+
The SDK that runs inside your page reports its own failures too: an observer that could not start, a patch that would not install. It does this over the **local bridge it is already connected to**; the SDK still makes no outbound request of its own, and the daemon decides what (if anything) is reported onward. What travels is our module name (`network_observer`), the error type, and the message with variables stripped, never your page's URL, and never anything from your app.
|
|
64
72
|
|
|
65
|
-
Errors are grouped by a **hash of the error's shape**, with every variable part removed first
|
|
73
|
+
Errors are grouped by a **hash of the error's shape**, with every variable part removed first, so `no baseline named 'checkout-v3'` and `no baseline named 'login'` become one anonymous group and neither flow name is sent. Alongside the hash we send that stripped shape (`no baseline named *`) and the tool that produced it, because a hash on its own can be counted but never understood.
|
|
66
74
|
|
|
67
|
-
A crash additionally carries **Reticle's own stack frames**
|
|
75
|
+
A crash additionally carries **Reticle's own stack frames** (`resolveAnchor@act-tools.js:142`), plus the tool that was running, the preceding tool names, and the Node version and CPU architecture. Those frames are our published npm code, readable by anyone who unpacks the tarball.
|
|
68
76
|
|
|
69
77
|
**Stack frames belonging to your application are dropped entirely**, along with node internals. In the example below only the two Reticle frames survive; your file, your function, and your home directory do not:
|
|
70
78
|
|
|
@@ -73,11 +81,11 @@ at doCheckout (/Users/ada/secret-app/src/checkout.tsx:42:9) ← dropped
|
|
|
73
81
|
at resolveAnchor (…/@reticlehq/server/dist/tools/act-tools.js:142:19) ← sent
|
|
74
82
|
```
|
|
75
83
|
|
|
76
|
-
**One narrow exception, for the crash that otherwise says nothing.** A refused connection
|
|
84
|
+
**One narrow exception, for the crash that otherwise says nothing.** A refused connection (`connect ECONNREFUSED`) has a stack that is _entirely_ node internals, so the rule above correctly keeps nothing and the report arrives with no location at all. Those crashes now also carry the failing **syscall** (`connect`), the **errno** (`ECONNREFUSED`), whether the target was **loopback** (one boolean), whether the port was **one of Reticle's own** (the enum `reticle` / `other`), and the innermost frame naming **Node's own source** (`node:net:1637`).
|
|
77
85
|
|
|
78
|
-
The address and the port number are used to compute those two answers and are then discarded
|
|
86
|
+
The address and the port number are used to compute those two answers and are then discarded; neither is ever sent. The Node frame is a line in Node's published source, not yours: it says a connect failed rather than a DNS lookup, and carries nothing about your machine, your app, or your directory layout. The frame is included **only** when the crash is a system error and no Reticle frame survived: the report that would otherwise be blind.
|
|
79
87
|
|
|
80
|
-
`project_profiled` carries: the framework and its major version, a size **bucket** (`tiny` … `huge`, never a file count), whether it is a monorepo, its age in whole **weeks**, how many saved flows/baselines/runs exist, and which Reticle feature families have been used. It also carries whether the project has git at all, whether it has ever been pushed (`none` / `local_only` / `remote`), and which forge hosts it
|
|
88
|
+
`project_profiled` carries: the framework and its major version, a size **bucket** (`tiny` … `huge`, never a file count), whether it is a monorepo, its age in whole **weeks**, how many saved flows/baselines/runs exist, and which Reticle feature families have been used. It also carries whether the project has git at all, whether it has ever been pushed (`none` / `local_only` / `remote`), and which forge hosts it: `github`, `gitlab`, `bitbucket`, `azure`, `sourcehut`, `codeberg`, or just `self_hosted` for anything else. A private git host is usually `git.<company>.com`, so self-hosted repos report **only** that they are self-hosted, never the hostname.
|
|
81
89
|
|
|
82
90
|
This is how we learn whether people are getting value from the whole product or only a corner of it. No file names, no paths, no flow names, no dependency list.
|
|
83
91
|
|
|
@@ -86,13 +94,13 @@ Every event carries the same few fields:
|
|
|
86
94
|
| Field | What it is | What it is not |
|
|
87
95
|
| --- | --- | --- |
|
|
88
96
|
| `anonymousId` | A random UUID minted locally on first run, stored at `~/.reticle/telemetry-id` | Not derived from your name, email, hardware, or anything else about you |
|
|
89
|
-
| `projectId` | A one-way SHA-256 hash of the git origin URL, or of the directory path outside a repo | Not reversible
|
|
90
|
-
| `actor` | Whether a person or an agent caused this
|
|
91
|
-
| `sessionId` | A random id for this daemon run, held in memory and never written to disk | Not a device id and not persistent
|
|
92
|
-
| `projectIdSource` | Whether `projectId` came from a shared git origin or from the local directory path | Not the origin or the path. It exists so we can tell when "how many people share this project" is a real measurement
|
|
93
|
-
| `version` | The Reticle version running |
|
|
97
|
+
| `projectId` | A one-way SHA-256 hash of the git origin URL, or of the directory path outside a repo | Not reversible: we can count _distinct_ projects, but not learn any project's name, URL, or location. Hashing the origin means one repo counts once instead of once per teammate and per clone |
|
|
98
|
+
| `actor` | Whether a person or an agent caused this: a typed `reticle` command is a person, an MCP tool call is the agent | Not a claim about _why_. Whether you asked your agent to verify, or it decided to, lives in a prompt Reticle never sees, and we do not guess |
|
|
99
|
+
| `sessionId` | A random id for this daemon run, held in memory and never written to disk | Not a device id and not persistent: a restarted daemon gets a new one. It only groups one run's own events together |
|
|
100
|
+
| `projectIdSource` | Whether `projectId` came from a shared git origin or from the local directory path | Not the origin or the path. It exists so we can tell when "how many people share this project" is a real measurement. Outside a pushed repo there is nothing shared to hash, so those rows always show one user |
|
|
101
|
+
| `version` | The Reticle version running | n/a |
|
|
94
102
|
| `os` | The platform (`darwin` / `linux` / `win32`) | Not the OS version, hostname, or hardware |
|
|
95
|
-
| `ci` | Whether the run is inside CI |
|
|
103
|
+
| `ci` | Whether the run is inside CI | n/a |
|
|
96
104
|
|
|
97
105
|
There are no IP-based profiles, no cookies, no fingerprinting, and no person profiles: events are processed in "personless" mode, so they are never joined into an identity.
|
|
98
106
|
|
|
@@ -108,23 +116,23 @@ A few of these are worth being explicit about, because they are the ones a produ
|
|
|
108
116
|
|
|
109
117
|
## Feedback
|
|
110
118
|
|
|
111
|
-
Reticle verifies apps for AI agents, and for a long time it had no way to hear when it got something wrong. An agent would hit a tool that misbehaved, work around it, and that knowledge would vanish at the end of the turn. The feedback channel is the fix
|
|
119
|
+
Reticle verifies apps for AI agents, and for a long time it had no way to hear when it got something wrong. An agent would hit a tool that misbehaved, work around it, and that knowledge would vanish at the end of the turn. The feedback channel is the fix, and because it is the only part of Reticle that transmits words someone wrote, it gets stricter rules than everything above.
|
|
112
120
|
|
|
113
121
|
**It is never passive.** There is no code path that sends feedback on its own. A `feedback` event exists only because one of these happened:
|
|
114
122
|
|
|
115
123
|
| Who | How | What it carries |
|
|
116
124
|
| --- | --- | --- |
|
|
117
|
-
| Your agent | Calls the `reticle_feedback` MCP tool
|
|
118
|
-
| You | Run `
|
|
119
|
-
| 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
|
|
125
|
+
| Your agent | Calls the `reticle_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
|
+
| You | Run `npx @reticlehq/server feedback [--rating 1-5] [--bug] "your words"` | Your words and your rating |
|
|
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 |
|
|
120
128
|
|
|
121
|
-
Agents are instructed, in the tool description itself, never to include app source, secrets, or user data
|
|
129
|
+
Agents are instructed, in the tool description itself, never to include app source, secrets, or user data, and to describe the failure in their own words instead.
|
|
122
130
|
|
|
123
|
-
**It is redacted before it leaves your machine.** Emails, credentials in URLs, `Authorization` headers and API-key assignments, recognizable vendor tokens (`sk-…`, `ghp_…`, `AKIA…`, `xoxb-…`, JWTs), and home-directory paths are stripped and replaced with `[redacted]`. This runs client-side, in [`feedback.ts`](../packages/server/src/telemetry/feedback.ts), so you can read exactly what it removes rather than take our word for it. It is a safety net, not a guarantee
|
|
131
|
+
**It is redacted before it leaves your machine.** Emails, credentials in URLs, `Authorization` headers and API-key assignments, recognizable vendor tokens (`sk-…`, `ghp_…`, `AKIA…`, `xoxb-…`, JWTs), and home-directory paths are stripped and replaced with `[redacted]`. This runs client-side, in [`feedback.ts`](../packages/server/src/telemetry/feedback.ts), so you can read exactly what it removes rather than take our word for it. It is a safety net, not a guarantee: it cannot catch a secret that looks like an ordinary sentence, which is why the instruction above comes first.
|
|
124
132
|
|
|
125
133
|
**It shows you what it sends.** `reticle feedback` prints the exact payload before transmitting, and names any redaction rule that fired.
|
|
126
134
|
|
|
127
|
-
Alongside the report, a feedback event carries context about the environment it came from
|
|
135
|
+
Alongside the report, a feedback event carries context about the environment it came from, never about you:
|
|
128
136
|
|
|
129
137
|
| Field | Example | Why |
|
|
130
138
|
| --- | --- | --- |
|
|
@@ -134,7 +142,7 @@ Alongside the report, a feedback event carries context about the environment it
|
|
|
134
142
|
| `driver` | `cdp`, `sdk` | Whether Reticle drove the page or observed your own browser |
|
|
135
143
|
| `client` | `claude-code`, `cursor` | The MCP client's own name from its handshake |
|
|
136
144
|
| `mcpScope` | `user`, `project` | How Reticle is registered |
|
|
137
|
-
| `kind` | `bug`, `gap`, `ambiguity`, `experience` | A defect, a blind spot, an undecidable verdict, or
|
|
145
|
+
| `kind` | `bug`, `gap`, `ambiguity`, `feature_request`, `improvement`, `experience` | A defect, a blind spot, an undecidable verdict, a thing that does not exist, a thing that exists but is awkward, or a human's overall take |
|
|
138
146
|
|
|
139
147
|
Plus the `version`, `os`, and `ci` fields every event carries. No paths, no project name, no dependency list, no user-agent string.
|
|
140
148
|
|
|
@@ -144,26 +152,26 @@ Plus the `version`, `os`, and `ci` fields every event carries. No paths, no proj
|
|
|
144
152
|
RETICLE_FEEDBACK=0
|
|
145
153
|
```
|
|
146
154
|
|
|
147
|
-
Every switch in [Your choices](#your-choices) disables feedback too. If telemetry is off, feedback is off
|
|
155
|
+
Every switch in [Your choices](#your-choices) disables feedback too. If telemetry is off, feedback is off, and `reticle feedback` will tell you so rather than silently discard what you wrote.
|
|
148
156
|
|
|
149
157
|
## Telling us who you are
|
|
150
158
|
|
|
151
|
-
Everything above is anonymous, and stays that way unless you decide otherwise. If you want us to know who you are
|
|
159
|
+
Everything above is anonymous, and stays that way unless you decide otherwise. If you want us to know who you are (to get support, to ask about an enterprise licence, or to be a design partner), there is one command, and running it is the only way it ever happens:
|
|
152
160
|
|
|
153
161
|
```bash
|
|
154
|
-
|
|
162
|
+
npx @reticlehq/server identify --context company --company "Acme" --email you@acme.com
|
|
155
163
|
```
|
|
156
164
|
|
|
157
165
|
`--context` is the only required part, and `company | side_project | open_source | learning` is the whole vocabulary. You can say "this is a company" without naming it, or name it without leaving an email.
|
|
158
166
|
|
|
159
167
|
Before it sends anything, it prints what it will send and one thing worth reading carefully: **the identity is linked to this machine's anonymous id, so identifying yourself also connects the anonymous usage already recorded from this machine to what you enter.** That is what makes it useful to us, and it is why you are told before choosing rather than after.
|
|
160
168
|
|
|
161
|
-
**One place mentions it to you.** After you send feedback as a human
|
|
169
|
+
**One place mentions it to you.** After you send feedback as a human (never as an agent, and never if you have already identified or already declined), the receipt prints one line offering this command, because a bug report we cannot reply to is a conversation that ends after one sentence. Your feedback has already been sent by then: the line is an offer, not a question, and nothing is gated on it. **No address is ever attached to the feedback itself**: it would put personal data on the anonymous stream, which is exactly what the rest of this page promises not to do.
|
|
162
170
|
|
|
163
171
|
To undo it:
|
|
164
172
|
|
|
165
173
|
```bash
|
|
166
|
-
|
|
174
|
+
npx @reticlehq/server identify --forget
|
|
167
175
|
```
|
|
168
176
|
|
|
169
177
|
That deletes the local file and stops any further sends. To have what was already sent removed, email support@reticlehq.com.
|
|
@@ -174,10 +182,10 @@ Events are sent over HTTPS to [PostHog](https://posthog.com) (US cloud), a produ
|
|
|
174
182
|
|
|
175
183
|
## Your choices
|
|
176
184
|
|
|
177
|
-
Telemetry is on by default and Reticle tells you so the first time it runs
|
|
185
|
+
Telemetry is on by default and Reticle tells you so the first time it runs: once, in one line, with a pointer to this page. To see the current state at any time:
|
|
178
186
|
|
|
179
187
|
```bash
|
|
180
|
-
|
|
188
|
+
npx @reticlehq/server telemetry status
|
|
181
189
|
```
|
|
182
190
|
|
|
183
191
|
Three ways to opt out, in whatever form fits your setup:
|
|
@@ -185,15 +193,15 @@ Three ways to opt out, in whatever form fits your setup:
|
|
|
185
193
|
| Method | Scope |
|
|
186
194
|
| --- | --- |
|
|
187
195
|
| `reticle telemetry disable` | This machine, permanently (until `reticle telemetry enable`) |
|
|
188
|
-
| `RETICLE_TELEMETRY=0` | Wherever the variable is set
|
|
189
|
-
| `DO_NOT_TRACK=1` | The [cross-tool convention](https://consoledonottrack.com)
|
|
196
|
+
| `RETICLE_TELEMETRY=0` | Wherever the variable is set; handy for CI or a fleet-wide profile |
|
|
197
|
+
| `DO_NOT_TRACK=1` | The [cross-tool convention](https://consoledonottrack.com), which Reticle honors |
|
|
190
198
|
|
|
191
|
-
Opting out changes nothing about how Reticle works. A failed or blocked telemetry send never delays, alters, or fails a command either
|
|
199
|
+
Opting out changes nothing about how Reticle works. A failed or blocked telemetry send never delays, alters, or fails a command either; sends are best-effort and asynchronous by design.
|
|
192
200
|
|
|
193
201
|
To also remove the locally stored random id, delete `~/.reticle/telemetry-id`.
|
|
194
202
|
|
|
195
203
|
## A note on data protection
|
|
196
204
|
|
|
197
|
-
The data described above is designed not to identify you: the only identifier is a locally minted random UUID, the project reference is a one-way hash, and no personal data is collected. We collect it on the basis of our legitimate interest in understanding and improving Reticle, we minimize what is collected to the fields listed here, and we honor every opt-out signal above. If you believe something in this design falls short of that intent, please open an issue
|
|
205
|
+
The data described above is designed not to identify you: the only identifier is a locally minted random UUID, the project reference is a one-way hash, and no personal data is collected. We collect it on the basis of our legitimate interest in understanding and improving Reticle, we minimize what is collected to the fields listed here, and we honor every opt-out signal above. If you believe something in this design falls short of that intent, please open an issue. That is a bug, and we will treat it as one.
|
|
198
206
|
|
|
199
207
|
Any change to what is collected will be listed on this page and called out in the release notes of the version that introduces it.
|
package/docs/testing.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Specs for CI
|
|
3
|
+
description: 'Turn an interactive drive into a repeatable suite with @reticlehq/test: declarative specs bound to signals, never DOM structure.'
|
|
4
|
+
icon: vial
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
To run Reticle checks in CI, install `@reticlehq/test`, write specs with `reticleTest(name, async (t) => …)`, then boot a headless session with `bootSession({ driveUrl, headless: true })` and run them with `runSpecs`. Exit non-zero when `summary.failed` is not `0`. Specs bind to **signals and testids, never DOM structure**, so they survive the refactors that break selector-based suites.
|
|
8
|
+
|
|
9
|
+
Driving Reticle interactively is reconnaissance. This page is how you make it repeatable.
|
|
4
10
|
|
|
5
11
|
```ts
|
|
6
12
|
import { reticleTest } from '@reticlehq/test';
|
|
@@ -20,7 +26,7 @@ reticleTest('ai chat edit', async (t) => {
|
|
|
20
26
|
|
|
21
27
|
## The test context `t`
|
|
22
28
|
|
|
23
|
-
A thin, typed façade over Reticle's tools
|
|
29
|
+
A thin, typed façade over Reticle's tools. It resolves testids → refs for you, so specs never touch refs or DOM:
|
|
24
30
|
|
|
25
31
|
| Method | What it does |
|
|
26
32
|
| --- | --- |
|
|
@@ -40,7 +46,7 @@ Any failed matcher throws with the structured evidence (near-miss, failure reaso
|
|
|
40
46
|
## Deterministic + honest
|
|
41
47
|
|
|
42
48
|
- **`t.clock`** bakes `reticle_clock` into the spec, so time-gated UI (a 5s auto-dismiss, a 500ms hover dwell) is tested deterministically instead of racing real timers.
|
|
43
|
-
- **`t.expectInputModeReal()
|
|
49
|
+
- **`t.expectInputModeReal()`**: a hover/drag spec asserts native input is active; if it's running synthetic (no CDP), the spec is **skipped with a reason**, never silently passing on a no-op. Enable real input headless with `reticle drive` (see [usage §18](usage.md#18-real-input-mode-native-hover--drag)).
|
|
44
50
|
|
|
45
51
|
## Run a suite (headless, the same path CI uses)
|
|
46
52
|
|
|
@@ -70,7 +76,7 @@ import { toJUnitXml, writeJUnit } from '@reticlehq/test';
|
|
|
70
76
|
|
|
71
77
|
## Flows become specs
|
|
72
78
|
|
|
73
|
-
`.reticle/` flows (see [Flows](flows.md)) can be executed directly as specs
|
|
79
|
+
`.reticle/` flows (see [Flows](flows.md)) can be executed directly as specs, replayed with their `expect`/`success` predicates and skipping `dynamic` (LLM-output) regions, so the recorded map and the suite can't drift apart:
|
|
74
80
|
|
|
75
81
|
```ts
|
|
76
82
|
import { flowsAsSpecs } from '@reticlehq/test';
|
|
@@ -79,4 +85,36 @@ import { flowsAsSpecs } from '@reticlehq/test';
|
|
|
79
85
|
|
|
80
86
|
## Authoring tip: record → prune → commit
|
|
81
87
|
|
|
82
|
-
You don't have to hand-write steps. Drive the flow once (or record it via the panel), let Reticle emit the program, trim it, and commit it as a spec
|
|
88
|
+
You don't have to hand-write steps. Drive the flow once (or record it via the panel), let Reticle emit the program, trim it, and commit it as a spec. The regression test is a byproduct of testing, not separate work.
|
|
89
|
+
|
|
90
|
+
## FAQ
|
|
91
|
+
|
|
92
|
+
### Do I need vitest or jest to run these?
|
|
93
|
+
|
|
94
|
+
No. `runSpecs` is its own runner: it takes the specs registered by `reticleTest`, an `invoke` function, and a `print` callback, and returns `{ results, summary }`. You can call it from a plain `node script.mjs`. If you already have vitest, nothing stops you calling `runSpecs` from inside a test, but the runner does not depend on one.
|
|
95
|
+
|
|
96
|
+
### How do I fail the CI job?
|
|
97
|
+
|
|
98
|
+
`runSpecs` never throws on a failing spec, so you have to read the summary and exit yourself:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
process.exit(summary.failed === 0 ? 0 : 1);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`summary` carries `{ total, passed, failed, skipped, ok }`. Note that **skipped is not failed**: a spec that skipped with a reason (see `t.expectInputModeReal()`) leaves `failed` at `0`.
|
|
105
|
+
|
|
106
|
+
### Why did my hover or drag spec skip instead of running?
|
|
107
|
+
|
|
108
|
+
Because native input was not active. `t.expectInputModeReal()` deliberately skips with a reason rather than passing on a synthetic no-op, since a synthetic hover cannot trigger a CSS `:hover` or a pointer-library drag. If you want those specs to actually run, drive the app with real input: `npx @reticlehq/server drive http://localhost:4310`, or point the server at a CDP endpoint with `RETICLE_CDP_URL`.
|
|
109
|
+
|
|
110
|
+
### Does the app need to be running already?
|
|
111
|
+
|
|
112
|
+
Yes. The spec runner is attach-only and never starts your dev server (that is the interactive agent's job, not CI's). Boot your app first, then pass its URL as `driveUrl`. If nothing is listening there, `bootSession` opens a headless tab against a URL that serves nothing and no session ever connects.
|
|
113
|
+
|
|
114
|
+
### Can I emit JUnit for my CI's test reporter?
|
|
115
|
+
|
|
116
|
+
Yes, `toJUnitXml` and `writeJUnit` are both exported from `@reticlehq/test`. Pass them the results array from `runSpecs`.
|
|
117
|
+
|
|
118
|
+
### Do I have to write specs by hand if I already have flows?
|
|
119
|
+
|
|
120
|
+
No. `flowsAsSpecs` registers one `reticleTest` per flow under `.reticle/flows/`, replayed with that flow's own `expect` and `success` predicates and skipping its `dynamic` regions. That keeps the recorded map and the CI suite from drifting apart, because they are the same artifact.
|
package/docs/token-efficiency.md
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Token efficiency
|
|
3
|
+
description: Why asking narrow questions costs a fraction of feeding the whole accessibility tree to the model every step.
|
|
4
|
+
icon: coins
|
|
5
|
+
---
|
|
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 `reticle_snapshot({ diff: true })`, the re-read costs about 99% less again.
|
|
2
8
|
|
|
3
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.
|
|
4
10
|
|
|
5
11
|
## Head-to-head (measured, same page, same moment)
|
|
6
12
|
|
|
7
|
-
Measured against the bench dashboard (`apps/bench-app`) **with a 1,000-item list rendered**, after login. Token estimate = characters ÷ 4. Reproduce with the benchmark harness
|
|
13
|
+
Measured against the bench dashboard (`apps/bench-app`) **with a 1,000-item list rendered**, after login. Token estimate = characters ÷ 4. Reproduce with the benchmark harness; see `bench/README.md`.
|
|
8
14
|
|
|
9
|
-
| Payload
|
|
10
|
-
|
|
|
11
|
-
| **Playwright MCP
|
|
12
|
-
| Playwright MCP
|
|
13
|
-
| Reticle
|
|
14
|
-
| Reticle
|
|
15
|
-
| Reticle
|
|
16
|
-
| Reticle
|
|
17
|
-
| Reticle
|
|
18
|
-
| Reticle
|
|
19
|
-
| **Reticle
|
|
15
|
+
| Payload | Tokens |
|
|
16
|
+
| ----------------------------------------------------------------------------- | ---------: |
|
|
17
|
+
| **Playwright MCP**, with-refs snapshot (the real payload it sends every step) | **~7,300** |
|
|
18
|
+
| Playwright MCP, bare accessibility tree (what we measured directly) | ~6,856 |
|
|
19
|
+
| Reticle `snapshot` `full` (whole page, incl. all 1,000 items) | ~4,144 |
|
|
20
|
+
| Reticle `snapshot` `interactive` (actionable elements only) | ~110 |
|
|
21
|
+
| Reticle `snapshot` `status` (route / dialogs / counters) | ~31 |
|
|
22
|
+
| Reticle `query`, one element | ~28 |
|
|
23
|
+
| Reticle `observe` (reaction after an action) | ~39 |
|
|
24
|
+
| Reticle `assert` verdict | ~33 |
|
|
25
|
+
| **Reticle, a full verify loop** (`query` + `observe` + `assert`) | **~100** |
|
|
20
26
|
|
|
21
|
-
**Result on this page:** the common Reticle loop is **~73× leaner** than Playwright MCP's per-step snapshot (100 vs ~7,300 tokens). The bare a11y tree we measured directly is 6,856; Playwright MCP's actual payload adds a `[ref=…]` to every node, pushing it to ~7,300. On the complex pages Playwright's ecosystem cites (50k+), the gap widens to **~100
|
|
27
|
+
**Result on this page:** the common Reticle loop is **~73× leaner** than Playwright MCP's per-step snapshot (100 vs ~7,300 tokens). The bare a11y tree we measured directly is 6,856; Playwright MCP's actual payload adds a `[ref=…]` to every node, pushing it to ~7,300. On the complex pages Playwright's ecosystem cites (50k+), the gap widens to **~100× to 500×**.
|
|
22
28
|
|
|
23
29
|
## Diffed snapshots: pay once, then only for changes
|
|
24
30
|
|
|
@@ -29,29 +35,33 @@ Measured on a representative 150-row dashboard (the shipped regression benchmark
|
|
|
29
35
|
| Payload | Tokens |
|
|
30
36
|
| ---------------------------------- | --------: |
|
|
31
37
|
| Full re-snapshot (150-row table) | **4,246** |
|
|
32
|
-
| `diff:true` after a one-row change | **
|
|
38
|
+
| `diff:true` after a one-row change | **65** |
|
|
33
39
|
| `diff:true` when nothing changed | **17** |
|
|
34
40
|
|
|
35
|
-
**~99% fewer tokens** to re-look after an action
|
|
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.
|
|
36
42
|
|
|
37
43
|
Every `reticle_snapshot`/`reticle_query` 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.
|
|
38
44
|
|
|
39
45
|
## The other tax: tool schemas, paid on every request
|
|
40
46
|
|
|
41
|
-
Per-payload leanness is only half the token story. Before an agent reads a single result, it pays for the tool SCHEMAS injected into its context on every request
|
|
47
|
+
Per-payload leanness is only half the token story. Before an agent reads a single result, it pays for the tool SCHEMAS injected into its context on every request, and this is the metric the field now organises around. A filed issue measures Playwright MCP's default tool list at 14.4k tokens = 7.2% of a Claude Code context window, and Microsoft's own README steers coding agents to the CLI over its MCP on exactly these grounds.
|
|
42
48
|
|
|
43
49
|
Measured live, all servers in one run, same tokenizer (`bench/harness/schema-tax.mjs`):
|
|
44
50
|
|
|
45
|
-
| MCP server
|
|
46
|
-
|
|
|
47
|
-
| **Reticle
|
|
48
|
-
| Playwright MCP
|
|
49
|
-
| Chrome DevTools MCP
|
|
50
|
-
| Reticle
|
|
51
|
+
| MCP server | tools | schema tokens |
|
|
52
|
+
| ---------------------------------------- | ----: | ------------: |
|
|
53
|
+
| **Reticle, the tool surface** | 18 | **~4,930** |
|
|
54
|
+
| Playwright MCP | 23 | 3,725 |
|
|
55
|
+
| Chrome DevTools MCP | 29 | 5,116 |
|
|
56
|
+
| Reticle, `RETICLE_ADVERTISE_ALL_TOOLS=1` | 48 | ~30,200 |
|
|
57
|
+
|
|
58
|
+
_Measured 2026-08-12 (`bench/raw/schema-tax.json`). Reticle's default surface has gained a tool since, so treat the first row as a floor._
|
|
51
59
|
|
|
52
60
|
There is one tool surface: the verify loop advertised directly, plus two meta-tools (`reticle_tools`, `reticle_run`) that reach every other tool on demand. Nothing is unreachable; the cold tail simply is not re-sent every turn.
|
|
53
61
|
|
|
54
|
-
`RETICLE_ADVERTISE_ALL_TOOLS=1` advertises everything WITH output schemas
|
|
62
|
+
`RETICLE_ADVERTISE_ALL_TOOLS=1` advertises everything WITH output schemas. It is a verification switch for suites that call by name, not a mode to run agents in. It is roughly 7x the per-turn cost, which is why it is opt-in.
|
|
63
|
+
|
|
64
|
+
**Every absolute number in this section is a dated reading, not a current value.** The table above was measured on 2026-08-12; a later `tools/list` read on 2026-08-14 put the default surface at 21,468 bytes and the full one at 134,368. The surface has grown a tool since, so both readings are already low. The _shape_ of the gap is the durable claim; the counts themselves are asserted by `surface-sizes.test.ts`, which reads them off the live surface, and that is the only place to trust them.
|
|
55
65
|
|
|
56
66
|
The typed result object still travels as `structuredContent` either way; the default surface simply does not advertise the output schema, which an agent reading the `text` block never consumed.
|
|
57
67
|
|
|
@@ -60,7 +70,7 @@ The typed result object still travels as `structuredContent` either way; the def
|
|
|
60
70
|
- **Full-tree vs full-tree, the gap is modest (~1.8×):** Reticle `full` (4,144) vs Playwright's with-refs snapshot (~7,300). Reticle collapses generic wrapper nodes, but both include every list item. If you force Reticle to dump the whole page each step, you don't save much.
|
|
61
71
|
- **The savings come from _not needing_ the full tree.** Playwright MCP's primary perception primitive is "return the accessibility tree"; Reticle's is "answer a specific question" (`query`/`assert`/`observe`/scoped or interactive `snapshot`). The win is architectural, not a cleverer serializer.
|
|
62
72
|
- **Cost scales with interactive elements + what you look at, not total DOM.** The 1,000 list items cost ~0 in `interactive` mode because they aren't interactive.
|
|
63
|
-
- **This is tool-output tokens only.** The agent's own reasoning tokens dominate either way
|
|
73
|
+
- **This is tool-output tokens only.** The agent's own reasoning tokens dominate either way, which is the point: keep observation cheap so the budget goes to thinking.
|
|
64
74
|
|
|
65
75
|
## Why it matters in practice
|
|
66
76
|
|
|
@@ -73,8 +83,8 @@ At scale (long flows, large dashboards, frequent re-runs for regression) that di
|
|
|
73
83
|
|
|
74
84
|
## Method & caveats
|
|
75
85
|
|
|
76
|
-
- One page, one tool, char/4 token proxy
|
|
77
|
-
- `_snapshotForAI()` (Playwright MCP's exact with-refs payload) was unavailable in the installed Playwright build, so we measured `body.ariaSnapshot()
|
|
86
|
+
- One page, one tool, char/4 token proxy: directional, not a benchmark suite. Absolute numbers vary by page; the _ratio_ is the point.
|
|
87
|
+
- `_snapshotForAI()` (Playwright MCP's exact with-refs payload) was unavailable in the installed Playwright build, so we measured `body.ariaSnapshot()`, the same accessibility tree it serializes; the real MCP payload is equal or slightly larger (it adds `[ref=…]`).
|
|
78
88
|
- Playwright MCP is excellent and Microsoft-backed; this is not a knock on it. It optimizes for cross-browser _driving_; Reticle optimizes for cheap, in-app _verification_. They can coexist (drive with one, assert with the other).
|
|
79
89
|
|
|
80
90
|
Run it yourself: the benchmark harness in `bench/` (see `bench/README.md`), with the demo + api running.
|