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,147 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { randomUUID, createHash } from "crypto";
4
+ import { DATA_DIR, zeroRetentionMode } from "./paths.js";
5
+ import logger from "./logger.js";
6
+ const POLICY_HISTORY_FILE = path.join(DATA_DIR, "policy-history.json");
7
+ const POLICY_VERSIONS_DIR = path.join(DATA_DIR, "policy-versions");
8
+ const MAX_HISTORY = 500;
9
+ const MAX_VERSIONS = 500;
10
+ let _entries = [];
11
+ let _loaded = false;
12
+ function _load() {
13
+ if (_loaded) return;
14
+ _loaded = true;
15
+ if (!fs.existsSync(POLICY_HISTORY_FILE)) return;
16
+ try {
17
+ const raw = JSON.parse(fs.readFileSync(POLICY_HISTORY_FILE, "utf8"));
18
+ if (raw?.version === 1 && Array.isArray(raw.entries)) {
19
+ _entries = raw.entries.slice(-MAX_HISTORY);
20
+ }
21
+ } catch (err) {
22
+ logger.warn({ err }, "policy-history: failed to load \u2014 starting fresh");
23
+ }
24
+ }
25
+ function _persist() {
26
+ if (zeroRetentionMode()) return;
27
+ try {
28
+ fs.mkdirSync(DATA_DIR, { recursive: true });
29
+ const tmp = `${POLICY_HISTORY_FILE}.tmp.${process.pid}`;
30
+ fs.writeFileSync(tmp, JSON.stringify({ version: 1, entries: _entries }), "utf8");
31
+ fs.renameSync(tmp, POLICY_HISTORY_FILE);
32
+ } catch (err) {
33
+ logger.warn({ err }, "policy-history: persist failed");
34
+ }
35
+ }
36
+ function _summariseRule(r) {
37
+ return { id: r.id, name: r.name, action: r.action };
38
+ }
39
+ function _versionIdFor(config) {
40
+ const hash = createHash("sha256").update(JSON.stringify(config)).digest("hex").slice(0, 16);
41
+ return `${Date.now()}-${hash}`;
42
+ }
43
+ function _versionPath(versionId) {
44
+ const safe = versionId.replace(/[^a-zA-Z0-9-]/g, "");
45
+ return path.join(POLICY_VERSIONS_DIR, `${safe}.json`);
46
+ }
47
+ function _writeSnapshot(config) {
48
+ if (zeroRetentionMode()) return null;
49
+ try {
50
+ fs.mkdirSync(POLICY_VERSIONS_DIR, { recursive: true });
51
+ const versionId = _versionIdFor(config);
52
+ fs.writeFileSync(_versionPath(versionId), JSON.stringify(config, null, 2), "utf8");
53
+ _pruneOldSnapshots();
54
+ return versionId;
55
+ } catch (err) {
56
+ logger.warn({ err }, "policy-history: failed to write policy version snapshot");
57
+ return null;
58
+ }
59
+ }
60
+ function _pruneOldSnapshots() {
61
+ try {
62
+ const files = fs.readdirSync(POLICY_VERSIONS_DIR).filter((f) => f.endsWith(".json")).sort();
63
+ const excess = files.length - MAX_VERSIONS;
64
+ if (excess > 0) {
65
+ for (const f of files.slice(0, excess)) {
66
+ try {
67
+ fs.unlinkSync(path.join(POLICY_VERSIONS_DIR, f));
68
+ } catch {
69
+ }
70
+ }
71
+ }
72
+ } catch {
73
+ }
74
+ }
75
+ function listPolicyVersions() {
76
+ try {
77
+ return fs.readdirSync(POLICY_VERSIONS_DIR).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length)).sort().reverse();
78
+ } catch {
79
+ return [];
80
+ }
81
+ }
82
+ function getPolicyVersion(versionId) {
83
+ try {
84
+ const raw = fs.readFileSync(_versionPath(versionId), "utf8");
85
+ return JSON.parse(raw);
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ function recordPolicyChange(before, after, actor = "unknown") {
91
+ _load();
92
+ const beforeRules = before?.rules ?? [];
93
+ const afterRules = after.rules;
94
+ const beforeIds = new Map(beforeRules.map((r) => [r.id, r]));
95
+ const afterIds = new Map(afterRules.map((r) => [r.id, r]));
96
+ const added = [];
97
+ const removed = [];
98
+ const modified = [];
99
+ for (const [id, rule] of afterIds) {
100
+ if (!beforeIds.has(id)) {
101
+ added.push(_summariseRule(rule));
102
+ } else {
103
+ const old = beforeIds.get(id);
104
+ const oldStr = JSON.stringify({ action: old.action, reason: old.reason, conditions: old.conditions });
105
+ const newStr = JSON.stringify({ action: rule.action, reason: rule.reason, conditions: rule.conditions });
106
+ if (oldStr !== newStr) {
107
+ modified.push({ id: rule.id, name: rule.name, from: oldStr.slice(0, 200), to: newStr.slice(0, 200) });
108
+ }
109
+ }
110
+ }
111
+ for (const [id, rule] of beforeIds) {
112
+ if (!afterIds.has(id)) removed.push(_summariseRule(rule));
113
+ }
114
+ const enabledChanged = before ? before.enabled !== after.enabled : null;
115
+ const versionId = _writeSnapshot(after);
116
+ const entry = {
117
+ id: randomUUID(),
118
+ changedAt: Date.now(),
119
+ actor,
120
+ rulesBefore: beforeRules.length,
121
+ rulesAfter: afterRules.length,
122
+ added,
123
+ removed,
124
+ modified,
125
+ enabledChanged,
126
+ enabledBefore: before?.enabled ?? null,
127
+ enabledAfter: after.enabled,
128
+ versionId
129
+ };
130
+ _entries.push(entry);
131
+ if (_entries.length > MAX_HISTORY) _entries = _entries.slice(-MAX_HISTORY);
132
+ _persist();
133
+ logger.info(
134
+ { added: added.length, removed: removed.length, modified: modified.length, actor },
135
+ "policy-history: change recorded"
136
+ );
137
+ }
138
+ function getPolicyHistory() {
139
+ _load();
140
+ return [..._entries].reverse();
141
+ }
142
+ export {
143
+ getPolicyHistory,
144
+ getPolicyVersion,
145
+ listPolicyVersions,
146
+ recordPolicyChange
147
+ };
@@ -0,0 +1,83 @@
1
+ import fs from "fs";
2
+ import { randomUUID } from "crypto";
3
+ import { PR_SHADOW_FILE, DATA_DIR } from "./paths.js";
4
+ import logger from "./logger.js";
5
+ const RING_SIZE = 500;
6
+ let _results = [];
7
+ let _loaded = false;
8
+ function ensureLoaded() {
9
+ if (_loaded) return;
10
+ _loaded = true;
11
+ try {
12
+ const raw = fs.readFileSync(PR_SHADOW_FILE, "utf8");
13
+ const parsed = JSON.parse(raw);
14
+ if (parsed.version === 1 && Array.isArray(parsed.results)) {
15
+ _results = parsed.results.slice(-RING_SIZE);
16
+ }
17
+ } catch {
18
+ }
19
+ }
20
+ function persist() {
21
+ try {
22
+ if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
23
+ const payload = { version: 1, results: _results };
24
+ fs.writeFileSync(PR_SHADOW_FILE, JSON.stringify(payload), "utf8");
25
+ } catch (err) {
26
+ logger.warn({ err }, "pr-shadow-store: persist failed");
27
+ }
28
+ }
29
+ function recordPRShadow(input) {
30
+ ensureLoaded();
31
+ const entry = {
32
+ ...input,
33
+ id: randomUUID(),
34
+ timestamp: Date.now()
35
+ };
36
+ _results.push(entry);
37
+ if (_results.length > RING_SIZE) _results.splice(0, _results.length - RING_SIZE);
38
+ persist();
39
+ logger.debug(
40
+ { prId: entry.prId, wouldHaveShown: entry.wouldHaveShown, score: entry.relevanceScore },
41
+ "pr-shadow: result recorded"
42
+ );
43
+ return entry;
44
+ }
45
+ function getPRShadowResults(limit = 100) {
46
+ ensureLoaded();
47
+ return _results.slice(-limit).reverse();
48
+ }
49
+ function getPRShadowStats(windowMs = 30 * 24 * 60 * 60 * 1e3) {
50
+ ensureLoaded();
51
+ const cutoff = Date.now() - windowMs;
52
+ const inWindow = _results.filter((r) => r.timestamp >= cutoff);
53
+ const total = inWindow.length;
54
+ const shown = inWindow.filter((r) => r.wouldHaveShown).length;
55
+ const wouldHaveBeenUsefulRate = total === 0 ? null : Math.round(shown / total * 1e3) / 10;
56
+ const avgRelevanceScore = total === 0 ? null : Math.round(
57
+ inWindow.reduce((a, r) => a + r.relevanceScore, 0) / total * 1e3
58
+ ) / 1e3;
59
+ const triggerCounts = {};
60
+ for (const r of inWindow) {
61
+ for (const t of r.triggeredBy) {
62
+ triggerCounts[t] = (triggerCounts[t] ?? 0) + 1;
63
+ }
64
+ }
65
+ const topTriggers = Object.entries(triggerCounts).sort((a, b) => b[1] - a[1]).map(([trigger, count]) => ({ trigger, count }));
66
+ return {
67
+ totalAnalyzed: total,
68
+ wouldHaveShown: shown,
69
+ wouldHaveBeenUsefulRate,
70
+ avgRelevanceScore,
71
+ topTriggers
72
+ };
73
+ }
74
+ function _resetPRShadowForTesting() {
75
+ _results = [];
76
+ _loaded = true;
77
+ }
78
+ export {
79
+ _resetPRShadowForTesting,
80
+ getPRShadowResults,
81
+ getPRShadowStats,
82
+ recordPRShadow
83
+ };
@@ -0,0 +1,162 @@
1
+ import { spawn } from "child_process";
2
+ import { store } from "./buffer.js";
3
+ import logger from "./logger.js";
4
+ const MAX_LINES_PER_SEC = 30;
5
+ const MAX_LINE_LENGTH = 2e3;
6
+ const RATE_WINDOW_MS = 1e3;
7
+ const ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07]*(?:\x07|\x1b\\)|\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e])/g;
8
+ function stripAnsi(s) {
9
+ return s.replace(ANSI_RE, "");
10
+ }
11
+ function frameworkEnv(command, args) {
12
+ const full = [command, ...args].join(" ").toLowerCase();
13
+ const env = { NO_COLOR: "1", TERM: "dumb" };
14
+ if (/python|manage\.py|django|gunicorn|uvicorn|fastapi/.test(full)) {
15
+ env.PYTHONUNBUFFERED = "1";
16
+ }
17
+ if (/\bjava\b|mvn|gradle|spring-boot|bootrun/.test(full)) {
18
+ const existing = process.env.JAVA_TOOL_OPTIONS ?? "";
19
+ const flag = "-Dspring.output.ansi.enabled=never";
20
+ env.JAVA_TOOL_OPTIONS = existing.includes(flag) ? existing : existing ? `${existing} ${flag}` : flag;
21
+ }
22
+ if (/\bruby\b|rails|puma|unicorn|rackup/.test(full)) {
23
+ env.DISABLE_SPRING = "1";
24
+ env.RAILS_LOG_TO_STDOUT = "1";
25
+ }
26
+ return env;
27
+ }
28
+ const TRACEPARENT_RE = /\b00-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}\b/i;
29
+ const TRACE_KV_RE = /(?:trace[_-]?id|traceid)["'\s]*[:=]["'\s]*([0-9a-f]{32})\b/i;
30
+ function extractTraceId(line) {
31
+ const tp = TRACEPARENT_RE.exec(line);
32
+ if (tp) return tp[1].toLowerCase();
33
+ const kv = TRACE_KV_RE.exec(line);
34
+ if (kv) return kv[1].toLowerCase();
35
+ return null;
36
+ }
37
+ const _watchers = /* @__PURE__ */ new Map();
38
+ function startProcessWatcher(opts) {
39
+ const { name, command, args = [], cwd, env } = opts;
40
+ if (_watchers.has(name)) {
41
+ logger.warn({ name }, "process watcher already running for this name \u2014 stopping previous");
42
+ stopProcessWatcher(name);
43
+ }
44
+ let proc;
45
+ try {
46
+ proc = spawn(command, args, {
47
+ cwd,
48
+ stdio: ["ignore", "pipe", "pipe"],
49
+ // frameworkEnv provides safe defaults; caller opts.env always wins
50
+ env: { ...process.env, ...frameworkEnv(command, args), ...env }
51
+ });
52
+ } catch (err) {
53
+ logger.warn({ name, err }, "process-watcher: failed to spawn process");
54
+ return;
55
+ }
56
+ const watcher = {
57
+ proc,
58
+ name,
59
+ lineCount: 0,
60
+ windowStart: Date.now(),
61
+ droppedInWindow: 0
62
+ };
63
+ _watchers.set(name, watcher);
64
+ logger.info({ name, command, args }, "process-watcher: started");
65
+ function handleLine(raw, isStderr) {
66
+ const now = Date.now();
67
+ if (now - watcher.windowStart > RATE_WINDOW_MS) {
68
+ if (watcher.droppedInWindow > 0) {
69
+ pushTerminal(watcher, `[mergen] dropped ${watcher.droppedInWindow} lines (rate limit: ${MAX_LINES_PER_SEC}/s)`);
70
+ }
71
+ watcher.lineCount = 0;
72
+ watcher.windowStart = now;
73
+ watcher.droppedInWindow = 0;
74
+ }
75
+ if (watcher.lineCount >= MAX_LINES_PER_SEC) {
76
+ watcher.droppedInWindow++;
77
+ return;
78
+ }
79
+ watcher.lineCount++;
80
+ const line = stripAnsi(raw).slice(0, MAX_LINE_LENGTH);
81
+ const data = isStderr ? `[stderr] ${line}` : line;
82
+ pushTerminal(watcher, data);
83
+ }
84
+ function pushTerminal(w, data) {
85
+ const traceId = extractTraceId(data) ?? void 0;
86
+ const event = {
87
+ type: "terminal",
88
+ terminalName: w.name,
89
+ data,
90
+ timestamp: Date.now(),
91
+ ...traceId ? { traceId } : {}
92
+ };
93
+ try {
94
+ store.push(event);
95
+ } catch {
96
+ }
97
+ }
98
+ let stdoutBuf = "";
99
+ proc.stdout?.on("data", (chunk) => {
100
+ stdoutBuf += chunk.toString("utf8");
101
+ const lines = stdoutBuf.split("\n");
102
+ stdoutBuf = lines.pop() ?? "";
103
+ for (const l of lines) if (l.trim()) handleLine(l, false);
104
+ });
105
+ let stderrBuf = "";
106
+ proc.stderr?.on("data", (chunk) => {
107
+ stderrBuf += chunk.toString("utf8");
108
+ const lines = stderrBuf.split("\n");
109
+ stderrBuf = lines.pop() ?? "";
110
+ for (const l of lines) if (l.trim()) handleLine(l, true);
111
+ });
112
+ proc.on("error", (err) => {
113
+ logger.warn({ name, err }, "process-watcher: spawn error");
114
+ _watchers.delete(name);
115
+ });
116
+ proc.on("close", (code, signal) => {
117
+ _watchers.delete(name);
118
+ const isCrash = code !== 0 && code !== null;
119
+ const isOom = code === 137;
120
+ const reason = isOom ? "oom" : signal ? "signal" : isCrash ? "crash" : "normal";
121
+ logger.info({ name, code, signal, reason }, "process-watcher: process exited");
122
+ const exitEvent = {
123
+ type: "process_exit",
124
+ process: name,
125
+ exitCode: code ?? -1,
126
+ reason,
127
+ signal: signal ?? void 0,
128
+ timestamp: Date.now()
129
+ };
130
+ try {
131
+ store.push(exitEvent);
132
+ } catch {
133
+ }
134
+ if (isCrash) {
135
+ pushTerminal({ ...watcher, name }, `[mergen] process "${name}" exited with code ${code}${signal ? ` (${signal})` : ""}`);
136
+ }
137
+ });
138
+ }
139
+ function stopProcessWatcher(name) {
140
+ const w = _watchers.get(name);
141
+ if (!w) return;
142
+ try {
143
+ w.proc.stdout?.destroy();
144
+ w.proc.stderr?.destroy();
145
+ w.proc.kill();
146
+ } catch {
147
+ }
148
+ _watchers.delete(name);
149
+ logger.info({ name }, "process-watcher: stopped");
150
+ }
151
+ function stopAllProcessWatchers() {
152
+ for (const name of _watchers.keys()) stopProcessWatcher(name);
153
+ }
154
+ function listProcessWatchers() {
155
+ return [..._watchers.keys()];
156
+ }
157
+ export {
158
+ listProcessWatchers,
159
+ startProcessWatcher,
160
+ stopAllProcessWatchers,
161
+ stopProcessWatcher
162
+ };
@@ -0,0 +1,92 @@
1
+ import fs from "fs";
2
+ import { RBAC_FILE, DATA_DIR } from "./paths.js";
3
+ import logger from "./logger.js";
4
+ const ROLE_RANK = { viewer: 0, responder: 1, admin: 2 };
5
+ let _loadFailed = false;
6
+ function load() {
7
+ if (!fs.existsSync(RBAC_FILE)) {
8
+ _loadFailed = false;
9
+ return { members: [] };
10
+ }
11
+ try {
12
+ const raw = fs.readFileSync(RBAC_FILE, "utf8");
13
+ const parsed = JSON.parse(raw);
14
+ if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.members)) {
15
+ _loadFailed = true;
16
+ logger.error({ path: RBAC_FILE }, 'rbac: RBAC file is malformed (missing/invalid "members" array) \u2014 failing closed (all actors restricted to viewer)');
17
+ return { members: [] };
18
+ }
19
+ _loadFailed = false;
20
+ return parsed;
21
+ } catch (err) {
22
+ _loadFailed = true;
23
+ logger.error({ err }, "rbac: RBAC file is unreadable \u2014 failing closed (all actors restricted to viewer)");
24
+ return { members: [] };
25
+ }
26
+ }
27
+ function save(store) {
28
+ try {
29
+ fs.mkdirSync(DATA_DIR, { recursive: true });
30
+ fs.writeFileSync(RBAC_FILE, JSON.stringify(store, null, 2), "utf8");
31
+ _loadFailed = false;
32
+ } catch (err) {
33
+ logger.warn({ err }, "rbac: failed to write rbac.json");
34
+ }
35
+ }
36
+ function listMembers() {
37
+ return load().members;
38
+ }
39
+ function upsertMember(id, role, source = "manual") {
40
+ const store = load();
41
+ const existing = store.members.find((m) => m.id === id);
42
+ if (existing) {
43
+ if (source === "oidc-sync" && (existing.source ?? "manual") === "manual") {
44
+ logger.info(
45
+ { id, pinnedRole: existing.role, syncedRole: role },
46
+ "rbac: OIDC group-role sync skipped \u2014 member role is manually pinned"
47
+ );
48
+ return { id: existing.id, role: existing.role, source: existing.source };
49
+ }
50
+ existing.role = role;
51
+ existing.source = source;
52
+ } else {
53
+ store.members.push({ id, role, source });
54
+ }
55
+ save(store);
56
+ logger.info({ id, role, source }, "rbac: member upserted");
57
+ return { id, role, source };
58
+ }
59
+ function removeMember(id) {
60
+ const store = load();
61
+ const before = store.members.length;
62
+ store.members = store.members.filter((m) => m.id !== id);
63
+ if (store.members.length < before) {
64
+ save(store);
65
+ logger.info({ id }, "rbac: member removed");
66
+ return true;
67
+ }
68
+ return false;
69
+ }
70
+ function resolveRole(actor) {
71
+ if (actor === "autopilot") return "admin";
72
+ const store = load();
73
+ if (_loadFailed) return "viewer";
74
+ if (store.members.length === 0) return "admin";
75
+ const member = store.members.find((m) => m.id === actor);
76
+ return member?.role ?? "viewer";
77
+ }
78
+ function hasPermission(actor, required) {
79
+ const actual = resolveRole(actor);
80
+ return ROLE_RANK[actual] >= ROLE_RANK[required];
81
+ }
82
+ function _resetForTesting() {
83
+ _loadFailed = false;
84
+ }
85
+ export {
86
+ _resetForTesting,
87
+ hasPermission,
88
+ listMembers,
89
+ removeMember,
90
+ resolveRole,
91
+ upsertMember
92
+ };
@@ -0,0 +1,114 @@
1
+ const DEFAULT_KEYS = [
2
+ "password",
3
+ "passwd",
4
+ "pwd",
5
+ "authorization",
6
+ "auth",
7
+ "cookie",
8
+ "set-cookie",
9
+ "token",
10
+ "access_token",
11
+ "refresh_token",
12
+ "id_token",
13
+ "jwt",
14
+ "apikey",
15
+ "api_key",
16
+ "api-key",
17
+ "secret",
18
+ "client_secret",
19
+ "session",
20
+ "sessionid",
21
+ "session_id",
22
+ "email",
23
+ "ssn",
24
+ "creditcard",
25
+ "credit_card",
26
+ "cc_number"
27
+ ];
28
+ function loadKeys() {
29
+ const env = process.env.MERGEN_REDACT_KEYS;
30
+ const extra = env ? env.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean) : [];
31
+ return /* @__PURE__ */ new Set([...DEFAULT_KEYS, ...extra]);
32
+ }
33
+ let _keys = loadKeys();
34
+ function reloadRedactKeys() {
35
+ _keys = loadKeys();
36
+ }
37
+ function reloadPiiConfig() {
38
+ loadPiiConfig();
39
+ }
40
+ const REDACTED = "[REDACTED]";
41
+ const MAX_DEPTH = 8;
42
+ const RE_JWT = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
43
+ const RE_BEARER = /\bBearer\s+[A-Za-z0-9._\-+/=]{8,}\b/gi;
44
+ const RE_EMAIL = /\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g;
45
+ const RE_CARD = /\b(?:\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{1,7}|\d{4}[ -]?\d{6}[ -]?\d{5})\b/g;
46
+ const RE_PHONE = /\b(\+?1[\s.-]?)?\(?[2-9]\d{2}\)?[\s.-]?\d{3}[\s.-]\d{4}\b/g;
47
+ const RE_AWS_KEY = /\b(AKIA|AROA|ASIA|AIDA|ANPA|ANVA|APKA)[0-9A-Z]{16}\b/g;
48
+ const RE_PEM = /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g;
49
+ const RE_STRIPE = /\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g;
50
+ const RE_CONN_STR = /\b([a-zA-Z0-9+.-]+:\/\/)([^:\s]+):([^@\s]+)@([^\s/]+)/gi;
51
+ import fs from "fs";
52
+ import path from "path";
53
+ import os from "os";
54
+ const PII_CONFIG_FILE = path.join(os.homedir(), ".mergen", "pii-config.json");
55
+ let _customPatterns = [];
56
+ function loadPiiConfig() {
57
+ if (!fs.existsSync(PII_CONFIG_FILE)) return;
58
+ try {
59
+ const raw = JSON.parse(fs.readFileSync(PII_CONFIG_FILE, "utf8"));
60
+ _customPatterns = (raw.patterns ?? []).flatMap((p) => {
61
+ try {
62
+ const m = p.match(/^\/(.*)\/([gimsuy]*)$/);
63
+ return m ? [new RegExp(m[1], m[2])] : [];
64
+ } catch {
65
+ return [];
66
+ }
67
+ });
68
+ } catch {
69
+ }
70
+ }
71
+ loadPiiConfig();
72
+ function redactString(s) {
73
+ if (s.length > 1e5) return REDACTED;
74
+ let out = s.replace(RE_PEM, REDACTED).replace(RE_JWT, REDACTED).replace(RE_BEARER, `Bearer ${REDACTED}`).replace(RE_AWS_KEY, REDACTED).replace(RE_STRIPE, REDACTED).replace(RE_CONN_STR, "$1$2:[REDACTED]@$4").replace(RE_EMAIL, REDACTED).replace(RE_CARD, REDACTED).replace(RE_PHONE, REDACTED);
75
+ for (const re of _customPatterns) {
76
+ re.lastIndex = 0;
77
+ out = out.replace(re, REDACTED);
78
+ }
79
+ return out;
80
+ }
81
+ function isSensitiveKey(key) {
82
+ return _keys.has(key.toLowerCase());
83
+ }
84
+ function redactInternal(value, depth) {
85
+ if (depth > MAX_DEPTH) return value;
86
+ if (value === null || value === void 0) return value;
87
+ const t = typeof value;
88
+ if (t === "string") return redactString(value);
89
+ if (t === "number" || t === "boolean" || t === "bigint") return value;
90
+ if (Array.isArray(value)) {
91
+ return value.map((v) => redactInternal(v, depth + 1));
92
+ }
93
+ if (t === "object") {
94
+ const obj = value;
95
+ const out = {};
96
+ for (const k of Object.keys(obj)) {
97
+ out[k] = isSensitiveKey(k) ? REDACTED : redactInternal(obj[k], depth + 1);
98
+ }
99
+ return out;
100
+ }
101
+ return value;
102
+ }
103
+ function redact(value) {
104
+ try {
105
+ return redactInternal(value, 0);
106
+ } catch {
107
+ return value;
108
+ }
109
+ }
110
+ export {
111
+ redact,
112
+ reloadPiiConfig,
113
+ reloadRedactKeys
114
+ };