@reticlehq/server 2.13.0 → 2.14.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 (451) hide show
  1. package/SKILL.md +8 -18
  2. package/bin/reticle.js +3 -0
  3. package/dist/bridge/auth-failure-reason.js +12 -1
  4. package/dist/bridge/bridge-security.d.ts +8 -0
  5. package/dist/bridge/bridge-security.js +6 -1
  6. package/dist/bridge/bridge.d.ts +3 -0
  7. package/dist/bridge/bridge.js +23 -1
  8. package/dist/bridge/bridge.test-harness.d.ts +10 -1
  9. package/dist/bridge/bridge.test-harness.js +7 -1
  10. package/dist/bridge/pairing-token.d.ts +17 -0
  11. package/dist/bridge/pairing-token.js +26 -0
  12. package/dist/bridge/token-auth.d.ts +7 -0
  13. package/dist/bridge/token-auth.js +7 -1
  14. package/dist/capsule/capsule-store.d.ts +4 -4
  15. package/dist/capsule/capsule.d.ts +1 -1
  16. package/dist/capsule/capsule.js +3 -3
  17. package/dist/capsule/causal-summary.d.ts +21 -1
  18. package/dist/capsule/causal-summary.js +8 -6
  19. package/dist/capsule/divergence.d.ts +1 -1
  20. package/dist/capsule/divergence.js +26 -10
  21. package/dist/chromium-launch-options.d.ts +2 -1
  22. package/dist/cli/adhoc-verdict.d.ts +58 -0
  23. package/dist/cli/adhoc-verdict.js +115 -0
  24. package/dist/cli/chromium-hint.d.ts +18 -4
  25. package/dist/cli/chromium-hint.js +54 -6
  26. package/dist/cli/cli-doctor.js +1 -2
  27. package/dist/cli/cli-flow-commands.d.ts +16 -2
  28. package/dist/cli/cli-flow-commands.js +28 -9
  29. package/dist/cli/cli-parse-feedback.js +1 -1
  30. package/dist/cli/cli-parse.d.ts +2 -1
  31. package/dist/cli/cli-parse.js +38 -0
  32. package/dist/cli/cli-port.d.ts +5 -13
  33. package/dist/cli/cli-port.js +5 -38
  34. package/dist/cli/cli-update-commands.js +4 -4
  35. package/dist/cli/cli-verify.d.ts +24 -7
  36. package/dist/cli/cli-verify.js +87 -13
  37. package/dist/cli/cloud-keystore.d.ts +2 -1
  38. package/dist/cli/cloud-login.d.ts +2 -1
  39. package/dist/cli/cloud-session.d.ts +2 -1
  40. package/dist/cli/cloud-session.js +1 -1
  41. package/dist/cli/daemon-start-options.d.ts +2 -1
  42. package/dist/cli/device-flow.d.ts +2 -2
  43. package/dist/cli/doctor-daemon-line.d.ts +2 -1
  44. package/dist/cli/doctor-project-line.d.ts +2 -1
  45. package/dist/cli/doctor-rows.d.ts +2 -1
  46. package/dist/cli/drive-attach.d.ts +4 -3
  47. package/dist/cli/gate-hook-message.d.ts +2 -1
  48. package/dist/cli/status-next-action.js +10 -11
  49. package/dist/cli.d.ts +1 -0
  50. package/dist/cli.js +21 -9
  51. package/dist/cloud/account-state.d.ts +20 -0
  52. package/dist/cloud/account-state.js +65 -0
  53. package/dist/cloud/cloud-sync.d.ts +46 -3
  54. package/dist/cloud/cloud-sync.js +116 -0
  55. package/dist/cloud/sync-cycle.d.ts +5 -4
  56. package/dist/cloud/sync-cycle.js +1 -1
  57. package/dist/cloud/sync-daemon.d.ts +3 -19
  58. package/dist/cloud/sync-daemon.js +2 -2
  59. package/dist/crawl/crawl-tools.js +1 -1
  60. package/dist/crawl/crawl.d.ts +5 -5
  61. package/dist/crawl/crawl.js +138 -4
  62. package/dist/daemon/daemon-resilience.d.ts +3 -2
  63. package/dist/daemon/daemon-resilience.js +43 -4
  64. package/dist/daemon/daemon-resolve.d.ts +0 -9
  65. package/dist/daemon/daemon-resolve.js +2 -2
  66. package/dist/daemon/dev-servers.d.ts +1 -1
  67. package/dist/daemon/dev-servers.js +1 -1
  68. package/dist/daemon/loopback-alias.d.ts +2 -1
  69. package/dist/daemon/startup-failure.d.ts +11 -0
  70. package/dist/daemon/startup-failure.js +46 -0
  71. package/dist/events/contradictions.d.ts +10 -16
  72. package/dist/events/contradictions.js +149 -17
  73. package/dist/events/declared.d.ts +13 -1
  74. package/dist/events/declared.js +59 -3
  75. package/dist/events/echo-mismatch.js +75 -6
  76. package/dist/events/lineage.d.ts +52 -0
  77. package/dist/events/lineage.js +122 -0
  78. package/dist/events/net-evidence.d.ts +21 -0
  79. package/dist/events/net-evidence.js +81 -0
  80. package/dist/events/predicate-console.d.ts +13 -0
  81. package/dist/events/predicate-console.js +138 -0
  82. package/dist/events/predicate-element.d.ts +13 -0
  83. package/dist/events/predicate-element.js +214 -0
  84. package/dist/events/predicate-eval.d.ts +15 -3
  85. package/dist/events/predicate-eval.js +128 -110
  86. package/dist/events/predicate-parse.js +34 -1
  87. package/dist/events/predicate-request-body.d.ts +50 -0
  88. package/dist/events/predicate-request-body.js +120 -0
  89. package/dist/events/predicate-route.d.ts +21 -1
  90. package/dist/events/predicate-route.js +149 -21
  91. package/dist/events/predicate-schema.d.ts +8 -1
  92. package/dist/events/predicate-schema.js +34 -0
  93. package/dist/events/predicate.d.ts +2 -0
  94. package/dist/events/predicate.js +52 -159
  95. package/dist/events/split-text-miss.d.ts +6 -4
  96. package/dist/events/split-text-miss.js +12 -5
  97. package/dist/flows/affected-tools.js +22 -9
  98. package/dist/flows/annotate.d.ts +2 -1
  99. package/dist/flows/annotate.js +19 -1
  100. package/dist/flows/coverage.d.ts +2 -2
  101. package/dist/flows/flake.d.ts +3 -3
  102. package/dist/flows/flow-expect-grammar.d.ts +33 -0
  103. package/dist/flows/flow-expect-grammar.js +155 -0
  104. package/dist/flows/flow-replay-run.d.ts +1 -1
  105. package/dist/flows/flow-replay-run.js +28 -15
  106. package/dist/flows/flow-replay.d.ts +10 -0
  107. package/dist/flows/flow-replay.js +57 -22
  108. package/dist/flows/flow-secret-field.d.ts +14 -0
  109. package/dist/flows/flow-secret-field.js +22 -0
  110. package/dist/flows/flow-step-runners.d.ts +1 -0
  111. package/dist/flows/flow-step-runners.js +91 -34
  112. package/dist/flows/flow-success.js +22 -0
  113. package/dist/flows/flow-tools.js +27 -8
  114. package/dist/flows/flow-upload-resolve.d.ts +23 -0
  115. package/dist/flows/flow-upload-resolve.js +46 -0
  116. package/dist/flows/flows.d.ts +15 -0
  117. package/dist/flows/flows.js +17 -14
  118. package/dist/flows/git-changed.d.ts +23 -4
  119. package/dist/flows/git-changed.js +32 -6
  120. package/dist/flows/heal-run.js +2 -2
  121. package/dist/flows/predicate-to-expect.d.ts +3 -2
  122. package/dist/flows/predicate-to-expect.js +11 -5
  123. package/dist/flows/recording-backtrack.d.ts +24 -0
  124. package/dist/flows/recording-backtrack.js +73 -0
  125. package/dist/flows/recordings.d.ts +6 -0
  126. package/dist/flows/replay-halt.d.ts +26 -0
  127. package/dist/flows/replay-halt.js +30 -0
  128. package/dist/flows/replay.d.ts +0 -8
  129. package/dist/flows/replay.js +15 -3
  130. package/dist/flows/suite-flakes.d.ts +2 -1
  131. package/dist/flows/verify-change-tools.js +16 -4
  132. package/dist/honesty/already-true.d.ts +14 -0
  133. package/dist/honesty/already-true.js +47 -4
  134. package/dist/honesty/blind-spots.d.ts +3 -1
  135. package/dist/honesty/blind-spots.js +24 -1
  136. package/dist/honesty/body-capture-remedy.d.ts +42 -0
  137. package/dist/honesty/body-capture-remedy.js +95 -0
  138. package/dist/honesty/feature-capture.d.ts +3 -2
  139. package/dist/honesty/feature-capture.js +1 -1
  140. package/dist/honesty/gap-ledger.d.ts +1 -1
  141. package/dist/honesty/gap-ledger.js +1 -1
  142. package/dist/honesty/instrumentation-gaps.d.ts +8 -1
  143. package/dist/honesty/instrumentation-gaps.js +12 -2
  144. package/dist/honesty/observability.d.ts +4 -3
  145. package/dist/honesty/tool-hit-rate.d.ts +3 -2
  146. package/dist/honesty/uncaptured-bodies.d.ts +3 -1
  147. package/dist/honesty/uncaptured-bodies.js +6 -2
  148. package/dist/honesty/unsettled.d.ts +9 -0
  149. package/dist/honesty/unsettled.js +45 -0
  150. package/dist/honesty/verified.d.ts +32 -1
  151. package/dist/honesty/verified.js +37 -10
  152. package/dist/impact/impact-store.d.ts +5 -14
  153. package/dist/impact/impact-store.js +49 -6
  154. package/dist/index.js +1 -1
  155. package/dist/input/network-detail.d.ts +26 -1
  156. package/dist/input/network-detail.js +44 -12
  157. package/dist/input/network-mock-tools.js +24 -17
  158. package/dist/input/real-input.js +1 -1
  159. package/dist/input/viewport-tools.js +25 -13
  160. package/dist/intent/inline-intent.d.ts +0 -10
  161. package/dist/intent/inline-intent.js +1 -1
  162. package/dist/intent/intent-shard-store.d.ts +3 -2
  163. package/dist/intent/intent-shard-store.js +1 -1
  164. package/dist/intent/intent-shard.d.ts +14 -14
  165. package/dist/intent/intent-shard.js +4 -4
  166. package/dist/intent/intent-store.d.ts +1 -1
  167. package/dist/intent/intent-store.js +1 -1
  168. package/dist/intent/intent-subject.d.ts +2 -1
  169. package/dist/intent/open-intents.d.ts +3 -2
  170. package/dist/license/license.d.ts +4 -4
  171. package/dist/license/license.js +1 -1
  172. package/dist/mcp/attach-memory.d.ts +2 -1
  173. package/dist/mcp/client-identity.d.ts +2 -1
  174. package/dist/mcp/mcp-outage.d.ts +1 -1
  175. package/dist/mcp/mcp-outage.js +1 -1
  176. package/dist/mcp/mcp-proxy.d.ts +3 -1
  177. package/dist/mcp/mcp-proxy.js +32 -7
  178. package/dist/mcp/mcp.js +3 -1
  179. package/dist/mcp/proxy-handshake.d.ts +2 -1
  180. package/dist/mcp/proxy-idle-exit.d.ts +45 -0
  181. package/dist/mcp/proxy-idle-exit.js +94 -0
  182. package/dist/mcp/server-instructions.d.ts +2 -1
  183. package/dist/memory/project-memory.d.ts +2 -3
  184. package/dist/memory/project-memory.js +1 -1
  185. package/dist/oracles/propose-consequences.js +5 -5
  186. package/dist/oracles/self-instrument.d.ts +2 -2
  187. package/dist/oracles/self-instrument.js +1 -1
  188. package/dist/platform.d.ts +3 -5
  189. package/dist/platform.js +3 -5
  190. package/dist/pool/browser-pool.d.ts +92 -0
  191. package/dist/pool/browser-pool.js +119 -5
  192. package/dist/pool/playwright-launcher.js +29 -2
  193. package/dist/pool/storage-seed.d.ts +23 -0
  194. package/dist/pool/storage-seed.js +198 -0
  195. package/dist/project/artifact-root.d.ts +2 -1
  196. package/dist/project/memory-fs.d.ts +2 -1
  197. package/dist/project/reticle-dir.js +1 -1
  198. package/dist/project/session-root.d.ts +8 -0
  199. package/dist/project/session-root.js +9 -2
  200. package/dist/runs/reticle-runner.d.ts +14 -1
  201. package/dist/runs/reticle-runner.js +39 -2
  202. package/dist/session/attachment-history.d.ts +2 -1
  203. package/dist/session/fake-session.d.ts +37 -0
  204. package/dist/session/fake-session.js +81 -0
  205. package/dist/session/lease-visibility.d.ts +2 -1
  206. package/dist/session/live-control-tools.js +6 -0
  207. package/dist/session/no-session-diagnosis.d.ts +17 -1
  208. package/dist/session/no-session-diagnosis.js +32 -6
  209. package/dist/session/no-session-next-action.d.ts +14 -0
  210. package/dist/session/no-session-next-action.js +18 -0
  211. package/dist/session/no-session-watch.js +46 -3
  212. package/dist/session/registered-projects.d.ts +54 -0
  213. package/dist/session/registered-projects.js +73 -0
  214. package/dist/session/review-store.d.ts +2 -1
  215. package/dist/session/session-health.d.ts +62 -1
  216. package/dist/session/session-health.js +82 -2
  217. package/dist/session/session-manager.d.ts +11 -1
  218. package/dist/session/session-manager.js +18 -3
  219. package/dist/session/session-replaced.d.ts +24 -0
  220. package/dist/session/session-replaced.js +29 -0
  221. package/dist/session/session-successor.js +20 -4
  222. package/dist/session/session.d.ts +13 -5
  223. package/dist/session/session.js +31 -23
  224. package/dist/setup/agent-configs.d.ts +3 -2
  225. package/dist/setup/agent-writer.d.ts +3 -2
  226. package/dist/setup/approval-migration.d.ts +2 -1
  227. package/dist/setup/approval-migration.js +1 -1
  228. package/dist/setup/auto-approve.d.ts +11 -19
  229. package/dist/setup/auto-approve.js +15 -12
  230. package/dist/{init → setup}/confirm.js +1 -1
  231. package/dist/setup/desktop-shape.d.ts +3 -2
  232. package/dist/setup/dev-server-wait.d.ts +8 -0
  233. package/dist/setup/dev-server-wait.js +3 -1
  234. package/dist/setup/drive-agent.d.ts +2 -3
  235. package/dist/setup/drive-agent.js +1 -1
  236. package/dist/setup/drive-plan.d.ts +3 -2
  237. package/dist/setup/ensure-daemon.d.ts +2 -1
  238. package/dist/setup/existing-app.d.ts +12 -0
  239. package/dist/setup/existing-app.js +13 -0
  240. package/dist/setup/init-host.d.ts +11 -0
  241. package/dist/setup/init-host.js +26 -0
  242. package/dist/setup/init-runtime.d.ts +4 -10
  243. package/dist/setup/init-runtime.js +23 -16
  244. package/dist/setup/license-key.d.ts +2 -1
  245. package/dist/setup/listeners.d.ts +2 -1
  246. package/dist/setup/loopback-probe-urls.d.ts +18 -0
  247. package/dist/setup/loopback-probe-urls.js +52 -0
  248. package/dist/setup/node-effects.d.ts +3 -0
  249. package/dist/setup/node-effects.js +26 -11
  250. package/dist/setup/page-probe.d.ts +5 -0
  251. package/dist/setup/relaunch.d.ts +2 -1
  252. package/dist/setup/run-setup.d.ts +5 -1
  253. package/dist/setup/run-setup.js +155 -48
  254. package/dist/setup/setup-command.d.ts +3 -2
  255. package/dist/setup/setup-command.js +8 -0
  256. package/dist/setup/setup-options.d.ts +5 -3
  257. package/dist/setup/setup-options.js +3 -2
  258. package/dist/setup/wait-progress.d.ts +28 -0
  259. package/dist/setup/wait-progress.js +43 -0
  260. package/dist/telemetry/app-instrumented.d.ts +2 -1
  261. package/dist/telemetry/app-instrumented.js +1 -1
  262. package/dist/telemetry/automation-hint.d.ts +1 -1
  263. package/dist/telemetry/automation-hint.js +1 -1
  264. package/dist/telemetry/cli-telemetry.js +1 -1
  265. package/dist/telemetry/connect-failure.d.ts +1 -1
  266. package/dist/telemetry/connect-failure.js +1 -1
  267. package/dist/telemetry/crash-cause.d.ts +3 -2
  268. package/dist/telemetry/crash-cause.js +1 -1
  269. package/dist/telemetry/daemon-telemetry.d.ts +3 -2
  270. package/dist/telemetry/daemon-telemetry.js +1 -1
  271. package/dist/telemetry/error-class.d.ts +3 -2
  272. package/dist/telemetry/error-class.js +1 -1
  273. package/dist/telemetry/feedback-cli.js +1 -1
  274. package/dist/telemetry/feedback-context.d.ts +3 -2
  275. package/dist/telemetry/feedback-context.js +5 -4
  276. package/dist/telemetry/feedback-local.d.ts +3 -2
  277. package/dist/telemetry/feedback.d.ts +4 -3
  278. package/dist/telemetry/feedback.js +1 -1
  279. package/dist/telemetry/git-facts.d.ts +2 -2
  280. package/dist/telemetry/git-facts.js +1 -1
  281. package/dist/telemetry/identify.d.ts +1 -1
  282. package/dist/telemetry/identify.js +1 -1
  283. package/dist/telemetry/init-telemetry.d.ts +2 -14
  284. package/dist/telemetry/init-telemetry.js +4 -13
  285. package/dist/telemetry/install-source.d.ts +7 -13
  286. package/dist/telemetry/install-source.js +7 -29
  287. package/dist/telemetry/license-activation.d.ts +3 -2
  288. package/dist/telemetry/license-activation.js +1 -1
  289. package/dist/telemetry/machine-snapshot.d.ts +1 -1
  290. package/dist/telemetry/mcp-connection.js +1 -1
  291. package/dist/telemetry/project-profile.d.ts +0 -14
  292. package/dist/telemetry/project-profile.js +1 -1
  293. package/dist/telemetry/proxy-telemetry.js +1 -1
  294. package/dist/telemetry/session-metrics.d.ts +1 -1
  295. package/dist/telemetry/session-metrics.js +1 -1
  296. package/dist/telemetry/telemetry.d.ts +1 -17
  297. package/dist/telemetry/telemetry.js +4 -4
  298. package/dist/telemetry/tool-refused.d.ts +1 -1
  299. package/dist/telemetry/tool-refused.js +1 -1
  300. package/dist/tools/act-observation.d.ts +3 -2
  301. package/dist/tools/act-preflight.js +8 -3
  302. package/dist/tools/act-target.d.ts +4 -0
  303. package/dist/tools/act-target.js +14 -5
  304. package/dist/tools/act-tools.d.ts +0 -2
  305. package/dist/tools/act-tools.js +45 -15
  306. package/dist/tools/assert-source.d.ts +30 -0
  307. package/dist/tools/assert-source.js +21 -0
  308. package/dist/tools/assert-verdict.d.ts +1 -1
  309. package/dist/tools/assert-verdict.js +14 -4
  310. package/dist/tools/browser-tools.js +28 -8
  311. package/dist/tools/complete-snapshot.d.ts +3 -2
  312. package/dist/tools/dynamic-tools.js +8 -0
  313. package/dist/tools/error-recovery.d.ts +10 -0
  314. package/dist/tools/error-recovery.js +23 -0
  315. package/dist/tools/feedback-tools.js +1 -1
  316. package/dist/tools/invoke-tool.js +2 -0
  317. package/dist/tools/lease-tools.d.ts +48 -20
  318. package/dist/tools/lease-tools.js +311 -76
  319. package/dist/tools/lineage-tools.d.ts +14 -0
  320. package/dist/tools/lineage-tools.js +64 -0
  321. package/dist/tools/merge-tools.js +1 -3
  322. package/dist/tools/navigate-arrival.d.ts +45 -1
  323. package/dist/tools/navigate-arrival.js +39 -6
  324. package/dist/tools/navigate-result.d.ts +8 -1
  325. package/dist/tools/navigate-result.js +14 -4
  326. package/dist/tools/numeric-bounds.d.ts +0 -11
  327. package/dist/tools/numeric-bounds.js +4 -4
  328. package/dist/tools/observe-tools.js +22 -26
  329. package/dist/tools/observed-nothing.d.ts +9 -0
  330. package/dist/tools/observed-nothing.js +2 -1
  331. package/dist/tools/read-tools.js +12 -17
  332. package/dist/tools/real-input-attempt.d.ts +14 -6
  333. package/dist/tools/real-input-attempt.js +75 -18
  334. package/dist/tools/reconcile-tools.js +2 -1
  335. package/dist/tools/reload-result.d.ts +6 -1
  336. package/dist/tools/reload-result.js +7 -2
  337. package/dist/tools/resolve-target.d.ts +7 -1
  338. package/dist/tools/resolve-target.js +72 -9
  339. package/dist/tools/settle-in-flight.d.ts +1 -1
  340. package/dist/tools/settle-in-flight.js +9 -2
  341. package/dist/tools/snapshot-delta.d.ts +24 -1
  342. package/dist/tools/snapshot-delta.js +66 -7
  343. package/dist/tools/tool-names.d.ts +1 -0
  344. package/dist/tools/tool-names.js +1 -0
  345. package/dist/tools/tool-surface.js +20 -2
  346. package/dist/tools/tools.js +120 -11
  347. package/dist/tools/verify-next-baton.d.ts +2 -1
  348. package/dist/update/sdk-sync.d.ts +1 -1
  349. package/dist/update/sdk-sync.js +1 -1
  350. package/dist/update/updater.js +1 -1
  351. package/dist/version/sdk-fix.d.ts +2 -1
  352. package/dist/version/version-skew.d.ts +0 -8
  353. package/dist/version/version-skew.js +1 -1
  354. package/docs/README.md +8 -6
  355. package/docs/actions.mdx +1 -1
  356. package/docs/agent-cheatsheet.md +4 -2
  357. package/docs/architecture.md +1 -1
  358. package/docs/cli/mcp.mdx +3 -1
  359. package/docs/cli/watch.mdx +1 -1
  360. package/docs/cli.mdx +1 -1
  361. package/docs/desktop.mdx +1 -1
  362. package/docs/docs.json +8 -3
  363. package/docs/faq.mdx +9 -0
  364. package/docs/first-drive.md +136 -0
  365. package/docs/frameworks.mdx +29 -7
  366. package/docs/gate-plan.md +2 -2
  367. package/docs/gates.md +19 -11
  368. package/docs/getting-started.md +7 -1
  369. package/docs/http-transport.md +164 -0
  370. package/docs/index.mdx +5 -3
  371. package/docs/install-manual.mdx +1 -1
  372. package/docs/packages/tauri.mdx +1 -1
  373. package/docs/packages.mdx +15 -3
  374. package/docs/platform-integration.md +1 -1
  375. package/docs/predicates.mdx +2 -0
  376. package/docs/system-map.md +2 -2
  377. package/docs/telemetry-contract.md +4 -4
  378. package/docs/telemetry-events.mdx +1 -1
  379. package/docs/telemetry.md +3 -3
  380. package/docs/tools-navigate.mdx +14 -6
  381. package/docs/usage.md +4 -4
  382. package/package.json +7 -4
  383. package/dist/init/agent-rules.d.ts +0 -116
  384. package/dist/init/agent-rules.js +0 -279
  385. package/dist/init/app-choice.d.ts +0 -20
  386. package/dist/init/app-choice.js +0 -25
  387. package/dist/init/astro-host.d.ts +0 -21
  388. package/dist/init/astro-host.js +0 -27
  389. package/dist/init/astro-patch.d.ts +0 -33
  390. package/dist/init/astro-patch.js +0 -300
  391. package/dist/init/capabilities.d.ts +0 -40
  392. package/dist/init/capabilities.js +0 -150
  393. package/dist/init/closing-hint.d.ts +0 -36
  394. package/dist/init/closing-hint.js +0 -120
  395. package/dist/init/connect-steps.d.ts +0 -2
  396. package/dist/init/connect-steps.js +0 -50
  397. package/dist/init/cra.d.ts +0 -79
  398. package/dist/init/cra.js +0 -178
  399. package/dist/init/csp-check.d.ts +0 -38
  400. package/dist/init/csp-check.js +0 -107
  401. package/dist/init/csp-doctor.d.ts +0 -32
  402. package/dist/init/csp-doctor.js +0 -54
  403. package/dist/init/desktop-doctor.d.ts +0 -41
  404. package/dist/init/desktop-doctor.js +0 -198
  405. package/dist/init/detect.d.ts +0 -96
  406. package/dist/init/detect.js +0 -209
  407. package/dist/init/dev-script.d.ts +0 -50
  408. package/dist/init/dev-script.js +0 -66
  409. package/dist/init/format-generated.d.ts +0 -18
  410. package/dist/init/format-generated.js +0 -39
  411. package/dist/init/install-hint.d.ts +0 -9
  412. package/dist/init/install-hint.js +0 -29
  413. package/dist/init/mcp-clients.d.ts +0 -89
  414. package/dist/init/mcp-clients.js +0 -299
  415. package/dist/init/mcp-registered.d.ts +0 -12
  416. package/dist/init/mcp-registered.js +0 -14
  417. package/dist/init/mcp.d.ts +0 -89
  418. package/dist/init/mcp.js +0 -129
  419. package/dist/init/next-patch.d.ts +0 -39
  420. package/dist/init/next-patch.js +0 -196
  421. package/dist/init/node-io.d.ts +0 -7
  422. package/dist/init/node-io.js +0 -117
  423. package/dist/init/non-js-project.d.ts +0 -27
  424. package/dist/init/non-js-project.js +0 -84
  425. package/dist/init/patch-kind.d.ts +0 -20
  426. package/dist/init/patch-kind.js +0 -10
  427. package/dist/init/plan-framework.d.ts +0 -64
  428. package/dist/init/plan-framework.js +0 -565
  429. package/dist/init/plan.d.ts +0 -242
  430. package/dist/init/plan.js +0 -703
  431. package/dist/init/preflight.d.ts +0 -28
  432. package/dist/init/preflight.js +0 -38
  433. package/dist/init/project-id.d.ts +0 -12
  434. package/dist/init/project-id.js +0 -25
  435. package/dist/init/refresh-rules.d.ts +0 -39
  436. package/dist/init/refresh-rules.js +0 -51
  437. package/dist/init/run.d.ts +0 -117
  438. package/dist/init/run.js +0 -806
  439. package/dist/init/slash-command.d.ts +0 -27
  440. package/dist/init/slash-command.js +0 -132
  441. package/dist/init/snippets.d.ts +0 -134
  442. package/dist/init/snippets.js +0 -572
  443. package/dist/init/vite-config.d.ts +0 -18
  444. package/dist/init/vite-config.js +0 -114
  445. package/dist/init/windows-quote.d.ts +0 -38
  446. package/dist/init/windows-quote.js +0 -70
  447. package/dist/init/workspace-apps.d.ts +0 -30
  448. package/dist/init/workspace-apps.js +0 -178
  449. package/dist/project/remember-project.d.ts +0 -24
  450. package/dist/project/remember-project.js +0 -40
  451. /package/dist/{init → setup}/confirm.d.ts +0 -0
@@ -37,6 +37,49 @@ export function routePathOf(pathname, hash) {
37
37
  const query = withoutHash.indexOf('?');
38
38
  return -1 === query ? withoutHash : withoutHash.slice(0, query);
39
39
  }
40
+ function asPayload(value) {
41
+ if (null === value || 'object' !== typeof value || Array.isArray(value))
42
+ return undefined;
43
+ return value;
44
+ }
45
+ function payloadOf(eventOrData) {
46
+ if (eventOrData['type'] === EventType.ROUTE_CHANGE)
47
+ return asPayload(eventOrData['data']);
48
+ // A different event: refuse rather than read whatever happened to sit in `data`.
49
+ if (undefined !== eventOrData['type'] && undefined !== eventOrData['data'])
50
+ return undefined;
51
+ return eventOrData;
52
+ }
53
+ function partsFromPayload(data) {
54
+ const docPath = str(data['pathname']) ?? str(data['to']);
55
+ if (docPath === undefined)
56
+ return undefined;
57
+ const hash = str(data['hash']) ?? '';
58
+ const search = str(data['search']) ?? '';
59
+ return {
60
+ routePath: routePathOf(docPath, hash),
61
+ docPath,
62
+ hash,
63
+ search,
64
+ full: `${docPath}${search}${hash}`,
65
+ };
66
+ }
67
+ /** A `ROUTE_CHANGE` event, or its `data` — one reading, so the sixth copy cannot drift. */
68
+ export function routeOfEvent(eventOrData) {
69
+ const data = payloadOf(eventOrData);
70
+ return data === undefined ? undefined : partsFromPayload(data);
71
+ }
72
+ /** Same shape from a session URL (the fallback when the tab hard-loaded and emitted no route event). */
73
+ export function routeOfUrl(url) {
74
+ if (!URL.canParse(url))
75
+ return undefined;
76
+ const parsed = new URL(url);
77
+ return partsFromPayload({
78
+ pathname: parsed.pathname,
79
+ search: parsed.search,
80
+ hash: parsed.hash,
81
+ });
82
+ }
40
83
  /**
41
84
  * The session's live URL, split the way a route-change event already carries it.
42
85
  *
@@ -44,14 +87,14 @@ export function routePathOf(pathname, hash) {
44
87
  * away — an unreadable URL is not worth losing the fallback over.
45
88
  */
46
89
  function readCurrentRoute(url) {
47
- const parsed = URL.canParse(url) ? new URL(url) : undefined;
48
- const pathname = parsed?.pathname ?? url;
49
- const search = parsed?.search ?? '';
50
- const hash = parsed?.hash ?? '';
90
+ const parts = routeOfUrl(url);
91
+ const pathname = parts?.docPath ?? url;
92
+ const search = parts?.search ?? '';
93
+ const hash = parts?.hash ?? '';
51
94
  return {
52
95
  pathname,
53
- routePath: routePathOf(pathname, hash),
54
- full: `${pathname}${search}${hash}`,
96
+ routePath: parts?.routePath ?? routePathOf(pathname, hash),
97
+ full: parts?.full ?? `${pathname}${search}${hash}`,
55
98
  decidedBy: RouteDecidedBy.CURRENT,
56
99
  data: { pathname, search, hash, url, decidedBy: RouteDecidedBy.CURRENT },
57
100
  };
@@ -72,25 +115,41 @@ export function evalRoute(events, p,
72
115
  * `reticle_snapshot({ mode: 'status' }).route` one call later. Optional so a caller with no session
73
116
  * (a replayed window) keeps the old answer instead of inventing a route.
74
117
  */
75
- currentUrl) {
118
+ currentUrl,
119
+ /** Event-time floor for the window, so a request already in flight before it is not counted. */
120
+ since = 0) {
76
121
  const routes = events.filter((e) => e.type === EventType.ROUTE_CHANGE);
77
122
  const last = routes.at(-1);
78
- const reading = last !== undefined
79
- ? (() => {
80
- const changedPath = str(last.data['pathname']) ?? str(last.data['to']) ?? '';
81
- const changedHash = str(last.data['hash']) ?? '';
82
- return {
83
- pathname: changedPath,
84
- routePath: routePathOf(changedPath, changedHash),
85
- full: `${changedPath}${str(last.data['search']) ?? ''}${changedHash}`,
86
- decidedBy: RouteDecidedBy.CHANGE,
87
- data: { ...last.data, decidedBy: RouteDecidedBy.CHANGE },
88
- };
89
- })()
123
+ const changed = last === undefined ? undefined : routeOfEvent(last);
124
+ const reading = changed !== undefined && last !== undefined
125
+ ? {
126
+ pathname: changed.docPath,
127
+ routePath: changed.routePath,
128
+ full: changed.full,
129
+ decidedBy: RouteDecidedBy.CHANGE,
130
+ data: { ...last.data, decidedBy: RouteDecidedBy.CHANGE },
131
+ }
90
132
  : currentUrl === undefined || 0 === currentUrl.length
91
133
  ? undefined
92
134
  : readCurrentRoute(currentUrl);
93
135
  if (reading === undefined) {
136
+ // A navigation the SERVER never answered reads exactly like one the APP prevented, and only one
137
+ // of those is the user's bug. See unansweredIn.
138
+ const unanswered = unansweredIn(events, since);
139
+ if (unanswered !== undefined) {
140
+ const reason = `no route change, and ${unanswered.count} request(s) started in this window never ` +
141
+ `completed — first: ${unanswered.method} ${unanswered.url}. A navigation the server has ` +
142
+ 'not answered is indistinguishable from one the app declined, so this is not evidence ' +
143
+ 'against the app. Check the server directly (curl the route) before looking at components';
144
+ return {
145
+ pass: false,
146
+ failureReason: reason,
147
+ inconclusive: reason,
148
+ observed: `${unanswered.count} request(s) still in flight, no route change`,
149
+ expected: `a route change to ${p.pathname ?? p.contains ?? 'any route'}`,
150
+ assertion: 'route.server-pending',
151
+ };
152
+ }
94
153
  return {
95
154
  pass: false,
96
155
  failureReason: 'no route change observed',
@@ -99,7 +158,7 @@ currentUrl) {
99
158
  assertion: 'route.changed',
100
159
  };
101
160
  }
102
- // The ROUTER's path, not the document's — see routePathOf.
161
+ // The ROUTER's path, not the document's — see routeOfEvent.
103
162
  const pathname = reading.routePath;
104
163
  if (p.pathname !== undefined && pathname !== p.pathname) {
105
164
  return {
@@ -126,5 +185,74 @@ currentUrl) {
126
185
  assertion: 'route.contains',
127
186
  };
128
187
  }
129
- return { pass: true, evidence: reading.data };
188
+ return {
189
+ pass: true,
190
+ evidence: reading.decidedBy === RouteDecidedBy.CHANGE
191
+ ? { ...reading.data, viewSettled: sawActivityAfter(events, last?.t ?? 0) }
192
+ : reading.data,
193
+ };
194
+ }
195
+ /**
196
+ * Did the page do ANYTHING after the URL moved?
197
+ *
198
+ * A route predicate goes green the instant the URL commits, and on an async-loading page that is a
199
+ * verdict taken over a shell. The reporter's sequence is the whole argument: `allOf [route, console
200
+ * absent]` returned `verified: "yes"`, a snapshot of `main` came back with 9 nodes that read as an
201
+ * empty page, and re-snapshotting moments later showed 22 with the real content — one step from
202
+ * filing "this route renders nothing" against code that was fine.
203
+ *
204
+ * The grade is deliberately NOT changed. Requiring a settle would make every route assertion wait
205
+ * and would flip green suites red, and the report's own acceptance allows either that or evidence
206
+ * saying the view had not settled. This is the second, and it costs nothing that already worked.
207
+ *
208
+ * No clock is read: if the route change is the LAST thing in the window, nothing has been observed
209
+ * since the URL moved, which is exactly the trap. Activity BEFORE the change is ignored — a mutation
210
+ * that predates the navigation says nothing about what the new route rendered.
211
+ *
212
+ * Reported only for a CHANGE reading. A pass decided by "the app is already there" has no commit
213
+ * moment to be too close to, and a caveat there would describe a reading it is not about.
214
+ */
215
+ function sawActivityAfter(events, routeAt) {
216
+ return events.some((e) => e.t > routeAt && ROUTE_SETTLE_ACTIVITY.has(e.type));
217
+ }
218
+ /**
219
+ * What counts as the view still moving. Same set the `settled` oracle uses, and deliberately the
220
+ * same one: two different answers to "did the page do something" is how a vocabulary rots.
221
+ */
222
+ const ROUTE_SETTLE_ACTIVITY = new Set([
223
+ EventType.NET_REQUEST,
224
+ EventType.DOM_ADDED,
225
+ EventType.DOM_REMOVED,
226
+ EventType.DOM_ATTR,
227
+ ]);
228
+ /**
229
+ * A request that STARTED in this window and never completed.
230
+ *
231
+ * The discriminator the route oracle was missing. `routeChanges: 0, network: 0` is reported the same
232
+ * way whether the app prevented the navigation or the server never answered the request the app
233
+ * made — and only the first is the user's bug. Reticle already saw the pending navigation; it just
234
+ * never said so, and attached a `source:` file:line that pointed at innocent code.
235
+ *
236
+ * Scoped to requests that started INSIDE the window, deliberately. A background poll that predates
237
+ * the action says nothing about this navigation, and counting it would make every route miss on a
238
+ * polling app inconclusive — softening away the finding this oracle exists to make. That is the same
239
+ * trap the duplicate-request and contradiction rules were each narrowed to avoid.
240
+ *
241
+ * Matched on the request id, the way `detectHungRequests` does: a NET_PENDING with no NET_REQUEST
242
+ * carrying the same id.
243
+ */
244
+ function unansweredIn(events, since) {
245
+ const settled = new Set();
246
+ for (const e of events)
247
+ if (e.type === EventType.NET_REQUEST)
248
+ settled.add(e.data['id']);
249
+ const open = events.filter((e) => e.type === EventType.NET_PENDING && e.t >= since && !settled.has(e.data['id']));
250
+ const first = open[0];
251
+ if (first === undefined)
252
+ return undefined;
253
+ return {
254
+ count: open.length,
255
+ url: str(first.data['url']) ?? '(unknown url)',
256
+ method: str(first.data['method']) ?? 'GET',
257
+ };
130
258
  }
@@ -19,6 +19,8 @@ export type Predicate = {
19
19
  * `act_and_wait` reports `already_true` for an action that did exactly the right thing.
20
20
  */
21
21
  scope?: string;
22
+ /** Match the scope root itself and check its combined subtree text. Requires `scope`. */
23
+ self?: boolean;
22
24
  } | {
23
25
  kind: typeof PredicateKind.NET;
24
26
  method?: string;
@@ -30,6 +32,10 @@ export type Predicate = {
30
32
  count?: number;
31
33
  /** A substring the RESPONSE body must contain — what the server answered, not what was sent. */
32
34
  bodyContains?: string;
35
+ /** A substring the REQUEST body must contain — what the UI sent, not what came back. */
36
+ requestBodyContains?: string;
37
+ /** Shallow JSON match over the REQUEST body, in the style of `signal.dataMatches`. */
38
+ requestBodyMatches?: Record<string, unknown>;
33
39
  } | {
34
40
  kind: typeof PredicateKind.ROUTE;
35
41
  pathname?: string;
@@ -80,7 +86,7 @@ export type Predicate = {
80
86
  kind: typeof PredicateKind.NOT;
81
87
  predicate: Predicate;
82
88
  };
83
- export interface ResidualQueryChecks {
89
+ interface ResidualQueryChecks {
84
90
  /** Dropped fields this side CAN check, as [field, wanted value] pairs. */
85
91
  checks: [string, string][];
86
92
  /** Dropped fields with no descriptor to check them against — refuse rather than ignore. */
@@ -159,3 +165,4 @@ export declare function nestedKeysOf(schema: z.ZodTypeAny | undefined): readonly
159
165
  * list is worse than none, because the agent trusts it and retries into the same wall.
160
166
  */
161
167
  export declare function predicateNestedFieldsFor(kind: string): Readonly<Record<string, readonly string[]>>;
168
+ export {};
@@ -82,6 +82,10 @@ const ELEMENT_QUERY_FIELDS = [
82
82
  */
83
83
  function usedQueryFields(query) {
84
84
  const used = new Set();
85
+ // The browser's `self` branch checks subtree text when supplied, but skips every other locator.
86
+ // Mark only text as consumed so role/name/etc. remain residual checks on the returned descriptor.
87
+ if (true === query.self)
88
+ return query.text === undefined ? used : used.add('text');
85
89
  if (query.by !== undefined && query.value !== undefined) {
86
90
  used.add('by').add('value');
87
91
  if (QueryBy.ROLE === query.by)
@@ -231,6 +235,7 @@ function predicateUnion() {
231
235
  visible: z.boolean().optional(),
232
236
  absent: z.boolean().optional(),
233
237
  scope: z.string().optional(),
238
+ self: z.boolean().optional(),
234
239
  })
235
240
  .strict(),
236
241
  z
@@ -260,6 +265,35 @@ function predicateUnion() {
260
265
  * was never recorded rather than reporting an ordinary mismatch.
261
266
  */
262
267
  bodyContains: z.string().min(1).optional(),
268
+ /**
269
+ * A substring the REQUEST body must contain — the other half of `bodyContains`.
270
+ *
271
+ * `bodyContains` deliberately searches only the response, and the comment above says why:
272
+ * searching the request too would let it pass on the very defect it exists to catch. That
273
+ * reasoning is sound and it leaves a real gap. For a filter, a search or a form, the thing
274
+ * under test IS the outgoing payload: "applying this filter actually sends it" had no
275
+ * verdict at all, only `reticle_network { bodies: true }` and a human reading `requestBody`
276
+ * by eye (#798).
277
+ *
278
+ * A separate field rather than a mode on `bodyContains`, so neither assertion can ever be
279
+ * satisfied by the wrong half of the exchange.
280
+ *
281
+ * Requires body capture, and says so when the request body was never recorded rather than
282
+ * reporting an ordinary mismatch.
283
+ */
284
+ requestBodyContains: z.string().min(1).optional(),
285
+ /**
286
+ * Shallow JSON match over the REQUEST body, keyed like `signal.dataMatches` and sharing its
287
+ * `matchValue` operators (`*`, `$gte`, `$contains`, …).
288
+ *
289
+ * `{ requestBodyMatches: { filter: "manual_review" } }` is the one-call verdict for "the UI
290
+ * put the selected value into the payload", and it does not care about key order,
291
+ * whitespace or how the client serialised the object — all of which a substring does.
292
+ *
293
+ * A field whose captured value was REDACTED cannot be matched, and the verdict says so
294
+ * instead of reporting a mismatch: a redacted field is unknown, not different.
295
+ */
296
+ requestBodyMatches: z.record(z.string(), z.unknown()).optional(),
263
297
  })
264
298
  .strict(),
265
299
  z
@@ -50,6 +50,8 @@ export interface PredicateSession {
50
50
  * Optional: a fake that never throttles simply omits it.
51
51
  */
52
52
  throttled?(): boolean;
53
+ /** Precondition failure reason (e.g. seedStorage), if any. */
54
+ preconditionFailure?(): string | undefined;
53
55
  }
54
56
  export declare function evaluatePredicate(session: PredicateSession, predicate: Predicate, since?: number, diagnose?: boolean): Promise<EvalResult>;
55
57
  /**
@@ -2,13 +2,12 @@ import { ElementState, PredicateKind, ReticleCommand, THROTTLED_STARVED_NOTE, is
2
2
  import { log } from '../log.js';
3
3
  import { bindSpanContext } from '../trace.js';
4
4
  import { selectPath, capDepth } from '../session/state-select.js';
5
- import { describeTestidMiss } from './testid-near-miss.js';
6
- import { describeSplitTextMiss } from './split-text-miss.js';
7
5
  import { predicateToExpectedLinks } from '../capsule/predicate-to-links.js';
8
6
  import { isAmbient, ambientKeyOf } from '../journal/ambient.js';
9
7
  import { evalRoute } from './predicate-route.js';
10
8
  import { describeSuperseded } from './observed-in-window.js';
11
- import { PredicateSchema, matchValue, evalNet, evalConsole, evalAnimation, evalSignal, evalSettled, residualQueryChecks, satisfiesResiduals, describeResidual, } from './predicate-eval.js';
9
+ import { evalElement } from './predicate-element.js';
10
+ import { PredicateSchema, matchValue, evalNet, evalConsole, evalAnimation, evalSignal, evalSettled, } from './predicate-eval.js';
12
11
  export { PredicateSchema, PredicateKind };
13
12
  /**
14
13
  * A composite result that carries a child's "nobody could evaluate this" up to the verdict rule.
@@ -22,155 +21,6 @@ function unreadableComposite(child, evidence) {
22
21
  const reason = child.inconclusive ?? 'a sub-predicate could not be evaluated';
23
22
  return { pass: false, failureReason: reason, inconclusive: reason, evidence };
24
23
  }
25
- async function matchOnce(session, query, state) {
26
- const res = await session.command(ReticleCommand.MATCH, { query, state });
27
- if (!res.ok)
28
- return { matched: false, count: 0, elements: [] };
29
- return (res.result ?? { matched: false, count: 0, elements: [] });
30
- }
31
- async function evalElement(session, query, state, absent, diagnose) {
32
- // Fields the browser's locator would have DROPPED, enforced back here — see residualQueryChecks.
33
- // Checked before the round-trip when nothing can enforce them: a predicate that cannot be evaluated
34
- // must say so rather than resolve to whatever the surviving half of it happened to match.
35
- const residual = residualQueryChecks(query);
36
- if (residual.unusable.length > 0) {
37
- const reason = `the element locator ignores ${residual.unusable.map((f) => `\`${f}\``).join(', ')} ` +
38
- `in ${JSON.stringify(query)} — it resolves by the first of by+value, component/source, role, ` +
39
- 'text, label, placeholder, testid, alt that is present, and nothing here can check the rest. ' +
40
- 'Assert them one locator at a time, or move the extra field into the locator';
41
- return { pass: false, failureReason: reason, inconclusive: reason };
42
- }
43
- let match = await matchOnce(session, query, state);
44
- const subject = JSON.stringify(query);
45
- // A residual narrows the SET; `count` is every match while `elements` is only the described prefix,
46
- // so a locator broad enough to be truncated cannot be narrowed honestly. Say so instead of guessing.
47
- if (residual.checks.length > 0 && match.count > match.elements.length) {
48
- const reason = `${String(match.count)} elements matched ${subject} and only ${String(match.elements.length)} were described, so ${residual.checks.map(([f]) => `\`${f}\``).join(', ')} could not be checked against all of them — narrow the locator`;
49
- return { pass: false, failureReason: reason, inconclusive: reason };
50
- }
51
- const kept = match.elements.filter((element) => satisfiesResiduals(element, residual.checks));
52
- // The locator found something and the dropped fields disagree with it. Reported separately from a
53
- // plain miss because the fixes are opposite: the element IS there, its value is not what was claimed.
54
- if (residual.checks.length > 0 && match.matched && 0 === kept.length && !absent) {
55
- const wanted = residual.checks.map(([f, want]) => `${f}=${JSON.stringify(want)}`).join(', ');
56
- return {
57
- pass: false,
58
- failureReason: `element matching ${subject} is present but ${wanted} does not hold`,
59
- observed: match.elements
60
- .map((element) => residual.checks.map(([f]) => describeResidual(element, f)).join(', '))
61
- .join('; '),
62
- expected: `an element matching ${subject} with ${wanted}`,
63
- assertion: `element.${residual.checks[0]?.[0] ?? 'residual'}`,
64
- evidence: match.elements,
65
- };
66
- }
67
- if (residual.checks.length > 0) {
68
- match = { ...match, matched: kept.length > 0, count: kept.length, elements: kept };
69
- }
70
- // A given-but-missing scope is handled ASYMMETRICALLY, because "absent" and "present" ask different
71
- // questions of a scope that no longer exists:
72
- // - ABSENT: an element is trivially absent from a container that isn't there. This is also the
73
- // everyday "wait for the #overlay/#spinner/#modal to disappear" pattern (scope the wait to the
74
- // node being removed) — treating scopeMissing as a hard fail there burned the whole timeout and
75
- // flipped a correct green to red. So scopeMissing satisfies an absence check.
76
- // - PRESENT: you cannot confirm an element is present inside a scope that resolved to nothing, and
77
- // silently widening to the whole page is the original false green. So scopeMissing FAILS presence
78
- // (on the wait_for path this just keeps polling until the scope appears).
79
- if (absent) {
80
- if (true === match.scopeMissing) {
81
- return { pass: true, evidence: { absent: true, scopeMissing: true } };
82
- }
83
- return match.matched
84
- ? {
85
- pass: false,
86
- failureReason: `expected element to be absent but found ${String(match.count)}`,
87
- observed: `${String(match.count)} element(s) matching ${subject}`,
88
- expected: `no element matching ${subject}`,
89
- assertion: 'element.absent',
90
- evidence: match.elements,
91
- }
92
- : { pass: true, evidence: { absent: true } };
93
- }
94
- if (true === match.scopeMissing) {
95
- return {
96
- pass: false,
97
- failureReason: `scope resolved to no element — cannot confirm ${subject} is present`,
98
- observed: 'the requested scope is not on the page (unmounted or selector matched nothing)',
99
- expected: `an element matching ${subject} within an existing scope`,
100
- assertion: 'element.present',
101
- evidence: { scopeMissing: true },
102
- };
103
- }
104
- if (match.matched)
105
- return { pass: true, evidence: match.elements };
106
- // The near-miss diagnostic below costs one or two EXTRA MATCH round-trips. It only enriches a FAILED
107
- // verdict, and a wait loop's interim rechecks read nothing but `pass` — so on the poll path (diagnose
108
- // false) skip straight to the plain fail. Under an event flood a role+name element wait was firing
109
- // two live-DOM scans per recheck for a diagnostic no interim eval ever reads; the final timeout eval
110
- // still runs with diagnose=true and produces the full near-miss.
111
- if (!diagnose) {
112
- return {
113
- pass: false,
114
- failureReason: `no element matched ${subject}${state === undefined ? '' : ` in state '${state}'`}`,
115
- observed: 'no matching element on the page',
116
- expected: `an element matching ${subject}${state === undefined ? '' : ` in state '${state}'`}`,
117
- assertion: 'element.present',
118
- };
119
- }
120
- // Diagnostic near-miss: was it there but in the wrong state, or a similar element present?
121
- if (state !== undefined) {
122
- const relaxed = await matchOnce(session, query, undefined);
123
- if (relaxed.matched) {
124
- return {
125
- pass: false,
126
- failureReason: `element exists but not in state '${state}'`,
127
- observed: `element matching ${subject} is present, states: ${relaxed.elements[0]?.states.join(', ') ?? 'unknown'}`,
128
- expected: `element matching ${subject} in state '${state}'`,
129
- assertion: 'element.state',
130
- evidence: { nearMiss: relaxed.elements },
131
- };
132
- }
133
- }
134
- if (query.role !== undefined && query.name !== undefined) {
135
- const roleOnly = await matchOnce(session, { role: query.role }, state);
136
- if (roleOnly.matched) {
137
- return {
138
- pass: false,
139
- failureReason: `no '${query.role}' named '${query.name}'; saw: ${roleOnly.elements
140
- .map((e) => e.name)
141
- .filter((n) => n.length > 0)
142
- .join(', ')}`,
143
- observed: `${String(roleOnly.count)} '${query.role}' element(s), named: ${roleOnly.elements
144
- .map((e) => e.name)
145
- .filter((n) => n.length > 0)
146
- .join(', ')}`,
147
- expected: `a '${query.role}' named '${query.name}'`,
148
- assertion: 'element.role+name',
149
- evidence: { nearMiss: roleOnly.elements },
150
- };
151
- }
152
- }
153
- // The testid near-miss: name what IS here, so a typo is one step from fixed rather than a dead
154
- // end. reticle_query has always done this; the predicate path had no equivalent. See
155
- // testid-near-miss.ts.
156
- const present = match.hint?.presentTestids ?? [];
157
- const alsoHere = query.testid === undefined ? undefined : describeTestidMiss(query.testid, present);
158
- // A text miss where the string is on the page but split across children reads exactly like an
159
- // element that never rendered. Naming the container is the difference between a retry and a bug
160
- // report against working code. See split-text-miss.ts.
161
- const splitText = describeSplitTextMiss(match.hint?.splitText);
162
- const clause = splitText ?? (alsoHere === undefined || '' === alsoHere ? undefined : alsoHere);
163
- const suffix = clause === undefined ? '' : ` — ${clause}`;
164
- return {
165
- pass: false,
166
- failureReason: `no element matched ${subject}${state === undefined ? '' : ` in state '${state}'`}${suffix}`,
167
- observed: `no matching element on the page${suffix}`,
168
- expected: `an element matching ${subject}${state === undefined ? '' : ` in state '${state}'`}`,
169
- assertion: 'element.present',
170
- ...(present.length > 0 ? { evidence: { presentTestids: present } } : {}),
171
- };
172
- }
173
- /** The single element of a list, or undefined when there is not exactly one. */
174
24
  function oneOf(names) {
175
25
  return 1 === names.length ? names[0] : undefined;
176
26
  }
@@ -439,17 +289,48 @@ function predicateSince(predicate) {
439
289
  * Idempotent, and a more specific `inconclusive` (unreadable locator, superseded window) is never
440
290
  * overwritten.
441
291
  */
442
- function annotateThrottledMiss(session, result) {
292
+ function annotateThrottledMiss(session, predicate, result) {
443
293
  if (result.pass)
444
294
  return result;
295
+ if (result.inconclusive !== undefined)
296
+ return result;
297
+ const preconditionFailure = session.preconditionFailure?.();
298
+ if (preconditionFailure !== undefined) {
299
+ return { ...result, inconclusive: preconditionFailure };
300
+ }
445
301
  if (true !== session.throttled?.())
446
302
  return result;
447
- if (result.inconclusive !== undefined)
303
+ if (failureRestsOnSeeing(predicate))
448
304
  return result;
449
305
  return { ...result, inconclusive: THROTTLED_STARVED_NOTE };
450
306
  }
307
+ /**
308
+ * Does this predicate FAIL by having seen something, rather than by not having seen it?
309
+ *
310
+ * The starved-tab caveat only applies to a negative reading. Throttling can stop the tab rendering,
311
+ * so "I did not find it" may mean "I could not look" — but nothing about a starved tab conjures
312
+ * elements that were not there, so "I found 13 of them" is as true on a throttled tab as anywhere.
313
+ * An `absent: true` predicate inverts exactly that: its failure IS the positive observation.
314
+ *
315
+ * Getting this wrong understated real product failures. An absence assertion that matched a
316
+ * framework debug page's `ProgrammingError` heading came back `unknown`, and `unknown` is what an
317
+ * agent re-drives or walks away from — so the proof it was holding never reached anybody.
318
+ *
319
+ * `not` flips the polarity again, and nests, so this recurses rather than checking one level.
320
+ * Composites (`allOf` / `anyOf`) deliberately fall through to `false`: a composite fails for a
321
+ * reason this cannot name, and keeping the caveat is the conservative half of the trade — an
322
+ * over-cautious `unknown` costs a re-drive, a missing one costs a wrong verdict.
323
+ */
324
+ function failureRestsOnSeeing(predicate) {
325
+ if (PredicateKind.NOT === predicate.kind)
326
+ return !failureRestsOnSeeing(predicate.predicate);
327
+ if ('absent' in predicate && true === predicate.absent)
328
+ return true;
329
+ // `count: 0` is absence written as arithmetic, and fails the same way: by matching something.
330
+ return 'count' in predicate && 0 === predicate.count;
331
+ }
451
332
  export async function evaluatePredicate(session, predicate, since = 0, diagnose = true) {
452
- return annotateThrottledMiss(session, await evaluatePredicateRaw(session, predicate, since, diagnose));
333
+ return annotateThrottledMiss(session, predicate, await evaluatePredicateRaw(session, predicate, since, diagnose));
453
334
  }
454
335
  async function evaluatePredicateRaw(session, predicate, since = 0,
455
336
  // Compute the (extra-round-trip) near-miss diagnostics on element failures. Default true so a
@@ -492,11 +373,17 @@ diagnose = true) {
492
373
  // with only `text` filled in, so scoping it needs the field, not a second code path.
493
374
  undefined === predicate.scope
494
375
  ? { text: predicate.contains }
495
- : { text: predicate.contains, scope: predicate.scope }, true === predicate.visible ? ElementState.VISIBLE : undefined, predicate.absent ?? false, diagnose);
376
+ : {
377
+ text: predicate.contains,
378
+ scope: predicate.scope,
379
+ ...(true === predicate.self ? { self: true } : {}),
380
+ }, true === predicate.visible ? ElementState.VISIBLE : undefined, predicate.absent ?? false, diagnose);
496
381
  case PredicateKind.NET:
497
382
  return evalNet(events, predicate);
498
383
  case PredicateKind.ROUTE:
499
- return evalRoute(events, predicate, session.url);
384
+ // `since` so an unanswered request already in flight before this window is not counted
385
+ // against the app — see unansweredIn.
386
+ return evalRoute(events, predicate, session.url, Math.max(since, predicateSince(predicate)));
500
387
  case PredicateKind.CONSOLE:
501
388
  return evalConsole(events, predicate);
502
389
  case PredicateKind.ANIMATION:
@@ -605,7 +492,13 @@ function assertsExactCount(predicate) {
605
492
  }
606
493
  if (PredicateKind.NOT === predicate.kind)
607
494
  return assertsExactCount(predicate.predicate);
608
- return PredicateKind.NET === predicate.kind && predicate.count !== undefined;
495
+ // `signal` carries `count` for the same reason `net` does, and for the same defect: its schema
496
+ // calls the double-fire "the defect no state-only oracle can see", because a handler wired twice
497
+ // leaves the store in the right shape and a presence check green on both. Reading `net` alone
498
+ // meant a signal count resolved on its first match, so `count: 1` silently meant "at least 1" on
499
+ // the one channel able to see the bug. `evalSignal` counts correctly; the wait stopped early.
500
+ return ((PredicateKind.NET === predicate.kind || PredicateKind.SIGNAL === predicate.kind) &&
501
+ predicate.count !== undefined);
609
502
  }
610
503
  /**
611
504
  * Evaluate now, else wait for it to become true (on each event + a poll) until timeout. `since` is
@@ -792,7 +685,7 @@ export function waitForPredicate(session, predicate, timeoutMs, since = 0) {
792
685
  // `{ pass, evidence, failureReason }` construction DISCARDED them on every timed-out wait and
793
686
  // assert. So the highest-value localization signal was computed and then thrown away exactly
794
687
  // on the failure path where it matters, no matter what the schema declared.
795
- finish(annotateThrottledMiss(session, {
688
+ finish(annotateThrottledMiss(session, predicate, {
796
689
  ...r,
797
690
  pass: false,
798
691
  failureReason: r.failureReason ?? 'timed out waiting for predicate',
@@ -12,11 +12,12 @@
12
12
  * only, so the channel an agent is most likely to copy from produces the one locator this query
13
13
  * cannot resolve.
14
14
  *
15
- * Naming the container ends it in one step: `ref` is a locator, and `self: true` returns the scope
16
- * element itself, so the recovery is a query the agent can paste rather than another guess.
15
+ * Naming the container ends it in one step: `ref` is a locator, and a scoped text predicate with
16
+ * `self: true` checks the container's combined subtree text. The recovery is a predicate the agent
17
+ * can paste rather than another guess, and it is serialized as JSON so the schema test can parse it.
17
18
  */
18
19
  /** The container descriptor the browser attaches to a missed text query. */
19
- export interface SplitTextOwner {
20
+ interface SplitTextOwner {
20
21
  ref?: string;
21
22
  role?: string;
22
23
  name?: string;
@@ -27,4 +28,5 @@ export interface SplitTextOwner {
27
28
  * Undefined — not a vaguer sentence — when the browser found no container: an ordinary miss keeps the
28
29
  * short message it already had. A clause that fires on every failure stops carrying information.
29
30
  */
30
- export declare function describeSplitTextMiss(owner: SplitTextOwner | undefined): string | undefined;
31
+ export declare function describeSplitTextMiss(owner: SplitTextOwner | undefined, searchedText?: string): string | undefined;
32
+ export {};
@@ -12,9 +12,11 @@
12
12
  * only, so the channel an agent is most likely to copy from produces the one locator this query
13
13
  * cannot resolve.
14
14
  *
15
- * Naming the container ends it in one step: `ref` is a locator, and `self: true` returns the scope
16
- * element itself, so the recovery is a query the agent can paste rather than another guess.
15
+ * Naming the container ends it in one step: `ref` is a locator, and a scoped text predicate with
16
+ * `self: true` checks the container's combined subtree text. The recovery is a predicate the agent
17
+ * can paste rather than another guess, and it is serialized as JSON so the schema test can parse it.
17
18
  */
19
+ import { PredicateKind } from '@reticlehq/core';
18
20
  /** Enough of the label to recognise it; the message is read in a tool result, not a log. */
19
21
  const MAX_TEXT = 60;
20
22
  /** What the container is, in the fewest words that still identify it on the page. */
@@ -32,13 +34,18 @@ function truncate(value) {
32
34
  * Undefined — not a vaguer sentence — when the browser found no container: an ordinary miss keeps the
33
35
  * short message it already had. A clause that fires on every failure stops carrying information.
34
36
  */
35
- export function describeSplitTextMiss(owner) {
37
+ export function describeSplitTextMiss(owner, searchedText) {
36
38
  if (owner === undefined)
37
39
  return undefined;
38
40
  const where = nameOf(owner);
39
41
  const scope = owner.ref !== undefined && owner.ref.length > 0 ? owner.ref : undefined;
40
- const recovery = scope === undefined
42
+ const recovery = scope === undefined || searchedText === undefined
41
43
  ? 'scope to that container and match it as a whole'
42
- : `retry as { scope: '${scope}', self: true }`;
44
+ : `retry as ${JSON.stringify({
45
+ kind: PredicateKind.TEXT,
46
+ contains: searchedText,
47
+ scope,
48
+ self: true,
49
+ })}`;
43
50
  return `that text IS on the page, split across the children of ${where} — no single element's own text carries it, so no text query can match it; ${recovery}`;
44
51
  }