@reticlehq/server 2.8.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/SKILL.md +130 -813
- 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 +16 -1
- package/dist/capsule/causal-summary.js +2 -1
- package/dist/cli/cli-doctor.js +41 -13
- package/dist/cli/cli-flow-commands.js +8 -8
- package/dist/cli/cli-launch.d.ts +0 -17
- package/dist/cli/cli-launch.js +0 -28
- package/dist/cli/cli-port.d.ts +24 -0
- package/dist/cli/cli-port.js +66 -35
- package/dist/cli/cli-verify.d.ts +21 -0
- package/dist/cli/cli-verify.js +45 -1
- 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.js +6 -3
- 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/status-next-action.d.ts +37 -0
- package/dist/cli/status-next-action.js +61 -0
- package/dist/cli.js +65 -70
- package/dist/events/contradictions.d.ts +32 -0
- package/dist/events/contradictions.js +9 -5
- 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 -91
- package/dist/events/predicate-eval.js +85 -326
- 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 +127 -5
- 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-replay-run.js +8 -1
- package/dist/flows/flow-tools.js +9 -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 +4 -0
- 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 +9 -0
- package/dist/honesty/unsettled.js +7 -3
- package/dist/honesty/verified.d.ts +29 -3
- package/dist/honesty/verified.js +51 -8
- 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 +44 -10
- package/dist/init/agent-rules.d.ts +20 -0
- package/dist/init/agent-rules.js +36 -0
- package/dist/init/astro-patch.js +27 -1
- package/dist/init/confirm.d.ts +21 -3
- package/dist/init/confirm.js +32 -5
- package/dist/init/connect-steps.d.ts +2 -0
- package/dist/init/connect-steps.js +50 -0
- 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/plan-framework.d.ts +10 -0
- package/dist/init/plan-framework.js +25 -1
- package/dist/init/plan.d.ts +7 -2
- package/dist/init/plan.js +66 -30
- package/dist/init/run.js +11 -5
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +12 -4
- package/dist/init/snippets.d.ts +1 -1
- package/dist/init/snippets.js +35 -7
- package/dist/init/vite-config.js +24 -2
- package/dist/input/network-mock-tools.js +8 -1
- 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/mcp/mcp-proxy.d.ts +5 -66
- package/dist/mcp/mcp-proxy.js +180 -163
- package/dist/mcp/mcp.d.ts +10 -1
- package/dist/mcp/mcp.js +47 -27
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +109 -2
- package/dist/mcp/proxy-lifecycle.d.ts +10 -1
- package/dist/mcp/proxy-lifecycle.js +11 -2
- 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/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 +48 -2
- package/dist/session/no-session-diagnosis.js +176 -20
- 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 +165 -11
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +13 -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/bug-found.d.ts +18 -0
- package/dist/telemetry/bug-found.js +14 -0
- package/dist/telemetry/daemon-telemetry.js +24 -0
- package/dist/telemetry/install-source.d.ts +20 -0
- package/dist/telemetry/install-source.js +34 -0
- package/dist/telemetry/instrumentation-stall.d.ts +50 -0
- package/dist/telemetry/instrumentation-stall.js +90 -0
- package/dist/telemetry/session-metrics.d.ts +8 -0
- package/dist/telemetry/session-metrics.js +10 -0
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +13 -2
- package/dist/tools/act-tools.js +166 -29
- package/dist/tools/assert-verdict.js +32 -7
- package/dist/tools/browser-tools.js +10 -0
- package/dist/tools/dynamic-tools.js +16 -7
- package/dist/tools/error-recovery.js +9 -5
- package/dist/tools/invoke-tool.js +20 -3
- package/dist/tools/lease-hint.d.ts +30 -8
- package/dist/tools/lease-hint.js +65 -14
- package/dist/tools/lease-tools.d.ts +11 -0
- package/dist/tools/lease-tools.js +141 -96
- package/dist/tools/observe-tools.js +9 -3
- package/dist/tools/react-grace.d.ts +4 -0
- package/dist/tools/react-grace.js +6 -3
- package/dist/tools/read-tools.js +47 -1
- 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 +15 -0
- package/dist/tools/settle-in-flight.js +42 -2
- package/dist/tools/snapshot-delta.d.ts +3 -0
- package/dist/tools/snapshot-delta.js +67 -6
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +85 -19
- package/dist/tools/tools.js +45 -4
- package/dist/tools/unadvertised-help.js +9 -6
- package/docs/README.md +1 -2
- package/docs/actions.mdx +41 -8
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +6 -0
- package/docs/benchmarks.md +60 -12
- package/docs/best-practices.mdx +11 -6
- package/docs/capabilities.mdx +201 -54
- package/docs/cli/affected.mdx +6 -4
- package/docs/cli/capsules.mdx +9 -8
- package/docs/cli/cloud.mdx +30 -4
- package/docs/cli/doctor.mdx +41 -8
- package/docs/cli/drive.mdx +34 -15
- package/docs/cli/gate.mdx +6 -6
- package/docs/cli/help.mdx +8 -6
- package/docs/cli/hunt.mdx +18 -2
- package/docs/cli/identify.mdx +16 -2
- package/docs/cli/init.mdx +35 -8
- package/docs/cli/kill.mdx +5 -5
- package/docs/cli/license.mdx +1 -1
- package/docs/cli/open.mdx +6 -0
- package/docs/cli/restart.mdx +6 -6
- package/docs/cli/serve.mdx +10 -3
- package/docs/cli/status.mdx +29 -5
- package/docs/cli/stop.mdx +8 -0
- package/docs/cli/verify.mdx +20 -1
- package/docs/cli/version.mdx +1 -1
- package/docs/cli/watch.mdx +9 -2
- package/docs/cli.mdx +44 -22
- package/docs/debugging.md +3 -1
- package/docs/deploy-checks.md +30 -1
- package/docs/desktop.mdx +276 -80
- package/docs/docs.json +13 -12
- package/docs/enterprise.md +2 -0
- package/docs/faq.mdx +49 -35
- package/docs/fixtures.md +2 -0
- package/docs/flows.md +3 -1
- package/docs/for-agents.md +3 -3
- package/docs/frameworks.mdx +46 -7
- package/docs/gate-plan.md +4 -2
- package/docs/gates.md +7 -5
- package/docs/getting-started.md +53 -12
- package/docs/human-control.md +2 -0
- package/docs/index.mdx +43 -1
- package/docs/install-agentic.mdx +13 -4
- package/docs/install-manual.mdx +6 -4
- package/docs/instrumentation.mdx +7 -1
- package/docs/integration-patterns.md +3 -1
- package/docs/local-registry.md +4 -2
- package/docs/multi-agent-testing.md +3 -1
- package/docs/packages/babel-plugin.mdx +15 -6
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/core.mdx +26 -16
- package/docs/packages/electron.mdx +5 -7
- package/docs/packages/eslint-plugin.mdx +5 -3
- package/docs/packages/next.mdx +3 -5
- package/docs/packages/react.mdx +3 -3
- package/docs/packages/server.mdx +2 -2
- package/docs/packages/tauri.mdx +6 -6
- package/docs/packages/test.mdx +18 -7
- package/docs/packages/vite-plugin.mdx +4 -6
- package/docs/packages.mdx +14 -13
- package/docs/platform-integration.md +2 -0
- package/docs/predicates.mdx +76 -7
- package/docs/quickstart.mdx +51 -5
- package/docs/recipes.mdx +62 -22
- package/docs/skill-file.mdx +3 -1
- package/docs/state-management.mdx +2 -0
- package/docs/system-map.md +3 -1
- package/docs/telemetry-contract.md +16 -5
- package/docs/telemetry-events.mdx +63 -18
- package/docs/telemetry.md +8 -5
- package/docs/testing.md +35 -1
- package/docs/token-efficiency.md +8 -2
- package/docs/tools-act-and-wait.mdx +64 -14
- package/docs/tools-act-sequence.mdx +34 -17
- package/docs/tools-act.mdx +24 -11
- package/docs/tools-assert.mdx +69 -10
- package/docs/tools-console.mdx +11 -7
- package/docs/tools-inspect.mdx +11 -8
- package/docs/tools-navigate.mdx +41 -5
- package/docs/tools-network.mdx +42 -9
- package/docs/tools-observe.mdx +66 -10
- package/docs/tools-overview.mdx +26 -23
- package/docs/tools-query.mdx +52 -8
- package/docs/tools-session-and-feedback.mdx +30 -9
- package/docs/tools-sessions.mdx +39 -10
- package/docs/tools-snapshot.mdx +57 -3
- package/docs/tools-state.mdx +24 -1
- package/docs/tools-tools-and-run.mdx +40 -9
- package/docs/tools-wait-for.mdx +34 -15
- package/docs/troubleshooting.mdx +80 -10
- package/docs/usage.md +43 -21
- package/docs/vs-chrome-devtools-mcp.mdx +41 -0
- package/docs/vs-playwright-mcp.mdx +42 -2
- package/docs/vs-screenshots.mdx +42 -1
- package/docs/what-is-reticle.mdx +1 -1
- package/docs/why-reticle.mdx +3 -1
- package/package.json +5 -5
- package/docs/desktop-apps.md +0 -265
|
@@ -4,6 +4,8 @@ description: 'Adopt Reticle on your own codebase, or embed it inside an AI app-b
|
|
|
4
4
|
icon: plug
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
Two ways in. **A team** runs `npx @reticlehq/server init` once and points its coding agent at the MCP server. **A platform** adds the dev-only SDK to its generated-app template, runs `npx @reticlehq/server serve --http --drive <preview-url>` beside each preview, and `POST`s to `/verify` after every generate or edit, gating the ship on `run.verdict.status === 'pass'` and feeding `run.repair.failurePackets[]` back to a fixer agent.
|
|
8
|
+
|
|
7
9
|
> The one guide for adopting Reticle, both for a team using a coding agent on its own app and for an AI app-builder platform (Lovable / Emergent / Bolt) embedding Reticle in its generation pipeline. Reticle reads the program from _inside_ a running app and returns a **verdict with evidence** ("did it actually work?"), not a screenshot. Enterprise/premium access lives in [`enterprise.md`](./enterprise.md).
|
|
8
10
|
|
|
9
11
|
## The loop
|
package/docs/predicates.mdx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Predicate reference: the grammar of green
|
|
2
|
+
title: 'Predicate reference: the grammar of green'
|
|
3
3
|
sidebarTitle: Predicates
|
|
4
4
|
description: The grammar for declaring what should be true. Every kind, every field, and which ones actually prove something.
|
|
5
5
|
icon: filter
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
A predicate declares what should be true
|
|
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
|
+
|
|
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_wait_for`](/tools-wait-for).
|
|
9
11
|
|
|
10
12
|
Every predicate is `{ kind, ...fields }`.
|
|
11
13
|
|
|
@@ -31,6 +33,8 @@ Take the advice. `element` and `text` are convenient and weak; `signal`, `state`
|
|
|
31
33
|
| `route` | medium | Navigation happened |
|
|
32
34
|
| `console` | medium | Something logged, or nothing did |
|
|
33
35
|
| `element` / `text` | weak | Something rendered. A mock renders too |
|
|
36
|
+
| `animation` | weak | An animation ran or finished |
|
|
37
|
+
| `settled` | timing | The page went quiet. A precondition, not a proof |
|
|
34
38
|
|
|
35
39
|
## The leaves
|
|
36
40
|
|
|
@@ -54,7 +58,7 @@ Add `"absent": true` to assert something is **gone**. The predicate for a remova
|
|
|
54
58
|
{ "kind": "text", "contains": "Saved successfully", "visible": true }
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
`contains` is the field the published schema declares. `value` and `text` are accepted as aliases and rewritten to it. `absent: true` asserts the text is not present, and `visible: true` requires it to be on screen rather than merely in the DOM.
|
|
58
62
|
|
|
59
63
|
### net
|
|
60
64
|
|
|
@@ -76,7 +80,19 @@ The one that catches the expensive bug:
|
|
|
76
80
|
{ "kind": "route", "pathname": "/success" }
|
|
77
81
|
```
|
|
78
82
|
|
|
79
|
-
`contains`
|
|
83
|
+
`pathname` matches the path exactly. `contains` matches anywhere in the whole route, path, query and fragment together. `path` is accepted as an alias for `pathname`, and `url` and `urlContains` as aliases for `contains`, but the canonical spellings are the ones above.
|
|
84
|
+
|
|
85
|
+
A passing route verdict quotes the transition itself:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
"evidence": {
|
|
89
|
+
"from": "http://localhost:4310/",
|
|
90
|
+
"to": "http://localhost:4310/deployments",
|
|
91
|
+
"pathname": "/deployments",
|
|
92
|
+
"search": "",
|
|
93
|
+
"hash": ""
|
|
94
|
+
}
|
|
95
|
+
```
|
|
80
96
|
|
|
81
97
|
<Warning>
|
|
82
98
|
`route` asserts the route **changed**. Asserting the route you are already on always fails, and
|
|
@@ -115,7 +131,35 @@ Walks a dot-path, with numeric array indices. `equals` takes a literal, or an op
|
|
|
115
131
|
| `$contains` | substring or array membership |
|
|
116
132
|
| `$length` | collection size |
|
|
117
133
|
|
|
118
|
-
Omit `equals` entirely to assert presence.
|
|
134
|
+
Omit `equals` entirely to assert presence. A real `$length` check against this fixture:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{ "kind": "state", "store": "app", "path": "deployments", "equals": { "$length": 40 } }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"verified": "yes",
|
|
143
|
+
"because": "assertion held at state grade over a clean capture with no channel disagreeing",
|
|
144
|
+
"pass": true,
|
|
145
|
+
"evidence": { "store": "app", "path": "deployments", "value": ["{…11 keys}", "…"] }
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Note `grade: state` in the `because` line, and that the evidence collapses each row to a size marker rather than returning forty objects.
|
|
150
|
+
|
|
151
|
+
A miss quotes the real value back at you:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"pass": false,
|
|
156
|
+
"failureReason": "state 'deployments.0.status' is \"queued\", expected \"live\"",
|
|
157
|
+
"observed": "deployments.0.status = \"queued\"",
|
|
158
|
+
"expected": "deployments.0.status = \"live\"",
|
|
159
|
+
"assertion": "state.equals",
|
|
160
|
+
"evidence": { "store": "app", "path": "deployments.0.status", "value": "queued" }
|
|
161
|
+
}
|
|
162
|
+
```
|
|
119
163
|
|
|
120
164
|
`store` is optional. Leave it out and the path picks the store: if exactly one registered store exposes it, that one is read. Registering several is normal, so this is the common case rather than a shortcut. Name a store when two of them genuinely expose the same path, which is the only situation Reticle cannot resolve on its own. If no store exposes it, that is a failure rather than a question, and the miss names the stores it searched.
|
|
121
165
|
|
|
@@ -127,6 +171,16 @@ This is the predicate that catches a UI-versus-store desync. A deploy that only
|
|
|
127
171
|
{ "kind": "animation", "name": "dialog-in", "completed": true }
|
|
128
172
|
```
|
|
129
173
|
|
|
174
|
+
`target` narrows it to one element. `completed: false` matches an animation that is still running.
|
|
175
|
+
|
|
176
|
+
### settled
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{ "kind": "settled", "quietMs": 500 }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
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.
|
|
183
|
+
|
|
130
184
|
## Combinators
|
|
131
185
|
|
|
132
186
|
```json
|
|
@@ -149,13 +203,13 @@ This is the predicate that catches a UI-versus-store desync. A deploy that only
|
|
|
149
203
|
A combinator needs its own `kind`, and the children go in `predicates`. A bare
|
|
150
204
|
`{ "allOf": [ … ] }` does not parse, and neither does `{ "kind": "allOf", "allOf": [ … ] }`.
|
|
151
205
|
|
|
152
|
-
Reticle refuses the call rather than running half of it: _"Nothing ran, the predicate was not evaluated, so no verdict was produced."_ Better to be told than to get a green from an assertion that never executed.
|
|
206
|
+
Reticle refuses the call rather than running half of it. The real refusal names the field, the fix and the consequence: _"predicates: Required; unknown field allOf. Nothing ran, the predicate was not evaluated, so no verdict was produced. allOf accepts: predicates."_ Better to be told than to get a green from an assertion that never executed.
|
|
153
207
|
|
|
154
208
|
</Warning>
|
|
155
209
|
|
|
156
210
|
`allOf` is the workhorse. "The signal fired **and** exactly one request went out **and** the console stayed clean" is a genuinely strong check, and it is one call.
|
|
157
211
|
|
|
158
|
-
Each child reports its own evidence:
|
|
212
|
+
Each child reports its own evidence, in order. That is a real verdict for exactly the `allOf` above:
|
|
159
213
|
|
|
160
214
|
```json
|
|
161
215
|
"evidence": [
|
|
@@ -165,6 +219,21 @@ Each child reports its own evidence:
|
|
|
165
219
|
]
|
|
166
220
|
```
|
|
167
221
|
|
|
222
|
+
On a failure each child reports separately too, so you can see which one broke:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
"evidence": [
|
|
226
|
+
{
|
|
227
|
+
"pass": false,
|
|
228
|
+
"expected": "an element matching {\"role\":\"dialog\"} in state 'visible'",
|
|
229
|
+
"observed": "no matching element on the page",
|
|
230
|
+
"assertion": "element.present",
|
|
231
|
+
"evidence": { "presentTestids": ["deploy-table", "filter-search", "env-filter"] }
|
|
232
|
+
},
|
|
233
|
+
{ "pass": true, "evidence": { "absent": true } }
|
|
234
|
+
]
|
|
235
|
+
```
|
|
236
|
+
|
|
168
237
|
## Timing and scoping
|
|
169
238
|
|
|
170
239
|
| Field | Where | What it does |
|
package/docs/quickstart.mdx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Quickstart: proof in five minutes
|
|
2
|
+
title: 'Quickstart: proof in five minutes'
|
|
3
3
|
sidebarTitle: Quickstart
|
|
4
4
|
description: Install Reticle, connect your coding agent, and get a real verdict with evidence in about five minutes.
|
|
5
5
|
icon: rocket
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
**Run `npx @reticlehq/server init` in your project, restart your dev server and your agent, then ask your agent to drive a flow.** That is the whole install. Reticle is a verification layer for AI coding agents: it embeds a dev-only SDK in your running web app so an agent can prove a change works instead of guessing.
|
|
9
|
+
|
|
8
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.
|
|
9
11
|
|
|
10
12
|
<Note>
|
|
@@ -81,10 +83,10 @@ npx @reticlehq/server open
|
|
|
81
83
|
This reuses the tab you already have open, or opens one if you don't. The SDK connects to the local daemon over a WebSocket, and your agent can now see the app.
|
|
82
84
|
|
|
83
85
|
<Warning>
|
|
84
|
-
If the browser console says
|
|
85
|
-
about the port. Set it explicitly:
|
|
86
|
-
`reticle.connect({url})`. This is
|
|
87
|
-
|
|
86
|
+
If the browser console says `[Reticle] this page could not open a websocket to …`, your app and
|
|
87
|
+
the daemon disagree about the port. Set it explicitly:
|
|
88
|
+
`VITE_RETICLE_WS_URL=ws://localhost:4400/reticle` for Vite, or `reticle.connect({url})`. This is
|
|
89
|
+
the single most common setup problem, and the console message names the exact address it tried.
|
|
88
90
|
</Warning>
|
|
89
91
|
|
|
90
92
|
## Step 4. Look at the page
|
|
@@ -193,6 +195,50 @@ Read what that actually proves:
|
|
|
193
195
|
|
|
194
196
|
That third state is the one that matters. Most tools have two outcomes and quietly file "I couldn't tell" under "fine". A verification layer that cannot admit uncertainty is just a very confident random number generator.
|
|
195
197
|
|
|
198
|
+
## Frequently asked questions
|
|
199
|
+
|
|
200
|
+
<AccordionGroup>
|
|
201
|
+
<Accordion title="How long does it actually take to install Reticle?">
|
|
202
|
+
One command and two restarts. `npx @reticlehq/server init` registers the MCP server, installs
|
|
203
|
+
the SDK, wires your build config and writes the agent rules. Then restart your dev server, so it
|
|
204
|
+
picks up the new build plugin, and restart your agent, so it re-reads its MCP server list. The
|
|
205
|
+
MCP registration is global, so every later project starts with the tools already there.
|
|
206
|
+
</Accordion>
|
|
207
|
+
|
|
208
|
+
<Accordion title="Do I need to write tests or learn a query syntax?">
|
|
209
|
+
No. You talk to your agent in plain language and it calls the tools. There is no spec file, no
|
|
210
|
+
selector language and no test runner involved in the loop on this page. If you later want the same
|
|
211
|
+
flow to block a bad merge, you can save it and run it in CI, but that is a separate step.
|
|
212
|
+
</Accordion>
|
|
213
|
+
|
|
214
|
+
<Accordion title="Will any of this end up in my production build?">
|
|
215
|
+
No. The SDK is dev-only. The Vite plugin declares `apply: 'serve'`, so it is dropped from `vite
|
|
216
|
+
build` entirely, and `connect()` self-disables when the build reports `NODE_ENV=production`. The
|
|
217
|
+
recommended wiring also guards the call behind `import.meta.env.DEV`, so there are two independent
|
|
218
|
+
locks.
|
|
219
|
+
</Accordion>
|
|
220
|
+
|
|
221
|
+
<Accordion title="Does my code or my app's data leave my machine?">
|
|
222
|
+
No. The daemon runs on your machine and the SDK runs in your browser. No application code, DOM,
|
|
223
|
+
network traffic or console output leaves it. Anonymous product analytics are sent unless you opt
|
|
224
|
+
out with `npx @reticlehq/server telemetry disable`, and they never include your code or your app's
|
|
225
|
+
data.
|
|
226
|
+
</Accordion>
|
|
227
|
+
|
|
228
|
+
<Accordion title="What if my framework is not React or Next.js?">
|
|
229
|
+
The core is framework-neutral, so DOM, network, console, routing and source mapping work anywhere
|
|
230
|
+
JavaScript runs. What varies is how `connect()` gets into your app and whether you get component
|
|
231
|
+
identity on top. See [Frameworks](/frameworks) for the exact wiring per stack and an honest table
|
|
232
|
+
of what is gated in CI versus wired but unverified.
|
|
233
|
+
</Accordion>
|
|
234
|
+
|
|
235
|
+
<Accordion title="Nothing connected. What is the first thing to check?">
|
|
236
|
+
Run `npx @reticlehq/server doctor`. If it shows the daemon running but `sessions` at zero, your
|
|
237
|
+
app is not reaching the bridge, and the usual cause is a port mismatch. The bridge port is not
|
|
238
|
+
your dev-server port. See [Troubleshooting](/troubleshooting).
|
|
239
|
+
</Accordion>
|
|
240
|
+
</AccordionGroup>
|
|
241
|
+
|
|
196
242
|
## Where to go next
|
|
197
243
|
|
|
198
244
|
<CardGroup cols={2}>
|
package/docs/recipes.mdx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Recipes: copy, paste, verify
|
|
2
|
+
title: 'Recipes: copy, paste, verify'
|
|
3
3
|
sidebarTitle: Recipes
|
|
4
4
|
description: Worked examples for the situations you are actually in, with the real response each one returned.
|
|
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.
|
|
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_query`](/tools-query) before copying a call.
|
|
9
9
|
|
|
10
10
|
## "Did the login actually work?"
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ One call, three conditions. The signal fired, exactly one request went out, and
|
|
|
13
13
|
|
|
14
14
|
```json
|
|
15
15
|
{
|
|
16
|
-
"ref": "
|
|
16
|
+
"ref": "e605",
|
|
17
17
|
"action": "click",
|
|
18
18
|
"until": {
|
|
19
19
|
"kind": "allOf",
|
|
@@ -51,7 +51,7 @@ Each child predicate reports its own evidence, in order. `count: 1` is the part
|
|
|
51
51
|
## "Did navigating actually change the view?"
|
|
52
52
|
|
|
53
53
|
```json
|
|
54
|
-
{ "ref": "
|
|
54
|
+
{ "ref": "e618", "action": "click", "until": { "kind": "signal", "name": "nav:changed" } }
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
```json
|
|
@@ -62,9 +62,11 @@ Each child predicate reports its own evidence, in order. `count: 1` is the part
|
|
|
62
62
|
"evidence": { "name": "nav:changed", "data": { "view": "deployments" } }
|
|
63
63
|
},
|
|
64
64
|
"summary": {
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
65
|
+
"statePathsChanged": ["app"],
|
|
66
|
+
"stateDiffs": [{ "path": "view", "from": "overview", "to": "deployments", "atMs": 49084 }],
|
|
67
|
+
"signals": ["nav:changed"]
|
|
68
|
+
},
|
|
69
|
+
"honesty": { "grade": "signal", "coverage": { "pct": 100, "partial": false } }
|
|
68
70
|
}
|
|
69
71
|
```
|
|
70
72
|
|
|
@@ -76,8 +78,9 @@ Here is a real failure, kept because it teaches more than another pass.
|
|
|
76
78
|
|
|
77
79
|
```json
|
|
78
80
|
{
|
|
79
|
-
"ref": "
|
|
81
|
+
"ref": "e623",
|
|
80
82
|
"action": "click",
|
|
83
|
+
"args": { "confirmDangerous": true },
|
|
81
84
|
"until": {
|
|
82
85
|
"kind": "allOf",
|
|
83
86
|
"predicates": [
|
|
@@ -98,19 +101,23 @@ Here is a real failure, kept because it teaches more than another pass.
|
|
|
98
101
|
"pass": false,
|
|
99
102
|
"expected": "an element matching {\"role\":\"dialog\"} in state 'visible'",
|
|
100
103
|
"observed": "no matching element on the page",
|
|
101
|
-
"
|
|
104
|
+
"assertion": "element.present",
|
|
105
|
+
"evidence": {
|
|
106
|
+
"presentTestids": ["deploy-table", "filter-search", "env-filter", "sign-out", "…"]
|
|
107
|
+
}
|
|
102
108
|
},
|
|
103
109
|
{ "pass": true, "evidence": { "absent": true } }
|
|
104
110
|
]
|
|
105
111
|
},
|
|
106
112
|
"summary": {
|
|
107
|
-
"stateDiffs": [{ "path": "newDeployOpen", "from": false, "to": true }],
|
|
113
|
+
"stateDiffs": [{ "path": "newDeployOpen", "from": false, "to": true, "atMs": 60862 }],
|
|
108
114
|
"signals": ["modal:opened"]
|
|
109
|
-
}
|
|
115
|
+
},
|
|
116
|
+
"capsule": { "blastRadius": ["state app", "signal modal:opened"], "firstDivergence": null }
|
|
110
117
|
}
|
|
111
118
|
```
|
|
112
119
|
|
|
113
|
-
Read the `summary`. The modal **did** open. `newDeployOpen` went `false
|
|
120
|
+
Read the `summary`. The modal **did** open. `newDeployOpen` went `false` to `true` and the app fired `modal:opened`. `capsule.blastRadius` says the same thing as a list: the click moved the `app` store and fired `modal:opened`, and nothing else.
|
|
114
121
|
|
|
115
122
|
The assertion was wrong, not the app. That modal is a `div` with no `role="dialog"`, so a role query could never match it. (Which is also an accessibility bug worth fixing, found for free.)
|
|
116
123
|
|
|
@@ -135,14 +142,16 @@ An empty `reticle_query` result tells you what _is_ there:
|
|
|
135
142
|
"count": 0,
|
|
136
143
|
"elements": [],
|
|
137
144
|
"hint": {
|
|
138
|
-
"route": "/",
|
|
145
|
+
"route": "/hostile",
|
|
139
146
|
"knownEmptyState": true,
|
|
140
|
-
"presentTestids": ["nav-overview", "nav-deployments", "
|
|
141
|
-
|
|
147
|
+
"presentTestids": ["nav-overview", "nav-deployments", "hostile-view", "hostile-ticker", "…"],
|
|
148
|
+
"presentRegions": [{ "role": "list", "childCount": 0, "name": "hostile-feed", "sample": [] }]
|
|
149
|
+
},
|
|
150
|
+
"cost": { "bytes": 402, "tokens": 101 }
|
|
142
151
|
}
|
|
143
152
|
```
|
|
144
153
|
|
|
145
|
-
|
|
154
|
+
The testid existed, on another route. `hint.route` says where you actually are and `presentTestids` says what is here instead, for 101 tokens and no snapshot. `presentRegions` is the other half: a list with `childCount: 0` is an empty state, which is a different bug from a list that failed to render at all.
|
|
146
155
|
|
|
147
156
|
## "Is this button destructive?"
|
|
148
157
|
|
|
@@ -158,7 +167,7 @@ Reticle refuses controls that look destructive, before acting:
|
|
|
158
167
|
Confirm it explicitly when you mean it:
|
|
159
168
|
|
|
160
169
|
```json
|
|
161
|
-
{ "ref": "
|
|
170
|
+
{ "ref": "e623", "action": "click", "args": { "confirmDangerous": true }, "until": { … } }
|
|
162
171
|
```
|
|
163
172
|
|
|
164
173
|
<Note>
|
|
@@ -215,10 +224,20 @@ Then drive the flow and assert the error UI appears. Pass `clear: true` to turn
|
|
|
215
224
|
|
|
216
225
|
<Warning>
|
|
217
226
|
This one needs a **Reticle-driven browser**, because mocks are applied through CDP and the always-on
|
|
218
|
-
SDK cannot do that. A pooled lease is **not** enough.
|
|
219
|
-
|
|
227
|
+
SDK cannot do that. A pooled lease is **not** enough. Re-verified on 2026-08-16: calling it against a
|
|
228
|
+
tab from `reticle_lease` returns
|
|
220
229
|
|
|
221
|
-
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"applied": false,
|
|
233
|
+
"count": 0,
|
|
234
|
+
"ok": false,
|
|
235
|
+
"reason": "no-cdp-provider",
|
|
236
|
+
"recommendation": "this needs a Reticle-driven browser (it is applied through CDP, which the always-on SDK cannot do), start with `reticle drive <url>` or set RETICLE_CDP_URL"
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Under a driven browser the same call answers `{ "applied": true, "count": 1 }`. The same rule applies to `reticle_screenshot`, `reticle_visual_diff` and `reticle_viewport`, which report `no-visual-provider` instead.
|
|
222
241
|
|
|
223
242
|
</Warning>
|
|
224
243
|
|
|
@@ -227,10 +246,31 @@ Your route is `RETICLE_CDP_URL` pointed at a Chrome started with `--remote-debug
|
|
|
227
246
|
Record it once, replay it forever:
|
|
228
247
|
|
|
229
248
|
```json
|
|
230
|
-
{ "tool": "reticle_flow_verify" }
|
|
249
|
+
{ "tool": "reticle_flow_verify", "args": { "names": ["docs-check"] } }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Omit `names` to replay every saved flow. Either way you get one consolidated verdict, and only the failures carry detail:
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"status": "fail",
|
|
257
|
+
"total": 1,
|
|
258
|
+
"passed": 0,
|
|
259
|
+
"failed": 1,
|
|
260
|
+
"summary": "0/1 flows pass, 1 need attention: docs-check",
|
|
261
|
+
"failures": [
|
|
262
|
+
{
|
|
263
|
+
"flow": "docs-check",
|
|
264
|
+
"verdict": "drift",
|
|
265
|
+
"whatChanged": "testid \"login-submit\" not found",
|
|
266
|
+
"whereInSource": "src/components/Login.tsx:81",
|
|
267
|
+
"nextAction": "rebind the anchor to \"sign-out\" (closest survivor), or update the flow if the change was intended."
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
}
|
|
231
271
|
```
|
|
232
272
|
|
|
233
|
-
|
|
273
|
+
It also returns `flaky`, the flows it has seen both pass and fail on unchanged code. Treat that list as a bug in the flow, not in the app.
|
|
234
274
|
|
|
235
275
|
<CardGroup cols={2}>
|
|
236
276
|
<Card title="The predicate grammar" icon="filter" href="/predicates">
|
package/docs/skill-file.mdx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: The SKILL file: paste and go
|
|
2
|
+
title: 'The SKILL file: paste and go'
|
|
3
3
|
sidebarTitle: SKILL file
|
|
4
4
|
description: One URL you paste into any coding agent to teach it Reticle. How it works, what it contains, and when you need it at all.
|
|
5
5
|
icon: graduation-cap
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
Paste `https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md` into any coding agent and say "read this and set up Reticle". The document installs Reticle, wires it into your app, and then drives a proper verification loop, choosing setup mode or test mode itself based on whether `.reticle.json` already exists. You often do not need it: `npx @reticlehq/server init` configures Claude Code and Cursor directly.
|
|
9
|
+
|
|
8
10
|
`SKILL.md` is a single document you hand to a coding agent. It teaches the agent to install Reticle, wire it into your app, and then drive a verification loop properly. Without you explaining any of it.
|
|
9
11
|
|
|
10
12
|
```
|
|
@@ -4,6 +4,8 @@ description: Register any store so Reticle can read what your app believes. Zust
|
|
|
4
4
|
icon: database
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
**Reticle reads any store that has `getState()` and `subscribe()`, which covers Zustand and Redux with no adapter at all. TanStack Query, Pinia, Svelte stores, Jotai, Valtio, MobX, XState and Recoil each have a one-line adapter, and `pushStore` covers anything else.** Register one with `registerStore('app', myStore)` in your dev-only `src/reticle-dev.ts`.
|
|
8
|
+
|
|
7
9
|
Reading application state is what separates a verdict from a screenshot. A `200` proves the server was reachable. A rendered row proves React ran. Only state proves your app accepted the change.
|
|
8
10
|
|
|
9
11
|
Reticle needs one thing from your store: a way to read it, and a way to know when it changed.
|
package/docs/system-map.md
CHANGED
|
@@ -4,6 +4,8 @@ description: 'How a tool call travels from a coding agent to your app and back,
|
|
|
4
4
|
icon: map
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
A tool call crosses five processes and four hops: coding agent → `reticle mcp` proxy (stdio) → daemon on `:4400` (HTTP POST out, SSE back) → browser SDK (WebSocket) → your app. Each hop has its own failure vocabulary and its own way of lying, and the ones marked **silent** below fail with no error reaching either the agent or you.
|
|
8
|
+
|
|
7
9
|
> How a tool call gets from a coding agent to your app and back, what each hop can do wrong, and which of those failures are **silent**. Written for anyone touching the transport, the daemon, the bridge, or a gate, and for contributors trying to work out where their change lives.
|
|
8
10
|
>
|
|
9
11
|
> Companion pages: [`harness-rules.md`](../apps/e2e/harness-rules.md) (what a gate must do about all this) and [`telemetry-contract.md`](./telemetry-contract.md) (the rules for anything that emits).
|
|
@@ -56,7 +58,7 @@ Three different counts, all measured, and the difference between them matters:
|
|
|
56
58
|
| --- | --- |
|
|
57
59
|
| **68** | name constants in `ReticleTool` |
|
|
58
60
|
| **48** | tools advertised under `RETICLE_ADVERTISE_ALL_TOOLS=1`, i.e. what `tool-surface-sweep-test` drives (51 calls) |
|
|
59
|
-
| **
|
|
61
|
+
| **19** | advertised by default; the rest are reached through `reticle_run` |
|
|
60
62
|
|
|
61
63
|
68 → 48 is **family folding**: `reticle_baseline`, `reticle_session`, `reticle_record`, `reticle_flow` and `reticle_lease` each absorb their members behind an `action` parameter. So "every tool is callable" is asserted over 48 surfaces, not 68 behaviours; a family member reachable only through an `action` value the sweep never passes is not covered by it. Worth closing when the per-tool budget lands (see [`gate-plan.md`](./gate-plan.md), Phase 4).
|
|
62
64
|
|
|
@@ -103,6 +103,7 @@ The single exception is `daemon_stopped`, which is **awaited**, because the proc
|
|
|
103
103
|
| `identified` | `reticle identify` | joins anonymous machine ids to a person who volunteered one |
|
|
104
104
|
| `mcp_client_connected` | an MCP client attached | how many sessions are agent-driven at all |
|
|
105
105
|
| `app_instrumented` | the first app carrying the SDK reached this daemon | **the funnel step everything turns on**; see below |
|
|
106
|
+
| `instrumentation_stalled` | the daemon waited and no app ever arrived | the same funnel step, seen from the failure side; see below |
|
|
106
107
|
| `mcp_connection_lost` | the proxy lost its daemon | **the transport-stability metric.** The disconnect that makes a user reopen `/mcp` is invisible without it |
|
|
107
108
|
| `init_completed` | `reticle init` finished | does install actually work, outside the fixtures gate |
|
|
108
109
|
| `bug_found` | a defect was detected in the app under test | the value delivered, as opposed to the work done |
|
|
@@ -116,7 +117,17 @@ Nothing measured the second. `daemon_started` and `mcp_client_connected` describ
|
|
|
116
117
|
|
|
117
118
|
`app_instrumented` fires **once per daemon run**, on the first session-ready only, so `daemon_started` → `app_instrumented` is a rate rather than an inference and a reloading page cannot inflate it. It carries `initialized` (had `init` run here), `agentAttached` (was an agent already waiting), and `msToFirstApp` (how long the daemon sat with nothing wired). It deliberately carries no stack and no framework: `project_profiled` already reports both for the same run, and the two join on `sessionId`.
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
### The counterpart: `instrumentation_stalled`
|
|
121
|
+
|
|
122
|
+
`app_instrumented` fires on success and only on success, so the larger population, runs where no app ever arrives, exists in the data as an **absence**. An absence is the one thing telemetry cannot interpret: it cannot separate "nothing was ever wired" from "the process was killed before it could say so" from "we never sent the event". Since the funnel breaks at this step and nowhere else, this is the one silence worth converting into a signal.
|
|
123
|
+
|
|
124
|
+
`instrumentation_stalled` fires **once per daemon run**, only after the daemon has been up past a generous threshold, and **never once an app has connected**. A run that starts slowly and then works reports nothing at all. It carries the same three facts as its success counterpart so the two are directly comparable, with `msWaited` in place of `msToFirstApp`.
|
|
125
|
+
|
|
126
|
+
Read it against `agentAttached`, which is what makes it actionable rather than merely sad. A stalled daemon with no agent attached is somebody who installed and walked away, and is not a defect. A stalled daemon **with** an agent attached is the failure the product actually has: the tools are loaded, something is asking for them, and there is nothing on the other end to drive.
|
|
127
|
+
|
|
128
|
+
It deliberately carries no diagnosis. Whether a dev server is listening is a live probe, and running one on a timer to enrich a metric would be collecting for the metric's sake. The diagnosis belongs where a human or an agent asks for it.
|
|
129
|
+
|
|
130
|
+
What neither event can see is **why** an app never connected. Every cause for that is page-side (the non-localhost gate, a port mismatch, a stale build, a dev server never restarted), where the daemon has no visibility. That needs the SDK to report its own refusals, and it is the next thing to build.
|
|
120
131
|
|
|
121
132
|
## Sessions: `daemon_stopped` vs `session_progress`
|
|
122
133
|
|
|
@@ -124,9 +135,9 @@ Count sessions with **`daemon_stopped`** (`final: true`). It fires once, at a cl
|
|
|
124
135
|
|
|
125
136
|
A running daemon rolls its window up every 5 minutes as **`session_progress`** (`final: false`), same payload shape. Sum work across both; count sessions with neither summed nor doubled.
|
|
126
137
|
|
|
127
|
-
This split exists because the flush used to be emitted AS `daemon_stopped`, an event named for an exit, fired while the process was alive.
|
|
138
|
+
This split exists because the flush used to be emitted AS `daemon_stopped`, an event named for an exit, fired while the process was alive. Anything counting sessions over the raw event therefore over-stated, and the two populations it merged are opposites: a flush comes from a daemon that has served tool calls, while a clean exit comes from one that has not, because a daemon that has served a tool never idle-exits and so never reaches a clean shutdown. A funnel over the raw event describes active sessions at one end and abandoned ones at the other.
|
|
128
139
|
|
|
129
|
-
The flush interval is also the **bound on what is lost**: nothing calls shutdown when a working daemon is finally killed, so its last partial window dies with it.
|
|
140
|
+
The flush interval is also the **bound on what is lost**: nothing calls shutdown when a working daemon is finally killed, so its last partial window dies with it. The old 30-minute interval was long enough to lose most of a typical session that way. Only non-empty windows emit, so a short interval costs nothing on the daemons that never serve a tool.
|
|
130
141
|
|
|
131
142
|
## The session summary's newer fields
|
|
132
143
|
|
|
@@ -140,7 +151,7 @@ Four counters and one flag were added because the data could not answer question
|
|
|
140
151
|
| `endedWithVerdict` | session summary (final only) | did this session ever produce a verdict. The headline metric, and previously the only thing in the payload that had to be COMPUTED, from lifetime counters sitting next to windowed ones, which is a subtraction that gets read wrong. Sent as `false` rather than omitted: a session that drove an app and never asked whether it worked is the finding. |
|
|
141
152
|
| `verification.browser` | `verification_completed` | `headless` \| `headed` \| `attached`: who DROVE the browser. `attached` (Reticle launched nothing, the SDK connected from a browser somebody else opened) is the common case in production, so on its own this is mostly "somebody's own browser". |
|
|
142
153
|
| `verification.brand` | `verification_completed` | WHICH browser it was: `chrome` \| `edge` \| `arc` \| `dia` \| `brave` \| `opera` \| `firefox` \| `safari` \| `other`, the closed `BrowserBrand` list in core. The axis `engine` cannot answer, since Chrome, Edge, Arc, Dia and Brave are all `blink`. The SDK reads `navigator.userAgentData.brands` (and the UA string on Firefox/Safari, which expose no `userAgentData`) and normalises IN THE PAGE: a raw brand or UA string is unbounded and fingerprintable and never leaves. Anything unrecognised is `other`. **Omitted rather than `"unknown"`** when the page did not say. A desktop webview has no brand and an older SDK does not report one, and a guess is indistinguishable from a measurement on a dashboard. |
|
|
143
|
-
| `verification.reason` | `verification_completed` | WHICH clause of `decideVerified` produced the verdict, from core's closed `VerifiedReason`: `inconclusive` \| `observation_lost` \| `assertion_failed` \| `contradicted` \| `already_true` \| `unclean_capture` \| `vacuous_grade` \| `outcome_pending` \| `outcome_unread` \| `unsettled` \| `proved`. See below. |
|
|
154
|
+
| `verification.reason` | `verification_completed` | WHICH clause of `decideVerified` produced the verdict, from core's closed `VerifiedReason`: `inconclusive` \| `observation_lost` \| `assertion_failed` \| `contradicted` \| `already_true` \| `unclean_capture` \| `vacuous_grade` \| `outcome_pending` \| `outcome_unread` \| `unsettled` (the page never went idle and no consequence was declared) \| `evidence_incomplete` (the assertion held, but a channel's outcome had not arrived when the window closed) \| `proved`. See below. |
|
|
144
155
|
| `verification.uncleanLoss` | `verification_completed` | WHAT was lost when `reason` is `unclean_capture`, from core's closed `CaptureLoss`: `buffer_loss` (our server ring buffer evicted evidence from the window) \| `transport_gap` (our browser queue overflowed) \| `blind_spot` (a boundary in the page, such as a cross-origin frame or a closed shadow root) \| `other`. Three owners, three fixes, and one bar on a dashboard until this existed. ONE value, not a list: a multi-value property is not something a breakdown can group by, so the first is sent, ours before the page's. **Absent whenever the capture was clean**, so its presence is itself the signal. Reported as `other` rather than omitted when the block says dirty and names nothing: a gap there would read as "no unclean verdicts happened". |
|
|
145
156
|
| `bug.attribution` | `bug_found` | `app` \| `request` \| `reticle`: whose fault the defect was. **Absent means unclassified**, never `app`. See below. |
|
|
146
157
|
| `outage.stage` / `outage.reason` / `outage.attempts` | `mcp_connection_lost` | which stage of the outage, why the stream went away (closed `OutageReason`, `other` for anything unnamed), and how many reconnects had been tried. See below. |
|
|
@@ -184,7 +195,7 @@ So `plugin` is the only route detectable without anybody typing anything (the pl
|
|
|
184
195
|
|
|
185
196
|
Captured against the real classifier: `verified: 'unknown'` covered "the agent malformed the call", "the consequence was already true", "the app answered 202", "a 2xx body went unread", "the capture was not clean", "nothing was asserted at a real grade" and "the page never settled": **seven causes, two wire payloads**. They belong to three different owners (the agent, the app, Reticle) and need opposite responses: teach the agent, wait and re-check, or ship a fix. On a dashboard they were one bar. `verified: 'no'` collapsed the same way: "channels disagree" (Reticle earning its keep) and "the agent's predicate failed" were the same string.
|
|
186
197
|
|
|
187
|
-
`VerifiedReason` lives in `@reticlehq/core` and is the **single list**. `decideVerified` returns a member from every clause, so a new clause cannot compile without one; `verified.test.ts` drives
|
|
198
|
+
`VerifiedReason` lives in `@reticlehq/core` and is the **single list**. `decideVerified` returns a member from every clause, so a new clause cannot compile without one; `verified.test.ts` drives every member and fails if a member exists that no clause produces. `verification-of.ts` narrows the result field against `Object.values(VerifiedReason)`, and anything else is dropped rather than forwarded, because a string nobody can group by is worse than a gap. **Nothing re-lists these**, including the battery spec, which imports the enum from core's build.
|
|
188
199
|
|
|
189
200
|
Optional on purpose: a suite verdict (`flow_verify`) is a pass/fail with no clause behind it, and an older sender has none. Absent means unclassified.
|
|
190
201
|
|