@reticlehq/server 2.13.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (449) hide show
  1. package/SKILL.md +8 -18
  2. package/bin/reticle.js +3 -0
  3. package/dist/bridge/auth-failure-reason.js +12 -1
  4. package/dist/bridge/bridge-security.d.ts +8 -0
  5. package/dist/bridge/bridge-security.js +6 -1
  6. package/dist/bridge/bridge.d.ts +3 -0
  7. package/dist/bridge/bridge.js +23 -1
  8. package/dist/bridge/bridge.test-harness.d.ts +10 -1
  9. package/dist/bridge/bridge.test-harness.js +7 -1
  10. package/dist/bridge/pairing-token.d.ts +17 -0
  11. package/dist/bridge/pairing-token.js +26 -0
  12. package/dist/bridge/token-auth.d.ts +7 -0
  13. package/dist/bridge/token-auth.js +7 -1
  14. package/dist/capsule/capsule-store.d.ts +4 -4
  15. package/dist/capsule/capsule.d.ts +1 -1
  16. package/dist/capsule/capsule.js +3 -3
  17. package/dist/capsule/causal-summary.d.ts +21 -1
  18. package/dist/capsule/causal-summary.js +8 -6
  19. package/dist/capsule/divergence.d.ts +1 -1
  20. package/dist/capsule/divergence.js +26 -10
  21. package/dist/chromium-launch-options.d.ts +2 -1
  22. package/dist/cli/adhoc-verdict.d.ts +58 -0
  23. package/dist/cli/adhoc-verdict.js +115 -0
  24. package/dist/cli/chromium-hint.d.ts +18 -4
  25. package/dist/cli/chromium-hint.js +54 -6
  26. package/dist/cli/cli-doctor.js +1 -2
  27. package/dist/cli/cli-flow-commands.d.ts +16 -2
  28. package/dist/cli/cli-flow-commands.js +28 -9
  29. package/dist/cli/cli-parse-feedback.js +1 -1
  30. package/dist/cli/cli-parse.d.ts +2 -1
  31. package/dist/cli/cli-parse.js +38 -0
  32. package/dist/cli/cli-port.d.ts +5 -13
  33. package/dist/cli/cli-port.js +5 -38
  34. package/dist/cli/cli-update-commands.js +4 -4
  35. package/dist/cli/cli-verify.d.ts +24 -7
  36. package/dist/cli/cli-verify.js +87 -13
  37. package/dist/cli/cloud-keystore.d.ts +2 -1
  38. package/dist/cli/cloud-login.d.ts +2 -1
  39. package/dist/cli/cloud-session.d.ts +2 -1
  40. package/dist/cli/cloud-session.js +1 -1
  41. package/dist/cli/daemon-start-options.d.ts +2 -1
  42. package/dist/cli/device-flow.d.ts +2 -2
  43. package/dist/cli/doctor-daemon-line.d.ts +2 -1
  44. package/dist/cli/doctor-project-line.d.ts +2 -1
  45. package/dist/cli/doctor-rows.d.ts +2 -1
  46. package/dist/cli/drive-attach.d.ts +4 -3
  47. package/dist/cli/gate-hook-message.d.ts +2 -1
  48. package/dist/cli/status-next-action.js +10 -11
  49. package/dist/cli.d.ts +1 -0
  50. package/dist/cli.js +21 -9
  51. package/dist/cloud/account-state.d.ts +20 -0
  52. package/dist/cloud/account-state.js +65 -0
  53. package/dist/cloud/cloud-sync.d.ts +46 -3
  54. package/dist/cloud/cloud-sync.js +116 -0
  55. package/dist/cloud/sync-cycle.d.ts +5 -4
  56. package/dist/cloud/sync-cycle.js +1 -1
  57. package/dist/cloud/sync-daemon.d.ts +3 -19
  58. package/dist/cloud/sync-daemon.js +2 -2
  59. package/dist/crawl/crawl-tools.js +1 -1
  60. package/dist/crawl/crawl.d.ts +5 -5
  61. package/dist/crawl/crawl.js +138 -4
  62. package/dist/daemon/daemon-resilience.d.ts +3 -2
  63. package/dist/daemon/daemon-resilience.js +43 -4
  64. package/dist/daemon/daemon-resolve.d.ts +0 -9
  65. package/dist/daemon/daemon-resolve.js +2 -2
  66. package/dist/daemon/dev-servers.d.ts +1 -1
  67. package/dist/daemon/dev-servers.js +1 -1
  68. package/dist/daemon/loopback-alias.d.ts +2 -1
  69. package/dist/daemon/startup-failure.d.ts +11 -0
  70. package/dist/daemon/startup-failure.js +46 -0
  71. package/dist/events/contradictions.d.ts +10 -16
  72. package/dist/events/contradictions.js +149 -17
  73. package/dist/events/declared.d.ts +13 -1
  74. package/dist/events/declared.js +59 -3
  75. package/dist/events/echo-mismatch.js +75 -6
  76. package/dist/events/lineage.d.ts +52 -0
  77. package/dist/events/lineage.js +122 -0
  78. package/dist/events/net-evidence.d.ts +21 -0
  79. package/dist/events/net-evidence.js +81 -0
  80. package/dist/events/predicate-console.d.ts +13 -0
  81. package/dist/events/predicate-console.js +138 -0
  82. package/dist/events/predicate-element.d.ts +13 -0
  83. package/dist/events/predicate-element.js +214 -0
  84. package/dist/events/predicate-eval.d.ts +15 -3
  85. package/dist/events/predicate-eval.js +128 -110
  86. package/dist/events/predicate-parse.js +34 -1
  87. package/dist/events/predicate-request-body.d.ts +50 -0
  88. package/dist/events/predicate-request-body.js +120 -0
  89. package/dist/events/predicate-route.d.ts +21 -1
  90. package/dist/events/predicate-route.js +149 -21
  91. package/dist/events/predicate-schema.d.ts +8 -1
  92. package/dist/events/predicate-schema.js +34 -0
  93. package/dist/events/predicate.d.ts +2 -0
  94. package/dist/events/predicate.js +52 -159
  95. package/dist/events/split-text-miss.d.ts +6 -4
  96. package/dist/events/split-text-miss.js +12 -5
  97. package/dist/flows/affected-tools.js +22 -9
  98. package/dist/flows/annotate.d.ts +2 -1
  99. package/dist/flows/annotate.js +19 -1
  100. package/dist/flows/coverage.d.ts +2 -2
  101. package/dist/flows/flake.d.ts +3 -3
  102. package/dist/flows/flow-expect-grammar.d.ts +33 -0
  103. package/dist/flows/flow-expect-grammar.js +155 -0
  104. package/dist/flows/flow-replay-run.d.ts +1 -1
  105. package/dist/flows/flow-replay-run.js +28 -15
  106. package/dist/flows/flow-replay.d.ts +10 -0
  107. package/dist/flows/flow-replay.js +57 -22
  108. package/dist/flows/flow-secret-field.d.ts +14 -0
  109. package/dist/flows/flow-secret-field.js +22 -0
  110. package/dist/flows/flow-step-runners.d.ts +1 -0
  111. package/dist/flows/flow-step-runners.js +91 -34
  112. package/dist/flows/flow-success.js +22 -0
  113. package/dist/flows/flow-tools.js +27 -8
  114. package/dist/flows/flow-upload-resolve.d.ts +23 -0
  115. package/dist/flows/flow-upload-resolve.js +46 -0
  116. package/dist/flows/flows.d.ts +15 -0
  117. package/dist/flows/flows.js +17 -14
  118. package/dist/flows/git-changed.d.ts +23 -4
  119. package/dist/flows/git-changed.js +32 -6
  120. package/dist/flows/heal-run.js +2 -2
  121. package/dist/flows/predicate-to-expect.d.ts +3 -2
  122. package/dist/flows/predicate-to-expect.js +11 -5
  123. package/dist/flows/recording-backtrack.d.ts +24 -0
  124. package/dist/flows/recording-backtrack.js +73 -0
  125. package/dist/flows/recordings.d.ts +6 -0
  126. package/dist/flows/replay-halt.d.ts +26 -0
  127. package/dist/flows/replay-halt.js +30 -0
  128. package/dist/flows/replay.d.ts +0 -8
  129. package/dist/flows/replay.js +15 -3
  130. package/dist/flows/suite-flakes.d.ts +2 -1
  131. package/dist/flows/verify-change-tools.js +16 -4
  132. package/dist/honesty/already-true.d.ts +14 -0
  133. package/dist/honesty/already-true.js +47 -4
  134. package/dist/honesty/blind-spots.d.ts +3 -1
  135. package/dist/honesty/blind-spots.js +24 -1
  136. package/dist/honesty/body-capture-remedy.d.ts +42 -0
  137. package/dist/honesty/body-capture-remedy.js +95 -0
  138. package/dist/honesty/feature-capture.d.ts +3 -2
  139. package/dist/honesty/feature-capture.js +1 -1
  140. package/dist/honesty/gap-ledger.d.ts +1 -1
  141. package/dist/honesty/gap-ledger.js +1 -1
  142. package/dist/honesty/instrumentation-gaps.d.ts +8 -1
  143. package/dist/honesty/instrumentation-gaps.js +12 -2
  144. package/dist/honesty/observability.d.ts +4 -3
  145. package/dist/honesty/tool-hit-rate.d.ts +3 -2
  146. package/dist/honesty/uncaptured-bodies.d.ts +3 -1
  147. package/dist/honesty/uncaptured-bodies.js +6 -2
  148. package/dist/honesty/unsettled.d.ts +9 -0
  149. package/dist/honesty/unsettled.js +45 -0
  150. package/dist/honesty/verified.d.ts +32 -1
  151. package/dist/honesty/verified.js +37 -10
  152. package/dist/impact/impact-store.d.ts +5 -14
  153. package/dist/impact/impact-store.js +49 -6
  154. package/dist/index.js +1 -1
  155. package/dist/input/network-detail.d.ts +26 -1
  156. package/dist/input/network-detail.js +44 -12
  157. package/dist/input/network-mock-tools.js +24 -17
  158. package/dist/input/real-input.js +1 -1
  159. package/dist/input/viewport-tools.js +25 -13
  160. package/dist/intent/inline-intent.d.ts +0 -10
  161. package/dist/intent/inline-intent.js +1 -1
  162. package/dist/intent/intent-shard-store.d.ts +3 -2
  163. package/dist/intent/intent-shard-store.js +1 -1
  164. package/dist/intent/intent-shard.d.ts +14 -14
  165. package/dist/intent/intent-shard.js +4 -4
  166. package/dist/intent/intent-store.d.ts +1 -1
  167. package/dist/intent/intent-store.js +1 -1
  168. package/dist/intent/intent-subject.d.ts +2 -1
  169. package/dist/intent/open-intents.d.ts +3 -2
  170. package/dist/license/license.d.ts +4 -4
  171. package/dist/license/license.js +1 -1
  172. package/dist/mcp/attach-memory.d.ts +2 -1
  173. package/dist/mcp/client-identity.d.ts +2 -1
  174. package/dist/mcp/mcp-outage.d.ts +1 -1
  175. package/dist/mcp/mcp-outage.js +1 -1
  176. package/dist/mcp/mcp-proxy.d.ts +3 -1
  177. package/dist/mcp/mcp-proxy.js +32 -7
  178. package/dist/mcp/mcp.js +3 -1
  179. package/dist/mcp/proxy-handshake.d.ts +2 -1
  180. package/dist/mcp/proxy-idle-exit.d.ts +45 -0
  181. package/dist/mcp/proxy-idle-exit.js +94 -0
  182. package/dist/mcp/server-instructions.d.ts +2 -1
  183. package/dist/memory/project-memory.d.ts +2 -3
  184. package/dist/memory/project-memory.js +1 -1
  185. package/dist/oracles/propose-consequences.js +5 -5
  186. package/dist/oracles/self-instrument.d.ts +2 -2
  187. package/dist/oracles/self-instrument.js +1 -1
  188. package/dist/platform.d.ts +3 -5
  189. package/dist/platform.js +3 -5
  190. package/dist/pool/browser-pool.d.ts +92 -0
  191. package/dist/pool/browser-pool.js +119 -5
  192. package/dist/pool/playwright-launcher.js +29 -2
  193. package/dist/pool/storage-seed.d.ts +23 -0
  194. package/dist/pool/storage-seed.js +198 -0
  195. package/dist/project/artifact-root.d.ts +2 -1
  196. package/dist/project/memory-fs.d.ts +2 -1
  197. package/dist/project/reticle-dir.js +1 -1
  198. package/dist/project/session-root.d.ts +8 -0
  199. package/dist/project/session-root.js +9 -2
  200. package/dist/runs/reticle-runner.d.ts +14 -1
  201. package/dist/runs/reticle-runner.js +39 -2
  202. package/dist/session/attachment-history.d.ts +2 -1
  203. package/dist/session/fake-session.d.ts +37 -0
  204. package/dist/session/fake-session.js +81 -0
  205. package/dist/session/lease-visibility.d.ts +2 -1
  206. package/dist/session/live-control-tools.js +6 -0
  207. package/dist/session/no-session-diagnosis.d.ts +17 -1
  208. package/dist/session/no-session-diagnosis.js +32 -6
  209. package/dist/session/no-session-next-action.d.ts +14 -0
  210. package/dist/session/no-session-next-action.js +18 -0
  211. package/dist/session/no-session-watch.js +46 -3
  212. package/dist/session/registered-projects.d.ts +54 -0
  213. package/dist/session/registered-projects.js +73 -0
  214. package/dist/session/review-store.d.ts +2 -1
  215. package/dist/session/session-health.d.ts +62 -1
  216. package/dist/session/session-health.js +82 -2
  217. package/dist/session/session-manager.d.ts +11 -1
  218. package/dist/session/session-manager.js +18 -3
  219. package/dist/session/session-replaced.d.ts +24 -0
  220. package/dist/session/session-replaced.js +29 -0
  221. package/dist/session/session-successor.js +20 -4
  222. package/dist/session/session.d.ts +13 -5
  223. package/dist/session/session.js +31 -23
  224. package/dist/setup/agent-configs.d.ts +3 -2
  225. package/dist/setup/agent-writer.d.ts +3 -2
  226. package/dist/setup/approval-migration.d.ts +2 -1
  227. package/dist/setup/approval-migration.js +1 -1
  228. package/dist/setup/auto-approve.d.ts +11 -19
  229. package/dist/setup/auto-approve.js +15 -12
  230. package/dist/{init → setup}/confirm.js +1 -1
  231. package/dist/setup/desktop-shape.d.ts +3 -2
  232. package/dist/setup/drive-agent.d.ts +2 -3
  233. package/dist/setup/drive-agent.js +1 -1
  234. package/dist/setup/drive-plan.d.ts +3 -2
  235. package/dist/setup/ensure-daemon.d.ts +2 -1
  236. package/dist/setup/existing-app.d.ts +12 -0
  237. package/dist/setup/existing-app.js +13 -0
  238. package/dist/setup/init-host.d.ts +11 -0
  239. package/dist/setup/init-host.js +26 -0
  240. package/dist/setup/init-runtime.d.ts +4 -10
  241. package/dist/setup/init-runtime.js +23 -16
  242. package/dist/setup/license-key.d.ts +2 -1
  243. package/dist/setup/listeners.d.ts +2 -1
  244. package/dist/setup/loopback-probe-urls.d.ts +18 -0
  245. package/dist/setup/loopback-probe-urls.js +52 -0
  246. package/dist/setup/node-effects.d.ts +3 -0
  247. package/dist/setup/node-effects.js +26 -11
  248. package/dist/setup/page-probe.d.ts +5 -0
  249. package/dist/setup/relaunch.d.ts +2 -1
  250. package/dist/setup/run-setup.d.ts +5 -1
  251. package/dist/setup/run-setup.js +141 -50
  252. package/dist/setup/setup-command.d.ts +3 -2
  253. package/dist/setup/setup-command.js +8 -0
  254. package/dist/setup/setup-options.d.ts +5 -3
  255. package/dist/setup/setup-options.js +3 -2
  256. package/dist/setup/wait-progress.d.ts +28 -0
  257. package/dist/setup/wait-progress.js +43 -0
  258. package/dist/telemetry/app-instrumented.d.ts +2 -1
  259. package/dist/telemetry/app-instrumented.js +1 -1
  260. package/dist/telemetry/automation-hint.d.ts +1 -1
  261. package/dist/telemetry/automation-hint.js +1 -1
  262. package/dist/telemetry/cli-telemetry.js +1 -1
  263. package/dist/telemetry/connect-failure.d.ts +1 -1
  264. package/dist/telemetry/connect-failure.js +1 -1
  265. package/dist/telemetry/crash-cause.d.ts +3 -2
  266. package/dist/telemetry/crash-cause.js +1 -1
  267. package/dist/telemetry/daemon-telemetry.d.ts +3 -2
  268. package/dist/telemetry/daemon-telemetry.js +1 -1
  269. package/dist/telemetry/error-class.d.ts +3 -2
  270. package/dist/telemetry/error-class.js +1 -1
  271. package/dist/telemetry/feedback-cli.js +1 -1
  272. package/dist/telemetry/feedback-context.d.ts +3 -2
  273. package/dist/telemetry/feedback-context.js +5 -4
  274. package/dist/telemetry/feedback-local.d.ts +3 -2
  275. package/dist/telemetry/feedback.d.ts +4 -3
  276. package/dist/telemetry/feedback.js +1 -1
  277. package/dist/telemetry/git-facts.d.ts +2 -2
  278. package/dist/telemetry/git-facts.js +1 -1
  279. package/dist/telemetry/identify.d.ts +1 -1
  280. package/dist/telemetry/identify.js +1 -1
  281. package/dist/telemetry/init-telemetry.d.ts +2 -14
  282. package/dist/telemetry/init-telemetry.js +4 -13
  283. package/dist/telemetry/install-source.d.ts +7 -13
  284. package/dist/telemetry/install-source.js +7 -29
  285. package/dist/telemetry/license-activation.d.ts +3 -2
  286. package/dist/telemetry/license-activation.js +1 -1
  287. package/dist/telemetry/machine-snapshot.d.ts +1 -1
  288. package/dist/telemetry/mcp-connection.js +1 -1
  289. package/dist/telemetry/project-profile.d.ts +0 -14
  290. package/dist/telemetry/project-profile.js +1 -1
  291. package/dist/telemetry/proxy-telemetry.js +1 -1
  292. package/dist/telemetry/session-metrics.d.ts +1 -1
  293. package/dist/telemetry/session-metrics.js +1 -1
  294. package/dist/telemetry/telemetry.d.ts +1 -17
  295. package/dist/telemetry/telemetry.js +4 -4
  296. package/dist/telemetry/tool-refused.d.ts +1 -1
  297. package/dist/telemetry/tool-refused.js +1 -1
  298. package/dist/tools/act-observation.d.ts +3 -2
  299. package/dist/tools/act-preflight.js +8 -3
  300. package/dist/tools/act-target.d.ts +4 -0
  301. package/dist/tools/act-target.js +14 -5
  302. package/dist/tools/act-tools.d.ts +0 -2
  303. package/dist/tools/act-tools.js +45 -15
  304. package/dist/tools/assert-source.d.ts +30 -0
  305. package/dist/tools/assert-source.js +21 -0
  306. package/dist/tools/assert-verdict.d.ts +1 -1
  307. package/dist/tools/assert-verdict.js +14 -4
  308. package/dist/tools/browser-tools.js +28 -8
  309. package/dist/tools/complete-snapshot.d.ts +3 -2
  310. package/dist/tools/dynamic-tools.js +8 -0
  311. package/dist/tools/error-recovery.d.ts +10 -0
  312. package/dist/tools/error-recovery.js +23 -0
  313. package/dist/tools/feedback-tools.js +1 -1
  314. package/dist/tools/invoke-tool.js +2 -0
  315. package/dist/tools/lease-tools.d.ts +48 -20
  316. package/dist/tools/lease-tools.js +311 -76
  317. package/dist/tools/lineage-tools.d.ts +14 -0
  318. package/dist/tools/lineage-tools.js +64 -0
  319. package/dist/tools/merge-tools.js +1 -3
  320. package/dist/tools/navigate-arrival.d.ts +45 -1
  321. package/dist/tools/navigate-arrival.js +39 -6
  322. package/dist/tools/navigate-result.d.ts +8 -1
  323. package/dist/tools/navigate-result.js +14 -4
  324. package/dist/tools/numeric-bounds.d.ts +0 -11
  325. package/dist/tools/numeric-bounds.js +4 -4
  326. package/dist/tools/observe-tools.js +22 -26
  327. package/dist/tools/observed-nothing.d.ts +9 -0
  328. package/dist/tools/observed-nothing.js +2 -1
  329. package/dist/tools/read-tools.js +12 -17
  330. package/dist/tools/real-input-attempt.d.ts +14 -6
  331. package/dist/tools/real-input-attempt.js +75 -18
  332. package/dist/tools/reconcile-tools.js +2 -1
  333. package/dist/tools/reload-result.d.ts +6 -1
  334. package/dist/tools/reload-result.js +7 -2
  335. package/dist/tools/resolve-target.d.ts +7 -1
  336. package/dist/tools/resolve-target.js +72 -9
  337. package/dist/tools/settle-in-flight.d.ts +1 -1
  338. package/dist/tools/settle-in-flight.js +9 -2
  339. package/dist/tools/snapshot-delta.d.ts +24 -1
  340. package/dist/tools/snapshot-delta.js +66 -7
  341. package/dist/tools/tool-names.d.ts +1 -0
  342. package/dist/tools/tool-names.js +1 -0
  343. package/dist/tools/tool-surface.js +20 -2
  344. package/dist/tools/tools.js +120 -11
  345. package/dist/tools/verify-next-baton.d.ts +2 -1
  346. package/dist/update/sdk-sync.d.ts +1 -1
  347. package/dist/update/sdk-sync.js +1 -1
  348. package/dist/update/updater.js +1 -1
  349. package/dist/version/sdk-fix.d.ts +2 -1
  350. package/dist/version/version-skew.d.ts +0 -8
  351. package/dist/version/version-skew.js +1 -1
  352. package/docs/README.md +8 -6
  353. package/docs/actions.mdx +1 -1
  354. package/docs/agent-cheatsheet.md +4 -2
  355. package/docs/architecture.md +1 -1
  356. package/docs/cli/mcp.mdx +3 -1
  357. package/docs/cli/watch.mdx +1 -1
  358. package/docs/cli.mdx +1 -1
  359. package/docs/desktop.mdx +1 -1
  360. package/docs/docs.json +8 -3
  361. package/docs/faq.mdx +9 -0
  362. package/docs/first-drive.md +136 -0
  363. package/docs/frameworks.mdx +29 -7
  364. package/docs/gate-plan.md +2 -2
  365. package/docs/gates.md +19 -11
  366. package/docs/getting-started.md +7 -1
  367. package/docs/http-transport.md +164 -0
  368. package/docs/index.mdx +5 -3
  369. package/docs/install-manual.mdx +1 -1
  370. package/docs/packages/tauri.mdx +1 -1
  371. package/docs/packages.mdx +15 -3
  372. package/docs/platform-integration.md +1 -1
  373. package/docs/predicates.mdx +2 -0
  374. package/docs/system-map.md +2 -2
  375. package/docs/telemetry-contract.md +4 -4
  376. package/docs/telemetry-events.mdx +1 -1
  377. package/docs/telemetry.md +3 -3
  378. package/docs/tools-navigate.mdx +14 -6
  379. package/docs/usage.md +4 -4
  380. package/package.json +7 -4
  381. package/dist/init/agent-rules.d.ts +0 -116
  382. package/dist/init/agent-rules.js +0 -279
  383. package/dist/init/app-choice.d.ts +0 -20
  384. package/dist/init/app-choice.js +0 -25
  385. package/dist/init/astro-host.d.ts +0 -21
  386. package/dist/init/astro-host.js +0 -27
  387. package/dist/init/astro-patch.d.ts +0 -33
  388. package/dist/init/astro-patch.js +0 -300
  389. package/dist/init/capabilities.d.ts +0 -40
  390. package/dist/init/capabilities.js +0 -150
  391. package/dist/init/closing-hint.d.ts +0 -36
  392. package/dist/init/closing-hint.js +0 -120
  393. package/dist/init/connect-steps.d.ts +0 -2
  394. package/dist/init/connect-steps.js +0 -50
  395. package/dist/init/cra.d.ts +0 -79
  396. package/dist/init/cra.js +0 -178
  397. package/dist/init/csp-check.d.ts +0 -38
  398. package/dist/init/csp-check.js +0 -107
  399. package/dist/init/csp-doctor.d.ts +0 -32
  400. package/dist/init/csp-doctor.js +0 -54
  401. package/dist/init/desktop-doctor.d.ts +0 -41
  402. package/dist/init/desktop-doctor.js +0 -198
  403. package/dist/init/detect.d.ts +0 -96
  404. package/dist/init/detect.js +0 -209
  405. package/dist/init/dev-script.d.ts +0 -50
  406. package/dist/init/dev-script.js +0 -66
  407. package/dist/init/format-generated.d.ts +0 -18
  408. package/dist/init/format-generated.js +0 -39
  409. package/dist/init/install-hint.d.ts +0 -9
  410. package/dist/init/install-hint.js +0 -29
  411. package/dist/init/mcp-clients.d.ts +0 -89
  412. package/dist/init/mcp-clients.js +0 -299
  413. package/dist/init/mcp-registered.d.ts +0 -12
  414. package/dist/init/mcp-registered.js +0 -14
  415. package/dist/init/mcp.d.ts +0 -89
  416. package/dist/init/mcp.js +0 -129
  417. package/dist/init/next-patch.d.ts +0 -39
  418. package/dist/init/next-patch.js +0 -196
  419. package/dist/init/node-io.d.ts +0 -7
  420. package/dist/init/node-io.js +0 -117
  421. package/dist/init/non-js-project.d.ts +0 -27
  422. package/dist/init/non-js-project.js +0 -84
  423. package/dist/init/patch-kind.d.ts +0 -20
  424. package/dist/init/patch-kind.js +0 -10
  425. package/dist/init/plan-framework.d.ts +0 -64
  426. package/dist/init/plan-framework.js +0 -565
  427. package/dist/init/plan.d.ts +0 -242
  428. package/dist/init/plan.js +0 -703
  429. package/dist/init/preflight.d.ts +0 -28
  430. package/dist/init/preflight.js +0 -38
  431. package/dist/init/project-id.d.ts +0 -12
  432. package/dist/init/project-id.js +0 -25
  433. package/dist/init/refresh-rules.d.ts +0 -39
  434. package/dist/init/refresh-rules.js +0 -51
  435. package/dist/init/run.d.ts +0 -117
  436. package/dist/init/run.js +0 -806
  437. package/dist/init/slash-command.d.ts +0 -27
  438. package/dist/init/slash-command.js +0 -132
  439. package/dist/init/snippets.d.ts +0 -134
  440. package/dist/init/snippets.js +0 -572
  441. package/dist/init/vite-config.d.ts +0 -18
  442. package/dist/init/vite-config.js +0 -114
  443. package/dist/init/windows-quote.d.ts +0 -38
  444. package/dist/init/windows-quote.js +0 -70
  445. package/dist/init/workspace-apps.d.ts +0 -30
  446. package/dist/init/workspace-apps.js +0 -178
  447. package/dist/project/remember-project.d.ts +0 -24
  448. package/dist/project/remember-project.js +0 -40
  449. /package/dist/{init → setup}/confirm.d.ts +0 -0
package/dist/init/run.js DELETED
@@ -1,806 +0,0 @@
1
- /**
2
- * The impure shell for `reticle init`: gather project files via an injected IO surface, build the
3
- * plan (pure), optionally write the apply-steps, and print a human-readable report. All filesystem
4
- * access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
5
- */
6
- import { dirname, join } from 'node:path';
7
- import { CSP_FILES } from './csp-doctor.js';
8
- import { preflightRefusal } from './preflight.js';
9
- import { noPackageJsonMessage } from './non-js-project.js';
10
- import { devCommandFrom } from './dev-script.js';
11
- import { restartHint, FEEDBACK_HINT } from './closing-hint.js';
12
- import { spanSync } from '../trace.js';
13
- import { projectIdOf, rememberProjectOnDisk } from '../project/remember-project.js';
14
- import { detect, Framework, namesAPackageManager, UiLibrary } from './detect.js';
15
- import { wasMcpRegistered } from './mcp-registered.js';
16
- import { pickAstroHost } from './astro-host.js';
17
- import { findWorkspaceApps, NEXT_CONFIG_CANDIDATES, PACKAGE_JSON, VITE_CONFIG_CANDIDATES, } from './workspace-apps.js';
18
- import { chooseWorkspaceApp } from './app-choice.js';
19
- import { isConnectStep } from './connect-steps.js';
20
- import { CURSOR_RULE_PATH, RETICLE_MD_PATH } from './agent-rules.js';
21
- import { CRA_ENV_PATH } from './cra.js';
22
- import { defaultPairingTokenDir, readOrCreatePairingTokenSync } from '../bridge/pairing-token.js';
23
- import { formatGeneratedSource } from './format-generated.js';
24
- /** CRA's bundled entry, in the order create-react-app itself generates them. */
25
- const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
26
- function craEntryOf(io) {
27
- for (const path of CRA_ENTRY_CANDIDATES) {
28
- const source = io.readFile(path);
29
- if (source !== null)
30
- return { path, source };
31
- }
32
- return null;
33
- }
34
- /**
35
- * The daemon's pairing token, minted here if nothing has written it yet.
36
- *
37
- * `init` used to READ the file and return empty when the daemon had never started. The CDN snippet
38
- * inlined that empty value permanently, and regenerating the token made the pasted literal stale.
39
- * Same mint as the daemon (`readOrCreatePairingToken`), honours `RETICLE_PAIRING_TOKEN_DIR`.
40
- */
41
- function readPairingToken() {
42
- return readOrCreatePairingTokenSync(defaultPairingTokenDir()) ?? '';
43
- }
44
- import { DEPS_TARGET, RETICLE_CONFIG_FILE, frameworkPackages, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
45
- import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
46
- import { reticleDevLocation } from './next-patch.js';
47
- import { scanTestids, storeHints, scanStores } from './capabilities.js';
48
- import { fileBackedClients, clientMarkerRelPath, ConfigScope, McpClient, CURSOR_PROJECT_MARKER, } from './mcp-clients.js';
49
- import { deriveProjectId, packageName } from './project-id.js';
50
- import { VITE_DEV_MODULE_PATH, connectArgWithToken, staticPageSnippet } from './snippets.js';
51
- import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
52
- import { SERVER_VERSION } from '../version/server-version.js';
53
- import { InitFailure, reportInitOutcome } from '../telemetry/init-telemetry.js';
54
- /** Lockfile basenames, in package-manager preference order (mirrors detect.ts). */
55
- const LOCKFILE_NAMES = [
56
- 'pnpm-lock.yaml',
57
- 'yarn.lock',
58
- 'bun.lockb',
59
- 'bun.lock',
60
- 'package-lock.json',
61
- ];
62
- /**
63
- * Resolve the lockfiles set used to pick the package manager. A lockfile in the project root wins;
64
- * otherwise we walk UP the directory tree (monorepos keep the lockfile at the workspace root, not in
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.
73
- */
74
- export function resolveLockfiles(rootFiles, cwd, io, nodeModulesMarkers = new Set()) {
75
- const set = new Set(rootFiles);
76
- if (LOCKFILE_NAMES.some((name) => set.has(name)))
77
- return set; // local lockfile is authoritative
78
- if (namesAPackageManager(nodeModulesMarkers))
79
- return set;
80
- let dir = cwd;
81
- for (let depth = 0; depth < 50; depth++) {
82
- for (const name of LOCKFILE_NAMES) {
83
- if (io.exists(join(dir, name))) {
84
- set.add(name);
85
- return set;
86
- }
87
- }
88
- const parent = dirname(dir);
89
- if (parent === dir)
90
- break; // filesystem root
91
- dir = parent;
92
- }
93
- return set;
94
- }
95
- const NODE_MODULES_DIR = 'node_modules';
96
- /**
97
- * Root-layout candidates, App Router only. `--src-dir` apps keep theirs under `src/app`, and the
98
- * ReticleDev component has to land NEXT TO the layout or the relative import it generates is dead.
99
- */
100
- const NEXT_LAYOUT_CANDIDATES = [
101
- 'app/layout.tsx',
102
- 'app/layout.jsx',
103
- 'app/layout.js',
104
- 'src/app/layout.tsx',
105
- 'src/app/layout.jsx',
106
- 'src/app/layout.js',
107
- ];
108
- /**
109
- * Pages Router mount points, checked only when there is no App Router layout. A Pages app has no
110
- * `app/` directory at all, so writing the component there produced a file nothing imported.
111
- */
112
- const NEXT_PAGES_APP_CANDIDATES = [
113
- 'pages/_app.tsx',
114
- 'pages/_app.jsx',
115
- 'pages/_app.js',
116
- 'src/pages/_app.tsx',
117
- 'src/pages/_app.jsx',
118
- 'src/pages/_app.js',
119
- ];
120
- const SVELTEKIT_HOOKS = 'src/hooks.client.ts';
121
- const SOURCE_FILE = /\.(tsx|jsx|ts|js|svelte|vue|astro)$/;
122
- /** Files read for the testid scan. A capabilities block is a hint; reading a whole repo for it is not. */
123
- const MAX_SCANNED_FILES = 200;
124
- /** Directories that never hold the app's own source, and are expensive or misleading to read. */
125
- const NOT_SOURCE_DIRS = new Set([
126
- 'node_modules',
127
- 'dist',
128
- 'build',
129
- 'out',
130
- 'coverage',
131
- 'target',
132
- 'public',
133
- 'static',
134
- '.next',
135
- '.svelte-kit',
136
- '.nuxt',
137
- ]);
138
- /** How far below the app root to look. Deep enough for `modules/users/UserList.tsx`, not a repo crawl. */
139
- const MAX_SCAN_DEPTH = 5;
140
- /**
141
- * Read a bounded set of the app's source files, for the `data-testid` scan.
142
- *
143
- * This used to walk the fixed list `src, src/components, src/pages, app, components`. Reported from
144
- * the field (#318) by a repo whose app is at `src/admin`: the scan read directories that were not
145
- * the app's and correctly reported finding nothing in them, so `init` said "no data-testid values
146
- * yet" about an app with several — which makes an agent go and write the ones already there. That
147
- * list had already grown once for a `frontend/` app, and a third report of the same shape is what
148
- * says the answer is not another name in a list. So: walk the app root, bounded by depth and by the
149
- * same file cap as before.
150
- */
151
- function readSourceFiles(io) {
152
- const out = [];
153
- const read = (path) => {
154
- const content = io.readFile(path);
155
- if (content !== null)
156
- out.push({ path, source: content });
157
- };
158
- for (const name of io.rootFiles()) {
159
- if (out.length >= MAX_SCANNED_FILES)
160
- return out;
161
- if (SOURCE_FILE.test(name))
162
- read(name);
163
- }
164
- const walk = (dir, depth) => {
165
- for (const name of io.listFiles(dir)) {
166
- if (out.length >= MAX_SCANNED_FILES)
167
- return;
168
- if (SOURCE_FILE.test(name))
169
- read(`${dir}/${name}`);
170
- }
171
- if (depth >= MAX_SCAN_DEPTH)
172
- return;
173
- for (const sub of io.listDirs(dir)) {
174
- if (out.length >= MAX_SCANNED_FILES)
175
- return;
176
- if (sub.startsWith('.') || NOT_SOURCE_DIRS.has(sub))
177
- continue;
178
- walk(`${dir}/${sub}`, depth + 1);
179
- }
180
- };
181
- for (const dir of io.listDirs('.')) {
182
- if (out.length >= MAX_SCANNED_FILES)
183
- return out;
184
- if (dir.startsWith('.') || NOT_SOURCE_DIRS.has(dir))
185
- continue;
186
- walk(dir, 1);
187
- }
188
- return out;
189
- }
190
- /** Direct dependency names, for naming the state libraries an app actually has. */
191
- function dependencyNames(pkg) {
192
- const p = (pkg ?? {});
193
- return new Set([
194
- ...Object.keys(p['dependencies'] ?? {}),
195
- ...Object.keys(p['devDependencies'] ?? {}),
196
- ]);
197
- }
198
- const ASTRO_CONFIG_CANDIDATES = [
199
- 'astro.config.mjs',
200
- 'astro.config.js',
201
- 'astro.config.ts',
202
- 'astro.config.cjs',
203
- ];
204
- /** Where a conventional Astro app keeps the layout every page wraps itself in. */
205
- const ASTRO_LAYOUTS_DIR = 'src/layouts';
206
- /** Also searched: an app with no layouts directory renders the document straight from a page. */
207
- const ASTRO_PAGES_DIR = 'src/pages';
208
- /**
209
- * `⚠` means WORK LEFT TO DO and nothing else — it is what an agent (and the release gate) counts to
210
- * decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
211
- * on a working install that happens to be on an ungated stack.
212
- */
213
- /**
214
- * A step's status AFTER the run, which is what actually happened to it.
215
- *
216
- * `report` applies the same downgrade when printing: a step that failed or was skipped is shown as
217
- * MANUAL whatever it planned to be. Reading the planned status alone would say a step applied when
218
- * the run had already given up on it.
219
- */
220
- function resolvedStatus(plan, target, failed, skipped) {
221
- const step = plan.steps.find((s) => s.target === target);
222
- if (step === undefined)
223
- return undefined;
224
- if (failed.has(target) || skipped.has(target))
225
- return StepStatus.MANUAL;
226
- return step.status;
227
- }
228
- const STATUS_SYMBOL = {
229
- [StepStatus.APPLY]: '✓',
230
- [StepStatus.MANUAL]: '⚠',
231
- [StepStatus.ALREADY]: '·',
232
- [StepStatus.SKIP]: '–',
233
- [StepStatus.NOTICE]: 'ℹ',
234
- };
235
- function firstPresent(files, candidates) {
236
- for (const c of candidates)
237
- if (files.has(c))
238
- return c;
239
- return null;
240
- }
241
- /**
242
- * The directory the human's agent runs in, when it is not the app's directory.
243
- *
244
- * `undefined` for a single-package repo, which keeps every agent-file path project-relative exactly
245
- * as it was — the two roots are the same there, which is why writing them beside the app went
246
- * unnoticed until a repo with its app at `src/admin` reported it.
247
- */
248
- function agentRootOf(options) {
249
- const root = options.agentRoot;
250
- return root === undefined || root === options.cwd ? undefined : root;
251
- }
252
- function gatherPlanInput(options, io, pkg) {
253
- // Stable identity derived from the app's package.json name + root, so it survives port changes.
254
- const projectId = deriveProjectId(packageName(pkg), options.cwd);
255
- const rootFiles = new Set(io.rootFiles());
256
- const nodeModulesMarkers = new Set(io.listFiles('node_modules'));
257
- const detectInput = {
258
- pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
259
- configFiles: rootFiles,
260
- // Walk up for the lockfile so a monorepo sub-package picks the workspace's package manager —
261
- // unless this package's own installed tree already answers it, which outranks an inherited one.
262
- lockfiles: resolveLockfiles(rootFiles, options.cwd, io, nodeModulesMarkers),
263
- // An already-installed tree names its own manager, which matters when no lockfile is committed.
264
- nodeModulesMarkers,
265
- };
266
- const detection = detect(detectInput);
267
- const vitePath = firstPresent(rootFiles, VITE_CONFIG_CANDIDATES);
268
- const viteSource = null === vitePath ? null : io.readFile(vitePath);
269
- const viteConfig = vitePath !== null && viteSource !== null ? { path: vitePath, source: viteSource } : null;
270
- // Global MCP registration targets each agent that's present: Claude via its CLI, Cursor via its
271
- // global config file. Only probe when the MCP step is in play.
272
- const availableProbe = claudeAvailableProbe();
273
- const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
274
- const existsProbe = claudeExistsProbe();
275
- const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
276
- // Every MCP client this machine shows evidence of. Conservative and one-directional: we write
277
- // into a config a client ALREADY has, and never create ~/.gemini or ~/.codeium for somebody who
278
- // does not use them.
279
- const detectedClients = options.mcp
280
- ? fileBackedClients()
281
- .map((spec) => {
282
- const marker = clientMarkerRelPath(spec);
283
- const absolute = spec.scope === ConfigScope.HOME ? join(io.homeDir(), spec.relPath) : spec.relPath;
284
- const markerPath = spec.scope === ConfigScope.HOME ? join(io.homeDir(), marker) : marker;
285
- // A fresh Cursor profile has not written ~/.cursor yet; the project-level .cursor/ is the
286
- // fallback that kept that real case working. Same signal, project scope.
287
- const projectFallback = spec.id === McpClient.CURSOR && !io.exists(markerPath)
288
- ? io.exists(CURSOR_PROJECT_MARKER)
289
- : false;
290
- if (!io.exists(markerPath) && !projectFallback)
291
- return null;
292
- return {
293
- id: spec.id,
294
- configPath: absolute,
295
- existing: io.readFile(absolute),
296
- };
297
- })
298
- .filter((entry) => entry !== null)
299
- : [];
300
- const astroPath = firstPresent(rootFiles, ASTRO_CONFIG_CANDIDATES);
301
- const astroSource = null === astroPath ? null : io.readFile(astroPath);
302
- // Which file owns the DOCUMENT, not how many files sit in a directory. The old rule ("exactly one
303
- // .astro in src/layouts") fired on neither real Astro app: one has no layouts directory and
304
- // renders from src/pages/index.astro, the other has three files there of which two are partials.
305
- // `</body>` is the discriminator — see astro-host.
306
- const astroCandidates = [ASTRO_LAYOUTS_DIR, ASTRO_PAGES_DIR].flatMap((dir) => io
307
- .listFiles(dir)
308
- .filter((f) => f.endsWith('.astro'))
309
- .map((f) => ({ path: `${dir}/${f}`, source: io.readFile(`${dir}/${f}`) }))
310
- .filter((c) => c.source !== null));
311
- const astroHost = pickAstroHost(astroCandidates);
312
- const layoutRelPath = astroHost?.path ?? null;
313
- const astroLayoutSource = astroHost?.source ?? null;
314
- const nextConfigFile = firstPresent(rootFiles, NEXT_CONFIG_CANDIDATES);
315
- // App Router first; a Pages Router app has no layout, and its mount point is pages/_app.
316
- const layoutPath = NEXT_LAYOUT_CANDIDATES.find((p) => io.exists(p)) ??
317
- NEXT_PAGES_APP_CANDIDATES.find((p) => io.exists(p)) ??
318
- null;
319
- const layoutSource = null === layoutPath ? null : io.readFile(layoutPath);
320
- // Where the component goes depends on WHICH router mounts it: `pages/` routes on presence, so a
321
- // component there becomes a broken route; `app/` routes on filename, so a sibling is inert.
322
- const devLocation = reticleDevLocation(layoutPath ?? 'app/layout.tsx', detection.typescript);
323
- // Read once: both the testid scan and the store scan want the same bounded set of files.
324
- const sourceFiles = readSourceFiles(io);
325
- const agentRoot = agentRootOf(options);
326
- const agentFile = (relPath) => agentRoot === undefined ? relPath : join(agentRoot, relPath);
327
- const cspSources = {};
328
- for (const file of CSP_FILES) {
329
- const source = io.readFile(file);
330
- if (null !== source)
331
- cspSources[file] = source;
332
- }
333
- return {
334
- detection,
335
- captureBodies: options.captureBodies,
336
- cspSources,
337
- claudeCli,
338
- mcpExists,
339
- platform: process.platform,
340
- detectedClients,
341
- cursorProjectPresent: io.exists(CURSOR_PROJECT_MARKER),
342
- viteConfig,
343
- astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
344
- astroLayout: layoutRelPath !== null && astroLayoutSource !== null
345
- ? { path: layoutRelPath, source: astroLayoutSource }
346
- : null,
347
- astroEnvDts: io.readFile('src/env.d.ts'),
348
- nextConfigFile,
349
- nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
350
- nextLayout: layoutPath !== null && layoutSource !== null
351
- ? { path: layoutPath, source: layoutSource }
352
- : null,
353
- // Capabilities: scanned, never asked for. Bounded — a hint for the agent, not a repo index.
354
- testids: scanTestids(sourceFiles.map((f) => f.source)),
355
- storeHints: storeHints(dependencyNames(pkg)),
356
- foundStores: scanStores(sourceFiles, dependencyNames(pkg)),
357
- nextFoundStores: scanStores(sourceFiles, dependencyNames(pkg), dirname(devLocation.path)),
358
- viteDevModuleExists: io.exists(VITE_DEV_MODULE_PATH),
359
- nextReticleDevPath: devLocation.path,
360
- nextReticleDevImport: devLocation.importSpecifier,
361
- nextReticleDevExists: io.exists(devLocation.path),
362
- nextReticleDevSource: io.readFile(devLocation.path),
363
- svelteKitHooksExists: io.exists(SVELTEKIT_HOOKS),
364
- craEntry: craEntryOf(io),
365
- craEnv: io.readFile(CRA_ENV_PATH),
366
- pairingToken: readPairingToken(),
367
- reticleConfigExists: io.exists(RETICLE_CONFIG_FILE),
368
- // The CONTENT, so a config that exists can be checked rather than trusted — a `"port"` set to
369
- // the app's own dev-server port used to survive every re-run of `init`.
370
- reticleConfigSource: io.readFile(RETICLE_CONFIG_FILE),
371
- // Read the agent instruction files so the rule merge stays idempotent across re-runs — from the
372
- // agent's own root, or the merge would idempotently check a file it is not going to write.
373
- claudeMdContent: io.readFile(agentFile('CLAUDE.md')),
374
- agentsMdContent: io.readFile(agentFile('AGENTS.md')),
375
- reticleMdContent: io.readFile(agentFile(RETICLE_MD_PATH)),
376
- cursorRuleContent: io.readFile(agentFile(CURSOR_RULE_PATH)),
377
- claudeCommandContent: io.readFile(agentFile(CLAUDE_COMMAND_PATH)),
378
- cursorCommandContent: io.readFile(agentFile(CURSOR_COMMAND_PATH)),
379
- ...(agentRoot === undefined
380
- ? {}
381
- : {
382
- agentFileRoot: agentRoot,
383
- // The config the AGENT's cwd already has, if any — see agentRootConfigStep.
384
- agentRootConfigSource: io.readFile(agentFile(RETICLE_CONFIG_FILE)),
385
- }),
386
- options: {
387
- port: options.port,
388
- mcp: options.mcp,
389
- install: options.install,
390
- projectId,
391
- // The SDK must match the CLI asking for it — see pinnedPackages.
392
- sdkVersion: SERVER_VERSION,
393
- },
394
- };
395
- }
396
- /** Where workspace tooling conventionally puts packages. */
397
- /** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
398
- const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
399
- 'installed stops it booting. Run that install, then re-run `reticle init`.';
400
- function report(plan, dryRun, failed, skipped, degraded, io, projectDir, agentRoot,
401
- /** The project's own dev command, so the closing line names what a human would type. */
402
- devCommand,
403
- /**
404
- * Whether this run continues into the phases that boot the app and drive it.
405
- *
406
- * The closing hint tells the reader to restart their dev server and then drive a flow. When those
407
- * are the very next things this command does, printing them is worse than noise: it is an
408
- * instruction to do by hand what is about to happen automatically, three lines before it happens.
409
- */
410
- continuesToRuntime = false) {
411
- io.print(dryRun ? 'reticle init (dry run, no files written)' : 'reticle init');
412
- // Every path below is printed RELATIVE, and until now nothing said what to. Reported from the
413
- // field as "[✓] Reticle config → .reticle.json" followed by the file not being there: the app was
414
- // in `frontend/`, init redirected into it, and the report's `.reticle.json` was true about a
415
- // directory the reader was not standing in. One line makes every path in the report unambiguous.
416
- io.print(` in ${projectDir}`);
417
- // The agent files go where the AGENT is, which after a redirect is not where the app is. Said out
418
- // loud because it changes where `/reticle` will exist, and a reader who assumes one directory for
419
- // everything goes looking in the wrong one (#318).
420
- if (agentRoot !== undefined)
421
- io.print(` agent files in ${agentRoot} (where /reticle will exist)`);
422
- io.print('');
423
- let applied = 0;
424
- let manual = 0;
425
- // A ⚠ on a CONNECT step is a guaranteed failure, not a caveat: nothing performs the manual step, so
426
- // the app never dials the daemon and every tool answers "no browser session connected". `ok` was
427
- // hardcoded true, so a run that could not possibly work reported success.
428
- let connectPending = false;
429
- for (const s of plan.steps) {
430
- // A side effect that failed to apply is reported as a manual step with its fallback command.
431
- const note = degraded.get(s.target);
432
- if (note !== undefined) {
433
- // Applied, but not the way it was asked for. A NOTICE, not work — the install did happen.
434
- io.print(` [${STATUS_SYMBOL[StepStatus.NOTICE]}] ${s.title} → ${s.target}`);
435
- for (const line of note.split('\n'))
436
- io.print(` ${line}`);
437
- applied++;
438
- continue;
439
- }
440
- const downgraded = failed.has(s.target) || skipped.has(s.target);
441
- const status = downgraded ? StepStatus.MANUAL : s.status;
442
- const detail = skipped.has(s.target)
443
- ? SKIPPED_DETAIL
444
- : downgraded && s.exec !== undefined
445
- ? `step failed — run manually: ${s.exec.fallback}`
446
- : s.detail;
447
- io.print(` [${STATUS_SYMBOL[status]}] ${s.title} → ${s.target}`);
448
- if (status === StepStatus.APPLY)
449
- applied++;
450
- if (status === StepStatus.MANUAL || status === StepStatus.NOTICE) {
451
- // A notice prints in full like a manual step — it is worth reading — but is NOT counted as work.
452
- if (status === StepStatus.MANUAL) {
453
- manual++;
454
- if (isConnectStep(s.title))
455
- connectPending = true;
456
- }
457
- for (const line of detail.split('\n'))
458
- io.print(` ${line}`);
459
- }
460
- else if (detail.length > 0) {
461
- io.print(` ${detail}`);
462
- }
463
- }
464
- io.print('');
465
- if (connectPending) {
466
- io.print('This app will NOT connect until the ⚠ step above is done by hand — Reticle tools will report ' +
467
- '"no browser session connected" until then. Everything else is already in place.');
468
- io.print('');
469
- }
470
- if (!continuesToRuntime) {
471
- io.print(restartHint(plan.framework, resolvedStatus(plan, MCP_TARGET, failed, skipped), devCommand));
472
- }
473
- return { ok: !connectPending, applied, manual };
474
- }
475
- /**
476
- * Perform the apply-step side effects; return the targets whose side effect failed.
477
- *
478
- * Steps run in plan order, which puts the dependency install BEFORE everything that imports what it
479
- * installs. If it fails, the wiring is skipped rather than applied: patching `next.config.ts` to
480
- * import a `@reticlehq/next` that was never installed takes the dev server down with
481
- * MODULE_NOT_FOUND, so the app stops booting *because* Reticle was installed. A skipped step is a
482
- * message; a half-wired app is a broken project.
483
- */
484
- /**
485
- * Are the SDK packages actually on disk, whatever the install step reported?
486
- *
487
- * `dependsOnInstall` exists to stop init writing a `next.config.ts` that imports a package which is
488
- * not there — that took a dev server down once, and installing Reticle must never be why an app
489
- * stops booting. The invariant it protects is "the import RESOLVES", but it was gated on "our
490
- * install subprocess exited 0", and those come apart in exactly the situation the failure creates:
491
- * init tells the user to install by hand, they do, they re-run init, the install step fails again
492
- * (wrong package manager, not on PATH) and every wiring step is skipped a second time. Reported from
493
- * a Next 16 app where npm had already installed both packages successfully — leaving an init that
494
- * could not be retried into working, which is the shape this guard was written to prevent.
495
- *
496
- * Reading node_modules answers the real question and costs one `exists` call per package.
497
- */
498
- function sdkPackagesPresent(framework, uiLibrary, io) {
499
- const packages = frameworkPackages(framework, uiLibrary);
500
- return (packages.length > 0 &&
501
- packages.every((p) => io.exists(`${NODE_MODULES_DIR}/${p}/${PACKAGE_JSON}`)));
502
- }
503
- function applyEffects(plan, io) {
504
- const failed = new Set();
505
- const skipped = new Set();
506
- const degraded = new Map();
507
- let installFailed = false;
508
- for (const s of plan.steps) {
509
- if (s.status !== StepStatus.APPLY)
510
- continue;
511
- if (installFailed && true === s.dependsOnInstall) {
512
- skipped.add(s.target);
513
- continue;
514
- }
515
- const write = s.write;
516
- if (write !== undefined) {
517
- // Confirm the EFFECT, not the intention. Reported from the field (#160): init printed
518
- // `[✓] Reticle config → .reticle.json` and the file was not there afterward. Nothing checked —
519
- // no arrangement of the filesystem (a read-only mount, a full disk, an antivirus quarantining
520
- // a new dotfile) could have turned that tick into anything else. A checkmark that cannot fail
521
- // is decoration, and this one is the first thing a new user reads. Same shape as #139.
522
- const wrote = spanSync('init.write', { target: s.target, path: write.path }, () => {
523
- try {
524
- // Format connect modules with the project's Prettier when present (#684) — a clean
525
- // install must not fail the project's own lint on a file we just wrote.
526
- const content = formatGeneratedSource(write.content, write.path, io.cwd());
527
- io.writeFile(write.path, content);
528
- }
529
- catch {
530
- return false; // a throw is the loud version of the same failure
531
- }
532
- return io.exists(write.path);
533
- });
534
- if (!wrote) {
535
- failed.add(s.target);
536
- continue; // do not run this step's exec against a file that is not there
537
- }
538
- }
539
- // Bound once so the traced call cannot need a `?? ''` fallback — a default there would turn a
540
- // narrowing mistake into an empty command that silently "succeeds".
541
- const exec = s.exec;
542
- // Per-step, because the interesting part of init's wall-clock is WHICH step spent it: a
543
- // package-manager install and a `claude mcp add` are both subprocesses, and one of them being
544
- // slow is a completely different problem from the other.
545
- if (exec !== undefined &&
546
- !spanSync('init.exec', { target: s.target, command: exec.command }, () => io.exec(exec.command, exec.args))) {
547
- // A weaker second attempt beats no install at all — but only when it is REPORTED, because the
548
- // thing it gives up is the version pin that keeps SDK and daemon in step.
549
- //
550
- // Traced separately, and it is the reason init can take twice as long as it looks like it
551
- // should: this is a SECOND full package-manager run, and until it had its own span 1.6 of
552
- // init's 2.3 seconds simply vanished — the span above accounted for the first attempt and
553
- // nothing accounted for this one.
554
- const retry = s.retry;
555
- if (retry !== undefined &&
556
- spanSync('init.exec.retry', { target: s.target, command: retry.command }, () => io.exec(retry.command, retry.args))) {
557
- degraded.set(s.target, retry.note);
558
- continue;
559
- }
560
- failed.add(s.target);
561
- // A failed install only blocks the wiring when the packages are genuinely ABSENT. See
562
- // sdkPackagesPresent: the guard protects "the import resolves", not "our subprocess exited 0".
563
- if (s.target === DEPS_TARGET && !sdkPackagesPresent(plan.framework, plan.uiLibrary, io))
564
- installFailed = true;
565
- }
566
- }
567
- // Where this project lives, remembered for a daemon that will be started somewhere else.
568
- //
569
- // Deliberately AFTER the loop and unconditional on which steps ran: most init runs report "already
570
- // wired", and a re-run in a re-cloned or moved checkout is exactly when the remembered path has
571
- // gone stale — so the run that would otherwise be a no-op is the one that repairs the entry. The
572
- // return value is ignored on purpose; this is a cache, and a read-only home directory must not
573
- // turn a wired project into a failed init.
574
- const projectId = projectIdOf(io.readFile(RETICLE_CONFIG_FILE));
575
- if (projectId !== undefined) {
576
- rememberProjectOnDisk(io, projectId, io.cwd(), Date.now());
577
- }
578
- return { failed, skipped, degraded };
579
- }
580
- /**
581
- * Which STEP failed, from the step targets — not from an error string, which would carry paths.
582
- *
583
- * The distinction that matters: a dependency install failing is a machine/network problem (offline,
584
- * a locked registry, a broken package manager), while MCP registration failing means the `claude` CLI
585
- * is missing or refused. Two completely different fixes, and until now both were simply "init didn't
586
- * work" with nothing to tell them apart.
587
- */
588
- function classifyInitFailure(failed) {
589
- if (failed.has(DEPS_TARGET))
590
- return InitFailure.DEPENDENCY_INSTALL;
591
- if (failed.has(MCP_TARGET))
592
- return InitFailure.MCP_REGISTRATION;
593
- return InitFailure.OTHER;
594
- }
595
- const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle init` inside the one you want:';
596
- /**
597
- * When the current directory is a workspace root with no app of its own, wire the app instead of the
598
- * root. One candidate is wired silently (there is nothing to ask about); several are listed, because
599
- * guessing which app someone meant is worse than one line of output.
600
- * Returns null when there is nothing to redirect to — the caller then proceeds here as before.
601
- */
602
- function redirectToWorkspaceApp(options, io, pkg) {
603
- if (true === options.redirected)
604
- return null;
605
- const rootFiles = new Set(io.rootFiles());
606
- const here = detect({
607
- pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
608
- configFiles: rootFiles,
609
- lockfiles: new Set(),
610
- });
611
- // `--app` is an INSTRUCTION, and it is read before the guess below. The guess answers "where is
612
- // the app?" for somebody who did not say; when somebody said, there is nothing left to infer.
613
- //
614
- // It used to be read after, and the check underneath returns early for any directory that looks
615
- // like an app — which a JS monorepo ROOT does, because shared tooling puts `vite` in its
616
- // devDependencies. So on a real pnpm+turbo monorepo (measured on nuclear, a Tauri v2 app at
617
- // product scale) `reticle init --app packages/player` silently ignored the flag, installed the
618
- // SDK into the root's package.json, wrote `.reticle.json` and a whole `src/reticle-dev.ts` into a
619
- // repository root that has no `src/`, left `packages/player` untouched — and reported three ✓ and
620
- // one ⚠. The one flag documented for this shape wired the wrong directory and said it worked.
621
- //
622
- // Existence is the test, not membership of the discovered list: discovery scans conventional
623
- // directories, and somebody who names a path knows their own layout better than the scan does.
624
- const named = options.app === undefined || '' === options.app ? undefined : options.app;
625
- if (named !== undefined) {
626
- const wanted = named.replace(/\/+$/, '');
627
- if (io.exists(`${wanted}/${PACKAGE_JSON}`))
628
- return enterApp(options, io, wanted, 'Wiring');
629
- io.print(`--app ${named} does not name a directory with a package.json in it.`);
630
- return { ok: false, applied: 0, manual: 1 };
631
- }
632
- if (here.framework !== Framework.HTML)
633
- return null; // this directory IS the app
634
- const apps = findWorkspaceApps(io);
635
- // An explicitly named app answers the ambiguity. Refusing to guess is right, but "re-run inside the
636
- // one you want" is not something a script, a CI step, or an agent that cannot change directory can
637
- // act on — so the refusal was a dead end for exactly the callers most likely to hit it.
638
- const chosen = chooseWorkspaceApp(options.app, apps);
639
- if (!chosen.ok) {
640
- io.print(chosen.message);
641
- return { ok: false, applied: 0, manual: 1 };
642
- }
643
- const target = chosen.app ?? (1 === apps.length ? apps[0] : undefined);
644
- if (target === undefined) {
645
- if (0 === apps.length)
646
- return null; // not a workspace — fall through to the normal HTML plan
647
- io.print(AMBIGUOUS_HEADER);
648
- for (const a of apps)
649
- io.print(` ${a}`);
650
- io.print('');
651
- io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
652
- return { ok: false, applied: 0, manual: apps.length };
653
- }
654
- return enterApp(options, io, target, 'No app in this directory — wiring');
655
- }
656
- /**
657
- * Re-enter `init` scoped to one directory of a workspace.
658
- *
659
- * One implementation for both routes in — the app somebody NAMED and the app discovery found when
660
- * nobody did. They differ only in the sentence printed; scoping the io, moving the cwd and keeping
661
- * the agent root has to be identical for both, and was worth having twice for exactly as long as it
662
- * took to get one of them wrong.
663
- */
664
- function enterApp(options, io, target, lead) {
665
- io.print(`${lead} ${target}.`);
666
- io.print('');
667
- return runInit({
668
- ...options,
669
- cwd: join(options.cwd, target),
670
- redirected: true,
671
- // Where the human is STANDING, kept across the redirect: their agent session runs here, so
672
- // this is the only place a `/reticle` command file can be found by it.
673
- agentRoot: options.agentRoot ?? options.cwd,
674
- }, io.scoped(target));
675
- }
676
- export function runInit(options, io) {
677
- const result = runInitSteps(options, io);
678
- // The ask goes here, not in report(): report() is only the success-shaped path, and the exits that
679
- // matter most are the ones that never reach it — no package.json, an ambiguous workspace — where
680
- // setup died before anything ran and the person holding the report has the least to go on. This
681
- // wrapper is the one point every exit passes through.
682
- //
683
- // `redirected` is what keeps it to ONE print: wiring an app in a monorepo re-enters runInit for the
684
- // chosen directory, and the inner call must not ask again.
685
- if (true !== options.redirected) {
686
- io.print('');
687
- io.print(FEEDBACK_HINT);
688
- }
689
- return result;
690
- }
691
- /**
692
- * The manifest, parsed once, or the reason it could not be.
693
- *
694
- * It used to be parsed in three places from the same raw string, and two of them were unguarded —
695
- * so `reticle init` on a `package.json` with a trailing comma died with a raw `SyntaxError` and a
696
- * stack through `redirectToWorkspaceApp`. A stack trace in front of a user is a bug whatever caused
697
- * it, and this one lands on the very first thing the command does, before it has said anything.
698
- *
699
- * `setup/reticle.mjs` has always got this right ("… is not valid JSON (…). Fix it and re-run"). The
700
- * shipped CLI did not, which is the shape of every divergence between the two: the prototype refuses
701
- * politely, `init` throws. Parsing once at the single point the file enters means no later caller
702
- * CAN reintroduce it — a guard per call site would have been three guards and a fourth one waiting.
703
- */
704
- function readManifest(io) {
705
- const raw = io.readFile(PACKAGE_JSON);
706
- if (null === raw)
707
- return { pkg: null };
708
- try {
709
- return { pkg: JSON.parse(raw) };
710
- }
711
- catch (err) {
712
- // First line only: JSON.parse's message carries the offending position, and the rest is noise.
713
- const detail = String(err instanceof Error ? err.message : err).split('\n')[0] ?? 'unparseable';
714
- return { error: detail };
715
- }
716
- }
717
- function runInitSteps(options, io) {
718
- const manifest = readManifest(io);
719
- if ('error' in manifest) {
720
- io.print(`${PACKAGE_JSON} is not valid JSON (${manifest.error}). Fix it and re-run — init reads the ` +
721
- 'framework, the dev script and the package manager from it, and will not guess at any of ' +
722
- 'them from a file it cannot read.');
723
- reportInitOutcome({ ok: false, reason: InitFailure.MALFORMED_PACKAGE_JSON });
724
- return { ok: false, applied: 0, manual: 0 };
725
- }
726
- const pkgRaw = manifest.pkg;
727
- // Look for the app BEFORE concluding there isn't one.
728
- //
729
- // A root with no package.json is not a dead end — it is the ordinary shape of a repo whose app
730
- // lives one directory down (`frontend/`, `web/`, `client/`) with no manifest at the top. Bailing
731
- // first made that repo un-instrumentable: `reticle init` said "No package.json found", and
732
- // `--app frontend`, the flag that exists for exactly this, was never read because the bail came
733
- // first. Reported twice by the same user, who tried the documented workaround and hit the same
734
- // wall. Discovery already handles the case (it scans top-level directories, not just declared
735
- // workspaces); it was simply unreachable.
736
- //
737
- // `'{}'` because the redirect only needs the manifest to ask "is THIS directory the app", and a
738
- // directory with no package.json is definitively not.
739
- const redirectedEarly = redirectToWorkspaceApp(options, io, pkgRaw ?? {});
740
- if (redirectedEarly !== null)
741
- return redirectedEarly;
742
- if (null === pkgRaw) {
743
- io.print(
744
- // Two genuinely different situations used to share one sentence: a JS developer in the wrong
745
- // directory, and a project that is not JavaScript at all. The second reads the old wording as
746
- // a path problem and goes looking for a directory that cannot exist — reported from a
747
- // Streamlit app, where the search continued into hunting for a browser bundle to inject by
748
- // hand before the real answer surfaced.
749
- noPackageJsonMessage((file) => io.exists(join(options.cwd, file))));
750
- // The message says "add the snippet below". Print the snippet, or the message is the same
751
- // broken promise in the other direction. `connectArg` carries the port; there is no projectId
752
- // to bake, because a projectId is derived from the package.json that does not exist here.
753
- io.print(staticPageSnippet(connectArgWithToken(options.port, undefined, readPairingToken())));
754
- // The onboarding funnel had NO instrumentation, so a setup that died here was indistinguishable
755
- // from someone who never ran the command — the two failure modes with the most different fixes.
756
- reportInitOutcome({ ok: false, reason: InitFailure.NO_PACKAGE_JSON });
757
- return { ok: false, applied: 0, manual: 0 };
758
- }
759
- // Init is the flow a user experiences the wait of personally, and the fixture gate measures it at
760
- // 1–6s per app with no explanation of the spread. These three spans split that number into detect
761
- // (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
762
- const planInput = gatherPlanInput(options, io, pkgRaw);
763
- // Before anything is written, and AFTER detection — the package manager to check is the one init
764
- // RESOLVED, not a raw lockfile read. An inherited pnpm-lock.yaml at a monorepo root does not mean
765
- // the app in frontend/ uses pnpm, and checking the file refused a scaffold the install gate proves
766
- // must succeed. Both conditions make every later phase fail, and each arrives far from its cause
767
- // when it is not checked here. See preflight.ts.
768
- const refusal = preflightRefusal({
769
- cwd: () => io.cwd(),
770
- canWrite: () => io.canWrite(),
771
- probe: (command, args) => io.probe(command, args),
772
- }, planInput.detection.packageManager);
773
- if (refusal !== undefined) {
774
- io.print(refusal);
775
- return { ok: false, applied: 0, manual: 1 };
776
- }
777
- const plan = spanSync('init.plan', {}, () => buildPlan(planInput));
778
- const effects = options.dryRun
779
- ? { failed: new Set(), skipped: new Set(), degraded: new Map() }
780
- : spanSync('init.apply', { steps: plan.steps.length }, () => applyEffects(plan, io));
781
- const { failed, skipped, degraded } = effects;
782
- // The project's own dev command, so the closing line names what a human would actually type.
783
- const devCommand = devCommandFrom(pkgRaw, planInput.detection.packageManager);
784
- const result = report(plan, options.dryRun, failed, skipped, degraded, io, options.cwd, agentRootOf(options), devCommand, true === options.continuesToRuntime);
785
- // A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
786
- if (options.dryRun)
787
- return result;
788
- const outcome = {
789
- ok: result.ok,
790
- ...(result.ok ? {} : { reason: classifyInitFailure(failed) }),
791
- stack: plan.framework,
792
- // The step's REAL final status, not the absence of a failure — see mcp-registered.
793
- mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
794
- };
795
- const context = {
796
- appDir: options.cwd,
797
- framework: plan.framework,
798
- packageManager: planInput.detection.packageManager,
799
- ...(undefined === devCommand ? {} : { devCommand }),
800
- ...(true === options.redirected ? { redirectedTo: options.cwd } : {}),
801
- };
802
- if (true === options.deferOutcome)
803
- return { ...result, context, outcome };
804
- reportInitOutcome(outcome);
805
- return { ...result, context };
806
- }