@reticlehq/server 2.3.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 (424) 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} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -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/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  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 +182 -93
  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.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -15,6 +15,7 @@
15
15
  * `console-error`, `duplicate-request`. Never a selector, a URL, an element, or a description of
16
16
  * anyone's app: that a class of defect was found, never what it was found in.
17
17
  */
18
+ import { ReticleTool } from '../tools/tool-names.js';
18
19
  import { BugSource, ContradictionKind } from '@reticlehq/core';
19
20
  /** Cap per tool call — a pathological crawl must not turn one call into a thousand events. */
20
21
  const MAX_BUGS_PER_CALL = 25;
@@ -24,7 +25,7 @@ function kindsOf(value) {
24
25
  return value
25
26
  .map((entry) => {
26
27
  const kind = entry?.kind;
27
- return typeof kind === 'string' ? kind : undefined;
28
+ return 'string' === typeof kind ? kind : undefined;
28
29
  })
29
30
  .filter((kind) => kind !== undefined);
30
31
  }
@@ -35,16 +36,111 @@ function kindsOf(value) {
35
36
  * the case the product exists for — the screen looked right, the write failed, and every other tool
36
37
  * on the market would have called it green. That pairing is what `falseGreen` marks.
37
38
  */
39
+ /**
40
+ * The verdict this result carries, whatever envelope it arrived in.
41
+ *
42
+ * `reticle_assert` reports a top-level boolean `pass`. `reticle_act_and_wait` — the tool agents
43
+ * actually reach for, and the one whose description calls it the act->observe->assert loop — reports
44
+ * `verdict.pass` with the summary at `verified`. Reading only `result.pass` meant a FAILED
45
+ * act_and_wait produced no bug at all, and a contradiction found next to a PASSING one was recorded
46
+ * with `falseGreen: false` — deflating the single number this product exists to publish, by the
47
+ * shape of its own envelope.
48
+ */
49
+ function verdictOf(result) {
50
+ if ('boolean' === typeof result['pass'])
51
+ return result['pass'];
52
+ const verdict = result['verdict'];
53
+ if ('object' === typeof verdict && verdict !== null) {
54
+ const nested = verdict['pass'];
55
+ if ('boolean' === typeof nested)
56
+ return nested;
57
+ }
58
+ // `verified` is the field the agent is told to gate on; "unknown" is deliberately not a verdict.
59
+ const verified = result['verified'];
60
+ if ('yes' === verified)
61
+ return true;
62
+ if ('no' === verified)
63
+ return false;
64
+ return undefined;
65
+ }
66
+ /** The assertion label, from either envelope — act_and_wait nests it under `verdict`. */
67
+ function assertionOf(result) {
68
+ if (result['assertion'] !== undefined)
69
+ return result['assertion'];
70
+ const verdict = result['verdict'];
71
+ return 'object' === typeof verdict && verdict !== null
72
+ ? verdict['assertion']
73
+ : undefined;
74
+ }
75
+ /**
76
+ * A verdict that could not be reached is not a defect found. `inconclusive` is set when the
77
+ * assertion was never evaluated — an under-specified call, or nothing instrumented to read — so
78
+ * counting it would put the agent's own mistakes into the number we publish.
79
+ */
80
+ function isInconclusive(result) {
81
+ const verdict = result['verdict'];
82
+ return ('object' === typeof verdict &&
83
+ verdict !== null &&
84
+ 'string' === typeof verdict['inconclusive']);
85
+ }
86
+ /** A suite row whose replay never STARTED — see SuiteFlowResult.couldNotRun. */
87
+ function couldNotRun(failure) {
88
+ return ('object' === typeof failure &&
89
+ failure !== null &&
90
+ true === failure.couldNotRun);
91
+ }
92
+ /**
93
+ * `bug.attribution` was REMOVED, deliberately, and this note is here so it is not re-added casually.
94
+ *
95
+ * It shipped twice and was wrong both times. First it stamped `app` on every contradiction, including
96
+ * `request-never-settled` — which Reticle raises from the ABSENCE of a settle event, so a dev-overlay
97
+ * poll manufactured it with the app doing nothing wrong. That was fixed, and a real drive then found
98
+ * it still inverted: across two full runs, EVERY `attribution: 'app'` was a misattribution, while the
99
+ * one defect that genuinely was a bad agent predicate carried none. One session would have published
100
+ * "2 defects in the app" against a true count of 0.
101
+ *
102
+ * A metric that is confidently wrong about WHOSE fault a defect is, is worse than no metric: it is
103
+ * the number a founder steers on, and it points at the customer. Counting nothing is recoverable;
104
+ * publishing a false accusation about somebody's product is not.
105
+ *
106
+ * Bringing it back needs evidence that actually separates the cases. It does not exist in the payload
107
+ * today: `element.present` covers "the button is missing", "the API is down" and "the agent mistyped
108
+ * a testid" identically, and `signal.absent` covers both a signal the app never fired and a ref
109
+ * Reticle drove into nothing. Until a verdict carries the reason it came out that way, the honest
110
+ * output is no owner at all.
111
+ */
38
112
  export function bugsInResult(toolName, result) {
113
+ // `reticle_run` is a WRAPPER: its handler calls runTool on the real tool, which already reported
114
+ // that result's defects under the real tool's name — then the outer chokepoint reports the very
115
+ // same object again as `reticle_run`. One sweep of 34 events was a perfect mirror image:
116
+ // flow-regression x8/reticle_flow_verify AND x8/reticle_run; assertion-failed x8/reticle_verify_change
117
+ // AND x8/reticle_run. Sixteen of the thirty-four were echoes, and a headline number that doubles
118
+ // because of HOW a tool was reached is not a measurement.
119
+ //
120
+ // Nothing is lost by staying silent here: when the inner tool refuses or throws, the wrapper
121
+ // returns { error, tool, params, hint }, which carries no verdict and no failures to classify.
122
+ if (toolName === ReticleTool.RUN)
123
+ return [];
39
124
  const bugs = [];
40
- const passed = result['pass'] === true;
125
+ const verdict = verdictOf(result);
126
+ const passed = true === verdict;
41
127
  // 1. Contradictions — channels disagreeing. Invisible to a human watching the screen.
42
128
  for (const kind of kindsOf(result['contradictions'])) {
43
- bugs.push({ source: BugSource.CONTRADICTION, kind, falseGreen: passed, tool: toolName });
129
+ // A contradiction is two of the APP's own channels disagreeing — never the agent's phrasing.
130
+ // Omitted for the one kind Reticle can produce on its own: see contradictionAttribution.
131
+ bugs.push({
132
+ source: BugSource.CONTRADICTION,
133
+ kind,
134
+ falseGreen: passed,
135
+ tool: toolName,
136
+ });
44
137
  }
45
138
  // 2. Crawl anomalies — found autonomously, with nobody writing a test. Crawl reports its own
46
139
  // contradictions inside `anomalies` too, so classify by kind rather than by which array it sat in.
47
140
  for (const kind of kindsOf(result['anomalies'])) {
141
+ // Crawl reports contradictions in this array too, so the same kind must get the same owner
142
+ // wherever it arrives — a `request-never-settled` found by the crawler is no better evidence
143
+ // about whose fault it was than one found by an assertion.
48
144
  bugs.push({
49
145
  source: BugSource.CRAWL,
50
146
  kind,
@@ -52,28 +148,44 @@ export function bugsInResult(toolName, result) {
52
148
  // — even though no assertion was made. See the definition on `falseGreen`.
53
149
  falseGreen: CONTRADICTION_KINDS.has(kind),
54
150
  tool: toolName,
151
+ // Nobody wrote a predicate here, so there is no bad request to blame: a crawl anomaly is a
152
+ // fault the app produced on its own.
55
153
  });
56
154
  }
57
155
  // 3. A failed assertion — the agent declared a consequence and it did not hold.
58
156
  // Only when nothing above already explained it, or one defect would be counted twice.
59
- if (result['pass'] === false && bugs.length === 0) {
60
- const reason = result['assertion'];
157
+ // A red the APP earned, not one Reticle produced. `verified: "unknown"` is the honesty layer
158
+ // saying nothing was proved either way, and a top-level `error` is a refusal — measured over one
159
+ // sweep, every bug_found emitted was one of these two: "no session connected" from reticle_run and
160
+ // an unverifiable suite from reticle_verify_change, counted as defects in the user's app.
161
+ const reticleFailure = 'unknown' === result['verified'] || 'string' === typeof result['error'];
162
+ if (false === verdict && 0 === bugs.length && !isInconclusive(result) && !reticleFailure) {
163
+ const reason = assertionOf(result);
164
+ const kind = 'string' === typeof reason && reason.length > 0 ? reason.slice(0, 64) : 'assertion-failed';
61
165
  bugs.push({
62
166
  source: BugSource.ASSERTION,
63
- kind: typeof reason === 'string' && reason.length > 0 ? reason.slice(0, 64) : 'assertion-failed',
167
+ kind,
64
168
  falseGreen: false,
65
169
  tool: toolName,
170
+ // Omitted when the label cannot say whose fault it was — see ASSERTION_ATTRIBUTION.
66
171
  });
67
172
  }
68
173
  // 4. Replay failures — a saved flow that used to pass no longer does. That is a regression caught,
69
174
  // which is a different and stronger claim than "a check failed".
70
- if (result['status'] === 'fail' && Array.isArray(result['failures'])) {
71
- for (const _failure of result['failures'].slice(0, MAX_BUGS_PER_CALL)) {
175
+ if ('fail' === result['status'] && Array.isArray(result['failures'])) {
176
+ for (const failure of result['failures'].slice(0, MAX_BUGS_PER_CALL)) {
177
+ // A suite that failed because nothing could RUN is not a regression in the user's app. This
178
+ // rule never looked past `status`, so a project whose flow files did not resolve reported one
179
+ // "defect found" per flow — Reticle's own failure, published as the customer's.
180
+ if (couldNotRun(failure))
181
+ continue;
72
182
  bugs.push({
73
183
  source: BugSource.REPLAY,
74
184
  kind: 'flow-regression',
75
185
  falseGreen: false,
76
186
  tool: toolName,
187
+ // A saved flow that used to pass and no longer does is a regression in the app. Rows that
188
+ // never RAN are skipped above precisely so Reticle's own failures do not land here.
77
189
  });
78
190
  }
79
191
  }
@@ -1,13 +1,3 @@
1
- /**
2
- * Report that a person ran a `reticle` subcommand.
3
- *
4
- * `_daemon` is EXCLUDED, and that exclusion is the whole point of this function. `reticle mcp` and
5
- * `reticle serve` start the daemon by re-running this very binary, so the child re-entered the CLI
6
- * entry point and emitted a second event for what a person experienced as ONE action. The old
7
- * `invoke` metric was therefore inflated ~2x — and inflated worst on the agent-driven sessions that
8
- * matter most, while one-shot commands like `version` counted once. That skewed the RATIO between
9
- * commands, not merely the scale, which is the kind of error you cannot correct for after the fact.
10
- * The daemon\'s own lifecycle is already reported by `daemon_started` / `daemon_stopped`; a spawned
11
- * daemon is not a CLI run.
12
- */
1
+ /** True when this command represents a person typing something. Exported for the gate. */
2
+ export declare function isHumanCliCommand(command: string): boolean;
13
3
  export declare function reportCliRun(argv: readonly string[]): void;
@@ -4,8 +4,8 @@
4
4
  * Lives in its own module so `cli.ts` stays a dispatcher, and so the `_daemon` exclusion below sits
5
5
  * next to the explanation of why it exists rather than buried in a startup function.
6
6
  */
7
- import { TelemetryActor, TelemetryEventKind } from '@reticlehq/core';
8
- import { DAEMON_INNER_COMMAND, knownCommand } from '../cli-parse.js';
7
+ import { TelemetryEventKind } from '@reticlehq/core';
8
+ import { DAEMON_INNER_COMMAND, knownCommand } from '../cli/cli-parse.js';
9
9
  import { getTelemetry } from './telemetry.js';
10
10
  import { describeCliFlags } from './argument-shape.js';
11
11
  /**
@@ -20,22 +20,45 @@ import { describeCliFlags } from './argument-shape.js';
20
20
  * The daemon\'s own lifecycle is already reported by `daemon_started` / `daemon_stopped`; a spawned
21
21
  * daemon is not a CLI run.
22
22
  */
23
+ /**
24
+ * The agent's MCP transport, not a person. `reticle mcp` is what an MCP client runs to open a stdio
25
+ * connection — nobody types it. Measured over one real day it was 475 of 561 `cli_command_run`
26
+ * events (85%), on an event whose whole purpose is human intent, and the agent attaching is already
27
+ * reported by `mcp_client_connected` with strictly more detail (reconnect, daemon age, client).
28
+ *
29
+ * `serve` is NOT excluded: nothing spawns it on a person's behalf, so typing it is a real act.
30
+ */
31
+ const AGENT_TRANSPORT_COMMAND = 'mcp';
32
+ /** True when this command represents a person typing something. Exported for the gate. */
33
+ export function isHumanCliCommand(command) {
34
+ return command !== AGENT_TRANSPORT_COMMAND && command !== DAEMON_INNER_COMMAND;
35
+ }
23
36
  export function reportCliRun(argv) {
24
37
  const firstArg = argv[0];
25
38
  if (firstArg === DAEMON_INNER_COMMAND)
26
39
  return;
40
+ const command = knownCommand(firstArg);
27
41
  const telemetry = getTelemetry();
42
+ // FIRST — before the human-command filter. The install happened whichever command ran, and on most
43
+ // machines the very first contact is the agent spawning `reticle mcp`, which that filter excludes.
44
+ // Measured over one sweep: 15 `init_completed` and ZERO `reticle_installed`, in a run that
45
+ // installed the SDK into nine apps — the top of every funnel, missing.
28
46
  // `detach` so a quick command (`version`/`gate`) exits immediately instead of waiting out the POST.
29
47
  if (telemetry.firstRun)
30
48
  void telemetry.emit(TelemetryEventKind.RETICLE_INSTALLED, { detach: true });
49
+ if (!isHumanCliCommand(command))
50
+ return;
31
51
  void telemetry.emit(TelemetryEventKind.CLI_COMMAND_RUN, {
32
52
  detach: true,
33
- actor: TelemetryActor.HUMAN,
53
+ // No `actor`: with the agent's transport excluded, this event is human BY DEFINITION. It was
54
+ // constant `human` on all 561 events in a real day — zero information, and wrong for the 475
55
+ // that were the agent. `actor` stays where it earns its place: verification_completed and
56
+ // bug_found, where it splits the agent's own loop from a human/CI-triggered run.
34
57
  // A fixed, low-cardinality vocabulary WE define, so it is safe to send whole and it is the closest
35
58
  // honest read we have on intent: `verify` and `gate` mean something very different from `status`.
36
59
  // An unrecognized first arg reports as `unknown` rather than being echoed — an echo would put
37
60
  // whatever someone mistyped, including a path or a URL, straight onto the wire.
38
- command: knownCommand(firstArg),
61
+ command,
39
62
  // Which flags were PRESENT, by name only — never their values. `--http-token` alone makes that
40
63
  // rule absolute rather than case-by-case; `--drive` and `--storage-state` carry a URL and a path.
41
64
  flags: describeCliFlags(argv),
@@ -1,3 +1,22 @@
1
+ /**
2
+ * How often a long-lived daemon flushes its counters.
3
+ *
4
+ * ponytail: coarse on purpose. A daemon that is SIGKILLed — OOM, a closed laptop, `kill -9` — never
5
+ * reaches its shutdown handler, so without this the whole session's counters die with it. Thirty
6
+ * minutes bounds that loss to one window while keeping the event volume near zero for the common case
7
+ * (most daemons stop cleanly and send exactly one summary). If the lost windows ever show up as a
8
+ * real gap, the upgrade is a small on-disk journal replayed at the next start, not a shorter timer.
9
+ */
10
+ /**
11
+ * How often a running daemon rolls up its window.
12
+ *
13
+ * Exported because it is the BOUND ON WHAT IS LOST: a daemon that has served a tool never
14
+ * idle-exits, so nothing calls shutdown, so its last partial window dies with the process. At 30
15
+ * minutes against a median 28-minute session that meant the median session reported nothing at all.
16
+ * Only non-empty windows emit, so a shorter interval costs nothing on the 74% of daemons that never
17
+ * serve a tool.
18
+ */
19
+ export declare const SESSION_FLUSH_MS: number;
1
20
  /** Stops the timers this installed. Called from the daemon's shutdown path. */
2
21
  export interface DaemonTelemetry {
3
22
  /**
@@ -23,7 +23,16 @@ const PROJECT_PROFILE_DELAY_MS = 5_000;
23
23
  * (most daemons stop cleanly and send exactly one summary). If the lost windows ever show up as a
24
24
  * real gap, the upgrade is a small on-disk journal replayed at the next start, not a shorter timer.
25
25
  */
26
- const SESSION_FLUSH_MS = 30 * 60 * 1000;
26
+ /**
27
+ * How often a running daemon rolls up its window.
28
+ *
29
+ * Exported because it is the BOUND ON WHAT IS LOST: a daemon that has served a tool never
30
+ * idle-exits, so nothing calls shutdown, so its last partial window dies with the process. At 30
31
+ * minutes against a median 28-minute session that meant the median session reported nothing at all.
32
+ * Only non-empty windows emit, so a shorter interval costs nothing on the 74% of daemons that never
33
+ * serve a tool.
34
+ */
35
+ export const SESSION_FLUSH_MS = 5 * 60 * 1000;
27
36
  /**
28
37
  * Report the daemon as started, profile the project once, and keep the session counters flushing.
29
38
  * Every timer is `unref`'d so telemetry can never be the reason a daemon refuses to exit.
@@ -51,7 +60,8 @@ export function installDaemonTelemetry(cwd, now = () => Date.now()) {
51
60
  const flush = setInterval(() => {
52
61
  if (metrics.empty)
53
62
  return; // an idle window is not worth an event
54
- void getTelemetry().emit(TelemetryEventKind.DAEMON_STOPPED, {
63
+ // NOT daemon_stopped: the daemon is still running. See SESSION_PROGRESS.
64
+ void getTelemetry().emit(TelemetryEventKind.SESSION_PROGRESS, {
55
65
  session: metrics.summarize(false),
56
66
  });
57
67
  metrics.reset();
@@ -21,7 +21,7 @@ export function isReticleSourceCheckout(cwd) {
21
21
  if (existsSync(pkg)) {
22
22
  try {
23
23
  const parsed = JSON.parse(readFileSync(pkg, 'utf8'));
24
- const name = typeof parsed === 'object' && parsed !== null
24
+ const name = 'object' === typeof parsed && parsed !== null
25
25
  ? parsed.name
26
26
  : undefined;
27
27
  if (name === MONOREPO_NAME)
@@ -42,7 +42,7 @@ export function isReticleSourceCheckout(cwd) {
42
42
  /** Strip one layer of matching quotes from a .env value. */
43
43
  function unquote(value) {
44
44
  const first = value[0];
45
- if ((first === '"' || first === "'") && value.endsWith(first) && value.length >= 2) {
45
+ if (('"' === first || "'" === first) && value.endsWith(first) && value.length >= 2) {
46
46
  return value.slice(1, -1);
47
47
  }
48
48
  return value;
@@ -67,13 +67,13 @@ export function loadDotEnv(dir, env = process.env) {
67
67
  }
68
68
  for (const raw of text.split('\n')) {
69
69
  const line = raw.trim();
70
- if (line.length === 0 || line.startsWith('#'))
70
+ if (0 === line.length || line.startsWith('#'))
71
71
  continue;
72
72
  const eq = line.indexOf('=');
73
73
  if (eq <= 0)
74
74
  continue; // not a KEY=value pair — skip rather than fail the whole file
75
75
  const key = line.slice(0, eq).trim();
76
- if (key.length === 0 || env[key] !== undefined)
76
+ if (0 === key.length || env[key] !== undefined)
77
77
  continue;
78
78
  env[key] = unquote(line.slice(eq + 1).trim());
79
79
  }
@@ -19,6 +19,25 @@ import { createHash } from 'node:crypto';
19
19
  * quoted value often contains the very numbers and paths the later rules would only partly catch.
20
20
  */
21
21
  const VARIABLE_PARTS = [
22
+ // ── Identifiers and credentials FIRST ────────────────────────────────────────────────────────
23
+ //
24
+ // The rules below this block were written to make messages GROUPABLE — blank the variable parts so
25
+ // the same defect hashes the same everywhere. Redaction was a side effect of that, and a side
26
+ // effect is not a guarantee. A telemetry audit caught `bob@acme.com` arriving verbatim in
27
+ // `crash_message`; probing further, API-key-shaped tokens survived intact and a JWT was only
28
+ // half-masked. This function also feeds `session.errors[]` on EVERY session summary, so the
29
+ // exposure was every session that logged an error naming a user, not a rare crash path.
30
+ //
31
+ // Order matters: these run before the generic patterns, because `\d+` and the hex rule would chew
32
+ // a token into pieces that no longer match a credential shape while still leaking most of it —
33
+ // which is exactly what happened to the JWT.
34
+ /[\w.+-]+@[\w-]+\.[\w.-]+/g, // email addresses
35
+ /\beyJ[\w-]*\.[\w-]*\.?[\w-]*/g, // JWTs (header segment is always base64 `eyJ`)
36
+ /\b(?:sk|pk|rk|ghp|gho|ghs|ghu|ghr|github_pat|xox[abposr]|AKIA|ASIA|glpat)[_-][A-Za-z0-9_-]{8,}/gi,
37
+ // Anything else long enough and dense enough to be a secret rather than a word. The catch-all for
38
+ // formats nobody here has seen: on a privacy boundary, unrecognised is not the same as safe.
39
+ /\b[A-Za-z0-9_-]{24,}\b/g,
40
+ // ── Then the grouping rules ──────────────────────────────────────────────────────────────────
22
41
  /"[^"]*"|'[^']*'|`[^`]*`/g, // quoted values — names, selectors, snippets
23
42
  /\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, // URLs
24
43
  /(?:\/[\w.-]+){2,}\/?/g, // POSIX-ish paths
@@ -53,7 +72,7 @@ export function reticleFrames(stack) {
53
72
  if (!/@reticlehq[/\\]|[/\\]reticle[/\\]dist[/\\]/.test(line))
54
73
  continue;
55
74
  const location = line.match(/([\w.-]+\.(?:js|mjs|cjs|ts)):(\d+)/);
56
- if (location === null)
75
+ if (null === location)
57
76
  continue;
58
77
  // `at Object.runTool (/path/to/invoke-tool.js:88:3)` → `runTool`. The function name is half the
59
78
  // value of a frame in an RCA — "it died in act-tools.js" narrows to a file, "it died in
@@ -62,7 +81,7 @@ export function reticleFrames(stack) {
62
81
  const where = `${location[1] ?? ''}:${location[2] ?? ''}`;
63
82
  // `Object.` / `Module.` prefixes are V8 noise that make the same function look like two.
64
83
  const name = fn?.replace(/^(?:Object|Module|Function)\./, '');
65
- frames.push(name === undefined || name === '' ? where : `${name}@${where}`);
84
+ frames.push(name === undefined || '' === name ? where : `${name}@${where}`);
66
85
  }
67
86
  return frames;
68
87
  }
@@ -1,4 +1,4 @@
1
- import { TelemetryAction } from '../cli-parse.js';
1
+ import { TelemetryAction } from '../cli/cli-parse.js';
2
2
  /**
3
3
  * `reticle feedback [--rating 1-5] [--bug] "what happened"` — the human half of the feedback channel.
4
4
  *
@@ -1,8 +1,8 @@
1
1
  import { FeedbackKind, FeedbackSource } from '@reticlehq/core';
2
- import { BUG_FLAG, RATING_FLAG } from '../cli-parse.js';
2
+ import { BUG_FLAG, RATING_FLAG } from '../cli/cli-parse.js';
3
3
  import { describeFeedbackPayload, submitFeedback } from './feedback.js';
4
4
  import { describeTelemetry, setTelemetryEnabled } from './telemetry.js';
5
- import { TelemetryAction } from '../cli-parse.js';
5
+ import { TelemetryAction } from '../cli/cli-parse.js';
6
6
  import { clearIdentity, IDENTIFY_NOTICE, readIdentity, saveIdentity, submitIdentity, UsageContextKind, } from './identify.js';
7
7
  /**
8
8
  * `reticle feedback [--rating 1-5] [--bug] "what happened"` — the human half of the feedback channel.
@@ -18,7 +18,7 @@ export async function handleFeedback(text, rating, bug) {
18
18
  const line = (s) => {
19
19
  process.stdout.write(`${s}\n`);
20
20
  };
21
- if (text === '' && rating === undefined) {
21
+ if ('' === text && rating === undefined) {
22
22
  line(`usage: reticle feedback [${RATING_FLAG} 1-5] [${BUG_FLAG}] "what worked, what didn't"`);
23
23
  line(' your words go to the maintainers; nothing from your app is ever included.');
24
24
  process.exitCode = 1;
@@ -28,7 +28,7 @@ export async function handleFeedback(text, rating, bug) {
28
28
  source: FeedbackSource.HUMAN,
29
29
  kind: bug ? FeedbackKind.BUG : FeedbackKind.EXPERIENCE,
30
30
  // A rating with no words is a real report, but the schema wants text — say what the rating means.
31
- text: text === '' ? `rated ${String(rating)}/5 with no comment` : text,
31
+ text: '' === text ? `rated ${String(rating)}/5 with no comment` : text,
32
32
  ...(rating !== undefined ? { rating } : {}),
33
33
  });
34
34
  line(`sending ${JSON.stringify(describeFeedbackPayload(receipt.context))}`);
@@ -27,6 +27,28 @@ export interface SessionFacts {
27
27
  engine?: string;
28
28
  /** True when Reticle drives the page over CDP rather than observing the human's own browser. */
29
29
  driven?: boolean;
30
+ /** What the APP said it is running, from the SDK's hello. The only session-scoped stack evidence. */
31
+ adapters?: string[];
30
32
  }
33
+ /**
34
+ * Reconcile what the DIRECTORY says with what the APP says.
35
+ *
36
+ * `detectStack` reads the daemon's cwd, which is not the session's project whenever one daemon
37
+ * serves several apps — the normal case for anyone with two dev servers, and the default in the
38
+ * fixtures. Reported from the field: feedback filed from an astro session arrived stamped
39
+ * `"stack":"sveltekit"`, because that was the directory the daemon happened to be started in. The
40
+ * sessionId was passed and ignored.
41
+ *
42
+ * The rule: keep the directory's answer only while the app agrees with it. An adapter that implies a
43
+ * different family means the directory is describing somebody else's project, so the app's own
44
+ * report wins — less specific, and true, which is the right trade for an attribution field.
45
+ */
46
+ export declare function reconcileStack(fromCwd: {
47
+ stack?: string;
48
+ stackMajor?: number;
49
+ }, adapters: readonly string[] | undefined): {
50
+ stack?: string;
51
+ stackMajor?: number;
52
+ };
31
53
  /** Assemble the full context. Every field independently optional — a partial context still ships. */
32
54
  export declare function feedbackContext(cwd: string, session?: SessionFacts): FeedbackContext;
@@ -116,6 +116,43 @@ function detectClient() {
116
116
  return {};
117
117
  }
118
118
  }
119
+ /**
120
+ * Which cwd-derived stacks each SDK adapter is consistent with.
121
+ *
122
+ * The adapter names a UI library; the directory may name a meta-framework built on it. `react` and
123
+ * `next` are the same app described at two levels, so the more specific one is kept. `react` and
124
+ * `sveltekit` are not — that pairing means the daemon's directory is not this session's project.
125
+ */
126
+ const ADAPTER_IMPLIES = {
127
+ react: ['react', 'next', 'remix'],
128
+ svelte: ['svelte', 'sveltekit'],
129
+ vue: ['vue', 'nuxt'],
130
+ solid: ['solid'],
131
+ angular: ['angular'],
132
+ };
133
+ /**
134
+ * Reconcile what the DIRECTORY says with what the APP says.
135
+ *
136
+ * `detectStack` reads the daemon's cwd, which is not the session's project whenever one daemon
137
+ * serves several apps — the normal case for anyone with two dev servers, and the default in the
138
+ * fixtures. Reported from the field: feedback filed from an astro session arrived stamped
139
+ * `"stack":"sveltekit"`, because that was the directory the daemon happened to be started in. The
140
+ * sessionId was passed and ignored.
141
+ *
142
+ * The rule: keep the directory's answer only while the app agrees with it. An adapter that implies a
143
+ * different family means the directory is describing somebody else's project, so the app's own
144
+ * report wins — less specific, and true, which is the right trade for an attribution field.
145
+ */
146
+ export function reconcileStack(fromCwd, adapters) {
147
+ const claimed = adapters?.find((adapter) => adapter in ADAPTER_IMPLIES);
148
+ if (claimed === undefined)
149
+ return fromCwd;
150
+ const consistent = ADAPTER_IMPLIES[claimed] ?? [];
151
+ if (fromCwd.stack !== undefined && consistent.includes(fromCwd.stack))
152
+ return fromCwd;
153
+ // The major belonged to the directory's framework, so it cannot travel with a different stack.
154
+ return { stack: claimed };
155
+ }
119
156
  function asRuntime(value) {
120
157
  return Object.values(AppRuntime).find((known) => known === value);
121
158
  }
@@ -131,7 +168,10 @@ export function feedbackContext(cwd, session) {
131
168
  const engine = asEngine(session?.engine);
132
169
  const client = detectClient();
133
170
  return {
134
- ...detectStack(cwd),
171
+ // Session-scoped when the app reports something that contradicts the directory — see
172
+ // reconcileStack. A daemon serving two apps otherwise stamps both with whichever one it was
173
+ // started next to.
174
+ ...reconcileStack(detectStack(cwd), session?.adapters),
135
175
  ...client,
136
176
  ...(runtime !== undefined ? { runtime } : {}),
137
177
  ...(engine !== undefined ? { engine } : {}),
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Telling the reporter that a BACKGROUND feedback delivery failed.
3
+ *
4
+ * The agent's `reticle_feedback` no longer waits for the POST — a ~340ms network round-trip on the
5
+ * one call an agent makes mid-task is the product blocking the user's work to talk about itself. But
6
+ * not waiting reopens the exact hole the awaited version was written to close: `sent` used to be
7
+ * unconditional, so a DNS miss and a 4xx both reported "filed", and this is the ONLY qualitative
8
+ * channel the product has. A lost report that was announced as filed is worse than no channel.
9
+ *
10
+ * So the wait goes and the honesty stays: the receipt says ACCEPTED (validated, redacted, queued),
11
+ * never "delivered", and if the send then fails the agent is told on its next tool result through
12
+ * the same one-shot envelope the update and skew nudges use. Told once — a reporter who cannot fix
13
+ * the network does not need it repeated, and a banner on every call is one agents learn to skip.
14
+ */
15
+ /** Record that a queued report never made it. */
16
+ export declare function noteFeedbackUndelivered(reason: string): void;
17
+ /** The undelivered-feedback notice, once. */
18
+ export declare function takeFeedbackUndelivered(): string | undefined;
19
+ /** Tests only — drop the module state so each case starts clean. */
20
+ export declare function resetFeedbackDelivery(): void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Telling the reporter that a BACKGROUND feedback delivery failed.
3
+ *
4
+ * The agent's `reticle_feedback` no longer waits for the POST — a ~340ms network round-trip on the
5
+ * one call an agent makes mid-task is the product blocking the user's work to talk about itself. But
6
+ * not waiting reopens the exact hole the awaited version was written to close: `sent` used to be
7
+ * unconditional, so a DNS miss and a 4xx both reported "filed", and this is the ONLY qualitative
8
+ * channel the product has. A lost report that was announced as filed is worse than no channel.
9
+ *
10
+ * So the wait goes and the honesty stays: the receipt says ACCEPTED (validated, redacted, queued),
11
+ * never "delivered", and if the send then fails the agent is told on its next tool result through
12
+ * the same one-shot envelope the update and skew nudges use. Told once — a reporter who cannot fix
13
+ * the network does not need it repeated, and a banner on every call is one agents learn to skip.
14
+ */
15
+ /** Set when a background feedback send failed and the reporter has not been told yet. */
16
+ let pending;
17
+ /** Record that a queued report never made it. */
18
+ export function noteFeedbackUndelivered(reason) {
19
+ pending = reason;
20
+ }
21
+ /** The undelivered-feedback notice, once. */
22
+ export function takeFeedbackUndelivered() {
23
+ const notice = pending;
24
+ pending = undefined;
25
+ return notice;
26
+ }
27
+ /** Tests only — drop the module state so each case starts clean. */
28
+ export function resetFeedbackDelivery() {
29
+ pending = undefined;
30
+ }
@@ -0,0 +1,22 @@
1
+ /** One queued report: the payload, plus what is needed to find and remove it again. */
2
+ export interface OutboxEntry {
3
+ id: string;
4
+ t: string;
5
+ payload: unknown;
6
+ }
7
+ export declare function outboxPath(home?: string): string;
8
+ /**
9
+ * Persist a report and return its id. Never throws: failing to queue must not also fail the send
10
+ * that was about to happen anyway.
11
+ */
12
+ export declare function queueFeedback(payload: unknown, now?: () => Date, home?: string): string | null;
13
+ /** Read every queued report. A malformed line is skipped, never fatal — the rest still matter. */
14
+ export declare function readOutbox(home?: string): OutboxEntry[];
15
+ /**
16
+ * Drop a report that was delivered. Rewrites the file without it.
17
+ *
18
+ * A rewrite rather than a tombstone because the file's whole purpose is "what still needs sending" —
19
+ * a reader should be able to `cat` it and see exactly the backlog, with no filtering rule to learn.
20
+ * Volume is a handful of lines a week, so the cost is irrelevant.
21
+ */
22
+ export declare function markDelivered(id: string | null, home?: string): void;