@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.
Files changed (125) hide show
  1. package/SKILL.md +64 -26
  2. package/dist/capsule/capsule-store.d.ts +2 -2
  3. package/dist/cli/cli-parse.d.ts +1 -1
  4. package/dist/cli/cli-parse.js +1 -1
  5. package/dist/cli/cli-verify.js +1 -1
  6. package/dist/crawl/crawl.d.ts +7 -2
  7. package/dist/crawl/crawl.js +52 -9
  8. package/dist/dev/stale-issue-guard.d.ts +43 -0
  9. package/dist/dev/stale-issue-guard.js +75 -0
  10. package/dist/domain/domain-tools.js +3 -2
  11. package/dist/ee/audit-log.js +2 -2
  12. package/dist/events/body-failures.d.ts +2 -2
  13. package/dist/events/contradiction-folds.d.ts +45 -0
  14. package/dist/events/contradiction-folds.js +42 -0
  15. package/dist/events/contradictions.d.ts +19 -1
  16. package/dist/events/contradictions.js +17 -2
  17. package/dist/events/echo-mismatch.d.ts +2 -2
  18. package/dist/events/predicate-asks.d.ts +17 -0
  19. package/dist/events/predicate-asks.js +26 -0
  20. package/dist/events/predicate-eval.js +211 -24
  21. package/dist/events/predicate-parse.js +9 -3
  22. package/dist/events/predicate-schema.d.ts +18 -0
  23. package/dist/events/predicate-schema.js +55 -0
  24. package/dist/events/stale-response.d.ts +2 -3
  25. package/dist/events/stale-response.js +49 -2
  26. package/dist/events/unit-mismatch.d.ts +2 -2
  27. package/dist/flows/flow-tools.js +34 -5
  28. package/dist/flows/predicate-to-expect.js +8 -0
  29. package/dist/flows/verify-change-tools.js +6 -2
  30. package/dist/honesty/blind-spots.d.ts +9 -0
  31. package/dist/honesty/blind-spots.js +17 -4
  32. package/dist/honesty/gap-ledger.d.ts +52 -0
  33. package/dist/honesty/gap-ledger.js +63 -0
  34. package/dist/honesty/instrumentation-gaps.d.ts +55 -0
  35. package/dist/honesty/instrumentation-gaps.js +21 -0
  36. package/dist/honesty/observability.d.ts +37 -0
  37. package/dist/honesty/observability.js +40 -0
  38. package/dist/honesty/verified.js +16 -0
  39. package/dist/index.d.ts +18 -1
  40. package/dist/index.js +57 -0
  41. package/dist/init/mcp.d.ts +1 -2
  42. package/dist/init/mcp.js +4 -1
  43. package/dist/init/node-io.js +3 -0
  44. package/dist/init/run.d.ts +18 -0
  45. package/dist/init/run.js +34 -3
  46. package/dist/init/slash-command.d.ts +1 -1
  47. package/dist/init/slash-command.js +33 -10
  48. package/dist/journal/envelope.d.ts +24 -24
  49. package/dist/license/license.d.ts +76 -5
  50. package/dist/license/license.js +93 -29
  51. package/dist/mcp/mcp-proxy.d.ts +2 -27
  52. package/dist/mcp/mcp-proxy.js +4 -88
  53. package/dist/mcp/mcp.d.ts +18 -2
  54. package/dist/mcp/mcp.js +31 -10
  55. package/dist/mcp/proxy-daemon-probe.d.ts +8 -0
  56. package/dist/mcp/proxy-daemon-probe.js +48 -0
  57. package/dist/mcp/proxy-handshake.js +1 -1
  58. package/dist/mcp/server-instructions.js +2 -2
  59. package/dist/mcp/sse-frame-parser.d.ts +19 -0
  60. package/dist/mcp/sse-frame-parser.js +41 -0
  61. package/dist/project/artifact-root.d.ts +83 -0
  62. package/dist/project/artifact-root.js +112 -0
  63. package/dist/project/memory-fs.d.ts +9 -0
  64. package/dist/project/memory-fs.js +88 -0
  65. package/dist/project/project-store.d.ts +17 -0
  66. package/dist/project/project-store.js +33 -0
  67. package/dist/project/remember-project.d.ts +24 -0
  68. package/dist/project/remember-project.js +40 -0
  69. package/dist/project/session-root.d.ts +15 -0
  70. package/dist/project/session-root.js +31 -0
  71. package/dist/runs/run-tools.js +2 -1
  72. package/dist/session/lease-availability.d.ts +40 -0
  73. package/dist/session/lease-availability.js +43 -0
  74. package/dist/session/no-session-diagnosis.d.ts +17 -0
  75. package/dist/session/no-session-diagnosis.js +41 -8
  76. package/dist/session/no-session-watch.js +7 -0
  77. package/dist/session/session-readiness.js +1 -1
  78. package/dist/session/session.d.ts +9 -0
  79. package/dist/session/session.js +9 -0
  80. package/dist/session/stall-clock.d.ts +18 -0
  81. package/dist/session/stall-clock.js +48 -0
  82. package/dist/telemetry/daemon-telemetry.js +16 -22
  83. package/dist/telemetry/license-activation.d.ts +48 -0
  84. package/dist/telemetry/license-activation.js +57 -0
  85. package/dist/telemetry/session-metrics.d.ts +1 -1
  86. package/dist/telemetry/session-metrics.js +1 -1
  87. package/dist/telemetry/telemetry.d.ts +12 -3
  88. package/dist/telemetry/telemetry.js +34 -5
  89. package/dist/tools/act-tools.js +34 -4
  90. package/dist/tools/assert-verdict.d.ts +2 -0
  91. package/dist/tools/assert-verdict.js +24 -1
  92. package/dist/tools/contract-tools.js +5 -3
  93. package/dist/tools/coverage-tools.js +33 -0
  94. package/dist/tools/dynamic-tools.js +1 -1
  95. package/dist/tools/error-recovery.js +1 -1
  96. package/dist/tools/feedback-tools.js +31 -6
  97. package/dist/tools/invoke-tool.d.ts +1 -1
  98. package/dist/tools/invoke-tool.js +10 -5
  99. package/dist/tools/observe-tools.js +8 -2
  100. package/dist/tools/tool-kit.d.ts +46 -4
  101. package/dist/tools/tool-names.d.ts +2 -0
  102. package/dist/tools/tool-names.js +2 -0
  103. package/dist/tools/tool-surface.js +2 -3
  104. package/dist/tools/tools.js +16 -1
  105. package/docs/README.md +1 -0
  106. package/docs/agent-cheatsheet.md +2 -2
  107. package/docs/capabilities.mdx +4 -4
  108. package/docs/cli/hunt.mdx +2 -2
  109. package/docs/deploy-checks.md +2 -2
  110. package/docs/docs.json +2 -2
  111. package/docs/enterprise.md +9 -3
  112. package/docs/flows.md +4 -4
  113. package/docs/gates.md +1 -1
  114. package/docs/license-activation.mdx +145 -0
  115. package/docs/recipes.mdx +2 -2
  116. package/docs/telemetry-contract.md +18 -12
  117. package/docs/telemetry-events.mdx +1 -34
  118. package/docs/telemetry.md +0 -1
  119. package/docs/tools-overview.mdx +1 -1
  120. package/docs/tools-session-and-feedback.mdx +5 -0
  121. package/docs/tools-tools-and-run.mdx +15 -15
  122. package/docs/usage.md +3 -3
  123. package/package.json +2 -2
  124. package/dist/telemetry/instrumentation-stall.d.ts +0 -50
  125. package/dist/telemetry/instrumentation-stall.js +0 -90
@@ -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": "reticle_coverage" }
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": "reticle_crawl", "args": { "maxSteps": 6 } }
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": "reticle_flow_verify" }
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 `reticle_flow_verify` reporting the same flow after the app changed underneath it:
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 `reticle_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?
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 reticle_crawl over the app, write the JSON:
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
@@ -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 `reticle_flow_verify { parallel }`, which gives each flow an isolated context.
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 `reticle_flow_verify { parallel: true }`, which gives every flow its own isolated browser context with its own cookies and storage.
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.9.0",
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
  },
@@ -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 renew to keep using enterprise features
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
- `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:
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
- reticle_flow_verify()
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 → `reticle_flow_verify` → fix from each failure's `nextAction` → repeat: the autonomous regression loop.
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
- | `reticle_flow_verify` | `{ names?, sessionId? }` | suite verdict `{ status, passed, failed, failures[] }` |
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 `reticle_flow_verify`, 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.
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": "reticle_verify_change", "args": { "since": "HEAD~1" } }
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": "reticle_flow_verify", "args": { "names": ["docs-check"] } }
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
- ### The counterpart: `instrumentation_stalled`
121
-
122
- `app_instrumented` fires on success and only on success, so the larger population, runs where no app ever arrives, exists in the data as an **absence**. An absence is the one thing telemetry cannot interpret: it cannot separate "nothing was ever wired" from "the process was killed before it could say so" from "we never sent the event". Since the funnel breaks at this step and nowhere else, this is the one silence worth converting into a signal.
123
-
124
- `instrumentation_stalled` fires **once per daemon run**, only after the daemon has been up past a generous threshold, and **never once an app has connected**. A run that starts slowly and then works reports nothing at all. It carries the same three facts as its success counterpart so the two are directly comparable, with `msWaited` in place of `msToFirstApp`.
125
-
126
- Read it against `agentAttached`, which is what makes it actionable rather than merely sad. A stalled daemon with no agent attached is somebody who installed and walked away, and is not a defect. A stalled daemon **with** an agent attached is the failure the product actually has: the tools are loaded, something is asking for them, and there is nothing on the other end to drive.
127
-
128
- It deliberately carries no diagnosis. Whether a dev server is listening is a live probe, and running one on a timer to enrich a metric would be collecting for the metric's sake. The diagnosis belongs where a human or an agent asks for it.
129
-
130
- What neither event can see is **why** an app never connected. Every cause for that is page-side (the non-localhost gate, a port mismatch, a stale build, a dev server never restarted), where the daemon has no visibility. That needs the SDK to report its own refusals, and it is the next thing to build.
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`, `instrumentation_stalled`, `project_profiled`, `verification_completed`, `bug_found`, `tool_refused`, `runtime_crashed`, `feedback_submitted`.
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 |
@@ -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": "reticle_flow_verify", "args": {} }
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 | What it does |
22
- | ----------------------- | ---------------------------------------------------------------------- |
23
- | `reticle_capabilities` | The app's advertised testable surface, testids, signals, stores, flows |
24
- | `reticle_flow_verify` | 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_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_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 |
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` / `reticle_flow_verify`, `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).
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
- - **`reticle_flow_verify({ 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_flow_verify` 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").
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.9.0",
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.9.0"
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;