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,632 @@
1
+ import { execSync } from "child_process";
2
+ import { writeFileSync } from "fs";
3
+ import { log, success, error, hr, ask } from "./shared.js";
4
+ async function prCommand(args) {
5
+ const copyFlag = args.includes("--copy");
6
+ const outputFile = args.find((a) => a.startsWith("--out="))?.slice(6) ?? null;
7
+ let port = 3e3;
8
+ for (let p = 3e3; p <= 3010; p++) {
9
+ try {
10
+ const r = await fetch(`http://127.0.0.1:${p}/health`, { signal: AbortSignal.timeout(500) });
11
+ if (r.ok) {
12
+ port = p;
13
+ break;
14
+ }
15
+ } catch {
16
+ }
17
+ }
18
+ const [health, calibRaw, freqRaw] = await Promise.all([
19
+ fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(1500) }).then((r) => r.json()).catch(() => ({})),
20
+ fetch(`http://127.0.0.1:${port}/calibration`, { signal: AbortSignal.timeout(1500) }).then((r) => r.json()).catch(() => ({})),
21
+ fetch(`http://127.0.0.1:${port}/error-frequency`, { signal: AbortSignal.timeout(1500) }).then((r) => r.json()).catch(() => ({}))
22
+ ]);
23
+ let branch = "unknown-branch";
24
+ let commits = [];
25
+ let changedFiles = [];
26
+ try {
27
+ branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
28
+ commits = execSync("git log origin/main..HEAD --oneline --no-merges 2>/dev/null || git log HEAD~5..HEAD --oneline --no-merges", { encoding: "utf8" }).trim().split("\n").filter(Boolean);
29
+ changedFiles = execSync("git diff --name-only origin/main 2>/dev/null || git diff --name-only HEAD~1", { encoding: "utf8" }).trim().split("\n").filter(Boolean);
30
+ } catch {
31
+ }
32
+ const errors = health.errors ?? 0;
33
+ const netErrs = health.networkErrors ?? 0;
34
+ const warns = health.warnings ?? 0;
35
+ const detectors = calibRaw.perDetector ?? [];
36
+ const trusted = detectors.filter((d) => d.trusted && d.accuracy >= 0.5);
37
+ const suppressed = detectors.filter((d) => d.trusted && d.accuracy < 0.2);
38
+ const lines = [];
39
+ lines.push(`## Summary`);
40
+ lines.push("");
41
+ if (commits.length > 0) {
42
+ for (const c of commits.slice(0, 5)) {
43
+ const msg = c.replace(/^[0-9a-f]+\s+/, "").replace(/^(fix|feat|chore|refactor|docs|test):\s*/i, "");
44
+ lines.push(`- ${msg}`);
45
+ }
46
+ } else {
47
+ lines.push(`- Changes on branch \`${branch}\``);
48
+ }
49
+ lines.push("");
50
+ lines.push(`## Runtime verification`);
51
+ lines.push("");
52
+ lines.push(`_Captured by Mergen during development on branch \`${branch}\`_`);
53
+ lines.push("");
54
+ if (errors === 0 && netErrs === 0 && warns === 0) {
55
+ lines.push("\u2705 **Buffer clean** \u2014 no console errors, network failures, or warnings at time of PR creation.");
56
+ } else {
57
+ if (errors > 0 || netErrs > 0) {
58
+ lines.push(`\u26A0\uFE0F **${errors + netErrs} error(s) remain in buffer** \u2014 review before merging.`);
59
+ }
60
+ if (warns > 0) {
61
+ lines.push(`\u26A0\uFE0F **${warns} warning(s)** in buffer.`);
62
+ }
63
+ }
64
+ const consoleFreq = freqRaw.console ?? [];
65
+ const netFreq = freqRaw.network ?? [];
66
+ if (consoleFreq.length > 0 || netFreq.length > 0) {
67
+ lines.push("");
68
+ lines.push("**Error patterns seen during development:**");
69
+ for (const e of consoleFreq.slice(0, 3)) {
70
+ lines.push(`- \`${e.fingerprint}\` \u2014 ${e.count}\xD7 \u2014 "${e.sample.slice(0, 80)}"`);
71
+ }
72
+ for (const n of netFreq.slice(0, 2)) {
73
+ lines.push(`- ${n.sample.slice(0, 100)} \u2014 ${n.count}\xD7`);
74
+ }
75
+ }
76
+ if (suppressed.length > 0) {
77
+ lines.push("");
78
+ lines.push(`**Suppressed noise patterns:** ${suppressed.map((d) => `\`${d.tag}\``).join(", ")}`);
79
+ lines.push("_(Mergen suppresses detectors with < 20% accuracy \u2014 these fired but were consistently wrong)_");
80
+ }
81
+ lines.push("");
82
+ lines.push(`## Files changed (${changedFiles.length})`);
83
+ lines.push("");
84
+ const grouped = {};
85
+ for (const f of changedFiles.slice(0, 20)) {
86
+ const dir = f.includes("/") ? f.split("/").slice(0, 2).join("/") : ".";
87
+ grouped[dir] = grouped[dir] ?? [];
88
+ grouped[dir].push(f);
89
+ }
90
+ for (const [dir, files] of Object.entries(grouped)) {
91
+ lines.push(`**${dir}/**`);
92
+ for (const f of files) lines.push(`- \`${f}\``);
93
+ }
94
+ if (changedFiles.length > 20) lines.push(`_...and ${changedFiles.length - 20} more_`);
95
+ lines.push("");
96
+ lines.push(`## Test plan`);
97
+ lines.push("");
98
+ lines.push("- [ ] Manually reproduced the scenario in the browser");
99
+ lines.push("- [ ] Mergen buffer clean before merge (`mergen-server guard`)");
100
+ lines.push("- [ ] Unit tests pass");
101
+ if (changedFiles.some((f) => /api|route|endpoint|controller|handler/i.test(f))) {
102
+ lines.push("- [ ] API contract unchanged (or migration included)");
103
+ }
104
+ if (changedFiles.some((f) => /auth|login|token|session/i.test(f))) {
105
+ lines.push("- [ ] Auth flow tested (login, logout, token refresh)");
106
+ }
107
+ lines.push("");
108
+ lines.push(`---`);
109
+ lines.push(`_Generated by [Mergen](https://github.com/omertt27/Mergen) \u2014 runtime context captured automatically during development._`);
110
+ const output = lines.join("\n");
111
+ if (outputFile) {
112
+ writeFileSync(outputFile, output, "utf8");
113
+ success(`PR description written to ${outputFile}`);
114
+ } else if (copyFlag) {
115
+ try {
116
+ const clipCmd = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
117
+ const { execSync: exec2 } = await import("child_process");
118
+ exec2(`echo ${JSON.stringify(output)} | ${clipCmd}`);
119
+ success("PR description copied to clipboard.");
120
+ } catch {
121
+ console.log(output);
122
+ log("(Could not copy to clipboard \u2014 output above)");
123
+ }
124
+ } else {
125
+ console.log("\n" + output + "\n");
126
+ log("Tip: --copy to copy to clipboard, --out=pr.md to write to file");
127
+ }
128
+ }
129
+ async function connectGitHubCommand(args) {
130
+ const { randomBytes } = await import("crypto");
131
+ const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2, readFileSync: readFileSync2, existsSync: existsSync2 } = await import("fs");
132
+ const { join: join2 } = await import("path");
133
+ const { homedir: homedir2 } = await import("os");
134
+ let repo = "";
135
+ let token = process.env.GITHUB_TOKEN ?? "";
136
+ let mergenUrl = "";
137
+ for (let i = 0; i < args.length; i++) {
138
+ if (args[i] === "--repo" && args[i + 1]) {
139
+ repo = args[++i];
140
+ continue;
141
+ }
142
+ if (args[i] === "--token" && args[i + 1]) {
143
+ token = args[++i];
144
+ continue;
145
+ }
146
+ if (args[i] === "--mergen-url" && args[i + 1]) {
147
+ mergenUrl = args[++i];
148
+ continue;
149
+ }
150
+ if (args[i].startsWith("--repo=")) {
151
+ repo = args[i].slice(7);
152
+ continue;
153
+ }
154
+ if (args[i].startsWith("--token=")) {
155
+ token = args[i].slice(8);
156
+ continue;
157
+ }
158
+ if (args[i].startsWith("--mergen-url=")) {
159
+ mergenUrl = args[i].slice(13);
160
+ continue;
161
+ }
162
+ }
163
+ console.log("\nMergen \xD7 GitHub \u2014 Automated Webhook Setup\n");
164
+ hr();
165
+ if (!repo) {
166
+ repo = await ask("GitHub repo (owner/repo, e.g. acme/api): ");
167
+ repo = repo.trim();
168
+ }
169
+ if (!/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
170
+ error(`Invalid repo format: "${repo}". Expected owner/repo.`);
171
+ process.exit(1);
172
+ }
173
+ if (!token) {
174
+ console.log("\nA GitHub Personal Access Token with repo:admin scope is required.");
175
+ console.log("Create one at: https://github.com/settings/tokens/new");
176
+ console.log(' \u2192 Required scope: "repo" (or "admin:repo_hook" for fine-grained tokens)\n');
177
+ token = await ask("GitHub Personal Access Token: ");
178
+ token = token.trim();
179
+ }
180
+ if (!token) {
181
+ error("Token is required. Set GITHUB_TOKEN env var or pass --token.");
182
+ process.exit(1);
183
+ }
184
+ if (!mergenUrl) {
185
+ const port = process.env.HTTP_PORT ?? "3000";
186
+ const defaultUrl = `http://127.0.0.1:${port}`;
187
+ const input = await ask(`Mergen server URL [${defaultUrl}]: `);
188
+ mergenUrl = input.trim() || defaultUrl;
189
+ }
190
+ mergenUrl = mergenUrl.replace(/\/$/, "");
191
+ const webhookUrl = `${mergenUrl}/webhooks/github`;
192
+ const webhookSecret = randomBytes(32).toString("hex");
193
+ process.stdout.write("\nChecking existing webhooks... ");
194
+ let existingId = null;
195
+ try {
196
+ const listRes = await fetch(`https://api.github.com/repos/${repo}/hooks`, {
197
+ headers: {
198
+ Authorization: `Bearer ${token}`,
199
+ Accept: "application/vnd.github+json",
200
+ "X-GitHub-Api-Version": "2022-11-28"
201
+ },
202
+ signal: AbortSignal.timeout(1e4)
203
+ });
204
+ if (!listRes.ok) {
205
+ console.log("\u2717");
206
+ const body = await listRes.text();
207
+ if (listRes.status === 401) {
208
+ error("GitHub authentication failed. Check your personal access token.");
209
+ } else if (listRes.status === 403) {
210
+ error('Permission denied. Token needs "repo" or "admin:repo_hook" scope.');
211
+ } else if (listRes.status === 404) {
212
+ error(`Repository "${repo}" not found. Check the name and token permissions.`);
213
+ } else {
214
+ error(`GitHub API error ${listRes.status}: ${body.slice(0, 150)}`);
215
+ }
216
+ process.exit(1);
217
+ }
218
+ const hooks = await listRes.json();
219
+ const existing2 = hooks.find((h) => h.config?.url?.includes("/webhooks/github"));
220
+ if (existing2) {
221
+ existingId = existing2.id;
222
+ console.log(`\u2713 (found existing Mergen webhook id=${existingId})`);
223
+ } else {
224
+ console.log("\u2713 (none found)");
225
+ }
226
+ } catch (e) {
227
+ console.log("\u2717");
228
+ error(`Could not reach GitHub API: ${e instanceof Error ? e.message : String(e)}`);
229
+ process.exit(1);
230
+ }
231
+ const hookPayload = {
232
+ name: "web",
233
+ active: true,
234
+ events: ["pull_request", "pull_request_review", "push"],
235
+ config: {
236
+ url: webhookUrl,
237
+ content_type: "json",
238
+ secret: webhookSecret,
239
+ insecure_ssl: "0"
240
+ }
241
+ };
242
+ if (existingId) {
243
+ process.stdout.write(`Updating webhook ${existingId}... `);
244
+ } else {
245
+ process.stdout.write("Registering webhook... ");
246
+ }
247
+ try {
248
+ const method = existingId ? "PATCH" : "POST";
249
+ const hookUrl = existingId ? `https://api.github.com/repos/${repo}/hooks/${existingId}` : `https://api.github.com/repos/${repo}/hooks`;
250
+ const createRes = await fetch(hookUrl, {
251
+ method,
252
+ headers: {
253
+ Authorization: `Bearer ${token}`,
254
+ Accept: "application/vnd.github+json",
255
+ "Content-Type": "application/json",
256
+ "X-GitHub-Api-Version": "2022-11-28"
257
+ },
258
+ body: JSON.stringify(hookPayload),
259
+ signal: AbortSignal.timeout(1e4)
260
+ });
261
+ if (!createRes.ok) {
262
+ console.log("\u2717");
263
+ const body = await createRes.text();
264
+ error(`GitHub API error ${createRes.status}: ${body.slice(0, 200)}`);
265
+ process.exit(1);
266
+ }
267
+ const hook = await createRes.json();
268
+ console.log(`\u2713 (id=${hook.id})`);
269
+ } catch (e) {
270
+ console.log("\u2717");
271
+ error(`Webhook registration failed: ${e instanceof Error ? e.message : String(e)}`);
272
+ process.exit(1);
273
+ }
274
+ const configDir = join2(homedir2(), ".mergen");
275
+ const configPath = join2(configDir, "config.json");
276
+ let existing = {};
277
+ if (existsSync2(configPath)) {
278
+ try {
279
+ existing = JSON.parse(readFileSync2(configPath, "utf8"));
280
+ } catch {
281
+ }
282
+ }
283
+ mkdirSync2(configDir, { recursive: true });
284
+ const github = existing.github ?? {};
285
+ const repos = github.repos ?? [];
286
+ if (!repos.includes(repo)) repos.push(repo);
287
+ const config = {
288
+ ...existing,
289
+ github: { ...github, webhookSecret, repos, token }
290
+ };
291
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), { encoding: "utf8", mode: 384 });
292
+ const secretFile = join2(configDir, "github-webhook-secret");
293
+ writeFileSync2(secretFile, webhookSecret, { encoding: "utf8", mode: 384 });
294
+ hr();
295
+ success(`GitHub webhook registered for ${repo}`);
296
+ console.log("");
297
+ console.log("What happens next:");
298
+ console.log(" \xB7 Every merged PR now populates the intent archive automatically");
299
+ console.log(' \xB7 In your AI IDE: ask "explain why service X has a 30s timeout"');
300
+ console.log(" \xB7 The archive grows with every commit \u2014 no manual steps needed");
301
+ console.log("");
302
+ console.log("To verify the webhook is receiving events:");
303
+ console.log(" mergen-server doctor (checks GitHub webhook status)");
304
+ console.log("");
305
+ log("Restart the server to load the new secret: mergen-server start", "\u2139");
306
+ process.env.GITHUB_WEBHOOK_SECRET = webhookSecret;
307
+ }
308
+ async function backfillGitHubCommand(args) {
309
+ const path2 = await import("path");
310
+ const os2 = await import("os");
311
+ const fs = await import("fs");
312
+ let repo = "";
313
+ let token = process.env.GITHUB_TOKEN ?? "";
314
+ let sinceMs = Date.now() - 180 * 24 * 60 * 60 * 1e3;
315
+ for (let i = 0; i < args.length; i++) {
316
+ if ((args[i] === "--repo" || args[i] === "-r") && args[i + 1]) {
317
+ repo = args[++i];
318
+ continue;
319
+ }
320
+ if ((args[i] === "--token" || args[i] === "-t") && args[i + 1]) {
321
+ token = args[++i];
322
+ continue;
323
+ }
324
+ if (args[i] === "--since" && args[i + 1]) {
325
+ const raw = args[++i];
326
+ const daysMatch = raw.match(/^(\d+)d$/i);
327
+ if (daysMatch) {
328
+ sinceMs = Date.now() - parseInt(daysMatch[1]) * 24 * 60 * 60 * 1e3;
329
+ } else {
330
+ const parsed = Date.parse(raw);
331
+ if (!isNaN(parsed)) sinceMs = parsed;
332
+ else {
333
+ error(`Invalid --since value: "${raw}". Use e.g. 180d or 2024-01-01`);
334
+ process.exit(1);
335
+ }
336
+ }
337
+ continue;
338
+ }
339
+ if (args[i].startsWith("--repo=")) {
340
+ repo = args[i].slice(7);
341
+ continue;
342
+ }
343
+ if (args[i].startsWith("--token=")) {
344
+ token = args[i].slice(8);
345
+ continue;
346
+ }
347
+ if (args[i].startsWith("--since=")) {
348
+ const raw = args[i].slice(8);
349
+ const daysMatch = raw.match(/^(\d+)d$/i);
350
+ if (daysMatch) sinceMs = Date.now() - parseInt(daysMatch[1]) * 24 * 60 * 60 * 1e3;
351
+ else {
352
+ const parsed = Date.parse(raw);
353
+ if (!isNaN(parsed)) sinceMs = parsed;
354
+ }
355
+ continue;
356
+ }
357
+ }
358
+ console.log("\nMergen \u2014 GitHub Historical Backfill\n");
359
+ hr();
360
+ if (!repo) {
361
+ repo = await ask("GitHub repo (owner/repo): ");
362
+ repo = repo.trim();
363
+ }
364
+ if (!/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
365
+ error(`Invalid repo format: "${repo}". Expected owner/repo.`);
366
+ process.exit(1);
367
+ }
368
+ if (!token) {
369
+ const configPath = path2.join(os2.homedir(), ".mergen", "config.json");
370
+ try {
371
+ const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
372
+ const gh = cfg.github ?? {};
373
+ if (typeof gh.token === "string") token = gh.token;
374
+ } catch {
375
+ }
376
+ }
377
+ if (!token) {
378
+ console.log("\nA GitHub Personal Access Token with repo read scope is required.");
379
+ token = await ask("GitHub Personal Access Token: ");
380
+ token = token.trim();
381
+ }
382
+ if (!token) {
383
+ error("Token is required. Set GITHUB_TOKEN env var or pass --token.");
384
+ process.exit(1);
385
+ }
386
+ const sinceDate = new Date(sinceMs).toISOString().slice(0, 10);
387
+ log(`Importing merged PRs for ${repo} since ${sinceDate}...`);
388
+ console.log("");
389
+ let upsertFn = null;
390
+ try {
391
+ const { commitContextStore } = await import("../sensor/commit-context-store.js");
392
+ await commitContextStore.init();
393
+ upsertFn = (ctx) => commitContextStore.upsert(ctx);
394
+ } catch (e) {
395
+ error(`Could not initialise commit context store: ${e instanceof Error ? e.message : String(e)}`);
396
+ process.exit(1);
397
+ }
398
+ const { extractLinkedIssues } = await import("../sensor/commit-context-store.js");
399
+ const { detectAiCommit } = await import("../intelligence/ai-commit.js");
400
+ const headers = {
401
+ Authorization: `Bearer ${token}`,
402
+ Accept: "application/vnd.github+json",
403
+ "X-GitHub-Api-Version": "2022-11-28"
404
+ };
405
+ let page = 1;
406
+ let imported = 0;
407
+ let skipped = 0;
408
+ let done = false;
409
+ const PAGE_SIZE = 100;
410
+ process.stdout.write("Fetching: ");
411
+ while (!done) {
412
+ const url = `https://api.github.com/repos/${repo}/pulls?state=closed&sort=updated&direction=desc&per_page=${PAGE_SIZE}&page=${page}`;
413
+ let prs;
414
+ try {
415
+ const res = await fetch(url, { headers, signal: AbortSignal.timeout(15e3) });
416
+ if (!res.ok) {
417
+ console.log("");
418
+ if (res.status === 401) {
419
+ error("GitHub authentication failed \u2014 check your token.");
420
+ } else if (res.status === 403) {
421
+ const remaining = res.headers.get("x-ratelimit-remaining");
422
+ const reset = res.headers.get("x-ratelimit-reset");
423
+ if (remaining === "0" && reset) {
424
+ const waitSec = Math.ceil(parseInt(reset) - Date.now() / 1e3);
425
+ error(`GitHub rate limit hit. Try again in ${waitSec}s, or use a different token.`);
426
+ } else {
427
+ error('GitHub permission denied \u2014 token needs "repo" read scope.');
428
+ }
429
+ } else if (res.status === 404) {
430
+ error(`Repository "${repo}" not found. Check the name and token permissions.`);
431
+ } else {
432
+ error(`GitHub API error ${res.status}: ${await res.text().then((t) => t.slice(0, 120))}`);
433
+ }
434
+ process.exit(1);
435
+ }
436
+ prs = await res.json();
437
+ } catch (e) {
438
+ console.log("");
439
+ error(`Network error: ${e instanceof Error ? e.message : String(e)}`);
440
+ process.exit(1);
441
+ }
442
+ if (!Array.isArray(prs) || prs.length === 0) break;
443
+ for (const pr of prs) {
444
+ const mergedAt = pr.merged_at ? Date.parse(String(pr.merged_at)) : null;
445
+ if (!mergedAt) {
446
+ skipped++;
447
+ continue;
448
+ }
449
+ if (mergedAt < sinceMs) {
450
+ done = true;
451
+ break;
452
+ }
453
+ const sha = String(
454
+ pr.merge_commit_sha ?? (pr.head?.sha ?? "")
455
+ );
456
+ if (!sha) {
457
+ skipped++;
458
+ continue;
459
+ }
460
+ const prNumber = typeof pr.number === "number" ? pr.number : null;
461
+ const prTitle = typeof pr.title === "string" ? pr.title : null;
462
+ const prBody = typeof pr.body === "string" ? pr.body : null;
463
+ const author = pr.user?.login;
464
+ const branch = pr.head?.ref;
465
+ const headSha = String(pr.head?.sha ?? "");
466
+ const aiResult = detectAiCommit(prTitle ?? "", typeof author === "string" ? author : void 0);
467
+ const linkedIssues = extractLinkedIssues(prBody ?? "");
468
+ const reviewers = pr.requested_reviewers ?? [];
469
+ const approvers = reviewers.map((r) => String(r.login ?? "")).filter(Boolean);
470
+ upsertFn({
471
+ sha,
472
+ repo,
473
+ branch: typeof branch === "string" ? branch : null,
474
+ prNumber,
475
+ prTitle,
476
+ prBody,
477
+ author: typeof author === "string" ? author : null,
478
+ approvers,
479
+ linkedIssues,
480
+ aiGenerated: aiResult.detected,
481
+ aiTool: aiResult.tool ?? null,
482
+ filesChanged: [],
483
+ // skipped for backfill — requires 1 extra API call per PR
484
+ capturedAt: Date.now(),
485
+ mergedAt
486
+ });
487
+ imported++;
488
+ if (imported % 10 === 0) process.stdout.write(".");
489
+ }
490
+ if (prs.length < PAGE_SIZE) break;
491
+ page++;
492
+ }
493
+ console.log("");
494
+ hr();
495
+ success(`Imported ${imported} PRs from ${repo}`);
496
+ if (skipped > 0) log(`${skipped} PRs skipped (unmerged or outside date window)`, "\u2139");
497
+ console.log("");
498
+ console.log("The intent archive is now populated. Try it:");
499
+ console.log(` Ask your AI IDE: "explain why <service> works the way it does"`);
500
+ console.log(` Or run: mergen-server timeline`);
501
+ console.log("");
502
+ log(`To keep the archive current, connect the live webhook: mergen-server connect github --repo ${repo}`, "\u2139");
503
+ }
504
+ async function prShadowCommand() {
505
+ let port = 0;
506
+ for (let p = 3e3; p <= 3010; p++) {
507
+ try {
508
+ const r = await fetch(`http://127.0.0.1:${p}/health`, { signal: AbortSignal.timeout(600) });
509
+ if (r.ok) {
510
+ port = p;
511
+ break;
512
+ }
513
+ } catch {
514
+ }
515
+ }
516
+ if (!port) {
517
+ error("Server not running. Start with: mergen-server start");
518
+ process.exit(1);
519
+ }
520
+ let data;
521
+ try {
522
+ const r = await fetch(`http://127.0.0.1:${port}/pr-shadow/stats`, { signal: AbortSignal.timeout(3e3) });
523
+ data = await r.json();
524
+ } catch (err) {
525
+ error(err instanceof Error ? err.message : String(err));
526
+ process.exit(1);
527
+ }
528
+ hr();
529
+ console.log("\u2B21 Mergen PR Shadow Mode\n");
530
+ const readyIcon = data.readyForPRComments ? "\u2713" : "\u25CB";
531
+ const enabledLabel = data.prCommentsEnabled ? " (ENABLED)" : " (shadow only)";
532
+ console.log(`${readyIcon} PR Comments: ${data.readyForPRComments ? "ready to enable" : "not ready"}${enabledLabel}
533
+ `);
534
+ console.log(` PRs analyzed: ${data.totalAnalyzed}`);
535
+ console.log(` Would have shown: ${data.wouldHaveShown}`);
536
+ console.log(` Useful rate: ${data.wouldHaveBeenUsefulRate ?? "N/A"}% (need \u2265${data.readyConditions.wouldHaveBeenUsefulRateThreshold}%)`);
537
+ console.log(` Avg relevance: ${data.avgRelevanceScore ?? "N/A"}`);
538
+ console.log(` Helpful rate (7d): ${data.readyConditions.helpfulRate7d ?? "N/A"}% (need \u2265${data.readyConditions.helpfulRateThreshold}%)`);
539
+ if (data.topTriggers.length > 0) {
540
+ console.log(`
541
+ Top triggers:`);
542
+ for (const t of data.topTriggers.slice(0, 3)) {
543
+ console.log(` ${t.trigger}: ${t.count}`);
544
+ }
545
+ }
546
+ if (data.nextSteps.length > 0) {
547
+ console.log("\n Next steps:");
548
+ for (const step of data.nextSteps) {
549
+ console.log(` ${step}`);
550
+ }
551
+ }
552
+ hr();
553
+ }
554
+ async function feedbackCommand(args) {
555
+ const id = args[0];
556
+ const flag = args[1];
557
+ if (!id || flag !== "--yes" && flag !== "--no") {
558
+ error("Usage: mergen-server feedback <id> --yes|--no");
559
+ error("Example: mergen-server feedback ew-1a2b3c --yes");
560
+ process.exit(1);
561
+ }
562
+ const helpful = flag === "--yes";
563
+ let port = 0;
564
+ for (let p = 3e3; p <= 3010; p++) {
565
+ try {
566
+ const r = await fetch(`http://127.0.0.1:${p}/health`, { signal: AbortSignal.timeout(600) });
567
+ if (r.ok) {
568
+ port = p;
569
+ break;
570
+ }
571
+ } catch {
572
+ }
573
+ }
574
+ if (!port) {
575
+ error("Server not running. Start with: mergen-server start");
576
+ process.exit(1);
577
+ }
578
+ try {
579
+ const r = await fetch(`http://127.0.0.1:${port}/explain-why/feedback`, {
580
+ method: "POST",
581
+ headers: { "Content-Type": "application/json" },
582
+ body: JSON.stringify({ id, helpful }),
583
+ signal: AbortSignal.timeout(3e3)
584
+ });
585
+ if (!r.ok) {
586
+ const body = await r.text().catch(() => "");
587
+ error(`Server returned ${r.status}: ${body}`);
588
+ process.exit(1);
589
+ }
590
+ success(helpful ? "Marked as helpful. Thanks!" : "Marked as not helpful. We'll use this to improve results.");
591
+ } catch (err) {
592
+ error(err instanceof Error ? err.message : String(err));
593
+ process.exit(1);
594
+ }
595
+ }
596
+ async function backfillCommand(args) {
597
+ const sub = args[0];
598
+ if (sub === "github") {
599
+ await backfillGitHubCommand(args.slice(1));
600
+ return;
601
+ }
602
+ console.log("Usage: mergen-server backfill <source>");
603
+ console.log("");
604
+ console.log("Available sources:");
605
+ console.log(" github Import historical merged PRs into the intent archive");
606
+ console.log("");
607
+ console.log("Example:");
608
+ console.log(" mergen-server backfill github --repo acme/api --since 180d");
609
+ }
610
+ async function connectCommand(args) {
611
+ const sub = args[0];
612
+ if (sub === "github") {
613
+ await connectGitHubCommand(args.slice(1));
614
+ return;
615
+ }
616
+ console.log("Usage: mergen-server connect <integration>");
617
+ console.log("");
618
+ console.log("Available integrations:");
619
+ console.log(" github Auto-register GitHub PR/push webhook \u2192 populates intent archive");
620
+ console.log("");
621
+ console.log("Example:");
622
+ console.log(" mergen-server connect github --repo acme/api");
623
+ }
624
+ export {
625
+ backfillCommand,
626
+ backfillGitHubCommand,
627
+ connectCommand,
628
+ connectGitHubCommand,
629
+ feedbackCommand,
630
+ prCommand,
631
+ prShadowCommand
632
+ };