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,171 @@
1
+ import { store } from "../sensor/buffer.js";
2
+ import { getCurrentTraceContext } from "../datadog/otel-trace.js";
3
+ import { registerFileContextResource } from "./resource-file-context.js";
4
+ import { postmortemStore } from "./postmortem-store.js";
5
+ import { incidentStore } from "../sensor/incident-store.js";
6
+ function registerResources(server) {
7
+ server.registerResource(
8
+ "mergen-buffer-snapshot",
9
+ "mergen://buffer/snapshot",
10
+ {
11
+ description: "Current ring buffer statistics and session health signals. Read this before calling any tool to get error/warning counts, active signals (auth failures, network bursts, etc.), and the timestamp of the most recent event.",
12
+ mimeType: "application/json"
13
+ },
14
+ async (uri) => ({
15
+ contents: [{
16
+ uri: uri.href,
17
+ mimeType: "application/json",
18
+ text: JSON.stringify({
19
+ size: store.size(),
20
+ counters: store.getCounters(),
21
+ lastEventAt: store.lastEventAt(),
22
+ clearedAt: store.clearedAt(),
23
+ signals: store.getSignals()
24
+ }, null, 2)
25
+ }]
26
+ })
27
+ );
28
+ server.registerResource(
29
+ "mergen-recent-errors",
30
+ "mergen://buffer/errors",
31
+ {
32
+ description: "Most recent console.error events (up to 20). Includes de-minified stack traces and git-blame suspects where available. Use as a quick read before calling reconstruct_context.",
33
+ mimeType: "application/json"
34
+ },
35
+ async (uri) => ({
36
+ contents: [{
37
+ uri: uri.href,
38
+ mimeType: "application/json",
39
+ text: JSON.stringify(store.getLogs(20, "error"), null, 2)
40
+ }]
41
+ })
42
+ );
43
+ server.registerResource(
44
+ "mergen-network-failures",
45
+ "mergen://buffer/network-failures",
46
+ {
47
+ description: "Recent failed network requests: 4xx, 5xx, and connection errors. Includes request/response bodies and W3C trace context (traceId, tracestate, baggage). Read this when diagnosing API failures before calling get_network_activity.",
48
+ mimeType: "application/json"
49
+ },
50
+ async (uri) => ({
51
+ contents: [{
52
+ uri: uri.href,
53
+ mimeType: "application/json",
54
+ text: JSON.stringify(
55
+ store.getNetwork(20).filter((e) => e.status >= 400 || e.status === 0 || !!e.error),
56
+ null,
57
+ 2
58
+ )
59
+ }]
60
+ })
61
+ );
62
+ server.registerResource(
63
+ "mergen-agent-trace-context",
64
+ "mergen://agent/trace-context",
65
+ {
66
+ description: "Current W3C traceparent for this Mergen session. Inject into your own HTTP calls via the traceparent header to extend the trace chain. Changes each time a new MCP tool call starts a server span.",
67
+ mimeType: "application/json"
68
+ },
69
+ async (uri) => {
70
+ const ctx = getCurrentTraceContext();
71
+ return {
72
+ contents: [{
73
+ uri: uri.href,
74
+ mimeType: "application/json",
75
+ text: JSON.stringify({
76
+ traceparent: ctx.traceparent,
77
+ traceId: ctx.traceId,
78
+ spanId: ctx.spanId,
79
+ usage: "Inject as HTTP header: traceparent: <value>"
80
+ }, null, 2)
81
+ }]
82
+ };
83
+ }
84
+ );
85
+ server.registerResource(
86
+ "mergen-corpus-postmortems",
87
+ "mergen://corpus/postmortems",
88
+ {
89
+ description: "Recent incident postmortems from the corpus (up to 10). Each entry contains: root cause, fix command, MTTR, confidence, git SHA/branch, and resolution method. Read this before triaging a new incident to check for corpus precedent \u2014 if this failure mode has appeared before, the top postmortem gives you the verified fix immediately. Updated automatically each time an incident is resolved.",
90
+ mimeType: "application/json"
91
+ },
92
+ async (uri) => {
93
+ const postmortems = postmortemStore.list(10).map((pm) => ({
94
+ pid: pm.pid,
95
+ tag: pm.tag,
96
+ service: pm.service,
97
+ rootCause: pm.rootCause,
98
+ fixCommand: pm.fixCommand,
99
+ confidence: pm.confidence,
100
+ mttrMs: pm.mttrMs,
101
+ resolvedAutonomously: pm.resolvedAutonomously,
102
+ generatedAt: new Date(pm.generatedAt).toISOString(),
103
+ gitBranch: pm.gitBranch,
104
+ gitSha: pm.gitSha
105
+ }));
106
+ const stats = postmortemStore.tagStats();
107
+ return {
108
+ contents: [{
109
+ uri: uri.href,
110
+ mimeType: "application/json",
111
+ text: JSON.stringify({
112
+ totalPostmortems: postmortemStore.count(),
113
+ failureModeCoverage: stats.length,
114
+ recentPostmortems: postmortems
115
+ }, null, 2)
116
+ }]
117
+ };
118
+ }
119
+ );
120
+ server.registerResource(
121
+ "mergen-corpus-topology",
122
+ "mergen://corpus/topology",
123
+ {
124
+ description: "Service \xD7 failure-mode topology matrix from the incident corpus. Shows which services have recurring failure patterns, incident counts, and MTTR per service. Use this to understand systemic brittleness before triaging a new incident. A service with high incident count and high MTTR is a refactor candidate.",
125
+ mimeType: "application/json"
126
+ },
127
+ async (uri) => {
128
+ const incidents = incidentStore.list(void 0, 200);
129
+ const serviceMap = /* @__PURE__ */ new Map();
130
+ for (const inc of incidents) {
131
+ const svc = inc.service ?? "unknown";
132
+ if (!serviceMap.has(svc)) {
133
+ serviceMap.set(svc, { incidentCount: 0, failureModes: /* @__PURE__ */ new Map(), mttrs: [], lastIncidentAt: 0 });
134
+ }
135
+ const entry = serviceMap.get(svc);
136
+ entry.incidentCount++;
137
+ if (inc.tag) {
138
+ entry.failureModes.set(inc.tag, (entry.failureModes.get(inc.tag) ?? 0) + 1);
139
+ }
140
+ if (inc.resolvedAt && inc.createdAt) {
141
+ entry.mttrs.push(inc.resolvedAt - inc.createdAt);
142
+ }
143
+ if (inc.createdAt > entry.lastIncidentAt) {
144
+ entry.lastIncidentAt = inc.createdAt;
145
+ }
146
+ }
147
+ const topology = [...serviceMap.entries()].map(([service, data]) => ({
148
+ service,
149
+ incidentCount: data.incidentCount,
150
+ avgMttrMs: data.mttrs.length > 0 ? Math.round(data.mttrs.reduce((a, b) => a + b, 0) / data.mttrs.length) : null,
151
+ topFailureModes: [...data.failureModes.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([tag, count]) => ({ tag, count })),
152
+ lastIncidentAt: data.lastIncidentAt > 0 ? new Date(data.lastIncidentAt).toISOString() : null
153
+ })).sort((a, b) => b.incidentCount - a.incidentCount);
154
+ return {
155
+ contents: [{
156
+ uri: uri.href,
157
+ mimeType: "application/json",
158
+ text: JSON.stringify({
159
+ totalServices: topology.length,
160
+ totalIncidents: incidents.length,
161
+ topology
162
+ }, null, 2)
163
+ }]
164
+ };
165
+ }
166
+ );
167
+ registerFileContextResource(server);
168
+ }
169
+ export {
170
+ registerResources
171
+ };
@@ -0,0 +1,108 @@
1
+ const CONFUSABLES = [
2
+ // ── r ─────────────────────────────────────────────────────────────────────
3
+ [/[гԹ𝐫𝑟𝒓𝓻𝔯𝕣𝖗𝗿𝘳𝙧ɼɾᵣ]/gu, "r"],
4
+ // ── m ─────────────────────────────────────────────────────────────────────
5
+ [/[𝐦𝑚𝒎𝓶𝔪𝕞𝖒𝗺𝘮𝙢ṃɱ]/gu, "m"],
6
+ // ── d ─────────────────────────────────────────────────────────────────────
7
+ [/[ԁ𝐝𝑑𝒅𝓭𝔡𝕕𝖉𝗱𝘥𝙙ḋḍ]/gu, "d"],
8
+ // ── o / 0 ─────────────────────────────────────────────────────────────────
9
+ [/[оοσ𝐨𝑜𝒐𝓸𝔬𝕠𝖔𝗼𝘰𝙤ọởôöō]/gu, "o"],
10
+ // ── p ─────────────────────────────────────────────────────────────────────
11
+ [/[рρ𝐩𝑝𝒑𝓹𝔭𝕡𝖕𝗽𝘱𝙥]/gu, "p"],
12
+ // ── e ─────────────────────────────────────────────────────────────────────
13
+ [/[еε𝐞𝑒𝒆𝓮𝔢𝕖𝖊𝗲𝘦𝙚ëēėę]/gu, "e"],
14
+ // ── t ─────────────────────────────────────────────────────────────────────
15
+ [/[τ𝐭𝑡𝒕𝓽𝔱𝕥𝖙𝗍𝘵𝙩ţ]/gu, "t"],
16
+ // ── a ─────────────────────────────────────────────────────────────────────
17
+ [/[аα𝐚𝑎𝒂𝓪𝔞𝕒𝖆𝗮𝘢𝙖äãāăâ]/gu, "a"],
18
+ // ── b ─────────────────────────────────────────────────────────────────────
19
+ [/[Ь𝐛𝑏𝒃𝓫𝔟𝕓𝖇𝗯𝘣𝙗ḃ]/gu, "b"],
20
+ // ── c ─────────────────────────────────────────────────────────────────────
21
+ [/[сϲ𝐜𝑐𝒄𝓬𝔠𝕔𝖈𝗰𝘤𝙘çć]/gu, "c"],
22
+ // ── f ─────────────────────────────────────────────────────────────────────
23
+ [/[𝐟𝑓𝒇𝓯𝔣𝕗𝖋𝗳𝘧𝙛ḟ]/gu, "f"],
24
+ // ── g ─────────────────────────────────────────────────────────────────────
25
+ [/[𝐠𝑔𝒈𝓰𝔤𝕘𝖌𝗴𝘨𝙜ğǧ]/gu, "g"],
26
+ // ── i ─────────────────────────────────────────────────────────────────────
27
+ [/[іϊ𝐢𝑖𝒊𝓲𝔦𝕚𝖎𝗶𝘪𝙞ìíîïīįı]/gu, "i"],
28
+ // ── k ─────────────────────────────────────────────────────────────────────
29
+ [/[κ𝐤𝑘𝒌𝓴𝔨𝕜𝖐𝗸𝘬𝙠]/gu, "k"],
30
+ // ── l ─────────────────────────────────────────────────────────────────────
31
+ [/[ӏ𝐥𝑙𝒍𝓵𝔩𝕝𝖑𝗹𝘭𝙡ļłℓ]/gu, "l"],
32
+ // ── n ─────────────────────────────────────────────────────────────────────
33
+ [/[η𝐧𝑛𝒏𝓷𝔫𝕟𝖓𝗻𝘯𝙣ñńņ]/gu, "n"],
34
+ // ── s ─────────────────────────────────────────────────────────────────────
35
+ [/[ѕ𝐬𝑠𝒔𝓼𝔰𝕤𝖘𝗌𝘴𝙨śšş]/gu, "s"],
36
+ // ── u ─────────────────────────────────────────────────────────────────────
37
+ [/[υ𝐮𝑢𝒖𝓾𝔲𝕦𝖚𝗎𝘶𝙪ùúûüū]/gu, "u"],
38
+ // ── v ─────────────────────────────────────────────────────────────────────
39
+ [/[ν𝐯𝑣𝒗𝓿𝔳𝕧𝖛𝗏𝘷𝙫]/gu, "v"],
40
+ // ── w ─────────────────────────────────────────────────────────────────────
41
+ [/[ω𝐰𝑤𝒘𝔀𝔴𝕨𝖜𝗐𝘸𝙬]/gu, "w"],
42
+ // ── x ─────────────────────────────────────────────────────────────────────
43
+ [/[х×𝐱𝑥𝒙𝔁𝔵𝕩𝖝𝗑𝘹𝙭]/gu, "x"],
44
+ // ── y ─────────────────────────────────────────────────────────────────────
45
+ [/[уγ𝐲𝑦𝒚𝔂𝔶𝕪𝖞𝗒𝘺𝙮ýÿ]/gu, "y"],
46
+ // ── z ─────────────────────────────────────────────────────────────────────
47
+ [/[𝐳𝑧𝒛𝔃𝔷𝕫𝖟𝗓𝘻𝙯źżž]/gu, "z"]
48
+ ];
49
+ function _deconfuse(s) {
50
+ let out = s;
51
+ for (const [re, replacement] of CONFUSABLES) {
52
+ out = out.replace(re, replacement);
53
+ }
54
+ return out;
55
+ }
56
+ function _stripInvisible(s) {
57
+ return s.replace(new RegExp("\\p{Cf}", "gu"), "");
58
+ }
59
+ function _decodeEscapesOnce(s) {
60
+ return s.replace(
61
+ /\\x([0-9a-fA-F]{2})|\\u([0-9a-fA-F]{4})|\\([0-7]{1,3})|\\(.)/g,
62
+ (_m, hex, uni, oct, generic) => {
63
+ if (hex !== void 0) return String.fromCharCode(parseInt(hex, 16));
64
+ if (uni !== void 0) return String.fromCharCode(parseInt(uni, 16));
65
+ if (oct !== void 0) return String.fromCharCode(parseInt(oct, 8));
66
+ return generic;
67
+ }
68
+ );
69
+ }
70
+ function _decodeEscapesFixedPoint(s) {
71
+ let out = s;
72
+ for (let i = 0; i < 5; i++) {
73
+ const next = _decodeEscapesOnce(out);
74
+ if (next === out) break;
75
+ out = next;
76
+ }
77
+ return out;
78
+ }
79
+ function _unwrapBase64(s) {
80
+ if (!/base64/i.test(s)) return s;
81
+ const tokenRe = /[A-Za-z0-9+/]{12,}={0,2}/g;
82
+ let appended = "";
83
+ let match;
84
+ let guard = 0;
85
+ while ((match = tokenRe.exec(s)) && guard < 10) {
86
+ guard++;
87
+ try {
88
+ const decoded = Buffer.from(match[0], "base64").toString("utf8");
89
+ if (decoded && /^[\t\n\r\x20-\x7E]+$/.test(decoded) && /[a-zA-Z]/.test(decoded)) {
90
+ appended += " " + decoded;
91
+ }
92
+ } catch {
93
+ }
94
+ }
95
+ return appended ? s + appended : s;
96
+ }
97
+ function normalizeForMatching(s) {
98
+ let out = s.normalize("NFKC");
99
+ out = _stripInvisible(out);
100
+ out = _decodeEscapesFixedPoint(out);
101
+ out = out.replace(/['"]/g, "");
102
+ out = _unwrapBase64(out);
103
+ out = out.replace(/\s+/g, " ").toLowerCase();
104
+ return _deconfuse(out);
105
+ }
106
+ export {
107
+ normalizeForMatching
108
+ };
@@ -0,0 +1,83 @@
1
+ import { postThreadReply } from "./slack.js";
2
+ import logger from "../sensor/logger.js";
3
+ const DISCORD_WEBHOOK_URL = process.env.MERGEN_DISCORD_WEBHOOK_URL;
4
+ function stripSlackMarkdown(text) {
5
+ return text.replace(/\*([^*]+)\*/g, "**$1**").replace(/_([^_]+)_/g, "_$1_").replace(/`([^`]+)`/g, "`$1`");
6
+ }
7
+ async function notifyDiscord(message, opts) {
8
+ if (!DISCORD_WEBHOOK_URL) return;
9
+ const content = stripSlackMarkdown(message).slice(0, 2e3);
10
+ const color = opts?.priority === "urgent" || opts?.priority === "high" ? 15548997 : 5793266;
11
+ const res = await fetch(DISCORD_WEBHOOK_URL, {
12
+ method: "POST",
13
+ headers: { "Content-Type": "application/json" },
14
+ body: JSON.stringify({
15
+ embeds: [{ description: content, color }]
16
+ }),
17
+ signal: AbortSignal.timeout(5e3)
18
+ });
19
+ if (!res.ok) logger.debug({ status: res.status }, "notifications: Discord non-2xx");
20
+ }
21
+ const NTFY_BASE_URL = process.env.MERGEN_NTFY_URL ?? "https://ntfy.sh";
22
+ const NTFY_TOPIC = process.env.MERGEN_NTFY_TOPIC ?? "";
23
+ const NTFY_PRIORITY_MAP = {
24
+ low: "2",
25
+ normal: "3",
26
+ high: "4",
27
+ urgent: "5"
28
+ };
29
+ async function notifyNtfy(message, opts) {
30
+ if (!NTFY_TOPIC) return;
31
+ const priority = NTFY_PRIORITY_MAP[opts?.priority ?? "normal"];
32
+ const plainText = message.replace(/[*_`]/g, "").slice(0, 4096);
33
+ const headers = {
34
+ "Content-Type": "text/plain; charset=utf-8",
35
+ "Priority": priority,
36
+ "Title": "Mergen"
37
+ };
38
+ if (opts?.tags?.length) headers["Tags"] = opts.tags.join(",");
39
+ const ntfyToken = process.env.MERGEN_NTFY_TOKEN;
40
+ if (ntfyToken) headers["Authorization"] = `Bearer ${ntfyToken}`;
41
+ const res = await fetch(`${NTFY_BASE_URL}/${NTFY_TOPIC}`, {
42
+ method: "POST",
43
+ headers,
44
+ body: plainText,
45
+ signal: AbortSignal.timeout(5e3)
46
+ });
47
+ if (!res.ok) logger.debug({ status: res.status }, "notifications: ntfy non-2xx");
48
+ }
49
+ async function notify(pid, message, opts) {
50
+ const tasks = [];
51
+ if (pid) {
52
+ tasks.push(
53
+ postThreadReply(pid, message).catch((err) => {
54
+ logger.debug({ err }, "notifications: Slack delivery failed");
55
+ })
56
+ );
57
+ }
58
+ if (DISCORD_WEBHOOK_URL) {
59
+ tasks.push(
60
+ notifyDiscord(message, opts).catch((err) => {
61
+ logger.debug({ err }, "notifications: Discord delivery failed");
62
+ })
63
+ );
64
+ }
65
+ if (NTFY_TOPIC) {
66
+ tasks.push(
67
+ notifyNtfy(message, opts).catch((err) => {
68
+ logger.debug({ err }, "notifications: ntfy delivery failed");
69
+ })
70
+ );
71
+ }
72
+ if (tasks.length === 0) {
73
+ logger.debug({ pid }, "notifications: no channels configured \u2014 message dropped");
74
+ }
75
+ await Promise.all(tasks);
76
+ }
77
+ function hasNotificationChannel() {
78
+ return !!(process.env.MERGEN_SLACK_BOT_TOKEN || DISCORD_WEBHOOK_URL || NTFY_TOPIC);
79
+ }
80
+ export {
81
+ hasNotificationChannel,
82
+ notify
83
+ };
@@ -0,0 +1,87 @@
1
+ import fs from "fs";
2
+ import { generateKeyPair, exportJWK, exportPKCS8, importPKCS8, SignJWT } from "jose";
3
+ import { randomUUID, createHash } from "crypto";
4
+ import { OIDC_SIGNING_KEY_FILE, DATA_DIR } from "../sensor/paths.js";
5
+ import logger from "../sensor/logger.js";
6
+ const ALG = "RS256";
7
+ let _cached = null;
8
+ async function loadOrGenerateKey() {
9
+ if (_cached) return _cached;
10
+ const envKey = process.env.MERGEN_OIDC_SIGNING_KEY;
11
+ if (envKey) {
12
+ const pkcs8 = envKey.includes("\\n") ? envKey.replace(/\\n/g, "\n") : envKey;
13
+ const privateKey2 = await importPKCS8(pkcs8, ALG, { extractable: true });
14
+ const publicJwk2 = await exportJWK(privateKey2);
15
+ delete publicJwk2.d;
16
+ delete publicJwk2.p;
17
+ delete publicJwk2.q;
18
+ delete publicJwk2.dp;
19
+ delete publicJwk2.dq;
20
+ delete publicJwk2.qi;
21
+ const kid2 = createHash("sha256").update(String(publicJwk2.n)).digest("hex").slice(0, 16);
22
+ _cached = { kid: kid2, privateKey: privateKey2, publicJwk: { ...publicJwk2, kid: kid2, alg: ALG, use: "sig" } };
23
+ return _cached;
24
+ }
25
+ try {
26
+ const raw = fs.readFileSync(OIDC_SIGNING_KEY_FILE, "utf8");
27
+ const persisted = JSON.parse(raw);
28
+ const privateKey2 = await importPKCS8(persisted.pkcs8, ALG, { extractable: true });
29
+ const publicJwk2 = await exportJWK(privateKey2);
30
+ delete publicJwk2.d;
31
+ delete publicJwk2.p;
32
+ delete publicJwk2.q;
33
+ delete publicJwk2.dp;
34
+ delete publicJwk2.dq;
35
+ delete publicJwk2.qi;
36
+ _cached = { kid: persisted.kid, privateKey: privateKey2, publicJwk: { ...publicJwk2, kid: persisted.kid, alg: ALG, use: "sig" } };
37
+ return _cached;
38
+ } catch {
39
+ }
40
+ const { publicKey, privateKey } = await generateKeyPair(ALG, { extractable: true });
41
+ const kid = randomUUID();
42
+ const publicJwk = { ...await exportJWK(publicKey), kid, alg: ALG, use: "sig" };
43
+ try {
44
+ fs.mkdirSync(DATA_DIR, { recursive: true, mode: 448 });
45
+ const pkcs8 = await exportPKCS8(privateKey);
46
+ fs.writeFileSync(OIDC_SIGNING_KEY_FILE, JSON.stringify({ kid, pkcs8 }), { encoding: "utf8", mode: 384 });
47
+ } catch (err) {
48
+ logger.warn({ err }, "oidc-issuer: could not persist signing key \u2014 using an ephemeral in-process key. Tokens issued before a restart will fail verification against the JWKS this process now exposes.");
49
+ }
50
+ _cached = { kid, privateKey, publicJwk };
51
+ return _cached;
52
+ }
53
+ function getIssuerUrl() {
54
+ return process.env.MERGEN_PUBLIC_URL ?? null;
55
+ }
56
+ function isOidcIssuerConfigured() {
57
+ return getIssuerUrl() !== null;
58
+ }
59
+ async function getPublicJwks() {
60
+ const { publicJwk } = await loadOrGenerateKey();
61
+ return { keys: [publicJwk] };
62
+ }
63
+ const DEFAULT_TTL_SECONDS = 300;
64
+ const MAX_TTL_SECONDS = 3600;
65
+ async function issueFederationToken(request) {
66
+ const issuer = getIssuerUrl();
67
+ if (!issuer) {
68
+ throw new Error("oidc-issuer: MERGEN_PUBLIC_URL must be set to issue federation tokens \u2014 the target cloud provider needs a reachable issuer URL to fetch JWKS from.");
69
+ }
70
+ const { kid, privateKey } = await loadOrGenerateKey();
71
+ const ttl = Math.min(request.ttlSeconds ?? DEFAULT_TTL_SECONDS, MAX_TTL_SECONDS);
72
+ return new SignJWT({ sub: `agent:${request.agentId}` }).setProtectedHeader({ alg: ALG, kid }).setIssuedAt().setIssuer(issuer).setAudience(request.audience).setExpirationTime(`${ttl}s`).sign(privateKey);
73
+ }
74
+ function _resetOidcIssuerForTesting() {
75
+ _cached = null;
76
+ try {
77
+ fs.unlinkSync(OIDC_SIGNING_KEY_FILE);
78
+ } catch {
79
+ }
80
+ }
81
+ export {
82
+ _resetOidcIssuerForTesting,
83
+ getIssuerUrl,
84
+ getPublicJwks,
85
+ isOidcIssuerConfigured,
86
+ issueFederationToken
87
+ };