@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
package/dist/init/run.js CHANGED
@@ -4,16 +4,52 @@
4
4
  * access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
5
5
  */
6
6
  import { dirname, join } from 'node:path';
7
- import { detect, Framework } from './detect.js';
8
- import { DEPS_TARGET, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
7
+ import { spanSync } from '../trace.js';
8
+ import { readFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { detect, Framework, namesAPackageManager } from './detect.js';
11
+ import { wasMcpRegistered } from './mcp-registered.js';
12
+ import { pickAstroHost } from './astro-host.js';
13
+ import { workspaceParents } from './workspace-apps.js';
14
+ import { chooseWorkspaceApp } from './app-choice.js';
15
+ import { isConnectStep } from './plan.js';
16
+ import { CLI, CURSOR_RULE_PATH } from './agent-rules.js';
17
+ import { CRA_ENV_PATH } from './cra.js';
18
+ /** CRA's bundled entry, in the order create-react-app itself generates them. */
19
+ const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
20
+ function craEntryOf(io) {
21
+ for (const path of CRA_ENTRY_CANDIDATES) {
22
+ const source = io.readFile(path);
23
+ if (source !== null)
24
+ return { path, source };
25
+ }
26
+ return null;
27
+ }
28
+ /**
29
+ * The daemon's pairing token, for the one stack that cannot read it at build time.
30
+ *
31
+ * Every other framework's snippet reads this file in Node when the dev server starts, so the token
32
+ * is never written into the project. CRA bundles browser code and inlines only REACT_APP_*, so it
33
+ * has to be materialised into .env.development.local — which CRA's own template gitignores.
34
+ */
35
+ function readPairingToken() {
36
+ try {
37
+ return readFileSync(join(homedir(), '.reticle', 'pairing-token'), 'utf8').trim();
38
+ }
39
+ catch {
40
+ return '';
41
+ }
42
+ }
43
+ import { DEPS_TARGET, frameworkPackages, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
9
44
  import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
10
45
  import { reticleDevLocation } from './next-patch.js';
11
46
  import { scanTestids, storeHints } from './capabilities.js';
12
47
  import { CURSOR_DIR_RELPATH, CURSOR_MCP_RELPATH } from './cursor.js';
48
+ import { fileBackedClients, clientMarkerRelPath, ConfigScope, McpClient } from './mcp-clients.js';
13
49
  import { deriveProjectId, packageName } from './project-id.js';
14
50
  import { VITE_DEV_MODULE_PATH } from './snippets.js';
15
51
  import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
16
- import { SERVER_VERSION } from '../server-version.js';
52
+ import { SERVER_VERSION } from '../version/server-version.js';
17
53
  import { InitFailure, reportInitOutcome } from '../telemetry/init-telemetry.js';
18
54
  /** Lockfile basenames, in package-manager preference order (mirrors detect.ts). */
19
55
  const LOCKFILE_NAMES = [
@@ -27,11 +63,20 @@ const LOCKFILE_NAMES = [
27
63
  * Resolve the lockfiles set used to pick the package manager. A lockfile in the project root wins;
28
64
  * otherwise we walk UP the directory tree (monorepos keep the lockfile at the workspace root, not in
29
65
  * each package) so `reticle init` in a sub-package suggests `pnpm add` instead of defaulting to `npm i`.
66
+ *
67
+ * The walk is skipped when the project has its own installed tree, because an INHERITED lockfile is
68
+ * weaker evidence than a `node_modules` sitting right there — the ancestor describes the workspace,
69
+ * the tree describes THIS package. Reported from the field: `init` in a `frontend/` app installed
70
+ * with npm emitted `pnpm add -D` off a repo-root `pnpm-lock.yaml`, pnpm was not on PATH, and the
71
+ * failed install took every downstream wiring step with it. A LOCAL lockfile still wins over the
72
+ * tree — it is a deliberate statement about this package, not an inheritance.
30
73
  */
31
- export function resolveLockfiles(rootFiles, cwd, io) {
74
+ export function resolveLockfiles(rootFiles, cwd, io, nodeModulesMarkers = new Set()) {
32
75
  const set = new Set(rootFiles);
33
76
  if (LOCKFILE_NAMES.some((name) => set.has(name)))
34
77
  return set; // local lockfile is authoritative
78
+ if (namesAPackageManager(nodeModulesMarkers))
79
+ return set;
35
80
  let dir = cwd;
36
81
  for (let depth = 0; depth < 50; depth++) {
37
82
  for (const name of LOCKFILE_NAMES) {
@@ -48,6 +93,7 @@ export function resolveLockfiles(rootFiles, cwd, io) {
48
93
  return set;
49
94
  }
50
95
  const PACKAGE_JSON = 'package.json';
96
+ const NODE_MODULES_DIR = 'node_modules';
51
97
  /**
52
98
  * Root-layout candidates, App Router only. `--src-dir` apps keep theirs under `src/app`, and the
53
99
  * ReticleDev component has to land NEXT TO the layout or the relative import it generates is dead.
@@ -108,6 +154,16 @@ const VITE_CONFIG_CANDIDATES = [
108
154
  'vite.config.mjs',
109
155
  'vite.config.mts',
110
156
  ];
157
+ const ASTRO_CONFIG_CANDIDATES = [
158
+ 'astro.config.mjs',
159
+ 'astro.config.js',
160
+ 'astro.config.ts',
161
+ 'astro.config.cjs',
162
+ ];
163
+ /** Where a conventional Astro app keeps the layout every page wraps itself in. */
164
+ const ASTRO_LAYOUTS_DIR = 'src/layouts';
165
+ /** Also searched: an app with no layouts directory renders the document straight from a page. */
166
+ const ASTRO_PAGES_DIR = 'src/pages';
111
167
  const NEXT_CONFIG_CANDIDATES = [
112
168
  'next.config.mjs',
113
169
  'next.config.js',
@@ -119,6 +175,21 @@ const NEXT_CONFIG_CANDIDATES = [
119
175
  * decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
120
176
  * on a working install that happens to be on an ungated stack.
121
177
  */
178
+ /**
179
+ * A step's status AFTER the run, which is what actually happened to it.
180
+ *
181
+ * `report` applies the same downgrade when printing: a step that failed or was skipped is shown as
182
+ * MANUAL whatever it planned to be. Reading the planned status alone would say a step applied when
183
+ * the run had already given up on it.
184
+ */
185
+ function resolvedStatus(plan, target, failed, skipped) {
186
+ const step = plan.steps.find((s) => s.target === target);
187
+ if (step === undefined)
188
+ return undefined;
189
+ if (failed.has(target) || skipped.has(target))
190
+ return StepStatus.MANUAL;
191
+ return step.status;
192
+ }
122
193
  const STATUS_SYMBOL = {
123
194
  [StepStatus.APPLY]: '✓',
124
195
  [StepStatus.MANUAL]: '⚠',
@@ -137,17 +208,19 @@ function gatherPlanInput(options, io, pkgRaw) {
137
208
  // Stable identity derived from the app's package.json name + root, so it survives port changes.
138
209
  const projectId = deriveProjectId(packageName(pkg), options.cwd);
139
210
  const rootFiles = new Set(io.rootFiles());
211
+ const nodeModulesMarkers = new Set(io.listFiles('node_modules'));
140
212
  const detectInput = {
141
- pkg: typeof pkg === 'object' && pkg !== null ? pkg : {},
213
+ pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
142
214
  configFiles: rootFiles,
143
- // Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager.
144
- lockfiles: resolveLockfiles(rootFiles, options.cwd, io),
215
+ // Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager —
216
+ // unless this package's own installed tree already answers it, which outranks an inherited one.
217
+ lockfiles: resolveLockfiles(rootFiles, options.cwd, io, nodeModulesMarkers),
145
218
  // An already-installed tree names its own manager, which matters when no lockfile is committed.
146
- nodeModulesMarkers: new Set(io.listFiles('node_modules')),
219
+ nodeModulesMarkers,
147
220
  };
148
221
  const detection = detect(detectInput);
149
222
  const vitePath = firstPresent(rootFiles, VITE_CONFIG_CANDIDATES);
150
- const viteSource = vitePath === null ? null : io.readFile(vitePath);
223
+ const viteSource = null === vitePath ? null : io.readFile(vitePath);
151
224
  const viteConfig = vitePath !== null && viteSource !== null ? { path: vitePath, source: viteSource } : null;
152
225
  // Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
153
226
  // global config file. Only probe when the MCP step is in play.
@@ -155,16 +228,57 @@ function gatherPlanInput(options, io, pkgRaw) {
155
228
  const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
156
229
  const existsProbe = claudeExistsProbe();
157
230
  const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
158
- const cursorDir = `${io.homeDir()}/${CURSOR_DIR_RELPATH}`;
159
- const cursorConfigPath = `${io.homeDir()}/${CURSOR_MCP_RELPATH}`;
160
- const cursorPresent = options.mcp && io.exists(cursorDir);
231
+ // join(), not a literal '/': this path is checked and WRITTEN on the user's machine, and a
232
+ // backslash platform silently misses the config it is meant to find.
233
+ const cursorDir = join(io.homeDir(), CURSOR_DIR_RELPATH);
234
+ const cursorConfigPath = join(io.homeDir(), CURSOR_MCP_RELPATH);
235
+ // Cursor is "present" if its global config directory exists OR the project carries a `.cursor/`.
236
+ // The directory alone missed a real case: Cursor installed with a FRESH profile has not written
237
+ // ~/.cursor yet, so a user who had it open got no Cursor step and no message about one. A
238
+ // project-level .cursor/ is unambiguous evidence somebody uses it here.
239
+ const cursorPresent = options.mcp && (io.exists(cursorDir) || io.exists(CURSOR_DIR_RELPATH));
161
240
  const cursorConfig = cursorPresent ? io.readFile(cursorConfigPath) : null;
241
+ // Every OTHER MCP client this machine shows evidence of. Conservative and one-directional: we
242
+ // write into a config a client ALREADY has, and never create ~/.gemini or ~/.codeium for somebody
243
+ // who does not use them. Cursor and Claude are handled above by their own steps.
244
+ const detectedClients = options.mcp
245
+ ? fileBackedClients()
246
+ .filter((spec) => spec.id !== McpClient.CURSOR)
247
+ .map((spec) => {
248
+ const marker = clientMarkerRelPath(spec);
249
+ const absolute = spec.scope === ConfigScope.HOME ? join(io.homeDir(), spec.relPath) : spec.relPath;
250
+ const markerPath = spec.scope === ConfigScope.HOME ? join(io.homeDir(), marker) : marker;
251
+ if (!io.exists(markerPath))
252
+ return null;
253
+ return {
254
+ id: spec.id,
255
+ configPath: absolute,
256
+ // io.readFile passes absolute paths through unchanged, so one call covers both scopes.
257
+ existing: io.readFile(absolute),
258
+ };
259
+ })
260
+ .filter((entry) => entry !== null)
261
+ : [];
262
+ const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
263
+ const astroSource = null === astroPath ? null : io.readFile(astroPath);
264
+ // Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
265
+ // .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
266
+ // renders from src/pages/index.astro, the other has three files there of which two are partials.
267
+ // `</body>` is the discriminator — see astro-host.
268
+ const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
269
+ .listFiles(dir)
270
+ .filter((f) => f.endsWith('.astro'))
271
+ .map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
272
+ .filter((c) => c.source !== null));
273
+ const astroHost = pickAstroHost(astroCandidates);
274
+ const layoutRelPath = astroHost?.path ?? null;
275
+ const astroLayoutSource = astroHost?.source ?? null;
162
276
  const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
163
277
  // App Router first; a Pages Router app has no layout, and its mount point is pages/_app.
164
- const layoutPath = (NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
165
- NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p))) ??
278
+ const layoutPath = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
279
+ NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ??
166
280
  null;
167
- const layoutSource = layoutPath === null ? null : io.readFile(layoutPath);
281
+ const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
168
282
  // Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
169
283
  // component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
170
284
  const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
@@ -173,12 +287,17 @@ function gatherPlanInput(options, io, pkgRaw) {
173
287
  claudeCli,
174
288
  mcpExists,
175
289
  cursorPresent,
290
+ detectedClients,
176
291
  cursorProjectPresent: io.exists(CURSOR_DIR_RELPATH),
177
292
  cursorConfig,
178
293
  cursorConfigPath,
179
294
  viteConfig,
295
+ astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
296
+ astroLayout: layoutRelPath !== null && astroLayoutSource !== null
297
+ ? { path: layoutRelPath, source: astroLayoutSource }
298
+ : null,
180
299
  nextConfigFile,
181
- nextConfigSource: nextConfigFile === null ? null : io.readFile(nextConfigFile),
300
+ nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
182
301
  nextLayout: layoutPath !== null && layoutSource !== null
183
302
  ? { path: layoutPath, source: layoutSource }
184
303
  : null,
@@ -190,13 +309,16 @@ function gatherPlanInput(options, io, pkgRaw) {
190
309
  nextReticleDevImport: devLocation.importSpecifier,
191
310
  nextReticleDevExists: io.exists(devLocation.path),
192
311
  svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
312
+ craEntry: craEntryOf(io),
313
+ craEnv: io.readFile(CRA_ENV_PATH),
314
+ pairingToken: readPairingToken(),
193
315
  reticleConfigExists: io.exists('.reticle.json'),
194
316
  // Read the agent instruction files so the rule merge stays idempotent across re-runs.
195
317
  claudeMdContent: io.readFile('CLAUDE.md'),
196
318
  agentsMdContent: io.readFile('AGENTS.md'),
197
- cursorRuleExists: io.exists('.cursor/rules/reticle.mdc'),
198
- claudeCommandExists: io.exists(CLAUDE_COMMAND_PATH),
199
- cursorCommandExists: io.exists(CURSOR_COMMAND_PATH),
319
+ cursorRuleContent: io.readFile(CURSOR_RULE_PATH),
320
+ claudeCommandContent: io.readFile(CLAUDE_COMMAND_PATH),
321
+ cursorCommandContent: io.readFile(CURSOR_COMMAND_PATH),
200
322
  options: {
201
323
  port: options.port,
202
324
  mcp: options.mcp,
@@ -208,12 +330,13 @@ function gatherPlanInput(options, io, pkgRaw) {
208
330
  };
209
331
  }
210
332
  /** Where workspace tooling conventionally puts packages. */
211
- const WORKSPACE_PARENTS = ['apps', 'packages'];
333
+ /** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
334
+ const PNPM_WORKSPACE = 'pnpm-workspace.yaml';
212
335
  /** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
213
336
  const APP_DEPS = ['next', 'vite'];
214
337
  function looksLikeApp(dir, io) {
215
338
  const pkgRaw = io.readFile(`${dir}/${PACKAGE_JSON}`);
216
- if (pkgRaw === null)
339
+ if (null === pkgRaw)
217
340
  return false;
218
341
  const configs = [...VITE_CONFIG_CANDIDATES, ...NEXT_CONFIG_CANDIDATES];
219
342
  if (configs.some((c) => io.exists(`${dir}/${c}`)))
@@ -231,15 +354,54 @@ function looksLikeApp(dir, io) {
231
354
  */
232
355
  export function findWorkspaceApps(io) {
233
356
  const found = [];
234
- for (const parent of WORKSPACE_PARENTS) {
357
+ // A workspace DECLARES its packages; `['apps','packages']` was a guess that missed a real repo
358
+ // with three Next apps at web/, admin/ and space/ — and missing them meant init ran against the
359
+ // root and reported ✓ for a file Next never compiles. See workspace-apps.
360
+ const pkgRaw = io.readFile(PACKAGE_JSON);
361
+ let pkgWorkspaces;
362
+ try {
363
+ const parsed = null === pkgRaw ? undefined : JSON.parse(pkgRaw);
364
+ pkgWorkspaces =
365
+ 'object' === typeof parsed && parsed !== null
366
+ ? parsed.workspaces
367
+ : undefined;
368
+ }
369
+ catch {
370
+ pkgWorkspaces = undefined;
371
+ }
372
+ const parents = workspaceParents({
373
+ ...(null === io.readFile(PNPM_WORKSPACE)
374
+ ? {}
375
+ : { pnpmWorkspace: io.readFile(PNPM_WORKSPACE) ?? '' }),
376
+ ...(pkgWorkspaces === undefined ? {} : { pkgWorkspaces }),
377
+ topLevelDirs: io.listDirs('.'),
378
+ });
379
+ // A declared parent can itself BE the app (`workspaces: ["web"]`), so check both the directory and
380
+ // its children rather than assuming one level of nesting.
381
+ for (const parent of parents) {
382
+ if (looksLikeApp(parent, io))
383
+ found.push(parent);
235
384
  for (const name of io.listDirs(parent)) {
236
385
  const dir = `${parent}/${name}`;
237
386
  if (looksLikeApp(dir, io))
238
387
  found.push(dir);
239
388
  }
240
389
  }
241
- return found;
390
+ return [...new Set(found)];
242
391
  }
392
+ /**
393
+ * The standing ask, printed at the end of every `init`.
394
+ *
395
+ * Addressed to the agent as much as the human: whichever of them just ran this command is the one
396
+ * holding the experience, and neither has a Reticle tool surface to file through at this point.
397
+ *
398
+ * Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
399
+ * the workspace redirect is kept from asking twice.
400
+ */
401
+ export const FEEDBACK_HINT = 'Anything wrong, missing, or awkward — in this setup or in Reticle itself? Tell us; it is the ' +
402
+ 'one thing that decides what gets fixed:\n' +
403
+ ` ${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" (agents)\n` +
404
+ ` ${CLI} feedback "what worked, what didn't" (humans)`;
243
405
  function restartHint(framework) {
244
406
  if (framework === Framework.NEXT)
245
407
  return 'Restart `next dev`, then ask your agent: "List Reticle sessions".';
@@ -253,13 +415,31 @@ function restartHint(framework) {
253
415
  }
254
416
  const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
255
417
  'installed stops it booting. Run that install, then re-run `reticle init`.';
256
- function report(plan, dryRun, failed, skipped, io) {
418
+ function report(plan, dryRun, failed, skipped, degraded, io, projectDir) {
257
419
  io.print(dryRun ? 'reticle init (dry run — no files written)' : 'reticle init');
420
+ // Every path below is printed RELATIVE, and until now nothing said what to. Reported from the
421
+ // field as "[✓] Reticle config → .reticle.json" followed by the file not being there: the app was
422
+ // in `frontend/`, init redirected into it, and the report's `.reticle.json` was true about a
423
+ // directory the reader was not standing in. One line makes every path in the report unambiguous.
424
+ io.print(` in ${projectDir}`);
258
425
  io.print('');
259
426
  let applied = 0;
260
427
  let manual = 0;
428
+ // A ⚠ on a CONNECT step is a guaranteed failure, not a caveat: nothing performs the manual step, so
429
+ // the app never dials the daemon and every tool answers "no browser session connected". `ok` was
430
+ // hardcoded true, so a run that could not possibly work reported success.
431
+ let connectPending = false;
261
432
  for (const s of plan.steps) {
262
433
  // A side effect that failed to apply is reported as a manual step with its fallback command.
434
+ const note = degraded.get(s.target);
435
+ if (note !== undefined) {
436
+ // Applied, but not the way it was asked for. A NOTICE, not work — the install did happen.
437
+ io.print(` [${STATUS_SYMBOL[StepStatus.NOTICE]}] ${s.title} → ${s.target}`);
438
+ for (const line of note.split('\n'))
439
+ io.print(` ${line}`);
440
+ applied++;
441
+ continue;
442
+ }
263
443
  const downgraded = failed.has(s.target) || skipped.has(s.target);
264
444
  const status = downgraded ? StepStatus.MANUAL : s.status;
265
445
  const detail = skipped.has(s.target)
@@ -272,8 +452,11 @@ function report(plan, dryRun, failed, skipped, io) {
272
452
  applied++;
273
453
  if (status === StepStatus.MANUAL || status === StepStatus.NOTICE) {
274
454
  // A notice prints in full like a manual step — it is worth reading — but is NOT counted as work.
275
- if (status === StepStatus.MANUAL)
455
+ if (status === StepStatus.MANUAL) {
276
456
  manual++;
457
+ if (isConnectStep(s.title))
458
+ connectPending = true;
459
+ }
277
460
  for (const line of detail.split('\n'))
278
461
  io.print(` ${line}`);
279
462
  }
@@ -282,8 +465,13 @@ function report(plan, dryRun, failed, skipped, io) {
282
465
  }
283
466
  }
284
467
  io.print('');
468
+ if (connectPending) {
469
+ io.print('This app will NOT connect until the ⚠ step above is done by hand — Reticle tools will report ' +
470
+ '"no browser session connected" until then. Everything else is already in place.');
471
+ io.print('');
472
+ }
285
473
  io.print(restartHint(plan.framework));
286
- return { ok: true, applied, manual };
474
+ return { ok: !connectPending, applied, manual };
287
475
  }
288
476
  /**
289
477
  * Perform the apply-step side effects; return the targets whose side effect failed.
@@ -294,26 +482,87 @@ function report(plan, dryRun, failed, skipped, io) {
294
482
  * MODULE_NOT_FOUND, so the app stops booting *because* Reticle was installed. A skipped step is a
295
483
  * message; a half-wired app is a broken project.
296
484
  */
485
+ /**
486
+ * Are the SDK packages actually on disk, whatever the install step reported?
487
+ *
488
+ * `dependsOnInstall` exists to stop init writing a `next.config.ts` that imports a package which is
489
+ * not there — that took a dev server down once, and installing Reticle must never be why an app
490
+ * stops booting. The invariant it protects is "the import RESOLVES", but it was gated on "our
491
+ * install subprocess exited 0", and those come apart in exactly the situation the failure creates:
492
+ * init tells the user to install by hand, they do, they re-run init, the install step fails again
493
+ * (wrong package manager, not on PATH) and every wiring step is skipped a second time. Reported from
494
+ * a Next 16 app where npm had already installed both packages successfully — leaving an init that
495
+ * could not be retried into working, which is the shape this guard was written to prevent.
496
+ *
497
+ * Reading node_modules answers the real question and costs one `exists` call per package.
498
+ */
499
+ function sdkPackagesPresent(framework, io) {
500
+ const packages = frameworkPackages(framework);
501
+ return (packages.length > 0 &&
502
+ packages.every((p) => io.exists(`${NODE_MODULES_DIR}/${p}/${PACKAGE_JSON}`)));
503
+ }
297
504
  function applyEffects(plan, io) {
298
505
  const failed = new Set();
299
506
  const skipped = new Set();
507
+ const degraded = new Map();
300
508
  let installFailed = false;
301
509
  for (const s of plan.steps) {
302
510
  if (s.status !== StepStatus.APPLY)
303
511
  continue;
304
- if (installFailed && s.dependsOnInstall === true) {
512
+ if (installFailed && true === s.dependsOnInstall) {
305
513
  skipped.add(s.target);
306
514
  continue;
307
515
  }
308
- if (s.write !== undefined)
309
- io.writeFile(s.write.path, s.write.content);
310
- if (s.exec !== undefined && !io.exec(s.exec.command, s.exec.args)) {
516
+ const write = s.write;
517
+ if (write !== undefined) {
518
+ // Confirm the EFFECT, not the intention. Reported from the field (#160): init printed
519
+ // `[✓] Reticle config → .reticle.json` and the file was not there afterward. Nothing checked —
520
+ // no arrangement of the filesystem (a read-only mount, a full disk, an antivirus quarantining
521
+ // a new dotfile) could have turned that tick into anything else. A checkmark that cannot fail
522
+ // is decoration, and this one is the first thing a new user reads. Same shape as #139.
523
+ const wrote = spanSync('init.write', { target: s.target, path: write.path }, () => {
524
+ try {
525
+ io.writeFile(write.path, write.content);
526
+ }
527
+ catch {
528
+ return false; // a throw is the loud version of the same failure
529
+ }
530
+ return io.exists(write.path);
531
+ });
532
+ if (!wrote) {
533
+ failed.add(s.target);
534
+ continue; // do not run this step's exec against a file that is not there
535
+ }
536
+ }
537
+ // Bound once so the traced call cannot need a `?? ''` fallback — a default there would turn a
538
+ // narrowing mistake into an empty command that silently "succeeds".
539
+ const exec = s.exec;
540
+ // Per-step, because the interesting part of init's wall-clock is WHICH step spent it: a
541
+ // package-manager install and a `claude mcp add` are both subprocesses, and one of them being
542
+ // slow is a completely different problem from the other.
543
+ if (exec !== undefined &&
544
+ !spanSync('init.exec', { target: s.target, command: exec.command }, () => io.exec(exec.command, exec.args))) {
545
+ // A weaker second attempt beats no install at all — but only when it is REPORTED, because the
546
+ // thing it gives up is the version pin that keeps SDK and daemon in step.
547
+ //
548
+ // Traced separately, and it is the reason init can take twice as long as it looks like it
549
+ // should: this is a SECOND full package-manager run, and until it had its own span 1.6 of
550
+ // init's 2.3 seconds simply vanished — the span above accounted for the first attempt and
551
+ // nothing accounted for this one.
552
+ const retry = s.retry;
553
+ if (retry !== undefined &&
554
+ spanSync('init.exec.retry', { target: s.target, command: retry.command }, () => io.exec(retry.command, retry.args))) {
555
+ degraded.set(s.target, retry.note);
556
+ continue;
557
+ }
311
558
  failed.add(s.target);
312
- if (s.target === DEPS_TARGET)
559
+ // A failed install only blocks the wiring when the packages are genuinely ABSENT. See
560
+ // sdkPackagesPresent: the guard protects "the import resolves", not "our subprocess exited 0".
561
+ if (s.target === DEPS_TARGET && !sdkPackagesPresent(plan.framework, io))
313
562
  installFailed = true;
314
563
  }
315
564
  }
316
- return { failed, skipped };
565
+ return { failed, skipped, degraded };
317
566
  }
318
567
  /**
319
568
  * Which STEP failed, from the step targets — not from an error string, which would carry paths.
@@ -338,25 +587,35 @@ const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle
338
587
  * Returns null when there is nothing to redirect to — the caller then proceeds here as before.
339
588
  */
340
589
  function redirectToWorkspaceApp(options, io, pkgRaw) {
341
- if (options.redirected === true)
590
+ if (true === options.redirected)
342
591
  return null;
343
592
  const pkg = JSON.parse(pkgRaw);
344
593
  const rootFiles = new Set(io.rootFiles());
345
594
  const here = detect({
346
- pkg: typeof pkg === 'object' && pkg !== null ? pkg : {},
595
+ pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
347
596
  configFiles: rootFiles,
348
597
  lockfiles: new Set(),
349
598
  });
350
599
  if (here.framework !== Framework.HTML)
351
600
  return null; // this directory IS the app
352
601
  const apps = findWorkspaceApps(io);
353
- const target = apps.length === 1 ? apps[0] : undefined;
602
+ // An explicitly named app answers the ambiguity. Refusing to guess is right, but "re-run inside the
603
+ // one you want" is not something a script, a CI step, or an agent that cannot change directory can
604
+ // act on — so the refusal was a dead end for exactly the callers most likely to hit it.
605
+ const chosen = chooseWorkspaceApp(options.app, apps);
606
+ if (!chosen.ok) {
607
+ io.print(chosen.message);
608
+ return { ok: false, applied: 0, manual: 1 };
609
+ }
610
+ const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
354
611
  if (target === undefined) {
355
- if (apps.length === 0)
612
+ if (0 === apps.length)
356
613
  return null; // not a workspace — fall through to the normal HTML plan
357
614
  io.print(AMBIGUOUS_HEADER);
358
615
  for (const a of apps)
359
616
  io.print(` ${a}`);
617
+ io.print('');
618
+ io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
360
619
  return { ok: false, applied: 0, manual: apps.length };
361
620
  }
362
621
  io.print(`No app in this directory — wiring ${target} instead.`);
@@ -364,30 +623,62 @@ function redirectToWorkspaceApp(options, io, pkgRaw) {
364
623
  return runInit({ ...options, cwd: join(options.cwd, target), redirected: true }, io.scoped(target));
365
624
  }
366
625
  export function runInit(options, io) {
626
+ const result = runInitSteps(options, io);
627
+ // The ask goes here, not in report(): report() is only the success-shaped path, and the exits that
628
+ // matter most are the ones that never reach it — no package.json, an ambiguous workspace — where
629
+ // setup died before anything ran and the person holding the report has the least to go on. This
630
+ // wrapper is the one point every exit passes through.
631
+ //
632
+ // `redirected` is what keeps it to ONE print: wiring an app in a monorepo re-enters runInit for the
633
+ // chosen directory, and the inner call must not ask again.
634
+ if (true !== options.redirected) {
635
+ io.print('');
636
+ io.print(FEEDBACK_HINT);
637
+ }
638
+ return result;
639
+ }
640
+ function runInitSteps(options, io) {
367
641
  const pkgRaw = io.readFile(PACKAGE_JSON);
368
- if (pkgRaw === null) {
369
- io.print('No package.json found. Run `reticle init` from your project root.');
642
+ // Look for the app BEFORE concluding there isn't one.
643
+ //
644
+ // A root with no package.json is not a dead end — it is the ordinary shape of a repo whose app
645
+ // lives one directory down (`frontend/`, `web/`, `client/`) with no manifest at the top. Bailing
646
+ // first made that repo un-instrumentable: `reticle init` said "No package.json found", and
647
+ // `--app frontend`, the flag that exists for exactly this, was never read because the bail came
648
+ // first. Reported twice by the same user, who tried the documented workaround and hit the same
649
+ // wall. Discovery already handles the case (it scans top-level directories, not just declared
650
+ // workspaces); it was simply unreachable.
651
+ //
652
+ // `'{}'` because the redirect only needs the manifest to ask "is THIS directory the app", and a
653
+ // directory with no package.json is definitively not.
654
+ const redirectedEarly = redirectToWorkspaceApp(options, io, pkgRaw ?? '{}');
655
+ if (redirectedEarly !== null)
656
+ return redirectedEarly;
657
+ if (null === pkgRaw) {
658
+ io.print('No package.json found here, and no app directory beneath it either. Run `reticle init` from ' +
659
+ "your app's directory, or from a repo root that contains it.");
370
660
  // The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
371
661
  // from someone who never ran the command — the two failure modes with the most different fixes.
372
662
  reportInitOutcome({ ok: false, reason: InitFailure.NO_PACKAGE_JSON });
373
663
  return { ok: false, applied: 0, manual: 0 };
374
664
  }
375
- const redirected = redirectToWorkspaceApp(options, io, pkgRaw);
376
- if (redirected !== null)
377
- return redirected;
378
- const plan = buildPlan(gatherPlanInput(options, io, pkgRaw));
665
+ // Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
666
+ // 1–6s per app with no explanation of the spread. These three spans split that number into detect
667
+ // (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
668
+ const plan = spanSync('init.plan', {}, () => buildPlan(gatherPlanInput(options, io, pkgRaw)));
379
669
  const effects = options.dryRun
380
- ? { failed: new Set(), skipped: new Set() }
381
- : applyEffects(plan, io);
382
- const { failed, skipped } = effects;
383
- const result = report(plan, options.dryRun, failed, skipped, io);
670
+ ? { failed: new Set(), skipped: new Set(), degraded: new Map() }
671
+ : spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
672
+ const { failed, skipped, degraded } = effects;
673
+ const result = report(plan, options.dryRun, failed, skipped, degraded, io, options.cwd);
384
674
  // A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
385
675
  if (!options.dryRun) {
386
676
  reportInitOutcome({
387
677
  ok: result.ok,
388
678
  ...(result.ok ? {} : { reason: classifyInitFailure(failed) }),
389
679
  stack: plan.framework,
390
- mcpRegistered: !failed.has(MCP_TARGET),
680
+ // The step's REAL final status, not the absence of a failure — see mcp-registered.
681
+ mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
391
682
  });
392
683
  }
393
684
  return result;
@@ -15,4 +15,13 @@
15
15
  export declare const CLAUDE_COMMAND_PATH = ".claude/commands/reticle.md";
16
16
  /** Cursor reads them from `.cursor/commands/<name>.md`. */
17
17
  export declare const CURSOR_COMMAND_PATH = ".cursor/commands/reticle.md";
18
+ /**
19
+ * The frontmatter line that marks a command file as OURS.
20
+ *
21
+ * A stale command has to be refreshable — a file frozen at whatever release created it can never be
22
+ * improved for anyone who already ran init — but the path is one a human may have claimed for their
23
+ * own `/reticle`, and overwriting that would be destroying somebody's work. This line is how the two
24
+ * are told apart.
25
+ */
26
+ export declare const SLASH_COMMAND_SIGNATURE = "description: Verify this app in the browser with Reticle";
18
27
  export declare const SLASH_COMMAND_BODY = "---\ndescription: Verify this app in the browser with Reticle \u2014 drive one real flow and report what happened.\n---\n\nVerify this running app with Reticle. Drive it; do not read the code and guess.\n\n## Pick ONE flow\n\nNot the whole app. Pick the single most important flow you can complete in a handful of steps \u2014 the\none a user would do first (sign in, search, add to cart, submit the form). If the user named a flow or\njust changed some code, use that one instead.\n\nState which flow you picked in one line before you start.\n\n## Drive it where the human can see\n\n1. `reticle_sessions` \u2014 find the connected tab. None? Tell the user to run their dev server and open\n the app, then stop. Reticle never starts a dev server.\n2. `reticle_snapshot` \u2014 read the accessibility tree and locate the elements the flow needs. Elements\n are addressable by role and name; you do **not** need `data-testid` to drive them.\n3. Walk the flow with `reticle_act_and_wait`, one step at a time. Narrate each step first \u2014 the human\n is watching the page, and the HUD shows what you say.\n4. After each step, check the effect with `reticle_assert` \u2014 not just that the click dispatched, but\n that the thing it was supposed to do actually happened.\n5. Finish with `reticle_console` and `reticle_network` to catch errors the DOM does not show.\n\n## Report\n\n- What you drove, step by step, and what each step actually produced.\n- Anything broken, with the `file:line` Reticle gave you.\n- If a step could not be verified, say so plainly. \"Unknown\" is a real answer; a green verdict you\n cannot back is not.\n\nDo not weaken or skip an assertion to make the run pass \u2014 that is a finding, not a fix.\n";
@@ -15,8 +15,17 @@
15
15
  export const CLAUDE_COMMAND_PATH = '.claude/commands/reticle.md';
16
16
  /** Cursor reads them from `.cursor/commands/<name>.md`. */
17
17
  export const CURSOR_COMMAND_PATH = '.cursor/commands/reticle.md';
18
+ /**
19
+ * The frontmatter line that marks a command file as OURS.
20
+ *
21
+ * A stale command has to be refreshable — a file frozen at whatever release created it can never be
22
+ * improved for anyone who already ran init — but the path is one a human may have claimed for their
23
+ * own `/reticle`, and overwriting that would be destroying somebody's work. This line is how the two
24
+ * are told apart.
25
+ */
26
+ export const SLASH_COMMAND_SIGNATURE = 'description: Verify this app in the browser with Reticle';
18
27
  export const SLASH_COMMAND_BODY = `---
19
- description: Verify this app in the browser with Reticle — drive one real flow and report what happened.
28
+ ${SLASH_COMMAND_SIGNATURE} — drive one real flow and report what happened.
20
29
  ---
21
30
 
22
31
  Verify this running app with Reticle. Drive it; do not read the code and guess.