@reticlehq/server 2.11.0 → 2.12.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 (252) hide show
  1. package/SKILL.md +26 -2
  2. package/dist/bridge/bridge.d.ts +0 -13
  3. package/dist/bridge/bridge.js +1 -1
  4. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  5. package/dist/bridge/bridge.test-harness.js +8 -1
  6. package/dist/capsule/capsule-store.d.ts +2 -1
  7. package/dist/capsule/capsule-store.js +1 -1
  8. package/dist/capsule/causal-summary.d.ts +2 -2
  9. package/dist/chromium-launch-options.d.ts +28 -0
  10. package/dist/chromium-launch-options.js +29 -0
  11. package/dist/cli/cli-doctor.js +8 -0
  12. package/dist/cli/cli-flow-commands.d.ts +3 -1
  13. package/dist/cli/cli-flow-commands.js +34 -4
  14. package/dist/cli/cli-kill.d.ts +2 -1
  15. package/dist/cli/cli-parse.d.ts +3 -1
  16. package/dist/cli/cli-parse.js +29 -7
  17. package/dist/cli/cli-update-commands.js +20 -1
  18. package/dist/cli/cli-verify.d.ts +1 -1
  19. package/dist/cli/cli-verify.js +2 -2
  20. package/dist/cli/cloud-cli.js +12 -6
  21. package/dist/cli/config-discovery.d.ts +2 -1
  22. package/dist/cli/doctor-rows.d.ts +3 -1
  23. package/dist/cli/doctor-rows.js +3 -1
  24. package/dist/cli/gate-exit.d.ts +21 -0
  25. package/dist/cli/gate-exit.js +21 -0
  26. package/dist/cli/gate-hook-message.d.ts +33 -0
  27. package/dist/cli/gate-hook-message.js +46 -0
  28. package/dist/cli/mcp-command.d.ts +25 -0
  29. package/dist/cli/mcp-command.js +143 -0
  30. package/dist/cli/sibling-ports.d.ts +37 -0
  31. package/dist/cli/sibling-ports.js +56 -0
  32. package/dist/cli.js +37 -110
  33. package/dist/cloud/cloud-config.d.ts +2 -1
  34. package/dist/cloud/cloud-sync.d.ts +11 -0
  35. package/dist/cloud/cloud-sync.js +32 -0
  36. package/dist/crawl/crawl.d.ts +9 -13
  37. package/dist/crawl/crawl.js +20 -3
  38. package/dist/daemon/daemon-resolve.d.ts +79 -0
  39. package/dist/daemon/daemon-resolve.js +118 -0
  40. package/dist/daemon/free-port.d.ts +29 -0
  41. package/dist/daemon/free-port.js +55 -0
  42. package/dist/daemon/heartbeat.d.ts +0 -4
  43. package/dist/daemon/heartbeat.js +2 -2
  44. package/dist/dev/stale-issue-guard.d.ts +0 -2
  45. package/dist/dev/stale-issue-guard.js +1 -1
  46. package/dist/events/contradictions.d.ts +41 -0
  47. package/dist/events/contradictions.js +125 -12
  48. package/dist/events/declared.d.ts +14 -0
  49. package/dist/events/declared.js +33 -0
  50. package/dist/events/echo-mismatch.js +14 -0
  51. package/dist/events/observed-in-window.d.ts +12 -0
  52. package/dist/events/observed-in-window.js +17 -0
  53. package/dist/events/predicate-asks.d.ts +13 -0
  54. package/dist/events/predicate-asks.js +22 -0
  55. package/dist/events/predicate-eval.js +64 -22
  56. package/dist/events/predicate-parse.d.ts +0 -6
  57. package/dist/events/predicate-parse.js +43 -3
  58. package/dist/events/predicate-schema.d.ts +44 -0
  59. package/dist/events/predicate-schema.js +126 -23
  60. package/dist/events/predicate.d.ts +15 -0
  61. package/dist/events/predicate.js +41 -2
  62. package/dist/flows/coverage.d.ts +0 -8
  63. package/dist/flows/coverage.js +1 -1
  64. package/dist/flows/decision.d.ts +5 -1
  65. package/dist/flows/decision.js +33 -7
  66. package/dist/flows/flake.d.ts +0 -2
  67. package/dist/flows/flake.js +1 -1
  68. package/dist/flows/flow-intent.d.ts +52 -0
  69. package/dist/flows/flow-intent.js +123 -0
  70. package/dist/flows/flow-replay-run.js +27 -4
  71. package/dist/flows/flow-success.js +15 -3
  72. package/dist/flows/flow-tools.js +32 -4
  73. package/dist/flows/flows.d.ts +6 -1
  74. package/dist/flows/flows.js +34 -22
  75. package/dist/flows/predicate-to-expect.js +6 -3
  76. package/dist/flows/server-verify.js +16 -2
  77. package/dist/flows/verify-change-tools.js +4 -1
  78. package/dist/honesty/blind-spots.d.ts +0 -17
  79. package/dist/honesty/blind-spots.js +1 -1
  80. package/dist/honesty/feature-capture.d.ts +150 -0
  81. package/dist/honesty/feature-capture.js +233 -0
  82. package/dist/honesty/instrumentation-gaps.d.ts +30 -6
  83. package/dist/honesty/instrumentation-gaps.js +61 -5
  84. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  85. package/dist/honesty/tool-hit-rate.js +90 -0
  86. package/dist/honesty/undeclared-change.d.ts +51 -0
  87. package/dist/honesty/undeclared-change.js +56 -0
  88. package/dist/honesty/verified.d.ts +15 -0
  89. package/dist/honesty/verified.js +68 -4
  90. package/dist/http-server.d.ts +0 -1
  91. package/dist/http-server.js +1 -1
  92. package/dist/hunt/hunt-report.d.ts +0 -2
  93. package/dist/hunt/hunt-report.js +1 -1
  94. package/dist/impact/impact-store.d.ts +0 -5
  95. package/dist/impact/impact-store.js +1 -1
  96. package/dist/index.d.ts +4 -1
  97. package/dist/init/agent-rules.d.ts +17 -0
  98. package/dist/init/agent-rules.js +31 -1
  99. package/dist/init/capabilities.d.ts +0 -2
  100. package/dist/init/capabilities.js +1 -1
  101. package/dist/init/closing-hint.d.ts +36 -0
  102. package/dist/init/closing-hint.js +120 -0
  103. package/dist/init/confirm.d.ts +0 -25
  104. package/dist/init/confirm.js +3 -3
  105. package/dist/init/cra.d.ts +14 -1
  106. package/dist/init/cra.js +43 -12
  107. package/dist/init/dev-script.d.ts +48 -0
  108. package/dist/init/dev-script.js +63 -0
  109. package/dist/init/mcp.d.ts +16 -5
  110. package/dist/init/mcp.js +26 -10
  111. package/dist/init/plan-framework.d.ts +0 -5
  112. package/dist/init/plan-framework.js +83 -9
  113. package/dist/init/plan.d.ts +10 -30
  114. package/dist/init/plan.js +30 -15
  115. package/dist/init/refresh-rules.d.ts +39 -0
  116. package/dist/init/refresh-rules.js +51 -0
  117. package/dist/init/run.d.ts +0 -26
  118. package/dist/init/run.js +14 -87
  119. package/dist/init/snippets.js +53 -10
  120. package/dist/input/real-input.d.ts +2 -0
  121. package/dist/input/real-input.js +3 -2
  122. package/dist/input/scroll-find.d.ts +7 -4
  123. package/dist/input/scroll-find.js +63 -25
  124. package/dist/intent/inline-intent.d.ts +46 -0
  125. package/dist/intent/inline-intent.js +103 -0
  126. package/dist/intent/intent-store.d.ts +37 -0
  127. package/dist/intent/intent-store.js +112 -0
  128. package/dist/intent/intent-tools.d.ts +2 -0
  129. package/dist/intent/intent-tools.js +80 -0
  130. package/dist/intent/open-intents.d.ts +21 -0
  131. package/dist/intent/open-intents.js +25 -0
  132. package/dist/journal/envelope.d.ts +26 -26
  133. package/dist/journal/journal-recorder.d.ts +23 -0
  134. package/dist/journal/journal-recorder.js +35 -0
  135. package/dist/mcp/attach-memory.d.ts +67 -0
  136. package/dist/mcp/attach-memory.js +142 -0
  137. package/dist/mcp/mcp-proxy.d.ts +0 -7
  138. package/dist/mcp/mcp-proxy.js +14 -2
  139. package/dist/mcp/mcp.d.ts +2 -0
  140. package/dist/mcp/mcp.js +45 -23
  141. package/dist/mcp/server-instructions.js +45 -3
  142. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  143. package/dist/mcp/tool-catalog-cache.js +10 -0
  144. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  145. package/dist/oracles/flow-instrument-gaps.js +4 -5
  146. package/dist/oracles/self-instrument.d.ts +9 -9
  147. package/dist/oracles/self-instrument.js +19 -13
  148. package/dist/pool/browser-pool.d.ts +12 -1
  149. package/dist/pool/browser-pool.js +31 -1
  150. package/dist/pool/playwright-launcher.js +3 -1
  151. package/dist/project/fs-port.d.ts +3 -0
  152. package/dist/project/fs-port.js +3 -2
  153. package/dist/project/memory-fs.js +6 -1
  154. package/dist/project/project-tools.js +3 -32
  155. package/dist/project/reticle-dir.d.ts +2 -0
  156. package/dist/project/reticle-dir.js +1 -0
  157. package/dist/runs/context-tools.d.ts +2 -0
  158. package/dist/runs/context-tools.js +89 -0
  159. package/dist/runs/run-context.d.ts +40 -0
  160. package/dist/runs/run-context.js +158 -0
  161. package/dist/runs/run-diff.d.ts +2 -1
  162. package/dist/runs/verification-sync.js +2 -2
  163. package/dist/session/dev-server-probe.d.ts +0 -8
  164. package/dist/session/dev-server-probe.js +1 -1
  165. package/dist/session/last-act.d.ts +7 -1
  166. package/dist/session/last-act.js +2 -2
  167. package/dist/session/live-control-tools.js +1 -42
  168. package/dist/session/no-session-diagnosis.d.ts +10 -0
  169. package/dist/session/no-session-diagnosis.js +24 -7
  170. package/dist/session/no-session-next-action.d.ts +5 -0
  171. package/dist/session/no-session-next-action.js +35 -3
  172. package/dist/session/no-session-watch.d.ts +8 -0
  173. package/dist/session/no-session-watch.js +82 -64
  174. package/dist/session/output-budget.js +4 -1
  175. package/dist/session/session-health.d.ts +9 -0
  176. package/dist/session/session-health.js +23 -0
  177. package/dist/session/session-manager.js +50 -1
  178. package/dist/session/session-successor.d.ts +37 -0
  179. package/dist/session/session-successor.js +78 -0
  180. package/dist/session/session.d.ts +31 -1
  181. package/dist/session/session.js +75 -2
  182. package/dist/telemetry/automation-hint.d.ts +2 -1
  183. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  184. package/dist/telemetry/project-profile.d.ts +0 -8
  185. package/dist/telemetry/project-profile.js +1 -1
  186. package/dist/telemetry/run-telemetry.d.ts +0 -2
  187. package/dist/telemetry/run-telemetry.js +1 -1
  188. package/dist/telemetry/verification-of.js +2 -2
  189. package/dist/tools/act-tools.js +126 -21
  190. package/dist/tools/assert-source.d.ts +22 -0
  191. package/dist/tools/assert-source.js +53 -0
  192. package/dist/tools/assert-verdict.d.ts +20 -3
  193. package/dist/tools/assert-verdict.js +36 -4
  194. package/dist/tools/browser-tools.js +0 -20
  195. package/dist/tools/complete-snapshot.d.ts +48 -0
  196. package/dist/tools/complete-snapshot.js +75 -0
  197. package/dist/tools/coverage-tools.d.ts +10 -1
  198. package/dist/tools/coverage-tools.js +123 -69
  199. package/dist/tools/dynamic-tools.js +29 -8
  200. package/dist/tools/error-recovery.d.ts +24 -14
  201. package/dist/tools/error-recovery.js +70 -9
  202. package/dist/tools/feedback-tools.d.ts +1 -1
  203. package/dist/tools/feedback-tools.js +2 -2
  204. package/dist/tools/invoke-tool.js +62 -2
  205. package/dist/tools/lease-hint.d.ts +10 -0
  206. package/dist/tools/lease-hint.js +53 -4
  207. package/dist/tools/lease-tools.d.ts +5 -1
  208. package/dist/tools/lease-tools.js +22 -8
  209. package/dist/tools/navigate-arrival.d.ts +0 -2
  210. package/dist/tools/navigate-arrival.js +1 -1
  211. package/dist/tools/observe-tools.js +63 -26
  212. package/dist/tools/read-tools.js +5 -3
  213. package/dist/tools/real-input-attempt.d.ts +22 -0
  214. package/dist/tools/real-input-attempt.js +153 -1
  215. package/dist/tools/reconcile-tools.js +18 -4
  216. package/dist/tools/resolve-target.d.ts +0 -7
  217. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  218. package/dist/tools/sdk-marker-probe.js +57 -0
  219. package/dist/tools/snapshot-delta.d.ts +1 -1
  220. package/dist/tools/tool-kit.d.ts +12 -0
  221. package/dist/tools/tool-kit.js +16 -0
  222. package/dist/tools/tool-names.d.ts +3 -0
  223. package/dist/tools/tool-names.js +3 -0
  224. package/dist/tools/tool-surface.d.ts +85 -6
  225. package/dist/tools/tool-surface.js +122 -2
  226. package/dist/tools/tools.js +22 -4
  227. package/dist/tools/unadvertised-help.js +4 -4
  228. package/dist/tools/verify-next-baton.d.ts +53 -0
  229. package/dist/tools/verify-next-baton.js +52 -0
  230. package/dist/update/update-nudge.d.ts +7 -10
  231. package/dist/update/update-nudge.js +12 -3
  232. package/dist/update/updater.d.ts +1 -1
  233. package/docs/README.md +2 -1
  234. package/docs/cli/cloud.mdx +1 -1
  235. package/docs/cli/doctor.mdx +1 -0
  236. package/docs/cli/verify.mdx +4 -5
  237. package/docs/desktop.mdx +1 -1
  238. package/docs/docs.json +2 -2
  239. package/docs/gates.md +1 -1
  240. package/docs/install-manual.mdx +1 -1
  241. package/docs/packages/browser.mdx +2 -2
  242. package/docs/packages/server.mdx +1 -1
  243. package/docs/packages.mdx +8 -6
  244. package/docs/platform-integration.md +17 -0
  245. package/docs/predicates.mdx +8 -0
  246. package/docs/reticle-manual.md +106 -0
  247. package/docs/tools-tools-and-run.mdx +2 -4
  248. package/docs/troubleshooting.mdx +9 -0
  249. package/docs/usage.md +3 -2
  250. package/package.json +2 -2
  251. package/dist/session/session-readiness.d.ts +0 -34
  252. package/dist/session/session-readiness.js +0 -36
@@ -0,0 +1,56 @@
1
+ import { NO_EDITS_OBSERVED } from '@reticlehq/core';
2
+ /**
3
+ * Is this verdict being drawn over code nobody said anything about?
4
+ *
5
+ * Declaring intent is opt-in, and nothing has ever noticed that a change landed and nobody said what
6
+ * it was supposed to make true. That is the moment it matters: a change with no declared intent can
7
+ * only be verified against itself, which is the definition of a false green.
8
+ *
9
+ * ## Why this fires on EVERY verdict, not once per change
10
+ *
11
+ * The house rule is that a gap nobody hit is a backlog, and a backlog reported as a finding is how
12
+ * an agent learns to stop reading findings. This clears that bar on the same test every other
13
+ * instrumentation gap clears it on: **did the absence make THIS verdict weaker?** An unwatched store
14
+ * weakens every state assertion made over it, not merely the first, so it is reported on every one.
15
+ * An undeclared change is the same shape — while nothing says what the edit was for, each verdict
16
+ * drawn after it is checked against nothing but itself, and that is true of the fifth one exactly as
17
+ * it was of the first. Reporting it once and then falling silent would leave four verdicts carrying
18
+ * a weakness they no longer disclose, which is the failure mode this whole surface exists to prevent.
19
+ *
20
+ * It is also not a backlog, because it is not a survey of the project: it is answered from what this
21
+ * session observed (an epoch that moved) about the verdict being returned right now, and it is
22
+ * closed by one call the agent can make immediately. A finding that stays open only because the
23
+ * agent has not made that call is a finding about the current verdict, not a chore.
24
+ *
25
+ * ## The silences, and why each one is required
26
+ *
27
+ * - **An edit was actually OBSERVED.** The epoch degrades to `NO_EDITS_OBSERVED` outside Vite, and
28
+ * that means "no edits observed", never "no edits happened" — a finding built on an absence
29
+ * Reticle cannot see would be a fabrication.
30
+ * - **The ledger holds nothing undischarged.** An open intent IS the declaration; silence is the
31
+ * correct response to it, and declaring one is what turns this off from the next verdict on.
32
+ *
33
+ * ## Why "covers the work" is answered as "anything open at all"
34
+ *
35
+ * The tempting answer is to match the intent's `surface` against the files that changed. Reticle
36
+ * cannot do that honestly: it observes that an epoch advanced, not WHAT advanced — the hot-update
37
+ * channel reports a counter, not a diff — so any file-level matching would be inference dressed as a
38
+ * fact, and it would fire on an agent that had declared its intent perfectly well. Over-reporting on
39
+ * a correct agent is the direction of error that trains it to stop reading findings.
40
+ *
41
+ * So the question asked is the coarse one, and it is asked honestly: has the agent declared anything
42
+ * that is still outstanding? If it has, it is working to a stated purpose and this stays quiet.
43
+ */
44
+ /**
45
+ * `openIntents` is a thunk rather than a list because reading the ledger touches disk, and the epoch
46
+ * check answers without it for every page that never reported a hot update — which is most of them.
47
+ * Once an edit HAS been observed the read happens per verdict, and that is the intended cost: the
48
+ * ledger is the only thing that can end it, so it has to be re-asked each time.
49
+ */
50
+ export async function isChangeUndeclared(
51
+ /** The epoch the session is under now. Undefined when the page never stamped one. */
52
+ currentEpoch, openIntents) {
53
+ if (undefined === currentEpoch || NO_EDITS_OBSERVED === currentEpoch)
54
+ return false;
55
+ return 0 === (await openIntents()).length;
56
+ }
@@ -76,6 +76,21 @@ interface VerifiedInputs {
76
76
  * Empty means nothing went unread.
77
77
  */
78
78
  outcomeUnread?: readonly string[];
79
+ /**
80
+ * The declared consequence that held does not depend on the response body — an exact string
81
+ * rendered, a store path, a signal. See `declaresBodyIndependentChannel`.
82
+ *
83
+ * The unread-body clause exists for the case where the body is the ONLY channel that could have
84
+ * contradicted the screen (a 200 with batch/GraphQL errors inside). When a body-independent
85
+ * declaration held, that is no longer true, and grading `unknown` costs a real verdict. The unread
86
+ * labels still ride out in `because`, the way absence-derived contradictions still ride out in
87
+ * `contradictions`.
88
+ *
89
+ * Narrow: it does not soften a 202, an observed contradiction, a dirty capture, or a net-only
90
+ * declaration. `declaredConsequence` alone is not enough — a `{ kind: "net", status: 200 }` is a
91
+ * declaration whose remaining channel IS the body.
92
+ */
93
+ independentOfBody?: boolean;
79
94
  /**
80
95
  * What the wait was for and what the window held when it ended — read ONLY by the two clauses that
81
96
  * answer UNSETTLED, which is the commonest reason a verdict comes back `unknown` and was also the
@@ -31,6 +31,24 @@ export function decideVerified(inputs) {
31
31
  'to repeat',
32
32
  };
33
33
  }
34
+ // ABOVE the failure clause, for the third time and the same reason: absence is not evidence.
35
+ //
36
+ // Omitting `until` waits for the page to SETTLE. Nobody declared a consequence, so a settle that
37
+ // times out says "the page was still busy when I stopped looking" — the definition of an
38
+ // absence-derived finding. The failure clause below took it at face value and returned
39
+ // `verified: "no"` with `because: 'the declared consequence did not hold'`, naming a consequence
40
+ // that does not exist. Measured on the hard fixture, whose push updates mean it never goes idle:
41
+ // a healthy pagination click was graded NO. Any app with polling or a live feed gets accused of a
42
+ // defect for being alive, which is the false NEGATIVE the ABSENCE_DERIVED doctrine exists to stop.
43
+ if (false === pass && false === inputs.declaredConsequence) {
44
+ return {
45
+ verified: Verified.UNKNOWN,
46
+ verifiedReason: VerifiedReason.UNSETTLED,
47
+ because: 'no consequence was declared, so this only waited for the page to go idle and it never ' +
48
+ 'did — that is a statement about when Reticle stopped looking, not about the app. Pass ' +
49
+ '`until` naming what the action should cause (a signal, a request, a route, or store state)',
50
+ };
51
+ }
34
52
  // A failed assertion is the most actionable fact there is; it leads — including over
35
53
  // `alreadyTrue`, because a condition that held before AND fails now is a real regression.
36
54
  if (false === pass) {
@@ -82,6 +100,27 @@ export function decideVerified(inputs) {
82
100
  inputs.unsettled !== undefined &&
83
101
  !openWrite &&
84
102
  contradictions.every((c) => c.kind === ContradictionKind.REQUEST_NEVER_SETTLED);
103
+ // `signal-without-consequence` gets its own sentence, because the generic one is FALSE for it.
104
+ //
105
+ // That sentence says the window "closed before the app finished" and then explains that a poll or
106
+ // a timer kept the page busy. Nothing kept this page busy — nothing happened at all, which is the
107
+ // entire finding. Measured on the bench fixture: the correct UNKNOWN arrived wearing an
108
+ // explanation that told the agent to wait longer or assert something else, when the fact to act on
109
+ // was that the app announced a consequence it did not deliver. A right verdict with a wrong reason
110
+ // sends an agent to the wrong place, which costs as much as the wrong verdict did.
111
+ const signalOnly = contradictions.length > 0 &&
112
+ contradictions.every((c) => c.kind === ContradictionKind.SIGNAL_WITHOUT_CONSEQUENCE);
113
+ if (signalOnly) {
114
+ return {
115
+ verified: Verified.UNKNOWN,
116
+ verifiedReason: VerifiedReason.EVIDENCE_INCOMPLETE,
117
+ because: 'the app fired the signal you waited for and NOTHING else moved — no DOM, no store, no ' +
118
+ 'route, no request — so the only evidence is the app saying so. A signal emitted from the ' +
119
+ 'value the app was ASKED for, rather than the one it committed, looks exactly like this. ' +
120
+ 'Check the consequence directly (reticle_snapshot for what rendered, reticle_state for what ' +
121
+ 'the store holds) before trusting it',
122
+ };
123
+ }
85
124
  if (contradictions.length > 0 && !settlementOnly) {
86
125
  const kinds = contradictions.map((c) => c.kind).join(', ');
87
126
  return {
@@ -142,7 +181,21 @@ export function decideVerified(inputs) {
142
181
  // Gated on `true === settled` rather than on a caller's say-so. A call that returned before the app
143
182
  // stopped moving has not earned this, whatever it declared — which is what stops `no-fault` from
144
183
  // becoming the green-forever button an always-available "nothing was wrong" would be.
145
- if (honesty.grade === HonestyGrade.NONE) {
184
+ // Two ways to have proved nothing, and only one of them used to be caught. Grade NONE is the
185
+ // engine's own reading — no signal, no request, no state change, nothing pinned. An explicit
186
+ // `declaredConsequence: false` is the CALLER saying it asserted nothing, which `act_and_wait`
187
+ // reports whenever `until` is omitted (an omitted `until` becomes `{kind:"settled"}` — a
188
+ // sleep-replacement, not an assertion).
189
+ //
190
+ // Grading alone missed the second case, because a click that merely moves the DOM lifts the grade
191
+ // to PRESENCE and walks past this branch into a full green. Measured live: clicking a submit
192
+ // button, and an inert text input, with no `until` both returned `verified:"yes"` /
193
+ // `verifiedReason:"proved"`. The claim it printed — "assertion held at presence grade" — was true
194
+ // about a grade and false about an assertion, because there was no assertion.
195
+ //
196
+ // It compounds: `reticle_context` persists proven claims, so an undeclared green becomes
197
+ // established fact for the agent that re-enters after a compaction.
198
+ if (honesty.grade === HonestyGrade.NONE || false === inputs.declaredConsequence) {
146
199
  if (true === settled) {
147
200
  return {
148
201
  verified: Verified.NO_FAULT,
@@ -178,7 +231,15 @@ export function decideVerified(inputs) {
178
231
  //
179
232
  // UNKNOWN, not NO: nothing is known to have failed. The remedy is in the sentence, because an
180
233
  // agent that cannot act on a caveat will learn to skip it.
181
- if (outcomeUnread !== undefined && outcomeUnread.length > 0) {
234
+ //
235
+ // Skipped when the caller declared a consequence that does not depend on the body and it held.
236
+ // The clause was written for the body being the ONLY remaining channel; a unique row on screen, a
237
+ // store path, or a signal that held is a different channel, and grading unknown there cost a real
238
+ // verdict (a 201 plus the exact message text, agent went to enable capture instead of finishing).
239
+ // The unread write still rides out in `because`. A net-only declaration does not skip: then the
240
+ // body is still the remaining channel.
241
+ const unreadHeldIndependently = declaredHeld && true === inputs.independentOfBody;
242
+ if (outcomeUnread !== undefined && outcomeUnread.length > 0 && !unreadHeldIndependently) {
182
243
  return {
183
244
  verified: Verified.UNKNOWN,
184
245
  verifiedReason: VerifiedReason.OUTCOME_UNREAD,
@@ -214,11 +275,14 @@ export function decideVerified(inputs) {
214
275
  const notIdle = false === settled
215
276
  ? ', though the page never went idle — this rests on the consequence you declared, not on the page going quiet'
216
277
  : '';
278
+ const unreadCaveat = unreadHeldIndependently && outcomeUnread !== undefined && outcomeUnread.length > 0
279
+ ? `, though a write returned 2xx with a response body that was never recorded (${outcomeUnread.join('; ')}) — a 200 describes the transport, not the result`
280
+ : '';
217
281
  return {
218
282
  verified: Verified.YES,
219
283
  verifiedReason: VerifiedReason.PROVED,
220
284
  because: true === honesty.coverage?.partial
221
- ? `assertion held at ${honesty.grade} grade with no channel disagreeing, but coverage was PARTIAL — see \`coverage\` for what went unobserved${notIdle}`
222
- : `assertion held at ${honesty.grade} grade over a clean capture with no channel disagreeing${notIdle}`,
285
+ ? `assertion held at ${honesty.grade} grade with no channel disagreeing, but coverage was PARTIAL — see \`coverage\` for what went unobserved${notIdle}${unreadCaveat}`
286
+ : `assertion held at ${honesty.grade} grade over a clean capture with no channel disagreeing${notIdle}${unreadCaveat}`,
223
287
  };
224
288
  }
@@ -50,4 +50,3 @@ export interface SharedServer {
50
50
  export declare function createSharedServer(options?: {
51
51
  token?: string;
52
52
  }): SharedServer;
53
- export declare function driveSchemeRefusal(url: string): string | undefined;
@@ -330,7 +330,7 @@ function schemeOf(url) {
330
330
  return undefined;
331
331
  }
332
332
  }
333
- export function driveSchemeRefusal(url) {
333
+ function driveSchemeRefusal(url) {
334
334
  const scheme = schemeOf(url);
335
335
  if (scheme !== undefined && DRIVEABLE_SCHEMES.includes(scheme))
336
336
  return undefined;
@@ -18,8 +18,6 @@
18
18
  * Pure functions over already-collected reports — the driving is `reticle_crawl`'s job, and running
19
19
  * it across a commit range is a shell loop. This is only the part that must not be done by eye.
20
20
  */
21
- /** The contradiction kinds — cross-channel disagreement, as opposed to a single-channel fault. */
22
- export declare const CONTRADICTION_KINDS: readonly string[];
23
21
  export interface HuntAnomaly {
24
22
  kind: string;
25
23
  ref?: string;
@@ -19,7 +19,7 @@
19
19
  * it across a commit range is a shell loop. This is only the part that must not be done by eye.
20
20
  */
21
21
  /** The contradiction kinds — cross-channel disagreement, as opposed to a single-channel fault. */
22
- export const CONTRADICTION_KINDS = [
22
+ const CONTRADICTION_KINDS = [
23
23
  'ui-advanced-request-failed',
24
24
  'signal-contradicted',
25
25
  'response-ignored',
@@ -3,11 +3,6 @@ export interface ImpactPaths {
3
3
  project: string;
4
4
  global: string;
5
5
  }
6
- /**
7
- * `reticleRoot` is the project's own `.reticle` directory (what every other store here is handed);
8
- * the global scope always lives beside the daemon's own state in `~/.reticle`.
9
- */
10
- export declare function impactPaths(reticleRoot: string): ImpactPaths;
11
6
  /** Read a scope, tolerating absence and corruption: a broken file starts over rather than throwing. */
12
7
  export declare function readScope(path: string, now: number): ImpactScope;
13
8
  /** YYYY-MM-DD in local time - the day boundary a person recognises, not UTC's. */
@@ -18,7 +18,7 @@ const WRITE_DEBOUNCE_MS = 800;
18
18
  * `reticleRoot` is the project's own `.reticle` directory (what every other store here is handed);
19
19
  * the global scope always lives beside the daemon's own state in `~/.reticle`.
20
20
  */
21
- export function impactPaths(reticleRoot) {
21
+ function impactPaths(reticleRoot) {
22
22
  return {
23
23
  project: join(reticleRoot, ReticleDir.IMPACT_FILE),
24
24
  global: join(homedir(), ReticleDir.ROOT, ReticleDir.IMPACT_FILE),
package/dist/index.d.ts CHANGED
@@ -117,7 +117,10 @@ export interface StartOptions {
117
117
  pairingTokenDir?: string;
118
118
  /** injectable clock for contract.json's generatedAt stamp. Defaults to Date.now. */
119
119
  now?: () => number;
120
- /** 'core' exposes the lean tool surface. Defaults to env RETICLE_TOOL_PROFILE, else 'full'. */
120
+ /**
121
+ * Retired profile name (`core`/`full`/…). Old values still map. The live switch is
122
+ * RETICLE_ADVERTISE_ALL_TOOLS=1; the default is the lean surface.
123
+ */
121
124
  toolProfile?: string;
122
125
  /** Start the OEM/CI verify HTTP endpoint alongside the daemon (`reticle serve --http`). */
123
126
  httpVerify?: boolean;
@@ -36,6 +36,15 @@ export declare const AGENTS_MD_PATH = "AGENTS.md";
36
36
  export declare const RETICLE_MD_PATH = "RETICLE.md";
37
37
  /** Cursor project rule; the frontmatter `alwaysApply` keeps it in context on every turn. */
38
38
  export declare const CURSOR_RULE_PATH = ".cursor/rules/reticle.mdc";
39
+ /**
40
+ * Does this file already carry the managed block?
41
+ *
42
+ * Exported so callers cannot invent their own looser test. The begin marker carries explanatory
43
+ * text inside the comment, so a substring check for `reticle:begin` matches files that
44
+ * `mergeMarkedInstruction` would treat as having NO block — and the two disagreeing means a refresh
45
+ * silently appends a second copy instead of updating the first.
46
+ */
47
+ export declare function hasManagedBlock(text: string): boolean;
39
48
  /**
40
49
  * How the agent must INVOKE the CLI.
41
50
  *
@@ -67,6 +76,14 @@ export declare const CLI: string;
67
76
  * Exported because slash-command.ts renders the same rule; there must not be two versions of it.
68
77
  */
69
78
  export declare const DEV_SERVER_POLICY = "**If no dev server is listening, start one yourself.** Read the project's own dev script out of `package.json` (`dev`, `start`, whatever this project calls it), run it in the BACKGROUND, tell the user in one line that it is running and how to stop it, then carry on. Stopping to ask is how a verification turn ends with nothing verified.\n\nFive guards, none optional:\n\n1. **Never start a second one.** If something is already listening on the app's port, use it.\n2. **Never guess the command.** It comes from `package.json` scripts. If there is no recognisable dev script, say so and stop rather than inventing one.\n3. **Never kill anything.** Not a dev server, not a daemon, not a port holder \u2014 including one you started.\n4. **Background it, and say so.** The user must know a server is running and how to stop it. A dev server the human does not know about is the same failure one step later.\n5. **The permission prompt belongs to your host.** Never try to bypass, suppress or auto-approve it, and take a refusal as the answer.";
79
+ /**
80
+ * The operative rules, carried by every file that loads on every turn.
81
+ *
82
+ * Short on purpose. This is the text that has to CHANGE BEHAVIOUR, so it states when to verify, when
83
+ * NOT to, how, and the guards against calling something proven when it is not. Everything an agent
84
+ * needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
85
+ */
86
+ export declare const RULE_BODY: string;
70
87
  /** The full-rules file, written verbatim (no markers: `init` owns this file entirely). */
71
88
  export declare function reticleMdFile(): string;
72
89
  export declare const AgentRuleStatus: {
@@ -41,6 +41,17 @@ export const CURSOR_RULE_PATH = '.cursor/rules/reticle.mdc';
41
41
  /** Markers delimiting the managed block so a re-run is idempotent and the block stays updatable. */
42
42
  const RULE_BEGIN = '<!-- reticle:begin (managed by `reticle init` — edit outside these markers) -->';
43
43
  const RULE_END = '<!-- reticle:end -->';
44
+ /**
45
+ * Does this file already carry the managed block?
46
+ *
47
+ * Exported so callers cannot invent their own looser test. The begin marker carries explanatory
48
+ * text inside the comment, so a substring check for `reticle:begin` matches files that
49
+ * `mergeMarkedInstruction` would treat as having NO block — and the two disagreeing means a refresh
50
+ * silently appends a second copy instead of updating the first.
51
+ */
52
+ export function hasManagedBlock(text) {
53
+ return text.includes(RULE_BEGIN);
54
+ }
44
55
  /**
45
56
  * How the agent must INVOKE the CLI.
46
57
  *
@@ -87,7 +98,7 @@ Five guards, none optional:
87
98
  * NOT to, how, and the guards against calling something proven when it is not. Everything an agent
88
99
  * needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
89
100
  */
90
- const RULE_BODY = `## Verifying with Reticle
101
+ export const RULE_BODY = `## Verifying with Reticle
91
102
 
92
103
  This app is instrumented by **Reticle**, an in-app verification layer exposed as \`reticle_*\` MCP tools and the \`${CLI}\` CLI (always through npx: Reticle's server is not installed into this project). Verifying is part of "done", not an optional extra.
93
104
 
@@ -103,21 +114,40 @@ This app is instrumented by **Reticle**, an in-app verification layer exposed as
103
114
  - **Only \`reticle_act_and_wait\` and \`reticle_assert\` produce a verdict.** \`reticle_act\` and everything else move or read the app and prove nothing, so a session ending without one of those two has no result however many tools it used.
104
115
  - Covered flows: \`${CLI} gate\` reports which recorded flows the changed files affect and whether they still pass.
105
116
 
117
+ **Setting Reticle up, or just restarted? You are mid-sequence — do not stop until a verdict exists.**
118
+ The whole of it is: instrument the app → get a dev server running → open the app in a browser →
119
+ drive one flow → report the verdict. Every step is yours to do, and none of them needs the user.
120
+ The ONE legitimate pause is a client restart, because a client reads its server list at startup;
121
+ when you come back, you are not finished, you are on the next step — resume it without being asked.
122
+ Stopping anywhere else leaves an app that looks installed and can verify nothing, which is the
123
+ single commonest way this goes wrong. \`/reticle\` carries the full recovery ladder when a step
124
+ does not work; use it rather than guessing, and never report the install as finished without a
125
+ verdict to point at.
126
+
106
127
  **Nothing connected? Get the app running.**
107
128
 
108
129
  ${DEV_SERVER_POLICY}
109
130
 
110
131
  A dev server that is already running does not pick up an edited build config or a newly created plugin file — restart it and hard-reload the tab. And if a server IS listening and still nothing connects, the cause is the SDK not loading in the page, not a missing dev server; do not tell the user to start one they are already running.
111
132
 
133
+ **Finish \`src/reticle-dev.ts\` before you claim setup is done.** \`init\` writes it and cannot always fill it in: a store that needs an argument only reading the code supplies (Jotai atoms, an XState actor, a TanStack \`queryClient\`) is left as a commented \`registerStore\` line. A file that registers nothing looks exactly like a finished one, and \`reticle_state\` then returns empty forever — which is indistinguishable from an app that has nothing to report, so it reads as success. Uncomment the line, complete it, and prove it by driving one flow and seeing your keys come back. If \`init\` told you to restart your client, this is the job waiting for you on the other side of that restart.
134
+
135
+ **Verify each feature as you finish it, not all of them at the end.** Asked for four, build one, drive it, get a verdict, then start the second. A red verdict after four builds has four suspects; after one it has none.
136
+
137
+ **Capture what a change is FOR while you are building it, not afterwards.** \`reticle_intent\` records the business outcome a change is meant to produce, and the only moment anybody knows it is while the change is being made. Pass \`intent\` when you save a flow, so the saved flow carries the reason it exists. A flow without one replays for months and then reports "step 3 failed" instead of what stopped being true for a user.
138
+
112
139
  **Honesty, which is the whole point:**
113
140
 
114
141
  - **\`verified: "unknown"\` is not a pass.** It means Reticle drove the app and could not tell what happened; \`verifiedReason\` says which clause decided that. Report it as unknown, never as working.
142
+ - **\`verified: "no-fault"\` is not a pass either.** It means nothing was DECLARED to prove: the page settled and no channel complained, but you asserted nothing, so there is no verification. You get it whenever \`until\` is omitted. Name a consequence the action changes — a signal, a request, a route, or store state — and call again.
115
143
  - **Never weaken a check to make it green.** Downgrading, skipping or deleting an assertion is a finding, not a fix.
116
144
  - **If Reticle cannot run** (no daemon, or this is not a running web app), say so. Do not skip verification silently.
117
145
  - **Setup is not finished until one real flow has been driven and produced a verdict.** \`init\` exiting 0, the tools appearing, and a session being listed are all things that happen before anything has been verified.
118
146
 
119
147
  **The \`/reticle\` skill runs this whole loop for you** — detect, connect, drive one flow, report. If your client does not have it, install it once: \`/plugin marketplace add reticlehq/reticle\` then \`/plugin install reticle@reticlehq\` in Claude Code, or \`npx skills add reticlehq/reticle\` anywhere the skills CLI works.
120
148
 
149
+ **A tool you need is missing? It exists.** The default surface advertises a subset; reach any other by name with \`reticle_run { tool, args }\`, and list them with \`reticle_tools\`. Two worth knowing: \`reticle_context\` returns this run's own memory — what is established, what is proven, what is still unverified — which is what you want after a compaction or when picking up work you did not start; \`reticle_intent\` records what a change was MEANT to do, while somebody still knows.
150
+
121
151
  **Report Reticle's own defects with \`reticle_feedback\` the moment you notice**, then carry on with your task. You are the user Reticle is built for and the only one who can say what it cost you, and that knowledge is gone when your context is.
122
152
 
123
153
  📄 **The rest is in [RETICLE.md](./${RETICLE_MD_PATH}): what to do when the tools are missing, when a result carries \`version_skew\` or \`update_available\`, when \`reticle_state\` comes back empty, and how to write a feedback report that can be acted on. Read it when you hit one of those, not before.**`;
@@ -24,8 +24,6 @@ export interface FoundStore {
24
24
  /** Module specifier relative to `src/reticle-dev.ts`. */
25
25
  importPath: string;
26
26
  }
27
- /** Where the generated dev module lives, for Vite. Next passes its own. */
28
- export declare const DEFAULT_DEV_MODULE_DIR = "src";
29
27
  /**
30
28
  * Store instances declared in the app's own source, ready to import and register.
31
29
  *
@@ -102,7 +102,7 @@ function importPathFor(storePath, fromDir) {
102
102
  return rel.startsWith('.') ? rel : `./${rel}`;
103
103
  }
104
104
  /** Where the generated dev module lives, for Vite. Next passes its own. */
105
- export const DEFAULT_DEV_MODULE_DIR = 'src';
105
+ const DEFAULT_DEV_MODULE_DIR = 'src';
106
106
  /**
107
107
  * Store instances declared in the app's own source, ready to import and register.
108
108
  *
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The last thing `init` prints, and the only part of it a user is asked to act on.
3
+ *
4
+ * Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
5
+ * this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
6
+ * what to say afterwards. The two failure modes are different too — a wrong write breaks an
7
+ * install, a wrong sentence loses a user quietly.
8
+ */
9
+ import { Framework } from './detect.js';
10
+ import { StepStatus } from './plan.js';
11
+ /**
12
+ * The standing ask, printed at the end of every `init`.
13
+ *
14
+ * Addressed to the agent as much as the human: whichever of them just ran this command is the one
15
+ * holding the experience, and neither has a Reticle tool surface to file through at this point.
16
+ *
17
+ * Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
18
+ * the workspace redirect is kept from asking twice.
19
+ */
20
+ export declare const FEEDBACK_HINT: string;
21
+ /**
22
+ * The last two lines a user reads, and the order matters.
23
+ *
24
+ * This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
25
+ * asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
26
+ * an agent client reads its tool list when it STARTS and never re-reads it, so the session that
27
+ * just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
28
+ * instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
29
+ * failed.
30
+ *
31
+ * So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
32
+ * run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
33
+ */
34
+ export declare function restartHint(framework: Framework, mcpStatus: StepStatus | undefined,
35
+ /** The project's own dev command, when package.json named one. */
36
+ devCommand?: string): string;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The last thing `init` prints, and the only part of it a user is asked to act on.
3
+ *
4
+ * Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
5
+ * this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
6
+ * what to say afterwards. The two failure modes are different too — a wrong write breaks an
7
+ * install, a wrong sentence loses a user quietly.
8
+ */
9
+ import { Framework } from './detect.js';
10
+ import { StepStatus } from './plan.js';
11
+ import { wasMcpRegistered } from './mcp-registered.js';
12
+ import { CLI } from './agent-rules.js';
13
+ /**
14
+ * The standing ask, printed at the end of every `init`.
15
+ *
16
+ * Addressed to the agent as much as the human: whichever of them just ran this command is the one
17
+ * holding the experience, and neither has a Reticle tool surface to file through at this point.
18
+ *
19
+ * Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
20
+ * the workspace redirect is kept from asking twice.
21
+ */
22
+ export const FEEDBACK_HINT = 'Anything wrong, missing, or awkward — in this setup or in Reticle itself? Tell us; it is the ' +
23
+ 'one thing that decides what gets fixed:\n' +
24
+ ` ${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" (agents)\n` +
25
+ ` ${CLI} feedback "what worked, what didn't" (humans)`;
26
+ /**
27
+ * What to say about the dev server.
28
+ *
29
+ * Names the project's own script when `package.json` has a recognisable one, and otherwise says the
30
+ * THING rather than guessing the command.
31
+ *
32
+ * The guesses that used to live here — `vite`, `next dev`, `astro dev` — were wrong often enough to
33
+ * be worth deleting. Measured on a real app: rowy has no `dev` script at all, so "Restart `vite`"
34
+ * sent the reader to work out what was meant instead of starting a server. Nobody needs the guess:
35
+ * an agent reading this can open package.json, and a human already knows how they start their own
36
+ * app. A wrong command costs more than a missing one.
37
+ */
38
+ function devServerRestart(_framework, devCommand) {
39
+ return devCommand === undefined
40
+ ? 'Restart your dev server'
41
+ : `Restart your dev server (\`${devCommand}\`)`;
42
+ }
43
+ /**
44
+ * The last two lines a user reads, and the order matters.
45
+ *
46
+ * This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
47
+ * asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
48
+ * an agent client reads its tool list when it STARTS and never re-reads it, so the session that
49
+ * just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
50
+ * instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
51
+ * failed.
52
+ *
53
+ * So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
54
+ * run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
55
+ */
56
+ export function restartHint(framework, mcpStatus,
57
+ /** The project's own dev command, when package.json named one. */
58
+ devCommand) {
59
+ const dev = `${devServerRestart(framework, devCommand)}.`;
60
+ // NAME THE COMMAND THAT PROVES IT, not one that merely asks.
61
+ //
62
+ // `init` writes files and stops; the install is not finished until an app carrying the SDK has
63
+ // actually dialled the daemon, and nothing here confirmed that. The field shape is unambiguous:
64
+ // people complete the agent half, never complete the app half, and keep a daemon running for
65
+ // weeks with nothing to drive — so this is the last instruction most of them read.
66
+ //
67
+ // "Ask your agent: List Reticle sessions" asks a question whose failure is a dead end. `reticle
68
+ // status` ANSWERS it: as of 2.7.0 it reports the session, or says why there is none — no app
69
+ // running, an app running that never dialled us, a tab that closed — with the fix for each.
70
+ // The one thing that works BEFORE the restart, so it goes before it.
71
+ //
72
+ // What comes after the restart, named as a JOB rather than as a capability.
73
+ //
74
+ // This used to point at a `reticle demo` command that opened its own browser and drove one
75
+ // control. It was cut: a demo command is a side show. The thing a user needs to see is THEIR app,
76
+ // instrumented in THEIR codebase, driven by THEIR agent through the tools they just installed —
77
+ // because that is the workflow they are deciding whether to adopt, and a bespoke demo path proves
78
+ // nothing about it.
79
+ //
80
+ // So the close hands the agent the sequence instead. It is deliberately one drive of one real
81
+ // flow ending in a verdict, because an install that ends in "the tools are available" is an
82
+ // install that ends in nothing.
83
+ const demo = 'Then drive one real flow of your app end to end and report the verdict — that is the install ' +
84
+ "proving itself, and it is the agent's job, not a separate command.";
85
+ const prove = 'Then run `npx @reticlehq/server status` — it confirms the app connected, or says exactly why ' +
86
+ 'it has not.';
87
+ // Nothing registered this run (`--no-mcp`, or a client that needs a manual edit): a restart picks
88
+ // up nothing, so advice about one would be advice about something we did not do.
89
+ if (!wasMcpRegistered(mcpStatus))
90
+ return `${dev}\n${demo}\n${prove}`;
91
+ // ALREADY: the tools are reachable RIGHT NOW, and this is the branch that used to lie.
92
+ //
93
+ // `wasMcpRegistered` is true for both APPLY and ALREADY, which is correct for the funnel field it
94
+ // was written for and wrong here, because the two cases have opposite answers to "must anybody
95
+ // restart". Every second project on a machine, and every Claude Code plugin install, took the
96
+ // APPLY text: "restart your agent — the tools only appear after that". An agent reads this AFTER
97
+ // whatever its skill file said, and it describes the output in front of it, so it wins. The agent
98
+ // stops with the project wired and nothing driven, which is the exact failure the install path
99
+ // exists to prevent, caused by the install path itself.
100
+ if (mcpStatus === StepStatus.ALREADY) {
101
+ return (`${dev}\n` +
102
+ `${demo}\n` +
103
+ 'The Reticle MCP server was already registered on this machine, so there is no restart to do ' +
104
+ 'and the tools are available now.\n' +
105
+ `${prove}\n` +
106
+ 'Once it shows a session, drive a flow — that is the install finished. Carry straight on; ' +
107
+ 'nothing here is waiting on a human.');
108
+ }
109
+ return (`${dev}\n` +
110
+ `${demo}\n` +
111
+ 'Then restart your agent so it picks up the new MCP server — restart Claude Code, reload the ' +
112
+ 'window in Cursor, or hit Start in `.vscode/mcp.json` in VS Code.\n' +
113
+ 'The tools only appear after that: your agent read its server list before Reticle existed, ' +
114
+ 'and no slash command re-reads it (`/mcp` manages servers already loaded, so it cannot pick ' +
115
+ 'up a new one). This is once per machine — Reticle is registered globally, so every later ' +
116
+ 'project starts with the tools already there.\n' +
117
+ `${prove}\n` +
118
+ 'Once it shows a session, drive one flow and report the verdict — that is the install ' +
119
+ 'finished. Do it now; nothing here is waiting on a human.');
120
+ }
@@ -27,15 +27,6 @@
27
27
  */
28
28
  import { InitConfirmation } from '@reticlehq/core';
29
29
  import type { InitOutcome } from '@reticlehq/core';
30
- /**
31
- * How long a human is asked to wait.
32
- *
33
- * Long enough for a page that is already loading to finish dialling, short enough that somebody who
34
- * has not restarted their dev server yet is not held hostage by a wait that cannot succeed — they
35
- * have not typed the command that would make it succeed, and the message tells them which one.
36
- */
37
- export declare const CONFIRM_WINDOW_MS = 12000;
38
- export declare const CONFIRM_POLL_MS = 500;
39
30
  export interface ConfirmDeps {
40
31
  /** Session ids connected right now, or null when nothing is listening on the bridge port. */
41
32
  listSessionIds: () => Promise<readonly string[] | null>;
@@ -78,22 +69,6 @@ export declare function confirmAppConnected(deps: ConfirmDeps): Promise<InitConf
78
69
  * up when the agent loads the tools — so it is a notice.
79
70
  */
80
71
  export declare function confirmationMessage(confirmation: InitConfirmation, port: number): string;
81
- /**
82
- * What a non-TTY run says instead of waiting.
83
- *
84
- * `init` deliberately does not block when nobody is at the terminal — a 12-second wait in a script
85
- * is wrong, and an agent driving `init` through a shell is never a TTY. But silence was the wrong
86
- * other half of that decision: the agent path IS the prescribed path (every skill and every README
87
- * block tells an agent to run exactly this command), so the one message joining "init finished" to
88
- * "an app connected" was withheld from most of the people who run it. They were left with files on
89
- * disk, an exit code of 0, and nothing saying the page half had not happened.
90
- *
91
- * It claims nothing it did not check. It did not look for a session, so it does not say whether one
92
- * exists — a one-shot look would be worse than none, because a daemon is shared across every project
93
- * on the machine and "a session exists" is satisfied by somebody else's app, which is the false
94
- * green `confirmAppConnected` refuses by design.
95
- */
96
- export declare function unwatchedMessage(port: number): string;
97
72
  /**
98
73
  * Finish `init` on evidence rather than on a write.
99
74
  *
@@ -35,8 +35,8 @@ import { reportInitOutcome } from '../telemetry/init-telemetry.js';
35
35
  * has not restarted their dev server yet is not held hostage by a wait that cannot succeed — they
36
36
  * have not typed the command that would make it succeed, and the message tells them which one.
37
37
  */
38
- export const CONFIRM_WINDOW_MS = 12_000;
39
- export const CONFIRM_POLL_MS = 500;
38
+ const CONFIRM_WINDOW_MS = 12_000;
39
+ const CONFIRM_POLL_MS = 500;
40
40
  /**
41
41
  * Poll until an app connects or the window runs out.
42
42
  *
@@ -110,7 +110,7 @@ export function confirmationMessage(confirmation, port) {
110
110
  * on the machine and "a session exists" is satisfied by somebody else's app, which is the false
111
111
  * green `confirmAppConnected` refuses by design.
112
112
  */
113
- export function unwatchedMessage(port) {
113
+ function unwatchedMessage(port) {
114
114
  return (' [ℹ] the files are written. An app CONNECTING is what finishes the install, and this run did ' +
115
115
  'not wait to see one.\n' +
116
116
  ' Start the dev server, load the app in a browser, then ' +
@@ -17,6 +17,19 @@ export declare const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
17
17
  export declare const CRA_DEV_MODULE_PATH = "src/reticle-dev.ts";
18
18
  export declare const CRA_ENV_PATH = ".env.development.local";
19
19
  export declare const TOKEN_VAR = "REACT_APP_RETICLE_TOKEN";
20
+ /**
21
+ * The bridge URL, carried through the same channel as the token.
22
+ *
23
+ * CRA is the one supported stack with no build-time hook of ours: no plugin, no config wrapper, no
24
+ * `define`. Vite and Astro re-resolve the daemon every dev-server start and Next does it through
25
+ * `withReticle`; CRA has nowhere to run that code. So the URL goes where the token already goes, and
26
+ * is refreshed by the same `init` step that refreshes the token.
27
+ *
28
+ * That makes `reticle init` the heal rather than the dev server, which is weaker and is stated
29
+ * plainly in the step's own detail. It is still strictly better than a URL frozen into a source file
30
+ * the owner has to hand-edit, and `.env.development.local` is already the per-machine file.
31
+ */
32
+ export declare const URL_VAR = "REACT_APP_RETICLE_URL";
20
33
  /**
21
34
  * What the app itself says when the token is not there.
22
35
  *
@@ -45,6 +58,6 @@ export declare const CRA_TOKEN_PER_MACHINE_NOTICE: string;
45
58
  /** Add the import after the last existing one, or null when it is already present. */
46
59
  export declare function craImportPatch(source: string): string | null;
47
60
  /** Set the token variable, or null when nothing needs to change. */
48
- export declare function craEnvPatch(existing: string | null, token: string): string | null;
61
+ export declare function craEnvPatch(existing: string | null, token: string, url?: string): string | null;
49
62
  /** The dev-only connect module imported from `src/index.tsx`. */
50
63
  export declare function craDevModuleFile(port: number | undefined, projectId?: string): string;