@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,90 @@
1
+ import { CORE_TOOL_NAMES, EXTENDED_TOOL_NAMES } from '../tools/tool-surface.js';
2
+ /**
3
+ * Which tools this session called, and how reachable each one was.
4
+ *
5
+ * The generalisation of the capture ledger: it recorded a short list of read tools to answer one
6
+ * feature's disproof, and the question that followed is the same question asked of the whole table —
7
+ * most of the surface has never been observed being called at all, and a decision about what to cut
8
+ * or consolidate is about to be made. This is the instrument for that decision, and nothing else.
9
+ *
10
+ * ## Why the count alone is not enough
11
+ *
12
+ * A tool that goes uncalled because nobody was told it exists and a tool that goes uncalled because
13
+ * nobody wants it look identical in a call count. They are opposite conclusions. So every count is
14
+ * reported beside the tool's REACHABILITY — advertised on the default surface, advertised only on
15
+ * the extended one, or reachable solely by naming it to `reticle_run` — and the interesting cell is
16
+ * the one where an agent found a run-only tool anyway.
17
+ *
18
+ * ## The tiers are derived, never restated
19
+ *
20
+ * `tierOfTool` reads `CORE_TOOL_NAMES` and `EXTENDED_TOOL_NAMES` directly. A tool promoted or
21
+ * demoted between surfaces changes tier here with nothing edited. A local copy of either list would
22
+ * go stale on the first move and would then be wrong about the exact axis this exists to measure —
23
+ * and it would be wrong silently, in a report used to justify deleting things.
24
+ *
25
+ * ## What these numbers do NOT establish
26
+ *
27
+ * - **One session is not a hit rate.** This is evidence about ONE run by ONE agent against ONE app.
28
+ * A hit rate is a distribution over many sessions; a single session quoted as one is an anecdote
29
+ * with a denominator drawn on it.
30
+ * - **Never-called is not useless.** A tool one team reaches for once a month in an emergency and a
31
+ * tool nobody has ever called are the same zero here. Frequency alone cannot separate them, so a
32
+ * cut decided on frequency alone will delete the wrong things.
33
+ * - **Absent is not zero.** A session with no ledger reports `observed: false`. It has not
34
+ * established that no tools were called; it has established that nothing was watching.
35
+ * - The tools that ran BEFORE this instrument existed in the process, or against a session this
36
+ * daemon adopted after the fact, are simply missing rather than counted low.
37
+ */
38
+ /** How an agent could have reached a tool. Derived from the surface sets — see above. */
39
+ export const ToolTier = {
40
+ /** Advertised directly on the surface every user gets. */
41
+ DEFAULT: 'default',
42
+ /** Advertised only when the full schema-carrying surface is switched on. */
43
+ EXTENDED: 'extended',
44
+ /** Advertised nowhere: reachable only by naming it to `reticle_run`. */
45
+ RUN_ONLY: 'run-only',
46
+ };
47
+ /** Which surface would have put this tool in front of an agent, read off that surface's own set. */
48
+ export function tierOfTool(tool) {
49
+ if (CORE_TOOL_NAMES.has(tool))
50
+ return ToolTier.DEFAULT;
51
+ if (EXTENDED_TOOL_NAMES.has(tool))
52
+ return ToolTier.EXTENDED;
53
+ return ToolTier.RUN_ONLY;
54
+ }
55
+ function tally(tools) {
56
+ return {
57
+ default: tools.filter((tier) => ToolTier.DEFAULT === tier).length,
58
+ extended: tools.filter((tier) => ToolTier.EXTENDED === tier).length,
59
+ runOnly: tools.filter((tier) => ToolTier.RUN_ONLY === tier).length,
60
+ };
61
+ }
62
+ /**
63
+ * Cross one session's per-tool counts with the tool table.
64
+ *
65
+ * `allTools` is the DISPATCHABLE table — the names `reticle_run` will accept — passed in rather than
66
+ * imported, because the table is assembled in the module that owns this tool and importing it back
67
+ * would close a cycle. A tool counted but absent from the table still appears, so a name the table
68
+ * lost is visible rather than dropped.
69
+ */
70
+ export function foldToolHitRate(input) {
71
+ const { calls } = input;
72
+ if (calls === undefined)
73
+ return { observed: false };
74
+ const called = [...calls]
75
+ .map(([tool, count]) => ({ tool, tier: tierOfTool(tool), calls: count }))
76
+ .sort((a, b) => b.calls - a.calls || a.tool.localeCompare(b.tool));
77
+ const neverCalled = input.allTools
78
+ .filter((tool) => !calls.has(tool))
79
+ .map((tool) => ({ tool, tier: tierOfTool(tool) }))
80
+ .sort((a, b) => a.tool.localeCompare(b.tool));
81
+ return {
82
+ observed: true,
83
+ cells: {
84
+ called: tally(called.map((entry) => entry.tier)),
85
+ neverCalled: tally(neverCalled.map((entry) => entry.tier)),
86
+ },
87
+ called,
88
+ neverCalled,
89
+ };
90
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Is this verdict being drawn over code nobody said anything about?
3
+ *
4
+ * Declaring intent is opt-in, and nothing has ever noticed that a change landed and nobody said what
5
+ * it was supposed to make true. That is the moment it matters: a change with no declared intent can
6
+ * only be verified against itself, which is the definition of a false green.
7
+ *
8
+ * ## Why this fires on EVERY verdict, not once per change
9
+ *
10
+ * The house rule is that a gap nobody hit is a backlog, and a backlog reported as a finding is how
11
+ * an agent learns to stop reading findings. This clears that bar on the same test every other
12
+ * instrumentation gap clears it on: **did the absence make THIS verdict weaker?** An unwatched store
13
+ * weakens every state assertion made over it, not merely the first, so it is reported on every one.
14
+ * An undeclared change is the same shape — while nothing says what the edit was for, each verdict
15
+ * drawn after it is checked against nothing but itself, and that is true of the fifth one exactly as
16
+ * it was of the first. Reporting it once and then falling silent would leave four verdicts carrying
17
+ * a weakness they no longer disclose, which is the failure mode this whole surface exists to prevent.
18
+ *
19
+ * It is also not a backlog, because it is not a survey of the project: it is answered from what this
20
+ * session observed (an epoch that moved) about the verdict being returned right now, and it is
21
+ * closed by one call the agent can make immediately. A finding that stays open only because the
22
+ * agent has not made that call is a finding about the current verdict, not a chore.
23
+ *
24
+ * ## The silences, and why each one is required
25
+ *
26
+ * - **An edit was actually OBSERVED.** The epoch degrades to `NO_EDITS_OBSERVED` outside Vite, and
27
+ * that means "no edits observed", never "no edits happened" — a finding built on an absence
28
+ * Reticle cannot see would be a fabrication.
29
+ * - **The ledger holds nothing undischarged.** An open intent IS the declaration; silence is the
30
+ * correct response to it, and declaring one is what turns this off from the next verdict on.
31
+ *
32
+ * ## Why "covers the work" is answered as "anything open at all"
33
+ *
34
+ * The tempting answer is to match the intent's `surface` against the files that changed. Reticle
35
+ * cannot do that honestly: it observes that an epoch advanced, not WHAT advanced — the hot-update
36
+ * channel reports a counter, not a diff — so any file-level matching would be inference dressed as a
37
+ * fact, and it would fire on an agent that had declared its intent perfectly well. Over-reporting on
38
+ * a correct agent is the direction of error that trains it to stop reading findings.
39
+ *
40
+ * So the question asked is the coarse one, and it is asked honestly: has the agent declared anything
41
+ * that is still outstanding? If it has, it is working to a stated purpose and this stays quiet.
42
+ */
43
+ /**
44
+ * `openIntents` is a thunk rather than a list because reading the ledger touches disk, and the epoch
45
+ * check answers without it for every page that never reported a hot update — which is most of them.
46
+ * Once an edit HAS been observed the read happens per verdict, and that is the intended cost: the
47
+ * ledger is the only thing that can end it, so it has to be re-asked each time.
48
+ */
49
+ export declare function isChangeUndeclared(
50
+ /** The epoch the session is under now. Undefined when the page never stamped one. */
51
+ currentEpoch: number | undefined, openIntents: () => Promise<readonly unknown[]>): Promise<boolean>;
@@ -0,0 +1,56 @@
1
+ import { NO_EDITS_OBSERVED } from '@reticlehq/core';
2
+ /**
3
+ * Is this verdict being drawn over code nobody said anything about?
4
+ *
5
+ * Declaring intent is opt-in, and nothing has ever noticed that a change landed and nobody said what
6
+ * it was supposed to make true. That is the moment it matters: a change with no declared intent can
7
+ * only be verified against itself, which is the definition of a false green.
8
+ *
9
+ * ## Why this fires on EVERY verdict, not once per change
10
+ *
11
+ * The house rule is that a gap nobody hit is a backlog, and a backlog reported as a finding is how
12
+ * an agent learns to stop reading findings. This clears that bar on the same test every other
13
+ * instrumentation gap clears it on: **did the absence make THIS verdict weaker?** An unwatched store
14
+ * weakens every state assertion made over it, not merely the first, so it is reported on every one.
15
+ * An undeclared change is the same shape — while nothing says what the edit was for, each verdict
16
+ * drawn after it is checked against nothing but itself, and that is true of the fifth one exactly as
17
+ * it was of the first. Reporting it once and then falling silent would leave four verdicts carrying
18
+ * a weakness they no longer disclose, which is the failure mode this whole surface exists to prevent.
19
+ *
20
+ * It is also not a backlog, because it is not a survey of the project: it is answered from what this
21
+ * session observed (an epoch that moved) about the verdict being returned right now, and it is
22
+ * closed by one call the agent can make immediately. A finding that stays open only because the
23
+ * agent has not made that call is a finding about the current verdict, not a chore.
24
+ *
25
+ * ## The silences, and why each one is required
26
+ *
27
+ * - **An edit was actually OBSERVED.** The epoch degrades to `NO_EDITS_OBSERVED` outside Vite, and
28
+ * that means "no edits observed", never "no edits happened" — a finding built on an absence
29
+ * Reticle cannot see would be a fabrication.
30
+ * - **The ledger holds nothing undischarged.** An open intent IS the declaration; silence is the
31
+ * correct response to it, and declaring one is what turns this off from the next verdict on.
32
+ *
33
+ * ## Why "covers the work" is answered as "anything open at all"
34
+ *
35
+ * The tempting answer is to match the intent's `surface` against the files that changed. Reticle
36
+ * cannot do that honestly: it observes that an epoch advanced, not WHAT advanced — the hot-update
37
+ * channel reports a counter, not a diff — so any file-level matching would be inference dressed as a
38
+ * fact, and it would fire on an agent that had declared its intent perfectly well. Over-reporting on
39
+ * a correct agent is the direction of error that trains it to stop reading findings.
40
+ *
41
+ * So the question asked is the coarse one, and it is asked honestly: has the agent declared anything
42
+ * that is still outstanding? If it has, it is working to a stated purpose and this stays quiet.
43
+ */
44
+ /**
45
+ * `openIntents` is a thunk rather than a list because reading the ledger touches disk, and the epoch
46
+ * check answers without it for every page that never reported a hot update — which is most of them.
47
+ * Once an edit HAS been observed the read happens per verdict, and that is the intended cost: the
48
+ * ledger is the only thing that can end it, so it has to be re-asked each time.
49
+ */
50
+ export async function isChangeUndeclared(
51
+ /** The epoch the session is under now. Undefined when the page never stamped one. */
52
+ currentEpoch, openIntents) {
53
+ if (undefined === currentEpoch || NO_EDITS_OBSERVED === currentEpoch)
54
+ return false;
55
+ return 0 === (await openIntents()).length;
56
+ }
@@ -76,6 +76,22 @@ interface VerifiedInputs {
76
76
  * Empty means nothing went unread.
77
77
  */
78
78
  outcomeUnread?: readonly string[];
79
+ /**
80
+ * The declared consequence that held does not depend on the response body — an exact string
81
+ * rendered, a store path, a signal, a route, or an element located by role / name / testid. See
82
+ * `declaresBodyIndependentChannel`.
83
+ *
84
+ * The unread-body clause exists for the case where the body is the ONLY channel that could have
85
+ * contradicted the screen (a 200 with batch/GraphQL errors inside). When a body-independent
86
+ * declaration held, that is no longer true, and grading `unknown` costs a real verdict. The unread
87
+ * labels still ride out in `because`, the way absence-derived contradictions still ride out in
88
+ * `contradictions`.
89
+ *
90
+ * Narrow: it does not soften a 202, an observed contradiction, a dirty capture, or a net-only
91
+ * declaration. `declaredConsequence` alone is not enough — a `{ kind: "net", status: 200 }` is a
92
+ * declaration whose remaining channel IS the body.
93
+ */
94
+ independentOfBody?: boolean;
79
95
  /**
80
96
  * What the wait was for and what the window held when it ended — read ONLY by the two clauses that
81
97
  * answer UNSETTLED, which is the commonest reason a verdict comes back `unknown` and was also the
@@ -31,6 +31,24 @@ export function decideVerified(inputs) {
31
31
  'to repeat',
32
32
  };
33
33
  }
34
+ // ABOVE the failure clause, for the third time and the same reason: absence is not evidence.
35
+ //
36
+ // Omitting `until` waits for the page to SETTLE. Nobody declared a consequence, so a settle that
37
+ // times out says "the page was still busy when I stopped looking" — the definition of an
38
+ // absence-derived finding. The failure clause below took it at face value and returned
39
+ // `verified: "no"` with `because: 'the declared consequence did not hold'`, naming a consequence
40
+ // that does not exist. Measured on the hard fixture, whose push updates mean it never goes idle:
41
+ // a healthy pagination click was graded NO. Any app with polling or a live feed gets accused of a
42
+ // defect for being alive, which is the false NEGATIVE the ABSENCE_DERIVED doctrine exists to stop.
43
+ if (false === pass && false === inputs.declaredConsequence) {
44
+ return {
45
+ verified: Verified.UNKNOWN,
46
+ verifiedReason: VerifiedReason.UNSETTLED,
47
+ because: 'no consequence was declared, so this only waited for the page to go idle and it never ' +
48
+ 'did — that is a statement about when Reticle stopped looking, not about the app. Pass ' +
49
+ '`until` naming what the action should cause (a signal, a request, a route, or store state)',
50
+ };
51
+ }
34
52
  // A failed assertion is the most actionable fact there is; it leads — including over
35
53
  // `alreadyTrue`, because a condition that held before AND fails now is a real regression.
36
54
  if (false === pass) {
@@ -82,6 +100,27 @@ export function decideVerified(inputs) {
82
100
  inputs.unsettled !== undefined &&
83
101
  !openWrite &&
84
102
  contradictions.every((c) => c.kind === ContradictionKind.REQUEST_NEVER_SETTLED);
103
+ // `signal-without-consequence` gets its own sentence, because the generic one is FALSE for it.
104
+ //
105
+ // That sentence says the window "closed before the app finished" and then explains that a poll or
106
+ // a timer kept the page busy. Nothing kept this page busy — nothing happened at all, which is the
107
+ // entire finding. Measured on the bench fixture: the correct UNKNOWN arrived wearing an
108
+ // explanation that told the agent to wait longer or assert something else, when the fact to act on
109
+ // was that the app announced a consequence it did not deliver. A right verdict with a wrong reason
110
+ // sends an agent to the wrong place, which costs as much as the wrong verdict did.
111
+ const signalOnly = contradictions.length > 0 &&
112
+ contradictions.every((c) => c.kind === ContradictionKind.SIGNAL_WITHOUT_CONSEQUENCE);
113
+ if (signalOnly) {
114
+ return {
115
+ verified: Verified.UNKNOWN,
116
+ verifiedReason: VerifiedReason.EVIDENCE_INCOMPLETE,
117
+ because: 'the app fired the signal you waited for and NOTHING else moved — no DOM, no store, no ' +
118
+ 'route, no request — so the only evidence is the app saying so. A signal emitted from the ' +
119
+ 'value the app was ASKED for, rather than the one it committed, looks exactly like this. ' +
120
+ 'Check the consequence directly (reticle_snapshot for what rendered, reticle_state for what ' +
121
+ 'the store holds) before trusting it',
122
+ };
123
+ }
85
124
  if (contradictions.length > 0 && !settlementOnly) {
86
125
  const kinds = contradictions.map((c) => c.kind).join(', ');
87
126
  return {
@@ -113,9 +152,24 @@ export function decideVerified(inputs) {
113
152
  // with routeChanges 0, because the text was the nav link already on screen — the real navigation
114
153
  // landed 1.8s later. UNKNOWN rather than NO on purpose: the app may well be fine, and reporting a
115
154
  // failure we did not observe would be its own false claim.
155
+ //
156
+ // `no-fault` rather than `unknown`, once the window settled — the same distinction
157
+ // `nothing_declared` already draws, gated the same way.
158
+ //
159
+ // The two words ask for opposite things. `unknown` means LOOK AGAIN WITH BETTER COVERAGE, so an
160
+ // agent reading it reasonably concludes the engine could not see, and spends its remaining turns
161
+ // enabling capture, widening timeouts and re-driving — none of which can help, because the engine
162
+ // saw the whole window and there was nothing wrong with the evidence. The fault is in the
163
+ // ASSERTION: it named something that was true before the action, so it cannot tell success from a
164
+ // no-op. `no-fault` says exactly that, and `because` already carried the remedy that `unknown`
165
+ // was burying.
166
+ //
167
+ // Gated on `true === settled` for the same reason `nothing_declared` is: no-fault claims the whole
168
+ // window was seen, and a call that returned before the app stopped moving has not earned that.
169
+ // Unsettled stays `unknown`, which is then the honest word for it.
116
170
  if (true === inputs.alreadyTrue) {
117
171
  return {
118
- verified: Verified.UNKNOWN,
172
+ verified: true === settled ? Verified.NO_FAULT : Verified.UNKNOWN,
119
173
  verifiedReason: VerifiedReason.ALREADY_TRUE,
120
174
  because: 'the declared consequence was already true before this action, so it proves nothing about it — assert something the action CHANGES (a signal, a request, a route, or store state)',
121
175
  };
@@ -142,7 +196,21 @@ export function decideVerified(inputs) {
142
196
  // Gated on `true === settled` rather than on a caller's say-so. A call that returned before the app
143
197
  // stopped moving has not earned this, whatever it declared — which is what stops `no-fault` from
144
198
  // becoming the green-forever button an always-available "nothing was wrong" would be.
145
- if (honesty.grade === HonestyGrade.NONE) {
199
+ // Two ways to have proved nothing, and only one of them used to be caught. Grade NONE is the
200
+ // engine's own reading — no signal, no request, no state change, nothing pinned. An explicit
201
+ // `declaredConsequence: false` is the CALLER saying it asserted nothing, which `act_and_wait`
202
+ // reports whenever `until` is omitted (an omitted `until` becomes `{kind:"settled"}` — a
203
+ // sleep-replacement, not an assertion).
204
+ //
205
+ // Grading alone missed the second case, because a click that merely moves the DOM lifts the grade
206
+ // to PRESENCE and walks past this branch into a full green. Measured live: clicking a submit
207
+ // button, and an inert text input, with no `until` both returned `verified:"yes"` /
208
+ // `verifiedReason:"proved"`. The claim it printed — "assertion held at presence grade" — was true
209
+ // about a grade and false about an assertion, because there was no assertion.
210
+ //
211
+ // It compounds: `reticle_context` persists proven claims, so an undeclared green becomes
212
+ // established fact for the agent that re-enters after a compaction.
213
+ if (honesty.grade === HonestyGrade.NONE || false === inputs.declaredConsequence) {
146
214
  if (true === settled) {
147
215
  return {
148
216
  verified: Verified.NO_FAULT,
@@ -178,7 +246,15 @@ export function decideVerified(inputs) {
178
246
  //
179
247
  // UNKNOWN, not NO: nothing is known to have failed. The remedy is in the sentence, because an
180
248
  // agent that cannot act on a caveat will learn to skip it.
181
- if (outcomeUnread !== undefined && outcomeUnread.length > 0) {
249
+ //
250
+ // Skipped when the caller declared a consequence that does not depend on the body and it held.
251
+ // The clause was written for the body being the ONLY remaining channel; a unique row on screen, a
252
+ // store path, or a signal that held is a different channel, and grading unknown there cost a real
253
+ // verdict (a 201 plus the exact message text, agent went to enable capture instead of finishing).
254
+ // The unread write still rides out in `because`. A net-only declaration does not skip: then the
255
+ // body is still the remaining channel.
256
+ const unreadHeldIndependently = declaredHeld && true === inputs.independentOfBody;
257
+ if (outcomeUnread !== undefined && outcomeUnread.length > 0 && !unreadHeldIndependently) {
182
258
  return {
183
259
  verified: Verified.UNKNOWN,
184
260
  verifiedReason: VerifiedReason.OUTCOME_UNREAD,
@@ -214,11 +290,14 @@ export function decideVerified(inputs) {
214
290
  const notIdle = false === settled
215
291
  ? ', though the page never went idle — this rests on the consequence you declared, not on the page going quiet'
216
292
  : '';
293
+ const unreadCaveat = unreadHeldIndependently && outcomeUnread !== undefined && outcomeUnread.length > 0
294
+ ? `, though a write returned 2xx with a response body that was never recorded (${outcomeUnread.join('; ')}) — a 200 describes the transport, not the result`
295
+ : '';
217
296
  return {
218
297
  verified: Verified.YES,
219
298
  verifiedReason: VerifiedReason.PROVED,
220
299
  because: true === honesty.coverage?.partial
221
- ? `assertion held at ${honesty.grade} grade with no channel disagreeing, but coverage was PARTIAL — see \`coverage\` for what went unobserved${notIdle}`
222
- : `assertion held at ${honesty.grade} grade over a clean capture with no channel disagreeing${notIdle}`,
300
+ ? `assertion held at ${honesty.grade} grade with no channel disagreeing, but coverage was PARTIAL — see \`coverage\` for what went unobserved${notIdle}${unreadCaveat}`
301
+ : `assertion held at ${honesty.grade} grade over a clean capture with no channel disagreeing${notIdle}${unreadCaveat}`,
223
302
  };
224
303
  }
@@ -50,4 +50,3 @@ export interface SharedServer {
50
50
  export declare function createSharedServer(options?: {
51
51
  token?: string;
52
52
  }): SharedServer;
53
- export declare function driveSchemeRefusal(url: string): string | undefined;
@@ -6,6 +6,7 @@ import { noteAgentPeer, PEER_VERSION_PARAM, PEER_CONTRACT_PARAM } from './versio
6
6
  import { log } from './log.js';
7
7
  import { reportMcpConnected } from './telemetry/mcp-connection.js';
8
8
  import { isLoopbackHost, isLocalWebOrigin, isLoopbackPeer, requestToken, tokensMatch, } from './bridge/token-auth.js';
9
+ import { LOOPBACK_IDLE_MS } from './loopback-agent.js';
9
10
  /**
10
11
  * Creates a shared HTTP server that handles both the WebSocket bridge (browser SDK) and the
11
12
  * SSE MCP transport (Claude/agent). Does NOT call listen — caller controls that.
@@ -258,6 +259,17 @@ export function createSharedServer(options = {}) {
258
259
  });
259
260
  });
260
261
  }
262
+ /**
263
+ * The daemon's half of the keep-alive pact with `loopbackAgent`.
264
+ *
265
+ * Node's default is 5 seconds, and the proxy's POST leg is one request per JSON-RPC message. An
266
+ * agent thinks for longer than 5s between tool calls essentially always, so with the default every
267
+ * tool call had to open a fresh socket however the client was configured — which on Windows ends
268
+ * in `ENOBUFS`. This has to stay ABOVE `LOOPBACK_IDLE_MS` so the CLIENT is always the side that
269
+ * retires an idle socket: if the server closed first, the proxy would keep picking up sockets that
270
+ * were already gone. It also has to stay below Node's 60s default `headersTimeout`.
271
+ */
272
+ httpServer.keepAliveTimeout = LOOPBACK_IDLE_MS + 10_000;
261
273
  return {
262
274
  httpServer,
263
275
  attachMcp,
@@ -330,7 +342,7 @@ function schemeOf(url) {
330
342
  return undefined;
331
343
  }
332
344
  }
333
- export function driveSchemeRefusal(url) {
345
+ function driveSchemeRefusal(url) {
334
346
  const scheme = schemeOf(url);
335
347
  if (scheme !== undefined && DRIVEABLE_SCHEMES.includes(scheme))
336
348
  return undefined;
@@ -18,8 +18,6 @@
18
18
  * Pure functions over already-collected reports — the driving is `reticle_crawl`'s job, and running
19
19
  * it across a commit range is a shell loop. This is only the part that must not be done by eye.
20
20
  */
21
- /** The contradiction kinds — cross-channel disagreement, as opposed to a single-channel fault. */
22
- export declare const CONTRADICTION_KINDS: readonly string[];
23
21
  export interface HuntAnomaly {
24
22
  kind: string;
25
23
  ref?: string;
@@ -19,7 +19,7 @@
19
19
  * it across a commit range is a shell loop. This is only the part that must not be done by eye.
20
20
  */
21
21
  /** The contradiction kinds — cross-channel disagreement, as opposed to a single-channel fault. */
22
- export const CONTRADICTION_KINDS = [
22
+ const CONTRADICTION_KINDS = [
23
23
  'ui-advanced-request-failed',
24
24
  'signal-contradicted',
25
25
  'response-ignored',
@@ -1,18 +1,30 @@
1
- import { type ImpactCounts, type ImpactSnapshot } from '@reticlehq/core';
1
+ import { type ImpactCounts, type ImpactDefect, type ImpactSnapshot } from '@reticlehq/core';
2
2
  import { ImpactStore, type ImpactFoldMeta } from './impact-store.js';
3
- /** Wire the recorder to a project. Idempotent; the first root wins for the daemon's lifetime. */
3
+ /**
4
+ * Wire the DEFAULT root — the daemon's own. Idempotent; the first one wins.
5
+ *
6
+ * Still idempotent, and still first-wins, because the daemon's own root genuinely does not change
7
+ * while it runs. What changed is that this is no longer the ONLY root: a call that knows its
8
+ * project passes one, and this is the fallback for calls that do not.
9
+ */
4
10
  export declare function initImpact(opts: {
5
11
  reticleRoot: string | undefined;
6
12
  projectName?: string;
7
13
  now?: () => number;
8
14
  }): ImpactStore | undefined;
9
- /** The live store, if one has been initialised. */
10
- export declare function impactStore(): ImpactStore | undefined;
11
- /** Test seam: drop the singleton so a suite can start from a clean record. */
15
+ /** The live store for a root (default when omitted), if one has been initialised. */
16
+ export declare function impactStore(root?: string): ImpactStore | undefined;
17
+ /** Test seam: drop the stores so a suite can start from a clean record. */
12
18
  export declare function resetImpactForTest(): void;
13
- /** Record one delta, if the recorder is live. Never throws: stats must not break a tool call. */
14
- export declare function recordImpact(delta: Partial<ImpactCounts>, meta?: ImpactFoldMeta): void;
15
- export declare function impactSnapshot(): ImpactSnapshot | undefined;
19
+ /**
20
+ * Record one delta against a project's ledger. Never throws: stats must not break a tool call.
21
+ *
22
+ * `root` is the `.reticle` directory this call's evidence belongs to. Omitted means "could not tell
23
+ * which project", which falls back to the daemon's own root — the old behaviour, kept for the paths
24
+ * that genuinely have no session to ask.
25
+ */
26
+ export declare function recordImpact(delta: Partial<ImpactCounts>, meta?: ImpactFoldMeta, root?: string): void;
27
+ export declare function impactSnapshot(root?: string): ImpactSnapshot | undefined;
16
28
  /**
17
29
  * What one tool call did, as counters.
18
30
  *
@@ -21,3 +33,14 @@ export declare function impactSnapshot(): ImpactSnapshot | undefined;
21
33
  * the same rule the product states to agents - everything else moves or reads the app.
22
34
  */
23
35
  export declare function deltaForToolResult(raw: unknown, durationMs: number, refused: boolean): Partial<ImpactCounts>;
36
+ /**
37
+ * What one FAILED verdict was, in a line.
38
+ *
39
+ * Only a verified:"no" produces one. That is the same rule the counters follow and the same rule
40
+ * the product states to agents: an "unknown" is not a defect, it is Reticle admitting it could not
41
+ * tell — recording those here would fill the user's short list with the tool's own blind spots.
42
+ *
43
+ * Everything is read from the result the tool already returned, so this costs nothing extra and
44
+ * cannot disagree with the verdict it describes.
45
+ */
46
+ export declare function defectForToolResult(raw: unknown, at: number): ImpactDefect | undefined;
@@ -1,43 +1,90 @@
1
- import { Verified } from '@reticlehq/core';
1
+ import { Verified, } from '@reticlehq/core';
2
2
  import { estimateTokens } from '../session/output-budget.js';
3
3
  import { ImpactStore } from './impact-store.js';
4
4
  /**
5
- * The daemon's single impact recorder.
5
+ * The daemon's impact recorders — ONE PER PROJECT ROOT.
6
6
  *
7
- * One store per daemon, created on the first call that knows the project's `.reticle` root. A
8
- * module singleton for the same reason the tool counters are: the dispatch chokepoint has to be
9
- * able to record without every tool threading a store through its arguments, and a second recorder
10
- * would silently split the user's own history in half.
7
+ * This was a single module-level store, and "the first root wins for the daemon's lifetime" was
8
+ * written down as if it were a design decision. It is only correct for a daemon that serves one
9
+ * project, and this daemon has never been that: `artifactRootFor` exists precisely because one
10
+ * daemon serves many, and nine modules already route their artifacts by the SESSION's project.
11
+ *
12
+ * Measured, not theorised: with a daemon rooted at repo A, two verdicts driven against app B landed
13
+ * in A's `impact.json` — B's own ledger never moved — and A's cloud link then carried them to a
14
+ * PRODUCTION dashboard belonging to a different account than B was linked to. The impact ledger is
15
+ * what the whole dashboard is computed from, so this was the one artifact whose misrouting was
16
+ * guaranteed to be visible and billed.
17
+ *
18
+ * Keyed by root path, so two projects driven through one daemon keep separate ledgers, and the
19
+ * daemon's own root remains the answer for anything that cannot name a project.
20
+ */
21
+ /** One store per `.reticle` root. */
22
+ const stores = new Map();
23
+ /** The daemon's own root — used when a call cannot say which project it was for. */
24
+ let defaultRoot;
25
+ /** Held so a per-root store is built with the same clock and project name as the default. */
26
+ let storeOpts = {};
27
+ /** Get-or-create the store for one root. */
28
+ function storeFor(root) {
29
+ const key = root ?? defaultRoot;
30
+ if (key === undefined || 0 === key.length)
31
+ return undefined;
32
+ let found = stores.get(key);
33
+ if (found === undefined) {
34
+ found = new ImpactStore({ ...storeOpts, reticleRoot: key });
35
+ stores.set(key, found);
36
+ }
37
+ return found;
38
+ }
39
+ /**
40
+ * Wire the DEFAULT root — the daemon's own. Idempotent; the first one wins.
41
+ *
42
+ * Still idempotent, and still first-wins, because the daemon's own root genuinely does not change
43
+ * while it runs. What changed is that this is no longer the ONLY root: a call that knows its
44
+ * project passes one, and this is the fallback for calls that do not.
11
45
  */
12
- let store;
13
- /** Wire the recorder to a project. Idempotent; the first root wins for the daemon's lifetime. */
14
46
  export function initImpact(opts) {
15
47
  // No root, no record. Programmatic callers and test doubles build their own deps and are not
16
48
  // obliged to carry one, and a courtesy counter must never be the reason a tool call throws.
17
49
  if (opts.reticleRoot === undefined || 0 === opts.reticleRoot.length)
18
- return store;
19
- store ??= new ImpactStore({ ...opts, reticleRoot: opts.reticleRoot });
20
- return store;
50
+ return storeFor(undefined);
51
+ if (defaultRoot === undefined) {
52
+ defaultRoot = opts.reticleRoot;
53
+ const { projectName, now } = opts;
54
+ storeOpts = {
55
+ ...(projectName === undefined ? {} : { projectName }),
56
+ ...(now === undefined ? {} : { now }),
57
+ };
58
+ }
59
+ return storeFor(opts.reticleRoot);
21
60
  }
22
- /** The live store, if one has been initialised. */
23
- export function impactStore() {
24
- return store;
61
+ /** The live store for a root (default when omitted), if one has been initialised. */
62
+ export function impactStore(root) {
63
+ return storeFor(root);
25
64
  }
26
- /** Test seam: drop the singleton so a suite can start from a clean record. */
65
+ /** Test seam: drop the stores so a suite can start from a clean record. */
27
66
  export function resetImpactForTest() {
28
- store = undefined;
67
+ stores.clear();
68
+ defaultRoot = undefined;
69
+ storeOpts = {};
29
70
  }
30
- /** Record one delta, if the recorder is live. Never throws: stats must not break a tool call. */
31
- export function recordImpact(delta, meta = {}) {
71
+ /**
72
+ * Record one delta against a project's ledger. Never throws: stats must not break a tool call.
73
+ *
74
+ * `root` is the `.reticle` directory this call's evidence belongs to. Omitted means "could not tell
75
+ * which project", which falls back to the daemon's own root — the old behaviour, kept for the paths
76
+ * that genuinely have no session to ask.
77
+ */
78
+ export function recordImpact(delta, meta = {}, root) {
32
79
  try {
33
- store?.record(delta, meta);
80
+ storeFor(root)?.record(delta, meta);
34
81
  }
35
82
  catch {
36
83
  // Counting is a courtesy to the user; failing to count is not worth failing their call over.
37
84
  }
38
85
  }
39
- export function impactSnapshot() {
40
- return store?.snapshot();
86
+ export function impactSnapshot(root) {
87
+ return storeFor(root)?.snapshot();
41
88
  }
42
89
  /**
43
90
  * What one tool call did, as counters.
@@ -71,6 +118,42 @@ export function deltaForToolResult(raw, durationMs, refused) {
71
118
  }
72
119
  return delta;
73
120
  }
121
+ /**
122
+ * What one FAILED verdict was, in a line.
123
+ *
124
+ * Only a verified:"no" produces one. That is the same rule the counters follow and the same rule
125
+ * the product states to agents: an "unknown" is not a defect, it is Reticle admitting it could not
126
+ * tell — recording those here would fill the user's short list with the tool's own blind spots.
127
+ *
128
+ * Everything is read from the result the tool already returned, so this costs nothing extra and
129
+ * cannot disagree with the verdict it describes.
130
+ */
131
+ export function defectForToolResult(raw, at) {
132
+ if (!isRecord(raw) || raw['verified'] !== Verified.NO)
133
+ return undefined;
134
+ const effect = isRecord(raw['effect']) ? raw['effect'] : undefined;
135
+ const verdict = isRecord(raw['verdict']) ? raw['verdict'] : undefined;
136
+ const named = text(effect?.['name']) ?? text(effect?.['testid']) ?? text(effect?.['component']);
137
+ const why = text(verdict?.['failureReason']) ?? text(raw['because']);
138
+ const defect = {
139
+ at,
140
+ // The control that was acted on, when the result names one — "Sign In did not sign in" is a
141
+ // sentence somebody can act on; "a verdict failed" is not.
142
+ title: named === undefined ? (why ?? 'a declared consequence did not hold') : named,
143
+ };
144
+ if (named !== undefined && why !== undefined)
145
+ defect.detail = why;
146
+ const source = text(raw['source']);
147
+ if (source !== undefined)
148
+ defect.source = source;
149
+ return defect;
150
+ }
151
+ function text(value) {
152
+ if ('string' !== typeof value)
153
+ return undefined;
154
+ const trimmed = value.trim();
155
+ return 0 === trimmed.length ? undefined : trimmed;
156
+ }
74
157
  function isRecord(value) {
75
158
  return 'object' === typeof value && null !== value;
76
159
  }