@reticlehq/server 2.4.0 → 2.6.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 (472) hide show
  1. package/SKILL.md +719 -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} +13 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +95 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
  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/cli-doctor.d.ts +1 -0
  20. package/dist/cli/cli-doctor.js +135 -0
  21. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  22. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  23. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  24. package/dist/cli/cli-launch.js +171 -0
  25. package/dist/cli/cli-parse-feedback.d.ts +31 -0
  26. package/dist/cli/cli-parse-feedback.js +56 -0
  27. package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
  28. package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
  29. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
  30. package/dist/{cli-port.js → cli/cli-port.js} +9 -9
  31. package/dist/cli/cli-update-commands.d.ts +4 -0
  32. package/dist/cli/cli-update-commands.js +93 -0
  33. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  34. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  35. package/dist/cli/doctor-daemon-line.d.ts +37 -0
  36. package/dist/cli/doctor-daemon-line.js +35 -0
  37. package/dist/cli/port-holder.d.ts +43 -0
  38. package/dist/cli/port-holder.js +99 -0
  39. package/dist/cli.d.ts +2 -2
  40. package/dist/cli.js +302 -100
  41. package/dist/cloud/cloud-config.d.ts +0 -1
  42. package/dist/cloud/cloud-config.js +9 -9
  43. package/dist/cloud/cloud-sync.d.ts +1 -7
  44. package/dist/cloud/cloud-sync.js +12 -12
  45. package/dist/crawl/crawl-empty.d.ts +16 -0
  46. package/dist/crawl/crawl-empty.js +24 -0
  47. package/dist/crawl/crawl-tools.js +4 -1
  48. package/dist/crawl/crawl.js +26 -7
  49. package/dist/daemon/daemon-resilience.d.ts +73 -0
  50. package/dist/daemon/daemon-resilience.js +279 -0
  51. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  52. package/dist/daemon/daemon-usefulness.js +62 -0
  53. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  54. package/dist/{daemon.js → daemon/daemon.js} +105 -48
  55. package/dist/daemon/heartbeat.d.ts +115 -0
  56. package/dist/daemon/heartbeat.js +141 -0
  57. package/dist/daemon/idle-grace.d.ts +32 -0
  58. package/dist/daemon/idle-grace.js +41 -0
  59. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
  60. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
  61. package/dist/daemon/port-presence.d.ts +76 -0
  62. package/dist/daemon/port-presence.js +93 -0
  63. package/dist/daemon/state-dir.d.ts +7 -0
  64. package/dist/daemon/state-dir.js +59 -0
  65. package/dist/daemon/unclean-predecessor.d.ts +23 -0
  66. package/dist/daemon/unclean-predecessor.js +27 -0
  67. package/dist/domain/domain-model.js +2 -2
  68. package/dist/events/body-failures.js +8 -8
  69. package/dist/events/contradictions.d.ts +1 -1
  70. package/dist/events/contradictions.js +60 -10
  71. package/dist/events/echo-mismatch.js +10 -10
  72. package/dist/events/event-filters.js +6 -6
  73. package/dist/events/json-salvage.d.ts +2 -1
  74. package/dist/events/json-salvage.js +6 -6
  75. package/dist/events/observed-in-window.d.ts +12 -0
  76. package/dist/events/observed-in-window.js +21 -0
  77. package/dist/events/predicate-eval.d.ts +53 -20
  78. package/dist/events/predicate-eval.js +179 -55
  79. package/dist/events/predicate-parse.d.ts +23 -0
  80. package/dist/events/predicate-parse.js +54 -0
  81. package/dist/events/predicate.d.ts +7 -2
  82. package/dist/events/predicate.js +126 -35
  83. package/dist/events/reconcile.js +5 -5
  84. package/dist/events/ring-buffer.js +2 -17
  85. package/dist/events/stale-response.js +6 -6
  86. package/dist/events/testid-near-miss.d.ts +22 -0
  87. package/dist/events/testid-near-miss.js +33 -0
  88. package/dist/events/unit-mismatch.js +8 -8
  89. package/dist/flows/affected-tools.js +19 -4
  90. package/dist/flows/affected.js +1 -1
  91. package/dist/flows/annotate-target.d.ts +1 -0
  92. package/dist/flows/annotate-target.js +20 -0
  93. package/dist/flows/annotate-tools.js +25 -3
  94. package/dist/flows/annotate.js +17 -4
  95. package/dist/flows/assertion-integrity.d.ts +2 -1
  96. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  97. package/dist/flows/assertion-tiers-store.js +2 -2
  98. package/dist/flows/attributed-failure.d.ts +17 -0
  99. package/dist/flows/attributed-failure.js +20 -0
  100. package/dist/flows/buddy-status.d.ts +2 -1
  101. package/dist/flows/buddy-status.js +2 -2
  102. package/dist/flows/coverage.d.ts +3 -2
  103. package/dist/flows/coverage.js +2 -2
  104. package/dist/flows/decision.js +24 -9
  105. package/dist/flows/empty-flow.d.ts +6 -0
  106. package/dist/flows/empty-flow.js +24 -0
  107. package/dist/flows/flake-store.d.ts +0 -1
  108. package/dist/flows/flake-store.js +1 -1
  109. package/dist/flows/flake.d.ts +2 -1
  110. package/dist/flows/flake.js +2 -2
  111. package/dist/flows/flow-classify.js +12 -0
  112. package/dist/flows/flow-replay-run.js +4 -4
  113. package/dist/flows/flow-replay.d.ts +6 -8
  114. package/dist/flows/flow-replay.js +132 -48
  115. package/dist/flows/flow-report.js +1 -1
  116. package/dist/flows/flow-scope.d.ts +1 -6
  117. package/dist/flows/flow-step-runners.d.ts +26 -6
  118. package/dist/flows/flow-step-runners.js +156 -6
  119. package/dist/flows/flow-success.js +20 -15
  120. package/dist/flows/flow-tools.js +63 -21
  121. package/dist/flows/flows.js +15 -11
  122. package/dist/flows/gate.d.ts +4 -3
  123. package/dist/flows/gate.js +1 -1
  124. package/dist/flows/heal-run.d.ts +0 -8
  125. package/dist/flows/heal-run.js +5 -5
  126. package/dist/flows/heal.js +3 -3
  127. package/dist/flows/predicate-to-expect.d.ts +38 -0
  128. package/dist/flows/predicate-to-expect.js +95 -0
  129. package/dist/flows/replay.d.ts +16 -1
  130. package/dist/flows/replay.js +58 -22
  131. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  132. package/dist/flows/role-anchor-nearest.js +45 -0
  133. package/dist/flows/role-drift-reason.d.ts +20 -0
  134. package/dist/flows/role-drift-reason.js +39 -0
  135. package/dist/flows/server-verify.d.ts +1 -1
  136. package/dist/flows/server-verify.js +5 -5
  137. package/dist/flows/suite-flakes.d.ts +33 -0
  138. package/dist/flows/suite-flakes.js +32 -0
  139. package/dist/flows/verify-change-tools.js +32 -10
  140. package/dist/flows/verify-change-verdict.d.ts +15 -0
  141. package/dist/flows/verify-change-verdict.js +25 -0
  142. package/dist/flows/watch-batcher.d.ts +3 -2
  143. package/dist/honesty/already-true.d.ts +2 -0
  144. package/dist/honesty/already-true.js +31 -0
  145. package/dist/honesty/blind-spots.d.ts +35 -3
  146. package/dist/honesty/blind-spots.js +57 -12
  147. package/dist/honesty/honesty.d.ts +3 -2
  148. package/dist/honesty/honesty.js +4 -4
  149. package/dist/honesty/uncaptured-bodies.js +1 -1
  150. package/dist/honesty/unread-outcome.js +2 -2
  151. package/dist/honesty/verified.d.ts +31 -3
  152. package/dist/honesty/verified.js +64 -8
  153. package/dist/http-server.js +23 -7
  154. package/dist/hunt/hunt-report.d.ts +2 -1
  155. package/dist/hunt/hunt-report.js +2 -2
  156. package/dist/index.d.ts +9 -6
  157. package/dist/index.js +41 -25
  158. package/dist/init/agent-rules.d.ts +20 -1
  159. package/dist/init/agent-rules.js +64 -8
  160. package/dist/init/app-choice.d.ts +20 -0
  161. package/dist/init/app-choice.js +25 -0
  162. package/dist/init/astro-host.d.ts +21 -0
  163. package/dist/init/astro-host.js +27 -0
  164. package/dist/init/astro-patch.d.ts +16 -0
  165. package/dist/init/astro-patch.js +240 -0
  166. package/dist/init/capabilities.js +4 -1
  167. package/dist/init/cra.d.ts +50 -0
  168. package/dist/init/cra.js +106 -0
  169. package/dist/init/cursor.js +39 -4
  170. package/dist/init/desktop-doctor.d.ts +3 -2
  171. package/dist/init/desktop-doctor.js +4 -4
  172. package/dist/init/detect.d.ts +9 -2
  173. package/dist/init/detect.js +24 -5
  174. package/dist/init/mcp-clients.d.ts +81 -0
  175. package/dist/init/mcp-clients.js +248 -0
  176. package/dist/init/mcp-registered.d.ts +12 -0
  177. package/dist/init/mcp-registered.js +14 -0
  178. package/dist/init/mcp.d.ts +27 -2
  179. package/dist/init/mcp.js +40 -4
  180. package/dist/init/next-patch.d.ts +2 -4
  181. package/dist/init/next-patch.js +109 -19
  182. package/dist/init/node-io.js +29 -6
  183. package/dist/init/plan-framework.d.ts +16 -1
  184. package/dist/init/plan-framework.js +166 -8
  185. package/dist/init/plan.d.ts +85 -6
  186. package/dist/init/plan.js +196 -33
  187. package/dist/init/project-id.d.ts +5 -9
  188. package/dist/init/project-id.js +10 -22
  189. package/dist/init/run.d.ts +23 -1
  190. package/dist/init/run.js +338 -47
  191. package/dist/init/slash-command.d.ts +9 -0
  192. package/dist/init/slash-command.js +10 -1
  193. package/dist/init/snippets.d.ts +7 -10
  194. package/dist/init/snippets.js +44 -15
  195. package/dist/init/vite-config.js +1 -1
  196. package/dist/init/windows-quote.d.ts +38 -0
  197. package/dist/init/windows-quote.js +70 -0
  198. package/dist/init/workspace-apps.d.ts +23 -0
  199. package/dist/init/workspace-apps.js +68 -0
  200. package/dist/input/driven-redaction.js +1 -1
  201. package/dist/input/network-detail.js +8 -8
  202. package/dist/input/network-mock-tools.js +2 -2
  203. package/dist/input/network-mock.js +4 -4
  204. package/dist/input/real-input.js +8 -3
  205. package/dist/input/scroll-find.js +2 -2
  206. package/dist/input/scroll-tools.d.ts +0 -5
  207. package/dist/input/scroll-tools.js +11 -4
  208. package/dist/input/viewport-tools.js +2 -2
  209. package/dist/journal/ambient-store.d.ts +0 -2
  210. package/dist/journal/ambient-store.js +1 -1
  211. package/dist/journal/ambient.js +1 -1
  212. package/dist/journal/attach-journal.d.ts +2 -1
  213. package/dist/journal/deviation-report.js +6 -6
  214. package/dist/journal/deviation-service.js +1 -1
  215. package/dist/journal/envelope-store.d.ts +0 -2
  216. package/dist/journal/envelope-store.js +1 -1
  217. package/dist/journal/envelope.d.ts +2 -3
  218. package/dist/journal/envelope.js +3 -3
  219. package/dist/journal/journal-query.js +1 -1
  220. package/dist/journal/journal-recorder.d.ts +2 -1
  221. package/dist/journal/journal-recorder.js +2 -2
  222. package/dist/journal/rollups.js +9 -9
  223. package/dist/journal/session-end.d.ts +2 -1
  224. package/dist/journal/session-journal.js +4 -4
  225. package/dist/license/license.d.ts +0 -14
  226. package/dist/license/license.js +8 -8
  227. package/dist/log.d.ts +10 -0
  228. package/dist/log.js +11 -1
  229. package/dist/mcp/mcp-is-error.d.ts +13 -0
  230. package/dist/mcp/mcp-is-error.js +18 -0
  231. package/dist/mcp/mcp-outage.d.ts +32 -0
  232. package/dist/mcp/mcp-outage.js +55 -0
  233. package/dist/mcp/mcp-proxy.d.ts +134 -0
  234. package/dist/mcp/mcp-proxy.js +720 -0
  235. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  236. package/dist/{mcp.js → mcp/mcp.js} +139 -37
  237. package/dist/mcp/proxy-handshake.d.ts +3 -0
  238. package/dist/mcp/proxy-handshake.js +54 -0
  239. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  240. package/dist/mcp/proxy-lifecycle.js +70 -0
  241. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  242. package/dist/mcp/tool-catalog-cache.js +55 -0
  243. package/dist/oracles/propose-consequences.d.ts +2 -1
  244. package/dist/oracles/propose-consequences.js +16 -11
  245. package/dist/oracles/self-instrument.d.ts +2 -1
  246. package/dist/oracles/self-instrument.js +1 -1
  247. package/dist/phenomena/phenomena.js +3 -3
  248. package/dist/platform.d.ts +16 -0
  249. package/dist/platform.js +16 -0
  250. package/dist/pool/browser-pool.d.ts +9 -0
  251. package/dist/pool/browser-pool.js +38 -9
  252. package/dist/pool/playwright-launcher.d.ts +15 -2
  253. package/dist/pool/playwright-launcher.js +21 -2
  254. package/dist/project/fs-port.js +1 -1
  255. package/dist/project/project-tools.js +2 -2
  256. package/dist/runs/build-verification-run.js +1 -1
  257. package/dist/runs/repair-prompt.d.ts +0 -6
  258. package/dist/runs/repair-prompt.js +3 -3
  259. package/dist/runs/risk-classify.js +1 -1
  260. package/dist/runs/run-diff.d.ts +0 -2
  261. package/dist/runs/run-diff.js +3 -3
  262. package/dist/runs/run-tools.js +2 -2
  263. package/dist/runs/verification-sync.js +2 -2
  264. package/dist/runs/verify-http.js +1 -1
  265. package/dist/runs/verify-server.js +1 -1
  266. package/dist/session/attachment-history.d.ts +46 -0
  267. package/dist/session/attachment-history.js +65 -0
  268. package/dist/session/command-timeout.d.ts +11 -3
  269. package/dist/session/command-timeout.js +2 -2
  270. package/dist/session/control-envelope.d.ts +6 -0
  271. package/dist/session/control-envelope.js +7 -1
  272. package/dist/session/dev-server-probe.d.ts +13 -0
  273. package/dist/session/dev-server-probe.js +52 -0
  274. package/dist/session/last-act.d.ts +17 -3
  275. package/dist/session/last-act.js +16 -4
  276. package/dist/session/live-control-tools.js +7 -2
  277. package/dist/session/live-control.js +1 -1
  278. package/dist/session/looks-like-dev-server.d.ts +14 -0
  279. package/dist/session/looks-like-dev-server.js +25 -0
  280. package/dist/session/no-session-diagnosis.d.ts +45 -0
  281. package/dist/session/no-session-diagnosis.js +120 -0
  282. package/dist/session/no-session-watch.d.ts +21 -0
  283. package/dist/session/no-session-watch.js +69 -0
  284. package/dist/session/observed-state.d.ts +4 -0
  285. package/dist/session/observed-state.js +19 -1
  286. package/dist/session/output-budget.js +1 -1
  287. package/dist/session/pending-commands.js +1 -0
  288. package/dist/session/session-health.d.ts +8 -1
  289. package/dist/session/session-health.js +8 -7
  290. package/dist/session/session-info.d.ts +43 -8
  291. package/dist/session/session-info.js +46 -1
  292. package/dist/session/session-lease.d.ts +0 -11
  293. package/dist/session/session-lease.js +1 -1
  294. package/dist/session/session-manager.d.ts +6 -0
  295. package/dist/session/session-manager.js +124 -12
  296. package/dist/session/session-readiness.d.ts +1 -1
  297. package/dist/session/session-readiness.js +6 -1
  298. package/dist/session/session-reaper.js +1 -1
  299. package/dist/session/session-reconnect.d.ts +32 -0
  300. package/dist/session/session-reconnect.js +42 -0
  301. package/dist/session/session.d.ts +15 -2
  302. package/dist/session/session.js +77 -26
  303. package/dist/session/state-select.d.ts +1 -1
  304. package/dist/session/state-select.js +1 -1
  305. package/dist/status-payload.d.ts +19 -0
  306. package/dist/status-payload.js +11 -0
  307. package/dist/telemetry/argument-shape.js +10 -1
  308. package/dist/telemetry/browser-mode.d.ts +27 -0
  309. package/dist/telemetry/browser-mode.js +33 -0
  310. package/dist/telemetry/bug-found.d.ts +17 -21
  311. package/dist/telemetry/bug-found.js +120 -8
  312. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  313. package/dist/telemetry/cli-telemetry.js +27 -4
  314. package/dist/telemetry/crash-cause.d.ts +47 -0
  315. package/dist/telemetry/crash-cause.js +104 -0
  316. package/dist/telemetry/daemon-telemetry.d.ts +38 -1
  317. package/dist/telemetry/daemon-telemetry.js +37 -12
  318. package/dist/telemetry/dev-repo.js +4 -4
  319. package/dist/telemetry/error-class.d.ts +26 -0
  320. package/dist/telemetry/error-class.js +52 -0
  321. package/dist/telemetry/error-fingerprint.js +21 -2
  322. package/dist/telemetry/feedback-cli.d.ts +8 -3
  323. package/dist/telemetry/feedback-cli.js +52 -8
  324. package/dist/telemetry/feedback-context.d.ts +23 -6
  325. package/dist/telemetry/feedback-context.js +105 -5
  326. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  327. package/dist/telemetry/feedback-delivery.js +30 -0
  328. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  329. package/dist/telemetry/feedback-outbox.js +87 -0
  330. package/dist/telemetry/feedback.d.ts +22 -1
  331. package/dist/telemetry/feedback.js +67 -8
  332. package/dist/telemetry/git-facts.d.ts +7 -0
  333. package/dist/telemetry/git-facts.js +5 -5
  334. package/dist/telemetry/project-profile.js +2 -1
  335. package/dist/telemetry/session-metrics.d.ts +47 -3
  336. package/dist/telemetry/session-metrics.js +315 -15
  337. package/dist/telemetry/telemetry.d.ts +45 -2
  338. package/dist/telemetry/telemetry.js +150 -37
  339. package/dist/telemetry/verification-of.d.ts +22 -0
  340. package/dist/telemetry/verification-of.js +84 -0
  341. package/dist/tools/act-capsule.js +2 -2
  342. package/dist/tools/act-danger.d.ts +12 -0
  343. package/dist/tools/act-danger.js +20 -3
  344. package/dist/tools/act-helpers.js +4 -4
  345. package/dist/tools/act-tools.d.ts +0 -8
  346. package/dist/tools/act-tools.js +114 -118
  347. package/dist/tools/act-view.js +7 -7
  348. package/dist/tools/alias-args.d.ts +17 -0
  349. package/dist/tools/alias-args.js +25 -0
  350. package/dist/tools/assert-grade.js +9 -9
  351. package/dist/tools/assert-verdict.d.ts +9 -1
  352. package/dist/tools/assert-verdict.js +21 -6
  353. package/dist/tools/browser-tools.js +36 -11
  354. package/dist/tools/contract-tools.d.ts +0 -5
  355. package/dist/tools/contract-tools.js +27 -1
  356. package/dist/tools/coverage-identity.d.ts +34 -0
  357. package/dist/tools/coverage-identity.js +46 -0
  358. package/dist/tools/coverage-tools.js +10 -14
  359. package/dist/tools/dynamic-tools.d.ts +2 -1
  360. package/dist/tools/dynamic-tools.js +96 -19
  361. package/dist/tools/error-recovery.d.ts +34 -2
  362. package/dist/tools/error-recovery.js +255 -4
  363. package/dist/tools/feedback-invite.d.ts +48 -0
  364. package/dist/tools/feedback-invite.js +71 -0
  365. package/dist/tools/feedback-tools.d.ts +12 -1
  366. package/dist/tools/feedback-tools.js +28 -5
  367. package/dist/tools/invoke-tool.d.ts +0 -12
  368. package/dist/tools/invoke-tool.js +126 -33
  369. package/dist/tools/lease-hint.d.ts +17 -0
  370. package/dist/tools/lease-hint.js +24 -0
  371. package/dist/tools/lease-tools.js +6 -5
  372. package/dist/tools/merge-tools.d.ts +10 -1
  373. package/dist/tools/merge-tools.js +9 -3
  374. package/dist/tools/merged-name-redirect.d.ts +13 -0
  375. package/dist/tools/merged-name-redirect.js +55 -0
  376. package/dist/tools/navigate-arrival.d.ts +26 -0
  377. package/dist/tools/navigate-arrival.js +60 -0
  378. package/dist/tools/navigate-result.d.ts +31 -0
  379. package/dist/tools/navigate-result.js +38 -0
  380. package/dist/tools/observe-tools.js +72 -20
  381. package/dist/tools/observed-nothing.d.ts +25 -0
  382. package/dist/tools/observed-nothing.js +33 -0
  383. package/dist/tools/query-paginate.js +2 -2
  384. package/dist/tools/query-shape.d.ts +18 -0
  385. package/dist/tools/query-shape.js +39 -0
  386. package/dist/tools/read-tools.js +81 -20
  387. package/dist/tools/real-input-attempt.d.ts +28 -0
  388. package/dist/tools/real-input-attempt.js +93 -0
  389. package/dist/tools/reconcile-tools.js +7 -7
  390. package/dist/tools/reload-result.d.ts +14 -0
  391. package/dist/tools/reload-result.js +29 -0
  392. package/dist/tools/snapshot-delta.d.ts +4 -5
  393. package/dist/tools/snapshot-delta.js +0 -0
  394. package/dist/tools/tool-kit.d.ts +16 -0
  395. package/dist/tools/tool-surface.d.ts +83 -0
  396. package/dist/tools/tool-surface.js +217 -0
  397. package/dist/tools/tools-helpers.js +6 -6
  398. package/dist/tools/tools.d.ts +15 -0
  399. package/dist/tools/tools.js +53 -17
  400. package/dist/tools/unadvertised-help.d.ts +5 -0
  401. package/dist/tools/unadvertised-help.js +39 -0
  402. package/dist/trace.d.ts +69 -0
  403. package/dist/trace.js +150 -0
  404. package/dist/update/nudge-credit.d.ts +4 -0
  405. package/dist/update/nudge-credit.js +53 -0
  406. package/dist/update/sdk-sync.d.ts +21 -0
  407. package/dist/update/sdk-sync.js +42 -0
  408. package/dist/update/update-checker.js +3 -3
  409. package/dist/update/update-nudge.d.ts +63 -6
  410. package/dist/update/update-nudge.js +128 -15
  411. package/dist/update/updater.d.ts +1 -9
  412. package/dist/update/updater.js +13 -8
  413. package/dist/version/peer-announce.d.ts +4 -0
  414. package/dist/version/peer-announce.js +30 -0
  415. package/dist/{server-version.js → version/server-version.js} +5 -1
  416. package/dist/version/version-nudge.d.ts +28 -0
  417. package/dist/version/version-nudge.js +40 -0
  418. package/dist/version/version-skew.d.ts +66 -0
  419. package/dist/version/version-skew.js +71 -0
  420. package/dist/visual/visual-diff.js +2 -2
  421. package/dist/visual/visual-tools.js +4 -4
  422. package/docs/README.md +55 -0
  423. package/docs/agent-cheatsheet.md +85 -0
  424. package/docs/architecture.md +113 -0
  425. package/docs/benchmarks.md +255 -0
  426. package/docs/debugging.md +102 -0
  427. package/docs/deploy-checks.md +69 -0
  428. package/docs/desktop-apps.md +256 -0
  429. package/docs/enterprise.md +62 -0
  430. package/docs/fixtures-dispatch-receiver.yml +76 -0
  431. package/docs/fixtures.md +45 -0
  432. package/docs/flows.md +166 -0
  433. package/docs/gate-plan.md +98 -0
  434. package/docs/gates.md +117 -0
  435. package/docs/getting-started.md +469 -0
  436. package/docs/human-control.md +63 -0
  437. package/docs/integration-patterns.md +171 -0
  438. package/docs/local-registry.md +126 -0
  439. package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
  440. package/docs/matrix/2.5.0/codex-machine.json +20 -0
  441. package/docs/matrix/2.5.0/cursor-machine.json +22 -0
  442. package/docs/matrix/2.5.0/gemini-machine.json +22 -0
  443. package/docs/matrix/2.5.0/opencode-machine.json +22 -0
  444. package/docs/matrix/2.5.0/vscode-machine.json +22 -0
  445. package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
  446. package/docs/matrix/MATRIX.md +26 -0
  447. package/docs/matrix/README.md +59 -0
  448. package/docs/mint.json +61 -0
  449. package/docs/multi-agent-testing.md +55 -0
  450. package/docs/platform-integration.md +122 -0
  451. package/docs/system-map.md +152 -0
  452. package/docs/telemetry-contract.md +214 -0
  453. package/docs/telemetry.md +191 -0
  454. package/docs/testing.md +82 -0
  455. package/docs/token-efficiency.md +80 -0
  456. package/docs/usage.md +1046 -0
  457. package/package.json +8 -5
  458. package/dist/cli-doctor.d.ts +0 -6
  459. package/dist/cli-doctor.js +0 -60
  460. package/dist/cli-launch.js +0 -112
  461. package/dist/daemon-resilience.d.ts +0 -22
  462. package/dist/daemon-resilience.js +0 -85
  463. package/dist/mcp-proxy.d.ts +0 -67
  464. package/dist/mcp-proxy.js +0 -337
  465. package/dist/tools/profiles.d.ts +0 -37
  466. package/dist/tools/profiles.js +0 -139
  467. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  468. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  469. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  470. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  471. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  472. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * How long an idle daemon is given before it shuts itself down.
3
+ *
4
+ * It used to be one number for every case, and at 5 minutes that number killed live runs: 187
5
+ * `reticle_daemon_idle_shutdown` lines on one user's machine, with their editor's Reticle
6
+ * disconnecting each time; and in the fixtures gate it fired during long dependency installs, so apps
7
+ * that booted afterwards hit ERR_CONNECTION_REFUSED and were scored as install failures when nothing
8
+ * was wrong with their install.
9
+ *
10
+ * Reverting is not an option. Daemons used to sit idle a median of 28 minutes at a 0.04% duty cycle
11
+ * because "an agent is attached" alone kept one alive for a whole editor session, and the widened
12
+ * rule is what fixed that.
13
+ *
14
+ * The distinction is TIME, not state. Mid-install an attached daemon is state-identical to one in an
15
+ * empty directory — nothing has been asked of either. What differs is how long it is reasonable to
16
+ * wait: quiet with a client attached means the human is thinking or the install is slow; the same
17
+ * quiet with nobody attached means the daemon is unwanted. Both still exit.
18
+ */
19
+ /**
20
+ * An attached daemon waits this many times the base grace. Six is chosen against the two measurements
21
+ * that matter: the default base is 5 minutes, so an attached daemon survives 30 — comfortably past
22
+ * the slow installs that were killing runs, and comfortably short of the 28-minute median idle the
23
+ * shorter rule was introduced to end.
24
+ */
25
+ export declare const ATTACHED_GRACE_MULTIPLIER = 6;
26
+ /**
27
+ * @param overrideMs an explicit attached grace (RETICLE_IDLE_ATTACHED_MS). Derived from the base when
28
+ * absent — but the derived value is arithmetic on a number the caller also controls, and a test that
29
+ * has to reason about both is a test that breaks for the wrong reason. Configuring it directly is
30
+ * what lets the e2e spec assert the BEHAVIOUR (an attached daemon exits) without racing a product.
31
+ */
32
+ export declare function idleGraceMs(baseMs: number, agentAttached: boolean, overrideMs?: number): number;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * How long an idle daemon is given before it shuts itself down.
3
+ *
4
+ * It used to be one number for every case, and at 5 minutes that number killed live runs: 187
5
+ * `reticle_daemon_idle_shutdown` lines on one user's machine, with their editor's Reticle
6
+ * disconnecting each time; and in the fixtures gate it fired during long dependency installs, so apps
7
+ * that booted afterwards hit ERR_CONNECTION_REFUSED and were scored as install failures when nothing
8
+ * was wrong with their install.
9
+ *
10
+ * Reverting is not an option. Daemons used to sit idle a median of 28 minutes at a 0.04% duty cycle
11
+ * because "an agent is attached" alone kept one alive for a whole editor session, and the widened
12
+ * rule is what fixed that.
13
+ *
14
+ * The distinction is TIME, not state. Mid-install an attached daemon is state-identical to one in an
15
+ * empty directory — nothing has been asked of either. What differs is how long it is reasonable to
16
+ * wait: quiet with a client attached means the human is thinking or the install is slow; the same
17
+ * quiet with nobody attached means the daemon is unwanted. Both still exit.
18
+ */
19
+ /**
20
+ * An attached daemon waits this many times the base grace. Six is chosen against the two measurements
21
+ * that matter: the default base is 5 minutes, so an attached daemon survives 30 — comfortably past
22
+ * the slow installs that were killing runs, and comfortably short of the 28-minute median idle the
23
+ * shorter rule was introduced to end.
24
+ */
25
+ export const ATTACHED_GRACE_MULTIPLIER = 6;
26
+ /**
27
+ * @param overrideMs an explicit attached grace (RETICLE_IDLE_ATTACHED_MS). Derived from the base when
28
+ * absent — but the derived value is arithmetic on a number the caller also controls, and a test that
29
+ * has to reason about both is a test that breaks for the wrong reason. Configuring it directly is
30
+ * what lets the e2e spec assert the BEHAVIOUR (an attached daemon exits) without racing a product.
31
+ */
32
+ export function idleGraceMs(baseMs, agentAttached, overrideMs) {
33
+ // A non-positive base means the watcher is disabled; multiplying must not resurrect it.
34
+ if (baseMs <= 0)
35
+ return baseMs;
36
+ if (!agentAttached)
37
+ return baseMs;
38
+ return overrideMs !== undefined && overrideMs > 0
39
+ ? overrideMs
40
+ : baseMs * ATTACHED_GRACE_MULTIPLIER;
41
+ }
@@ -5,6 +5,14 @@ export interface IdleShutdownOptions {
5
5
  onShutdown: () => void;
6
6
  /** Continuous-idle window before shutdown. `0` (or negative) disables the watcher entirely. */
7
7
  graceMs: number;
8
+ /**
9
+ * Is an agent's MCP client attached right now? An attached daemon gets a much longer grace — quiet
10
+ * with a client present means a slow install or a thinking human, not an unwanted daemon. It still
11
+ * exits. See idle-grace.
12
+ */
13
+ agentAttached?: () => boolean;
14
+ /** Explicit attached grace (RETICLE_IDLE_ATTACHED_MS); derived from graceMs when absent. */
15
+ attachedGraceMs?: number;
8
16
  /** How often to re-check idleness. */
9
17
  checkIntervalMs?: number;
10
18
  /** Injected clock — never read wall-clock time in logic (repo rule). */
@@ -27,7 +35,20 @@ export declare class IdleShutdown {
27
35
  stop(): void;
28
36
  }
29
37
  /**
30
- * Resolve the idle-shutdown grace from `RETICLE_IDLE_SHUTDOWN_MS`: a non-negative integer of milliseconds,
31
- * `0` to disable. Anything missing/invalid falls back to the default. Pure.
38
+ * The idle re-check cadence, from the environment. Same shape as the grace resolver, with one
39
+ * deliberate difference: `0` is not a legal cadence here (it would spin), so `<= 0` falls back to
40
+ * the default rather than meaning anything.
41
+ */
42
+ export declare function resolveIdleCheckMs(raw: string | undefined): number;
43
+ /**
44
+ * Resolve the idle-shutdown grace from `RETICLE_IDLE_SHUTDOWN_MS`: a non-negative integer of
45
+ * milliseconds, `0` to disable. Anything missing or invalid falls back to the default. Pure.
46
+ *
47
+ * The disable is NOT implemented here — 0 is passed through as 0, and `IdleShutdown` treats
48
+ * `graceMs <= 0` as "never self-shut-down" in both `start()` and `check()`. Said out loud because
49
+ * this docblock had drifted onto the function above it, leaving this one undocumented next to a
50
+ * bare `Math.floor`: a reviewer driving 2.5.0 read it as a documented option with no branch
51
+ * implementing it and filed the gate that depends on it as inverted. The behaviour was correct in
52
+ * every build; only the code was readable as broken.
32
53
  */
33
54
  export declare function resolveIdleShutdownMs(raw: string | undefined): number;
@@ -1,5 +1,6 @@
1
1
  import { SESSION_LIFECYCLE } from '@reticlehq/core';
2
- import { log } from './log.js';
2
+ import { idleGraceMs } from './idle-grace.js';
3
+ import { log } from '../log.js';
3
4
  /**
4
5
  * Self-terminates an idle daemon so Reticle never lingers eating a user's resources (the daemon process,
5
6
  * any headless Chromium the pool launched, and the bound port) after the editor closes. The `reticle mcp`
@@ -15,12 +16,16 @@ export class IdleShutdown {
15
16
  #isIdle;
16
17
  #onShutdown;
17
18
  #graceMs;
19
+ #agentAttached;
20
+ #attachedGraceMs;
18
21
  #checkMs;
19
22
  #clock;
20
23
  constructor(opts) {
21
24
  this.#isIdle = opts.isIdle;
22
25
  this.#onShutdown = opts.onShutdown;
23
26
  this.#graceMs = opts.graceMs;
27
+ this.#agentAttached = opts.agentAttached;
28
+ this.#attachedGraceMs = opts.attachedGraceMs;
24
29
  this.#checkMs = opts.checkIntervalMs ?? SESSION_LIFECYCLE.DAEMON_IDLE_CHECK_MS;
25
30
  this.#clock = opts.clock ?? (() => Date.now());
26
31
  }
@@ -34,9 +39,15 @@ export class IdleShutdown {
34
39
  }
35
40
  const now = this.#clock();
36
41
  this.#idleSince ??= now;
37
- if (now - this.#idleSince >= this.#graceMs) {
42
+ // Read per CHECK, not at construction: a client attaches and detaches during a daemon's life, and
43
+ // the question "how long should this quiet be tolerated" is only answerable now. See idle-grace.
44
+ const grace = idleGraceMs(this.#graceMs, this.#agentAttached?.() ?? false, this.#attachedGraceMs);
45
+ if (now - this.#idleSince >= grace) {
38
46
  this.#fired = true;
39
- log('reticle_daemon_idle_shutdown', { idleMs: now - this.#idleSince });
47
+ log('reticle_daemon_idle_shutdown', {
48
+ idleMs: now - this.#idleSince,
49
+ attached: this.#agentAttached?.() ?? false,
50
+ });
40
51
  this.#onShutdown();
41
52
  }
42
53
  }
@@ -54,11 +65,31 @@ export class IdleShutdown {
54
65
  }
55
66
  }
56
67
  /**
57
- * Resolve the idle-shutdown grace from `RETICLE_IDLE_SHUTDOWN_MS`: a non-negative integer of milliseconds,
58
- * `0` to disable. Anything missing/invalid falls back to the default. Pure.
68
+ * The idle re-check cadence, from the environment. Same shape as the grace resolver, with one
69
+ * deliberate difference: `0` is not a legal cadence here (it would spin), so `<= 0` falls back to
70
+ * the default rather than meaning anything.
71
+ */
72
+ export function resolveIdleCheckMs(raw) {
73
+ if (raw === undefined || '' === raw.trim())
74
+ return SESSION_LIFECYCLE.DAEMON_IDLE_CHECK_MS;
75
+ const n = Number(raw);
76
+ if (!Number.isFinite(n) || n <= 0)
77
+ return SESSION_LIFECYCLE.DAEMON_IDLE_CHECK_MS;
78
+ return Math.floor(n);
79
+ }
80
+ /**
81
+ * Resolve the idle-shutdown grace from `RETICLE_IDLE_SHUTDOWN_MS`: a non-negative integer of
82
+ * milliseconds, `0` to disable. Anything missing or invalid falls back to the default. Pure.
83
+ *
84
+ * The disable is NOT implemented here — 0 is passed through as 0, and `IdleShutdown` treats
85
+ * `graceMs <= 0` as "never self-shut-down" in both `start()` and `check()`. Said out loud because
86
+ * this docblock had drifted onto the function above it, leaving this one undocumented next to a
87
+ * bare `Math.floor`: a reviewer driving 2.5.0 read it as a documented option with no branch
88
+ * implementing it and filed the gate that depends on it as inverted. The behaviour was correct in
89
+ * every build; only the code was readable as broken.
59
90
  */
60
91
  export function resolveIdleShutdownMs(raw) {
61
- if (raw === undefined || raw.trim() === '')
92
+ if (raw === undefined || '' === raw.trim())
62
93
  return SESSION_LIFECYCLE.DAEMON_IDLE_SHUTDOWN_MS;
63
94
  const n = Number(raw);
64
95
  if (!Number.isFinite(n) || n < 0)
@@ -0,0 +1,76 @@
1
+ /**
2
+ * What is actually on the bridge port — three states, because two produced a lie.
3
+ *
4
+ * `isRunning` reads the pid file and never the port. So "a stranger holds 4400" and "nothing holds
5
+ * 4400" both answer `running: false`, and every surface built on it inherits that:
6
+ *
7
+ * $ reticle serve
8
+ * {"event":"reticle_daemon_spawned","port":4400} <- exit 0
9
+ * $ reticle status
10
+ * {"event":"reticle_status","port":4400,"running":false}
11
+ * $ tail -1 ~/.reticle/daemon-4400.log
12
+ * {"event":"reticle_daemon_start_failed","error":"listen EADDRINUSE: ... 127.0.0.1:4400"}
13
+ *
14
+ * The daemon knew exactly what was wrong and wrote it where nobody looks. The true sentence — *port
15
+ * 4400 is held by another process* — was never said by anything a person sees.
16
+ *
17
+ * Deliberately probe-based rather than lsof-based: two questions (does TCP connect, does `/status`
18
+ * answer) separate all three states with no platform-specific code, and they work on Windows, where
19
+ * every `lsof` recipe in this repo does not. Naming the holding process is a nicer message and a
20
+ * strictly harder problem; it is not needed to stop lying.
21
+ */
22
+ export declare const PortPresence: {
23
+ /** A Reticle daemon answered `/status`. The only usable state. */
24
+ readonly DAEMON: "daemon";
25
+ /**
26
+ * Something accepts connections here and does not serve Reticle's status endpoint.
27
+ *
28
+ * A foreign process, or one of our own daemons wedged mid-start — and those are the same fact for
29
+ * every caller: this port cannot be bound and cannot be used.
30
+ */
31
+ readonly FOREIGN: "foreign";
32
+ /** Nothing is listening. */
33
+ readonly FREE: "free";
34
+ };
35
+ export type PortPresence = (typeof PortPresence)[keyof typeof PortPresence];
36
+ interface PortFacts {
37
+ /** A TCP connection to the port was accepted. */
38
+ tcpOpen: boolean;
39
+ /** An HTTP GET of `/status` returned a Reticle status payload. */
40
+ statusAnswered: boolean;
41
+ }
42
+ /**
43
+ * Pure. The probes live at the call sites (`probeDaemon`, `fetchStatus`) so this stays testable
44
+ * without a socket, and so a probe that starts lying is a separate failure from a rule that does.
45
+ */
46
+ export declare function classifyPort(facts: PortFacts): PortPresence;
47
+ /** True only for a daemon that answered. Anything else must not be treated as "running". */
48
+ export declare function presenceIsUsable(presence: PortPresence): boolean;
49
+ /**
50
+ * Ask the port both questions and classify the answers.
51
+ *
52
+ * The TCP probe is first and cheap; `/status` is only asked when something accepted, which keeps the
53
+ * common "nothing there" case to one failed connect. Impure by necessity — the rule above is where
54
+ * the behaviour lives, and it is tested without a socket.
55
+ */
56
+ export declare function probePresence(port: number, probes: {
57
+ tcpOpen: (port: number) => Promise<boolean>;
58
+ status: (port: number) => Promise<unknown>;
59
+ }): Promise<PortPresence>;
60
+ /**
61
+ * Who is holding the port, when we can tell.
62
+ *
63
+ * `FOREIGN` covers two situations that need the same answer to "can I bind this?" and OPPOSITE
64
+ * answers to "what do I do?": a stranger's process, and one of our own daemons wedged. Passing
65
+ * these lets the sentence tell them apart when — and only when — the recorded pid for the port
66
+ * matches the process actually holding it.
67
+ */
68
+ export interface PortHolder {
69
+ /** The pid we recorded for this port, from the daemon pid file. `null` when there is none. */
70
+ ourPid: number | null;
71
+ /** The pid actually holding the port, when the caller could determine it. */
72
+ holderPid: number | undefined;
73
+ }
74
+ /** One sentence a human can act on, for each state. Exhaustive by switch, so a new state must add one. */
75
+ export declare function describePresence(presence: PortPresence, port: number, holder?: PortHolder): string;
76
+ export {};
@@ -0,0 +1,93 @@
1
+ /**
2
+ * What is actually on the bridge port — three states, because two produced a lie.
3
+ *
4
+ * `isRunning` reads the pid file and never the port. So "a stranger holds 4400" and "nothing holds
5
+ * 4400" both answer `running: false`, and every surface built on it inherits that:
6
+ *
7
+ * $ reticle serve
8
+ * {"event":"reticle_daemon_spawned","port":4400} <- exit 0
9
+ * $ reticle status
10
+ * {"event":"reticle_status","port":4400,"running":false}
11
+ * $ tail -1 ~/.reticle/daemon-4400.log
12
+ * {"event":"reticle_daemon_start_failed","error":"listen EADDRINUSE: ... 127.0.0.1:4400"}
13
+ *
14
+ * The daemon knew exactly what was wrong and wrote it where nobody looks. The true sentence — *port
15
+ * 4400 is held by another process* — was never said by anything a person sees.
16
+ *
17
+ * Deliberately probe-based rather than lsof-based: two questions (does TCP connect, does `/status`
18
+ * answer) separate all three states with no platform-specific code, and they work on Windows, where
19
+ * every `lsof` recipe in this repo does not. Naming the holding process is a nicer message and a
20
+ * strictly harder problem; it is not needed to stop lying.
21
+ */
22
+ export const PortPresence = {
23
+ /** A Reticle daemon answered `/status`. The only usable state. */
24
+ DAEMON: 'daemon',
25
+ /**
26
+ * Something accepts connections here and does not serve Reticle's status endpoint.
27
+ *
28
+ * A foreign process, or one of our own daemons wedged mid-start — and those are the same fact for
29
+ * every caller: this port cannot be bound and cannot be used.
30
+ */
31
+ FOREIGN: 'foreign',
32
+ /** Nothing is listening. */
33
+ FREE: 'free',
34
+ };
35
+ /**
36
+ * Pure. The probes live at the call sites (`probeDaemon`, `fetchStatus`) so this stays testable
37
+ * without a socket, and so a probe that starts lying is a separate failure from a rule that does.
38
+ */
39
+ export function classifyPort(facts) {
40
+ if (facts.statusAnswered) {
41
+ if (!facts.tcpOpen) {
42
+ // Not reachable from a real pair of probes. Folding it into DAEMON would mean a broken TCP
43
+ // probe reports a healthy daemon — the exact shape of confident-wrong this module exists to
44
+ // remove — so it is an error rather than a guess.
45
+ throw new Error('a port cannot answer /status without accepting a TCP connection; one of the probes is wrong');
46
+ }
47
+ return PortPresence.DAEMON;
48
+ }
49
+ return facts.tcpOpen ? PortPresence.FOREIGN : PortPresence.FREE;
50
+ }
51
+ /** True only for a daemon that answered. Anything else must not be treated as "running". */
52
+ export function presenceIsUsable(presence) {
53
+ return presence === PortPresence.DAEMON;
54
+ }
55
+ /**
56
+ * Ask the port both questions and classify the answers.
57
+ *
58
+ * The TCP probe is first and cheap; `/status` is only asked when something accepted, which keeps the
59
+ * common "nothing there" case to one failed connect. Impure by necessity — the rule above is where
60
+ * the behaviour lives, and it is tested without a socket.
61
+ */
62
+ export async function probePresence(port, probes) {
63
+ const tcpOpen = await probes.tcpOpen(port);
64
+ if (!tcpOpen)
65
+ return PortPresence.FREE;
66
+ const status = await probes.status(port);
67
+ return classifyPort({ tcpOpen, statusAnswered: status !== undefined });
68
+ }
69
+ /** One sentence a human can act on, for each state. Exhaustive by switch, so a new state must add one. */
70
+ export function describePresence(presence, port, holder) {
71
+ switch (presence) {
72
+ case PortPresence.DAEMON:
73
+ return `a Reticle daemon is serving :${String(port)}`;
74
+ case PortPresence.FOREIGN:
75
+ // Our own daemon, wedged: it accepts TCP and never answers `/status`, so it classifies as
76
+ // FOREIGN — correct for binding, and a lie in prose. Found by SIGSTOPping a daemon: doctor
77
+ // said "which is not a Reticle daemon" about a pid its own pid file named, and sent the
78
+ // reader hunting a stranger while never mentioning the fix that works.
79
+ if (holder?.ourPid !== null &&
80
+ holder?.ourPid !== undefined &&
81
+ holder.ourPid === holder.holderPid) {
82
+ return (`port ${String(port)} is held by YOUR Reticle daemon (pid ${String(holder.ourPid)}), ` +
83
+ 'and it is not responding — it is running but wedged, so nothing can use it and nothing ' +
84
+ 'else can bind the port. Stop it (`reticle stop`) and the next tool call starts a fresh ' +
85
+ 'one.');
86
+ }
87
+ return (`port ${String(port)} is held by another process that is not a Reticle daemon — ` +
88
+ 'a daemon cannot bind it. Stop that process, or run Reticle on a different port ' +
89
+ '(`--port`), then retry.');
90
+ case PortPresence.FREE:
91
+ return `nothing is listening on :${String(port)} — no daemon is running`;
92
+ }
93
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A sentence naming the problem, or `undefined` when the directory is usable.
3
+ *
4
+ * A directory that does not exist yet is NOT a problem: the daemon creates it, and the creation
5
+ * failing is caught by the same probe on the parent path.
6
+ */
7
+ export declare function stateDirProblem(dir: string): string | undefined;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Can Reticle write the directory it keeps everything in?
3
+ *
4
+ * `~/.reticle` holds the pid files, the daemon logs, the telemetry id and outbox, and every saved
5
+ * flow. A daemon that cannot write it does not start — and until now said nothing useful about why.
6
+ *
7
+ * Measured on 2.6.0 with `chmod 555 ~/.reticle`:
8
+ *
9
+ * the daemon did not come up on :4415 — nothing is listening on :4415 — no daemon is running
10
+ * see /…/.reticle/daemon-4415.log
11
+ *
12
+ * The first line restates the symptom. The second points at a log **inside the unwritable
13
+ * directory**, so the one diagnostic offered cannot exist. Someone hitting this — a locked-down home
14
+ * directory, a container with a read-only mount, a managed corporate profile — has nothing to go on.
15
+ *
16
+ * Deliberately a PROBE, not a permission-bit read: `stat` says what the mode claims, and an ACL, a
17
+ * read-only mount, SELinux or a full disk all produce a writable-looking mode and a failing write.
18
+ * The only honest test of "can I write here" is writing.
19
+ */
20
+ import { mkdirSync, existsSync, writeFileSync, unlinkSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+ /** Probe filename. Distinctive so a leftover is obviously ours, and unlinked immediately either way. */
23
+ const PROBE = '.reticle-write-probe';
24
+ /**
25
+ * A sentence naming the problem, or `undefined` when the directory is usable.
26
+ *
27
+ * A directory that does not exist yet is NOT a problem: the daemon creates it, and the creation
28
+ * failing is caught by the same probe on the parent path.
29
+ */
30
+ export function stateDirProblem(dir) {
31
+ if (!existsSync(dir)) {
32
+ // Not yet created is the ordinary first-run state. Only report if we cannot create it.
33
+ try {
34
+ mkdirSync(dir, { recursive: true });
35
+ }
36
+ catch {
37
+ return (`Reticle cannot create its state directory at ${dir}. Everything it keeps — the daemon ` +
38
+ 'log, the pid file, saved flows — lives there, so the daemon cannot start. Check the ' +
39
+ 'permissions on the parent directory, or point Reticle elsewhere with HOME.');
40
+ }
41
+ return undefined;
42
+ }
43
+ const probe = join(dir, PROBE);
44
+ try {
45
+ writeFileSync(probe, '', 'utf8');
46
+ }
47
+ catch {
48
+ return (`Reticle cannot write to its state directory at ${dir}. Everything it keeps — the daemon ` +
49
+ 'log, the pid file, saved flows — lives there, so the daemon cannot start. Make it writable ' +
50
+ '(`chmod u+w`), or point Reticle elsewhere with HOME.');
51
+ }
52
+ try {
53
+ unlinkSync(probe);
54
+ }
55
+ catch {
56
+ /* wrote but could not clean up: the directory IS writable, which is the question asked */
57
+ }
58
+ return undefined;
59
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The pid of a daemon that died without exiting, observed at the next start.
3
+ *
4
+ * `DaemonExitReason` covers every departure that goes through Node — signal, idle, and the
5
+ * load-bearing `unknown` for a stray exit or an uncaught throw. It cannot cover SIGKILL, an OOM
6
+ * kill, or a lost machine: no handler runs, so the log's last word about that daemon is whatever it
7
+ * happened to be doing. From the report (#123):
8
+ *
9
+ * 10:49:05 mcp_daemon_started port:4400 <- pid 2884
10
+ * 10:51:29 mcp_daemon_started port:4400 pid:4900 <- a SECOND daemon binds the SAME port
11
+ *
12
+ * pid 2884 died with no exit event at all, and the only evidence was that a second daemon could bind
13
+ * a port the first one held — something a reader has to notice and interpret.
14
+ *
15
+ * The next startup is the only place that death is observable, because it is the only moment
16
+ * anything looks at the pidfile the dead process left behind. A tidy shutdown unlinks its own, so
17
+ * finding one whose owner is gone IS the signal.
18
+ *
19
+ * Pure, and deliberately conservative: an owner that is still ALIVE is a port conflict, not a death,
20
+ * and accusing a live sibling of dying would be a new false claim in the log this exists to make
21
+ * honest.
22
+ */
23
+ export declare function uncleanPredecessor(owner: number | null, alive: boolean, self: number): number | null;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The pid of a daemon that died without exiting, observed at the next start.
3
+ *
4
+ * `DaemonExitReason` covers every departure that goes through Node — signal, idle, and the
5
+ * load-bearing `unknown` for a stray exit or an uncaught throw. It cannot cover SIGKILL, an OOM
6
+ * kill, or a lost machine: no handler runs, so the log's last word about that daemon is whatever it
7
+ * happened to be doing. From the report (#123):
8
+ *
9
+ * 10:49:05 mcp_daemon_started port:4400 <- pid 2884
10
+ * 10:51:29 mcp_daemon_started port:4400 pid:4900 <- a SECOND daemon binds the SAME port
11
+ *
12
+ * pid 2884 died with no exit event at all, and the only evidence was that a second daemon could bind
13
+ * a port the first one held — something a reader has to notice and interpret.
14
+ *
15
+ * The next startup is the only place that death is observable, because it is the only moment
16
+ * anything looks at the pidfile the dead process left behind. A tidy shutdown unlinks its own, so
17
+ * finding one whose owner is gone IS the signal.
18
+ *
19
+ * Pure, and deliberately conservative: an owner that is still ALIVE is a port conflict, not a death,
20
+ * and accusing a live sibling of dying would be a new false claim in the log this exists to make
21
+ * honest.
22
+ */
23
+ export function uncleanPredecessor(owner, alive, self) {
24
+ if (null === owner || owner === self || alive)
25
+ return null;
26
+ return owner;
27
+ }
@@ -107,11 +107,11 @@ export function buildDomainModel(flows, contract, runs = []) {
107
107
  };
108
108
  }
109
109
  function buildSummary(flowCount, coverage, gaps, topRisk) {
110
- if (flowCount === 0) {
110
+ if (0 === flowCount) {
111
111
  return 'No saved flows yet — record the critical journeys (reticle_record{action:"start"}) so the agent learns the app.';
112
112
  }
113
113
  const parts = [
114
- `${String(flowCount)} flow${flowCount === 1 ? '' : 's'}: ${String(coverage.asserted)} asserted, ${String(coverage.presenceOnly)} presence-only, ${String(coverage.assertionFree)} assertion-free`,
114
+ `${String(flowCount)} flow${1 === flowCount ? '' : 's'}: ${String(coverage.asserted)} asserted, ${String(coverage.presenceOnly)} presence-only, ${String(coverage.assertionFree)} assertion-free`,
115
115
  ];
116
116
  if (topRisk !== undefined) {
117
117
  parts.push(`test first: ${topRisk.name} (${topRisk.reason})`);
@@ -25,19 +25,19 @@ const ERROR_KEYS = ['error', 'errors', 'errorMessage', 'failureReason'];
25
25
  /** Arrays worth walking for per-item outcomes. Bulk APIs converge on a handful of names. */
26
26
  const ITEM_KEYS = ['results', 'items', 'data', 'records', 'responses'];
27
27
  function isRecord(value) {
28
- return typeof value === 'object' && value !== null && !Array.isArray(value);
28
+ return 'object' === typeof value && value !== null && !Array.isArray(value);
29
29
  }
30
30
  /** Whether one object reports a failure of its own. */
31
31
  function reportsFailure(item) {
32
32
  if (!isRecord(item))
33
33
  return false;
34
34
  for (const flag of FAILURE_FLAGS) {
35
- if (item[flag] === false)
35
+ if (false === item[flag])
36
36
  return true;
37
37
  }
38
38
  for (const key of ERROR_KEYS) {
39
39
  const value = item[key];
40
- if (value === undefined || value === null)
40
+ if (value === undefined || null === value)
41
41
  continue;
42
42
  if (Array.isArray(value)) {
43
43
  if (value.length > 0)
@@ -45,7 +45,7 @@ function reportsFailure(item) {
45
45
  continue;
46
46
  }
47
47
  // An empty string or an explicit null is an API saying "no error", not an error.
48
- if (typeof value === 'string' && value.length === 0)
48
+ if ('string' === typeof value && 0 === value.length)
49
49
  continue;
50
50
  return true;
51
51
  }
@@ -55,7 +55,7 @@ function reportsFailure(item) {
55
55
  function itemOutcomes(body) {
56
56
  for (const key of ITEM_KEYS) {
57
57
  const list = body[key];
58
- if (!Array.isArray(list) || list.length === 0)
58
+ if (!Array.isArray(list) || 0 === list.length)
59
59
  continue;
60
60
  const failed = list.filter(reportsFailure).length;
61
61
  return { failed, total: list.length };
@@ -63,7 +63,7 @@ function itemOutcomes(body) {
63
63
  return { failed: 0, total: 0 };
64
64
  }
65
65
  function parse(raw) {
66
- if (typeof raw !== 'string' || raw.length === 0)
66
+ if (typeof raw !== 'string' || 0 === raw.length)
67
67
  return undefined;
68
68
  try {
69
69
  const parsed = JSON.parse(raw);
@@ -87,8 +87,8 @@ export function findBodyFailures(events) {
87
87
  const body = parse(event.data['responseBody']);
88
88
  if (body === undefined)
89
89
  continue;
90
- const url = typeof event.data['url'] === 'string' ? event.data['url'] : '';
91
- const method = typeof event.data['method'] === 'string' ? event.data['method'] : '';
90
+ const url = 'string' === typeof event.data['url'] ? event.data['url'] : '';
91
+ const method = 'string' === typeof event.data['method'] ? event.data['method'] : '';
92
92
  const where = `${method} ${url} → ${String(status)}`;
93
93
  const { failed, total } = itemOutcomes(body);
94
94
  if (failed > 0) {
@@ -50,4 +50,4 @@ export interface ContradictionOptions {
50
50
  */
51
51
  mutatedWithin?: number | undefined;
52
52
  }
53
- export declare function findContradictions(events: readonly ReticleEvent[], options?: ContradictionOptions): Contradiction[];
53
+ export declare function findContradictions(allEvents: readonly ReticleEvent[], options?: ContradictionOptions): Contradiction[];