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,809 @@
1
+ import { readFileSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+ import { log, success, error, hr, ask, VERSION } from "./shared.js";
5
+ async function inviteCommand() {
6
+ let host = process.env.MERGEN_DASHBOARD_URL ?? "";
7
+ if (!host) {
8
+ let port = 3e3;
9
+ for (let p = 3e3; p <= 3010; p++) {
10
+ try {
11
+ const r = await fetch(`http://127.0.0.1:${p}/health`, { signal: AbortSignal.timeout(600) });
12
+ if (r.ok) {
13
+ port = p;
14
+ break;
15
+ }
16
+ } catch {
17
+ }
18
+ }
19
+ let lanIp = "127.0.0.1";
20
+ try {
21
+ const { networkInterfaces } = await import("os");
22
+ const ifaces = networkInterfaces();
23
+ for (const iface of Object.values(ifaces)) {
24
+ for (const addr of iface ?? []) {
25
+ if (!addr.internal && addr.family === "IPv4") {
26
+ lanIp = addr.address;
27
+ break;
28
+ }
29
+ }
30
+ }
31
+ } catch {
32
+ }
33
+ host = `http://${lanIp}:${port}`;
34
+ }
35
+ let secret = "";
36
+ try {
37
+ const { readFileSync: readFileSync2 } = await import("fs");
38
+ const { join: join2 } = await import("path");
39
+ const { homedir: homedir2 } = await import("os");
40
+ secret = readFileSync2(join2(homedir2(), ".mergen", "secret"), "utf8").trim();
41
+ } catch {
42
+ }
43
+ const inviteUrl = `${host}/setup?server=${encodeURIComponent(host)}&token=${encodeURIComponent(secret)}`;
44
+ hr();
45
+ console.log("\u2B21 Mergen Team Invite\n");
46
+ console.log("Share this URL with your teammate:");
47
+ console.log(`
48
+ ${inviteUrl}
49
+ `);
50
+ console.log("When they open it, the setup wizard auto-fills the server endpoint and secret.");
51
+ console.log("\nOr they can run:");
52
+ console.log(` npx mergen-server join "${inviteUrl}"`);
53
+ hr();
54
+ console.log(`
55
+ Note: make sure your Mergen server is reachable from their machine.`);
56
+ console.log(` If it's local-only, set MERGEN_BIND=0.0.0.0 and restart.`);
57
+ }
58
+ async function joinCommand(args) {
59
+ const url = args[1];
60
+ if (!url) {
61
+ error("Usage: mergen-server join <invite-url>");
62
+ process.exit(1);
63
+ }
64
+ let parsed;
65
+ try {
66
+ parsed = new URL(url);
67
+ } catch {
68
+ error("Invalid URL: " + url);
69
+ process.exit(1);
70
+ return;
71
+ }
72
+ const server = parsed.searchParams.get("server") ?? "";
73
+ const token = parsed.searchParams.get("token") ?? "";
74
+ if (!server) {
75
+ error("Invite URL missing server parameter");
76
+ process.exit(1);
77
+ }
78
+ console.log("\u2B21 Joining Mergen team instance...\n");
79
+ log(`Server: ${server}`);
80
+ const { writeFileSync: writeFileSync2, mkdirSync: mkdirSync2 } = await import("fs");
81
+ const { join: join2 } = await import("path");
82
+ const { homedir: homedir2 } = await import("os");
83
+ const configDir = join2(homedir2(), ".mergen");
84
+ mkdirSync2(configDir, { recursive: true });
85
+ writeFileSync2(join2(configDir, "team-server"), server, "utf8");
86
+ if (token) writeFileSync2(join2(configDir, "team-secret"), token, { encoding: "utf8", mode: 384 });
87
+ success(`Team server saved to ~/.mergen/team-server`);
88
+ try {
89
+ const r = await fetch(`${server}/health`, { signal: AbortSignal.timeout(3e3) });
90
+ if (r.ok) {
91
+ success(`Server reachable at ${server}`);
92
+ } else {
93
+ error(`Server returned ${(await r.json()).status}`);
94
+ }
95
+ } catch {
96
+ error(`Could not reach ${server} \u2014 make sure MERGEN_BIND=0.0.0.0 is set on the server`);
97
+ }
98
+ log(`
99
+ Open the dashboard: ${server}/dashboard`);
100
+ }
101
+ async function impactReportCommand(args) {
102
+ const htmlMode = args.includes("--html");
103
+ const slideMode = args.includes("--slide");
104
+ const baselineMode = args.includes("--baseline");
105
+ const compareMode = args.includes("--compare");
106
+ let port = 3e3;
107
+ let serverFound = false;
108
+ for (let p = 3e3; p <= 3010; p++) {
109
+ try {
110
+ const r = await fetch(`http://127.0.0.1:${p}/health`, { signal: AbortSignal.timeout(800) });
111
+ if (r.ok) {
112
+ port = p;
113
+ serverFound = true;
114
+ break;
115
+ }
116
+ } catch {
117
+ }
118
+ }
119
+ if (!serverFound) {
120
+ error("Server not running. Start with: mergen-server start");
121
+ process.exit(1);
122
+ }
123
+ const secret = (() => {
124
+ try {
125
+ return readFileSync(join(homedir(), ".mergen", "secret"), "utf8").trim();
126
+ } catch {
127
+ return "";
128
+ }
129
+ })();
130
+ const headers = {};
131
+ if (secret) headers["x-mergen-secret"] = secret;
132
+ const BASE = `http://127.0.0.1:${port}`;
133
+ if (htmlMode) {
134
+ const res2 = await fetch(`${BASE}/impact-report?format=html`, { headers, signal: AbortSignal.timeout(1e4) });
135
+ if (!res2.ok) {
136
+ error(`Server returned ${res2.status}`);
137
+ process.exit(1);
138
+ }
139
+ const html = await res2.text();
140
+ const outFile = `./impact-report-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.html`;
141
+ writeFileSync(outFile, html, "utf8");
142
+ success(`HTML report written to ${outFile}`);
143
+ console.log(` Open in browser or send to your CISO.
144
+ `);
145
+ return;
146
+ }
147
+ if (slideMode || baselineMode || compareMode) {
148
+ const res2 = await fetch(`${BASE}/impact-report?format=slide`, { headers, signal: AbortSignal.timeout(1e4) });
149
+ if (!res2.ok) {
150
+ error(`Server returned ${res2.status}`);
151
+ process.exit(1);
152
+ }
153
+ const { slide: s } = await res2.json();
154
+ const baselinePath = join(homedir(), ".mergen", "baseline.json");
155
+ if (baselineMode) {
156
+ const { mkdirSync: mkdirSync2 } = await import("fs");
157
+ mkdirSync2(join(homedir(), ".mergen"), { recursive: true });
158
+ writeFileSync(baselinePath, JSON.stringify({ ...s, savedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n", "utf8");
159
+ success(`Baseline saved to ${baselinePath}`);
160
+ console.log(" Run again at Day 30 with --compare to see the delta.\n");
161
+ _printSlide(s);
162
+ return;
163
+ }
164
+ if (compareMode) {
165
+ let baseline = null;
166
+ try {
167
+ baseline = JSON.parse(readFileSync(baselinePath, "utf8"));
168
+ } catch {
169
+ }
170
+ if (!baseline) {
171
+ error("No baseline found. Run: mergen-server impact-report --baseline on Day 1 first.");
172
+ process.exit(1);
173
+ }
174
+ _printSlideCompare(baseline, s);
175
+ return;
176
+ }
177
+ _printSlide(s);
178
+ return;
179
+ }
180
+ const res = await fetch(`${BASE}/impact-report`, { headers, signal: AbortSignal.timeout(1e4) });
181
+ if (!res.ok) {
182
+ error(`Server returned ${res.status}`);
183
+ process.exit(1);
184
+ }
185
+ const { report: d } = await res.json();
186
+ const W = 60;
187
+ const div = "\u2500".repeat(W);
188
+ const fmtMs = (ms) => {
189
+ if (ms == null) return "pending";
190
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
191
+ return `${Math.floor(ms / 6e4)}m ${Math.round(ms % 6e4 / 1e3)}s`;
192
+ };
193
+ console.log("\nMergen Day-30 Impact Report");
194
+ console.log(div);
195
+ console.log(`Incidents processed: ${d["totalIncidents"]} (last ${d["windowDays"]} days)`);
196
+ console.log(`Resolution rate: ${d["wouldResolveRate"]}% (${d["wouldResolveCount"]} would have resolved autonomously)`);
197
+ console.log(`MTTR delta: ${fmtMs(d["avgAutonomousMttrMs"])} autonomous vs ${fmtMs(d["avgManualMttrMs"])} manual`);
198
+ console.log(`False positive rate: ${d["falsePositiveRate"] != null ? `${Math.round(d["falsePositiveRate"] * 100)}%` : `pending (need ${Math.max(0, 5 - (d["humanReviewedCount"] ?? 0))} more human shadow reviews)`}`);
199
+ console.log(`Override corpus: ${d["overridePatterns"]} patterns encoded, ${d["corpusBlockCount"]} triggered this window`);
200
+ const blunders = d["agentBlunderSummary"];
201
+ if (blunders) {
202
+ const chain = blunders.chainVerified ? "\u2713" : "!";
203
+ console.log(`Agent blunders blocked: ${blunders.totalPrevented.toLocaleString()} (chain ${chain})`);
204
+ }
205
+ console.log(`
206
+ Full deck summary:`);
207
+ const summary = (d["deckSummary"] ?? "").match(/.{1,56}(\s|$)/g) ?? [];
208
+ for (const line of summary) console.log(` ${line.trim()}`);
209
+ console.log(`
210
+ Flags:`);
211
+ console.log(` --slide 5 pre-agreed numbers, screenshot-ready`);
212
+ console.log(` --baseline save Day-1 numbers to compare at Day 30`);
213
+ console.log(` --compare show delta vs. saved baseline`);
214
+ console.log(` --html write full HTML report to disk`);
215
+ console.log(div + "\n");
216
+ }
217
+ function _printSlide(s) {
218
+ const W = 62;
219
+ const div = "\u2500".repeat(W);
220
+ const pad = (label, value) => {
221
+ const dots = ".".repeat(Math.max(2, W - label.length - value.length - 2));
222
+ return ` ${label} ${dots} ${value}`;
223
+ };
224
+ const pend = (v) => v == null ? "pending" : String(v);
225
+ const pct = (v) => v == null ? "pending" : `${v}%`;
226
+ console.log("\nMergen \u2014 Pre-agreed Day-30 metrics");
227
+ console.log(div);
228
+ console.log(pad("1. Incidents processed", `${s["incidents_processed"]}`));
229
+ console.log(pad("2. Autonomous resolution rate", pct(s["autonomous_resolution_rate_pct"])));
230
+ console.log(pad(
231
+ "3. MTTR: autonomous / manual",
232
+ `${pend(s["mttr_autonomous_minutes"])}min / ${pend(s["mttr_manual_minutes"])}min`
233
+ ));
234
+ console.log(pad("4. Gate false positive rate", pct(s["gate_false_positive_rate_pct"])));
235
+ console.log(pad("5. Agent blunders blocked", `${s["agent_blunders_blocked"]}`));
236
+ console.log(div);
237
+ console.log(` Window: last ${s["windowDays"]} days | Generated: ${new Date(s["generatedAt"]).toLocaleDateString()}`);
238
+ console.log();
239
+ }
240
+ function _printSlideCompare(baseline, now) {
241
+ const W = 72;
242
+ const div = "\u2500".repeat(W);
243
+ const delta = (b, n, lowerIsBetter = false) => {
244
+ if (b == null || n == null) return "";
245
+ const diff = Number(n) - Number(b);
246
+ if (diff === 0) return " (no change)";
247
+ const arrow = diff > 0 ? "+" : "";
248
+ const good = lowerIsBetter ? diff < 0 : diff > 0;
249
+ return ` ${arrow}${diff} ${good ? "(better)" : "(worse)"}`;
250
+ };
251
+ console.log("\nMergen \u2014 Day-1 vs Day-30 comparison");
252
+ console.log(div);
253
+ const row = (label, b, n, d) => console.log(` ${label.padEnd(34)} ${String(b ?? "n/a").padStart(10)} \u2192 ${String(n ?? "n/a").padEnd(10)}${d}`);
254
+ row("Incidents processed", baseline["incidents_processed"], now["incidents_processed"], delta(baseline["incidents_processed"], now["incidents_processed"]));
255
+ row("Autonomous resolution rate", `${baseline["autonomous_resolution_rate_pct"]}%`, `${now["autonomous_resolution_rate_pct"]}%`, delta(baseline["autonomous_resolution_rate_pct"], now["autonomous_resolution_rate_pct"]));
256
+ row("MTTR autonomous (min)", baseline["mttr_autonomous_minutes"], now["mttr_autonomous_minutes"], delta(baseline["mttr_autonomous_minutes"], now["mttr_autonomous_minutes"], true));
257
+ row("MTTR manual (min)", baseline["mttr_manual_minutes"], now["mttr_manual_minutes"], delta(baseline["mttr_manual_minutes"], now["mttr_manual_minutes"], true));
258
+ row("Gate FP rate", `${baseline["gate_false_positive_rate_pct"]}%`, `${now["gate_false_positive_rate_pct"]}%`, delta(baseline["gate_false_positive_rate_pct"], now["gate_false_positive_rate_pct"], true));
259
+ row("Agent blunders blocked", baseline["agent_blunders_blocked"], now["agent_blunders_blocked"], delta(baseline["agent_blunders_blocked"], now["agent_blunders_blocked"]));
260
+ console.log(div);
261
+ console.log(` Baseline saved: ${baseline["savedAt"]}`);
262
+ console.log();
263
+ }
264
+ async function exportCommand(args) {
265
+ const label = args[1] ?? `session-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-")}`;
266
+ const outJson = `./${label}.mergen-report.json`;
267
+ const outHtml = `./${label}.mergen-report.html`;
268
+ console.log("\u{1F4E6} Mergen Export\n");
269
+ hr();
270
+ let port = 3e3;
271
+ let serverFound = false;
272
+ for (let p = 3e3; p <= 3010; p++) {
273
+ try {
274
+ const r = await fetch(`http://127.0.0.1:${p}/health`, { signal: AbortSignal.timeout(800) });
275
+ if (r.ok) {
276
+ port = p;
277
+ serverFound = true;
278
+ break;
279
+ }
280
+ } catch {
281
+ }
282
+ }
283
+ if (!serverFound) {
284
+ error("Server not running. Start with: mergen-server start");
285
+ process.exit(1);
286
+ }
287
+ log(`Fetching session data from :${port}...`);
288
+ const BASE = `http://127.0.0.1:${port}`;
289
+ async function safeFetch(path2) {
290
+ try {
291
+ const r = await fetch(`${BASE}${path2}`, { signal: AbortSignal.timeout(3e3) });
292
+ return r.ok ? await r.json() : null;
293
+ } catch {
294
+ return null;
295
+ }
296
+ }
297
+ const [health, lastPack, history, timeline, calibration] = await Promise.all([
298
+ safeFetch("/health"),
299
+ safeFetch("/last-pack"),
300
+ safeFetch("/history?limit=20"),
301
+ safeFetch("/timeline?seconds=3600&limit=500"),
302
+ safeFetch("/calibration")
303
+ ]);
304
+ const report = {
305
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
306
+ label,
307
+ server: { port, health },
308
+ diagnoses: history?.entries ?? [],
309
+ latest_analysis: lastPack,
310
+ timeline: timeline?.rows ?? [],
311
+ calibration
312
+ };
313
+ writeFileSync(outJson, JSON.stringify(report, null, 2), "utf8");
314
+ success(`JSON report: ${outJson}`);
315
+ const rows = timeline?.rows ?? [];
316
+ const html = `<!DOCTYPE html>
317
+ <html lang="en"><head><meta charset="UTF-8"><title>Mergen Report \u2014 ${label}</title>
318
+ <style>
319
+ body{font-family:system-ui,sans-serif;max-width:900px;margin:40px auto;padding:0 20px;color:#222}
320
+ h1{font-size:20px;margin-bottom:4px}
321
+ .meta{color:#888;font-size:13px;margin-bottom:24px}
322
+ table{width:100%;border-collapse:collapse;font-size:13px}
323
+ th{text-align:left;padding:6px 10px;background:#f5f5f5;border-bottom:2px solid #ddd}
324
+ td{padding:5px 10px;border-bottom:1px solid #eee}
325
+ .error{color:#d32}
326
+ .warn{color:#b80}
327
+ .request{color:#07a}
328
+ .context{color:#888}
329
+ </style></head><body>
330
+ <h1>Mergen Session Report</h1>
331
+ <div class="meta">Exported ${report.exported_at} \xB7 ${rows.length} events</div>
332
+ <table>
333
+ <thead><tr><th>Time</th><th>Type</th><th>Summary</th></tr></thead>
334
+ <tbody>
335
+ ${rows.map((r) => `<tr><td>${r.isoTs.slice(11, 19)}</td><td class="${r.kind}">${r.kind}</td><td>${r.summary.replace(/</g, "&lt;")}</td></tr>`).join("\n")}
336
+ </tbody></table>
337
+ </body></html>`;
338
+ writeFileSync(outHtml, html, "utf8");
339
+ success(`HTML report: ${outHtml}`);
340
+ hr();
341
+ const eventCount = rows.length;
342
+ const diagCount = report.diagnoses.length;
343
+ console.log(`
344
+ ${eventCount} events \xB7 ${diagCount} diagnosis session(s) exported`);
345
+ console.log(`
346
+ Share with your team: cat ${outJson} | pbcopy`);
347
+ }
348
+ async function initCommand() {
349
+ console.log("\u2B21 Mergen Init \u2014 Connect Datadog\n");
350
+ hr();
351
+ const { mkdirSync: mkdir2, writeFileSync: write2, readFileSync: read2, existsSync: exists2 } = await import("fs");
352
+ const { join: join2 } = await import("path");
353
+ const { homedir: home2 } = await import("os");
354
+ const configDir = join2(home2(), ".mergen");
355
+ const configPath = join2(configDir, "config.json");
356
+ let existing = {};
357
+ if (exists2(configPath)) {
358
+ try {
359
+ existing = JSON.parse(read2(configPath, "utf8"));
360
+ } catch {
361
+ }
362
+ }
363
+ const dd = existing.datadog ?? {};
364
+ console.log("You will need:");
365
+ console.log(" - DD_API_KEY (Datadog \u2192 Organization Settings \u2192 API Keys)");
366
+ console.log(" - DD_APP_KEY (Datadog \u2192 Organization Settings \u2192 Application Keys)");
367
+ console.log("");
368
+ const apiKey = await ask(`Datadog API Key${dd.apiKey ? " [keep existing]" : ""}: `);
369
+ const appKey = await ask(`Datadog App Key${dd.appKey ? " [keep existing]" : ""}: `);
370
+ const sites = {
371
+ "1": "datadoghq.com",
372
+ "2": "datadoghq.eu",
373
+ "3": "us3.datadoghq.com",
374
+ "4": "us5.datadoghq.com",
375
+ "5": "ap1.datadoghq.com"
376
+ };
377
+ console.log("\nDatadog site:");
378
+ console.log(" 1. US1 - datadoghq.com (default)");
379
+ console.log(" 2. EU - datadoghq.eu");
380
+ console.log(" 3. US3 - us3.datadoghq.com");
381
+ console.log(" 4. US5 - us5.datadoghq.com");
382
+ console.log(" 5. AP1 - ap1.datadoghq.com");
383
+ const siteChoice = await ask("\nSite [1]: ");
384
+ const site = sites[siteChoice.trim() || "1"] ?? "datadoghq.com";
385
+ const finalApiKey = apiKey.trim() || dd.apiKey || "";
386
+ const finalAppKey = appKey.trim() || dd.appKey || "";
387
+ if (!finalApiKey || !finalAppKey) {
388
+ error("API Key and App Key are both required.");
389
+ process.exit(1);
390
+ }
391
+ process.stdout.write("\nTesting Datadog connection... ");
392
+ try {
393
+ const res = await fetch(`https://api.${site}/api/v1/validate`, {
394
+ headers: { "DD-API-KEY": finalApiKey, "DD-APPLICATION-KEY": finalAppKey },
395
+ signal: AbortSignal.timeout(8e3)
396
+ });
397
+ if (!res.ok) {
398
+ console.log("\u2717");
399
+ const body = await res.text();
400
+ error(`Auth failed (${res.status}): ${body.slice(0, 100)}`);
401
+ error("Double-check your API Key and App Key in Datadog Organization Settings.");
402
+ process.exit(1);
403
+ }
404
+ console.log("\u2713");
405
+ } catch (e) {
406
+ console.log("\u2717");
407
+ error(`Could not reach api.${site}: ${e instanceof Error ? e.message : String(e)}`);
408
+ process.exit(1);
409
+ }
410
+ mkdir2(configDir, { recursive: true });
411
+ const config = {
412
+ ...existing,
413
+ datadog: { apiKey: finalApiKey, appKey: finalAppKey, site }
414
+ };
415
+ write2(configPath, JSON.stringify(config, null, 2), { encoding: "utf8", mode: 384 });
416
+ hr();
417
+ success("Datadog credentials saved to ~/.mergen/config.json");
418
+ console.log("");
419
+ console.log("Available MCP tools:");
420
+ console.log(" get_incident_context \u2192 fetch + compact latest error trace");
421
+ console.log(" get_datadog_trace \u2192 compact a specific trace by ID");
422
+ console.log("");
423
+ console.log("PagerDuty auto-trigger (optional):");
424
+ console.log(" Add a webhook in PagerDuty \u2192 Service \u2192 Webhooks:");
425
+ console.log(" URL: http://127.0.0.1:3000/webhooks/pagerduty");
426
+ console.log(" Type: V3 webhook");
427
+ console.log("");
428
+ log("Restart the server to apply: mergen-server start");
429
+ }
430
+ async function demoCommand() {
431
+ const { createServer } = await import("http");
432
+ const { createApp } = await import("../app.js");
433
+ const { loadSeedCorpus, SEED_COUNT } = await import("../seeds/corpus.js");
434
+ console.log("\n\u2B21 Mergen\n");
435
+ process.stdout.write("Loading 50 sample incidents...");
436
+ const { loaded } = loadSeedCorpus();
437
+ console.log(` \u2713 (${loaded > 0 ? `${loaded} loaded` : `${SEED_COUNT} ready`})`);
438
+ try {
439
+ const { listSnapshotPids, replayIncident } = await import("../intelligence/incident-replay.js");
440
+ const pids = listSnapshotPids().filter((p) => p.startsWith("seed-"));
441
+ if (pids.length > 0) {
442
+ const pid = pids[0];
443
+ process.stdout.write("Running causal analysis...");
444
+ const result = await replayIncident(pid);
445
+ if (result) {
446
+ const conf = Math.round((result.replayedHypothesis.confidenceScore ?? 0) * 100);
447
+ const rawTag = (result.replayedHypothesis.tag ?? "unknown").replace(/^infra_/, "");
448
+ const label = rawTag.split("_").map((w, i) => i === 0 ? w.toUpperCase() : w).join(" ");
449
+ const fix = result.replayedHypothesis.fixHint ?? "";
450
+ console.log(` \u2713`);
451
+ console.log("");
452
+ console.log(` Detected: ${label} [${conf}% confidence]`);
453
+ if (fix) {
454
+ console.log(` Fix: ${fix.split(".")[0]}.`);
455
+ }
456
+ console.log("");
457
+ }
458
+ }
459
+ } catch {
460
+ }
461
+ const { createServer: createNetServer } = await import("net");
462
+ let port = 3e3;
463
+ for (let p = 3e3; p <= 3010; p++) {
464
+ const available = await new Promise((res) => {
465
+ const probe = createNetServer();
466
+ probe.once("error", () => res(false));
467
+ probe.once("listening", () => {
468
+ probe.close(() => res(true));
469
+ });
470
+ probe.listen(p, "127.0.0.1");
471
+ });
472
+ if (available) {
473
+ port = p;
474
+ break;
475
+ }
476
+ if (p === 3010) {
477
+ error("Ports 3000\u20133010 are all in use.");
478
+ console.log(" Free one with: lsof -ti:3000 | xargs kill");
479
+ process.exit(1);
480
+ }
481
+ log(`Port ${p} in use \u2014 trying ${p + 1}`, "\u21A9");
482
+ }
483
+ process.stdout.write("Starting server...");
484
+ const app = createApp({
485
+ serverVersion: VERSION,
486
+ localSecret: "demo",
487
+ port,
488
+ bindHost: "127.0.0.1"
489
+ });
490
+ const server = createServer(app);
491
+ await new Promise((resolve2, reject) => {
492
+ server.listen(port, "127.0.0.1", resolve2);
493
+ server.on("error", reject);
494
+ });
495
+ const url = `http://localhost:${port}/demo`;
496
+ console.log(` \u2713`);
497
+ console.log(` http://localhost:${port}
498
+ `);
499
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
500
+ console.log("Connect your stack when ready (all optional):");
501
+ console.log(" Watch any process: mergen-server watch npm start");
502
+ console.log(" Docker containers: curl -X POST http://127.0.0.1:3000/watchers/docker");
503
+ console.log(" Add to your IDE: mergen-server setup");
504
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
505
+ const open = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
506
+ try {
507
+ const { exec } = await import("child_process");
508
+ exec(`${open} "${url}"`);
509
+ } catch {
510
+ }
511
+ process.on("SIGINT", () => {
512
+ console.log("\nDemo server stopped.");
513
+ server.close();
514
+ process.exit(0);
515
+ });
516
+ await new Promise(() => {
517
+ });
518
+ }
519
+ async function exportRiskReportCommand(args) {
520
+ const fmt = args.includes("--markdown") || args.includes("--md") ? "md" : "json";
521
+ let port = 3e3;
522
+ for (let p = 3e3; p <= 3010; p++) {
523
+ try {
524
+ const r = await fetch(`http://127.0.0.1:${p}/health`, { signal: AbortSignal.timeout(600) });
525
+ if (r.ok) {
526
+ port = p;
527
+ break;
528
+ }
529
+ } catch {
530
+ }
531
+ }
532
+ const { join: join2 } = await import("path");
533
+ const { homedir: homedir2 } = await import("os");
534
+ const { existsSync: existsSync2, readFileSync: readFileSync2 } = await import("fs");
535
+ let secret = "";
536
+ const secretPath = join2(homedir2(), ".mergen", "secret");
537
+ if (existsSync2(secretPath)) {
538
+ try {
539
+ secret = readFileSync2(secretPath, "utf8").trim();
540
+ } catch {
541
+ }
542
+ }
543
+ const headers = {};
544
+ if (secret) headers["x-mergen-secret"] = secret;
545
+ let resp;
546
+ try {
547
+ resp = await fetch(`http://127.0.0.1:${port}/risk-report${fmt === "md" ? "?format=md" : ""}`, {
548
+ headers,
549
+ signal: AbortSignal.timeout(5e3)
550
+ });
551
+ } catch {
552
+ error("Mergen server is not running. Start it first: mergen-server start");
553
+ process.exit(1);
554
+ return;
555
+ }
556
+ if (!resp.ok) {
557
+ error(`Risk report request failed: ${resp.status}`);
558
+ process.exit(1);
559
+ }
560
+ if (fmt === "md") {
561
+ const { writeFileSync: wf } = await import("fs");
562
+ const filename = `mergen-risk-report-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.md`;
563
+ const body = await resp.text();
564
+ wf(filename, body, "utf8");
565
+ success(`CISO risk report saved: ${filename}`);
566
+ log("Share this with your security team \u2014 all data sourced from your local infrastructure.");
567
+ } else {
568
+ const data = await resp.json();
569
+ if (!data.ok) {
570
+ error("Failed to generate risk report.");
571
+ process.exit(1);
572
+ }
573
+ const { report } = data;
574
+ hr();
575
+ console.log("\u2B21 Mergen \u2014 AI Agent Security Risk Report\n");
576
+ console.log(` Risk Level : ${String(report.riskScore ?? "").toUpperCase()}`);
577
+ console.log(` Assessment : ${report.riskRationale}`);
578
+ console.log(` Total blocked (all time) : ${report.totalBlocked}`);
579
+ console.log(` Blocked \u2014 last 7 days : ${report.last7Days}`);
580
+ console.log(` Blocked \u2014 last 30 days : ${report.last30Days}`);
581
+ console.log(` Active block rules : ${report.activeRules}`);
582
+ console.log(` Override corpus entries : ${report.overrideCorpusSize}`);
583
+ console.log("");
584
+ log("For a shareable CISO document: mergen-server export-risk-report --markdown");
585
+ hr();
586
+ }
587
+ }
588
+ async function partnerShortlistCommand(subArgs) {
589
+ const listPath = join(homedir(), ".mergen", "partner-shortlist.json");
590
+ const { mkdirSync: mkdirSync2 } = await import("fs");
591
+ if (subArgs[0] === "--add") {
592
+ const [, name = "", company = "", role = "", reach = "", ...noteParts] = subArgs;
593
+ const notes = noteParts.join(" ");
594
+ if (!name || !company) {
595
+ error("Usage: partner-shortlist --add <name> <company> [role] [reach] [notes]");
596
+ process.exit(1);
597
+ }
598
+ mkdirSync2(join(homedir(), ".mergen"), { recursive: true });
599
+ let list = [];
600
+ try {
601
+ list = JSON.parse(readFileSync(listPath, "utf8"));
602
+ } catch {
603
+ }
604
+ list.push({ name, company, role, reach, notes, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
605
+ writeFileSync(listPath, JSON.stringify(list, null, 2) + "\n", "utf8");
606
+ success(`Added ${name} (${company}) to partner shortlist`);
607
+ return;
608
+ }
609
+ if (subArgs[0] === "--list") {
610
+ let list = [];
611
+ try {
612
+ list = JSON.parse(readFileSync(listPath, "utf8"));
613
+ } catch {
614
+ }
615
+ if (list.length === 0) {
616
+ log("No candidates yet. Add with: mergen-server partner-shortlist --add", "\u2139");
617
+ return;
618
+ }
619
+ const div2 = "\u2500".repeat(60);
620
+ console.log("\nMergen \u2014 Design-partner shortlist\n" + div2);
621
+ for (const c of list) {
622
+ console.log(` ${c.name.padEnd(22)} ${c.company.padEnd(20)} ${c.role}`);
623
+ if (c.reach) console.log(` Reach: ${c.reach}`);
624
+ if (c.notes) console.log(` Notes: ${c.notes}`);
625
+ }
626
+ console.log(div2 + "\n");
627
+ return;
628
+ }
629
+ const W = 60;
630
+ const div = "\u2500".repeat(W);
631
+ console.log("\nMergen \u2014 Design-partner filter criteria");
632
+ console.log(div);
633
+ console.log(" The right first partner gives you a credible case study,");
634
+ console.log(" not a friendly pilot that never stresses the gate.\n");
635
+ console.log(" Required (all must be true):");
636
+ console.log(" [ ] Public postmortem published in last 12 months");
637
+ console.log(" (they already do blameless post-mortems \u2192 safety mindset)");
638
+ console.log(" [ ] 2\u20135 person SRE / platform team");
639
+ console.log(" (small enough for Mergen to matter, big enough to have incidents)");
640
+ console.log(" [ ] On PagerDuty (webhook integration = Day 1 incident coverage)");
641
+ console.log(" [ ] Someone with public reach (Twitter/X, blog, conference talk)");
642
+ console.log(" (their Day-30 quote is worth 10\xD7 paid ads)\n");
643
+ console.log(" Nice to have:");
644
+ console.log(" [ ] Already using an AI coding agent (Claude Code / Cursor / Copilot)");
645
+ console.log(" [ ] Compliance pressure (SOC 2, HIPAA, fintech) \u2014 AEG is mandatory for them");
646
+ console.log(" [ ] Recent infrastructure incident (motivation is high)\n");
647
+ console.log(" Disqualify if:");
648
+ console.log(" [ ] > 150 developers (sale requires VP Eng buy-in, wrong stage)");
649
+ console.log(" [ ] No PagerDuty / no incident workflow (Mergen has nothing to triage)");
650
+ console.log(" [ ] Solo developer (Layer 2+ features don't apply yet)\n");
651
+ console.log(" Day-30 pre-agreement (send before trial starts):");
652
+ console.log(` "At Day 30 we'll run: mergen-server impact-report --slide"`);
653
+ console.log(' "We pre-agree these 5 numbers are the success criteria."');
654
+ console.log(' "No new metrics after the trial starts."\n');
655
+ console.log(" Commands:");
656
+ console.log(" mergen-server partner-shortlist --add <name> <company> [role] [reach] [notes]");
657
+ console.log(" mergen-server partner-shortlist --list");
658
+ console.log(div + "\n");
659
+ }
660
+ async function execCommand(args) {
661
+ let actor = "claude";
662
+ let service = "cli";
663
+ let allowOffline = false;
664
+ let dashDash = args.indexOf("--");
665
+ const flagArgs = dashDash >= 0 ? args.slice(0, dashDash) : args;
666
+ for (let i = 0; i < flagArgs.length; i++) {
667
+ if (flagArgs[i] === "--actor" && flagArgs[i + 1]) actor = flagArgs[++i];
668
+ else if (flagArgs[i] === "--service" && flagArgs[i + 1]) service = flagArgs[++i];
669
+ else if (flagArgs[i] === "--allow-offline") allowOffline = true;
670
+ }
671
+ const cmdParts = dashDash >= 0 ? args.slice(dashDash + 1) : args;
672
+ if (cmdParts.length === 0) {
673
+ error("Usage: mergen-server exec [--actor <name>] [--service <name>] [--allow-offline] -- <command> [args...]");
674
+ process.exit(1);
675
+ }
676
+ const fullCommand = cmdParts.join(" ");
677
+ const { evaluateViaGate } = await import("./gate.js");
678
+ let evaluation;
679
+ try {
680
+ evaluation = await evaluateViaGate(fullCommand);
681
+ } catch (err) {
682
+ error(`exec: gate evaluation request failed \u2014 ${err instanceof Error ? err.message : String(err)}`);
683
+ process.exit(1);
684
+ return;
685
+ }
686
+ if (evaluation.reachable) {
687
+ if (evaluation.isError) {
688
+ hr();
689
+ console.error("\u2B21 Mergen \u2014 BLOCKED\n");
690
+ console.error(evaluation.text);
691
+ hr();
692
+ process.exit(1);
693
+ }
694
+ } else if (!allowOffline) {
695
+ hr();
696
+ error("exec: no mergen-server is running \u2014 refusing to execute (fail closed).");
697
+ console.error(" Start it with: mergen-server start");
698
+ console.error(" Or explicitly accept reduced, local-only checks: mergen-server exec --allow-offline -- ...");
699
+ hr();
700
+ process.exit(1);
701
+ } else {
702
+ console.warn("\u2B21 Mergen \u2014 mergen-server not reachable, running with reduced local-only checks (--allow-offline)");
703
+ const {
704
+ evaluateEnterprisePolicy,
705
+ loadEnterprisePolicy,
706
+ DEFAULT_ENTERPRISE_POLICY: DEFAULT_POLICY,
707
+ _resetPolicyCacheForTesting: _resetPolicy
708
+ } = await import("../intelligence/enterprise-policy-engine.js");
709
+ const evalInput = { files: [], commands: [fullCommand], actor, service };
710
+ const savedPolicy = loadEnterprisePolicy();
711
+ _resetPolicy(DEFAULT_POLICY);
712
+ const defaultResult = evaluateEnterprisePolicy(evalInput);
713
+ _resetPolicy(savedPolicy);
714
+ const userResult = evaluateEnterprisePolicy(evalInput);
715
+ const verdictRank = (v) => v === "block" ? 2 : v === "warn" ? 1 : 0;
716
+ const result = verdictRank(defaultResult.verdict) >= verdictRank(userResult.verdict) ? defaultResult : userResult;
717
+ if (result.verdict === "block") {
718
+ hr();
719
+ console.error("\u2B21 Mergen \u2014 BLOCKED (offline check)\n");
720
+ console.error(` Command : ${fullCommand}`);
721
+ console.error(` Reason : ${result.reasons[0] ?? "Destructive pattern matched"}`);
722
+ hr();
723
+ const { randomUUID } = await import("crypto");
724
+ const blunder = {
725
+ id: randomUUID(),
726
+ recordedAt: Date.now(),
727
+ type: "blunder",
728
+ blunderType: "pipeline_block",
729
+ command: fullCommand,
730
+ blockReason: result.reasons[0] ?? "Destructive pattern matched",
731
+ service,
732
+ actor
733
+ };
734
+ try {
735
+ const { appendFileSync, mkdirSync: mkdirSync2 } = await import("fs");
736
+ const { join: join2, dirname: dirname2 } = await import("path");
737
+ const { homedir: homedir2 } = await import("os");
738
+ const file = join2(homedir2(), ".mergen", "offline-blunders.jsonl");
739
+ mkdirSync2(dirname2(file), { recursive: true });
740
+ appendFileSync(file, JSON.stringify(blunder) + "\n", "utf8");
741
+ } catch {
742
+ }
743
+ process.exit(1);
744
+ }
745
+ if (result.verdict === "warn") {
746
+ console.warn(`\u2B21 Mergen \u2014 WARNING: ${result.reasons[0] ?? "Action requires review"}`);
747
+ console.warn(" Proceeding \u2014 but this action has been flagged for audit (offline mode: no HITL hold available).");
748
+ }
749
+ }
750
+ const { traceEnv } = await import("../intelligence/trace-context.js");
751
+ const { spawnSandboxed } = await import("../intelligence/sandbox.js");
752
+ const { child, sandboxed, backend } = await spawnSandboxed(cmdParts[0], cmdParts.slice(1), {
753
+ cwd: process.cwd(),
754
+ stdio: "inherit",
755
+ env: { ...process.env, ...traceEnv() }
756
+ });
757
+ let watcher = null;
758
+ if (backend !== "docker" && child.pid) {
759
+ const rootPid = child.pid;
760
+ const { watchDescendants, killTree } = await import("../intelligence/process-tree.js");
761
+ const { evaluateEnterprisePolicy } = await import("../intelligence/enterprise-policy-engine.js");
762
+ watcher = watchDescendants(rootPid, {
763
+ onSpawn: (proc) => {
764
+ const result = evaluateEnterprisePolicy({ files: [], commands: [proc.command], actor, service });
765
+ if (result.verdict !== "block") return;
766
+ console.error(
767
+ `
768
+ \u2B21 Mergen \u2014 a spawned child process matched a destructive pattern; terminating the process tree (detected after the fact, not blocked pre-exec \u2014 see mergen-server docs on process-tree monitoring limits).`
769
+ );
770
+ console.error(` pid=${proc.pid} command=${proc.command}`);
771
+ void killTree(rootPid);
772
+ void (async () => {
773
+ const { getStores } = await import("../storage/store-registry.js");
774
+ await getStores().blunders.record({
775
+ blunderType: "pipeline_block",
776
+ command: proc.command,
777
+ blockReason: `Descendant process matched a destructive pattern (post-spawn detect-and-kill): ${result.reasons.join("; ")}`,
778
+ service,
779
+ tag: "process_tree_descendant",
780
+ actor,
781
+ pid: String(proc.pid),
782
+ confidenceScore: null,
783
+ triggeredRules: result.triggeredRules
784
+ });
785
+ })();
786
+ }
787
+ });
788
+ }
789
+ child.on("close", (code) => {
790
+ watcher?.stop();
791
+ process.exit(code ?? 0);
792
+ });
793
+ child.on("error", (err) => {
794
+ watcher?.stop();
795
+ error(`exec failed: ${err.message}`);
796
+ process.exit(1);
797
+ });
798
+ }
799
+ export {
800
+ demoCommand,
801
+ execCommand,
802
+ exportCommand,
803
+ exportRiskReportCommand,
804
+ impactReportCommand,
805
+ initCommand,
806
+ inviteCommand,
807
+ joinCommand,
808
+ partnerShortlistCommand
809
+ };