@reticlehq/server 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
package/dist/init/run.js CHANGED
@@ -4,11 +4,51 @@
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 { spanSync } from '../trace.js';
8
+ import { readFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
7
10
  import { detect, Framework } 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 { 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
+ }
8
43
  import { DEPS_TARGET, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
9
44
  import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
45
+ import { reticleDevLocation } from './next-patch.js';
46
+ import { scanTestids, storeHints } from './capabilities.js';
10
47
  import { CURSOR_DIR_RELPATH, CURSOR_MCP_RELPATH } from './cursor.js';
11
48
  import { deriveProjectId, packageName } from './project-id.js';
49
+ import { VITE_DEV_MODULE_PATH } from './snippets.js';
50
+ import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
51
+ import { SERVER_VERSION } from '../version/server-version.js';
12
52
  import { InitFailure, reportInitOutcome } from '../telemetry/init-telemetry.js';
13
53
  /** Lockfile basenames, in package-manager preference order (mirrors detect.ts). */
14
54
  const LOCKFILE_NAMES = [
@@ -43,25 +83,108 @@ export function resolveLockfiles(rootFiles, cwd, io) {
43
83
  return set;
44
84
  }
45
85
  const PACKAGE_JSON = 'package.json';
46
- const NEXT_RETICLE_DEV = 'app/reticle-dev.tsx';
86
+ /**
87
+ * Root-layout candidates, App Router only. `--src-dir` apps keep theirs under `src/app`, and the
88
+ * ReticleDev component has to land NEXT TO the layout or the relative import it generates is dead.
89
+ */
90
+ const NEXT_LAYOUT_CANDIDATES = [
91
+ 'app/layout.tsx',
92
+ 'app/layout.jsx',
93
+ 'app/layout.js',
94
+ 'src/app/layout.tsx',
95
+ 'src/app/layout.jsx',
96
+ 'src/app/layout.js',
97
+ ];
98
+ /**
99
+ * Pages Router mount points, checked only when there is no App Router layout. A Pages app has no
100
+ * `app/` directory at all, so writing the component there produced a file nothing imported.
101
+ */
102
+ const NEXT_PAGES_APP_CANDIDATES = [
103
+ 'pages/_app.tsx',
104
+ 'pages/_app.jsx',
105
+ 'pages/_app.js',
106
+ 'src/pages/_app.tsx',
107
+ 'src/pages/_app.jsx',
108
+ 'src/pages/_app.js',
109
+ ];
47
110
  const SVELTEKIT_HOOKS = 'src/hooks.client.ts';
111
+ /** Where an app's components live. Bounded on purpose — this is a hint, not an index of the repo. */
112
+ const SOURCE_DIRS = ['src', 'src/components', 'src/pages', 'app', 'components'];
113
+ const SOURCE_FILE = /\.(tsx|jsx|ts|js|svelte|vue|astro)$/;
114
+ /** Files read for the testid scan. A capabilities block is a hint; reading a whole repo for it is not. */
115
+ const MAX_SCANNED_FILES = 200;
116
+ /** Read a bounded set of the app's source files, for the `data-testid` scan. */
117
+ function readSourceFiles(io) {
118
+ const out = [];
119
+ for (const dir of SOURCE_DIRS) {
120
+ for (const name of io.listFiles(dir)) {
121
+ if (!SOURCE_FILE.test(name))
122
+ continue;
123
+ const content = io.readFile(`${dir}/${name}`);
124
+ if (content !== null)
125
+ out.push(content);
126
+ if (out.length >= MAX_SCANNED_FILES)
127
+ return out;
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+ /** Direct dependency names, for naming the state libraries an app actually has. */
133
+ function dependencyNames(pkg) {
134
+ const p = (pkg ?? {});
135
+ return new Set([
136
+ ...Object.keys(p['dependencies'] ?? {}),
137
+ ...Object.keys(p['devDependencies'] ?? {}),
138
+ ]);
139
+ }
48
140
  const VITE_CONFIG_CANDIDATES = [
49
141
  'vite.config.ts',
50
142
  'vite.config.js',
51
143
  'vite.config.mjs',
52
144
  'vite.config.mts',
53
145
  ];
146
+ const ASTRO_CONFIG_CANDIDATES = [
147
+ 'astro.config.mjs',
148
+ 'astro.config.js',
149
+ 'astro.config.ts',
150
+ 'astro.config.cjs',
151
+ ];
152
+ /** Where a conventional Astro app keeps the layout every page wraps itself in. */
153
+ const ASTRO_LAYOUTS_DIR = 'src/layouts';
154
+ /** Also searched: an app with no layouts directory renders the document straight from a page. */
155
+ const ASTRO_PAGES_DIR = 'src/pages';
54
156
  const NEXT_CONFIG_CANDIDATES = [
55
157
  'next.config.mjs',
56
158
  'next.config.js',
57
159
  'next.config.ts',
58
160
  'next.config.cjs',
59
161
  ];
162
+ /**
163
+ * `⚠` means WORK LEFT TO DO and nothing else — it is what an agent (and the release gate) counts to
164
+ * decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
165
+ * on a working install that happens to be on an ungated stack.
166
+ */
167
+ /**
168
+ * A step's status AFTER the run, which is what actually happened to it.
169
+ *
170
+ * `report` applies the same downgrade when printing: a step that failed or was skipped is shown as
171
+ * MANUAL whatever it planned to be. Reading the planned status alone would say a step applied when
172
+ * the run had already given up on it.
173
+ */
174
+ function resolvedStatus(plan, target, failed, skipped) {
175
+ const step = plan.steps.find((s) => s.target === target);
176
+ if (step === undefined)
177
+ return undefined;
178
+ if (failed.has(target) || skipped.has(target))
179
+ return StepStatus.MANUAL;
180
+ return step.status;
181
+ }
60
182
  const STATUS_SYMBOL = {
61
183
  [StepStatus.APPLY]: '✓',
62
184
  [StepStatus.MANUAL]: '⚠',
63
185
  [StepStatus.ALREADY]: '·',
64
186
  [StepStatus.SKIP]: '–',
187
+ [StepStatus.NOTICE]: 'ℹ',
65
188
  };
66
189
  function firstPresent(files, candidates) {
67
190
  for (const c of candidates)
@@ -75,14 +198,16 @@ function gatherPlanInput(options, io, pkgRaw) {
75
198
  const projectId = deriveProjectId(packageName(pkg), options.cwd);
76
199
  const rootFiles = new Set(io.rootFiles());
77
200
  const detectInput = {
78
- pkg: typeof pkg === 'object' && pkg !== null ? pkg : {},
201
+ pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
79
202
  configFiles: rootFiles,
80
203
  // Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager.
81
204
  lockfiles: resolveLockfiles(rootFiles, options.cwd, io),
205
+ // An already-installed tree names its own manager, which matters when no lockfile is committed.
206
+ nodeModulesMarkers: new Set(io.listFiles('node_modules')),
82
207
  };
83
208
  const detection = detect(detectInput);
84
209
  const vitePath = firstPresent(rootFiles, VITE_CONFIG_CANDIDATES);
85
- const viteSource = vitePath === null ? null : io.readFile(vitePath);
210
+ const viteSource = null === vitePath ? null : io.readFile(vitePath);
86
211
  const viteConfig = vitePath !== null && viteSource !== null ? { path: vitePath, source: viteSource } : null;
87
212
  // Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
88
213
  // global config file. Only probe when the MCP step is in play.
@@ -90,55 +215,195 @@ function gatherPlanInput(options, io, pkgRaw) {
90
215
  const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
91
216
  const existsProbe = claudeExistsProbe();
92
217
  const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
93
- const cursorDir = `${io.homeDir()}/${CURSOR_DIR_RELPATH}`;
94
- const cursorConfigPath = `${io.homeDir()}/${CURSOR_MCP_RELPATH}`;
95
- const cursorPresent = options.mcp && io.exists(cursorDir);
218
+ // join(), not a literal '/': this path is checked and WRITTEN on the user's machine, and a
219
+ // backslash platform silently misses the config it is meant to find.
220
+ const cursorDir = join(io.homeDir(), CURSOR_DIR_RELPATH);
221
+ const cursorConfigPath = join(io.homeDir(), CURSOR_MCP_RELPATH);
222
+ // Cursor is "present" if its global config directory exists OR the project carries a `.cursor/`.
223
+ // The directory alone missed a real case: Cursor installed with a FRESH profile has not written
224
+ // ~/.cursor yet, so a user who had it open got no Cursor step and no message about one. A
225
+ // project-level .cursor/ is unambiguous evidence somebody uses it here.
226
+ const cursorPresent = options.mcp && (io.exists(cursorDir) || io.exists(CURSOR_DIR_RELPATH));
96
227
  const cursorConfig = cursorPresent ? io.readFile(cursorConfigPath) : null;
228
+ const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
229
+ const astroSource = null === astroPath ? null : io.readFile(astroPath);
230
+ // Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
231
+ // .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
232
+ // renders from src/pages/index.astro, the other has three files there of which two are partials.
233
+ // `</body>` is the discriminator — see astro-host.
234
+ const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
235
+ .listFiles(dir)
236
+ .filter((f) => f.endsWith('.astro'))
237
+ .map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
238
+ .filter((c) => c.source !== null));
239
+ const astroHost = pickAstroHost(astroCandidates);
240
+ const layoutRelPath = astroHost?.path ?? null;
241
+ const astroLayoutSource = astroHost?.source ?? null;
242
+ const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
243
+ // App Router first; a Pages Router app has no layout, and its mount point is pages/_app.
244
+ const layoutPath = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
245
+ NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ??
246
+ null;
247
+ const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
248
+ // Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
249
+ // component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
250
+ const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
97
251
  return {
98
252
  detection,
99
253
  claudeCli,
100
254
  mcpExists,
101
255
  cursorPresent,
256
+ cursorProjectPresent: io.exists(CURSOR_DIR_RELPATH),
102
257
  cursorConfig,
103
258
  cursorConfigPath,
104
259
  viteConfig,
105
- nextConfigFile: firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES),
106
- nextReticleDevExists: io.exists(NEXT_RETICLE_DEV),
260
+ astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
261
+ astroLayout: layoutRelPath !== null && astroLayoutSource !== null
262
+ ? { path: layoutRelPath, source: astroLayoutSource }
263
+ : null,
264
+ nextConfigFile,
265
+ nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
266
+ nextLayout: layoutPath !== null && layoutSource !== null
267
+ ? { path: layoutPath, source: layoutSource }
268
+ : null,
269
+ // Capabilities: scanned, never asked for. Bounded — a hint for the agent, not a repo index.
270
+ testids: scanTestids(readSourceFiles(io)),
271
+ storeHints: storeHints(dependencyNames(pkg)),
272
+ viteDevModuleExists: io.exists(VITE_DEV_MODULE_PATH),
273
+ nextReticleDevPath: devLocation.path,
274
+ nextReticleDevImport: devLocation.importSpecifier,
275
+ nextReticleDevExists: io.exists(devLocation.path),
107
276
  svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
277
+ craEntry: craEntryOf(io),
278
+ craEnv: io.readFile(CRA_ENV_PATH),
279
+ pairingToken: readPairingToken(),
108
280
  reticleConfigExists: io.exists('.reticle.json'),
109
281
  // Read the agent instruction files so the rule merge stays idempotent across re-runs.
110
282
  claudeMdContent: io.readFile('CLAUDE.md'),
111
283
  agentsMdContent: io.readFile('AGENTS.md'),
112
- cursorRuleExists: io.exists('.cursor/rules/reticle.mdc'),
113
- options: { port: options.port, mcp: options.mcp, install: options.install, projectId },
284
+ cursorRuleContent: io.readFile(CURSOR_RULE_PATH),
285
+ claudeCommandContent: io.readFile(CLAUDE_COMMAND_PATH),
286
+ cursorCommandContent: io.readFile(CURSOR_COMMAND_PATH),
287
+ options: {
288
+ port: options.port,
289
+ mcp: options.mcp,
290
+ install: options.install,
291
+ projectId,
292
+ // The SDK must match the CLI asking for it — see pinnedPackages.
293
+ sdkVersion: SERVER_VERSION,
294
+ },
114
295
  };
115
296
  }
297
+ /** Where workspace tooling conventionally puts packages. */
298
+ /** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
299
+ const PNPM_WORKSPACE = 'pnpm-workspace.yaml';
300
+ /** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
301
+ const APP_DEPS = ['next', 'vite'];
302
+ function looksLikeApp(dir, io) {
303
+ const pkgRaw = io.readFile(`${dir}/${PACKAGE_JSON}`);
304
+ if (null === pkgRaw)
305
+ return false;
306
+ const configs = [...VITE_CONFIG_CANDIDATES, ...NEXT_CONFIG_CANDIDATES];
307
+ if (configs.some((c) => io.exists(`${dir}/${c}`)))
308
+ return true;
309
+ // `next.config` is optional in Next, so the dependency list is the other half of the signal.
310
+ return APP_DEPS.some((d) => pkgRaw.includes(`"${d}"`));
311
+ }
312
+ /**
313
+ * App directories under a workspace root.
314
+ *
315
+ * Running `reticle init` at the repo root is what people actually do, and in a monorepo the app is a
316
+ * directory down — so init detected "no framework", printed a wall of manual HTML instructions, and
317
+ * would have installed the SDK into the ROOT package.json. It already walks UP for the lockfile, so
318
+ * it knows it is in a workspace; this is the matching walk DOWN.
319
+ */
320
+ export function findWorkspaceApps(io) {
321
+ const found = [];
322
+ // A workspace DECLARES its packages; `['apps','packages']` was a guess that missed a real repo
323
+ // with three Next apps at web/, admin/ and space/ — and missing them meant init ran against the
324
+ // root and reported ✓ for a file Next never compiles. See workspace-apps.
325
+ const pkgRaw = io.readFile(PACKAGE_JSON);
326
+ let pkgWorkspaces;
327
+ try {
328
+ const parsed = null === pkgRaw ? undefined : JSON.parse(pkgRaw);
329
+ pkgWorkspaces =
330
+ 'object' === typeof parsed && parsed !== null
331
+ ? parsed.workspaces
332
+ : undefined;
333
+ }
334
+ catch {
335
+ pkgWorkspaces = undefined;
336
+ }
337
+ const parents = workspaceParents({
338
+ ...(null === io.readFile(PNPM_WORKSPACE)
339
+ ? {}
340
+ : { pnpmWorkspace: io.readFile(PNPM_WORKSPACE) ?? '' }),
341
+ ...(pkgWorkspaces === undefined ? {} : { pkgWorkspaces }),
342
+ topLevelDirs: io.listDirs('.'),
343
+ });
344
+ // A declared parent can itself BE the app (`workspaces: ["web"]`), so check both the directory and
345
+ // its children rather than assuming one level of nesting.
346
+ for (const parent of parents) {
347
+ if (looksLikeApp(parent, io))
348
+ found.push(parent);
349
+ for (const name of io.listDirs(parent)) {
350
+ const dir = `${parent}/${name}`;
351
+ if (looksLikeApp(dir, io))
352
+ found.push(dir);
353
+ }
354
+ }
355
+ return [...new Set(found)];
356
+ }
116
357
  function restartHint(framework) {
117
358
  if (framework === Framework.NEXT)
118
359
  return 'Restart `next dev`, then ask your agent: "List Reticle sessions".';
119
360
  if (framework === Framework.VITE)
120
361
  return 'Restart `vite`, then ask your agent: "List Reticle sessions".';
362
+ if (framework === Framework.ASTRO)
363
+ return 'Restart `astro dev`, then ask your agent: "List Reticle sessions".';
121
364
  if (framework === Framework.SVELTEKIT)
122
365
  return 'Restart your dev server (`npm run dev`), then ask your agent: "List Reticle sessions".';
123
366
  return 'Reload your app on localhost, then ask your agent: "List Reticle sessions".';
124
367
  }
125
- function report(plan, dryRun, failed, io) {
368
+ const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
369
+ 'installed stops it booting. Run that install, then re-run `reticle init`.';
370
+ function report(plan, dryRun, failed, skipped, degraded, io) {
126
371
  io.print(dryRun ? 'reticle init (dry run — no files written)' : 'reticle init');
127
372
  io.print('');
128
373
  let applied = 0;
129
374
  let manual = 0;
375
+ // A ⚠ on a CONNECT step is a guaranteed failure, not a caveat: nothing performs the manual step, so
376
+ // the app never dials the daemon and every tool answers "no browser session connected". `ok` was
377
+ // hardcoded true, so a run that could not possibly work reported success.
378
+ let connectPending = false;
130
379
  for (const s of plan.steps) {
131
380
  // A side effect that failed to apply is reported as a manual step with its fallback command.
132
- const downgraded = failed.has(s.target);
381
+ const note = degraded.get(s.target);
382
+ if (note !== undefined) {
383
+ // Applied, but not the way it was asked for. A NOTICE, not work — the install did happen.
384
+ io.print(` [${STATUS_SYMBOL[StepStatus.NOTICE]}] ${s.title} → ${s.target}`);
385
+ for (const line of note.split('\n'))
386
+ io.print(` ${line}`);
387
+ applied++;
388
+ continue;
389
+ }
390
+ const downgraded = failed.has(s.target) || skipped.has(s.target);
133
391
  const status = downgraded ? StepStatus.MANUAL : s.status;
134
- const detail = downgraded && s.exec !== undefined
135
- ? `step failed — run manually: ${s.exec.fallback}`
136
- : s.detail;
392
+ const detail = skipped.has(s.target)
393
+ ? SKIPPED_DETAIL
394
+ : downgraded && s.exec !== undefined
395
+ ? `step failed — run manually: ${s.exec.fallback}`
396
+ : s.detail;
137
397
  io.print(` [${STATUS_SYMBOL[status]}] ${s.title} → ${s.target}`);
138
398
  if (status === StepStatus.APPLY)
139
399
  applied++;
140
- if (status === StepStatus.MANUAL) {
141
- manual++;
400
+ if (status === StepStatus.MANUAL || status === StepStatus.NOTICE) {
401
+ // A notice prints in full like a manual step — it is worth reading — but is NOT counted as work.
402
+ if (status === StepStatus.MANUAL) {
403
+ manual++;
404
+ if (isConnectStep(s.title))
405
+ connectPending = true;
406
+ }
142
407
  for (const line of detail.split('\n'))
143
408
  io.print(` ${line}`);
144
409
  }
@@ -147,21 +412,68 @@ function report(plan, dryRun, failed, io) {
147
412
  }
148
413
  }
149
414
  io.print('');
415
+ if (connectPending) {
416
+ io.print('This app will NOT connect until the ⚠ step above is done by hand — Reticle tools will report ' +
417
+ '"no browser session connected" until then. Everything else is already in place.');
418
+ io.print('');
419
+ }
150
420
  io.print(restartHint(plan.framework));
151
- return { ok: true, applied, manual };
421
+ return { ok: !connectPending, applied, manual };
152
422
  }
153
- /** Perform the apply-step side effects; return the targets whose side effect failed. */
423
+ /**
424
+ * Perform the apply-step side effects; return the targets whose side effect failed.
425
+ *
426
+ * Steps run in plan order, which puts the dependency install BEFORE everything that imports what it
427
+ * installs. If it fails, the wiring is skipped rather than applied: patching `next.config.ts` to
428
+ * import a `@reticlehq/next` that was never installed takes the dev server down with
429
+ * MODULE_NOT_FOUND, so the app stops booting *because* Reticle was installed. A skipped step is a
430
+ * message; a half-wired app is a broken project.
431
+ */
154
432
  function applyEffects(plan, io) {
155
433
  const failed = new Set();
434
+ const skipped = new Set();
435
+ const degraded = new Map();
436
+ let installFailed = false;
156
437
  for (const s of plan.steps) {
157
438
  if (s.status !== StepStatus.APPLY)
158
439
  continue;
159
- if (s.write !== undefined)
160
- io.writeFile(s.write.path, s.write.content);
161
- if (s.exec !== undefined && !io.exec(s.exec.command, s.exec.args))
440
+ if (installFailed && true === s.dependsOnInstall) {
441
+ skipped.add(s.target);
442
+ continue;
443
+ }
444
+ const write = s.write;
445
+ if (write !== undefined) {
446
+ spanSync('init.write', { target: s.target, path: write.path }, () => {
447
+ io.writeFile(write.path, write.content);
448
+ });
449
+ }
450
+ // Bound once so the traced call cannot need a `?? ''` fallback — a default there would turn a
451
+ // narrowing mistake into an empty command that silently "succeeds".
452
+ const exec = s.exec;
453
+ // Per-step, because the interesting part of init's wall-clock is WHICH step spent it: a
454
+ // package-manager install and a `claude mcp add` are both subprocesses, and one of them being
455
+ // slow is a completely different problem from the other.
456
+ if (exec !== undefined &&
457
+ !spanSync('init.exec', { target: s.target, command: exec.command }, () => io.exec(exec.command, exec.args))) {
458
+ // A weaker second attempt beats no install at all — but only when it is REPORTED, because the
459
+ // thing it gives up is the version pin that keeps SDK and daemon in step.
460
+ //
461
+ // Traced separately, and it is the reason init can take twice as long as it looks like it
462
+ // should: this is a SECOND full package-manager run, and until it had its own span 1.6 of
463
+ // init's 2.3 seconds simply vanished — the span above accounted for the first attempt and
464
+ // nothing accounted for this one.
465
+ const retry = s.retry;
466
+ if (retry !== undefined &&
467
+ spanSync('init.exec.retry', { target: s.target, command: retry.command }, () => io.exec(retry.command, retry.args))) {
468
+ degraded.set(s.target, retry.note);
469
+ continue;
470
+ }
162
471
  failed.add(s.target);
472
+ if (s.target === DEPS_TARGET)
473
+ installFailed = true;
474
+ }
163
475
  }
164
- return failed;
476
+ return { failed, skipped, degraded };
165
477
  }
166
478
  /**
167
479
  * Which STEP failed, from the step targets — not from an error string, which would carry paths.
@@ -178,25 +490,78 @@ function classifyInitFailure(failed) {
178
490
  return InitFailure.MCP_REGISTRATION;
179
491
  return InitFailure.OTHER;
180
492
  }
493
+ const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle init` inside the one you want:';
494
+ /**
495
+ * When the current directory is a workspace root with no app of its own, wire the app instead of the
496
+ * root. One candidate is wired silently (there is nothing to ask about); several are listed, because
497
+ * guessing which app someone meant is worse than one line of output.
498
+ * Returns null when there is nothing to redirect to — the caller then proceeds here as before.
499
+ */
500
+ function redirectToWorkspaceApp(options, io, pkgRaw) {
501
+ if (true === options.redirected)
502
+ return null;
503
+ const pkg = JSON.parse(pkgRaw);
504
+ const rootFiles = new Set(io.rootFiles());
505
+ const here = detect({
506
+ pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
507
+ configFiles: rootFiles,
508
+ lockfiles: new Set(),
509
+ });
510
+ if (here.framework !== Framework.HTML)
511
+ return null; // this directory IS the app
512
+ const apps = findWorkspaceApps(io);
513
+ // An explicitly named app answers the ambiguity. Refusing to guess is right, but "re-run inside the
514
+ // one you want" is not something a script, a CI step, or an agent that cannot change directory can
515
+ // act on — so the refusal was a dead end for exactly the callers most likely to hit it.
516
+ const chosen = chooseWorkspaceApp(options.app, apps);
517
+ if (!chosen.ok) {
518
+ io.print(chosen.message);
519
+ return { ok: false, applied: 0, manual: 1 };
520
+ }
521
+ const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
522
+ if (target === undefined) {
523
+ if (0 === apps.length)
524
+ return null; // not a workspace — fall through to the normal HTML plan
525
+ io.print(AMBIGUOUS_HEADER);
526
+ for (const a of apps)
527
+ io.print(` ${a}`);
528
+ io.print('');
529
+ io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
530
+ return { ok: false, applied: 0, manual: apps.length };
531
+ }
532
+ io.print(`No app in this directory — wiring ${target} instead.`);
533
+ io.print('');
534
+ return runInit({ ...options, cwd: join(options.cwd, target), redirected: true }, io.scoped(target));
535
+ }
181
536
  export function runInit(options, io) {
182
537
  const pkgRaw = io.readFile(PACKAGE_JSON);
183
- if (pkgRaw === null) {
538
+ if (null === pkgRaw) {
184
539
  io.print('No package.json found. Run `reticle init` from your project root.');
185
540
  // The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
186
541
  // from someone who never ran the command — the two failure modes with the most different fixes.
187
542
  reportInitOutcome({ ok: false, reason: InitFailure.NO_PACKAGE_JSON });
188
543
  return { ok: false, applied: 0, manual: 0 };
189
544
  }
190
- const plan = buildPlan(gatherPlanInput(options, io, pkgRaw));
191
- const failed = options.dryRun ? new Set() : applyEffects(plan, io);
192
- const result = report(plan, options.dryRun, failed, io);
545
+ const redirected = redirectToWorkspaceApp(options, io, pkgRaw);
546
+ if (redirected !== null)
547
+ return redirected;
548
+ // Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
549
+ // 1–6s per app with no explanation of the spread. These three spans split that number into detect
550
+ // (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
551
+ const plan = spanSync('init.plan', {}, () => buildPlan(gatherPlanInput(options, io, pkgRaw)));
552
+ const effects = options.dryRun
553
+ ? { failed: new Set(), skipped: new Set(), degraded: new Map() }
554
+ : spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
555
+ const { failed, skipped, degraded } = effects;
556
+ const result = report(plan, options.dryRun, failed, skipped, degraded, io);
193
557
  // A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
194
558
  if (!options.dryRun) {
195
559
  reportInitOutcome({
196
560
  ok: result.ok,
197
561
  ...(result.ok ? {} : { reason: classifyInitFailure(failed) }),
198
562
  stack: plan.framework,
199
- mcpRegistered: !failed.has(MCP_TARGET),
563
+ // The step's REAL final status, not the absence of a failure — see mcp-registered.
564
+ mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
200
565
  });
201
566
  }
202
567
  return result;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The `/reticle` slash command — the entry point the docs have been promising and nothing created.
3
+ *
4
+ * SKILL.md tells the user "Type `/reticle` anytime to verify the app" in three places, and `init`
5
+ * never wrote the file that makes it exist. So the one obvious way in was a command that silently did
6
+ * nothing in every tool. Claude Code and Cursor both read custom commands from a markdown file per
7
+ * command; the body is the prompt the agent receives.
8
+ *
9
+ * Deliberately SCOPED to one flow. A user installing Reticle has an existing app with many flows, and
10
+ * an agent told to "verify the app" will try to instrument all of them — slow, noisy, and nothing to
11
+ * see for minutes. One flow, driven where the human can watch it, is the whole difference between a
12
+ * two-minute first impression and an afternoon.
13
+ */
14
+ /** Claude Code reads project commands from `.claude/commands/<name>.md`. */
15
+ export declare const CLAUDE_COMMAND_PATH = ".claude/commands/reticle.md";
16
+ /** Cursor reads them from `.cursor/commands/<name>.md`. */
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";
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";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The `/reticle` slash command — the entry point the docs have been promising and nothing created.
3
+ *
4
+ * SKILL.md tells the user "Type `/reticle` anytime to verify the app" in three places, and `init`
5
+ * never wrote the file that makes it exist. So the one obvious way in was a command that silently did
6
+ * nothing in every tool. Claude Code and Cursor both read custom commands from a markdown file per
7
+ * command; the body is the prompt the agent receives.
8
+ *
9
+ * Deliberately SCOPED to one flow. A user installing Reticle has an existing app with many flows, and
10
+ * an agent told to "verify the app" will try to instrument all of them — slow, noisy, and nothing to
11
+ * see for minutes. One flow, driven where the human can watch it, is the whole difference between a
12
+ * two-minute first impression and an afternoon.
13
+ */
14
+ /** Claude Code reads project commands from `.claude/commands/<name>.md`. */
15
+ export const CLAUDE_COMMAND_PATH = '.claude/commands/reticle.md';
16
+ /** Cursor reads them from `.cursor/commands/<name>.md`. */
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';
27
+ export const SLASH_COMMAND_BODY = `---
28
+ ${SLASH_COMMAND_SIGNATURE} — drive one real flow and report what happened.
29
+ ---
30
+
31
+ Verify this running app with Reticle. Drive it; do not read the code and guess.
32
+
33
+ ## Pick ONE flow
34
+
35
+ Not the whole app. Pick the single most important flow you can complete in a handful of steps — the
36
+ one a user would do first (sign in, search, add to cart, submit the form). If the user named a flow or
37
+ just changed some code, use that one instead.
38
+
39
+ State which flow you picked in one line before you start.
40
+
41
+ ## Drive it where the human can see
42
+
43
+ 1. \`reticle_sessions\` — find the connected tab. None? Tell the user to run their dev server and open
44
+ the app, then stop. Reticle never starts a dev server.
45
+ 2. \`reticle_snapshot\` — read the accessibility tree and locate the elements the flow needs. Elements
46
+ are addressable by role and name; you do **not** need \`data-testid\` to drive them.
47
+ 3. Walk the flow with \`reticle_act_and_wait\`, one step at a time. Narrate each step first — the human
48
+ is watching the page, and the HUD shows what you say.
49
+ 4. After each step, check the effect with \`reticle_assert\` — not just that the click dispatched, but
50
+ that the thing it was supposed to do actually happened.
51
+ 5. Finish with \`reticle_console\` and \`reticle_network\` to catch errors the DOM does not show.
52
+
53
+ ## Report
54
+
55
+ - What you drove, step by step, and what each step actually produced.
56
+ - Anything broken, with the \`file:line\` Reticle gave you.
57
+ - If a step could not be verified, say so plainly. "Unknown" is a real answer; a green verdict you
58
+ cannot back is not.
59
+
60
+ Do not weaken or skip an assertion to make the run pass — that is a finding, not a fix.
61
+ `;