@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,67 @@
1
+ /**
2
+ * Two durable bits about the CLIENT side of the install: has an MCP client ever started Reticle's
3
+ * MCP server on this port, and has one ever asked it for the tool list.
4
+ *
5
+ * Some hosts register the server, start it, and then never send `tools/list` until the client is
6
+ * restarted. The agent has no Reticle tools, the user has no message, and from our side that install
7
+ * is byte-identical to one somebody wired and abandoned: daemon idle, no sessions, nothing to say.
8
+ * `status` could report "registered" and "connected"; it could not report the state in between,
9
+ * which is the one with an answer ("restart your client").
10
+ *
11
+ * Both bits record something that HAPPENED — the proxy ran, a `tools/list` arrived — never an
12
+ * inference from a config file. A config entry proves somebody wrote a line of JSON; it does not
13
+ * prove a client ever read it, which is precisely the thing in question here.
14
+ *
15
+ * Deliberately the same shape as `connection-memory.ts`: one small JSON file per port under the
16
+ * daemon's state home, best-effort in both directions. It is a HINT, so every failure degrades to
17
+ * "we do not know" rather than throwing — a diagnostic that can crash the process it is diagnosing
18
+ * is worse than no diagnostic at all.
19
+ *
20
+ * KNOWN LIMIT, and why the messages below say so out loud: the record is per PORT, not per client.
21
+ * Two clients on one machine share it, so a healthy one masks a broken one, and the record itself
22
+ * only begins on the version that introduced it — a long-working install reads as never attached
23
+ * until its client next starts the server. Both limits are stated in the text the user reads,
24
+ * because an honest "here is what I can see, here is what to check" beats a confident wrong no.
25
+ */
26
+ /** What the client side of this install has been observed doing on this port. */
27
+ export declare const AttachState: {
28
+ /** No client has started Reticle's MCP server here, as far as this record goes. */
29
+ readonly NEVER_ATTACHED: "never-attached";
30
+ /** A client started it and never asked for the tool list — the state that needs a restart. */
31
+ readonly NEVER_ENUMERATED: "never-enumerated";
32
+ /** Started and enumerated. Nothing to say. */
33
+ readonly ENUMERATED: "enumerated";
34
+ };
35
+ export type AttachState = (typeof AttachState)[keyof typeof AttachState];
36
+ /** Record that an MCP client started the proxy on this port. */
37
+ export declare function rememberProxyStarted(stateDir: string, port: number): void;
38
+ /**
39
+ * Record that a client asked for the tool list on this port.
40
+ *
41
+ * Enumeration implies attachment — a `tools/list` cannot arrive at a server nobody started — so this
42
+ * sets both bits, and a record written by an older proxy that only ever set one is still read
43
+ * correctly.
44
+ */
45
+ export declare function rememberEnumerated(stateDir: string, port: number): void;
46
+ /** Which of the three client-side states this port is in. */
47
+ export declare function attachState(stateDir: string, port: number): AttachState;
48
+ /**
49
+ * The sentence to print for a state, or `undefined` when there is nothing to fix.
50
+ *
51
+ * The healthy case has to stay silent: advice printed beside a working client reads as though
52
+ * something is still wrong, which is its own kind of lie.
53
+ */
54
+ export declare function describeAttachState(state: AttachState): string | undefined;
55
+ /** What `reticle status` reports about the client side of the install. */
56
+ export interface AttachStatusFields {
57
+ mcpClient: AttachState;
58
+ mcpClientAction?: string;
59
+ }
60
+ /**
61
+ * The client-side fields for one `status` run.
62
+ *
63
+ * The state is reported on EVERY run, healthy included — "the client has read the tools" is the
64
+ * fact that was missing, and reporting it only when broken leaves the reader unable to tell a
65
+ * healthy client from a version too old to look. The advice rides along only when there is any.
66
+ */
67
+ export declare function attachStatusFields(stateDir: string, port: number): AttachStatusFields;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Two durable bits about the CLIENT side of the install: has an MCP client ever started Reticle's
3
+ * MCP server on this port, and has one ever asked it for the tool list.
4
+ *
5
+ * Some hosts register the server, start it, and then never send `tools/list` until the client is
6
+ * restarted. The agent has no Reticle tools, the user has no message, and from our side that install
7
+ * is byte-identical to one somebody wired and abandoned: daemon idle, no sessions, nothing to say.
8
+ * `status` could report "registered" and "connected"; it could not report the state in between,
9
+ * which is the one with an answer ("restart your client").
10
+ *
11
+ * Both bits record something that HAPPENED — the proxy ran, a `tools/list` arrived — never an
12
+ * inference from a config file. A config entry proves somebody wrote a line of JSON; it does not
13
+ * prove a client ever read it, which is precisely the thing in question here.
14
+ *
15
+ * Deliberately the same shape as `connection-memory.ts`: one small JSON file per port under the
16
+ * daemon's state home, best-effort in both directions. It is a HINT, so every failure degrades to
17
+ * "we do not know" rather than throwing — a diagnostic that can crash the process it is diagnosing
18
+ * is worse than no diagnostic at all.
19
+ *
20
+ * KNOWN LIMIT, and why the messages below say so out loud: the record is per PORT, not per client.
21
+ * Two clients on one machine share it, so a healthy one masks a broken one, and the record itself
22
+ * only begins on the version that introduced it — a long-working install reads as never attached
23
+ * until its client next starts the server. Both limits are stated in the text the user reads,
24
+ * because an honest "here is what I can see, here is what to check" beats a confident wrong no.
25
+ */
26
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
27
+ import { join } from 'node:path';
28
+ /** What the client side of this install has been observed doing on this port. */
29
+ export const AttachState = {
30
+ /** No client has started Reticle's MCP server here, as far as this record goes. */
31
+ NEVER_ATTACHED: 'never-attached',
32
+ /** A client started it and never asked for the tool list — the state that needs a restart. */
33
+ NEVER_ENUMERATED: 'never-enumerated',
34
+ /** Started and enumerated. Nothing to say. */
35
+ ENUMERATED: 'enumerated',
36
+ };
37
+ /**
38
+ * What to do about a client that started Reticle and never listed its tools.
39
+ *
40
+ * Names the cause AND the action, like the rest of the doctor/status vocabulary, and names its own
41
+ * blind spot: this record cannot see WHICH client asked, so it must not tell somebody running two
42
+ * of them that the one in front of them is the broken one.
43
+ */
44
+ const NEVER_ENUMERATED_ACTION = 'an MCP client started Reticle on this port and never asked for the tool list, so the tools are ' +
45
+ 'here and your client has not read them — restart your MCP client, which is what makes it list ' +
46
+ 'them again. This record is per port, not per client, so with more than one client registered ' +
47
+ 'the one that never listed may not be the one you are looking at.';
48
+ /**
49
+ * What to do when nothing has ever attached.
50
+ *
51
+ * The hedge is not padding. This record begins the first time a client starts the server on a
52
+ * version that keeps it, so an install that has worked for months reads as never attached until its
53
+ * client next starts Reticle — and telling that user their server is unregistered would send them
54
+ * to re-run `init` over a working setup.
55
+ */
56
+ const NEVER_ATTACHED_ACTION = 'no MCP client has started Reticle on this port, so either the server is not registered with your ' +
57
+ 'client or nothing has attached since this record began — register it with `npx ' +
58
+ '@reticlehq/server init`, then restart your MCP client and run status again.';
59
+ const EMPTY = { attached: false, enumerated: false };
60
+ function memoryPath(stateDir, port) {
61
+ return join(stateDir, `attach-${String(port)}.json`);
62
+ }
63
+ /** The record for a port. Empty on absent, unreadable, or malformed state. */
64
+ function read(stateDir, port) {
65
+ try {
66
+ const parsed = JSON.parse(readFileSync(memoryPath(stateDir, port), 'utf8'));
67
+ if ('object' !== typeof parsed || null === parsed)
68
+ return EMPTY;
69
+ const record = parsed;
70
+ return {
71
+ attached: true === record.attached,
72
+ enumerated: true === record.enumerated,
73
+ };
74
+ }
75
+ catch {
76
+ return EMPTY;
77
+ }
78
+ }
79
+ /** Write the record back, best-effort. A hint must never be the reason a proxy fails to serve. */
80
+ function write(stateDir, port, record) {
81
+ try {
82
+ mkdirSync(stateDir, { recursive: true });
83
+ writeFileSync(memoryPath(stateDir, port), JSON.stringify(record), 'utf8');
84
+ }
85
+ catch {
86
+ // An unwritable state home is already reported elsewhere; it must not break the client path.
87
+ }
88
+ }
89
+ /** Record that an MCP client started the proxy on this port. */
90
+ export function rememberProxyStarted(stateDir, port) {
91
+ const known = read(stateDir, port);
92
+ if (known.attached)
93
+ return;
94
+ write(stateDir, port, { ...known, attached: true });
95
+ }
96
+ /**
97
+ * Record that a client asked for the tool list on this port.
98
+ *
99
+ * Enumeration implies attachment — a `tools/list` cannot arrive at a server nobody started — so this
100
+ * sets both bits, and a record written by an older proxy that only ever set one is still read
101
+ * correctly.
102
+ */
103
+ export function rememberEnumerated(stateDir, port) {
104
+ const known = read(stateDir, port);
105
+ if (known.enumerated)
106
+ return;
107
+ write(stateDir, port, { attached: true, enumerated: true });
108
+ }
109
+ /** Which of the three client-side states this port is in. */
110
+ export function attachState(stateDir, port) {
111
+ const known = read(stateDir, port);
112
+ if (known.enumerated)
113
+ return AttachState.ENUMERATED;
114
+ return known.attached ? AttachState.NEVER_ENUMERATED : AttachState.NEVER_ATTACHED;
115
+ }
116
+ /**
117
+ * The sentence to print for a state, or `undefined` when there is nothing to fix.
118
+ *
119
+ * The healthy case has to stay silent: advice printed beside a working client reads as though
120
+ * something is still wrong, which is its own kind of lie.
121
+ */
122
+ export function describeAttachState(state) {
123
+ if (state === AttachState.NEVER_ENUMERATED)
124
+ return NEVER_ENUMERATED_ACTION;
125
+ if (state === AttachState.NEVER_ATTACHED)
126
+ return NEVER_ATTACHED_ACTION;
127
+ return undefined;
128
+ }
129
+ /**
130
+ * The client-side fields for one `status` run.
131
+ *
132
+ * The state is reported on EVERY run, healthy included — "the client has read the tools" is the
133
+ * fact that was missing, and reporting it only when broken leaves the reader unable to tell a
134
+ * healthy client from a version too old to look. The advice rides along only when there is any.
135
+ */
136
+ export function attachStatusFields(stateDir, port) {
137
+ const state = attachState(stateDir, port);
138
+ const action = describeAttachState(state);
139
+ // OMITTED rather than undefined: `exactOptionalPropertyTypes` is on, and an absent key is also
140
+ // what keeps the healthy line to one fact.
141
+ return { mcpClient: state, ...(action === undefined ? {} : { mcpClientAction: action }) };
142
+ }
@@ -47,13 +47,6 @@ export declare class PendingRequests {
47
47
  * with the MCP server still perfectly alive. An agent cannot tell that apart from "still working".
48
48
  */
49
49
  export declare function streamLossReplies(pending: PendingRequests, reason: string, nextStep?: string): string[];
50
- /**
51
- * The one shape every "the transport ate your call" answer takes, whichever leg died.
52
- *
53
- * `nextStep` is appended when the caller knows one. Without it this reply describes a condition and
54
- * stops there, which is all a first-run user got out of their first ever tool call.
55
- */
56
- export declare function transportLossReply(id: unknown, reason: string, nextStep?: string): string;
57
50
  /**
58
51
  * How long a request may sit in the queue, unsent, before the proxy answers it itself.
59
52
  *
@@ -1,6 +1,7 @@
1
1
  import * as http from 'node:http';
2
2
  import { localInitializeResponse, isHandshakeLine, drainLines, MAX_STDIN_LINE_BYTES, } from './proxy-handshake.js';
3
- import { ToolCatalogCache } from './tool-catalog-cache.js';
3
+ import { isToolsListRequest, ToolCatalogCache } from './tool-catalog-cache.js';
4
+ import { rememberEnumerated, rememberProxyStarted } from './attach-memory.js';
4
5
  import { toolsChangedNotification } from './proxy-handshake.js';
5
6
  import { LOOPBACK_HOST, MCP_SSE_PATH, MCP_SHUTDOWN_EVENT, ReticleEnv, CONTRACT_FINGERPRINT, } from '@reticlehq/core';
6
7
  import { SseFrameParser } from './sse-frame-parser.js';
@@ -141,7 +142,7 @@ const TRANSPORT_LOSS_CODE = -32001;
141
142
  * `nextStep` is appended when the caller knows one. Without it this reply describes a condition and
142
143
  * stops there, which is all a first-run user got out of their first ever tool call.
143
144
  */
144
- export function transportLossReply(id, reason, nextStep) {
145
+ function transportLossReply(id, reason, nextStep) {
145
146
  return JSON.stringify({
146
147
  jsonrpc: '2.0',
147
148
  id,
@@ -347,6 +348,11 @@ export function startMcpProxy(port,
347
348
  */
348
349
  ensureDaemon) {
349
350
  return new Promise((_resolve, reject) => {
351
+ // A client just started us, which is the only honest evidence that Reticle is registered with
352
+ // one at all: a config entry proves somebody wrote JSON, not that a client ever read it. Paired
353
+ // with the `tools/list` record below, this is what lets `status` name the state in between
354
+ // "never registered" and "connected" — see attach-memory.ts.
355
+ rememberProxyStarted(reticleStateHome(), port);
350
356
  // Declared before the SSE handler that fills it: the handler is a closure created below but
351
357
  // invoked on the first daemon frame, and a `const` referenced before its declaration executes is
352
358
  // a runtime throw, not a type error.
@@ -767,6 +773,12 @@ ensureDaemon) {
767
773
  continue;
768
774
  replay.observeOutbound(trimmed);
769
775
  pending.observeOutbound(trimmed);
776
+ // The ARRIVAL of the request, not the delivery of an answer: "this client asked for the tool
777
+ // list" is the fact `status` reports on, and a request that arrived and then failed is a
778
+ // different problem with its own diagnosis. Recording the answer instead would let a host
779
+ // that never asks look identical to one that asked and was refused.
780
+ if (isToolsListRequest(trimmed))
781
+ rememberEnumerated(reticleStateHome(), port);
770
782
  const action = onClientRequest(postUrl !== null, dormant);
771
783
  if (action === OnRequest.SEND && postUrl !== null) {
772
784
  forward(postUrl, trimmed);
package/dist/mcp/mcp.d.ts CHANGED
@@ -3,6 +3,8 @@ import { z } from 'zod';
3
3
  import { type ToolDeps } from '../tools/tools.js';
4
4
  import type { ToolDef } from '../tools/tools.js';
5
5
  import { type ToolSurface } from '../tools/tool-surface.js';
6
+ /** The JSON-RPC method the SDK registers its tool dispatcher under. */
7
+ export declare const CALL_TOOL_METHOD = "tools/call";
6
8
  /**
7
9
  * Merge the runtime-spliced envelope (health/lease/age/control) into a session-bound tool's declared
8
10
  * outputSchema so structuredContent validation keeps those fields instead of dropping them. The tool's
package/dist/mcp/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { z } from 'zod';
4
- import { PredicateSchema } from '../events/predicate.js';
4
+ import { isPredicateParam } from '../events/predicate-eval.js';
5
5
  import { isToonable, resultToToon, MCP_SERVER_NAME } from '@reticlehq/core';
6
6
  import { TOOLS } from '../tools/tools.js';
7
7
  import { filterTools, describeToolSurface, TOOL_SURFACE, } from '../tools/tool-surface.js';
@@ -15,7 +15,19 @@ import { resultIsError } from './mcp-is-error.js';
15
15
  import { buildServerInstructions } from './server-instructions.js';
16
16
  import { unadvertisedToolHelp } from '../tools/unadvertised-help.js';
17
17
  /** The JSON-RPC method the SDK registers its tool dispatcher under. */
18
- const CALL_TOOL_METHOD = 'tools/call';
18
+ export const CALL_TOOL_METHOD = 'tools/call';
19
+ /**
20
+ * What we say when an SDK internal these patches ride on is no longer there.
21
+ *
22
+ * LOG rather than THROW, deliberately. Both patches improve an ERROR MESSAGE; neither is load
23
+ * bearing for a single tool call, and a throw here runs at server construction, so a renamed
24
+ * internal in a minor SDK bump would take the whole MCP server down for every user rather than
25
+ * degrade one message. That trade is backwards. The place a shape change must fail is the gate —
26
+ * `sdk-private-shape.test.ts` asserts both shapes and reddens CI on the bump — and this line is the
27
+ * backstop for the one case the gate cannot see: a user whose installed SDK is not the one CI
28
+ * resolved. The old behaviour, a bare `return`, gave neither.
29
+ */
30
+ const SDK_SHAPE_MISSING = 'sdk_internal_missing';
19
31
  import { log } from '../log.js';
20
32
  import { SERVER_VERSION } from '../version/server-version.js';
21
33
  import { setMcpClientNameHook } from '../telemetry/feedback-context.js';
@@ -73,23 +85,6 @@ export function firstSentence(description) {
73
85
  * Across the three tools that take a predicate that is 72% of the entire advertised input schema — a
74
86
  * cost re-sent on every request, forever, to describe a grammar most calls use one variant of.
75
87
  */
76
- /**
77
- * Is this parameter ACTUALLY the predicate union?
78
- *
79
- * This used to be a name check — `new Set(['predicate', 'until'])` — and `until` is overloaded on
80
- * this surface: the act/assert family means a predicate by it, while reticle_observe / _network /
81
- * _console mean a NUMBER, an upper cursor bound. So all three had their numeric parameter advertised
82
- * to the agent as `Predicate object: { kind, ...fields }`, typed as a record, under the DEFAULT
83
- * profile. An agent believing the schema passes an object, `asNumber` yields undefined, the bound is
84
- * silently dropped and the tool answers over the wrong window — a wrong answer wearing the shape of
85
- * an answer.
86
- *
87
- * Keying on the schema cannot make that mistake: a parameter is compacted because it IS a predicate.
88
- */
89
- function isPredicateParam(schema) {
90
- const inner = schema instanceof z.ZodOptional ? schema.unwrap() : schema;
91
- return inner === PredicateSchema || inner === PredicateSchema.optional();
92
- }
93
88
  /**
94
89
  * What a lean profile advertises in place of the full predicate union.
95
90
  *
@@ -246,7 +241,12 @@ export function advertisedConfig(tool, advertised, profile) {
246
241
  // `verify` is lean for the same reason `default` is, and more so: it exists to make a verdict
247
242
  // cheap, and it was serving FULL descriptions plus outputSchema because the check named only the
248
243
  // default. That put 8,207 bytes on the wire for three tools.
249
- const lean = profile === TOOL_SURFACE.DEFAULT || profile === TOOL_SURFACE.VERIFY;
244
+ // `lean` here is the ADJECTIVE (terse descriptions, no output schemas), which every trimmed surface
245
+ // wants — including TOOL_SURFACE.LEAN, the profile that borrows the word. A trimmed surface serving
246
+ // full prose plus output schemas is the exact defect that put 8,207 bytes on `verify`'s three tools.
247
+ const lean = profile === TOOL_SURFACE.DEFAULT ||
248
+ profile === TOOL_SURFACE.VERIFY ||
249
+ profile === TOOL_SURFACE.LEAN;
250
250
  const terse = lean && !isMetaTool;
251
251
  // The first advertised tool carrying a predicate spells the grammar out; the rest point at it.
252
252
  const anchor = advertised.find((t) => Object.values(t.inputSchema).some((schema) => isPredicateParam(schema)))?.name;
@@ -270,8 +270,10 @@ function unknownKeys(args, allowed) {
270
270
  export function installFriendlyArgErrors(server, examples, allowedKeys = new Map()) {
271
271
  const target = server;
272
272
  const original = target.validateToolInput;
273
- if (typeof original !== 'function')
273
+ if (typeof original !== 'function') {
274
+ log(SDK_SHAPE_MISSING, { internal: 'validateToolInput', lost: 'friendly argument errors' });
274
275
  return;
276
+ }
275
277
  target.validateToolInput = async (tool, args, toolName) => {
276
278
  // An UNKNOWN key is the failure zod cannot catch, because object schemas are non-strict: the key
277
279
  // is silently dropped and the tool answers as if it had not been asked. Measured live —
@@ -281,8 +283,14 @@ export function installFriendlyArgErrors(server, examples, allowedKeys = new Map
281
283
  const unknown = unknownKeys(args, allowedKeys.get(toolName));
282
284
  if (unknown.length > 0) {
283
285
  const example = examples.get(toolName);
286
+ // What the tool DOES declare, in the same breath as what it does not. Naming only the bad key
287
+ // leaves the caller guessing again, and the next guess costs another round trip — which is the
288
+ // whole shape of the failure being fixed here. The list is already in hand, and for a tool
289
+ // with no example (the meta-tools) it is the only thing standing between a typo and a retry.
290
+ const declared = [...(allowedKeys.get(toolName) ?? [])].join(', ');
284
291
  throw new McpError(ErrorCode.InvalidParams, `Unknown ${1 === unknown.length ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')}. ` +
285
292
  `They were NOT applied — a result computed without them would look like an answer. ` +
293
+ (0 === declared.length ? '' : `${toolName} declares: ${declared}. `) +
286
294
  (example === undefined
287
295
  ? `Call reticle_tools { names: ["${toolName}"] } for its parameters.`
288
296
  : `A valid call looks like: ${toolName} ${example}`));
@@ -347,8 +355,13 @@ function installUnadvertisedToolHelp(server, advertised, known) {
347
355
  const inner = server.server;
348
356
  const handlers = inner._requestHandlers;
349
357
  const original = handlers?.get(CALL_TOOL_METHOD);
350
- if (handlers === undefined || original === undefined)
358
+ if (handlers === undefined || original === undefined) {
359
+ log(SDK_SHAPE_MISSING, {
360
+ internal: handlers === undefined ? '_requestHandlers' : CALL_TOOL_METHOD,
361
+ lost: 'unadvertised-tool help',
362
+ });
351
363
  return;
364
+ }
352
365
  handlers.set(CALL_TOOL_METHOD, async (request, extra) => {
353
366
  const name = toolNameOf(request);
354
367
  const help = name === undefined ? undefined : unadvertisedToolHelp(name, advertised, known);
@@ -439,7 +452,16 @@ tools = TOOLS) {
439
452
  .map((tool) => [tool.name, JSON.stringify(tool.example)])),
440
453
  // Every tool in the table, not just the advertised ones: reticle_run dispatches to the full
441
454
  // table, so an unadvertised tool reached through the hatch must be just as strict.
442
- new Map(tools.map((tool) => [tool.name, new Set(Object.keys(tool.inputSchema))])));
455
+ //
456
+ // ...and the advertised list on top of it, because the two META-tools are not in the table — they
457
+ // are composed onto the surface — so they had no entry, and no entry means no declared keys means
458
+ // every key allowed. `reticle_tools` took a misnamed argument, dropped it, and answered with the
459
+ // whole catalogue: a well-formed answer to a question nobody asked, on the one tool whose entire
460
+ // job is telling an agent what the surface is.
461
+ new Map([...tools, ...advertised].map((tool) => [
462
+ tool.name,
463
+ new Set(Object.keys(tool.inputSchema)),
464
+ ])));
443
465
  for (const tool of advertised) {
444
466
  // Output schemas are now the largest slice of the per-request tax (55.6% of the hybrid payload
445
467
  // after the predicate fix) and we are the only one of the three MCP servers that sends them.
@@ -18,11 +18,31 @@ import { SHARED_PARAM_GUIDANCE } from './shared-params.js';
18
18
  * So the lead is state-dependent. A project that has never had an app connect is told the one thing
19
19
  * it needs; a project that has is not nagged about a step it already took. The verdict discipline
20
20
  * and the feedback ask are constant, because they matter in both states.
21
+ *
22
+ * The stopping rule is here for a measured reason. On a HEALTHY app in the competitor benchmark,
23
+ * this agent spent 22 turns and roughly 3.5x the cheapest competitor's tokens confirming that
24
+ * nothing was wrong — and it was not payload, which was the smallest of its five runs. It was
25
+ * turns: three navigations, four queries, a console read, a network read and an assert, on a page
26
+ * whose first verdict had already come back "yes" over a clean capture. Nothing anywhere told it
27
+ * when it was finished. Every other instruction here pushes toward MORE checking, which is right
28
+ * when something is broken and is the whole bill when nothing is.
29
+ *
30
+ * The diagnose-from-source rule has the same provenance and a bigger number behind it. Measured on
31
+ * a fix-and-verify benchmark, split at the call that writes the fix: this agent spent 14 and 18
32
+ * calls BEFORE its first edit where chrome-devtools-mcp spent 8 and 9 — and the sequences say why.
33
+ * It drove the app to understand it (snapshot, act, wait_for, snapshot, act, act_and_wait) and only
34
+ * then opened a file, while the competitor read six files and then looked once. Per-turn cost was
35
+ * IDENTICAL on the hardest cell (22,575 against 22,466), so the whole gap was turns, and most of
36
+ * those turns were spent asking a browser a question that only source can answer.
21
37
  */
22
38
  /** The tools, and the rule that only two of them decide anything. Constant across both states. */
23
- const VERDICT_DISCIPLINE = `Reticle verifies a running web app from the inside: look (reticle_snapshot / reticle_query), act and prove in one hop (reticle_act_and_wait), observe (reticle_state / reticle_network / reticle_console), assert (reticle_assert). Verify a user-facing change against the real app before you call it done, and never weaken a check to make it pass.
39
+ const VERDICT_DISCIPLINE = `Reticle verifies a running web app from the inside: go (reticle_navigate), look (reticle_snapshot / reticle_query), act and prove in one hop (reticle_act_and_wait), observe (reticle_observe / reticle_state / reticle_network / reticle_console), assert (reticle_assert). Verify a user-facing change against the real app before you call it done, and never weaken a check to make it pass.
40
+
41
+ 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 drive that ends without one of those two has no result however many tools it used. Prefer reticle_act_and_wait({ ref, action, until }) — it names the expected consequence BEFORE the action, which is the difference between a check and a rationalisation. Only verified:"yes" is a pass — "unknown" means Reticle could not tell what happened, "no-fault" means nothing was declared to prove. Report either as not proved.
24
42
 
25
- 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 drive that ends without one of those two has no result however many tools it used. Prefer reticle_act_and_wait({ ref, action, until }) — it names the expected consequence BEFORE the action, which is the difference between a check and a rationalisation. Only verified:"yes" is a pass — "unknown" means Reticle could not tell what happened, "no-fault" means nothing was declared to prove. Report either as not proved.`;
43
+ A "yes" over a clean capture IS the answer — re-reading the page after one finds the same state. When every consequence you set out to check has one, stop.
44
+
45
+ DIAGNOSING a bug? Read the source first — Reticle proves what the app DOES, not why. Drive to CONFIRM a fix, not to find one.`;
26
46
  /**
27
47
  * The first move, for a project no app has ever connected to.
28
48
  *
@@ -35,6 +55,28 @@ Only reticle_act_and_wait and reticle_assert produce a verdict. reticle_act and
35
55
  const FIRST_MOVE = `FIRST: no app has ever connected to Reticle in this project, so nothing here can be verified yet. Having these tools is not the same as being set up — the tools reach a daemon, and the daemon has nothing to look at until the app itself is instrumented.
36
56
 
37
57
  Fix that before anything else: run \`npx @reticlehq/server init\` in the project, then RESTART the dev server (a plugin added to a config the running server already read is not in the bundle), then load the app in a browser. Confirm with reticle_sessions — a session listed there is the proof, and until one appears no other tool can tell you anything about this app.`;
58
+ /**
59
+ * When to reach for the tools that are advertised but never explained, and how to reach the two
60
+ * that are explained but not advertised. Both halves of the same defect.
61
+ *
62
+ * An advertised tool arrives as a name and one line of description. That is enough to USE and not
63
+ * enough to CHOOSE, and `reticle_observe` is the case that proves it costs something: the
64
+ * measurement recorded on TOOL_SURFACE.VERIFY is that dropping the observation tools TRIPLED false
65
+ * alarms, because the model stops observing and reaches for the verdict without the evidence. So
66
+ * observe gets a reason, not a listing.
67
+ *
68
+ * The other direction: `reticle_context` and `reticle_intent` sit on the EXTENDED surface, so they
69
+ * are not in the list an agent is handed. Naming them without their call shape sent an agent at a
70
+ * tool it could not call, which is worse than silence — it teaches distrust of everything else
71
+ * here. They are now written the way the skill writes them, as the `reticle_run` call that works.
72
+ *
73
+ * Only in the connected state, and that is not a budget trick: an agent whose app has never
74
+ * connected has nothing to observe, no run to carry forward and nothing to declare an intent
75
+ * against. Its one instruction is the first move, and adding a second would compete with it.
76
+ */
77
+ const REACH_FOR = `reticle_observe is the evidence channel: everything the page did since a cursor, in one read. Take it BEFORE you call a build broken — measured, stripping the observation tools TRIPLED false alarms, because an agent that stops observing reaches for the verdict without the evidence. reticle_wait_for settles a page that is changing without you; reticle_inspect maps an element to its source file:line, which turns a finding into an edit; reticle_session {action:"yield"} hands the tab back to the human.
78
+
79
+ Everything else is one hop: reticle_tools lists it, reticle_run calls it. Two to know early: reticle_run({ tool: "reticle_context", args: {} }) returns what this run already established, after a compaction or handover, and reticle_run({ tool: "reticle_intent", args: { action: "declare", ... } }) declares what a change was MEANT to do, so the verdict has something to check against other than itself.`;
38
80
  /** The feedback ask. Constant: an agent that cannot get set up is the report we most need. */
39
81
  const FEEDBACK_ASK = `Feedback is first-class here, not a courtesy. Reticle is built FOR agents, so your experience is the only signal that decides what gets built or fixed — and it disappears when your context does. Send it with \`reticle_feedback\` the moment you notice, then carry on with your task:
40
82
  - Reticle broke, lied, or could not see something you needed (\`bug\` / \`gap\` / \`ambiguity\`).
@@ -50,7 +92,7 @@ Nothing is too minor, and a report costs one call. If the tools are unreachable
50
92
  */
51
93
  export function buildServerInstructions(state) {
52
94
  const base = state.previouslyConnected
53
- ? `${VERDICT_DISCIPLINE}\n\n${FEEDBACK_ASK}`
95
+ ? `${VERDICT_DISCIPLINE}\n\n${REACH_FOR}\n\n${FEEDBACK_ASK}`
54
96
  : `${FIRST_MOVE}\n\n${VERDICT_DISCIPLINE}\n\n${FEEDBACK_ASK}`;
55
97
  return `${base}\n\n${SHARED_PARAM_GUIDANCE}`;
56
98
  }
@@ -15,6 +15,14 @@
15
15
  * its own confidently-wrong answer, and a proxy that has never seen one has nothing honest to serve —
16
16
  * so it says nothing rather than inventing a list the agent would then call into.
17
17
  */
18
+ /**
19
+ * Is this client line a request for the tool list?
20
+ *
21
+ * Lives here because this module already owns the method name and the tolerant parse. The caller is
22
+ * the proxy's stdin reader, which records the arrival as the one observable fact behind `reticle
23
+ * status`'s "registered, never enumerated" state — see attach-memory.ts.
24
+ */
25
+ export declare function isToolsListRequest(line: string): boolean;
18
26
  export declare class ToolCatalogCache {
19
27
  #private;
20
28
  /** Watch a line coming BACK from the daemon; remember it if it is a tool catalog. */
@@ -25,6 +25,16 @@ function parse(line) {
25
25
  return null;
26
26
  }
27
27
  }
28
+ /**
29
+ * Is this client line a request for the tool list?
30
+ *
31
+ * Lives here because this module already owns the method name and the tolerant parse. The caller is
32
+ * the proxy's stdin reader, which records the arrival as the one observable fact behind `reticle
33
+ * status`'s "registered, never enumerated" state — see attach-memory.ts.
34
+ */
35
+ export function isToolsListRequest(line) {
36
+ return parse(line)?.method === LIST_METHOD;
37
+ }
28
38
  export class ToolCatalogCache {
29
39
  #tools;
30
40
  /** Watch a line coming BACK from the daemon; remember it if it is a tool catalog. */
@@ -1,5 +1,5 @@
1
1
  import { type FlowStep } from '@reticlehq/core';
2
- import { type InstrumentationGap } from './self-instrument.js';
2
+ import type { LocatedGap } from './self-instrument.js';
3
3
  /**
4
4
  * Bridge domain gaps → LOCATED instrumentation gaps. A flow that asserts no consequence should emit one —
5
5
  * and the flow's own step anchors carry the source file:line the babel/next plugin stamped, so we know
@@ -12,7 +12,7 @@ export declare function lastSource(steps: readonly FlowStep[]): {
12
12
  line: number;
13
13
  } | undefined;
14
14
  /**
15
- * Turn unasserted flows into missing-signal gaps at each flow's source location. Flows with no stamped
16
- * source are skipped (no location to propose). The suggested signal name is `<flow>:done`.
15
+ * Turn unasserted flows into no-signal-on-mutation gaps at each flow's source location. Flows with
16
+ * no stamped source are skipped (no location to propose). The suggested signal name is `<flow>:done`.
17
17
  */
18
- export declare function instrumentationGapsForFlows(unassertedFlows: readonly string[], flowStepsByName: ReadonlyMap<string, readonly FlowStep[]>): InstrumentationGap[];
18
+ export declare function instrumentationGapsForFlows(unassertedFlows: readonly string[], flowStepsByName: ReadonlyMap<string, readonly FlowStep[]>): LocatedGap[];
@@ -1,5 +1,4 @@
1
- import { AnchorKind } from '@reticlehq/core';
2
- import { InstrumentationGapKind } from './self-instrument.js';
1
+ import { AnchorKind, InstrumentationGapKind } from '@reticlehq/core';
3
2
  /**
4
3
  * Bridge domain gaps → LOCATED instrumentation gaps. A flow that asserts no consequence should emit one —
5
4
  * and the flow's own step anchors carry the source file:line the babel/next plugin stamped, so we know
@@ -22,8 +21,8 @@ export function lastSource(steps) {
22
21
  return found;
23
22
  }
24
23
  /**
25
- * Turn unasserted flows into missing-signal gaps at each flow's source location. Flows with no stamped
26
- * source are skipped (no location to propose). The suggested signal name is `<flow>:done`.
24
+ * Turn unasserted flows into no-signal-on-mutation gaps at each flow's source location. Flows with
25
+ * no stamped source are skipped (no location to propose). The suggested signal name is `<flow>:done`.
27
26
  */
28
27
  export function instrumentationGapsForFlows(unassertedFlows, flowStepsByName) {
29
28
  const gaps = [];
@@ -33,7 +32,7 @@ export function instrumentationGapsForFlows(unassertedFlows, flowStepsByName) {
33
32
  if (source === undefined)
34
33
  continue;
35
34
  gaps.push({
36
- kind: InstrumentationGapKind.MISSING_SIGNAL,
35
+ kind: InstrumentationGapKind.NO_SIGNAL_ON_MUTATION,
37
36
  file: source.file,
38
37
  line: source.line,
39
38
  name: `${name}:done`,
@@ -4,15 +4,15 @@
4
4
  * apply instrumentation proposals as concrete diffs, so the AGENT closes the gap and the app's
5
5
  * observability compounds run over run — instead of a human being a prerequisite. Deterministic mapping
6
6
  * from a located gap to the code to insert; pure and testable. Result-enrichment wiring is a later step.
7
+ *
8
+ * Kinds come from `@reticlehq/core`. A second enum here is how `reticle_domain` used to say
9
+ * `missing-signal` while honesty said `no-signal-on-mutation` for the same absence.
7
10
  */
8
- export declare const InstrumentationGapKind: {
9
- readonly MISSING_SIGNAL: "missing-signal";
10
- readonly UNREGISTERED_STORE: "unregistered-store";
11
- readonly MISSING_TESTID: "missing-testid";
12
- };
13
- export type InstrumentationGapKind = (typeof InstrumentationGapKind)[keyof typeof InstrumentationGapKind];
14
- export interface InstrumentationGap {
15
- kind: InstrumentationGapKind;
11
+ import { InstrumentationGapKind } from '@reticlehq/core';
12
+ /** The three core kinds a located proposal can close. Other kinds are verdict-side, not insert-side. */
13
+ export type LocatableGapKind = typeof InstrumentationGapKind.NO_SIGNAL_ON_MUTATION | typeof InstrumentationGapKind.NO_STORE_REGISTERED | typeof InstrumentationGapKind.MISSING_TESTID;
14
+ export interface LocatedGap {
15
+ kind: LocatableGapKind;
16
16
  file: string;
17
17
  line: number;
18
18
  /** The name to instrument with — a signal name, store name, or testid. */
@@ -28,5 +28,5 @@ interface InstrumentationProposal {
28
28
  rationale: string;
29
29
  }
30
30
  /** Turn located capability gaps into ready-to-apply instrumentation proposals. Deterministic. */
31
- export declare function proposeInstrumentation(gaps: readonly InstrumentationGap[]): InstrumentationProposal[];
31
+ export declare function proposeInstrumentation(gaps: readonly LocatedGap[]): InstrumentationProposal[];
32
32
  export {};