@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/flows.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Flows and the recorder
|
|
3
|
+
description: 'Record an interactive run once and replay it forever as a git-checked, self-healing program anchored on meaning, not selectors.'
|
|
4
|
+
icon: repeat
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
A **flow** is a recorded interactive run that Reticle stores as JSON under `.reticle/flows/` and replays forever with no AI model in the loop. Steps are anchored on **meaning** (a testid, a signal, or an auto-derived component + source location), never on a `eXX` ref or a coordinate, so they survive refactors; when an anchor does drift, `reticle_flow_replay` names what changed and `reticle_flow_heal` can rebind it.
|
|
8
|
+
|
|
9
|
+
Reticle turns an interactive run into a **git-checked, replayable program** stored under `.reticle/`. Flows are anchored on **meaning** (testid + signal), not volatile element refs or coordinates, so they survive refactors, and when an anchor does drift, Reticle tells you _why_ and can repair it. This is what makes Reticle "the project's living test suite a human seeds and an agent maintains."
|
|
4
10
|
|
|
5
11
|
> All of this is on-disk and human-readable, so flows are reviewed in PRs and diffed like code.
|
|
6
12
|
|
|
@@ -18,7 +24,7 @@ When you record or save a contract, Reticle writes a git-checked workspace next
|
|
|
18
24
|
|
|
19
25
|
The server resolves `.reticle/` from the working directory it runs in (your project root). A fresh agent can read `.reticle/contract.json` to learn the testable surface **without grepping your source**.
|
|
20
26
|
|
|
21
|
-
## The contract
|
|
27
|
+
## The contract: advertise the testable surface
|
|
22
28
|
|
|
23
29
|
In your app, declare what's testable (see [Step 6 in Getting Started](getting-started.md)):
|
|
24
30
|
|
|
@@ -40,7 +46,7 @@ Then persist it to disk so it's committed and any agent can read it:
|
|
|
40
46
|
|
|
41
47
|
## Create a flow
|
|
42
48
|
|
|
43
|
-
**(a) Agent-recorded
|
|
49
|
+
**(a) Agent-recorded.** The agent drives, then saves:
|
|
44
50
|
|
|
45
51
|
```jsonc
|
|
46
52
|
reticle_record {action:"start"}({ recordingName: "create-task" })
|
|
@@ -49,7 +55,7 @@ reticle_record {action:"stop"}({ recordingName: "create-task" })
|
|
|
49
55
|
reticle_flow_save({ flowName: "create-task" }) // → .reticle/flows/create-task.json
|
|
50
56
|
```
|
|
51
57
|
|
|
52
|
-
**(b) Human-recorded (the recorder toolbar)
|
|
58
|
+
**(b) Human-recorded (the recorder toolbar).** With the presenter on (`present: true`), the floating panel hosts a recorder: a human clicks the golden path in the page and Reticle captures each interaction as a **semantic-anchored** step (testid, else role+name), then persists it via `reticle_flow_save_recorded`. The agent then runs and maintains it. _(First cut: structured annotations only, see below; free natural-language annotations are future work.)_
|
|
53
59
|
|
|
54
60
|
### What a flow file looks like
|
|
55
61
|
|
|
@@ -71,7 +77,7 @@ reticle_flow_save({ flowName: "create-task" }) // → .reticle/flows/cr
|
|
|
71
77
|
}
|
|
72
78
|
```
|
|
73
79
|
|
|
74
|
-
Each step binds to a **semantic anchor**, never a `eXX` ref: a `testid`/`signal` when available, else an auto-derived `component` anchor (component name + source `file:line`) for an element with no testid
|
|
80
|
+
Each step binds to a **semantic anchor**, never a `eXX` ref: a `testid`/`signal` when available, else an auto-derived `component` anchor (component name + source `file:line`) for an element with no testid, so the flow stays stable with zero hand-added testids. Only when none of those resolve is a step kept `degraded: true` (a last-resort "add a testid here" marker) rather than silently dropped.
|
|
75
81
|
|
|
76
82
|
## Run a flow
|
|
77
83
|
|
|
@@ -81,17 +87,17 @@ reticle_flow {action:"load"}({ flowName: "create-task" }) // → the flow JSON
|
|
|
81
87
|
reticle_flow_replay({ flowName: "create-task" }) // re-resolve each anchor against the LIVE DOM, run it
|
|
82
88
|
```
|
|
83
89
|
|
|
84
|
-
**Watch it replay on the page.** When the presenter is on (`present: true`), a replay isn't silent
|
|
90
|
+
**Watch it replay on the page.** When the presenter is on (`present: true`), a replay isn't silent. Each step drives the real page, so the synthetic cursor flies to the element, the focus ring lands, and the activity log streams the journey live. You (or a teammate) literally watch the saved journey re-walk itself on your app, then see the verdict land. It's the fastest way to _see_ that a flow still works, not just read a green checkmark.
|
|
85
91
|
|
|
86
92
|
`reticle_flow_replay` returns a status:
|
|
87
93
|
|
|
88
|
-
- `ok
|
|
89
|
-
- `drift
|
|
90
|
-
- `error
|
|
94
|
+
- `ok`: every anchor resolved and every `expect` held.
|
|
95
|
+
- `drift`: an anchor missed (a testid was renamed, or a signal never fired). The result is **legible**, never a blind failure: `{ step, anchor, drift: { reasonKind: "testid_not_found", nearest: "send-message" } }`. (This is the "whose fault is it" principle.)
|
|
96
|
+
- `error`: the flow file is missing/invalid, or a resolved action failed. Runtime failures include the failed step and a top-level error envelope.
|
|
91
97
|
|
|
92
|
-
A testid-_preserving_ refactor (you moved markup but kept the testids) still replays green. A step whose element has **no testid** is anchored on its component + source location (`{ kind: "component", component, source: { file, line } }`)
|
|
98
|
+
A testid-_preserving_ refactor (you moved markup but kept the testids) still replays green. A step whose element has **no testid** is anchored on its component + source location (`{ kind: "component", component, source: { file, line } }`), an auto-derived stable anchor, so a flow records cleanly with zero hand-added testids and replay re-resolves it via `reticle_query by:'component'`.
|
|
93
99
|
|
|
94
|
-
### The decision envelope
|
|
100
|
+
### The decision envelope: what to do next, not just pass/fail
|
|
95
101
|
|
|
96
102
|
On a `drift` or `error`, the replay result carries a `decision` an agent can act on directly:
|
|
97
103
|
|
|
@@ -105,28 +111,28 @@ decision: {
|
|
|
105
111
|
}
|
|
106
112
|
```
|
|
107
113
|
|
|
108
|
-
This is the feedback a human reviewer used to give
|
|
114
|
+
This is the feedback a human reviewer used to give, made machine-actionable, so the agent decides its next move without one.
|
|
109
115
|
|
|
110
116
|
## Verify the whole suite in one call
|
|
111
117
|
|
|
112
|
-
`reticle_flow_verify` replays **every** saved flow (or a named subset) deterministically
|
|
118
|
+
`reticle_flow_verify` replays **every** saved flow (or a named subset) deterministically, with no LLM per flow, and returns one consolidated verdict. This is the regression check to run after any change:
|
|
113
119
|
|
|
114
120
|
```jsonc
|
|
115
121
|
reticle_flow_verify()
|
|
116
122
|
// → { status: "fail", total: 4, passed: 3, failed: 1,
|
|
117
|
-
// summary: "3/4 flows pass
|
|
123
|
+
// summary: "3/4 flows pass, 1 needs attention: ship-deploy",
|
|
118
124
|
// failures: [{ flow: "ship-deploy", verdict: "drift",
|
|
119
125
|
// whatChanged: "...", whereInSource: "src/...:NN", nextAction: "..." }] }
|
|
120
126
|
```
|
|
121
127
|
|
|
122
|
-
Passing flows are counted; only failures carry detail (token-cheap). Build → `reticle_flow_verify` → fix from each failure's `nextAction` → repeat
|
|
128
|
+
Passing flows are counted; only failures carry detail (token-cheap). Build → `reticle_flow_verify` → fix from each failure's `nextAction` → repeat: the autonomous regression loop.
|
|
123
129
|
|
|
124
|
-
## Self-healing
|
|
130
|
+
## Self-healing: the agent maintains the flow
|
|
125
131
|
|
|
126
|
-
When a testid is renamed, the flow drifts. `reticle_flow_heal` proposes
|
|
132
|
+
When a testid is renamed, the flow drifts. `reticle_flow_heal` proposes (and optionally applies) the nearest-match rebind, so flows don't rot:
|
|
127
133
|
|
|
128
134
|
```jsonc
|
|
129
|
-
reticle_flow_heal({ flowName: "create-task" }) // PROPOSE only
|
|
135
|
+
reticle_flow_heal({ flowName: "create-task" }) // PROPOSE only, never writes
|
|
130
136
|
// → { status: "drift", applied: false,
|
|
131
137
|
// proposals: [{ step: 0, from: "add-tassk", to: "add-task", confidence: 0.8 }] }
|
|
132
138
|
|
|
@@ -134,19 +140,19 @@ reticle_flow_heal({ flowName: "create-task", apply: true }) // rewrite the anch
|
|
|
134
140
|
// → { status: "healed", applied: true, proposals: [...] }
|
|
135
141
|
```
|
|
136
142
|
|
|
137
|
-
With `apply: false` the flow file is **never modified
|
|
143
|
+
With `apply: false` the flow file is **never modified**; you get the proposed diff to review. With `apply: true` Reticle rewrites the drifted anchor(s) to the confident nearest match and a subsequent replay passes. A drift with **no** confident nearest match leaves the file untouched.
|
|
138
144
|
|
|
139
145
|
## Annotations (structured)
|
|
140
146
|
|
|
141
147
|
`reticle_annotate` attaches a structured annotation that compiles into the flow, so replay is a _checked_ re-run, not a blind macro:
|
|
142
148
|
|
|
143
149
|
- `assert-signal` / `assert-visible` → a step `expect` predicate (the invariant).
|
|
144
|
-
- `mark-dynamic` → a `flow.dynamic[]` entry
|
|
145
|
-
- `success-state` → `flow.success` (the golden end condition). Pass `signal`/`testid`, or `statePath` (+ `store`, `equals`) to make the golden condition a **store-truth** assertion
|
|
150
|
+
- `mark-dynamic` → a `flow.dynamic[]` entry; replay asserts the region's _presence_ but **not its words** (the LLM-output case: assert `caption:generated`, ignore the caption text).
|
|
151
|
+
- `success-state` → `flow.success` (the golden end condition). Pass `signal`/`testid`, or `statePath` (+ `store`, `equals`) to make the golden condition a **store-truth** assertion against the app's own source of truth, which no DOM read can reach (e.g. `statePath: "deployments.0.status", equals: "live"` fails the flow if a deploy only _looks_ shipped on screen). State assertions are graded as consequences, so they satisfy the business-outcome oracle.
|
|
146
152
|
|
|
147
153
|
## Flows are your test suite
|
|
148
154
|
|
|
149
|
-
`.reticle/` flows can be executed as CI specs
|
|
155
|
+
`.reticle/` flows can be executed as CI specs via `@reticlehq/test`'s `flowsAsSpecs`, replayed with their `expect`/`success` predicates and skipping `dynamic` regions. See [Testing with Reticle](testing.md).
|
|
150
156
|
|
|
151
157
|
## Tool reference
|
|
152
158
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Docs for agents
|
|
3
|
+
description: Fetch any page as plain Markdown, pull the whole site as one file, or hand a page straight to your agent.
|
|
4
|
+
icon: robot
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Append `.md` to any Reticle docs URL to get that page as plain Markdown, or fetch [`https://docs.reticle.sh/llms.txt`](https://docs.reticle.sh/llms.txt) for the index of every page. No API key, no scraper, no HTML parser.
|
|
8
|
+
|
|
9
|
+
The site lives at **`https://docs.reticle.sh`**. Everything below is a plain HTTP GET.
|
|
10
|
+
|
|
11
|
+
## Fetch one page
|
|
12
|
+
|
|
13
|
+
Append `.md` to any documentation URL and you get the source Markdown, with the page's frontmatter and no site chrome:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl https://docs.reticle.sh/getting-started.md
|
|
17
|
+
curl https://docs.reticle.sh/agent-cheatsheet.md
|
|
18
|
+
curl https://docs.reticle.sh/usage.md
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### What comes back
|
|
22
|
+
|
|
23
|
+
Every Markdown page is prepended with two things before its content, so a single fetch orients you without a second call:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
> ## Documentation Index
|
|
27
|
+
> Fetch the complete documentation index at: https://docs.reticle.sh/llms.txt
|
|
28
|
+
> Use this file to discover all available pages before exploring further.
|
|
29
|
+
|
|
30
|
+
> ## Agent Instructions
|
|
31
|
+
> Reticle is a dev-only, localhost-only verification layer for AI coding agents...
|
|
32
|
+
> Only `reticle_act_and_wait` and `reticle_assert` produce a verdict...
|
|
33
|
+
> A verdict of `verified: "unknown"` is not a pass...
|
|
34
|
+
> Package names are scoped `@reticlehq/*` and the CLI is `reticle`...
|
|
35
|
+
|
|
36
|
+
# reticle_query
|
|
37
|
+
...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The four rules are the ones worth knowing before you drive anything, and they arrive whether or not you asked for them. You do not have to read this page first.
|
|
41
|
+
|
|
42
|
+
The slug is the filename in [`docs/`](https://github.com/reticlehq/reticle/tree/main/docs), so `docs/token-efficiency.md` in the repo is `/token-efficiency` on the site and `/token-efficiency.md` as raw text.
|
|
43
|
+
|
|
44
|
+
## Fetch the index, or everything
|
|
45
|
+
|
|
46
|
+
| URL | What it is | Use it when |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| [`/llms.txt`](https://docs.reticle.sh/llms.txt) | Every page title and URL, and nothing else | You want to pick the right page before spending tokens on it |
|
|
49
|
+
| [`/llms-full.txt`](https://docs.reticle.sh/llms-full.txt) | The entire documentation as one file (hundreds of KB, and growing) | You are seeding a context window or an index once |
|
|
50
|
+
|
|
51
|
+
Start with `llms.txt`. It is small enough to read in full and tells you which single page answers the question, which is almost always cheaper than pulling `llms-full.txt`.
|
|
52
|
+
|
|
53
|
+
## Pull a page into your harness
|
|
54
|
+
|
|
55
|
+
Every page has a menu in its top-right corner: copy the page as Markdown, or open it directly in Claude, ChatGPT, Cursor or VS Code with the source already attached. That is the fastest route when a human is driving and wants the agent to have the page.
|
|
56
|
+
|
|
57
|
+
> None of this is Reticle itself. These endpoints serve the **documentation**. Reticle runs on your machine and verifies your app; you get it with `npx @reticlehq/server init`. See [Getting started](/getting-started).
|
|
58
|
+
|
|
59
|
+
## Which page to read
|
|
60
|
+
|
|
61
|
+
You rarely need more than one.
|
|
62
|
+
|
|
63
|
+
| You want to | Read |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| Install Reticle and get a first verdict | [Getting started](/getting-started) |
|
|
66
|
+
| Get fluent in one screen | [Agent cheat sheet](/agent-cheatsheet) |
|
|
67
|
+
| Look up a specific tool, flag, or workflow | [Complete usage guide](/usage) |
|
|
68
|
+
| Understand what Reticle is doing under the hood | [Architecture](/architecture) |
|
|
69
|
+
| Wire it into a desktop app | [Desktop apps](/desktop) |
|
|
70
|
+
| Make the checks repeatable in CI | [Specs for CI](/testing) |
|
|
71
|
+
| Work on Reticle itself | [Gates](/gates), then [System map](/system-map) |
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Frameworks: proven, wired, and the difference'
|
|
3
|
+
sidebarTitle: Frameworks
|
|
4
|
+
description: Which frameworks Reticle supports, which are wired but unverified, and the exact wiring for each one. React, Next.js, Remix, Astro, SvelteKit, Nuxt, Vue and plain HTML.
|
|
5
|
+
icon: layer-group
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**React + Vite, Next.js, Remix and Astro are fully supported, each with an example app driven to a verdict in CI. SvelteKit, Nuxt, Vue, Preact, Create React App and plain HTML are wired and documented but not gated, so treat them as plausible rather than proven.** The exact wiring for each is below.
|
|
9
|
+
|
|
10
|
+
Reticle is a verification layer that embeds a dev-only SDK in your running web app so an AI coding agent can prove a change works. Its core is framework-neutral: DOM, network, console, routing, animations and source mapping work anywhere JavaScript runs.
|
|
11
|
+
|
|
12
|
+
What varies is two things: how `connect()` gets into your app, and whether you get component identity on top.
|
|
13
|
+
|
|
14
|
+
**Source pointer** is the `file:line` on a verdict, the thing an agent follows straight to the code after a red. It comes from the build plugin stamping your JSX, not from the SDK, so it is a property of how the framework is wired rather than of the framework. Remix and Astro render their own HTML, so the plugin's connect-injection cannot fire there, and both examples used to drop the plugin entirely, which quietly cost them the stamping too. Both now wire it with `inject: false`, which keeps the half that applies. A row marked `unproven` has no example app, so nobody has checked.
|
|
15
|
+
|
|
16
|
+
## Where each framework stands
|
|
17
|
+
|
|
18
|
+
We separate "we tested this" from "this should work". Four frameworks have an example app and a CI gate. The rest are wired and documented but nothing proves they still work.
|
|
19
|
+
|
|
20
|
+
Two columns say different things and it is worth keeping them apart. **CI gate** `yes` means an example app is driven to a verdict on every change. **install only** means the install gate scaffolds that stack from scratch, runs `init`, boots it and waits for a session, so the setup is proven and the drive is not. Vue is there because a release once nearly shipped an installer that left a Vue app connecting 0% of the time, and the gate that exists for exactly that had only React scaffolds.
|
|
21
|
+
|
|
22
|
+
**Source pointer** is the `file:line` on a verdict. It needs the build plugin to stamp the source, which it does for JSX and, separately, for Svelte components. Vue single-file components are neither, so a Vue app gets every other signal and no `source` field. Measured, not assumed: a Svelte counter returns `src/lib/Counter.svelte:5` and the same drive on Vue returns none.
|
|
23
|
+
|
|
24
|
+
| Framework | `init` detects | Example app | CI gate | Source pointer | Status |
|
|
25
|
+
| --- | :-: | :-: | :-: | :-: | --- |
|
|
26
|
+
| **React + Vite** | yes | yes | yes | yes | Supported |
|
|
27
|
+
| **Next.js** | yes | yes | yes | yes | Supported |
|
|
28
|
+
| **Remix** | yes | yes | yes | yes | Supported |
|
|
29
|
+
| **Astro** | yes | yes | yes | yes | Supported |
|
|
30
|
+
| **SvelteKit** | yes | no | no | unproven | Wired, unverified |
|
|
31
|
+
| **Nuxt** | yes | no | no | unproven | Wired, unverified |
|
|
32
|
+
| **Create React App** | yes | no | no | unproven | Wired, unverified |
|
|
33
|
+
| **Plain HTML** | yes | no | no | no | Wired, unverified |
|
|
34
|
+
| **Vue (non-Nuxt)** | as Vite | no | install only | no | Install gated, drive unverified |
|
|
35
|
+
| **Preact** | as Vite | no | no | unproven | Sensor, React adapter via `preact/compat` |
|
|
36
|
+
|
|
37
|
+
<Warning>
|
|
38
|
+
"Wired, unverified" means exactly that. The recipe exists and was written from a real integration,
|
|
39
|
+
but there is no app in CI exercising it, so nothing will tell us if it breaks. If one of these
|
|
40
|
+
does not register a session, that is a bug worth reporting.
|
|
41
|
+
</Warning>
|
|
42
|
+
|
|
43
|
+
`init` detects the UI library separately from the framework. That distinction exists because detection used to stop at "vite is in package.json", which installed the React adapter into Vue and Preact apps and reported all green with nothing attached.
|
|
44
|
+
|
|
45
|
+
## React on Vite
|
|
46
|
+
|
|
47
|
+
The shortest path, and the one with the most in it.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm i -D @reticlehq/react @reticlehq/vite-plugin
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { defineConfig } from 'vite';
|
|
55
|
+
import react from '@vitejs/plugin-react';
|
|
56
|
+
import { reticle } from '@reticlehq/vite-plugin';
|
|
57
|
+
|
|
58
|
+
export default defineConfig({
|
|
59
|
+
plugins: [reticle(), react()],
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
That is the whole install. The plugin injects `connect()` and stamps source locations, which is what turns a DOM node into `src/components/Login.tsx:81`.
|
|
64
|
+
|
|
65
|
+
## Next.js
|
|
66
|
+
|
|
67
|
+
Keeps SWC. No Babel migration.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm i -D @reticlehq/react @reticlehq/next
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const { withReticle } = require('@reticlehq/next');
|
|
75
|
+
|
|
76
|
+
module.exports = withReticle({/* your config */});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
<Note>
|
|
80
|
+
`@reticlehq/next` is CommonJS on purpose. `next.config.js` is loaded by Node before any ESM
|
|
81
|
+
transform, so an ESM-only helper fails at the least helpful moment.
|
|
82
|
+
</Note>
|
|
83
|
+
|
|
84
|
+
## Remix and Astro
|
|
85
|
+
|
|
86
|
+
Both run on Vite, so both use the Vite plugin exactly as React does. Astro needs the React integration if you want component identity in `.jsx` islands. Static `.astro` markup gets DOM, network and routing without it.
|
|
87
|
+
|
|
88
|
+
## SvelteKit
|
|
89
|
+
|
|
90
|
+
SvelteKit renders through `app.html` and never triggers Vite's `index.html` injection, so the plugin cannot auto-connect. A client hook is the reliable path, and it is what `init` writes.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// src/hooks.client.ts
|
|
94
|
+
import { reticle } from '@reticlehq/browser';
|
|
95
|
+
|
|
96
|
+
if (import.meta.env.DEV) reticle.connect();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The Vite plugin still stamps `data-reticle-source` into `.svelte` components, so verdicts carry `file:line`.
|
|
100
|
+
|
|
101
|
+
For state, use the `svelteStore` adapter. See [State management](/state-management).
|
|
102
|
+
|
|
103
|
+
## Nuxt
|
|
104
|
+
|
|
105
|
+
Nuxt owns its own Vite instance and renders its own HTML. There is no `vite.config` to patch and no `index.html` to inject into. The idiom is a dev-only client plugin.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// plugins/reticle.client.ts
|
|
109
|
+
export default defineNuxtPlugin(() => {
|
|
110
|
+
if (!import.meta.dev) return;
|
|
111
|
+
void import('@reticlehq/browser').then(({ reticle }) => {
|
|
112
|
+
reticle.connect();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Three traps, all reported from the field rather than imagined:
|
|
118
|
+
|
|
119
|
+
<AccordionGroup>
|
|
120
|
+
<Accordion title="The .client suffix is load-bearing">
|
|
121
|
+
It is what keeps the plugin out of the server bundle. Without it you are running the SDK during
|
|
122
|
+
SSR, where `window` does not exist.
|
|
123
|
+
</Accordion>
|
|
124
|
+
<Accordion title="Guard on import.meta.dev, never on hostname">
|
|
125
|
+
`window.location.hostname === 'localhost'` fails twice over here. `window` does not exist in
|
|
126
|
+
SSR, and the check is false on any hosts-file alias or LAN address. `import.meta.dev` resolves
|
|
127
|
+
at build time and does not care what host you develop on.
|
|
128
|
+
</Accordion>
|
|
129
|
+
<Accordion title="Restart the dev server">
|
|
130
|
+
A running dev server does not pick up a new plugin. It will not appear in
|
|
131
|
+
`.nuxt/plugins/client.mjs`, and the app comes up with no SDK at all and no error saying so.
|
|
132
|
+
</Accordion>
|
|
133
|
+
</AccordionGroup>
|
|
134
|
+
|
|
135
|
+
Use `@reticlehq/browser`, the framework-neutral sensor. You get DOM, network, console, routing and source `file:line`. What you do not get is React component identity, which is the only thing the React adapter adds.
|
|
136
|
+
|
|
137
|
+
<Tip>
|
|
138
|
+
Developing on something other than `localhost`? Add `allowNonLocalhost: true` to the connect call.
|
|
139
|
+
Without it the SDK loads and then refuses, and the only sign is one line in the browser console.
|
|
140
|
+
</Tip>
|
|
141
|
+
|
|
142
|
+
## Vue
|
|
143
|
+
|
|
144
|
+
Vue apps outside Nuxt are Vite apps, so `init` finds them. Install the sensor rather than the React kit.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm i -D @reticlehq/browser
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { reticle } from '@reticlehq/browser';
|
|
152
|
+
if (import.meta.env.DEV) reticle.connect();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Pinia is a first-class store. See [State management](/state-management).
|
|
156
|
+
|
|
157
|
+
## Preact
|
|
158
|
+
|
|
159
|
+
Detected as its own UI library, because a Preact app using `preact/compat` aliases React and would otherwise be handed the React kit with nothing to attach to.
|
|
160
|
+
|
|
161
|
+
The sensor works. If you use `preact/compat`, the React adapter can attach through the alias, but that path is not gated.
|
|
162
|
+
|
|
163
|
+
## Create React App and plain bundlers
|
|
164
|
+
|
|
165
|
+
No config file to patch, so `connect()` goes in your entry module.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// src/index.tsx
|
|
169
|
+
import { reticle } from '@reticlehq/browser';
|
|
170
|
+
if (process.env.NODE_ENV !== 'production') reticle.connect();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The same shape works for webpack, Parcel, and the Vue and Svelte CLIs.
|
|
174
|
+
|
|
175
|
+
## Plain HTML
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<script type="module">
|
|
179
|
+
import { reticle } from '/node_modules/@reticlehq/browser/dist/index.js';
|
|
180
|
+
reticle.connect();
|
|
181
|
+
</script>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Keep it out of your production template. Reticle self-disables when the build reports `NODE_ENV=production`, but a second lock on a door leading to your users is a reasonable number.
|
|
185
|
+
|
|
186
|
+
## Desktop is a different shape
|
|
187
|
+
|
|
188
|
+
Electron and Tauri invert the direction: your app dials the daemon, and there is no URL for an agent to open. Reticle also reaches the main-process and Rust IPC boundary, which the renderer cannot see.
|
|
189
|
+
|
|
190
|
+
<Card title="Desktop apps" icon="display" href="/desktop">
|
|
191
|
+
Electron and Tauri, IPC observation, and the CSP trap whose failure is completely silent.
|
|
192
|
+
</Card>
|
|
193
|
+
|
|
194
|
+
## Not supported
|
|
195
|
+
|
|
196
|
+
Angular and Solid appear in dependency detection but have no wiring, no adapter and no gate. The sensor may work if you call `connect()` yourself. Nothing proves it, so we are not going to list them as supported.
|
|
197
|
+
|
|
198
|
+
## Frequently asked questions
|
|
199
|
+
|
|
200
|
+
<AccordionGroup>
|
|
201
|
+
<Accordion title="Does Reticle work with Next.js?">
|
|
202
|
+
Yes, and it keeps SWC. Install `@reticlehq/react` and `@reticlehq/next`, then wrap your config
|
|
203
|
+
with `withReticle`. There is no Babel migration. Next.js has an example app and a CI gate, so it
|
|
204
|
+
is supported rather than merely wired, and you get component identity and `file:line` source
|
|
205
|
+
pointers.
|
|
206
|
+
</Accordion>
|
|
207
|
+
|
|
208
|
+
<Accordion title="Does Reticle work with Vue?">
|
|
209
|
+
Partly, and the limit is specific. Install `@reticlehq/browser` rather than the React kit, and you
|
|
210
|
+
get DOM, network, console, routing, actions and Pinia state. What you do not get is a `source`
|
|
211
|
+
field: source stamping covers JSX and Svelte components, and a Vue single-file component is
|
|
212
|
+
neither. The install path is covered by a gate; the drive is not.
|
|
213
|
+
</Accordion>
|
|
214
|
+
|
|
215
|
+
<Accordion title="Which framework should I pick if I want the strongest verdicts?">
|
|
216
|
+
React on Vite. It is the shortest install, and it is the only combination that gives you all three
|
|
217
|
+
of component identity, `file:line` source pointers and a CI-gated example app. If you are already
|
|
218
|
+
on Next.js, Remix or Astro, you get the same guarantees.
|
|
219
|
+
</Accordion>
|
|
220
|
+
|
|
221
|
+
<Accordion title="My framework is not listed at all. Will it work?">
|
|
222
|
+
Probably, with one line. `@reticlehq/browser` is the framework-neutral sensor: call
|
|
223
|
+
`reticle.connect()` behind a dev guard anywhere your app boots, and you get DOM, network, console
|
|
224
|
+
and routing. What you lose is component identity and, unless a build plugin stamps your source,
|
|
225
|
+
the `file:line` pointer. If it does not register a session, that is a bug worth reporting.
|
|
226
|
+
</Accordion>
|
|
227
|
+
|
|
228
|
+
<Accordion title="What does 'wired, unverified' actually mean for me?">
|
|
229
|
+
The recipe on this page was written from a real integration and is expected to work, but no app
|
|
230
|
+
in CI exercises it, so nothing will tell us when it breaks. If you depend on one of these, the
|
|
231
|
+
useful thing you can do is report it when it stops working, since that is the only signal there
|
|
232
|
+
is.
|
|
233
|
+
</Accordion>
|
|
234
|
+
</AccordionGroup>
|
|
235
|
+
|
|
236
|
+
<Card title="Something missing?" icon="comments" href="/tools-session-and-feedback">
|
|
237
|
+
A framework you need is a `gap` report. It is the signal that decides what gets built.
|
|
238
|
+
</Card>
|
package/docs/gate-plan.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Release gate plan
|
|
3
|
+
description: 'What has to be true before a PR merges and before a release ships, and what is built so far.'
|
|
4
|
+
icon: list-check
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Two gates, not one: a **merge gate** that answers "does this PR break anything" and runs on every PR in minutes, and a **release gate** that answers "does this version work in the clients people use" and runs on a release candidate over hours. Phases 0, 1, 2 and 4 are built; Phase 3 (the client matrix) is blocked on `init` learning each client, and Tier 2 is blocked on a token that cannot be created from inside this repo.
|
|
2
8
|
|
|
3
9
|
> What has to be true before a PR merges and before a release ships, why each piece exists, and what is built so far. Derived from [`system-map.md`](./system-map.md); the harness rules every tier obeys are in [`harness-rules.md`](../apps/e2e/harness-rules.md).
|
|
4
10
|
|
|
@@ -17,17 +23,17 @@
|
|
|
17
23
|
|
|
18
24
|
| Tier | Question | Who | When | Blocks |
|
|
19
25
|
| --- | --- | --- | --- | --- |
|
|
20
|
-
| 0
|
|
21
|
-
| 1
|
|
22
|
-
| 2
|
|
23
|
-
| 3
|
|
24
|
-
| 4
|
|
26
|
+
| 0: repo | is the code internally consistent | Actions | every PR | merge |
|
|
27
|
+
| 1: install | does it install into an app nobody instrumented | Actions, scaffolded apps | PRs touching install/wire | merge |
|
|
28
|
+
| 2: ecosystem | does it survive third-party apps at real scale | `reticle-fixtures`, dispatch | 3×/week + release | release |
|
|
29
|
+
| 3: conformance | does each MCP client actually work | humans + headless CLIs | release candidate | the tag |
|
|
30
|
+
| 4: canary | does a real model still succeed | nightly, live LLM | nightly | nothing (alerts) |
|
|
25
31
|
|
|
26
|
-
**Tier 1 vs 2.** Tier 1 scaffolds a pristine app at CI time (`npm create vite`)
|
|
32
|
+
**Tier 1 vs 2.** Tier 1 scaffolds a pristine app at CI time (`npm create vite`): clean by construction, seconds to produce, catches install _regressions_, fast enough to block a PR. Tier 2 uses vendored production apps and catches install _complexity_. Conflating them produces a gate too slow to block and too shallow to trust.
|
|
27
33
|
|
|
28
34
|
**Tier 3 is the [CNCF conformance](https://github.com/cncf/k8s-conformance) model:** contributors do not _claim_ a client works, they run one command and submit its machine-generated output as a PR, which a bot validates before a human looks. Self-reported pass/fail cannot gate anything.
|
|
29
35
|
|
|
30
|
-
**No LLM in the merge path.** Deterministic tests on every commit; live-model evals nightly and alert-only. The `record → save → verify → heal` chain is what makes this possible
|
|
36
|
+
**No LLM in the merge path.** Deterministic tests on every commit; live-model evals nightly and alert-only. The `record → save → verify → heal` chain is what makes this possible: a flow is recorded once by an agent, then replayed forever with no model.
|
|
31
37
|
|
|
32
38
|
## The matrix, factored
|
|
33
39
|
|
|
@@ -35,64 +41,64 @@
|
|
|
35
41
|
|
|
36
42
|
## Phases
|
|
37
43
|
|
|
38
|
-
### Phase 0
|
|
44
|
+
### Phase 0: the harness contract and the map (**done**)
|
|
39
45
|
|
|
40
46
|
Nothing below is trustworthy until a gate result can be distinguished from a gate artifact.
|
|
41
47
|
|
|
42
|
-
- [x] `docs/system-map.md
|
|
43
|
-
- [x] `apps/e2e/harness-rules.md
|
|
44
|
-
- [x] `apps/e2e/gate-harness.mjs
|
|
48
|
+
- [x] `docs/system-map.md`: topology, connection sequence, tool graph, fragility inventory
|
|
49
|
+
- [x] `apps/e2e/harness-rules.md`: the four rules, with the incidents that produced each
|
|
50
|
+
- [x] `apps/e2e/gate-harness.mjs`: the rules as code, `portHolders`, `freePortSafely`, `startOwnedDaemon`, `watchTransport`, `attributeOutcome`, plus a self-check
|
|
45
51
|
- [x] `run.mjs` and `ci.yml` no longer kill client sockets on the bridge port
|
|
46
52
|
|
|
47
|
-
### Phase 1
|
|
53
|
+
### Phase 1: close the silent gaps already in reach
|
|
48
54
|
|
|
49
|
-
- [x] **Trace-shape assertions
|
|
50
|
-
- [x] **Daemon truthfulness (the port half)
|
|
51
|
-
- [x] **`daemon_alive` heartbeat
|
|
52
|
-
- [x] **`observation_lost
|
|
53
|
-
- [x] **Telemetry chokepoint coverage
|
|
54
|
-
- [x] **Specified transport faults** in place of `kill -9
|
|
55
|
+
- [x] **Trace-shape assertions**: `apps/e2e/trace-shape.mjs` + `trace-shape-test`. One root span per callId, no nested span without a completed parent (the hang signature), no undeclared parentless `browser.command`, no `ok:false` without an error. Calibrated against a real 494-span run: silent there, fires on all four fault shapes.
|
|
56
|
+
- [x] **Daemon truthfulness (the port half)**: `daemon/port-presence.ts` gives a three-state answer (`daemon` / `foreign` / `free`) from two probes and no platform code. `serve` now refuses a foreign port and waits for a real bind before claiming success; `status` and `doctor` name the obstacle. Guarded by `daemon-port-honesty-test`, which squats the port and includes the free-port control. (#105, #112, #115)
|
|
57
|
+
- [x] **`daemon_alive` heartbeat**: `daemon/heartbeat.ts` beats unconditionally on a fixed cadence (30s, `RETICLE_HEARTBEAT_MS` to override) and `classifyDaemonLife` reads a log back into `alive` / `clean` / `signalled` / `died_silently` / `unknown`. A heartbeat nobody interprets is only log volume, so the reader ships with it and is exported for the gate. Guarded by `daemon-heartbeat-test`, which SIGKILLs a real daemon and includes the tidy-shutdown control. (#123)
|
|
58
|
+
- [x] **`observation_lost`**: a new `VerifiedReason`, matched above the `pass === false` clause, so a lost connection grades UNKNOWN instead of being reported as a failed assertion. Signalled by a structured flag from `waitForPredicate`, never by matching on `failureReason` (which is free prose about the app everywhere else it is produced). Threaded through the assert and wait paths too, not just act. (#124)
|
|
59
|
+
- [x] **Telemetry chokepoint coverage**: `tools/dispatch-chokepoint.test.ts` scans for any `.handler(` call outside `runTool` and fails unless it is declared with a stated reason and cost. Three exist today: family folding in `merge-tools.ts` (counted as the family, not the member), `verify-change-tools.ts` (inner `flow_verify` uncounted), and the bridge test harness. Two of those are known observability gaps; the value is that they are now _known_ and a fourth cannot arrive unnoticed. Verified by introducing a bypass and watching it redden.
|
|
60
|
+
- [x] **Specified transport faults** in place of `kill -9`: `apps/e2e/fault-proxy.mjs` (no dependency; toxiproxy is not installed and the battery is deliberately dependency-free) gives none / reset-peer / blackhole / latency / truncate over `node:net`, each proven distinguishable by its own self-check. `transport-faults-test` puts it between the MCP proxy and a real daemon and asserts the product claim: every call is ANSWERED and the stdio server survives. It also separates the two unanswered-call populations by timing: a queued call answered by the 20s queue timer, a call broken in flight answered in ~600ms via `sse_aborted`. A toxic breaks the connection and never the process, so the self-inflicted `kill -9` confusion is unreachable here.
|
|
55
61
|
|
|
56
|
-
### Phase 2
|
|
62
|
+
### Phase 2: the install gate
|
|
57
63
|
|
|
58
|
-
- [x] **Tier 1
|
|
64
|
+
- [x] **Tier 1, all three `init` paths**: `apps/e2e/install-gate.mjs` (`pnpm gate:install`), wired into CI as its own `install-gate` job. Scaffolds `npm create vite`, `create-next-app` (app router) and `create-next-app --no-app` (pages router), publishes this checkout to a local Verdaccio, lets **`init` do its own dependency install** from it, boots each app, opens it in a real browser and POLLS for a session. **3/3 scaffolds, 8/8 assertions each.** The pages-router path (no `app/` root layout, so connect must mount via `pages/_app`) is the one that once did nothing at all, silently.
|
|
59
65
|
- `⚠` is an absolute zero, not a tolerated exemption. The earlier `file:`-wired version had to pass `--no-install` and then argue away the `⚠` it produced, which left the step most likely to regress as the one step untested.
|
|
60
|
-
- A `package-lock.json` check confirms the SDK came from the local registry. Without it a scope typo silently measures PUBLISHED code
|
|
61
|
-
- **Negative control, run FIRST in CI** (`pnpm gate:install:self-test`): every scaffold is mis-wired to a port the daemon is not on and every one must go RED. Verified
|
|
66
|
+
- A `package-lock.json` check confirms the SDK came from the local registry. Without it a scope typo silently measures PUBLISHED code, which is exactly the defect this work found in `docs/local-registry.md` (`@reticle:` vs `@reticlehq:`).
|
|
67
|
+
- **Negative control, run FIRST in CI** (`pnpm gate:install:self-test`): every scaffold is mis-wired to a port the daemon is not on and every one must go RED. Verified: all three fail on the session assertion alone (7 passed, 1 failed), attributed FAIL rather than INCONCLUSIVE because the bridge was provably up. A guard that has never failed is not a guard.
|
|
62
68
|
- `file:` wiring is a dead end and the reason is worth keeping: npm symlinks it, which Vite resolves and Next does not; `--install-links` copies instead and then cannot resolve `workspace:*` at all.
|
|
63
|
-
- [◐] **Tier 2
|
|
69
|
+
- [◐] **Tier 2: built, NOT live.** The sending half is `fixtures-dispatch` in `ci.yml` (on `main` only, never fails the workflow; it is a notification, and the verdict lands in the fixtures repo's run). The receiving half is a template at [`docs/fixtures-dispatch-receiver.yml`](./fixtures-dispatch-receiver.yml), kept in the _sending_ repo so the two ends of the contract cannot drift apart.
|
|
64
70
|
- **Blocked on two things neither of which can be done from inside this repo:** a fine-grained PAT with Actions:write on `reticle-fixtures` added as `FIXTURES_DISPATCH_TOKEN`, and the template copied into that repo. Until the token exists the job says so and exits 0.
|
|
65
71
|
- **It has never run.** Every line is unverified. Tier 2 appearing in this plan is not Tier 2 being live, and the checkbox is deliberately ◐ rather than ✗ or ✓.
|
|
66
|
-
- [x] **Committed baseline of what `init` plans
|
|
67
|
-
- The `target` is in the fingerprint and is the load-bearing field. Leaving it out
|
|
68
|
-
- Proven by tampering: changing one committed entry to `vite.config.js` reddens with a readable expected/got, while every other assertion stays green
|
|
69
|
-
- [x] **Risk routing
|
|
70
|
-
- The reason this was deferred is now solved rather than ignored: a job skipped by an `if:` reports **skipped**, and a branch-protection rule requiring `install-gate` directly would block every docs-only PR forever, waiting on a job that will never run. So nothing is required directly
|
|
72
|
+
- [x] **Committed baseline of what `init` plans**: `apps/e2e/install-baseline.json`, one line per step (`mark title → target`) per scaffold, diffed on every run; `--update-baseline` re-records it so a change arrives as a reviewable diff. "Zero ⚠" is an absolute and cannot carry this alone: a step silently changing mark (`✓ → ℹ`) still passes it, and a step DISAPPEARING from the plan passes it most comfortably of all, because the thing that would have warned you is the thing that is gone. A threshold answers "is this bad"; a baseline answers "is this DIFFERENT", and silent regressions are almost always the second question.
|
|
73
|
+
- The `target` is in the fingerprint and is the load-bearing field. Leaving it out (which the first version did, on an assumption about absolute paths that was simply wrong) made the app-router and pages-router baselines byte-identical, so a regression mounting the pages-router app into the wrong file would have diffed clean. `Mount ReticleDev → app/layout.tsx` vs `→ pages/_app.tsx` is the whole distinction.
|
|
74
|
+
- Proven by tampering: changing one committed entry to `vite.config.js` reddens with a readable expected/got, while every other assertion stays green, which is the point, since a one-character target change is invisible to both "zero ⚠" and "a session appeared".
|
|
75
|
+
- [x] **Risk routing**: a `changes` job computes the diff with plain `git diff --name-only` (no third-party action: the question is "what changed", and a paths-filter dependency for a one-line diff is a supply-chain decision for nothing) and gates `install-gate` on it. It **fails open**: an unresolvable base ref runs every tier, because the cost of a false positive is CI time and the cost of a false negative is the 0%-connect class shipping again.
|
|
76
|
+
- The reason this was deferred is now solved rather than ignored: a job skipped by an `if:` reports **skipped**, and a branch-protection rule requiring `install-gate` directly would block every docs-only PR forever, waiting on a job that will never run. So nothing is required directly: a single aggregate `gate` job is, and it passes when every dependency succeeded **or was deliberately skipped**, and fails on anything else. `always()` on that job is load-bearing; without it the aggregate is itself skipped whenever a dependency is, reproducing the problem one level up.
|
|
71
77
|
- Routing verified against twelve representative paths without touching CI.
|
|
72
78
|
|
|
73
|
-
### Phase 3
|
|
79
|
+
### Phase 3: the client matrix
|
|
74
80
|
|
|
75
|
-
**Blocked on `init` learning each client.** Today only Claude Code (`claude mcp add`) and Cursor (`~/.cursor/mcp.json`) are first-class; everything else gets a printed JSON snippet, so a
|
|
81
|
+
**Blocked on `init` learning each client.** Today only Claude Code (`claude mcp add`) and Cursor (`~/.cursor/mcp.json`) are first-class; everything else gets a printed JSON snippet, so a per-client release artifact would measure the contributor's copy-paste rather than the product.
|
|
76
82
|
|
|
77
|
-
- [x] **`init` support per client
|
|
83
|
+
- [x] **`init` support per client**: `init/mcp-clients.ts` registers Reticle with **seven** clients, not two. Every path and shape was read from that client's own documentation rather than recalled: Cursor/Windsurf/Gemini share `mcpServers`, VS Code uses `servers`, OpenCode uses `mcp` with the command as an **array**, Codex is TOML. A single "write mcp.json" assuming the Cursor shape produces a file three of them ignore: an install that reports success and registers nothing.
|
|
78
84
|
- Detection is conservative and one-directional: we write into a config a client **already has**, never creating `~/.gemini` or `~/.codeium` for somebody who does not use them.
|
|
79
85
|
- Codex is deliberately not auto-written. Editing TOML without a parser risks every _other_ server in the user's file; it returns MANUAL with an exact block.
|
|
80
|
-
- [x] **Per-client compat script
|
|
86
|
+
- [x] **Per-client compat script**: `apps/e2e/client-compat.mjs`. Extracts the command from the config **exactly the way that client would**, runs it, and speaks MCP to it. 7/7 clients, 23 assertions, each advertising the default surface (the count is asserted in `surface-sizes.test.ts`, never restated here).
|
|
81
87
|
- It proves only the half a machine can prove, and the verdict is named `runnable-unverified` so the distinction survives into the matrix. Whether the client _reads_ that path needs the real client, which is the submitted half.
|
|
82
|
-
- [x] **Submission flow
|
|
83
|
-
- [x] **Generated `MATRIX.md
|
|
88
|
+
- [x] **Submission flow**: `apps/e2e/matrix.mjs --validate`, run by CI's `matrix-records` job on every PR. The validator refuses what cannot be acted on: `works` without `checks.toolsVisible` (the self-report the flow exists to replace), `broken` without the client's verbatim error, or any record with no host or version. Its self-check runs first, because a validator that has never refused anything is not a validator. Contributor guide in [`docs/matrix/README.md`](./matrix/README.md).
|
|
89
|
+
- [x] **Generated `MATRIX.md`**: built from submitted records, prettier-ignored because a generated file that fails `format:check` teaches people to skip the gate. **The current honest state is seven clients and ZERO ticks**: `◐` means a runnable entry was written where the client documents it and _nobody has run that client_, and the generated legend says so rather than rounding `◐` up to `✅`.
|
|
84
90
|
|
|
85
|
-
### Phase 4
|
|
91
|
+
### Phase 4: rates, not booleans
|
|
86
92
|
|
|
87
93
|
Connection stability is a rate. A pass/fail cannot express "breaks a lot", and a number that is not recorded cannot regress.
|
|
88
94
|
|
|
89
|
-
- [x] **Soak with a recorded stability rate
|
|
90
|
-
- **Idle time between rounds is the point**, not a pause for politeness. 150 back-to-back calls finish in seconds and measure throughput; the disconnect users actually report happens while nobody is calling
|
|
95
|
+
- [x] **Soak with a recorded stability rate**: `apps/e2e/soak.mjs` (`pnpm gate:soak`), run inside the e2e battery where a paired app already exists, and appended to `bench/soak-history.jsonl` so the rate can regress against itself. One held-open MCP link for the whole run, never a reconnect per call (harness rule 3). First recorded row: **120/120 answered, 0 link drops over 39s**.
|
|
96
|
+
- **Idle time between rounds is the point**, not a pause for politeness. 150 back-to-back calls finish in seconds and measure throughput; the disconnect users actually report happens while nobody is calling: the agent is thinking and the next call finds the link gone. Keep-alives, idle shutdown and proxy timeouts all live in that gap, so a soak with no idle never enters it. `--idle-ms 30000 --rounds 60` is the half-hour release soak.
|
|
91
97
|
- Attribution is 3-valued: a transport that did not stay up reports INCONCLUSIVE and claims nothing about the product (harness rule 4).
|
|
92
|
-
- [x] **Per-tool latency and failure budgets
|
|
93
|
-
- **Latency is recorded, NOT gated, and that is a deliberate deviation from the wording of this plan.** CLAUDE.md: timing assertions are a bug. A p95 budget in wall-clock ms is a statement about the machine, goes red only under parallel CI load, and teaches everyone to re-run
|
|
94
|
-
- The warn carries a 50ms floor, added because the first real run shouted `reticle_sessions p95 8ms vs 1ms (>4x)
|
|
98
|
+
- [x] **Per-tool latency and failure budgets**, same run, `bench/TOOL-PROFILE.md`. Answers "which tool is breaking": `tool-surface-sweep` proves each tool is callable **once**, and one call cannot have a failure rate.
|
|
99
|
+
- **Latency is recorded, NOT gated, and that is a deliberate deviation from the wording of this plan.** CLAUDE.md: timing assertions are a bug. A p95 budget in wall-clock ms is a statement about the machine, goes red only under parallel CI load, and teaches everyone to re-run; a gate people re-run has stopped working. A >4x p95 jump prints a loud WARN instead. What IS gated: answer rate (absolute) and per-tool failure rate (vs the recorded baseline), both deterministic.
|
|
100
|
+
- The warn carries a 50ms floor, added because the first real run shouted `reticle_sessions p95 8ms vs 1ms (>4x)`: true, imperceptible, and exactly how a warning channel dies.
|
|
95
101
|
- It profiles the **6 repeatable read-only tools, not all 48 advertised**, and says so in the generated file. Repeating a mutating tool measures the fixture drifting rather than the tool degrading.
|
|
96
|
-
- Eleven-case self-check (`node apps/e2e/soak.mjs --self-check`), in CI's `matrix-records` job. It needs no build and no app
|
|
102
|
+
- Eleven-case self-check (`node apps/e2e/soak.mjs --self-check`), in CI's `matrix-records` job. It needs no build and no app, proven by running it in an empty directory, which is how a static import of the built CLI was caught.
|
|
97
103
|
- [ ] false-green scorecard as a standing gate against apps we did not write (#130)
|
|
98
104
|
- **Genuinely blocked here, not deferred by choice.** "Apps we did not write" is the whole content of the check, and this repo contains only apps we wrote. It belongs to the fixtures repo (Tier 2) and needs `FIXTURES_DISPATCH_TOKEN`.
|