@reticlehq/server 2.9.0 → 2.11.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 +64 -26
- package/dist/capsule/capsule-store.d.ts +2 -2
- package/dist/cli/cli-parse.d.ts +1 -1
- package/dist/cli/cli-parse.js +1 -1
- package/dist/cli/cli-verify.js +1 -1
- package/dist/crawl/crawl.d.ts +7 -2
- package/dist/crawl/crawl.js +52 -9
- package/dist/dev/stale-issue-guard.d.ts +43 -0
- package/dist/dev/stale-issue-guard.js +75 -0
- package/dist/domain/domain-tools.js +3 -2
- package/dist/ee/audit-log.js +2 -2
- package/dist/events/body-failures.d.ts +2 -2
- package/dist/events/contradiction-folds.d.ts +45 -0
- package/dist/events/contradiction-folds.js +42 -0
- package/dist/events/contradictions.d.ts +19 -1
- package/dist/events/contradictions.js +17 -2
- package/dist/events/echo-mismatch.d.ts +2 -2
- package/dist/events/predicate-asks.d.ts +17 -0
- package/dist/events/predicate-asks.js +26 -0
- package/dist/events/predicate-eval.js +211 -24
- package/dist/events/predicate-parse.js +9 -3
- package/dist/events/predicate-schema.d.ts +18 -0
- package/dist/events/predicate-schema.js +55 -0
- package/dist/events/stale-response.d.ts +2 -3
- package/dist/events/stale-response.js +49 -2
- package/dist/events/unit-mismatch.d.ts +2 -2
- package/dist/flows/flow-tools.js +34 -5
- package/dist/flows/predicate-to-expect.js +8 -0
- package/dist/flows/verify-change-tools.js +6 -2
- package/dist/honesty/blind-spots.d.ts +9 -0
- package/dist/honesty/blind-spots.js +17 -4
- package/dist/honesty/gap-ledger.d.ts +52 -0
- package/dist/honesty/gap-ledger.js +63 -0
- package/dist/honesty/instrumentation-gaps.d.ts +55 -0
- package/dist/honesty/instrumentation-gaps.js +21 -0
- package/dist/honesty/observability.d.ts +37 -0
- package/dist/honesty/observability.js +40 -0
- package/dist/honesty/verified.js +16 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.js +57 -0
- package/dist/init/mcp.d.ts +1 -2
- package/dist/init/mcp.js +4 -1
- package/dist/init/node-io.js +3 -0
- package/dist/init/run.d.ts +18 -0
- package/dist/init/run.js +34 -3
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +33 -10
- package/dist/journal/envelope.d.ts +24 -24
- package/dist/license/license.d.ts +76 -5
- package/dist/license/license.js +93 -29
- package/dist/mcp/mcp-proxy.d.ts +2 -27
- package/dist/mcp/mcp-proxy.js +4 -88
- package/dist/mcp/mcp.d.ts +18 -2
- package/dist/mcp/mcp.js +31 -10
- package/dist/mcp/proxy-daemon-probe.d.ts +8 -0
- package/dist/mcp/proxy-daemon-probe.js +48 -0
- package/dist/mcp/proxy-handshake.js +1 -1
- package/dist/mcp/server-instructions.js +2 -2
- package/dist/mcp/sse-frame-parser.d.ts +19 -0
- package/dist/mcp/sse-frame-parser.js +41 -0
- package/dist/project/artifact-root.d.ts +83 -0
- package/dist/project/artifact-root.js +112 -0
- package/dist/project/memory-fs.d.ts +9 -0
- package/dist/project/memory-fs.js +88 -0
- package/dist/project/project-store.d.ts +17 -0
- package/dist/project/project-store.js +33 -0
- package/dist/project/remember-project.d.ts +24 -0
- package/dist/project/remember-project.js +40 -0
- package/dist/project/session-root.d.ts +15 -0
- package/dist/project/session-root.js +31 -0
- package/dist/runs/run-tools.js +2 -1
- package/dist/session/lease-availability.d.ts +40 -0
- package/dist/session/lease-availability.js +43 -0
- package/dist/session/no-session-diagnosis.d.ts +17 -0
- package/dist/session/no-session-diagnosis.js +41 -8
- package/dist/session/no-session-watch.js +7 -0
- package/dist/session/session-readiness.js +1 -1
- package/dist/session/session.d.ts +9 -0
- package/dist/session/session.js +9 -0
- package/dist/session/stall-clock.d.ts +18 -0
- package/dist/session/stall-clock.js +48 -0
- package/dist/telemetry/daemon-telemetry.js +16 -22
- package/dist/telemetry/license-activation.d.ts +48 -0
- package/dist/telemetry/license-activation.js +57 -0
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +12 -3
- package/dist/telemetry/telemetry.js +34 -5
- package/dist/tools/act-tools.js +34 -4
- package/dist/tools/assert-verdict.d.ts +2 -0
- package/dist/tools/assert-verdict.js +24 -1
- package/dist/tools/contract-tools.js +5 -3
- package/dist/tools/coverage-tools.js +33 -0
- package/dist/tools/dynamic-tools.js +1 -1
- package/dist/tools/error-recovery.js +1 -1
- package/dist/tools/feedback-tools.js +31 -6
- package/dist/tools/invoke-tool.d.ts +1 -1
- package/dist/tools/invoke-tool.js +10 -5
- package/dist/tools/observe-tools.js +8 -2
- package/dist/tools/tool-kit.d.ts +46 -4
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- package/dist/tools/tool-surface.js +2 -3
- package/dist/tools/tools.js +16 -1
- package/docs/README.md +1 -0
- package/docs/agent-cheatsheet.md +2 -2
- package/docs/capabilities.mdx +4 -4
- package/docs/cli/hunt.mdx +2 -2
- package/docs/deploy-checks.md +2 -2
- package/docs/docs.json +2 -2
- package/docs/enterprise.md +9 -3
- package/docs/flows.md +4 -4
- package/docs/gates.md +1 -1
- package/docs/license-activation.mdx +145 -0
- package/docs/recipes.mdx +2 -2
- package/docs/telemetry-contract.md +18 -12
- package/docs/telemetry-events.mdx +1 -34
- package/docs/telemetry.md +0 -1
- package/docs/tools-overview.mdx +1 -1
- package/docs/tools-session-and-feedback.mdx +5 -0
- package/docs/tools-tools-and-run.mdx +15 -15
- package/docs/usage.md +3 -3
- package/package.json +2 -2
- package/dist/telemetry/instrumentation-stall.d.ts +0 -50
- package/dist/telemetry/instrumentation-stall.js +0 -90
package/docs/capabilities.mdx
CHANGED
|
@@ -15,7 +15,7 @@ These work through the always-on SDK, in any connected session.
|
|
|
15
15
|
### Coverage: what you have not driven yet
|
|
16
16
|
|
|
17
17
|
```json
|
|
18
|
-
{ "tool": "
|
|
18
|
+
{ "tool": "reticle_verify", "args": { "action": "coverage" } }
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
```json
|
|
@@ -90,7 +90,7 @@ Freeze the clock, advance it by exactly the interval, assert the consequence. No
|
|
|
90
90
|
### Crawl: click everything, report anomalies
|
|
91
91
|
|
|
92
92
|
```json
|
|
93
|
-
{ "tool": "
|
|
93
|
+
{ "tool": "reticle_verify", "args": { "action": "crawl", "maxSteps": 6 } }
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
```json
|
|
@@ -327,7 +327,7 @@ The loop closer, and the reason Reticle is not only an interactive tool.
|
|
|
327
327
|
{ "tool": "reticle_record", "args": { "action": "start", "recordingName": "ship-a-deploy" } }
|
|
328
328
|
{ "tool": "reticle_record", "args": { "action": "stop", "recordingName": "ship-a-deploy" } }
|
|
329
329
|
{ "tool": "reticle_flow_save", "args": { "flowName": "ship-a-deploy" } }
|
|
330
|
-
{ "tool": "
|
|
330
|
+
{ "tool": "reticle_verify", "args": { "action": "flows" } }
|
|
331
331
|
```
|
|
332
332
|
|
|
333
333
|
<Warning>
|
|
@@ -396,7 +396,7 @@ Nine hundred and fifty-three bytes for 445 recorded events. `timeline_omitted` h
|
|
|
396
396
|
|
|
397
397
|
Reticle grades the flow as you save it, and tells you when you have just created a test that cannot fail. Take the warning seriously: an assertion-free flow replays green whatever the app does.
|
|
398
398
|
|
|
399
|
-
A replay against a drifted app names what moved and where. This is `
|
|
399
|
+
A replay against a drifted app names what moved and where. This is `reticle_verify {action:"flows"}` reporting the same flow after the app changed underneath it:
|
|
400
400
|
|
|
401
401
|
```json
|
|
402
402
|
{
|
package/docs/cli/hunt.mdx
CHANGED
|
@@ -4,7 +4,7 @@ description: Aggregate many crawl reports into one false-green rate across alrea
|
|
|
4
4
|
icon: crosshairs
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
`reticle hunt` is arithmetic, not driving. You point it at a directory of crawl reports (one JSON file per checkout, each produced by `
|
|
7
|
+
`reticle hunt` is arithmetic, not driving. You point it at a directory of crawl reports (one JSON file per checkout, each produced by `reticle_verify {action:"crawl"}`) and it turns the pile into the one number the core claim needs: across N already-merged, already-green changes, how many carried a candidate false green?
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx @reticlehq/server hunt <dir-of-crawl-reports>
|
|
@@ -66,7 +66,7 @@ That path exits `1`.
|
|
|
66
66
|
```bash
|
|
67
67
|
for sha in $(git log --format=%H -n 30 main); do
|
|
68
68
|
git checkout "$sha" && npm run dev &
|
|
69
|
-
# drive
|
|
69
|
+
# drive reticle_verify {action:"crawl"} over the app, write the JSON:
|
|
70
70
|
# .../reports/$sha.json
|
|
71
71
|
done
|
|
72
72
|
npx @reticlehq/server hunt ./reports
|
package/docs/deploy-checks.md
CHANGED
|
@@ -76,7 +76,7 @@ Use both: `gate` in the agent's Stop hook (see `agent-cheatsheet`), `verify` at
|
|
|
76
76
|
## Honest limits
|
|
77
77
|
|
|
78
78
|
- **`verify` needs one connected session.** If several tabs of the app are open against the same daemon it refuses rather than guessing which to drive.
|
|
79
|
-
- **It replays flows sequentially against one tab**, so flows must not depend on each other's leftover state (a flow that logs in contaminates the next one). Author self-contained flows, or use `
|
|
79
|
+
- **It replays flows sequentially against one tab**, so flows must not depend on each other's leftover state (a flow that logs in contaminates the next one). Author self-contained flows, or use `reticle_verify { action: "flows", parallel }`, which gives each flow an isolated context.
|
|
80
80
|
- **No saved flows means nothing to verify.** `verify` fails rather than reporting a vacuous pass.
|
|
81
81
|
|
|
82
82
|
## FAQ
|
|
@@ -91,7 +91,7 @@ Both. For a non-loopback origin Reticle injects `reticle.connect()` with a one-t
|
|
|
91
91
|
|
|
92
92
|
### One of my flows logs in and the next one starts logged in. How do I stop that?
|
|
93
93
|
|
|
94
|
-
`verify` replays flows sequentially against one tab, so leftover state carries. Either author each flow to be self-contained, or use `
|
|
94
|
+
`verify` replays flows sequentially against one tab, so leftover state carries. Either author each flow to be self-contained, or use `reticle_verify { action: "flows", parallel: true }`, which gives every flow its own isolated browser context with its own cookies and storage.
|
|
95
95
|
|
|
96
96
|
### Should I run reticle gate as well as reticle verify?
|
|
97
97
|
|
package/docs/docs.json
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"navigation": {
|
|
33
33
|
"versions": [
|
|
34
34
|
{
|
|
35
|
-
"version": "2.
|
|
35
|
+
"version": "2.11.0",
|
|
36
36
|
"default": true,
|
|
37
37
|
"tabs": [
|
|
38
38
|
{
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
"group": "Trust & licensing",
|
|
186
|
-
"pages": ["telemetry", "enterprise"]
|
|
186
|
+
"pages": ["telemetry", "license-activation", "enterprise"]
|
|
187
187
|
}
|
|
188
188
|
]
|
|
189
189
|
},
|
package/docs/enterprise.md
CHANGED
|
@@ -8,25 +8,31 @@ Enterprise features ship inside the open package and are unlocked by a signed li
|
|
|
8
8
|
|
|
9
9
|
> Premium access (how you get + activate it), what's gated, the security/data-handling posture, and the licensing model. Integration mechanics live in [`platform-integration.md`](./platform-integration.md).
|
|
10
10
|
|
|
11
|
+
> **Already have a key and just need to switch it on?** [Activating a license key](./license-activation) is the step-by-step version: where to set it on each platform, the editor and CI cases, how to read every status, and what to do when it does not work.
|
|
12
|
+
|
|
11
13
|
## How premium access works (offline, no phone-home)
|
|
12
14
|
|
|
13
15
|
Enterprise (`ee/`) features ship **inside the open package**; they're source-available (free for development, testing, and evaluation). A **license key activates them in production**. Activation is verified locally with Ed25519; nothing about your usage ever leaves your machine.
|
|
14
16
|
|
|
15
17
|
**The flow, end to end:**
|
|
16
18
|
|
|
17
|
-
1. **Buy.** Contact **[hey@reticle.sh](mailto:hey@reticle.sh)**; we issue you a signed license key (org, plan, expiry, feature set).
|
|
19
|
+
1. **Buy.** Contact **[hey@reticle.sh](mailto:hey@reticle.sh)**; we issue you a signed license key (license id, org, plan, expiry, feature set). The **license id** is the stable handle on your subscription: it survives a rename, and it is what a renewal continues.
|
|
18
20
|
2. **Install.** Set it on the machine running the Reticle server:
|
|
19
21
|
```bash
|
|
20
22
|
export RETICLE_LICENSE_KEY="<your key>"
|
|
21
23
|
# the release already bakes the issuer public key (RETICLE_LICENSE_PUBLIC_KEY)
|
|
22
24
|
```
|
|
23
25
|
3. **Verify.** `reticle license` shows your status:
|
|
26
|
+
|
|
24
27
|
```
|
|
25
28
|
active licensed to Acme Corp (enterprise), expires 2027-06-20 · features: sso, audit
|
|
26
29
|
eval evaluation mode: enterprise features run free (no issuer key configured)
|
|
27
|
-
missing set RETICLE_LICENSE_KEY to activate enterprise features in production
|
|
28
|
-
expired
|
|
30
|
+
missing set RETICLE_LICENSE_KEY to activate enterprise features in production. Request a key from hey@reticle.sh
|
|
31
|
+
expired license expired. Renew with hey@reticle.sh to keep using enterprise features
|
|
29
32
|
```
|
|
33
|
+
|
|
34
|
+
Every status reports `contact`, so a reader who is stuck always has somebody to write to. An unlicensed status also reports `gated`, what a licence unlocks in the build you are running, so the command answers "what would this buy me, and how do I get one" without anybody having to find this page first. It is derived from the features the gate actually enforces, so it names what your build will really refuse rather than a roadmap. An `active` licence does not report it: that reader has already bought, and listing feature names at them answers nothing they asked.
|
|
35
|
+
|
|
30
36
|
4. **Unlock.** Enterprise features now run in production; without a valid key they refuse to run there (a clear error, never a silent half-feature). In eval/dev they always run free.
|
|
31
37
|
5. **Renew.** Keys carry an expiry; `reticle license` warns before it lapses.
|
|
32
38
|
|
package/docs/flows.md
CHANGED
|
@@ -115,17 +115,17 @@ This is the feedback a human reviewer used to give, made machine-actionable, so
|
|
|
115
115
|
|
|
116
116
|
## Verify the whole suite in one call
|
|
117
117
|
|
|
118
|
-
`
|
|
118
|
+
`reticle_verify {action:"flows"}` 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:
|
|
119
119
|
|
|
120
120
|
```jsonc
|
|
121
|
-
|
|
121
|
+
reticle_verify({ action: "flows" })
|
|
122
122
|
// → { status: "fail", total: 4, passed: 3, failed: 1,
|
|
123
123
|
// summary: "3/4 flows pass, 1 needs attention: ship-deploy",
|
|
124
124
|
// failures: [{ flow: "ship-deploy", verdict: "drift",
|
|
125
125
|
// whatChanged: "...", whereInSource: "src/...:NN", nextAction: "..." }] }
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
Passing flows are counted; only failures carry detail (token-cheap). Build → `
|
|
128
|
+
Passing flows are counted; only failures carry detail (token-cheap). Build → `reticle_verify {action:"flows"}` → fix from each failure's `nextAction` → repeat: the autonomous regression loop.
|
|
129
129
|
|
|
130
130
|
## Self-healing: the agent maintains the flow
|
|
131
131
|
|
|
@@ -165,7 +165,7 @@ With `apply: false` the flow file is **never modified**; you get the proposed di
|
|
|
165
165
|
| `reticle_flow {action:"list"}` | `{}` | flows on disk |
|
|
166
166
|
| `reticle_flow {action:"load"}` | `{ flowName }` | the flow JSON |
|
|
167
167
|
| `reticle_flow_replay` | `{ flowName }` | `{ status, steps, decision? }` (decision on drift/fail) |
|
|
168
|
-
| `
|
|
168
|
+
| `reticle_verify {action:"flows"}` | `{ names?, sessionId? }` | suite verdict `{ status, passed, failed, failures[] }` |
|
|
169
169
|
| `reticle_flow_heal` | `{ flowName, apply? }` | propose / apply nearest-match rebind |
|
|
170
170
|
| `reticle_annotate` | `{ kind, … }` | compile a structured annotation into the flow |
|
|
171
171
|
|
package/docs/gates.md
CHANGED
|
@@ -31,7 +31,7 @@ Every gate below was executed end to end against `main` on **2026-08-12** (macOS
|
|
|
31
31
|
| `pnpm matrix:compat --only cursor` | ✅ 4/4 | ~10s |
|
|
32
32
|
| `pnpm bench` | ✅ 10/10 (**was failing before this sweep**, see below) | 279s |
|
|
33
33
|
|
|
34
|
-
**`pnpm bench` was broken and nobody knew.** `suite-rre.mjs` recorded four flows that asserted no observable consequence and then demanded a `pass` verdict from
|
|
34
|
+
**`pnpm bench` was broken and nobody knew.** `suite-rre.mjs` recorded four flows that asserted no observable consequence and then demanded a `pass` verdict from whole-suite replay, which correctly grades an assertion-free suite `unverifiable`. The product got more honest about false greens; the benchmark measuring it did not follow, so the whole run aborted at script 9 of 10 and `replay-determinism` never ran at all. Each flow now carries a success oracle. This is the failure mode `bench/` is most exposed to: **nothing in CI runs it**, so it can only rot silently.
|
|
35
35
|
|
|
36
36
|
`pnpm gate:install` (~15 min) and the Windows / Rust jobs were **not** run in this sweep; they are CI-only or network-bound. They are green on `main` per the last CI run, which is a weaker claim than every row above, and is stated that way on purpose.
|
|
37
37
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Activating a license key'
|
|
3
|
+
description: 'Set your key, confirm it worked, and fix it when it did not. Everything an activation needs, with no account and no call.'
|
|
4
|
+
icon: key
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
If your organisation has a Reticle license key, this page is everything you need to put it in place. There is no account to create, no login, and no call with us required.
|
|
8
|
+
|
|
9
|
+
Do not have a key? Write to [hey@reticle.sh](mailto:hey@reticle.sh).
|
|
10
|
+
|
|
11
|
+
## One key per organisation
|
|
12
|
+
|
|
13
|
+
A key covers your whole organisation. Every developer and every machine uses the same one, so share it the way you would share any other build-time credential: your secrets manager, your onboarding doc, your CI configuration.
|
|
14
|
+
|
|
15
|
+
## Set it
|
|
16
|
+
|
|
17
|
+
Reticle reads one environment variable, `RETICLE_LICENSE_KEY`. Set it everywhere Reticle runs, which is usually more than one place.
|
|
18
|
+
|
|
19
|
+
<CodeGroup>
|
|
20
|
+
|
|
21
|
+
```bash macOS / Linux
|
|
22
|
+
# In ~/.zshrc, ~/.bashrc, or your team's equivalent.
|
|
23
|
+
export RETICLE_LICENSE_KEY="your-key-here"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```powershell Windows
|
|
27
|
+
[Environment]::SetEnvironmentVariable('RETICLE_LICENSE_KEY', 'your-key-here', 'User')
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```yaml GitHub Actions
|
|
31
|
+
env:
|
|
32
|
+
RETICLE_LICENSE_KEY: ${{ secrets.RETICLE_LICENSE_KEY }}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash Docker
|
|
36
|
+
docker run -e RETICLE_LICENSE_KEY="your-key-here" your-image
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
</CodeGroup>
|
|
40
|
+
|
|
41
|
+
An export only affects shells started after it, so open a new terminal or `source` your profile.
|
|
42
|
+
|
|
43
|
+
### Editors and agents that launch Reticle themselves
|
|
44
|
+
|
|
45
|
+
<Warning>
|
|
46
|
+
This is the step teams most often miss. Claude Desktop, Cursor and similar tools launch from the
|
|
47
|
+
desktop rather than from a terminal, so they never read your shell profile. An `export` that
|
|
48
|
+
plainly works in your terminal is invisible to them, and Reticle reports itself unlicensed.
|
|
49
|
+
</Warning>
|
|
50
|
+
|
|
51
|
+
Put the key in the MCP server's own `env` block instead, then restart the editor. Configuration is read at startup.
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"reticle": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["@reticlehq/server", "mcp"],
|
|
59
|
+
"env": {
|
|
60
|
+
"RETICLE_LICENSE_KEY": "your-key-here"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Confirm it worked
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx @reticlehq/server license
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You do not need `NODE_ENV` set, a project, a dev server, or a running app. This reads the key and nothing else.
|
|
74
|
+
|
|
75
|
+
| `status` | What it means | What to do |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `active` | Valid and in date. | Nothing. You are done. |
|
|
78
|
+
| `missing` | Reticle found no key in its environment. | The variable is not visible to this process. See below. |
|
|
79
|
+
| `expired` | The key was valid and its term has ended. | Contact us to renew. |
|
|
80
|
+
| `invalid` | The key was not accepted. | Usually a copy or paste problem. See below. |
|
|
81
|
+
| `eval` | This build has no licensing configured. | You are on a build older than licensing. Update. |
|
|
82
|
+
|
|
83
|
+
## Over time
|
|
84
|
+
|
|
85
|
+
Your key carries an expiry, shown in the `license` output. A renewal is a new key for the same organisation: replace the value everywhere you set it, and nothing else changes.
|
|
86
|
+
|
|
87
|
+
Nothing breaks without warning. An expired key reports `expired` rather than failing silently, and you can check at any time with the command above.
|
|
88
|
+
|
|
89
|
+
Activation is **offline**. Reticle verifies your key cryptographically on your own machine, so there is no license server to reach. It works behind a firewall, on an air-gapped machine, and during any outage on our side.
|
|
90
|
+
|
|
91
|
+
## When it does not work
|
|
92
|
+
|
|
93
|
+
<AccordionGroup>
|
|
94
|
+
|
|
95
|
+
<Accordion title="It says missing, but the variable is definitely set">
|
|
96
|
+
The commonest cause by a distance is that Reticle is being launched by something that does not
|
|
97
|
+
inherit your shell environment: an editor, a desktop agent, a systemd unit, a container. Run
|
|
98
|
+
`echo $RETICLE_LICENSE_KEY` in the same context Reticle runs in, not just in your own terminal. If
|
|
99
|
+
an editor launches it, use the MCP `env` block above.
|
|
100
|
+
|
|
101
|
+
Second commonest: the variable was set in a shell that was already open. Environment variables are read when a process starts, so open a new terminal and restart anything long-running.
|
|
102
|
+
|
|
103
|
+
</Accordion>
|
|
104
|
+
|
|
105
|
+
<Accordion title="It says invalid">
|
|
106
|
+
The key changed between us sending it and Reticle reading it. Almost always one of:
|
|
107
|
+
|
|
108
|
+
- truncated, most often by a line wrap when pasted into a config file or a chat client
|
|
109
|
+
- a character dropped or added while copying by hand
|
|
110
|
+
- quoted in a way that swallowed a character
|
|
111
|
+
|
|
112
|
+
Surrounding whitespace and a trailing newline are tolerated, so neither is the cause. Copy the key again in one piece. If it still reports `invalid`, write to us and we will re-issue it.
|
|
113
|
+
|
|
114
|
+
</Accordion>
|
|
115
|
+
|
|
116
|
+
<Accordion title="command not found: reticle">
|
|
117
|
+
Use the `npx @reticlehq/server ...` form shown on this page. It runs without a global install, and
|
|
118
|
+
it is the form we support.
|
|
119
|
+
</Accordion>
|
|
120
|
+
|
|
121
|
+
</AccordionGroup>
|
|
122
|
+
|
|
123
|
+
## What a licensed deployment reports
|
|
124
|
+
|
|
125
|
+
Stated plainly, because a security review will ask.
|
|
126
|
+
|
|
127
|
+
Activation itself makes **no network request**. Your key is verified on your machine with public key cryptography.
|
|
128
|
+
|
|
129
|
+
Separately, Reticle sends anonymous usage metrics, as it does for every user. On a licensed deployment these carry your **license id**, an opaque identifier that means nothing without our own records, so we can tell your organisation's usage apart from everyone else's and know when to talk to you about renewal.
|
|
130
|
+
|
|
131
|
+
Never sent, licensed or not: your source code, your application's data, your users' data, URLs, selectors, page content, file contents, or your organisation's name.
|
|
132
|
+
|
|
133
|
+
The kill switch has no exception for licensed installations:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
export RETICLE_TELEMETRY=0
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`DO_NOT_TRACK=1` is honoured identically. If your policy forbids outbound telemetry, set it. Your activation is unaffected, because activation never needed the network. To keep your own usage data without sending it, `RETICLE_TELEMETRY_FILE=/path/to/file.jsonl` records the same stream locally and sends nothing.
|
|
140
|
+
|
|
141
|
+
## Getting help
|
|
142
|
+
|
|
143
|
+
[hey@reticle.sh](mailto:hey@reticle.sh) for activation problems, renewals and re-issues.
|
|
144
|
+
|
|
145
|
+
Include the output of `npx @reticlehq/server license`. It carries your license id and no secrets, and it usually tells us the answer immediately.
|
package/docs/recipes.mdx
CHANGED
|
@@ -179,7 +179,7 @@ Confirm it explicitly when you mean it:
|
|
|
179
179
|
## "Did my change break anything else?"
|
|
180
180
|
|
|
181
181
|
```json
|
|
182
|
-
{ "tool": "
|
|
182
|
+
{ "tool": "reticle_verify", "args": { "action": "change", "since": "HEAD~1" } }
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
Give it the files you edited, or a git ref. It works out which saved flows cover them and replays only those.
|
|
@@ -246,7 +246,7 @@ Under a driven browser the same call answers `{ "applied": true, "count": 1 }`.
|
|
|
246
246
|
Record it once, replay it forever:
|
|
247
247
|
|
|
248
248
|
```json
|
|
249
|
-
{ "tool": "
|
|
249
|
+
{ "tool": "reticle_verify", "args": { "action": "flows", "names": ["docs-check"] } }
|
|
250
250
|
```
|
|
251
251
|
|
|
252
252
|
Omit `names` to replay every saved flow. Either way you get one consolidated verdict, and only the failures carry detail:
|
|
@@ -103,7 +103,6 @@ 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 |
|
|
107
106
|
| `mcp_connection_lost` | the proxy lost its daemon | **the transport-stability metric.** The disconnect that makes a user reopen `/mcp` is invisible without it |
|
|
108
107
|
| `init_completed` | `reticle init` finished | does install actually work, outside the fixtures gate |
|
|
109
108
|
| `bug_found` | a defect was detected in the app under test | the value delivered, as opposed to the work done |
|
|
@@ -117,17 +116,7 @@ Nothing measured the second. `daemon_started` and `mcp_client_connected` describ
|
|
|
117
116
|
|
|
118
117
|
`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`.
|
|
119
118
|
|
|
120
|
-
|
|
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.
|
|
119
|
+
The failure side is a **set difference**, not an event: `daemon_started` minus `app_instrumented`, joined on `sessionId`. There used to be an `instrumentation_stalled` event for it, and it was removed because the derivation is strictly more complete. The event could only fire on a flush tick or a graceful shutdown, so a killed daemon never produced one and landed in the absence anyway, while the set difference catches it. Two ways to compute one number, disagreeing with each other, is worse than one way.
|
|
131
120
|
|
|
132
121
|
## Sessions: `daemon_stopped` vs `session_progress`
|
|
133
122
|
|
|
@@ -156,11 +145,28 @@ Four counters and one flag were added because the data could not answer question
|
|
|
156
145
|
| `bug.attribution` | `bug_found` | `app` \| `request` \| `reticle`: whose fault the defect was. **Absent means unclassified**, never `app`. See below. |
|
|
157
146
|
| `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. |
|
|
158
147
|
| `installSource` | `reticle_installed`, `init_completed` | WHICH published route brought this install in, from core's closed `InstallSource`. Read from one self-declared marker (`RETICLE_INSTALL_SOURCE`) and NEVER inferred, so `unknown` is expected to dominate until every channel's own copy of the install command carries it. See below. |
|
|
148
|
+
| `licenseId` / `licenseStatus` | every event, on a licensed build | Enterprise activation. `licenseStatus` is core's closed `LicenseActivation` (`active` \| `missing` \| `invalid` \| `expired`) and rides through the FAILURE states too, which is what makes a lapse distinguishable from a churn. `licenseId` is present only while a key verifies, so on identity alone a customer whose key expired and one who left are the same silence. `licenseId` is an opaque uuid that resolves to a company only against the issuance ledger held locally, so the analytics backend never holds a customer list. The organisation NAME is never sent. **All three absent on a build with no issuer key baked**, which is every OSS install, so absence means "not a licensed build" and costs nothing to say. See below. |
|
|
159
149
|
| `init.confirmation` | `init_completed` | what `init` SAW after writing, from core's closed `InitConfirmation`: `connected` (an app carrying the SDK reached the daemon while it watched, and it is the only value that means installed) \| `no_daemon` (nothing was listening, so no session could arrive) \| `no_session` (a daemon was up and no app connected inside the window). **Absent means it never looked**, which is every scripted run: `init` waits only when a human is at the terminal. Read absent as "not measured", never as a failure to connect. |
|
|
160
150
|
| `automation` | every event | ADVISORY hint that the run looks automated when `CI` does not say so, from core's closed `AutomationHint`: `container` \| `hosted_workspace` \| `no_tty`. `ci` reads one environment variable set only by a runner, so a gate driven from a cloud sandbox lands as a human at a machine. **Never a filter**: people work in containers, in Codespaces, and over ssh with no terminal, and dropping a row because this is set drops real users. Absent means nothing looked automated, not that a human was present. |
|
|
161
151
|
| `tzOffsetMin` | every event | minutes offset from UTC. One integer, no location. |
|
|
162
152
|
| `versionChange.nudged` | `version_changed` | an agent had been told about exactly this version recently, so the nudge plausibly caused the update. The daemon that nudges and the `reticle update` that acts are different processes, so a marker file joins them. |
|
|
163
153
|
|
|
154
|
+
## Enterprise activation: `licenseId`, `licenseStatus`
|
|
155
|
+
|
|
156
|
+
A licensed deployment reports which licence it is running under, so per-customer usage can be answered at all. Three things make this different from every other property here, and all three are deliberate:
|
|
157
|
+
|
|
158
|
+
**It is on every event, not on an activation event.** The questions a licensed customer generates are "how much is this org using it", "what is breaking for them", "did their key lapse", and every one of those is answered by an event that has nothing to do with licensing. A status riding only its own event would say a key verified once and nothing about the sessions it covered.
|
|
159
|
+
|
|
160
|
+
**Status is separate from identity, and reports failures.** `licenseId` exists only while a key verifies. If that were the whole signal, a lapse would look exactly like a departure, and the renewal conversation would start after the customer noticed rather than before. `licenseStatus` keeps arriving through `expired` and `invalid`. A mis-built release (production with no issuer key baked) reports `invalid` rather than staying quiet, because that one is ours to fix.
|
|
161
|
+
|
|
162
|
+
**The plan is not sent, and neither is the organisation name.** They are left out for different reasons and both are worth stating. The name is free text somebody typed when the key was signed, so rule 3 forbids it. The plan is merely redundant, which is the quieter reason and the easier one to lose sight of: the issuance ledger already holds it against this same id, so sending it would be a per-event cost, from every machine, forever, for something the join that resolves the id resolves at the same moment. `reticle license` still reports it locally, where it costs nothing.
|
|
163
|
+
|
|
164
|
+
**The organisation name never goes on the wire.** It is free text somebody typed when the key was signed, so it falls under rule 3. The id is opaque; the map from id to company is a local ledger. An analytics-side breach therefore cannot expose who is evaluating Reticle.
|
|
165
|
+
|
|
166
|
+
Resolution reads the EVENT's clock, not one captured at daemon start: sessions here run to eleven hours, and a key that expires mid-session has to start reporting `expired` from the event it expired on.
|
|
167
|
+
|
|
168
|
+
> **This changes what a licensed deployment sends, so it is a contract term, not a quiet addition.** The enterprise agreement has to say that licensed deployments report usage attributed to their licence id, and list these fields. `RETICLE_TELEMETRY=0` and `DO_NOT_TRACK` still switch it off exactly as they switch off everything else. There is no exception for licensed installs, and adding one would put a hole in the kill switch that a security review is entitled to find.
|
|
169
|
+
|
|
164
170
|
## Why they stopped: `tool_refused`
|
|
165
171
|
|
|
166
172
|
The refusal path computes a precise diagnosis, hands it to the agent as prose, and throws it away. So the biggest cohort in the funnel, the users who attach an agent and never drive, emitted nothing at all and was reachable only by subtracting two other numbers. Half of issue #172.
|
|
@@ -55,7 +55,7 @@ Plus `installSource`, which is not a block. It is resolved once per process and
|
|
|
55
55
|
|
|
56
56
|
`isSessionScoped(kind)` decides whether `sessionId` is attached. Twelve kinds are session-scoped:
|
|
57
57
|
|
|
58
|
-
`daemon_started`, `daemon_stopped`, `session_progress`, `mcp_client_connected`, `app_instrumented`, `
|
|
58
|
+
`daemon_started`, `daemon_stopped`, `session_progress`, `mcp_client_connected`, `app_instrumented`, `project_profiled`, `verification_completed`, `bug_found`, `tool_refused`, `runtime_crashed`, `feedback_submitted`.
|
|
59
59
|
|
|
60
60
|
`mcp_connection_lost` is deliberately **not** on that list even though it feels session-shaped. It fires from the proxy process rather than the daemon, so it carries no `sessionId` and cannot be joined to a session at all.
|
|
61
61
|
|
|
@@ -341,39 +341,6 @@ Fired **once per daemon run**, on the first connect only, so `daemon_started` to
|
|
|
341
341
|
|
|
342
342
|
---
|
|
343
343
|
|
|
344
|
-
## instrumentation_stalled
|
|
345
|
-
|
|
346
|
-
**Wire name:** `instrumentation_stalled`. **Emitted from:** `packages/server/src/telemetry/instrumentation-stall.ts:73`, called from `daemon-telemetry.ts:105`. **Session-scoped:** yes. **Payload block:** `stall`.
|
|
347
|
-
|
|
348
|
-
The failure side of `app_instrumented`, and the reason the funnel has a denominator at all. A daemon that runs for ten minutes without a single SDK-carrying page is an install that half worked, and until this event existed that outcome was pure silence: indistinguishable from a machine nobody touched.
|
|
349
|
-
|
|
350
|
-
| Field | Type | Meaning |
|
|
351
|
-
| --- | --- | --- |
|
|
352
|
-
| `initialized` | boolean | Has `init` run in this working directory (`readProjectId(cwd)` resolves). False means the daemon is running somewhere `init` never wrote a project id, which is usually a wrong-directory problem rather than a wiring one |
|
|
353
|
-
| `agentAttached` | boolean | Was an agent connected at the moment the check ran. **This is the field that decides whether the row is a defect.** False means somebody installed and walked away, which is not a bug. True means an agent was sitting there with tools it could not use, which is the failure the product actually has |
|
|
354
|
-
| `msWaited` | int | Milliseconds since the daemon started, from the clock marked at `daemon-telemetry.ts:84` |
|
|
355
|
-
|
|
356
|
-
Deliberately the same three facts as `app_instrumented`, with `msWaited` standing in for `msToFirstApp`, so the success and failure rows are directly comparable without reshaping either.
|
|
357
|
-
|
|
358
|
-
**When it fires:** on every flush tick, but only once all four guards at `instrumentation-stall.ts:64` pass: it has not already been reported this run, no app has ever connected, the clock was marked, and at least `STALL_AFTER_MS` (10 minutes) has elapsed. It is checked **before** the empty-window early return in `flushWindow`, which is not an accident: a stalled install produces an empty window by definition, so checking after the return would blind the event to the exact condition it exists to report.
|
|
359
|
-
|
|
360
|
-
**The question it answers:** of the installs that never reach a verdict, how many had an agent present and waiting? That is the difference between a distribution problem and a wiring problem, and they need opposite responses.
|
|
361
|
-
|
|
362
|
-
**What it is blind to:**
|
|
363
|
-
|
|
364
|
-
- **Any diagnosis.** It never says whether a dev server is listening, because that is a live probe, and probing a user's machine to enrich a metric is collecting for the metric's sake.
|
|
365
|
-
- **Slow starts.** A run that connects at minute eleven reports nothing at all, and neither does the recovery. The event answers "did this run stall", not "how long do installs take".
|
|
366
|
-
- **The page side.** Every real cause (a non-localhost gate, a port mismatch, a stale build, a dev server never restarted) lives where the daemon cannot see. Attributing a stall needs the SDK to report its own refusals, and that is not built.
|
|
367
|
-
- **Repeats.** Once per daemon run, so a user stuck for a week contributes one row per daemon start, not one per hour.
|
|
368
|
-
|
|
369
|
-
<Warning>
|
|
370
|
-
This kind has no live check in `apps/e2e/specs/telemetry-events-test.mjs`, and neither do
|
|
371
|
-
`session_progress` and `app_instrumented`. Those three are the funnel, and the funnel is the part
|
|
372
|
-
of the wire nothing fires end to end. Adding one is the highest-value thing left in this file.
|
|
373
|
-
</Warning>
|
|
374
|
-
|
|
375
|
-
---
|
|
376
|
-
|
|
377
344
|
## mcp_connection_lost
|
|
378
345
|
|
|
379
346
|
**Wire name:** `mcp_connection_lost`. **Emitted from:** `packages/server/src/mcp/mcp-outage.ts:53`. **Session-scoped:** no. **Payload block:** `outage`.
|
package/docs/telemetry.md
CHANGED
|
@@ -31,7 +31,6 @@ Eighteen kinds of events, each a single small JSON object. This is the whole lis
|
|
|
31
31
|
| `daemon_stopped` | The local daemon stops | A summary of the session; see below |
|
|
32
32
|
| `session_progress` | Periodically, from a daemon that is still running | The same summary as `daemon_stopped`, marked `final: false`. It exists so a long session is not invisible until it exits |
|
|
33
33
|
| `app_instrumented` | The first time your app's SDK connects in a given daemon run | Nothing beyond the common fields. It is the second half of the install funnel: registering the MCP server is one thing, getting the SDK into a running page is another |
|
|
34
|
-
| `instrumentation_stalled` | Ten minutes into a daemon run in which your app has still never connected | The same three facts as `app_instrumented`, with how long it waited instead of how long it took. It fires at most once per daemon run, and a run that starts slowly and then works reports nothing at all. It carries no diagnosis: whether a dev server is listening would need a live probe, and probing your machine to enrich a metric is collecting for the metric's sake |
|
|
35
34
|
| `verification_completed` | A verification produces a verdict | Whether it passed, whether Reticle refused to call a passing check verified, and **why** the verdict came out that way; see below |
|
|
36
35
|
| `project_profiled` | Once per daemon start | The shape of the project; see below |
|
|
37
36
|
| `version_changed` | You update or roll back | The two version numbers, and which direction |
|
package/docs/tools-overview.mdx
CHANGED
|
@@ -75,7 +75,7 @@ Direct `reticle_network` and `reticle_console` stay advertised even though `reti
|
|
|
75
75
|
## Reaching the other 29
|
|
76
76
|
|
|
77
77
|
```json
|
|
78
|
-
{ "tool": "
|
|
78
|
+
{ "tool": "reticle_verify", "args": { "action": "flows" } }
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
`reticle_run` takes a tool name and its arguments. `reticle_tools` lists what's available and returns the full argument grammar for any of them, so your agent can discover a tool mid-task without you having pre-loaded its schema.
|
|
@@ -70,6 +70,11 @@ Reticle is built for agents, which creates an unusual problem: the primary user'
|
|
|
70
70
|
| `ambiguity` | The check ran but you could not tell what the answer meant |
|
|
71
71
|
| `feature_request` | You wished a capability existed |
|
|
72
72
|
| `improvement` | It works, but costs too many calls |
|
|
73
|
+
| `experience` | Something worked notably well (pair it with `rating`, 1-5) |
|
|
74
|
+
|
|
75
|
+
`experience` is the only kind here that is not a complaint, and that is why it exists. Every other row grows the corpus into a defect list, which says nothing about which parts are worth protecting when we change them. File it unprompted, when you actually notice it, and name the concrete moment: which call, what it saved you, what you would have done instead.
|
|
76
|
+
|
|
77
|
+
The `rating` is secondary to that text on purpose. A score produced because something asked for one is indistinguishable from a score that was earned, and once both sit in the same column neither means anything. The text is the part that can be acted on or quoted.
|
|
73
78
|
|
|
74
79
|
### It will tell you when it cannot send
|
|
75
80
|
|
|
@@ -18,21 +18,21 @@ Reticle ships 46 tools plus these two meta-tools. On the default profile your ag
|
|
|
18
18
|
|
|
19
19
|
Returns every tool with a one-line summary. A sample of what lives in the cold tail:
|
|
20
20
|
|
|
21
|
-
| Tool
|
|
22
|
-
|
|
|
23
|
-
| `reticle_capabilities`
|
|
24
|
-
| `
|
|
25
|
-
| `reticle_screenshot`
|
|
26
|
-
| `reticle_visual_diff`
|
|
27
|
-
| `reticle_network_mock`
|
|
28
|
-
| `reticle_viewport`
|
|
29
|
-
| `
|
|
30
|
-
| `reticle_clock`
|
|
31
|
-
| `reticle_storage`
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `reticle_reconcile`
|
|
35
|
-
| `reticle_scroll_to`
|
|
21
|
+
| Tool | What it does |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `reticle_capabilities` | The app's advertised testable surface, testids, signals, stores, flows |
|
|
24
|
+
| `reticle_verify` (`flows`) | Replay every saved flow, return one consolidated suite verdict |
|
|
25
|
+
| `reticle_screenshot` | Pixel screenshot of the driven page, saved as a visual baseline |
|
|
26
|
+
| `reticle_visual_diff` | Perceptually diff the live page against that baseline |
|
|
27
|
+
| `reticle_network_mock` | Force a 500, go offline, or delay a response |
|
|
28
|
+
| `reticle_viewport` | Pin the viewport so screenshots are reproducible across machines |
|
|
29
|
+
| `reticle_verify` (`crawl`) | Click every reachable control and report anomalies, with no script |
|
|
30
|
+
| `reticle_clock` | Freeze time or fast-forward timers, toasts, debounces, auto-dismiss |
|
|
31
|
+
| `reticle_storage` | localStorage, sessionStorage and readable cookies |
|
|
32
|
+
| `reticle_verify` (`coverage`) | Which controls you have driven this session, and which you have not |
|
|
33
|
+
| `reticle_verify` (`change`) | Give it the files you edited; it replays the flows that cover them |
|
|
34
|
+
| `reticle_reconcile` | Compare what the API returned against what the page actually renders |
|
|
35
|
+
| `reticle_scroll_to` | Find a row in a virtualised list that has not rendered yet |
|
|
36
36
|
|
|
37
37
|
The response also tells you which surface is active:
|
|
38
38
|
|
package/docs/usage.md
CHANGED
|
@@ -286,10 +286,10 @@ List interactive elements + console-error count for autonomous exploration. See
|
|
|
286
286
|
|
|
287
287
|
### Flows, recorder & self-healing (`.reticle/`)
|
|
288
288
|
|
|
289
|
-
`reticle_contract_save`, `reticle_flow_save` / `reticle_flow_save_recorded` / `reticle_flow {action:"list"}` / `reticle_flow {action:"load"}` / `reticle_flow_replay` / `
|
|
289
|
+
`reticle_contract_save`, `reticle_flow_save` / `reticle_flow_save_recorded` / `reticle_flow {action:"list"}` / `reticle_flow {action:"load"}` / `reticle_flow_replay` / `reticle_verify {action:"flows"}`, `reticle_flow_heal`, `reticle_annotate`: record once, replay forever (anchored on testid/signal, or on an auto-derived component/source anchor when there's no testid), with legible drift + self-heal. Full guide: [Flows, the recorder & self-healing](flows.md).
|
|
290
290
|
|
|
291
|
-
- **`
|
|
292
|
-
- **Decision envelope:** on a drift/fail, `reticle_flow_replay` (and each `
|
|
291
|
+
- **`reticle_verify({ action: "flows", names?, sessionId? })`** is the regression-suite call: it replays EVERY saved flow (or a subset) deterministically and returns one verdict `{ status, passed, failed, failures: [{ flow, verdict, whatChanged, whereInSource, nextAction }] }`. Passing flows are counted; only failures carry detail. Run it after any change: one call, no LLM per flow.
|
|
292
|
+
- **Decision envelope:** on a drift/fail, `reticle_flow_replay` (and each `reticle_verify {action:"flows"}` failure) returns the actionable fix: `whatChanged`, `whereInSource` (`file:line`), and a one-line `nextAction` (e.g. "rebind the anchor to 'new-deploy', or update the flow if intended").
|
|
293
293
|
|
|
294
294
|
### Human-in-the-loop control
|
|
295
295
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Reticle bridge + MCP server. Hosts the browser WS endpoint and exposes MCP tools to the coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
23
23
|
"ws": "^8.21.3",
|
|
24
24
|
"zod": "^3.24.1",
|
|
25
|
-
"@reticlehq/core": "2.
|
|
25
|
+
"@reticlehq/core": "2.11.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/pngjs": "^6.0.5",
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* When a daemon has waited long enough that no app is coming.
|
|
3
|
-
*
|
|
4
|
-
* `app_instrumented` fires on success and only on success, so the population that matters most —
|
|
5
|
-
* daemons that never get an app wired at all — exists in the data only as an absence. An absence is
|
|
6
|
-
* the one thing telemetry cannot interpret: it cannot separate "nothing was ever wired" from "the
|
|
7
|
-
* process was killed before it could say so" from "we never sent the event". The funnel breaks at
|
|
8
|
-
* exactly this step and nowhere else, which makes it the one silence worth converting into a signal.
|
|
9
|
-
*
|
|
10
|
-
* Once per daemon run, and never after an app has connected. A daemon that takes a while and then
|
|
11
|
-
* works reports nothing at all: the question is "did this run ever get an app", and a run that did
|
|
12
|
-
* is not stalled no matter how long it took.
|
|
13
|
-
*
|
|
14
|
-
* The threshold is a judgement, not a measurement, and it is deliberately generous. Someone running
|
|
15
|
-
* `init` and then going to make coffee has not hit a defect, and calling that a stall would drown
|
|
16
|
-
* the real signal in ordinary human latency.
|
|
17
|
-
*
|
|
18
|
-
* Read the result against `agentAttached`, which is what makes it actionable: a stalled daemon with
|
|
19
|
-
* no agent is somebody who installed and walked away, and is not a defect. A stalled daemon WITH an
|
|
20
|
-
* agent attached is the failure the product actually has — the tools are loaded, something is asking
|
|
21
|
-
* for them, and there is nothing on the other end to drive.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* How long a daemon may sit with no app before it counts as stalled.
|
|
25
|
-
*
|
|
26
|
-
* Ten minutes sits well past setup latency — reading a doc, restarting a dev server, opening a tab —
|
|
27
|
-
* and well short of the half-hour idle windows that dominate the field, so a stall is reported while
|
|
28
|
-
* the session is still alive rather than inferred from its corpse.
|
|
29
|
-
*/
|
|
30
|
-
export declare const STALL_AFTER_MS: number;
|
|
31
|
-
/** Start the clock. Called from the same place the instrumentation clock is marked. */
|
|
32
|
-
export declare function markStallClock(now: number): void;
|
|
33
|
-
export interface StallFacts {
|
|
34
|
-
/** Whether this project has a projectId stamped — i.e. `init` has run here. */
|
|
35
|
-
initialized: boolean;
|
|
36
|
-
/** Whether an MCP client is attached right now. */
|
|
37
|
-
agentAttached: boolean;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Report a stalled install, at most once per daemon run.
|
|
41
|
-
*
|
|
42
|
-
* Returns whether it emitted, so the caller's test can assert the once-only rule without reaching
|
|
43
|
-
* into the telemetry client. Best-effort like every other metric here: it must never be the reason
|
|
44
|
-
* a daemon misbehaves.
|
|
45
|
-
*/
|
|
46
|
-
export declare function reportInstrumentationStall(facts: StallFacts, now?: () => number, opts?: {
|
|
47
|
-
atShutdown?: boolean;
|
|
48
|
-
}): boolean;
|
|
49
|
-
/** Tests only. */
|
|
50
|
-
export declare function resetInstrumentationStall(): void;
|