@reticlehq/server 2.11.0 → 2.13.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 (513) hide show
  1. package/SKILL.md +93 -111
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: 'Reticle: what each tool actually does'
3
+ description: Generated from real calls against a running app. Every row is a probe that ran and the answer it got.
4
+ icon: table-list
5
+ ---
6
+
7
+ **Generated by `bench/harness/behaviour-matrix.mjs`. Do not edit by hand.**
8
+
9
+ Every row below is a call that was made against a running app and the answer that came back. Nothing here is described from the source or from intent. If a claim has no probe behind it, it is not in this file. That is the whole point: the hand-written guides drifted, and the drift was invisible because nobody ran them.
10
+
11
+ `refused (names the fix)` means the error told you what to call instead, so you can correct without reading anything else. `refused (no fix named)` means it did not.
12
+
13
+ ## reticle_snapshot
14
+
15
+ | you call it with | you get back | bytes |
16
+ | --- | --- | --- |
17
+ | an argument the tool does not declare: `{"thisArgDoesNotExist":1}` | refused (names the fix) | 273 |
18
+ | the documented call: `{"mode":"interactive"}` | ran | 434 |
19
+ | a value outside the allowed set: `{"mode":"not-a-mode"}` | refused (names the fix) | 236 |
20
+
21
+ ## reticle_query
22
+
23
+ | you call it with | you get back | bytes |
24
+ | --- | --- | --- |
25
+ | an argument the tool does not declare: `{"thisArgDoesNotExist":1}` | refused (names the fix) | 368 |
26
+ | the documented call: `{"by":"role","value":"button"}` | ran | 200 |
27
+ | a value outside the allowed set: `{"by":"telepathy","value":"x"}` | refused (names the fix) | 290 |
28
+
29
+ ## reticle_network
30
+
31
+ | you call it with | you get back | bytes |
32
+ | --- | --- | --- |
33
+ | an argument the tool does not declare: `{"thisArgDoesNotExist":1}` | refused (names the fix) | 354 |
34
+ | the documented call: `{"limit":5}` | ran | 1175 |
35
+ | a number outside its range: `{"limit":-1}` | refused (names the fix) | 222 |
36
+
37
+ ## reticle_console
38
+
39
+ | you call it with | you get back | bytes |
40
+ | --- | --- | --- |
41
+ | an argument the tool does not declare: `{"thisArgDoesNotExist":1}` | refused (names the fix) | 293 |
42
+
43
+ ## reticle_state
44
+
45
+ | you call it with | you get back | bytes |
46
+ | --- | --- | --- |
47
+ | an argument the tool does not declare: `{"thisArgDoesNotExist":1}` | refused (names the fix) | 268 |
48
+ | a number outside its range: `{"depth":-5}` | refused (names the fix) | 173 |
49
+ | the documented call: `{"depth":2}` | ran | 210 |
50
+
51
+ - a negative depth was silently ignored before v2.12.0
52
+
53
+ ## reticle_observe
54
+
55
+ | you call it with | you get back | bytes |
56
+ | --- | --- | --- |
57
+ | an argument the tool does not declare: `{"thisArgDoesNotExist":1}` | refused (names the fix) | 305 |
58
+
59
+ ## reticle_act_and_wait
60
+
61
+ | you call it with | you get back | bytes |
62
+ | --- | --- | --- |
63
+ | no expectation declared: `{"ref":"e1005","action":"click"}` | verified:no-fault | 1343 |
64
+ | the documented call: `{"ref":"e1105","action":"click","until":{"kind":"text","value":"Invalid email or password"},"timeout_ms":6000}` | verified:yes | 1297 |
65
+ | no expectation declared: `{"ref":"e1205","action":"click","until":{"kind":"settled"}}` | verified:no-fault | 1343 |
66
+ | a value outside the allowed set: `{"ref":"e903","action":"click","until":[{"kind":"text","value":"x"}]}` | refused (names the fix) | 399 |
67
+ | a ref that no longer resolves: `{"ref":"e999999","action":"click"}` | refused (names the fix) | 519 |
68
+
69
+ - no `until`: this returned verified:"yes" before v2.12.0
70
+
71
+ - a consequence the click CAUSES: the shape that earns verified:"yes"
72
+
73
+ - an explicit `{kind:"settled"}` is the idle wait, not a declaration, so it grades no-fault too
74
+
75
+ - a bare array is not a predicate
76
+
77
+ ## reticle_act
78
+
79
+ | you call it with | you get back | bytes |
80
+ | --- | --- | --- |
81
+ | a required argument omitted: `{"ref":"e901"}` | refused (names the fix) | 192 |
82
+ | a value outside the allowed set: `{"ref":"e901","action":"teleport"}` | refused (names the fix) | 415 |
83
+ | an argument the tool does not declare: `{"ref":"e901","action":"fill","value":"x"}` | refused (names the fix) | 314 |
84
+ | the documented call: `{"ref":"e901","action":"fill","args":{"value":"probe"}}` | refused (names the fix) | 516 |
85
+
86
+ - action arguments nest under `args`
87
+
88
+ ## reticle_assert
89
+
90
+ | you call it with | you get back | bytes |
91
+ | --- | --- | --- |
92
+ | a required argument omitted: `{}` | refused (names the fix) | 592 |
93
+ | the documented call: `{"predicate":{"kind":"text","value":"Sign in"},"timeout_ms":3000}` | verified:yes | 794 |
94
+
95
+ ## reticle_tools
96
+
97
+ | you call it with | you get back | bytes |
98
+ | ------------------------- | ----------------------- | ----- |
99
+ | the documented call: `{}` | refused (names the fix) | 246 |
100
+
101
+ ## reticle_run
102
+
103
+ | you call it with | you get back | bytes |
104
+ | --- | --- | --- |
105
+ | a value outside the allowed set: `{"tool":"reticle_nope","args":{}}` | refused (names the fix) | 894 |
106
+ | the documented call: `{"tool":"reticle_context","args":{}}` | ran | 54 |
@@ -5,7 +5,7 @@ description: One URL you paste into any coding agent to teach it Reticle. How it
5
5
  icon: graduation-cap
6
6
  ---
7
7
 
8
- Paste `https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md` into any coding agent and say "read this and set up Reticle". The document installs Reticle, wires it into your app, and then drives a proper verification loop, choosing setup mode or test mode itself based on whether `.reticle.json` already exists. You often do not need it: `npx @reticlehq/server init` configures Claude Code and Cursor directly.
8
+ Paste `https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md` into any coding agent and say "read this and set up Reticle". The document tells the agent to run `npx @reticlehq/server init --flow "<what>"`, which does the whole install and ends with a verdict, and gives it the rules for verifying afterwards. You often do not need the file at all: that one command configures Claude Code, Cursor and nine other agents directly, and the file mostly exists to tell an agent which journey to prove and how to read what comes back.
9
9
 
10
10
  `SKILL.md` is a single document you hand to a coding agent. It teaches the agent to install Reticle, wire it into your app, and then drive a verification loop properly. Without you explaining any of it.
11
11
 
@@ -40,8 +40,8 @@ One document, two jobs, no flag for you to remember. The second time you invoke
40
40
  | Phase | What the agent does |
41
41
  | --- | --- |
42
42
  | Save the skill | Store it as `/reticle` so the loop is one word away from then on |
43
- | Run `init` | Without asking you questions it can answer itself |
44
- | Manual fallbacks | Per-agent MCP config, per-framework SDK wiring, for anything `init` marked `⚠` |
43
+ | Run `init --flow` | Without asking you questions it can answer itself, passing the journey worth proving |
44
+ | Manual fallbacks | Only when `init` could not run: an older CLI that stops after writing files, or the same failure twice |
45
45
  | Validate | Confirm a session actually connected. Not that a checklist went green |
46
46
  | Prove it | Drive one real flow while you watch, so the install ends in evidence |
47
47
 
@@ -27,7 +27,7 @@ Tool usage, timing, errors, verifications and bugs are all recorded in **one pla
27
27
 
28
28
  Do **not** add telemetry inside a tool handler. If you find yourself wanting to, the metric probably belongs at the chokepoint, read off the result.
29
29
 
30
- > The one exception is a path that genuinely does not go through `runTool`. Today that is only the verification runner (`reticle verify`, the HTTP verify surface), which has its own reporter in `telemetry/run-telemetry.ts`. **If you add a second dispatch path, it needs the same treatment**, and until it has one it is invisible. That gap existed for real: CI-found bugs were uncounted.
30
+ > The exceptions are paths that genuinely do not go through `runTool`. One is the verification runner (`reticle verify`, the HTTP verify surface), which has its own reporter in `telemetry/run-telemetry.ts`. The other is the MCP proxy's POST leg (`postToSession`): a socket failure there never reaches a tool handler, so the counts live on the proxy's session summary and flush as `session_progress`. **If you add another dispatch path, it needs the same treatment**, and until it has one it is invisible. That gap existed for real: CI-found bugs were uncounted.
31
31
 
32
32
  ### 2. Names say what happened
33
33
 
@@ -118,6 +118,52 @@ Nothing measured the second. `daemon_started` and `mcp_client_connected` describ
118
118
 
119
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.
120
120
 
121
+ ## Where the install stopped: reading the non-instrumented majority
122
+
123
+ Most daemons never see an app. The failure side is a **set difference** -- `daemon_started` minus `app_instrumented`, joined on `sessionId` -- and a set difference cannot say why. Four situations with four different owners land in it as one silence.
124
+
125
+ Three fields split it, and none of them is a new event kind:
126
+
127
+ | `init_completed.ok` | `project.initialized` | `project.appConnectedBefore` | Reading | Owner |
128
+ | --- | --- | --- | --- | --- |
129
+ | -- | `false` | `false` | `init` was never run here. Nothing was ever wired. | acquisition / docs |
130
+ | `false` | -- | -- | `init` ran and failed. `init.reason` says which step. | us |
131
+ | `true` | `true` | `false` | The config is written and **no page has ever reached the daemon**. The dev server was never restarted, the plugin never loaded, or the handshake was refused page-side. | us + onboarding |
132
+ | -- | `true` | `true` | A working install whose app simply is not up right now. **Not a loss.** | nobody |
133
+
134
+ The third row is the cohort the funnel actually loses, and it was invisible: `initialized` alone is `true` for the working installs too, so only the pair separates them.
135
+
136
+ What this still does **not** answer: whether the dev server is running right now. Nothing probes for one at profile time, so "restarted the dev server and it still did not connect" and "never started the app at all" are both in the third row. `no-session-diagnosis.ts` computes that distinction live, for the agent, as prose -- it is not classified and it is not emitted.
137
+
138
+ Two more traps on these fields. `project_profiled` fires **5 seconds after** the daemon comes up, so a daemon killed inside that window sends none of this and lands in the silence it was meant to explain. And `appConnectedBefore` is scoped to project + port: an app that has only ever connected on a different port reads `false`, correctly for the question asked and wrongly if you read it as "this user has never had Reticle work".
139
+
140
+ ## What the agent had to look at: `connection.appConnected`
141
+
142
+ Most MCP clients that attach never call a single tool, and that cohort emits **nothing at all**. `tool_refused` cannot see it -- an agent that reads the server instructions, learns nothing is wired, and stops has refused nothing, because it made no call.
143
+
144
+ `connection.appConnected` is the one bit that splits it. `false` means the handshake happened against a daemon with no app to look at, which is the state the first-move instructions describe and the state in which no tool could have answered anything. The two halves need opposite fixes: one is an install that never finished, the other is an agent that had everything it needed and did not use it.
145
+
146
+ It does **not** say which instruction variant the agent was served. `buildServerInstructions` keys on `previouslyConnected` (durable, project-scoped), not on whether an app is attached right now, and those disagree on a working install whose page is closed.
147
+
148
+ ## Was the verdict trustworthy: reading `unknown` and the passing greens
149
+
150
+ Two questions get asked of `verification_completed` constantly and both have exact answers already in the payload. Neither needs a new field, and both are read wrong without `reason`.
151
+
152
+ **"A large share of verdicts come back `unknown`."** `verified` has three values and the rule that produces it has eleven clauses, so `unknown` on its own is seven different situations belonging to three different owners. **Always break `unknown` down by `verification.reason`** before treating it as a quality number:
153
+
154
+ - `inconclusive`, `nothing_declared`, `vacuous_grade` -- the agent's own call. Teach the agent; the product worked.
155
+ - `outcome_pending`, `outcome_unread`, `unsettled`, `evidence_incomplete` -- the app had not finished. Wait and re-check; often a budget that ended early.
156
+ - `observation_lost`, `unclean_capture`, `absence_blind_spot` -- **ours**. Ship a fix. `uncleanLoss` names which of the three owners inside that one.
157
+
158
+ An `unknown` rate quoted without this split is a number about all three at once and actionable by none of them.
159
+
160
+ **"Many verdicts passed their own assertion yet did not report `yes`."** That is `passed: true` with `verified !== 'yes'`, and it is mostly the product working as designed: `decideVerified` refuses a green seven ways. Split it:
161
+
162
+ - `verified: 'no'` -- a green actively refuted by another channel. This is `falseGreenCaught`, and it is the thesis.
163
+ - `verified: 'unknown'` -- Reticle declined to endorse a green it could not stand behind. `already_true` (the condition held before the action, so the check proved nothing) is a _correct_ refusal and not a defect anywhere.
164
+
165
+ `falseGreenCaught` is deliberately narrow -- only `passed && verified === 'no'` -- so the wider number can be counted from `reason` without a definition change. **Do not widen it**: a metric whose definition shifts underneath it produces two series that look comparable and are not.
166
+
121
167
  ## Sessions: `daemon_stopped` vs `session_progress`
122
168
 
123
169
  Count sessions with **`daemon_stopped`** (`final: true`). It fires once, at a clean exit.
@@ -135,6 +181,8 @@ Four counters and one flag were added because the data could not answer question
135
181
  | Field | On | Means |
136
182
  | --- | --- | --- |
137
183
  | `noSessionErrors` | session summary | tool calls that failed because there was no app to reach: no session, no session by that id, or several with none named. The largest drop-off in the funnel; it was previously reachable only by unpacking `errors[]`. |
184
+ | `postSocketFailures` | session summary | connection-level POST failures on the MCP proxy (`ENOBUFS`, `EMFILE`, `EADDRNOTAVAIL`, `ECONNREFUSED` before any bytes were sent). These never produce `tool_refused` (the call never reached a handler) and never produce `mcp_connection_lost` (the SSE stream is fine). Counted in the proxy process and flushed as `session_progress` on stdin end. The send is awaited, because fire-and-forget before `process.exit` is how `daemon_stopped` never arrived. Absent when none happened. |
185
+ | `postRetriesSaved` | session summary | of those POST failures, how many a bounded retry then delivered. The numerator against `postSocketFailures`: a retry that quietly saves a call is a different fact from a retry that never runs. Absent when none were saved. |
138
186
  | `consecutiveRepeats` | session summary | longest back-to-back run per tool name. `toolCounts` reports five useful calls and five retries of one failing call identically, and those are opposite facts. |
139
187
  | `abandonedActions` | session summary | actions driven with no verdict AFTER them (the trailing unsettled run, not `actions - verifications`). That difference ignores order, so a verdict that drove nothing (a `flow_verify` over saved flows) silently paid for an abandoned action elsewhere. |
140
188
  | `endedWithVerdict` | session summary (final only) | did this session ever produce a verdict. The headline metric, and previously the only thing in the payload that had to be COMPUTED, from lifetime counters sitting next to windowed ones, which is a subtraction that gets read wrong. Sent as `false` rather than omitted: a session that drove an app and never asked whether it worked is the finding. |
@@ -142,13 +190,19 @@ Four counters and one flag were added because the data could not answer question
142
190
  | `verification.brand` | `verification_completed` | WHICH browser it was: `chrome` \| `edge` \| `arc` \| `dia` \| `brave` \| `opera` \| `firefox` \| `safari` \| `other`, the closed `BrowserBrand` list in core. The axis `engine` cannot answer, since Chrome, Edge, Arc, Dia and Brave are all `blink`. The SDK reads `navigator.userAgentData.brands` (and the UA string on Firefox/Safari, which expose no `userAgentData`) and normalises IN THE PAGE: a raw brand or UA string is unbounded and fingerprintable and never leaves. Anything unrecognised is `other`. **Omitted rather than `"unknown"`** when the page did not say. A desktop webview has no brand and an older SDK does not report one, and a guess is indistinguishable from a measurement on a dashboard. |
143
191
  | `verification.reason` | `verification_completed` | WHICH clause of `decideVerified` produced the verdict, from core's closed `VerifiedReason`: `inconclusive` \| `observation_lost` \| `assertion_failed` \| `contradicted` \| `already_true` \| `unclean_capture` \| `vacuous_grade` \| `outcome_pending` \| `outcome_unread` \| `unsettled` (the page never went idle and no consequence was declared) \| `evidence_incomplete` (the assertion held, but a channel's outcome had not arrived when the window closed) \| `proved`. See below. |
144
192
  | `verification.uncleanLoss` | `verification_completed` | WHAT was lost when `reason` is `unclean_capture`, from core's closed `CaptureLoss`: `buffer_loss` (our server ring buffer evicted evidence from the window) \| `transport_gap` (our browser queue overflowed) \| `blind_spot` (a boundary in the page, such as a cross-origin frame or a closed shadow root) \| `other`. Three owners, three fixes, and one bar on a dashboard until this existed. ONE value, not a list: a multi-value property is not something a breakdown can group by, so the first is sent, ours before the page's. **Absent whenever the capture was clean**, so its presence is itself the signal. Reported as `other` rather than omitted when the block says dirty and names nothing: a gap there would read as "no unclean verdicts happened". |
193
+ | `project.stackUnknownReason` | `project_profiled` | WHY there is no `stack`, from core's closed `StackUnknownReason`: `no_app_found` \| `manifest_unrecognised` (we READ an app's manifest and knew nothing in it, which one line in `STACK_BY_DEP` fixes) \| `workspace_apps_unrecognised` \| `workspace_root_no_apps` (a monorepo root where discovery surfaced no app at all) \| `discovery_failed`. `stack` unknown is one of the largest buckets and an empty field is not a cause: it collapsed four facts with four different fixes. **Absent whenever a stack WAS found**, so its presence marks the unknown bucket. Note `workspace_root_no_apps` is expected to dominate over `workspace_apps_unrecognised`: `findWorkspaceApps` admits a directory only on a Vite/Next config file or a literal `next`/`vite` dependency, so a workspace app on any other framework is never surfaced and its manifest is never read. |
145
194
  | `bug.attribution` | `bug_found` | `app` \| `request` \| `reticle`: whose fault the defect was. **Absent means unclassified**, never `app`. See below. |
195
+ | `refusal.noSessionReason` | `tool_refused` | For `no_session` only: WHICH no-session situation, from core's closed `NoSessionReason`: `lease_expired` \| `tab_gone` \| `app_not_reopened` \| `config_elsewhere` \| `no_listener_no_config` \| `no_listener` \| `no_config` \| `sdk_not_reaching_daemon`. `no_session` is the largest refusal cohort and on its own it is a set difference: nothing connected, with no word on which of several opposite situations that was. "Restarted the dev server and it still did not connect" and "never started the app" need opposite fixes and arrived as the same silence. Derived from the branches of `explainNoSession`, not classified beside them, so the code cannot describe a diagnosis different from the sentence the user was shown. **Absent on every other refusal reason.** |
146
196
  | `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. |
197
+ | `project.initialized` | `project_profiled` | has `reticle init` run here -- a `.reticle.json` is present. On `project_profiled` rather than `app_instrumented` deliberately: that event fires once per daemon start whatever happens next, so it is the only place a fact about a project reaches us for the users who never instrument anything. **Absent means an older sender, never `false`.** |
198
+ | `project.appConnectedBefore` | `project_profiled` | has an app for THIS project ever connected to Reticle, from durable state -- not from this process. Scoped to project + port like every other reader of that state, so it cannot borrow another project's success on a shared daemon. **Absent when the daemon did not know its own port**, which is not-measured rather than `false`; a `false` invented from a read error would put the working installs into the cohort we are sizing. |
199
+ | `connection.appConnected` | `mcp_client_connected` | was an app already attached when the agent arrived. The mirror of `instrumentation.agentAttached`, read off the same flag so the two halves cannot disagree about one daemon run. The closest thing we have to WHAT THE AGENT SAW. |
147
200
  | `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
201
  | `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. |
149
202
  | `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. |
150
203
  | `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. |
151
204
  | `tzOffsetMin` | every event | minutes offset from UTC. One integer, no location. |
205
+ | `session.updateNudged` / `session.updateOffered` | session summary | did the update nudge actually fire this daemon run, and which version it knew about. The nudge is the ENTIRE adoption mechanism for a published fix -- it rides the tool-result envelope once per daemon process -- and for several releases it emitted nothing at all. `updateNudged` is the one-shot delivery flag: `true` means an agent was told, never how often. `updateOffered` is our own published version number, so it is low-cardinality and says nothing about the machine; without it `updateNudged: false` would mean "nothing was available" and "something was and the nudge did not fire" at once, and only the second is a defect. See below. |
152
206
  | `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. |
153
207
 
154
208
  ## Enterprise activation: `licenseId`, `licenseStatus`
@@ -183,6 +237,23 @@ Two things about it are deliberate and easy to get wrong later.
183
237
 
184
238
  Capped at 50 per daemon run. Volume is part of this taxonomy's design and a stuck agent is exactly the shape that produces hundreds; `consecutiveRepeats` on the session summary still reports how long the loop ran.
185
239
 
240
+ ## Did anybody hear about the release: the nudge
241
+
242
+ `versionChange.nudged` answers "did the nudge cause this update", and it only ever reaches us from machines that **did** update. The cohort that matters is the opposite one: an install pinned several releases back never fires `version_changed` at all, so the population being nudged the hardest was the population the metric structurally could not see.
243
+
244
+ `session.updateNudged` + `session.updateOffered` close that. Crossed against the same installId's `version` on a later day, they separate two causes that need opposite fixes:
245
+
246
+ | `updateOffered` | `updateNudged` | version moves later | Reading |
247
+ | --- | --- | --- | --- |
248
+ | absent | `false` | -- | nothing was available. Not a finding. |
249
+ | present | `false`, run after run | no | **the nudge is not firing for them**: a manifest cache that never warms, a check that never returns, an offline machine. Ours. |
250
+ | present | `true`, run after run | no | the agent is receiving it and **dropping it out of the envelope**, or telling a human who declines. A delivery problem, not a detection one. |
251
+ | present | `true` | yes | it worked. `versionChange.nudged` confirms from the other side. |
252
+
253
+ What it does **not** answer: whether the agent surfaced the nudge to its human. Nothing on this side of the envelope can see that, and inferring it from a later upgrade would credit the nudge for a `reticle update` somebody ran for their own reasons -- which is precisely the credit `nudge-credit.ts` bounds to a seven-day window rather than claiming outright.
254
+
255
+ One edge to know when querying: `updateNudged` reads the delivery flag, and `armUpdateNudgeFrom` re-arms it when a newer manifest lands mid-session. On a long session that spans a release it therefore reports the LAST arming's state, not "was ever shown". Sessions in the data run to eleven hours, so this is reachable; it is rare, and it errs toward `false`.
256
+
186
257
  ## Which route brought them in: `installSource`
187
258
 
188
259
  Four install routes ship at once (the SKILL.md paste URL, an `npx skills add` package, a Claude Code plugin, and docs.reticle.sh), and not one install could be attributed to any of them. Every decision about where to spend distribution effort was made blind.
@@ -195,6 +266,12 @@ So `plugin` is the only route detectable without anybody typing anything (the pl
195
266
 
196
267
  **`unknown` is therefore expected to be the largest bucket, and shrinking it is a distribution job rather than a classifier job.** Read a small `unknown` as a marker that spread. A guessed attribution would be worse than none: it is the number distribution decisions get steered on, and once a guess sits in the same column as a measurement the two cannot be told apart.
197
268
 
269
+ **Where the value lives, and why it is not the environment.** The marker is an environment variable, so it exists for exactly one command and is gone by the next -- which is why the field originally reached two of the rarest events there are and nothing else. `init` writes it into `.reticle.json`, and `projectInstallSource()` reads **config first, then environment**, once per process, stamping it on every event that process sends. So the marker has to survive `init` to survive at all.
270
+
271
+ **A re-run backfills it, and never overwrites it.** `installSource` used to be written only when `.reticle.json` was CREATED, and `init` reports that file as `already exists` on every re-run -- so everyone who arrived before the marker shipped, and everyone who ran `init` once without one, was stuck on `unknown` with no way to fix it, which is most of the population the field describes. `configWithInstallSource()` now ADDS the field to a config that lacks one. It never replaces a recorded value: the first channel is the one that brought the user in, and a later `init` through a different route must not take credit for an acquisition it did not make. An unparseable config is left exactly as it is.
272
+
273
+ **Two ways this still degrades to `unknown`, and neither is a classifier bug.** An agent that paraphrases the published command drops the `RETICLE_INSTALL_SOURCE=` prefix along with it, and there is nothing on the machine afterwards that could recover it. And a project whose `init` never ran -- the SDK wired by hand, or a monorepo where the config sits somewhere the daemon's walk does not reach -- has no config to read from.
274
+
198
275
  ## Why a verdict came out that way: `verification.reason`
199
276
 
200
277
  `verified` has three values. The rule that produces it has **eleven clauses**. Everything in between was thrown away at the moment it was known.
@@ -236,12 +313,37 @@ The transport-stability metric shipped with an **empty payload** for months, and
236
313
 
237
314
  The lesson is not "wire the field". It is that **the battery asserted the event ARRIVED and never that it carried anything**, and a kind-only assertion cannot see an empty payload. When you add an event kind, the live check has to assert the FIELDS.
238
315
 
239
- - `stage` is `first` (this session lost MCP at all) or `budget_spent` (it stopped retrying). These are the two facts the event exists to separate: the share of sessions that lose MCP, and the share where it never came back on its own.
240
- - `reason` is the closed `OutageReason`: `sse_ended` | `sse_error` | `sse_aborted` | `sse_closed` | `connect_error` | `other`. The proxy's own reason strings are free text that also feeds a log, so `mcp-outage.ts` narrows them and reports **`other`** for anything unnamed. A classifier that cannot say "I don't know" lies instead, and an unbounded string must never reach the wire.
316
+ - `stage` is the closed `OutageStage`: `first` (this session lost MCP at all), `budget_spent` (it stopped retrying and went dormant), or `recovered` (the link came back on its own). Each is reported **at most once per proxy process**, so the three are a per-session state and never a count.
317
+ - `reason` is the closed `OutageReason`: `sse_ended` | `daemon_shutdown` | `sse_error` | `sse_aborted` | `sse_closed` | `connect_error` | `other`. The proxy's own reason strings are free text that also feeds a log, so `mcp-outage.ts` narrows them and reports **`other`** for anything unnamed. A classifier that cannot say "I don't know" lies instead, and an unbounded string must never reach the wire.
241
318
  - `attempts`: consecutive reconnects tried when this was reported.
319
+ - `pendingLost`: in-flight tool calls this drop actually killed -- the only part an agent can FEEL. Sent always, **including zero**, because zero is the finding.
320
+
321
+ **Three things about querying this, and every one of them has already produced a wrong number.**
322
+
323
+ **`daemon_shutdown` is not an outage.** The daemon announces a planned retirement before it closes the stream, and from the socket's side that is byte-identical to a daemon dying under a live client. Without the split, the metric meant to say "the agent lost its tools" spends most of its volume counting the idle exit working exactly as designed. **Filter it out before reading this event as an outage rate at all** -- a chart over the raw kind describes the idle timer, not the transport.
324
+
325
+ **`attempts` on a `first` event is 1 by construction and carries no information.** It is emitted at the moment of the drop, when the counter has just been incremented once, and the once-per-process cap keeps any later drop from replacing it. Reading a field that can only hold one value as "reconnection never advances past the first attempt" is reading the emission point, not the transport. **`recovered` carries the real cost of coming back**; it is the only stage whose `attempts` is a measurement.
326
+
327
+ **`first` alone is unfalsifiable, and the pair is the metric.** `first` with a matching `recovered` is a blip the agent probably never noticed (check `pendingLost`). `first` with `budget_spent` and no `recovered` is a session whose tools never came back on their own, which is the number worth driving down. Counting `first` on its own over-states the problem by roughly the whole of it.
242
328
 
243
329
  Still true and worth knowing when you query it: `mcp_connection_lost` carries **no `sessionId`** (it fires from the proxy process, not the daemon), and is capped at two per proxy process by design.
244
330
 
331
+ ### Licence activation
332
+
333
+ Three envelope scalars, present on **every** event rather than on a licence event of their own: the questions a licensed customer generates are "how much is this org using it", "what is breaking for them" and "did their key lapse", and every one of those is answered by an event that has nothing to do with licensing.
334
+
335
+ - `licenseId`. The signed licence id. An opaque uuid that resolves to a company only against the issuance ledger held locally, so the analytics backend never holds a customer list. Present only while a key verifies.
336
+ - `licenseStatus`. `active` | `missing` | `invalid` | `expired`. Rides whenever an issuer key is baked, **including the failure states**, which is what makes a lapse distinguishable from a churn: a lapsed customer stops sending `licenseId`, so on identity alone a lapse and a departure look identical.
337
+ - `licenseKeyPresent`. A key was placed in the environment, whatever this build concluded about it.
338
+
339
+ **The trap this last one exists to close.** `licenseStatus` is absent entirely on a build with no issuer key baked, which is every OSS install and every source checkout. That is deliberate and correct for a machine with no key. And wrong for a machine with one. A customer who pastes a real enterprise key into such a build produced _no licence signal whatsoever_ and was indistinguishable from someone who has never held a key. So the one population most worth seeing was the one that could not be seen.
340
+
341
+ **`licenseKeyPresent: true` with no `licenseStatus` is the "their key is not taking effect" case.** Query for it directly; it is a support ticket that has not been filed yet.
342
+
343
+ **Never the key.** `licenseKeyPresent` is a boolean. The key is a credential and does not leave the machine, and neither does the organisation NAME (free text, and rule 3 is names-never-values) or the PLAN (the issuance ledger already holds it against the same id).
344
+
345
+ **Where the key is looked for.** The daemon is spawned without an explicit `cwd` and inherits the editor's, so `<cwd>/.env` alone missed keys routinely: in a monorepo the daemon starts at the workspace root while the key sits in the app's own `.env`, and under some editors the cwd is the user's home. `license-env.ts` searches `.env` and `.env.local`, upward to the project root and one level down into `apps/*` and `packages/*`. Only the licence key is taken out of a file found by walking. Bulk-importing a `.env` from a directory the caller never named could rebind the daemon's port or its allowed origins, which would be a far worse bug than the one it fixes.
346
+
245
347
  ## Recording locally instead of sending: `RETICLE_TELEMETRY_FILE`
246
348
 
247
349
  Set it to a path and every event is appended there as one JSON object per line, and **nothing is sent**. The payload is the one the wire would have carried, built by the same code and redacted by the same rules, so what a run records is what a user would have sent.
@@ -265,7 +367,9 @@ One deliberate exception to the rules above: `RETICLE_TELEMETRY_FILE` keeps tele
265
367
  | **A new finding shape** in a tool result | Teach `bugsInResult` the field. Add a case to the contract test | ✓ |
266
368
  | **A failure path** (connect, install, crash) | Classify it into an enum with an explicit `OTHER` bucket; a classifier that cannot say "I don't know" lies instead | ✓ |
267
369
  | **An event kind** | Add to `TelemetryEventKind` + a payload schema + emit it + add a live check to `apps/e2e/specs/telemetry-events-test.mjs` that asserts the **fields**, not just that it arrived | partly; the live check is on you |
268
- | **A block on `TelemetryExtra`** | Also add it to the `emit()` event build, the `blocks` flattening map, AND `TelemetryEventSchema`. Missing any one of the three drops the payload in silence (see `outage`) | ✗ **not enforced; be careful** |
370
+ | **A field on an EXISTING block** | Add it to that block's schema in core, and nothing else. The `blocks` map flattens every key it finds, so a new field on `connection`/`project`/`outage`/… reaches the wire for free | `extra-blocks-reach-the-wire.test.ts` |
371
+ | **A new block on `TelemetryExtra`** | FOUR hand-maintained lists, and missing any one drops the payload in silence (see `outage`): `TelemetryExtra` (`telemetry/telemetry.ts`), the `emit()` event-build spread (same file), the destructure + `blocks` prefix map (same file), and `TelemetryEventSchema` (`core/src/telemetry.ts`). Then add a `SAMPLES` entry in `extra-blocks-reach-the-wire.test.ts` | `extra-blocks-reach-the-wire.test.ts` |
372
+ | **A new SCALAR on the envelope** (`installSource`, `automation`, `licenseId`) | THREE: `TelemetryExtra` if a caller sets it, the `emit()` event build, and `TelemetryEventSchema`. **No `blocks` entry** -- there is nothing to flatten, and adding one is a silent no-op | ✗ **not enforced; be careful** |
269
373
  | **A dispatch path** that bypasses `runTool` | Give it a reporter like `run-telemetry.ts`, or it is invisible | ✗ **not enforced; be careful** |
270
374
 
271
375
  ## Verifying it actually works
@@ -22,6 +22,22 @@ This tool exists because of a measured problem: agents were driving forms one ca
22
22
  }
23
23
  ```
24
24
 
25
+ Steps may also name a `target` instead of a `ref`, the same locator [`reticle_act`](/tools-act) takes. The sequence resolves each one before dispatch, so a login does not need a query round trip per field:
26
+
27
+ ```json
28
+ {
29
+ "steps": [
30
+ { "target": { "label": "Email" }, "action": "fill", "args": { "value": "admin@reticle.dev" } },
31
+ {
32
+ "target": { "label": "Password" },
33
+ "action": "fill",
34
+ "args": { "value": "correct-horse-battery-staple" }
35
+ },
36
+ { "target": { "testid": "login-submit" }, "action": "click" }
37
+ ]
38
+ }
39
+ ```
40
+
25
41
  Real response. Each `effects[]` entry also carries `targetMatched`, `visible`, `enabled`, `defaultPrevented`, `occluded`, `occludedBy` and `scrolledIntoView`, and each `steps[]` entry also carries `dispatched`, `settled`, `settleReason`, `component`, `role` and `name`. Those are trimmed here for length:
26
42
 
27
43
  ```json
@@ -6,9 +6,7 @@ icon: boxes-stacked
6
6
 
7
7
  `reticle_tools` lists every Reticle tool and, on request, returns the full argument grammar for the ones you name. `reticle_run` then invokes any of them. Reach for the pair whenever you need a capability that is not in the default advertised surface, which is most of what Reticle can do.
8
8
 
9
- Reticle ships 46 tools plus these two meta-tools. On the default profile your agent is shown 17 of them plus the meta pair, so 29 sit in the cold tail. These two are how it reaches those 29 without paying for their schemas on every turn.
10
-
11
- `reticle_tools` discovers. `reticle_run` invokes. Together they cost two tool definitions instead of twenty-nine.
9
+ `reticle_tools` discovers. `reticle_run` invokes. Together they cost two tool definitions instead of the rest of the catalog.
12
10
 
13
11
  ## Discovering what exists
14
12
 
@@ -41,7 +39,7 @@ The response also tells you which surface is active:
41
39
  "profile": {
42
40
  "active": "all",
43
41
  "source": "RETICLE_ADVERTISE_ALL_TOOLS set in the DAEMON's environment at startup",
44
- "note": "The profile is read once at daemon startup: change RETICLE_TOOL_PROFILE and restart the daemon, or it has no effect. Under this profile every unadvertised tool is reachable through reticle_run; under `full` there are no meta-tools because every tool is advertised directly."
42
+ "note": "The surface is read once at daemon startup: set RETICLE_ADVERTISE_ALL_TOOLS=1 and restart the daemon, or it has no effect. No surface advertises every tool: the advertised count is capped because editors budget tools across all connected MCP servers. Every tool listed here is callable through reticle_run { tool, args } whether or not it is advertised."
45
43
  },
46
44
  "next": "Load params with reticle_tools { names:[…] }, then call reticle_run { tool, args }."
47
45
  }
@@ -83,6 +83,15 @@ the URL explicitly (Vite: VITE_RETICLE_WS_URL, or reticle.connect({ url })). An
83
83
  open a ws:// socket at all. Still retrying…
84
84
  ```
85
85
 
86
+ **You should not have to read this page to find that out.** If the agent opened the app with `reticle_lease`, which is the path `init` steers it to, the daemon reads that address off the page's own console, compares it with the port it is bound to, and puts the mismatch in the tool result:
87
+
88
+ ```
89
+ The page dialled ws://localhost:4460/reticle (port 4460) and this daemon is on
90
+ 4400, so the dial never reached it. Nothing about the app's wiring is in question.
91
+ ```
92
+
93
+ Neither side can work that out alone: the page cannot see the daemon, and the daemon never saw a dial that went somewhere else. The rest of this section covers what the lease cannot: a tab you opened yourself, or an app the pool cannot reach.
94
+
86
95
  That message names the exact address it tried. Compare its port with `bridge port` from `doctor` and set whichever is wrong:
87
96
 
88
97
  ```bash
package/docs/usage.md CHANGED
@@ -129,7 +129,7 @@ Deep detail on one element, including the signals a snapshot/a11y tree omits, so
129
129
  Perform one action / several in order.
130
130
 
131
131
  - **`reticle_act` args:** `ref`, `action`, `args?`, `refuseWhenThrottled?`, `sessionId?`. → `{ since, dispatched, settled, settleReason, result, session, warning? }` where `result = { ok, ref, action, dispatched, settled, settleReason, effect }`. The `session` block `{ lastSeenMs, throttled, focused }` reports tab health on every act; when `throttled` is true a `warning` string is also attached. Pass `refuseWhenThrottled: true` to hard-fail instead of warning (opt-in; default is warn-only so background testing never breaks).
132
- - **`reticle_act_sequence` args:** `steps: [{ ref, action, args? }]`. → `{ since, dispatched, result }` where `result = { ok, count, effects: [...], steps: [...] }` (one `effect` per step; each step carries its own `dispatched`/`settled`/`settleReason`).
132
+ - **`reticle_act_sequence` args:** `steps: [{ ref | target, action, args? }]`. Each step takes `ref` (from a snapshot/query) or `target` (`{ testid }` / `{ label }` / `{ role, name }` / `{ text }`). That is the same locator `reticle_act` accepts. → `{ since, dispatched, result }` where `result = { ok, count, effects: [...], steps: [...] }` (one `effect` per step; each step carries its own `dispatched`/`settled`/`settleReason`).
133
133
  - See [§5](#5-actions-full-list) for the action list.
134
134
 
135
135
  **Dispatch vs settle.** The action is two phases: the **dispatch** (the synchronous click/fill, which is what can fail) and the **settle** (waiting one animation frame so React's commit lands before we return). The settle is **bounded** (~200ms): in a throttled/background tab `requestAnimationFrame` never fires, so Reticle falls back to a timer and resolves anyway. A settle timeout is therefore **never an error**: `reticle_act` resolves with `settled:false, settleReason:"timeout"` and the dispatch (the click) has still landed. Only a real dispatch failure (stale ref, wrong element type) throws.
@@ -183,7 +183,7 @@ The timeline + summary of what happened.
183
183
 
184
184
  Act, then wait for a predicate: the whole act→observe→assert loop in one hop.
185
185
 
186
- - **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `sessionId?`.
186
+ - **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `intent?`, `sessionId?`.
187
187
  - **returns:** `{ effect, verdict, trace, session, warning? }`. `effect` is the action result (`{ ok, ref, action }`), `verdict` is `{ pass, evidence?, failureReason? }`, `trace` is the reaction report of everything the app did after the action, and `session` is the tab-health block `{ lastSeenMs, throttled, focused }` (with a `warning` when throttled). A failing `verdict` still returns `effect` + `trace` so you can see what _did_ happen. The predicate is automatically floored at this act's cursor, so it only matches events the action actually caused.
188
188
 
189
189
  ### `reticle_wait_for`
@@ -197,9 +197,10 @@ Block until a predicate holds (or time out). Looks both backward (recent buffer)
197
197
 
198
198
  Verify a predicate; optionally wait for it.
199
199
 
200
- - **args:** `predicate`, `timeout_ms?` (0 = evaluate once), `since?`, `sessionId?`.
200
+ - **args:** `predicate`, `timeout_ms?` (0 = evaluate once), `since?`, `intent?`, `sessionId?`.
201
201
  - Same `since` default as `reticle_wait_for`: scoped to your last act so a stale buffered event can't fake a pass; override with an explicit `since`.
202
202
  - **returns:** `{ verified, because, pass, evidence, contradictions?, coverage?, failureReason?, session, warning? }`. On failure includes a **near-miss** (e.g. "found the dialog but not visible", or "no button named 'Submit'; saw: Cancel"). The `session` block `{ lastSeenMs, throttled, focused }` reports tab health on every assert; when throttled a `warning` is attached so you never assert against a tab that is silently no-oping.
203
+ - **`intent` declares what the change was FOR, inline.** Pass a sentence in your own words and it lands in `.reticle/intent.json`, the same git-checked ledger `reticle_intent` writes, before the verdict is drawn; a green verdict then marks it proved and names itself as the proof. Pass the **id** of an intent you already declared instead, to point several verdicts at one statement rather than restating it. `reticle_act_and_wait` takes the same argument. Omit it and nothing is written.
203
204
  - **Read `verified`, not `pass`.** `pass` says the predicate held; `verified` says whether that means anything. It is `"no"` when a channel contradicts the assertion (a failed write under a green screen, a batch whose body reports per-item failures, a request still in flight), and `"unknown"` when the outcome could not be known yet: a `202 Accepted` that has not reconciled, or a write whose response body was never recorded. `because` names the deciding evidence in one sentence.
204
205
 
205
206
  ### `reticle_reconcile`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reticlehq/server",
3
- "version": "2.11.0",
3
+ "version": "2.13.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,11 +22,12 @@
22
22
  "@modelcontextprotocol/sdk": "^1.30.0",
23
23
  "ws": "^8.21.3",
24
24
  "zod": "^3.24.1",
25
- "@reticlehq/core": "2.11.0"
25
+ "@reticlehq/core": "2.13.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/pngjs": "^6.0.5",
29
- "@types/ws": "^8.5.13"
29
+ "@types/ws": "^8.5.13",
30
+ "yaml": "^2.9.0"
30
31
  },
31
32
  "optionalDependencies": {
32
33
  "pixelmatch": "^7.2.0",
@@ -1,24 +0,0 @@
1
- /**
2
- * Global Cursor registration for `reticle init`. Cursor has no CLI, but its global MCP config is a
3
- * small dedicated file (`~/.cursor/mcp.json`) — safe to merge directly (unlike `~/.claude.json`).
4
- * We add the `reticle` server once at this global path so every project picks it up. Unparseable
5
- * (jsonc/comment) files bail to manual rather than being rewritten.
6
- */
7
- /** Path of Cursor's global MCP config, relative to the user's home directory. */
8
- export declare const CURSOR_MCP_RELPATH = ".cursor/mcp.json";
9
- /** The directory whose presence signals Cursor is installed for this user. */
10
- export declare const CURSOR_DIR_RELPATH = ".cursor";
11
- export declare const CursorMergeStatus: {
12
- readonly APPLY: "apply";
13
- readonly ALREADY: "already";
14
- readonly MANUAL: "manual";
15
- };
16
- export type CursorMergeStatus = (typeof CursorMergeStatus)[keyof typeof CursorMergeStatus];
17
- interface CursorMergeResult {
18
- status: CursorMergeStatus;
19
- /** Full file content to write (2-space JSON, trailing newline). Unchanged when not `apply`. */
20
- content: string;
21
- }
22
- export declare function cursorServerEntry(): Record<string, unknown>;
23
- export declare function mergeCursorConfig(existing: string | null): CursorMergeResult;
24
- export {};
@@ -1,80 +0,0 @@
1
- /**
2
- * Global Cursor registration for `reticle init`. Cursor has no CLI, but its global MCP config is a
3
- * small dedicated file (`~/.cursor/mcp.json`) — safe to merge directly (unlike `~/.claude.json`).
4
- * We add the `reticle` server once at this global path so every project picks it up. Unparseable
5
- * (jsonc/comment) files bail to manual rather than being rewritten.
6
- */
7
- import { NPX, MCP_SERVER_NAME, npxServerArgs, isReticleRegistration } from './mcp.js';
8
- /** Path of Cursor's global MCP config, relative to the user's home directory. */
9
- export const CURSOR_MCP_RELPATH = '.cursor/mcp.json';
10
- /** The directory whose presence signals Cursor is installed for this user. */
11
- export const CURSOR_DIR_RELPATH = '.cursor';
12
- export const CursorMergeStatus = {
13
- APPLY: 'apply',
14
- ALREADY: 'already',
15
- MANUAL: 'manual',
16
- };
17
- export function cursorServerEntry() {
18
- return { command: NPX, args: npxServerArgs() };
19
- }
20
- function parseConfig(existing) {
21
- if (null === existing || 0 === existing.trim().length)
22
- return { ok: true, config: {} };
23
- try {
24
- const parsed = JSON.parse(existing);
25
- // Valid JSON that is not a PLAIN OBJECT — `[]`, `3`, `"x"`, `null` — used to fall through to an
26
- // empty config, and the file was then rewritten wholesale. Unparseable JSON was already handled
27
- // conservatively; this adjacent case destroyed the file instead. Whatever is in there is the
28
- // user's, and we do not understand it, so we do not touch it.
29
- if (typeof parsed !== 'object' || null === parsed || Array.isArray(parsed))
30
- return { ok: false };
31
- return { ok: true, config: parsed };
32
- }
33
- catch {
34
- return { ok: false };
35
- }
36
- }
37
- /**
38
- * Should this entry be left exactly as it is?
39
- *
40
- * Two different reasons to leave one alone, and only one of them is "it is correct":
41
- * - it already matches what we would write; or
42
- * - it is not an entry WE wrote. Somebody pointing `reticle` at their own local build is a
43
- * deliberate choice, and silently replacing it with the published npx command would break their
44
- * setup to fix a problem they do not have.
45
- *
46
- * What IS repaired is a stale entry of our own shape — an old pin, a command that has moved — which
47
- * presence-only idempotency reported as "already registered" and never fixed, so an upgrade could
48
- * not repair the thing an upgrade exists to repair. A WRONG entry counts as ours to repair too:
49
- * `["@reticlehq/core","mcp"]` was reported from the field, and `core` has no MCP server in it.
50
- */
51
- function leaveEntryAlone(existing) {
52
- if (JSON.stringify(existing) === JSON.stringify(cursorServerEntry()))
53
- return true;
54
- if (typeof existing !== 'object' || null === existing)
55
- return true;
56
- const record = existing;
57
- const tokens = [record.command, record.args].flat().filter((v) => 'string' === typeof v);
58
- return !isReticleRegistration(tokens);
59
- }
60
- export function mergeCursorConfig(existing) {
61
- const parsed = parseConfig(existing);
62
- if (!parsed.ok) {
63
- return { status: CursorMergeStatus.MANUAL, content: existing ?? '' };
64
- }
65
- const config = parsed.config;
66
- const servers = config.mcpServers ?? {};
67
- // Presence alone used to mean "already registered", so a `reticle` entry left by an older release —
68
- // a command that no longer exists, or a stale pin — was reported done and never repaired. An
69
- // upgrade could not fix a bad entry, which is exactly when it needs to. Only OUR key is replaced;
70
- // every other server and top-level key is carried through untouched by the spread below.
71
- if (Object.prototype.hasOwnProperty.call(servers, MCP_SERVER_NAME) &&
72
- leaveEntryAlone(servers[MCP_SERVER_NAME])) {
73
- return { status: CursorMergeStatus.ALREADY, content: existing ?? '' };
74
- }
75
- const merged = {
76
- ...config,
77
- mcpServers: { ...servers, [MCP_SERVER_NAME]: cursorServerEntry() },
78
- };
79
- return { status: CursorMergeStatus.APPLY, content: `${JSON.stringify(merged, null, 2)}\n` };
80
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * First-run readiness: the agent often issues its first tool call in the window between `reticle init`
3
- * and the app's SDK actually connecting its WebSocket — so a naive resolve throws "no session". This
4
- * lets the agent (via reticle_wait_ready) BLOCK briefly until a session appears instead of failing the
5
- * race, smoothing the most common first-5-minutes footgun.
6
- *
7
- * Pure control loop: the session count, clock, and sleep are all injected, so it is unit-tested with
8
- * no real timers — and in production it polls a real `setTimeout` sleep.
9
- */
10
- interface WaitForReadyOptions {
11
- /** Live count of connected sessions (SessionManager.count). */
12
- count: () => number;
13
- /** Give up after this much elapsed time. */
14
- timeoutMs: number;
15
- /** Injected monotonic clock (ms). */
16
- now: () => number;
17
- /** Injected delay between polls. */
18
- sleep: (ms: number) => Promise<void>;
19
- /** Poll interval (ms). Default 25 — see DEFAULT_POLL_MS. */
20
- pollMs?: number;
21
- }
22
- /**
23
- * One-line orientation for a FRESH agent — the Reticle loop, returned by reticle_wait_ready (the first call)
24
- * so an agent learns how to drive Reticle without reading docs. Named so it is not a free string; kept
25
- * terse on purpose (it rides the first response, where token budget is tightest).
26
- */
27
- export declare const RETICLE_LOOP_GUIDE: string;
28
- /**
29
- * Resolve `true` as soon as at least one session is connected, or `false` if the timeout elapses
30
- * first. Returns immediately when a session is already connected (the common, already-ready case —
31
- * so this never adds latency on the happy path, including the benchmark which always has a session).
32
- */
33
- export declare function waitForReady(opts: WaitForReadyOptions): Promise<boolean>;
34
- export {};