mergen-server 1.4.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 (308) hide show
  1. package/LICENSE +57 -0
  2. package/README.md +96 -0
  3. package/dist/__stubs__/calibration.js +471 -0
  4. package/dist/__stubs__/causal.js +601 -0
  5. package/dist/__stubs__/closed-source.js +150 -0
  6. package/dist/__stubs__/license.js +35 -0
  7. package/dist/__stubs__/plans.js +197 -0
  8. package/dist/app.js +467 -0
  9. package/dist/cli.js +250 -0
  10. package/dist/commands/agent-identity.js +79 -0
  11. package/dist/commands/gate.js +107 -0
  12. package/dist/commands/github.js +632 -0
  13. package/dist/commands/incident.js +644 -0
  14. package/dist/commands/policy.js +710 -0
  15. package/dist/commands/scan.js +243 -0
  16. package/dist/commands/setup.js +1218 -0
  17. package/dist/commands/shared.js +109 -0
  18. package/dist/commands/team.js +809 -0
  19. package/dist/datadog/blame-attribution.js +228 -0
  20. package/dist/datadog/client.js +159 -0
  21. package/dist/datadog/compactor.js +169 -0
  22. package/dist/datadog/fingerprinter.js +34 -0
  23. package/dist/datadog/incident-state.js +21 -0
  24. package/dist/datadog/line-matcher.js +56 -0
  25. package/dist/datadog/memory-store.js +399 -0
  26. package/dist/datadog/otel-trace.js +67 -0
  27. package/dist/index.js +505 -0
  28. package/dist/intelligence/action-risk.js +132 -0
  29. package/dist/intelligence/activity-feed.js +40 -0
  30. package/dist/intelligence/agent-identity.js +115 -0
  31. package/dist/intelligence/agent-pipeline.js +329 -0
  32. package/dist/intelligence/agent-profiles.js +81 -0
  33. package/dist/intelligence/ai-commit.js +16 -0
  34. package/dist/intelligence/approval-analytics.js +42 -0
  35. package/dist/intelligence/approval-events.js +5 -0
  36. package/dist/intelligence/arch-boundaries.js +94 -0
  37. package/dist/intelligence/arch-graph.js +117 -0
  38. package/dist/intelligence/autonomy.js +397 -0
  39. package/dist/intelligence/azure-broker.js +57 -0
  40. package/dist/intelligence/azure-proxy-sessions.js +49 -0
  41. package/dist/intelligence/baseline.js +1 -0
  42. package/dist/intelligence/behavior-baseline.js +134 -0
  43. package/dist/intelligence/billing.js +1 -0
  44. package/dist/intelligence/blast-radius.js +224 -0
  45. package/dist/intelligence/bypass.js +157 -0
  46. package/dist/intelligence/calibration-classifier.js +77 -0
  47. package/dist/intelligence/calibration-git-sync.js +74 -0
  48. package/dist/intelligence/calibration.js +1 -0
  49. package/dist/intelligence/cascade-detector.js +58 -0
  50. package/dist/intelligence/case-study-generator.js +126 -0
  51. package/dist/intelligence/causal-graph.js +126 -0
  52. package/dist/intelligence/causal.js +1 -0
  53. package/dist/intelligence/change-risk.js +102 -0
  54. package/dist/intelligence/compliance-report.js +134 -0
  55. package/dist/intelligence/confidence-report.js +85 -0
  56. package/dist/intelligence/corpus-to-policy.js +140 -0
  57. package/dist/intelligence/credential-broker.js +112 -0
  58. package/dist/intelligence/credential-misuse.js +39 -0
  59. package/dist/intelligence/debug-sessions.js +108 -0
  60. package/dist/intelligence/decision-escalation.js +36 -0
  61. package/dist/intelligence/decision-memory.js +47 -0
  62. package/dist/intelligence/decision-similarity.js +85 -0
  63. package/dist/intelligence/default-safety-tests.js +53 -0
  64. package/dist/intelligence/degradation-watcher.js +108 -0
  65. package/dist/intelligence/detector-plugins.js +70 -0
  66. package/dist/intelligence/detectors.js +1 -0
  67. package/dist/intelligence/device-auth.js +138 -0
  68. package/dist/intelligence/diff-size.js +42 -0
  69. package/dist/intelligence/ebpf-verify.js +45 -0
  70. package/dist/intelligence/enterprise-policy-engine.js +1122 -0
  71. package/dist/intelligence/error-fingerprint.js +1 -0
  72. package/dist/intelligence/execution-gate.js +100 -0
  73. package/dist/intelligence/execution-mode.js +22 -0
  74. package/dist/intelligence/gate-analytics.js +256 -0
  75. package/dist/intelligence/gate-decision.js +64 -0
  76. package/dist/intelligence/gcp-broker.js +50 -0
  77. package/dist/intelligence/git-adr-sync.js +207 -0
  78. package/dist/intelligence/hitl-hold.js +460 -0
  79. package/dist/intelligence/hypothesis-history.js +93 -0
  80. package/dist/intelligence/idp-identity.js +83 -0
  81. package/dist/intelligence/impl-critic.js +216 -0
  82. package/dist/intelligence/incident-autopilot.js +622 -0
  83. package/dist/intelligence/incident-replay.js +162 -0
  84. package/dist/intelligence/incident-result-cache.js +33 -0
  85. package/dist/intelligence/incident-similarity.js +58 -0
  86. package/dist/intelligence/infra-detectors.js +312 -0
  87. package/dist/intelligence/license.js +185 -0
  88. package/dist/intelligence/llm-spokesperson.js +62 -0
  89. package/dist/intelligence/mcp-prompts.js +320 -0
  90. package/dist/intelligence/mcp-resources.js +171 -0
  91. package/dist/intelligence/normalize.js +108 -0
  92. package/dist/intelligence/notifications.js +83 -0
  93. package/dist/intelligence/oidc-issuer.js +87 -0
  94. package/dist/intelligence/override-corpus.js +597 -0
  95. package/dist/intelligence/planning-gate.js +84 -0
  96. package/dist/intelligence/plans.js +223 -0
  97. package/dist/intelligence/platt-scaling.js +164 -0
  98. package/dist/intelligence/policy-proposals.js +84 -0
  99. package/dist/intelligence/policy-suggester.js +161 -0
  100. package/dist/intelligence/policy-sync.js +103 -0
  101. package/dist/intelligence/policy-test-runner.js +35 -0
  102. package/dist/intelligence/postmortem-parser.js +181 -0
  103. package/dist/intelligence/postmortem-retrieval.js +224 -0
  104. package/dist/intelligence/postmortem-store.js +596 -0
  105. package/dist/intelligence/pr-commenter.js +81 -0
  106. package/dist/intelligence/pr-policy-gate.js +38 -0
  107. package/dist/intelligence/pr-shadow-analyzer.js +229 -0
  108. package/dist/intelligence/process-tree.js +108 -0
  109. package/dist/intelligence/prompts.js +1 -0
  110. package/dist/intelligence/replay.js +38 -0
  111. package/dist/intelligence/repro-steps.js +1 -0
  112. package/dist/intelligence/resource-extractors.js +0 -0
  113. package/dist/intelligence/resource-file-context.js +138 -0
  114. package/dist/intelligence/risk-score.js +22 -0
  115. package/dist/intelligence/rollback.js +137 -0
  116. package/dist/intelligence/runbook-updater.js +143 -0
  117. package/dist/intelligence/sandbox.js +194 -0
  118. package/dist/intelligence/script-trust.js +104 -0
  119. package/dist/intelligence/session-metrics.js +67 -0
  120. package/dist/intelligence/session-threat-tracker.js +231 -0
  121. package/dist/intelligence/shadow-digest-cron.js +81 -0
  122. package/dist/intelligence/shadow-log.js +284 -0
  123. package/dist/intelligence/shell-ast.js +145 -0
  124. package/dist/intelligence/siem-forward.js +104 -0
  125. package/dist/intelligence/slack-digest.js +151 -0
  126. package/dist/intelligence/slack-override-loop.js +217 -0
  127. package/dist/intelligence/slack-routing.js +67 -0
  128. package/dist/intelligence/slack.js +900 -0
  129. package/dist/intelligence/sql-ast.js +31 -0
  130. package/dist/intelligence/team.js +1 -0
  131. package/dist/intelligence/telemetry.js +76 -0
  132. package/dist/intelligence/threshold-optimizer.js +75 -0
  133. package/dist/intelligence/token-budget.js +66 -0
  134. package/dist/intelligence/tool-guard.js +708 -0
  135. package/dist/intelligence/tool-manifest 2.js +74 -0
  136. package/dist/intelligence/tool-manifest.js +125 -0
  137. package/dist/intelligence/tools-analysis.js +857 -0
  138. package/dist/intelligence/tools-arch.js +152 -0
  139. package/dist/intelligence/tools-autonomy.js +495 -0
  140. package/dist/intelligence/tools-blast-radius.js +140 -0
  141. package/dist/intelligence/tools-browser.js +431 -0
  142. package/dist/intelligence/tools-change-timeline.js +154 -0
  143. package/dist/intelligence/tools-credentials.js +117 -0
  144. package/dist/intelligence/tools-datadog.js +233 -0
  145. package/dist/intelligence/tools-debug-sessions.js +209 -0
  146. package/dist/intelligence/tools-discovery.js +284 -0
  147. package/dist/intelligence/tools-infra.js +612 -0
  148. package/dist/intelligence/tools-intent.js +128 -0
  149. package/dist/intelligence/tools-memory.js +514 -0
  150. package/dist/intelligence/tools-runbook.js +951 -0
  151. package/dist/intelligence/tools-sessions.js +107 -0
  152. package/dist/intelligence/tools-state.js +232 -0
  153. package/dist/intelligence/tools-utility.js +430 -0
  154. package/dist/intelligence/tools-validate.js +215 -0
  155. package/dist/intelligence/tools.js +1 -0
  156. package/dist/intelligence/trace-context.js +11 -0
  157. package/dist/intelligence/unclassified-clusters.js +87 -0
  158. package/dist/intelligence/usage.js +195 -0
  159. package/dist/routes/active-authors.js +63 -0
  160. package/dist/routes/activity-feed.js +21 -0
  161. package/dist/routes/adr.js +117 -0
  162. package/dist/routes/agent-activity.js +184 -0
  163. package/dist/routes/agent-blunders.js +163 -0
  164. package/dist/routes/agents.js +175 -0
  165. package/dist/routes/api-keys.js +65 -0
  166. package/dist/routes/arch.js +75 -0
  167. package/dist/routes/audit-export.js +93 -0
  168. package/dist/routes/billing-dashboard.js +158 -0
  169. package/dist/routes/billing-outcome.js +77 -0
  170. package/dist/routes/calibration.js +305 -0
  171. package/dist/routes/ci-gate-history.js +20 -0
  172. package/dist/routes/ci-gate.js +232 -0
  173. package/dist/routes/ci.js +293 -0
  174. package/dist/routes/compliance-report.js +24 -0
  175. package/dist/routes/confidence.js +30 -0
  176. package/dist/routes/credentials.js +119 -0
  177. package/dist/routes/dashboard.js +1407 -0
  178. package/dist/routes/decision-memory.js +42 -0
  179. package/dist/routes/demo.js +844 -0
  180. package/dist/routes/device-auth-stub.js +173 -0
  181. package/dist/routes/explain-why.js +39 -0
  182. package/dist/routes/gate-analytics.js +213 -0
  183. package/dist/routes/gate.js +28 -0
  184. package/dist/routes/github-webhook.js +364 -0
  185. package/dist/routes/habituation.js +30 -0
  186. package/dist/routes/health-integrations.js +243 -0
  187. package/dist/routes/heartbeats.js +45 -0
  188. package/dist/routes/hitl.js +364 -0
  189. package/dist/routes/impact-report.js +645 -0
  190. package/dist/routes/incident-webhook.js +63 -0
  191. package/dist/routes/incidents.js +366 -0
  192. package/dist/routes/layers.js +62 -0
  193. package/dist/routes/license.js +187 -0
  194. package/dist/routes/oidc-issuer.js +30 -0
  195. package/dist/routes/onboarding.js +170 -0
  196. package/dist/routes/otel.js +67 -0
  197. package/dist/routes/otlp-receiver.js +179 -0
  198. package/dist/routes/overrides.js +241 -0
  199. package/dist/routes/pagerduty.js +258 -0
  200. package/dist/routes/policies.js +809 -0
  201. package/dist/routes/policy-nl.js +169 -0
  202. package/dist/routes/postmortem.js +102 -0
  203. package/dist/routes/pr-shadow.js +47 -0
  204. package/dist/routes/rbac.js +61 -0
  205. package/dist/routes/replay.js +17 -0
  206. package/dist/routes/risk-report.js +164 -0
  207. package/dist/routes/runbooks.js +125 -0
  208. package/dist/routes/safety-test.js +174 -0
  209. package/dist/routes/sdk.js +222 -0
  210. package/dist/routes/sensor.js +819 -0
  211. package/dist/routes/sentry.js +140 -0
  212. package/dist/routes/sessions.js +43 -0
  213. package/dist/routes/setup-ui.js +594 -0
  214. package/dist/routes/shadow-report.js +107 -0
  215. package/dist/routes/slack-routing.js +171 -0
  216. package/dist/routes/team-usage.js +71 -0
  217. package/dist/routes/telemetry.js +28 -0
  218. package/dist/routes/tenants.js +199 -0
  219. package/dist/routes/tickets.js +167 -0
  220. package/dist/routes/validate.js +13 -0
  221. package/dist/routes/war-room.js +113 -0
  222. package/dist/scripts/check-arch.js +22 -0
  223. package/dist/seeds/community-corpus.js +176 -0
  224. package/dist/seeds/corpus.js +1224 -0
  225. package/dist/sensor/action-ledger.js +327 -0
  226. package/dist/sensor/adr-store.js +140 -0
  227. package/dist/sensor/agent-blunder-store.js +370 -0
  228. package/dist/sensor/agent-context-store.js +225 -0
  229. package/dist/sensor/agent-memory-store.js +231 -0
  230. package/dist/sensor/audit-fetch.js +23 -0
  231. package/dist/sensor/audit-log.js +273 -0
  232. package/dist/sensor/buffer-schemas.js +225 -0
  233. package/dist/sensor/buffer.js +599 -0
  234. package/dist/sensor/bypass-tracker.js +93 -0
  235. package/dist/sensor/ci-gate-history.js +70 -0
  236. package/dist/sensor/cloud-auth.js +183 -0
  237. package/dist/sensor/commit-context-store.js +235 -0
  238. package/dist/sensor/docker-log-stream.js +164 -0
  239. package/dist/sensor/docker-monitor.js +122 -0
  240. package/dist/sensor/extended-buffer.js +46 -0
  241. package/dist/sensor/feedback-token.js +41 -0
  242. package/dist/sensor/file-lock.js +6 -0
  243. package/dist/sensor/fs-watcher.js +106 -0
  244. package/dist/sensor/gate-heartbeat.js +122 -0
  245. package/dist/sensor/git-suspect.js +136 -0
  246. package/dist/sensor/habituation-store.js +77 -0
  247. package/dist/sensor/heartbeat-monitor.js +133 -0
  248. package/dist/sensor/incident-store.js +340 -0
  249. package/dist/sensor/infra-normalizer.js +513 -0
  250. package/dist/sensor/ingest.js +339 -0
  251. package/dist/sensor/jest-reporter.js +52 -0
  252. package/dist/sensor/k8s-events.js +132 -0
  253. package/dist/sensor/layer2-store.js +111 -0
  254. package/dist/sensor/layer3-store.js +230 -0
  255. package/dist/sensor/layer4-store.js +147 -0
  256. package/dist/sensor/logger.js +13 -0
  257. package/dist/sensor/otel-exporter.js +161 -0
  258. package/dist/sensor/paths.js +71 -0
  259. package/dist/sensor/policy-history.js +147 -0
  260. package/dist/sensor/pr-shadow-store.js +83 -0
  261. package/dist/sensor/process-watcher.js +162 -0
  262. package/dist/sensor/rbac.js +92 -0
  263. package/dist/sensor/redact.js +114 -0
  264. package/dist/sensor/redis-store.js +191 -0
  265. package/dist/sensor/route-reachability.js +64 -0
  266. package/dist/sensor/security-utils.js +18 -0
  267. package/dist/sensor/service-graph.js +170 -0
  268. package/dist/sensor/service-topology.js +227 -0
  269. package/dist/sensor/session-history.js +74 -0
  270. package/dist/sensor/session-persist.js +38 -0
  271. package/dist/sensor/shadow-promote.js +122 -0
  272. package/dist/sensor/sourcemap.js +281 -0
  273. package/dist/sensor/sqlite-store.js +205 -0
  274. package/dist/sensor/sso.js +164 -0
  275. package/dist/sensor/vitest-reporter.js +65 -0
  276. package/dist/sensor/watcher.js +48 -0
  277. package/dist/storage/interfaces.js +0 -0
  278. package/dist/storage/pg/pg-action-ledger.js +138 -0
  279. package/dist/storage/pg/pg-approval-store.js +107 -0
  280. package/dist/storage/pg/pg-blunder-store.js +193 -0
  281. package/dist/storage/pg/pg-ci-gate-history.js +83 -0
  282. package/dist/storage/pg/pg-client.js +24 -0
  283. package/dist/storage/pg/pg-event-store.js +63 -0
  284. package/dist/storage/pg/pg-incident-store.js +190 -0
  285. package/dist/storage/pg/pg-migrations.js +26 -0
  286. package/dist/storage/pg/pg-override-corpus.js +447 -0
  287. package/dist/storage/pg/pg-shadow-log.js +129 -0
  288. package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
  289. package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
  290. package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
  291. package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
  292. package/dist/storage/sqlite/sqlite-event-store.js +29 -0
  293. package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
  294. package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
  295. package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
  296. package/dist/storage/store-factory.js +55 -0
  297. package/dist/storage/store-registry.js +31 -0
  298. package/dist/update-checker.js +107 -0
  299. package/dist/workers/autopilot-worker.js +28 -0
  300. package/dist/workers/notification-worker.js +28 -0
  301. package/dist/workers/queues.js +58 -0
  302. package/dist/workers/validation-worker.js +24 -0
  303. package/dist/workers/worker-registry.js +18 -0
  304. package/package.json +123 -0
  305. package/sdk/mergen-inject.js +260 -0
  306. package/sdk/node.js +313 -0
  307. package/sdk/vite-plugin.ts +57 -0
  308. package/sdk/webpack-plugin.js +106 -0
@@ -0,0 +1,430 @@
1
+ import { z } from "zod";
2
+ import { store } from "../sensor/buffer.js";
3
+ import { getUsageSnapshot } from "./usage.js";
4
+ import { getLicenseState } from "./license.js";
5
+ import { layer3Store } from "../sensor/layer3-store.js";
6
+ import { trackCall, buildCreditBar, setLastClearAt, withTierGate, entitlementLines, executionUsageLines } from "./tools-state.js";
7
+ import { getTierForTool } from "./tool-manifest.js";
8
+ import { saveSessionToHistory } from "../sensor/session-history.js";
9
+ import { adrStore } from "../sensor/adr-store.js";
10
+ import { confidenceStore, formatConfidenceReport } from "./confidence-report.js";
11
+ import { generateRollbackPlan } from "./rollback.js";
12
+ function registerUtilityTools(server) {
13
+ server.registerTool(
14
+ "get_status",
15
+ {
16
+ description: "Returns your current plan, credit usage, billing status, and next reset date. Use this to check how many incidents remain this month before calling that tool."
17
+ },
18
+ async () => {
19
+ const snap = getUsageSnapshot();
20
+ const licState = getLicenseState();
21
+ const lines = [
22
+ `## Mergen Status`,
23
+ "",
24
+ `**Plan:** ${snap.planName}`,
25
+ `**Period:** ${snap.month} | **Resets:** ${new Date(snap.resetsAt).toUTCString()}`,
26
+ ""
27
+ ];
28
+ if (snap.included === null) {
29
+ lines.push(`**Credits:** ${snap.used} used (unlimited plan)`);
30
+ } else {
31
+ const bar = buildCreditBar(snap.used, snap.included);
32
+ lines.push(`**Credits:** ${snap.used} / ${snap.included} used ${bar}`);
33
+ lines.push(`**Remaining:** ${snap.remaining}`);
34
+ if (snap.lowCredits)
35
+ lines.push(`\u26A0 **Low credits** \u2014 only ${snap.remaining} call(s) left at no extra charge.`);
36
+ }
37
+ if (snap.overage > 0) {
38
+ const rate = `$${(snap.overageCentsPerCredit / 100).toFixed(2)}/call`;
39
+ lines.push(
40
+ "",
41
+ `**Overage:** ${snap.overage} call(s) \xD7 ${rate} = **$${(snap.estimatedOverageCents / 100).toFixed(2)}** estimated`,
42
+ `**Billing status:** ${snap.billingStatus === "confirmed" ? "\u2705 confirmed" : "\u23F3 pending (will be sent to LemonSqueezy within 5 s)"}`
43
+ );
44
+ }
45
+ lines.push(...entitlementLines());
46
+ lines.push(...executionUsageLines());
47
+ if (licState?.customerEmail) {
48
+ lines.push("", `**Account:** ${licState.customerEmail} | Last validated: ${licState.validatedAt?.slice(0, 10)}`);
49
+ }
50
+ return { content: [{ type: "text", text: lines.join("\n") }] };
51
+ }
52
+ );
53
+ server.registerTool(
54
+ "clear_buffer",
55
+ { description: "Clears all events from the in-memory buffer. The current session is archived to disk before clearing so it can be replayed later via get_session_replay." },
56
+ async () => {
57
+ const events = store.serialize();
58
+ saveSessionToHistory(events, "mcp-clear");
59
+ const was = store.size();
60
+ store.clear();
61
+ setLastClearAt(Date.now());
62
+ return { content: [{ type: "text", text: `Cleared ${was} event(s) from buffer. Session archived to history.` }] };
63
+ }
64
+ );
65
+ server.registerTool(
66
+ "mark_capture_start",
67
+ {
68
+ description: "Records the current timestamp as a capture-start point for this debug session. Call this BEFORE the user reproduces a bug. Then pass the returned `since` value to get_recent_logs and get_network_activity to see only what happened during reproduction. Workflow: (1) mark_capture_start \u2192 get timestamp T; (2) user reproduces bug; (3) get_recent_logs(since: T) + get_network_activity(since: T).",
69
+ inputSchema: {}
70
+ },
71
+ async () => {
72
+ const ts = Date.now();
73
+ const iso = new Date(ts).toISOString();
74
+ return {
75
+ content: [{
76
+ type: "text",
77
+ text: `Capture started at ${iso} (since: ${ts}).
78
+
79
+ Now ask the user to reproduce the bug, then call:
80
+ get_recent_logs(since: ${ts})
81
+ get_network_activity(since: ${ts})`
82
+ }]
83
+ };
84
+ }
85
+ );
86
+ server.registerTool(
87
+ "export_session",
88
+ {
89
+ description: "Exports the current session \u2014 all buffered events, signals, and the latest analysis \u2014 as a structured JSON file in the current working directory. Use this to share a bug context with teammates or attach to a GitHub issue. Returns the file path and a human-readable summary.",
90
+ inputSchema: {
91
+ label: z.string().optional().describe("Optional filename label (default: session-<ISO timestamp>)")
92
+ }
93
+ },
94
+ async ({ label }) => {
95
+ const { writeFileSync } = await import("fs");
96
+ const { resolve } = await import("path");
97
+ const ts = Date.now();
98
+ const name = label ?? `session-${new Date(ts).toISOString().slice(0, 19).replace(/:/g, "-")}`;
99
+ const outPath = resolve(process.cwd(), `${name}.mergen-report.json`);
100
+ const logs = store.getLogs(200);
101
+ const network = store.getNetwork(200);
102
+ const context = store.getContext(20);
103
+ const testResults = store.getTestResults(50);
104
+ const diagnostics = store.getDiagnostics(50);
105
+ const signals = store.getSignals();
106
+ const report = {
107
+ exported_at: new Date(ts).toISOString(),
108
+ label: name,
109
+ summary: {
110
+ total_events: store.size(),
111
+ logs: logs.length,
112
+ network_events: network.length,
113
+ errors: logs.filter((l) => l.level === "error").length,
114
+ warnings: logs.filter((l) => l.level === "warn").length,
115
+ network_errors: network.filter((n) => n.status >= 400 || n.status === 0).length,
116
+ signals: signals.length,
117
+ test_failures: testResults.filter((t) => t.status === "fail").length
118
+ },
119
+ signals,
120
+ logs,
121
+ network,
122
+ context,
123
+ test_results: testResults,
124
+ diagnostics
125
+ };
126
+ writeFileSync(outPath, JSON.stringify(report, null, 2), "utf8");
127
+ const s = report.summary;
128
+ return {
129
+ content: [{
130
+ type: "text",
131
+ text: [
132
+ `## Session Exported`,
133
+ "",
134
+ `**File:** \`${outPath}\``,
135
+ "",
136
+ `**Summary:**`,
137
+ `- ${s.logs} log events (${s.errors} errors, ${s.warnings} warnings)`,
138
+ `- ${s.network_events} network events (${s.network_errors} failures)`,
139
+ `- ${s.signals} active signals`,
140
+ `- ${s.test_failures} test failure(s)`,
141
+ "",
142
+ `Share with your team: \`cat ${outPath} | pbcopy\``
143
+ ].join("\n")
144
+ }]
145
+ };
146
+ }
147
+ );
148
+ server.registerTool(
149
+ "get_diagnostics",
150
+ {
151
+ description: "Returns recent editor diagnostic events (TypeScript/ESLint errors and warnings) captured from VS Code. Use this to correlate compile-time errors with runtime crashes.",
152
+ inputSchema: {
153
+ limit: z.number().int().min(1).max(200).optional().describe("Max events (default 50)"),
154
+ severity: z.enum(["error", "warning", "info", "hint"]).optional().describe("Filter by severity"),
155
+ since: z.number().int().optional().describe("Only return events after this Unix ms timestamp")
156
+ }
157
+ },
158
+ async ({ limit = 50, severity, since }) => {
159
+ trackCall("get_diagnostics");
160
+ const events = store.getDiagnostics(limit, severity, since);
161
+ if (events.length === 0) {
162
+ return { content: [{ type: "text", text: "No editor diagnostics captured yet. Make sure the VS Code extension is installed and the Mergen server is running." }] };
163
+ }
164
+ const lines = [`## Editor Diagnostics (${events.length} events)`, ""];
165
+ for (const e of events) {
166
+ const icon = e.severity === "error" ? "\u{1F534}" : e.severity === "warning" ? "\u{1F7E1}" : "\u{1F535}";
167
+ lines.push(`${icon} **${e.severity.toUpperCase()}** \`${e.file}:${e.line}:${e.column}\``);
168
+ lines.push(` ${e.source ? `[${e.source}] ` : ""}${e.message}${e.code ? ` (${e.code})` : ""}`);
169
+ lines.push(` _${new Date(e.timestamp).toISOString()}_`, "");
170
+ }
171
+ return { content: [{ type: "text", text: lines.join("\n") }] };
172
+ }
173
+ );
174
+ server.registerTool(
175
+ "get_test_results",
176
+ {
177
+ description: "Returns recent test results streamed from Vitest/Jest reporters. Shows failing tests with error messages and stack traces.",
178
+ inputSchema: {
179
+ limit: z.number().int().min(1).max(200).optional().describe("Max events (default 50)"),
180
+ status: z.enum(["pass", "fail", "skip", "todo"]).optional().describe("Filter by test status"),
181
+ since: z.number().int().optional().describe("Only return events after this Unix ms timestamp")
182
+ }
183
+ },
184
+ async ({ limit = 50, status, since }) => {
185
+ trackCall("get_test_results");
186
+ const events = store.getTestResults(limit, status, since);
187
+ if (events.length === 0) {
188
+ return { content: [{ type: "text", text: "No test results captured yet. Add the Mergen reporter to your vitest.config.ts or jest.config.js." }] };
189
+ }
190
+ const byStatus = { pass: 0, fail: 0, skip: 0, todo: 0 };
191
+ for (const e of events) byStatus[e.status]++;
192
+ const lines = [
193
+ `## Test Results (${events.length} tests)`,
194
+ `\u2705 ${byStatus.pass} passed \xB7 \u274C ${byStatus.fail} failed \xB7 \u23ED ${byStatus.skip} skipped`,
195
+ ""
196
+ ];
197
+ const failing = events.filter((e) => e.status === "fail");
198
+ if (failing.length > 0) {
199
+ lines.push("### Failing tests:", "");
200
+ for (const e of failing) {
201
+ lines.push(`\u274C **${e.name}**`, ` File: \`${e.file}\``);
202
+ if (e.error) {
203
+ lines.push(` Error: ${e.error.message}`);
204
+ if (e.error.stack) lines.push(` \`\`\`
205
+ ${e.error.stack.slice(0, 500)}
206
+ \`\`\``);
207
+ }
208
+ lines.push("");
209
+ }
210
+ }
211
+ return { content: [{ type: "text", text: lines.join("\n") }] };
212
+ }
213
+ );
214
+ server.registerTool(
215
+ "create_ticket",
216
+ {
217
+ description: "Creates a pre-filled bug ticket in Linear or Jira from the current hypothesis. Auto-fills: title (hypothesis summary), description, reproduction steps, affected SHA, CODEOWNERS, and a link to the Mergen dashboard. Requires LINEAR_API_KEY + LINEAR_TEAM_ID or JIRA_BASE_URL + JIRA_API_TOKEN + JIRA_EMAIL + JIRA_PROJECT_KEY env vars.",
218
+ inputSchema: {
219
+ provider: z.enum(["linear", "jira"]).describe("Ticket system to create in"),
220
+ pid: z.string().optional().describe("Hypothesis pid to use (defaults to current top hypothesis)")
221
+ }
222
+ },
223
+ withTierGate(getTierForTool("create_ticket"), async ({ provider, pid }) => {
224
+ trackCall("create_ticket");
225
+ const port = process.env.PORT ?? "3000";
226
+ try {
227
+ const { default: http } = await import("http");
228
+ const body = JSON.stringify({ pid });
229
+ const resp = await new Promise((resolve, reject) => {
230
+ const req = http.request(
231
+ {
232
+ hostname: "127.0.0.1",
233
+ port: Number(port),
234
+ path: `/tickets/${provider}`,
235
+ method: "POST",
236
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
237
+ },
238
+ (res) => {
239
+ let d = "";
240
+ res.on("data", (c) => {
241
+ d += c;
242
+ });
243
+ res.on("end", () => resolve(d));
244
+ }
245
+ );
246
+ req.on("error", reject);
247
+ req.write(body);
248
+ req.end();
249
+ });
250
+ const parsed = JSON.parse(resp);
251
+ if (!parsed.ok) {
252
+ return { content: [{ type: "text", text: `Failed to create ${provider} ticket: ${parsed.error ?? "unknown error"}` }] };
253
+ }
254
+ return {
255
+ content: [{
256
+ type: "text",
257
+ text: `\u2705 ${provider === "linear" ? "Linear" : "Jira"} ticket created!
258
+
259
+ URL: ${parsed.url}
260
+ ID: ${parsed.id ?? parsed.key}`
261
+ }]
262
+ };
263
+ } catch (err) {
264
+ return { content: [{ type: "text", text: `Error creating ticket: ${err instanceof Error ? err.message : String(err)}
265
+
266
+ Make sure the Mergen server is running and the API keys are configured.` }] };
267
+ }
268
+ })
269
+ );
270
+ server.registerTool(
271
+ "get_snapshots",
272
+ {
273
+ description: "Returns diagnostic snapshots captured when a breakpoint was hit. Each snapshot bundles the triggering event with the 20 most recent console events, 10 most recent network events, and the latest DOM/storage context snapshot. Download a full snapshot bundle via GET /snapshots/:id. Use after set_breakpoint fires to replay the exact state offline.",
274
+ inputSchema: {
275
+ limit: z.number().int().min(1).max(50).optional().describe("Max snapshots to return (default 10)")
276
+ }
277
+ },
278
+ async ({ limit }) => {
279
+ trackCall("get_snapshots");
280
+ const snaps = layer3Store.listSnapshots().slice(0, limit ?? 10);
281
+ if (snaps.length === 0) {
282
+ return { content: [{ type: "text", text: "No snapshots captured yet. Set a breakpoint with set_breakpoint, then trigger the condition in your browser." }] };
283
+ }
284
+ const lines = snaps.map((s) => {
285
+ const ts = new Date(s.capturedAt).toISOString();
286
+ const errSummary = s.recentLogs.filter((e) => e.level === "error").length;
287
+ const netFails = s.recentNetwork.filter((n) => n.status >= 400 || n.status === 0).length;
288
+ return [
289
+ `Snapshot ${s.id} @ ${ts}`,
290
+ ` Trigger: [${s.trigger.eventType}] ${s.trigger.summary}`,
291
+ ` Breakpoint: ${s.trigger.breakpointId}`,
292
+ ` Context: ${s.recentLogs.length} logs (${errSummary} errors), ${s.recentNetwork.length} network (${netFails} failures)`,
293
+ ` Stack: ${s.stack ? s.stack.split("\n")[0] : "none"}`,
294
+ ` Download: GET /snapshots/${s.id}`
295
+ ].join("\n");
296
+ });
297
+ return { content: [{ type: "text", text: `${snaps.length} snapshot(s):
298
+
299
+ ${lines.join("\n\n")}` }] };
300
+ }
301
+ );
302
+ server.registerTool(
303
+ "inject_logpoint",
304
+ {
305
+ description: "Injects a temporary log statement into the running browser page without restarting or redeploying. The logpoint attaches to a DOM element and fires on a given event, evaluating a JS expression. Results stream back via the ingest pipeline as console events. Use to add ad-hoc diagnostics to production-like sessions without modifying source code.",
306
+ inputSchema: {
307
+ selector: z.string().describe('CSS selector of the element to attach to (e.g. "#login-btn", "form.checkout")'),
308
+ event: z.string().describe('DOM event to listen for (e.g. "click", "submit", "change")'),
309
+ expression: z.string().describe(`JS expression to evaluate and log when the event fires (e.g. "document.querySelector('input[name=email]').value")`)
310
+ }
311
+ },
312
+ withTierGate(getTierForTool("inject_logpoint"), async ({ selector, event, expression }) => {
313
+ trackCall("inject_logpoint");
314
+ const id = layer3Store.injectLog(selector, event, expression);
315
+ return { content: [{ type: "text", text: `Logpoint injected (id: ${id}).
316
+ Waiting for "${event}" on "${selector}".
317
+ Expression: ${expression}
318
+ Results will appear in get_recent_logs within seconds of the event firing.` }] };
319
+ })
320
+ );
321
+ server.registerTool(
322
+ "remove_logpoint",
323
+ {
324
+ description: "Removes a previously injected logpoint by its id.",
325
+ inputSchema: {
326
+ id: z.string().describe("Logpoint id returned by inject_logpoint")
327
+ }
328
+ },
329
+ withTierGate(getTierForTool("remove_logpoint"), async ({ id }) => {
330
+ trackCall("remove_logpoint");
331
+ const removed = layer3Store.removeInjectedLog(id);
332
+ return { content: [{ type: "text", text: removed ? `Logpoint ${id} removed.` : `Logpoint ${id} not found (may have already fired and auto-removed).` }] };
333
+ })
334
+ );
335
+ server.registerTool(
336
+ "search_adrs",
337
+ {
338
+ description: "Search Architectural Decision Records (ADRs) to understand why the system is built the way it is. Call this before modifying any module that has an associated ADR \u2014 it will surface the constraints and trade-offs that must remain intact. Returns matching ADRs with decision, rationale, and consequences.",
339
+ inputSchema: {
340
+ query: z.string().optional().describe("Keyword to filter ADRs (searches title, decision, rationale, alternatives). Omit to list all.")
341
+ }
342
+ },
343
+ async ({ query }) => {
344
+ trackCall("search_adrs");
345
+ const results = adrStore.list(query);
346
+ if (results.length === 0) {
347
+ return { content: [{ type: "text", text: query ? `No ADRs matched "${query}".` : "No ADRs on record." }] };
348
+ }
349
+ const lines = [`## Architectural Decision Records (${results.length} found)`, ""];
350
+ for (const adr of results) {
351
+ lines.push(`### ${adr.id}: ${adr.title}`);
352
+ lines.push(`**Status:** ${adr.status} | **Date:** ${adr.date}`, "");
353
+ lines.push(`**Decision:** ${adr.decision}`, "");
354
+ if (adr.alternatives.length > 0) {
355
+ lines.push("**Alternatives rejected:**");
356
+ for (const alt of adr.alternatives) lines.push(` - ${alt}`);
357
+ lines.push("");
358
+ }
359
+ lines.push(`**Rationale:** ${adr.rationale}`, "");
360
+ if (adr.consequences) lines.push(`**Consequences:** ${adr.consequences}`, "");
361
+ lines.push("---", "");
362
+ }
363
+ return { content: [{ type: "text", text: lines.join("\n") }] };
364
+ }
365
+ );
366
+ server.registerTool(
367
+ "report_confidence",
368
+ {
369
+ description: 'File a pre-implementation confidence report before making any changes. This is the "Confidence Reporting" step of the structured agent workflow: declare your confidence score (0\u20131), what you are assuming, what you do not know, and which files you plan to modify. The report is stored and queryable via GET /confidence-reports. If confidence < 0.6, halt and present an impact report to the engineer before proceeding.',
370
+ inputSchema: {
371
+ confidence: z.number().min(0).max(1).describe("Confidence score from 0.0 (uncertain) to 1.0 (fully confident)"),
372
+ scope: z.string().min(1).max(300).describe("What this implementation covers"),
373
+ assumptions: z.array(z.string().max(500)).default([]).describe("Things treated as true without verification"),
374
+ unknowns: z.array(z.string().max(500)).default([]).describe("Open questions that could affect correctness"),
375
+ filesModified: z.array(z.string().max(500)).default([]).describe("Source files that will be changed"),
376
+ rationale: z.string().max(1e3).optional().describe("Explanation for the confidence score")
377
+ }
378
+ },
379
+ async ({ confidence, scope, assumptions, unknowns, filesModified, rationale }) => {
380
+ trackCall("report_confidence");
381
+ const report = confidenceStore.add({ confidence, scope, assumptions, unknowns, filesModified, rationale: rationale ?? "" });
382
+ const text = formatConfidenceReport(report);
383
+ return { content: [{ type: "text", text }] };
384
+ }
385
+ );
386
+ server.registerTool(
387
+ "plan_rollback",
388
+ {
389
+ description: "Generate a rollback plan BEFORE implementation starts. Provide the list of files you will modify and any commands you plan to execute. Returns: rollback procedure, whether auto-rollback is feasible, estimated recovery time, and the anticipated inverse commands. Review this plan alongside report_confidence before proceeding. The plan is also stored in the confidence report if one was filed for the same scope.",
390
+ inputSchema: {
391
+ files: z.array(z.string()).min(1).describe("Files that will be modified"),
392
+ commands: z.array(z.string()).optional().describe("Execution commands that will be run (kubectl, helm, npm, etc.)"),
393
+ featureFlag: z.string().optional().describe("Feature flag name that can disable this change at runtime")
394
+ }
395
+ },
396
+ async ({ files, commands, featureFlag }) => {
397
+ trackCall("plan_rollback");
398
+ const plan = generateRollbackPlan({ files, commands, featureFlag });
399
+ const lines = [
400
+ "## Rollback Plan",
401
+ "",
402
+ `**Files to modify:** ${plan.filesModified.length}`
403
+ ];
404
+ for (const f of plan.filesModified) lines.push(` - \`${f}\``);
405
+ lines.push("");
406
+ if (plan.featureFlag) {
407
+ lines.push(`**Feature flag:** \`${plan.featureFlag}\` (fastest rollback path)`);
408
+ lines.push("");
409
+ }
410
+ lines.push(`**Can auto-rollback:** ${plan.canAutoRollback ? "Yes" : "No \u2014 manual intervention required"}`);
411
+ lines.push(`**Estimated recovery time:** ~${Math.round(plan.estimatedRollbackMs / 1e3)}s`);
412
+ lines.push("");
413
+ if (plan.anticipatedRollbackCommands.length > 0) {
414
+ lines.push("**Anticipated rollback commands:**");
415
+ for (const cmd of plan.anticipatedRollbackCommands) lines.push(` \`${cmd}\``);
416
+ lines.push("");
417
+ }
418
+ lines.push("**Rollback procedure:**");
419
+ for (const step of plan.rollbackProcedure) lines.push(` ${step}`);
420
+ if (!plan.canAutoRollback) {
421
+ lines.push("");
422
+ lines.push("> \u26A0 This change cannot be rolled back automatically. Ensure the engineer is available to intervene if regression is detected.");
423
+ }
424
+ return { content: [{ type: "text", text: lines.join("\n") }] };
425
+ }
426
+ );
427
+ }
428
+ export {
429
+ registerUtilityTools
430
+ };
@@ -0,0 +1,215 @@
1
+ import { z } from "zod";
2
+ import { store } from "../sensor/buffer.js";
3
+ import { getRecords, recordVerdict, updateCalibrationNote, getRealVerdictCount, isCorpusSeeded, classifyVerdict } from "./calibration.js";
4
+ import { layer4Store } from "../sensor/layer4-store.js";
5
+ import { closeSession } from "./session-metrics.js";
6
+ import { trackCall } from "./tools-state.js";
7
+ function _registerValidateFix(server) {
8
+ server.registerTool(
9
+ "validate_fix",
10
+ {
11
+ description: "Check whether a code fix actually resolved the issue. Pass the pid from reconstruct_context and the Unix ms timestamp from just before the fix was applied. Compares console error counts and network error counts before vs after. Returns RESOLVED (0 errors after), PARTIAL (\u226550% reduction), REGRESSED (more errors), or UNRESOLVED. Automatically records the verdict to the calibration corpus so future diagnoses improve. ALWAYS call this after applying a fix \u2014 even if you are confident it worked.",
12
+ inputSchema: {
13
+ pid: z.string().describe("Prediction id (pid) from reconstruct_context \u2014 identifies the hypothesis being validated"),
14
+ since: z.number().int().describe("Unix ms timestamp from just before the fix was applied \u2014 used as the before/after boundary"),
15
+ window_ms: z.number().int().min(5e3).max(3e5).optional().describe("How many ms of pre-fix history to compare against (default 60000 = 1 minute)")
16
+ }
17
+ },
18
+ async ({ pid, since, window_ms = 6e4 }) => {
19
+ trackCall("validate_fix");
20
+ const windowStart = since - window_ms;
21
+ const records = getRecords();
22
+ const prediction = records.find((r) => r.pid === pid);
23
+ const logsBefore = store.getLogs(200, "error", windowStart).filter((e) => e.timestamp < since);
24
+ const netBefore = store.getNetwork(200, void 0, windowStart).filter(
25
+ (e) => e.timestamp < since && (e.status >= 400 || !!e.error)
26
+ );
27
+ const logsAfter = store.getLogs(200, "error", since);
28
+ const netAfter = store.getNetwork(200, void 0, since).filter((e) => e.status >= 400 || !!e.error);
29
+ const errsBefore = logsBefore.length + netBefore.length;
30
+ const errsAfter = logsAfter.length + netAfter.length;
31
+ const verdict = classifyVerdict(errsBefore, errsAfter);
32
+ let status;
33
+ if (verdict === "correct") {
34
+ status = errsBefore === 0 ? "CLEAN \u2014 no errors before or after" : "RESOLVED";
35
+ } else if (verdict === "partial") {
36
+ status = "PARTIAL";
37
+ } else {
38
+ status = errsAfter > errsBefore ? "REGRESSED" : "UNRESOLVED";
39
+ }
40
+ if (prediction && !prediction.verdict) {
41
+ recordVerdict(pid, verdict);
42
+ if (prediction.errorFingerprint) {
43
+ layer4Store.linkFix(prediction.errorFingerprint, pid, prediction.tag, verdict);
44
+ }
45
+ const outcome = verdict === "correct" ? "resolved" : verdict === "partial" ? "partial" : "unresolved";
46
+ closeSession(pid, outcome);
47
+ }
48
+ const tag = prediction?.tag ?? "unknown";
49
+ const verdictEmoji = verdict === "correct" ? "\u2705" : verdict === "partial" ? "\u26A0\uFE0F" : "\u274C";
50
+ const lines = [
51
+ `## Fix Validation \u2014 \`${tag}\``,
52
+ "",
53
+ `**Status: ${verdictEmoji} ${status}**`,
54
+ "",
55
+ `| | Before fix | After fix |`,
56
+ `|---|---|---|`,
57
+ `| Console errors | ${logsBefore.length} | ${logsAfter.length} |`,
58
+ `| Network errors | ${netBefore.length} | ${netAfter.length} |`,
59
+ `| **Total** | **${errsBefore}** | **${errsAfter}** |`,
60
+ "",
61
+ prediction ? `Verdict \`${verdict}\` recorded for detector \`${prediction.tag}\`.` : `No prediction record found for pid \`${pid}\` \u2014 calibration not updated.`
62
+ ];
63
+ if (verdict === "wrong") {
64
+ lines.push(
65
+ "",
66
+ "### \u274C Diagnosis was incorrect",
67
+ "",
68
+ `The fix did not reduce errors \u2014 the \`${tag}\` hypothesis appears to have been wrong.`,
69
+ "",
70
+ "**What this means:**",
71
+ "- This verdict has been recorded. Future diagnoses for this pattern will be recalibrated downward.",
72
+ "- The causal chain may have been incomplete \u2014 only the first hypothesis was tested.",
73
+ "",
74
+ "**Next steps:**",
75
+ "1. Call `reconstruct_context` again \u2014 the wrong-verdict feedback improves the next analysis.",
76
+ "2. Check if errors changed in character (new error type appearing post-fix may reveal the real cause).",
77
+ "3. If you know what the actual root cause was, the calibration record is already saved \u2014 no additional action needed."
78
+ );
79
+ } else if (verdict === "partial") {
80
+ lines.push(
81
+ "",
82
+ "> \u26A0\uFE0F **Partial resolution** \u2014 errors reduced but not eliminated. The hypothesis may be correct but the fix incomplete, or there are multiple overlapping causes. Call `reconstruct_context` to check remaining signals."
83
+ );
84
+ }
85
+ if (logsAfter.length > 0) {
86
+ lines.push("", "### Remaining console errors:");
87
+ for (const e of logsAfter.slice(0, 5)) {
88
+ const msg = e.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").slice(0, 150);
89
+ lines.push(`- \`${new Date(e.timestamp).toISOString().slice(11, 19)}\` ${msg}`);
90
+ }
91
+ }
92
+ if (netAfter.length > 0) {
93
+ lines.push("", "### Remaining network errors:");
94
+ for (const n of netAfter.slice(0, 5)) {
95
+ lines.push(`- \`${new Date(n.timestamp).toISOString().slice(11, 19)}\` ${n.method} ${n.url} \u2192 ${n.status || "ERR"}`);
96
+ }
97
+ }
98
+ const realVerdicts = getRealVerdictCount();
99
+ const provisional = isCorpusSeeded() || realVerdicts < 10;
100
+ if (prediction && verdict !== "wrong") {
101
+ lines.push(
102
+ "",
103
+ "---",
104
+ "",
105
+ "### \u{1F4DD} Optional: record why (for future reference)",
106
+ "",
107
+ `Root cause \`${tag}\` is recorded. If you know *why* this pattern occurred \u2014 a constraint, a workaround, or context that would help future-you \u2014 call:`,
108
+ "",
109
+ `\`\`\``,
110
+ `record_fix_rationale(pid: "${pid}", rationale: "your note here")`,
111
+ `\`\`\``,
112
+ "",
113
+ "This is skippable at zero cost \u2014 ignore it and nothing breaks. If answered, the note surfaces wherever past incidents for this detector are shown."
114
+ );
115
+ }
116
+ if (provisional) {
117
+ const countLabel = realVerdicts === 0 ? "no local verdicts yet" : `${realVerdicts} local verdict${realVerdicts !== 1 ? "s" : ""} recorded`;
118
+ lines.push(
119
+ "",
120
+ `> \u26A0\uFE0F **Provisional accuracy** \u2014 ${countLabel} in this environment. The published 94% figure is from built-in priors, not measurements on your stack. Treat confidence scores as estimates until you have 10+ real verdicts. Autopilot applies a more conservative effective threshold during this period (the untrained classifier holds blended confidence below the execution gate).`
121
+ );
122
+ } else {
123
+ lines.push(
124
+ "",
125
+ `> \u2705 **${realVerdicts} local verdicts recorded** \u2014 confidence estimates are calibrated from your environment's actual incident history.`
126
+ );
127
+ }
128
+ return { content: [{ type: "text", text: lines.join("\n") }] };
129
+ }
130
+ );
131
+ }
132
+ function registerValidateFix(server) {
133
+ _registerValidateFix(server);
134
+ }
135
+ function registerValidateTools(server) {
136
+ _registerValidateFix(server);
137
+ server.registerTool(
138
+ "record_fix_rationale",
139
+ {
140
+ description: "Record WHY a root cause was correct \u2014 the constraint, workaround, or context that would help future-you. Call this immediately after validate_fix returns RESOLVED or PARTIAL. The note is stored on the calibration record and surfaced wherever past incidents for this detector are shown. Completely optional \u2014 if skipped, nothing breaks and you will not be reminded again for this incident.",
141
+ inputSchema: {
142
+ pid: z.string().describe("Prediction id (pid) from reconstruct_context / validate_fix"),
143
+ rationale: z.string().min(1).max(500).describe("Free-text note: why did this root cause occur? What constraint or context is worth remembering?")
144
+ }
145
+ },
146
+ async ({ pid, rationale }) => {
147
+ trackCall("record_fix_rationale");
148
+ const saved = updateCalibrationNote(pid, rationale);
149
+ return {
150
+ content: [{
151
+ type: "text",
152
+ text: saved ? `\u2705 Rationale recorded for prediction \`${pid}\`. It will appear in \`get_detector_calibration\` output for future incidents of the same type.` : `\u274C Prediction \`${pid}\` not found \u2014 rationale not saved. Check that the pid is from a recent \`reconstruct_context\` call.`
153
+ }]
154
+ };
155
+ }
156
+ );
157
+ server.registerTool(
158
+ "watch_for_fix",
159
+ {
160
+ description: "Start watching source files for changes so validate_fix runs automatically on each save. Returns immediately \u2014 validation happens in the background 2 seconds after any file changes. Results appear in the dashboard at http://127.0.0.1:3000/dashboard and in the next validate_fix call. Best practice: call right after reconstruct_context, before editing any files. Only one watch is active at a time \u2014 starting a new one stops the previous.",
161
+ inputSchema: {
162
+ pid: z.string().describe("Prediction id from reconstruct_context"),
163
+ since: z.number().int().describe("Unix ms timestamp from just before you started editing \u2014 the before/after boundary"),
164
+ paths: z.array(z.string()).min(1).max(50).describe('File or directory paths to watch for changes (e.g. ["src/auth/token.ts", "src/api/"])')
165
+ }
166
+ },
167
+ async ({ pid, since, paths }) => {
168
+ trackCall("watch_for_fix");
169
+ const { startFileWatch } = await import("../sensor/fs-watcher.js");
170
+ startFileWatch({ pid, since, paths });
171
+ return {
172
+ content: [{
173
+ type: "text",
174
+ text: [
175
+ `Watching ${paths.length} path${paths.length !== 1 ? "s" : ""} for changes.`,
176
+ "",
177
+ `Paths: ${paths.slice(0, 5).join(", ")}${paths.length > 5 ? ` +${paths.length - 5} more` : ""}`,
178
+ `Prediction: \`${pid}\``,
179
+ "",
180
+ `**Next steps:**`,
181
+ `1. Tell the user: "Apply your fix and save the file."`,
182
+ `2. After they save, call \`validate_fix(pid: "${pid}", since: ${since})\` to get the RESOLVED/PARTIAL/REGRESSED verdict.`,
183
+ `3. Report the verdict clearly: "\u2705 RESOLVED \u2014 0 errors after fix" or "\u274C UNRESOLVED \u2014 N errors remain".`,
184
+ `4. If RESOLVED, call \`stop_file_watch()\`. If not, show remaining errors and suggest next steps.`,
185
+ "",
186
+ `Dashboard: http://127.0.0.1:3000/dashboard (shows live validation state)`
187
+ ].join("\n")
188
+ }]
189
+ };
190
+ }
191
+ );
192
+ server.registerTool(
193
+ "stop_file_watch",
194
+ {
195
+ description: "Stop the active file watch. Called automatically when a fix is confirmed resolved. Call manually when switching to a different issue or ending a debug session.",
196
+ inputSchema: {}
197
+ },
198
+ async () => {
199
+ trackCall("stop_file_watch");
200
+ const { stopFileWatch, getWatchState } = await import("../sensor/fs-watcher.js");
201
+ const state = getWatchState();
202
+ stopFileWatch();
203
+ return {
204
+ content: [{
205
+ type: "text",
206
+ text: state ? `Stopped watching ${state.paths.length} path${state.paths.length !== 1 ? "s" : ""}.` : "No active file watch."
207
+ }]
208
+ };
209
+ }
210
+ );
211
+ }
212
+ export {
213
+ registerValidateFix,
214
+ registerValidateTools
215
+ };
@@ -0,0 +1 @@
1
+ export * from '../__stubs__/closed-source.js';
@@ -0,0 +1,11 @@
1
+ import { randomUUID } from "crypto";
2
+ function getTraceId() {
3
+ return process.env.MERGEN_TRACE_ID || randomUUID();
4
+ }
5
+ function traceEnv() {
6
+ return { MERGEN_TRACE_ID: getTraceId() };
7
+ }
8
+ export {
9
+ getTraceId,
10
+ traceEnv
11
+ };