@reticlehq/server 2.4.0 → 2.5.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 (416) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +63 -13
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +2 -11
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +9 -9
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +174 -92
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.js +24 -9
  88. package/dist/flows/empty-flow.d.ts +6 -0
  89. package/dist/flows/empty-flow.js +24 -0
  90. package/dist/flows/flake-store.d.ts +0 -1
  91. package/dist/flows/flake-store.js +1 -1
  92. package/dist/flows/flake.d.ts +2 -1
  93. package/dist/flows/flake.js +2 -2
  94. package/dist/flows/flow-classify.js +12 -0
  95. package/dist/flows/flow-replay-run.js +4 -4
  96. package/dist/flows/flow-replay.d.ts +5 -7
  97. package/dist/flows/flow-replay.js +114 -47
  98. package/dist/flows/flow-report.js +1 -1
  99. package/dist/flows/flow-scope.d.ts +1 -6
  100. package/dist/flows/flow-step-runners.d.ts +26 -6
  101. package/dist/flows/flow-step-runners.js +157 -6
  102. package/dist/flows/flow-success.js +20 -15
  103. package/dist/flows/flow-tools.js +53 -7
  104. package/dist/flows/flows.js +15 -11
  105. package/dist/flows/gate.d.ts +4 -3
  106. package/dist/flows/gate.js +1 -1
  107. package/dist/flows/heal-run.d.ts +0 -8
  108. package/dist/flows/heal-run.js +5 -5
  109. package/dist/flows/heal.js +3 -3
  110. package/dist/flows/predicate-to-expect.d.ts +38 -0
  111. package/dist/flows/predicate-to-expect.js +95 -0
  112. package/dist/flows/replay.d.ts +16 -1
  113. package/dist/flows/replay.js +58 -22
  114. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  115. package/dist/flows/role-anchor-nearest.js +45 -0
  116. package/dist/flows/server-verify.d.ts +1 -1
  117. package/dist/flows/server-verify.js +5 -5
  118. package/dist/flows/verify-change-tools.js +32 -10
  119. package/dist/flows/verify-change-verdict.d.ts +15 -0
  120. package/dist/flows/verify-change-verdict.js +25 -0
  121. package/dist/flows/watch-batcher.d.ts +3 -2
  122. package/dist/honesty/already-true.d.ts +2 -0
  123. package/dist/honesty/already-true.js +31 -0
  124. package/dist/honesty/blind-spots.d.ts +16 -3
  125. package/dist/honesty/blind-spots.js +24 -12
  126. package/dist/honesty/honesty.d.ts +3 -2
  127. package/dist/honesty/honesty.js +4 -4
  128. package/dist/honesty/uncaptured-bodies.js +1 -1
  129. package/dist/honesty/unread-outcome.js +2 -2
  130. package/dist/honesty/verified.d.ts +22 -3
  131. package/dist/honesty/verified.js +48 -8
  132. package/dist/http-server.js +14 -7
  133. package/dist/hunt/hunt-report.d.ts +2 -1
  134. package/dist/hunt/hunt-report.js +2 -2
  135. package/dist/index.d.ts +8 -6
  136. package/dist/index.js +32 -25
  137. package/dist/init/agent-rules.d.ts +2 -1
  138. package/dist/init/agent-rules.js +45 -7
  139. package/dist/init/app-choice.d.ts +20 -0
  140. package/dist/init/app-choice.js +25 -0
  141. package/dist/init/astro-host.d.ts +21 -0
  142. package/dist/init/astro-host.js +27 -0
  143. package/dist/init/astro-patch.d.ts +16 -0
  144. package/dist/init/astro-patch.js +240 -0
  145. package/dist/init/capabilities.js +4 -1
  146. package/dist/init/cra.d.ts +37 -0
  147. package/dist/init/cra.js +90 -0
  148. package/dist/init/cursor.js +39 -4
  149. package/dist/init/desktop-doctor.d.ts +3 -2
  150. package/dist/init/desktop-doctor.js +4 -4
  151. package/dist/init/detect.d.ts +3 -2
  152. package/dist/init/detect.js +16 -5
  153. package/dist/init/mcp-registered.d.ts +12 -0
  154. package/dist/init/mcp-registered.js +14 -0
  155. package/dist/init/mcp.d.ts +27 -2
  156. package/dist/init/mcp.js +40 -4
  157. package/dist/init/next-patch.d.ts +2 -4
  158. package/dist/init/next-patch.js +109 -19
  159. package/dist/init/node-io.js +23 -6
  160. package/dist/init/plan-framework.d.ts +16 -1
  161. package/dist/init/plan-framework.js +132 -8
  162. package/dist/init/plan.d.ts +73 -8
  163. package/dist/init/plan.js +133 -34
  164. package/dist/init/project-id.d.ts +5 -9
  165. package/dist/init/project-id.js +10 -22
  166. package/dist/init/run.d.ts +5 -0
  167. package/dist/init/run.js +210 -36
  168. package/dist/init/slash-command.d.ts +9 -0
  169. package/dist/init/slash-command.js +10 -1
  170. package/dist/init/snippets.d.ts +7 -10
  171. package/dist/init/snippets.js +44 -15
  172. package/dist/init/vite-config.js +1 -1
  173. package/dist/init/workspace-apps.d.ts +23 -0
  174. package/dist/init/workspace-apps.js +68 -0
  175. package/dist/input/driven-redaction.js +1 -1
  176. package/dist/input/network-detail.js +8 -8
  177. package/dist/input/network-mock-tools.js +2 -2
  178. package/dist/input/network-mock.js +4 -4
  179. package/dist/input/real-input.js +8 -3
  180. package/dist/input/scroll-find.js +2 -2
  181. package/dist/input/scroll-tools.d.ts +0 -5
  182. package/dist/input/scroll-tools.js +11 -4
  183. package/dist/input/viewport-tools.js +2 -2
  184. package/dist/journal/ambient-store.d.ts +0 -2
  185. package/dist/journal/ambient-store.js +1 -1
  186. package/dist/journal/ambient.js +1 -1
  187. package/dist/journal/attach-journal.d.ts +2 -1
  188. package/dist/journal/deviation-report.js +6 -6
  189. package/dist/journal/deviation-service.js +1 -1
  190. package/dist/journal/envelope-store.d.ts +0 -2
  191. package/dist/journal/envelope-store.js +1 -1
  192. package/dist/journal/envelope.d.ts +2 -3
  193. package/dist/journal/envelope.js +3 -3
  194. package/dist/journal/journal-query.js +1 -1
  195. package/dist/journal/journal-recorder.d.ts +2 -1
  196. package/dist/journal/journal-recorder.js +2 -2
  197. package/dist/journal/rollups.js +9 -9
  198. package/dist/journal/session-end.d.ts +2 -1
  199. package/dist/journal/session-journal.js +4 -4
  200. package/dist/license/license.d.ts +0 -14
  201. package/dist/license/license.js +8 -8
  202. package/dist/log.d.ts +10 -0
  203. package/dist/log.js +11 -1
  204. package/dist/mcp/mcp-is-error.d.ts +13 -0
  205. package/dist/mcp/mcp-is-error.js +18 -0
  206. package/dist/mcp/mcp-outage.d.ts +31 -0
  207. package/dist/mcp/mcp-outage.js +48 -0
  208. package/dist/mcp/mcp-proxy.d.ts +124 -0
  209. package/dist/mcp/mcp-proxy.js +697 -0
  210. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  211. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  212. package/dist/mcp/proxy-handshake.d.ts +3 -0
  213. package/dist/mcp/proxy-handshake.js +54 -0
  214. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  215. package/dist/mcp/proxy-lifecycle.js +70 -0
  216. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  217. package/dist/mcp/tool-catalog-cache.js +55 -0
  218. package/dist/oracles/propose-consequences.d.ts +2 -1
  219. package/dist/oracles/propose-consequences.js +16 -11
  220. package/dist/oracles/self-instrument.d.ts +2 -1
  221. package/dist/oracles/self-instrument.js +1 -1
  222. package/dist/phenomena/phenomena.js +3 -3
  223. package/dist/platform.d.ts +16 -0
  224. package/dist/platform.js +16 -0
  225. package/dist/pool/playwright-launcher.d.ts +15 -2
  226. package/dist/pool/playwright-launcher.js +21 -2
  227. package/dist/project/fs-port.js +1 -1
  228. package/dist/project/project-tools.js +2 -2
  229. package/dist/runs/build-verification-run.js +1 -1
  230. package/dist/runs/repair-prompt.d.ts +0 -6
  231. package/dist/runs/repair-prompt.js +3 -3
  232. package/dist/runs/risk-classify.js +1 -1
  233. package/dist/runs/run-diff.d.ts +0 -2
  234. package/dist/runs/run-diff.js +3 -3
  235. package/dist/runs/run-tools.js +2 -2
  236. package/dist/runs/verification-sync.js +2 -2
  237. package/dist/runs/verify-http.js +1 -1
  238. package/dist/runs/verify-server.js +1 -1
  239. package/dist/session/command-timeout.d.ts +11 -3
  240. package/dist/session/command-timeout.js +2 -2
  241. package/dist/session/control-envelope.d.ts +6 -0
  242. package/dist/session/control-envelope.js +7 -1
  243. package/dist/session/dev-server-probe.d.ts +13 -0
  244. package/dist/session/dev-server-probe.js +52 -0
  245. package/dist/session/last-act.d.ts +17 -3
  246. package/dist/session/last-act.js +16 -4
  247. package/dist/session/live-control-tools.js +7 -2
  248. package/dist/session/live-control.js +1 -1
  249. package/dist/session/looks-like-dev-server.d.ts +14 -0
  250. package/dist/session/looks-like-dev-server.js +25 -0
  251. package/dist/session/no-session-diagnosis.d.ts +38 -0
  252. package/dist/session/no-session-diagnosis.js +70 -0
  253. package/dist/session/no-session-watch.d.ts +19 -0
  254. package/dist/session/no-session-watch.js +61 -0
  255. package/dist/session/observed-state.d.ts +4 -0
  256. package/dist/session/observed-state.js +19 -1
  257. package/dist/session/output-budget.js +1 -1
  258. package/dist/session/session-health.d.ts +8 -1
  259. package/dist/session/session-health.js +8 -7
  260. package/dist/session/session-info.d.ts +26 -7
  261. package/dist/session/session-info.js +42 -1
  262. package/dist/session/session-lease.d.ts +0 -11
  263. package/dist/session/session-lease.js +1 -1
  264. package/dist/session/session-manager.d.ts +3 -0
  265. package/dist/session/session-manager.js +52 -9
  266. package/dist/session/session-readiness.d.ts +1 -1
  267. package/dist/session/session-readiness.js +6 -1
  268. package/dist/session/session-reaper.js +1 -1
  269. package/dist/session/session-reconnect.d.ts +32 -0
  270. package/dist/session/session-reconnect.js +42 -0
  271. package/dist/session/session.d.ts +14 -2
  272. package/dist/session/session.js +69 -26
  273. package/dist/session/state-select.d.ts +1 -1
  274. package/dist/session/state-select.js +1 -1
  275. package/dist/status-payload.d.ts +19 -0
  276. package/dist/status-payload.js +11 -0
  277. package/dist/telemetry/argument-shape.js +10 -1
  278. package/dist/telemetry/browser-mode.d.ts +27 -0
  279. package/dist/telemetry/browser-mode.js +33 -0
  280. package/dist/telemetry/bug-found.d.ts +17 -21
  281. package/dist/telemetry/bug-found.js +120 -8
  282. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  283. package/dist/telemetry/cli-telemetry.js +27 -4
  284. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  285. package/dist/telemetry/daemon-telemetry.js +12 -2
  286. package/dist/telemetry/dev-repo.js +4 -4
  287. package/dist/telemetry/error-fingerprint.js +21 -2
  288. package/dist/telemetry/feedback-cli.d.ts +1 -1
  289. package/dist/telemetry/feedback-cli.js +4 -4
  290. package/dist/telemetry/feedback-context.d.ts +22 -0
  291. package/dist/telemetry/feedback-context.js +41 -1
  292. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  293. package/dist/telemetry/feedback-delivery.js +30 -0
  294. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  295. package/dist/telemetry/feedback-outbox.js +87 -0
  296. package/dist/telemetry/feedback.d.ts +22 -1
  297. package/dist/telemetry/feedback.js +67 -8
  298. package/dist/telemetry/git-facts.js +1 -1
  299. package/dist/telemetry/session-metrics.d.ts +2 -0
  300. package/dist/telemetry/session-metrics.js +68 -6
  301. package/dist/telemetry/telemetry.d.ts +27 -2
  302. package/dist/telemetry/telemetry.js +119 -29
  303. package/dist/telemetry/verification-of.d.ts +22 -0
  304. package/dist/telemetry/verification-of.js +84 -0
  305. package/dist/tools/act-capsule.js +2 -2
  306. package/dist/tools/act-danger.d.ts +12 -0
  307. package/dist/tools/act-danger.js +20 -3
  308. package/dist/tools/act-helpers.js +4 -4
  309. package/dist/tools/act-tools.d.ts +0 -8
  310. package/dist/tools/act-tools.js +84 -115
  311. package/dist/tools/act-view.js +7 -7
  312. package/dist/tools/alias-args.d.ts +17 -0
  313. package/dist/tools/alias-args.js +25 -0
  314. package/dist/tools/assert-grade.js +9 -9
  315. package/dist/tools/assert-verdict.d.ts +3 -1
  316. package/dist/tools/assert-verdict.js +8 -5
  317. package/dist/tools/browser-tools.js +30 -11
  318. package/dist/tools/contract-tools.js +1 -1
  319. package/dist/tools/coverage-identity.d.ts +34 -0
  320. package/dist/tools/coverage-identity.js +46 -0
  321. package/dist/tools/coverage-tools.js +10 -14
  322. package/dist/tools/dynamic-tools.d.ts +2 -1
  323. package/dist/tools/dynamic-tools.js +90 -18
  324. package/dist/tools/error-recovery.d.ts +20 -2
  325. package/dist/tools/error-recovery.js +179 -4
  326. package/dist/tools/feedback-tools.d.ts +12 -1
  327. package/dist/tools/feedback-tools.js +28 -5
  328. package/dist/tools/invoke-tool.d.ts +0 -6
  329. package/dist/tools/invoke-tool.js +57 -32
  330. package/dist/tools/lease-hint.d.ts +17 -0
  331. package/dist/tools/lease-hint.js +24 -0
  332. package/dist/tools/lease-tools.js +6 -5
  333. package/dist/tools/merge-tools.d.ts +10 -1
  334. package/dist/tools/merge-tools.js +9 -3
  335. package/dist/tools/merged-name-redirect.d.ts +13 -0
  336. package/dist/tools/merged-name-redirect.js +55 -0
  337. package/dist/tools/navigate-result.d.ts +17 -0
  338. package/dist/tools/navigate-result.js +31 -0
  339. package/dist/tools/observe-tools.js +54 -20
  340. package/dist/tools/observed-nothing.d.ts +25 -0
  341. package/dist/tools/observed-nothing.js +33 -0
  342. package/dist/tools/query-paginate.js +2 -2
  343. package/dist/tools/query-shape.d.ts +18 -0
  344. package/dist/tools/query-shape.js +39 -0
  345. package/dist/tools/read-tools.js +81 -20
  346. package/dist/tools/real-input-attempt.d.ts +28 -0
  347. package/dist/tools/real-input-attempt.js +88 -0
  348. package/dist/tools/reconcile-tools.js +7 -7
  349. package/dist/tools/reload-result.d.ts +14 -0
  350. package/dist/tools/reload-result.js +29 -0
  351. package/dist/tools/snapshot-delta.d.ts +4 -5
  352. package/dist/tools/snapshot-delta.js +0 -0
  353. package/dist/tools/tool-kit.d.ts +8 -0
  354. package/dist/tools/tool-surface.d.ts +83 -0
  355. package/dist/tools/tool-surface.js +204 -0
  356. package/dist/tools/tools-helpers.js +6 -6
  357. package/dist/tools/tools.d.ts +15 -0
  358. package/dist/tools/tools.js +53 -17
  359. package/dist/tools/unadvertised-help.d.ts +5 -0
  360. package/dist/tools/unadvertised-help.js +39 -0
  361. package/dist/trace.d.ts +69 -0
  362. package/dist/trace.js +150 -0
  363. package/dist/update/nudge-credit.d.ts +4 -0
  364. package/dist/update/nudge-credit.js +53 -0
  365. package/dist/update/sdk-sync.d.ts +21 -0
  366. package/dist/update/sdk-sync.js +42 -0
  367. package/dist/update/update-checker.js +3 -3
  368. package/dist/update/update-nudge.d.ts +63 -6
  369. package/dist/update/update-nudge.js +128 -15
  370. package/dist/update/updater.d.ts +1 -9
  371. package/dist/update/updater.js +13 -8
  372. package/dist/version/peer-announce.d.ts +4 -0
  373. package/dist/version/peer-announce.js +30 -0
  374. package/dist/{server-version.js → version/server-version.js} +5 -1
  375. package/dist/version/version-nudge.d.ts +28 -0
  376. package/dist/version/version-nudge.js +40 -0
  377. package/dist/version/version-skew.d.ts +66 -0
  378. package/dist/version/version-skew.js +71 -0
  379. package/dist/visual/visual-diff.js +2 -2
  380. package/dist/visual/visual-tools.js +4 -4
  381. package/docs/agent-cheatsheet.md +85 -0
  382. package/docs/architecture.md +113 -0
  383. package/docs/benchmarks.md +255 -0
  384. package/docs/debugging.md +102 -0
  385. package/docs/deploy-checks.md +69 -0
  386. package/docs/desktop-apps.md +256 -0
  387. package/docs/enterprise.md +62 -0
  388. package/docs/fixtures.md +28 -0
  389. package/docs/flows.md +166 -0
  390. package/docs/getting-started.md +469 -0
  391. package/docs/human-control.md +63 -0
  392. package/docs/integration-patterns.md +171 -0
  393. package/docs/local-registry.md +126 -0
  394. package/docs/mint.json +61 -0
  395. package/docs/multi-agent-testing.md +55 -0
  396. package/docs/platform-integration.md +122 -0
  397. package/docs/telemetry-contract.md +214 -0
  398. package/docs/telemetry.md +184 -0
  399. package/docs/testing.md +82 -0
  400. package/docs/token-efficiency.md +80 -0
  401. package/docs/usage.md +1046 -0
  402. package/package.json +8 -5
  403. package/dist/cli-launch.js +0 -112
  404. package/dist/daemon-resilience.d.ts +0 -22
  405. package/dist/daemon-resilience.js +0 -85
  406. package/dist/mcp-proxy.d.ts +0 -67
  407. package/dist/mcp-proxy.js +0 -337
  408. package/dist/tools/profiles.d.ts +0 -37
  409. package/dist/tools/profiles.js +0 -139
  410. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  411. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  412. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  413. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  414. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  415. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  416. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -12,7 +12,7 @@ import { replayFlow } from './flow-replay.js';
12
12
  import { applyHealChanges, collectProposals } from './heal.js';
13
13
  import { assertSuccess, dynamicTestids, successLabel } from './flow-success.js';
14
14
  import { flowErrorMessage, sessionProjectId } from './flow-replay-run.js';
15
- export const HEAL_MESSAGES = {
15
+ const HEAL_MESSAGES = {
16
16
  NOTHING: 'nothing to heal — every anchor resolved on replay',
17
17
  HEALED: "rewrote drifted testid anchors to their nearest surviving match and re-verified the flow's success consequence still fires",
18
18
  DRIFT_DRY: 'confident rebind(s) proposed — re-run with apply:true to write them to disk',
@@ -25,7 +25,7 @@ function toChange(proposal) {
25
25
  }
26
26
  export async function healFlow(deps, args) {
27
27
  const name = asString(args['flowName']) ?? '';
28
- const apply = args['apply'] === true;
28
+ const apply = true === args['apply'];
29
29
  const projectId = sessionProjectId(deps, asString(args['sessionId']));
30
30
  const loaded = await deps.flows.load(name, projectId);
31
31
  if (!loaded.ok) {
@@ -40,7 +40,7 @@ export async function healFlow(deps, args) {
40
40
  };
41
41
  }
42
42
  const session = deps.sessions.resolve(asString(args['sessionId']));
43
- const steps = await replayFlow(session, loaded.value, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, args['confirmDangerous'] === true);
43
+ const steps = await replayFlow(session, loaded.value, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous']);
44
44
  const drifted = steps.some((s) => s.drift !== undefined);
45
45
  const failed = steps.find((s) => !s.ok && s.drift === undefined);
46
46
  if (failed !== undefined) {
@@ -66,7 +66,7 @@ export async function healFlow(deps, args) {
66
66
  };
67
67
  }
68
68
  const proposals = collectProposals(steps);
69
- if (proposals.length === 0) {
69
+ if (0 === proposals.length) {
70
70
  return {
71
71
  name,
72
72
  status: HealStatus.UNHEALABLE,
@@ -97,7 +97,7 @@ export async function healFlow(deps, args) {
97
97
  // Floor the success oracle at the start of the VERIFY replay so the earlier drift replay's signal
98
98
  // cannot fake the verification.
99
99
  const verifyFloor = session.elapsed();
100
- const verifySteps = await replayFlow(session, toVerify, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, args['confirmDangerous'] === true);
100
+ const verifySteps = await replayFlow(session, toVerify, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous']);
101
101
  const verifyClean = verifySteps.length > 0 && verifySteps.every((s) => s.ok && s.drift === undefined);
102
102
  const verdict = verifyClean
103
103
  ? await assertSuccess(session, healed.success, dynamicTestids(healed), waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, verifyFloor)
@@ -23,7 +23,7 @@ export function confidenceFor(from, to) {
23
23
  if (from === to)
24
24
  return 1;
25
25
  const span = Math.max(from.length, to.length);
26
- if (span === 0)
26
+ if (0 === span)
27
27
  return 1;
28
28
  const raw = 1 - editDistance(from, to) / span;
29
29
  if (raw >= 1)
@@ -62,10 +62,10 @@ function proposeRebindWith(drift, step, minConfidence) {
62
62
  // Never auto-heal an ambiguous drift: when two present testids tie at the minimum distance, the
63
63
  // `nearest` pick is arbitrary, so a rebind would be a coin-flip that can land on the wrong element
64
64
  // and ship a bug green. Surface it (the drift still carries `nearest`) and defer to a human.
65
- if (drift.ambiguous === true)
65
+ if (true === drift.ambiguous)
66
66
  return undefined;
67
67
  const to = drift.nearest;
68
- if (to === null)
68
+ if (null === to)
69
69
  return undefined;
70
70
  const confidence = confidenceFor(drift.anchor, to);
71
71
  if (confidence < minConfidence)
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The assertion the agent MADE, in the shape a saved flow can keep.
3
+ *
4
+ * `reticle_act_and_wait { until }` is how agents assert — 12 of 14 calls in a day of telemetry
5
+ * carried an `until`. `compileActStep` recorded only the ACTION, so a flow saved after an asserted
6
+ * drive came back graded `assertion-free`: "performs actions but asserts no observable consequence —
7
+ * it will pass even if the feature is broken." The agent had already said what success meant;
8
+ * Reticle discarded it and then warned the agent that the flow asserts nothing.
9
+ *
10
+ * That is the regression-suite story failing at its last step. "Record once, verify forever" is only
11
+ * worth anything if the recorded flow can go RED, and locally 32 of 39 saved flows cannot.
12
+ *
13
+ * The inverse of `successToPredicate`. Only the kinds FlowExpect can express are carried: `settled`
14
+ * is a wait rather than a claim, and `route`/`animation`/`anyOf`/`not` have no representation at all.
15
+ * Inventing one would write an assertion into the file that the agent never made, which is worse
16
+ * than recording none — a flow that asserts something nobody chose is a false green with extra steps.
17
+ */
18
+ import type { FlowExpect } from '@reticlehq/core';
19
+ import type { Predicate } from '../events/predicate.js';
20
+ export declare function predicateToExpect(predicate: Predicate): FlowExpect | undefined;
21
+ /**
22
+ * The subset a REPLAY actually enforces today.
23
+ *
24
+ * `flow-replay` checks exactly two things per step: `expect.element.testid` is present after the
25
+ * action, and `expect.state` holds. A recorded `net` or `signal` expect is graded as a consequence
26
+ * assertion by `classifyFlowAssertions` and then never evaluated — so writing one into a flow file
27
+ * would make it report `grade: "asserted"` while nothing checks it.
28
+ *
29
+ * That is a false green, in the feature whose entire purpose is preventing them, produced by the
30
+ * change that was meant to strengthen it. So only the enforced kinds are recorded: a flow that says
31
+ * it asserts something must actually go red when that thing breaks.
32
+ *
33
+ * Lifting this is worth doing — net and signal are the assertions agents most often make, and the
34
+ * conversion above already handles them — but it requires replay to evaluate them per step (the
35
+ * machinery exists: successToPredicate + the predicate engine). Until then, recording them would
36
+ * trade a weak flow for a lying one.
37
+ */
38
+ export declare function enforcedOnReplay(expect: FlowExpect | undefined): FlowExpect | undefined;
@@ -0,0 +1,95 @@
1
+ import { PredicateKind } from '@reticlehq/core';
2
+ /** Merge two partial expectations; later keys win only where the earlier one said nothing. */
3
+ function merge(into, from) {
4
+ return { ...from, ...into };
5
+ }
6
+ export function predicateToExpect(predicate) {
7
+ switch (predicate.kind) {
8
+ case PredicateKind.SIGNAL: {
9
+ if (predicate.name === undefined)
10
+ return undefined;
11
+ return predicate.dataMatches === undefined
12
+ ? { signal: predicate.name }
13
+ : { signal: predicate.name, signalData: predicate.dataMatches };
14
+ }
15
+ case PredicateKind.NET: {
16
+ const net = {};
17
+ if (predicate.method !== undefined)
18
+ net.method = predicate.method;
19
+ if (predicate.urlContains !== undefined)
20
+ net.urlContains = predicate.urlContains;
21
+ if (predicate.status !== undefined)
22
+ net.status = predicate.status;
23
+ if (predicate.count !== undefined)
24
+ net.count = predicate.count;
25
+ return 0 === Object.keys(net).length ? undefined : { net };
26
+ }
27
+ case PredicateKind.CONSOLE: {
28
+ const console_ = {};
29
+ if (predicate.level !== undefined)
30
+ console_.level = predicate.level;
31
+ if (predicate.absent !== undefined)
32
+ console_.absent = predicate.absent;
33
+ return 0 === Object.keys(console_).length ? undefined : { console: console_ };
34
+ }
35
+ case PredicateKind.ELEMENT: {
36
+ const element = {};
37
+ if (predicate.query.testid !== undefined)
38
+ element.testid = predicate.query.testid;
39
+ if (predicate.query.role !== undefined)
40
+ element.role = predicate.query.role;
41
+ if (predicate.query.name !== undefined)
42
+ element.name = predicate.query.name;
43
+ return 0 === Object.keys(element).length ? undefined : { element };
44
+ }
45
+ case PredicateKind.STATE: {
46
+ const state = { path: predicate.path };
47
+ if (predicate.store !== undefined)
48
+ state.store = predicate.store;
49
+ if (predicate.equals !== undefined)
50
+ state.equals = predicate.equals;
51
+ return { state };
52
+ }
53
+ case PredicateKind.ALL_OF: {
54
+ // `settled` members drop out on their own by returning undefined.
55
+ let combined;
56
+ for (const part of predicate.predicates) {
57
+ const expect = predicateToExpect(part);
58
+ if (expect === undefined)
59
+ continue;
60
+ combined = combined === undefined ? expect : merge(combined, expect);
61
+ }
62
+ return combined;
63
+ }
64
+ default:
65
+ // settled | route | animation | anyOf | not — nothing FlowExpect can say honestly.
66
+ return undefined;
67
+ }
68
+ }
69
+ /**
70
+ * The subset a REPLAY actually enforces today.
71
+ *
72
+ * `flow-replay` checks exactly two things per step: `expect.element.testid` is present after the
73
+ * action, and `expect.state` holds. A recorded `net` or `signal` expect is graded as a consequence
74
+ * assertion by `classifyFlowAssertions` and then never evaluated — so writing one into a flow file
75
+ * would make it report `grade: "asserted"` while nothing checks it.
76
+ *
77
+ * That is a false green, in the feature whose entire purpose is preventing them, produced by the
78
+ * change that was meant to strengthen it. So only the enforced kinds are recorded: a flow that says
79
+ * it asserts something must actually go red when that thing breaks.
80
+ *
81
+ * Lifting this is worth doing — net and signal are the assertions agents most often make, and the
82
+ * conversion above already handles them — but it requires replay to evaluate them per step (the
83
+ * machinery exists: successToPredicate + the predicate engine). Until then, recording them would
84
+ * trade a weak flow for a lying one.
85
+ */
86
+ export function enforcedOnReplay(expect) {
87
+ if (expect === undefined)
88
+ return undefined;
89
+ const kept = {};
90
+ if (expect.element?.testid !== undefined)
91
+ kept.element = expect.element;
92
+ if (expect.state !== undefined)
93
+ kept.state = expect.state;
94
+ return 0 === Object.keys(kept).length ? undefined : kept;
95
+ }
@@ -15,6 +15,17 @@ export declare function ambiguousTestidNote(value: string): string;
15
15
  export declare function queryRefs(result: CommandResult): string[];
16
16
  /** A destructive-action confirmation is one-shot and must never persist into a recording. */
17
17
  export declare function replayActionArgs(value: unknown, confirmDangerous?: boolean): Record<string, unknown>;
18
+ /**
19
+ * Capture an act into every in-flight recording, or do nothing when none is running.
20
+ *
21
+ * Every tool that drives the page must call this. reticle_act_and_wait did not, which is the tool
22
+ * the documented agent recipe drives with — so "record start -> drive -> record stop -> flow_save"
23
+ * saved a flow with zero steps.
24
+ */
25
+ export declare function captureAct(recordings: {
26
+ active: () => string[];
27
+ capture: (step: RecordedStep) => void;
28
+ }, args: Record<string, unknown>, res: unknown): void;
18
29
  /**
19
30
  * Compile a single reticle_act invocation into a normalized RecordedStep using the action result. Anchor
20
31
  * priority mirrors synthesizeAnchor: a data-testid is the gold standard; failing that, the element's
@@ -22,7 +33,11 @@ export declare function replayActionArgs(value: unknown, confirmDangerous?: bool
22
33
  * a volatile ref. Only when neither is available is the step ref-bound (in-session only).
23
34
  */
24
35
  export declare function compileActStep(args: Record<string, unknown>, res: unknown): RecordedStep;
25
- /** Compile an reticle_act_sequence invocation, normalizing each sub-step to its testid where resolvable. */
36
+ /**
37
+ * Compile an reticle_act_sequence invocation, normalizing each sub-step through the SAME anchor
38
+ * priority a single act uses (see compileAnchorArgs) — not testid-or-nothing, which is what made
39
+ * every sequence unreplayable on an app without testids.
40
+ */
26
41
  export declare function compileSequenceStep(args: Record<string, unknown>, res: unknown): RecordedStep;
27
42
  interface ReplayStepResult {
28
43
  tool: string;
@@ -1,5 +1,7 @@
1
1
  import { DANGEROUS_ACTION_CONFIRM_ARG, ReticleCommand, QueryBy, } from '@reticlehq/core';
2
2
  import { ReticleTool } from '../tools/tool-names.js';
3
+ import { predicateToExpect, enforcedOnReplay } from './predicate-to-expect.js';
4
+ import { PredicateSchema } from '../events/predicate.js';
3
5
  import { asString, asRecord } from '../tools/tools-helpers.js';
4
6
  /**
5
7
  * The note attached when a testid resolves to multiple live elements (the first match is used).
@@ -35,10 +37,38 @@ function sourceFromResult(res) {
35
37
  if (typeof source['file'] !== 'string' || typeof source['line'] !== 'number')
36
38
  return undefined;
37
39
  const out = { file: source['file'], line: source['line'] };
38
- if (typeof source['column'] === 'number')
40
+ if ('number' === typeof source['column'])
39
41
  out['column'] = source['column'];
40
42
  return out;
41
43
  }
44
+ /**
45
+ * Capture an act into every in-flight recording, or do nothing when none is running.
46
+ *
47
+ * Every tool that drives the page must call this. reticle_act_and_wait did not, which is the tool
48
+ * the documented agent recipe drives with — so "record start -> drive -> record stop -> flow_save"
49
+ * saved a flow with zero steps.
50
+ */
51
+ export function captureAct(recordings, args, res) {
52
+ if (0 === recordings.active().length)
53
+ return;
54
+ const step = compileActStep(args, res);
55
+ // Keep the assertion the agent actually made. `act_and_wait { until }` IS the agent saying what
56
+ // success means — 12 of 14 calls in a day carried one — and dropping it produced a flow graded
57
+ // "assertion-free: it will pass even if the feature is broken", which is the regression-suite
58
+ // story failing at its last step. Only kinds FlowExpect can express survive; see
59
+ // predicate-to-expect.ts.
60
+ const until = args['until'] ?? args['predicate'];
61
+ if (until !== undefined) {
62
+ const parsed = PredicateSchema.safeParse(until);
63
+ // Only what a replay actually CHECKS — recording an unenforced assertion would grade the flow
64
+ // "asserted" while nothing verifies it, which is a false green in the feature built to prevent
65
+ // them. See enforcedOnReplay.
66
+ const expect = parsed.success ? enforcedOnReplay(predicateToExpect(parsed.data)) : undefined;
67
+ if (expect !== undefined)
68
+ step.expect = expect;
69
+ }
70
+ recordings.capture(step);
71
+ }
42
72
  /**
43
73
  * Compile a single reticle_act invocation into a normalized RecordedStep using the action result. Anchor
44
74
  * priority mirrors synthesizeAnchor: a data-testid is the gold standard; failing that, the element's
@@ -46,10 +76,20 @@ function sourceFromResult(res) {
46
76
  * a volatile ref. Only when neither is available is the step ref-bound (in-session only).
47
77
  */
48
78
  export function compileActStep(args, res) {
49
- const r = asRecord(res);
79
+ const { args: compiled, stable } = compileAnchorArgs(asRecord(res), asString(args['ref']) ?? '', asString(args['action']) ?? '', replayActionArgs(args['args']));
80
+ return { tool: ReticleTool.ACT, stable, args: compiled };
81
+ }
82
+ /**
83
+ * The ONE anchor chooser: testid (gold) > role+name (identifies an instance) > component/source
84
+ * (the JSX site) > a volatile ref (in-session only).
85
+ *
86
+ * It is shared because it was not: `compileSequenceStep` understood only a testid, so every
87
+ * act_sequence sub-step on a testid-less app compiled to a ref, saved as the degraded `unresolved`
88
+ * sentinel, and drifted on every replay — while a single act on the very same element compiled a
89
+ * perfectly stable anchor.
90
+ */
91
+ function compileAnchorArgs(r, ref, action, actArgs) {
50
92
  const testid = asString(r['testid']);
51
- const action = asString(args['action']) ?? '';
52
- const actArgs = replayActionArgs(args['args']);
53
93
  const source = sourceFromResult(r);
54
94
  if (testid !== undefined) {
55
95
  // The testid anchors the step; source rides along purely so a failure can name a file. Carrying
@@ -62,7 +102,7 @@ export function compileActStep(args, res) {
62
102
  };
63
103
  if (source !== undefined)
64
104
  testidArgs['source'] = source;
65
- return { tool: ReticleTool.ACT, stable: true, args: testidArgs };
105
+ return { args: testidArgs, stable: true };
66
106
  }
67
107
  // Role + NAME before component: a component/source anchor names the JSX site, so every row's copy
68
108
  // of the same control collapses onto it. The accessible name separates them, and it is also the
@@ -80,7 +120,7 @@ export function compileActStep(args, res) {
80
120
  };
81
121
  if (source !== undefined)
82
122
  roleArgs['source'] = source;
83
- return { tool: ReticleTool.ACT, stable: true, args: roleArgs };
123
+ return { args: roleArgs, stable: true };
84
124
  }
85
125
  const component = asString(r['component']);
86
126
  if (component !== undefined || source !== undefined) {
@@ -89,15 +129,15 @@ export function compileActStep(args, res) {
89
129
  componentArgs['component'] = component;
90
130
  if (source !== undefined)
91
131
  componentArgs['source'] = source;
92
- return { tool: ReticleTool.ACT, stable: true, args: componentArgs };
132
+ return { args: componentArgs, stable: true };
93
133
  }
94
- return {
95
- tool: ReticleTool.ACT,
96
- stable: false,
97
- args: { ref: asString(args['ref']) ?? '', action, args: actArgs },
98
- };
134
+ return { args: { ref, action, args: actArgs }, stable: false };
99
135
  }
100
- /** Compile an reticle_act_sequence invocation, normalizing each sub-step to its testid where resolvable. */
136
+ /**
137
+ * Compile an reticle_act_sequence invocation, normalizing each sub-step through the SAME anchor
138
+ * priority a single act uses (see compileAnchorArgs) — not testid-or-nothing, which is what made
139
+ * every sequence unreplayable on an app without testids.
140
+ */
101
141
  export function compileSequenceStep(args, res) {
102
142
  const inputSteps = Array.isArray(args['steps']) ? args['steps'] : [];
103
143
  const resolved = Array.isArray(asRecord(res)['steps'])
@@ -106,14 +146,10 @@ export function compileSequenceStep(args, res) {
106
146
  let stable = inputSteps.length > 0;
107
147
  const subSteps = inputSteps.map((raw, i) => {
108
148
  const step = asRecord(raw);
109
- const action = asString(step['action']) ?? '';
110
- const stepArgs = replayActionArgs(step['args']);
111
- const testid = asString(asRecord(resolved[i])['testid']);
112
- if (testid !== undefined) {
113
- return { by: QueryBy.TESTID, value: testid, action, args: stepArgs };
114
- }
115
- stable = false;
116
- return { ref: asString(step['ref']) ?? '', action, args: stepArgs };
149
+ const compiled = compileAnchorArgs(asRecord(resolved[i]), asString(step['ref']) ?? '', asString(step['action']) ?? '', replayActionArgs(step['args']));
150
+ if (!compiled.stable)
151
+ stable = false;
152
+ return compiled.args;
117
153
  });
118
154
  return { tool: ReticleTool.ACT_SEQUENCE, stable, args: { steps: subSteps } };
119
155
  }
@@ -181,7 +217,7 @@ async function resolveRef(session, step) {
181
217
  : { ref };
182
218
  }
183
219
  const ref = asString(step.ref);
184
- if (ref === undefined || ref.length === 0) {
220
+ if (ref === undefined || 0 === ref.length) {
185
221
  throw new Error('step has no resolvable anchor (testid, role+name, component) and no ref');
186
222
  }
187
223
  return { ref, note: 'replayed by stale ref (not portable across sessions)' };
@@ -0,0 +1,6 @@
1
+ /** Present controls, as role + accessible name. */
2
+ export interface RoleCandidate {
3
+ role: string;
4
+ name: string;
5
+ }
6
+ export declare function nearestRoleName(role: string, missing: string, present: readonly RoleCandidate[]): string | null;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The closest surviving control to a role+name anchor that no longer resolves.
3
+ *
4
+ * `runRoleStep` returned `nearest: null` as a literal, so `flow_heal` answered "no nearest match
5
+ * cleared the confidence floor" for every role-anchored drift — which reads as a judgement about
6
+ * candidates when in fact nothing had looked. Reported from a real sweep: a three-step flow failed
7
+ * on `button named 'Menu' did not resolve` and heal called it unhealable, however small the change.
8
+ *
9
+ * What this does NOT do is make role anchors auto-healable, and that restraint is the point. A
10
+ * testid is an identifier a developer put there deliberately; a role name is user-visible text where
11
+ * "Save" and "Save as" are one edit apart. Silently rebinding a step to a similar-looking control is
12
+ * precisely the wrong-element green this product exists to catch. So the bar here is deliberately
13
+ * higher than the testid path's: same role, unambiguous, and genuinely close — otherwise null, and
14
+ * the agent is told to add a testid.
15
+ */
16
+ import { editDistance } from './flow-replay.js';
17
+ /**
18
+ * A name must be at least this similar to be worth showing: at most a third of it changed. A looser
19
+ * bar turns "no candidate" into "a confident wrong one", which is worse than saying nothing.
20
+ */
21
+ const MAX_CHANGED_FRACTION = 1 / 3;
22
+ export function nearestRoleName(role, missing, present) {
23
+ const sameRole = present.filter((candidate) => candidate.role === role);
24
+ if (0 === sameRole.length)
25
+ return null;
26
+ const budget = Math.floor(missing.length * MAX_CHANGED_FRACTION);
27
+ let best = null;
28
+ let bestDistance = Number.POSITIVE_INFINITY;
29
+ let tied = false;
30
+ for (const candidate of sameRole) {
31
+ const distance = editDistance(missing, candidate.name);
32
+ if (distance < bestDistance) {
33
+ bestDistance = distance;
34
+ best = candidate.name;
35
+ tied = false;
36
+ }
37
+ else if (distance === bestDistance) {
38
+ tied = true;
39
+ }
40
+ }
41
+ // A tie is a coin flip between two live controls; naming one would be inventing confidence.
42
+ if (null === best || tied || bestDistance > budget)
43
+ return null;
44
+ return best;
45
+ }
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { SuiteVerdict } from '@reticlehq/core';
9
9
  import type { ToolDeps } from '../tools/tools.js';
10
- import { type ProjectCloud } from './../cloud/cloud-config.js';
10
+ import { type ProjectCloud } from '../cloud/cloud-config.js';
11
11
  import { type ServerVerification } from '../cloud/cloud-sync.js';
12
12
  /** Map the hosted runner's report onto the local suite-verdict shape (status/total/passed/failed/failures). */
13
13
  export declare function toSuiteVerdict(report: ServerVerification): SuiteVerdict;
@@ -1,4 +1,4 @@
1
- import { VerifyMode } from './../cloud/cloud-config.js';
1
+ import { VerifyMode } from '../cloud/cloud-config.js';
2
2
  import { submitServerVerification } from '../cloud/cloud-sync.js';
3
3
  const PASS = 'pass';
4
4
  const UNVERIFIED = 'unverified';
@@ -14,12 +14,12 @@ export function toSuiteVerdict(report) {
14
14
  }));
15
15
  const total = report.flows.length;
16
16
  const passed = total - failures.length;
17
- const status = report.verdict === PASS && failures.length === 0 ? 'pass' : 'fail';
17
+ const status = report.verdict === PASS && 0 === failures.length ? 'pass' : 'fail';
18
18
  return { status, total, passed, failed: failures.length, summary: report.summary, failures };
19
19
  }
20
20
  /** Run the suite on the hosted runner, or null to signal the caller should replay locally. */
21
21
  export async function runServerVerify(deps, cloud, sessionId, flows) {
22
- if (cloud.verify !== VerifyMode.SERVER || cloud.config === null)
22
+ if (cloud.verify !== VerifyMode.SERVER || null === cloud.config)
23
23
  return null;
24
24
  let previewUrl;
25
25
  try {
@@ -29,10 +29,10 @@ export async function runServerVerify(deps, cloud, sessionId, flows) {
29
29
  previewUrl = undefined;
30
30
  }
31
31
  // The server hits the URL itself; with no URL (or a localhost one it can't reach) fall back to local.
32
- if (previewUrl === undefined || previewUrl.length === 0)
32
+ if (previewUrl === undefined || 0 === previewUrl.length)
33
33
  return null;
34
34
  const report = await submitServerVerification({ previewUrl, flows, source: SOURCE }, cloud.config, (url, init) => fetch(url, init));
35
- if (report === null)
35
+ if (null === report)
36
36
  return null;
37
37
  // The hosted runner said it couldn't actually verify (e.g. it's not enabled yet). Never surface that as
38
38
  // a pass or a fail — fall back to the real local replay so the verdict reflects a browser that ran.
@@ -1,14 +1,15 @@
1
1
  import { z } from 'zod';
2
+ import { verdictForSuite } from './verify-change-verdict.js';
3
+ import { attributedFailures } from './attributed-failure.js';
2
4
  import { Verified } from '@reticlehq/core';
3
5
  import { ReticleTool } from '../tools/tool-names.js';
4
6
  import { asNumber, asRecord, asString } from '../tools/tools-helpers.js';
5
- import { loadNamedFlows, resolveChangedFiles } from '../cli-flow-commands.js';
7
+ import { loadNamedFlows, resolveChangedFiles } from '../cli/cli-flow-commands.js';
6
8
  import { affectedSavedFlows } from './flow-sources.js';
7
9
  import { FLOW_TOOLS } from './flow-tools.js';
8
10
  import { findContradictions } from '../events/contradictions.js';
9
11
  import { parseControls } from '../tools/coverage-tools.js';
10
12
  import { ReticleCommand, SnapshotMode } from '@reticlehq/core';
11
- const PASS = 'pass';
12
13
  export const VERIFY_CHANGE_TOOLS = [
13
14
  {
14
15
  name: ReticleTool.VERIFY_CHANGE,
@@ -57,13 +58,13 @@ export const VERIFY_CHANGE_TOOLS = [
57
58
  },
58
59
  handler: async (deps, args) => {
59
60
  const files = Array.isArray(args['files'])
60
- ? args['files'].filter((f) => typeof f === 'string')
61
+ ? args['files'].filter((f) => 'string' === typeof f)
61
62
  : [];
62
63
  const since = asString(args['since']);
63
64
  const changedFiles = await resolveChangedFiles(files, since);
64
65
  const flows = await loadNamedFlows(deps.fs, deps.reticleRoot);
65
66
  const { affected, unknownProvenance } = affectedSavedFlows(flows, changedFiles);
66
- if (changedFiles.length === 0) {
67
+ if (0 === changedFiles.length) {
67
68
  return {
68
69
  verified: Verified.UNKNOWN,
69
70
  because: 'no changed files were given, so there was nothing to decide about',
@@ -77,10 +78,10 @@ export const VERIFY_CHANGE_TOOLS = [
77
78
  }
78
79
  // The guard: an uncovered change is UNKNOWN. Reporting `yes` here would mean "your change is
79
80
  // fine" on the strength of having run nothing at all.
80
- if (affected.length === 0) {
81
+ if (0 === affected.length) {
81
82
  return {
82
83
  verified: Verified.UNKNOWN,
83
- because: `no saved flow covers ${changedFiles.length === 1 ? 'this file' : 'these files'} — record one with reticle_record { action: "start" } then reticle_flow_save, or verify by driving the app directly`,
84
+ because: `no saved flow covers ${1 === changedFiles.length ? 'this file' : 'these files'} — record one with reticle_record { action: "start" } then reticle_flow_save, or verify by driving the app directly`,
84
85
  changedFiles,
85
86
  flowsRun: [],
86
87
  unknownProvenance,
@@ -104,11 +105,32 @@ export const VERIFY_CHANGE_TOOLS = [
104
105
  }));
105
106
  const failed = suite.failed ?? 0;
106
107
  const passed = suite.passed ?? 0;
107
- const failedSuite = suite.status !== PASS || failed > 0;
108
- if (failedSuite) {
108
+ // `unverifiable` is NOT a failure — the suite ran and proved nothing, which is the same fact
109
+ // this tool answers UNKNOWN for an uncovered change. Treating it as a red emitted a bug_found
110
+ // that nothing earned. See verify-change-verdict.
111
+ const verdict = verdictForSuite(String(suite.status), failed);
112
+ // A NO must be EARNED by a failing flow genuinely tied to the changed files. `affectedSavedFlows`
113
+ // re-runs any flow whose sources it cannot determine — over-running beats skipping — and those
114
+ // land in `unknownProvenance`. Judging the change on them produced a negative verdict whose own
115
+ // explanation admitted the evidence was not tied to the file. See attributed-failure.
116
+ const failingNames = Array.isArray(suite.failures)
117
+ ? suite.failures
118
+ .map((f) => 'object' === typeof f && f !== null ? asString(asRecord(f)['flow']) : undefined)
119
+ .filter((n) => n !== undefined)
120
+ : // No per-failure detail: the failure is somewhere in the set that ran, so ask the question
121
+ // of all of them rather than guessing which.
122
+ affected;
123
+ const attributed = attributedFailures(failingNames, unknownProvenance);
124
+ const earnedNo = verdict === Verified.NO && attributed.length > 0;
125
+ const reported = verdict === Verified.NO && !earnedNo ? Verified.UNKNOWN : verdict;
126
+ if (reported !== Verified.YES) {
109
127
  return {
110
- verified: Verified.NO,
111
- because: `${String(failed)} of ${String(suite.total ?? affected.length)} covering flows failed${provenanceNote}`,
128
+ verified: reported,
129
+ because: earnedNo
130
+ ? `${String(failed)} of ${String(suite.total ?? affected.length)} covering flows failed, including ${attributed.join(', ')}, which cover the changed files${provenanceNote}`
131
+ : verdict === Verified.NO
132
+ ? `flows failed, but Reticle cannot tell which sources any of them cover (${failingNames.join(', ')}) — so this says nothing about the files you changed. Re-record them so their sources are stamped, or read the replay yourself`
133
+ : `the covering flows ran but proved nothing (${String(suite.status)}) — they assert no observable consequence, so a green from them would not have meant your change is safe${provenanceNote}`,
112
134
  changedFiles,
113
135
  flowsRun: affected,
114
136
  suite,
@@ -0,0 +1,15 @@
1
+ /**
2
+ * What a replayed suite's status entitles `verify_change` to claim.
3
+ *
4
+ * This was `suite.status !== 'pass'` meaning failure. That was true while the only statuses were
5
+ * pass and fail — but `flow_verify` now reports `unverifiable` for a suite that ran and proved
6
+ * nothing (an empty suite used to report `pass`, which was a false green). Under the old test,
7
+ * `unverifiable` fell into the failure branch: a suite that proved nothing came back as proof the
8
+ * change was broken, and emitted a `bug_found` for it.
9
+ *
10
+ * `unverifiable` is the same fact this tool already handles elsewhere as "no saved flow covers this
11
+ * change" — answered UNKNOWN, with the comment calling it "the honest answer, never a green: nothing
12
+ * ran, so nothing was proved". A red nothing earned is the mirror image of that green.
13
+ */
14
+ import { Verified } from '@reticlehq/core';
15
+ export declare function verdictForSuite(status: string, failedCount: number): Verified;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What a replayed suite's status entitles `verify_change` to claim.
3
+ *
4
+ * This was `suite.status !== 'pass'` meaning failure. That was true while the only statuses were
5
+ * pass and fail — but `flow_verify` now reports `unverifiable` for a suite that ran and proved
6
+ * nothing (an empty suite used to report `pass`, which was a false green). Under the old test,
7
+ * `unverifiable` fell into the failure branch: a suite that proved nothing came back as proof the
8
+ * change was broken, and emitted a `bug_found` for it.
9
+ *
10
+ * `unverifiable` is the same fact this tool already handles elsewhere as "no saved flow covers this
11
+ * change" — answered UNKNOWN, with the comment calling it "the honest answer, never a green: nothing
12
+ * ran, so nothing was proved". A red nothing earned is the mirror image of that green.
13
+ */
14
+ import { Verified } from '@reticlehq/core';
15
+ const PASS = 'pass';
16
+ const FAIL = 'fail';
17
+ export function verdictForSuite(status, failedCount) {
18
+ // A real failure outranks everything: flows ran and some went red.
19
+ if (failedCount > 0 || status === FAIL)
20
+ return Verified.NO;
21
+ if (status === PASS)
22
+ return Verified.YES;
23
+ // `unverifiable`, or any status added later. Nothing was proved, so nothing is claimed.
24
+ return Verified.UNKNOWN;
25
+ }
@@ -4,10 +4,10 @@
4
4
  * a quiet window, so we compute affected flows once per edit, not per keystroke. The scheduler (setTimeout)
5
5
  * is injected, keeping the batching logic pure and unit-testable.
6
6
  */
7
- export interface WatchBatcher {
7
+ interface WatchBatcher {
8
8
  onChange(file: string): void;
9
9
  }
10
- export interface WatchBatcherOptions {
10
+ interface WatchBatcherOptions {
11
11
  debounceMs: number;
12
12
  /** Injected scheduler (setTimeout) — deterministic in tests. */
13
13
  schedule: (fn: () => void, ms: number) => void;
@@ -15,3 +15,4 @@ export interface WatchBatcherOptions {
15
15
  onFlush: (files: string[]) => void;
16
16
  }
17
17
  export declare function createWatchBatcher(options: WatchBatcherOptions): WatchBatcher;
18
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Predicate } from '../events/predicate.js';
2
+ export declare function readsDomState(predicate: Predicate): boolean;