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,599 @@
1
+ import {
2
+ LogLevelSchema,
3
+ ConsoleEventSchema,
4
+ NetworkEventSchema,
5
+ ContextSnapshotSchema,
6
+ WebSocketFrameSchema,
7
+ WebSocketEventSchema,
8
+ SSEEventSchema,
9
+ DiagnosticEventSchema,
10
+ TerminalOutputEventSchema,
11
+ TestResultEventSchema,
12
+ ProcessExitEventSchema,
13
+ CIEventSchema,
14
+ DeploymentEventSchema,
15
+ BackendSpanEventSchema,
16
+ BrowserEventSchema
17
+ } from "./buffer-schemas.js";
18
+ const AUTH_KEYS = ["token", "userToken", "accessToken", "authToken", "jwt", "user", "session"];
19
+ const AUTH_URL_RE = /login|auth|signin|token/i;
20
+ const MIN_SIGNAL_CONFIDENCE = 0.45;
21
+ function getDefaultTenantId() {
22
+ return process.env.MERGEN_DEFAULT_TENANT_ID ?? void 0;
23
+ }
24
+ let _getEffectiveBufferSize = () => MAX_BUFFER_SIZE;
25
+ function setBufferSizeGetter(fn) {
26
+ _getEffectiveBufferSize = fn;
27
+ }
28
+ const MAX_BUFFER_SIZE = (() => {
29
+ const env = parseInt(process.env.MERGEN_BUFFER_SIZE ?? "", 10);
30
+ if (!Number.isFinite(env) || env < 1) return 2e3;
31
+ return Math.min(env, 1e4);
32
+ })();
33
+ const MAX_SIZE = MAX_BUFFER_SIZE;
34
+ class RingBuffer {
35
+ _ring = new Array(MAX_SIZE).fill(void 0);
36
+ _head = 0;
37
+ // index of the oldest slot
38
+ _count = 0;
39
+ // number of occupied slots
40
+ // O(1) running counters — updated on push() and clear()
41
+ _errors = 0;
42
+ _warnings = 0;
43
+ _networkErrors = 0;
44
+ // Track last localStorage snapshot per URL for diffing
45
+ _lastLocalStorageByUrl = /* @__PURE__ */ new Map();
46
+ _lastEventAt = null;
47
+ _clearedAt = null;
48
+ _incrementCounters(event) {
49
+ if (event.type === "console") {
50
+ if (event.level === "error") this._errors++;
51
+ else if (event.level === "warn") this._warnings++;
52
+ } else if (event.type === "network" && (event.status >= 400 || event.status === 0 || event.error)) {
53
+ this._networkErrors++;
54
+ }
55
+ }
56
+ _decrementCounters(event) {
57
+ if (event.type === "console") {
58
+ if (event.level === "error") this._errors--;
59
+ else if (event.level === "warn") this._warnings--;
60
+ } else if (event.type === "network" && (event.status >= 400 || event.status === 0 || event.error)) {
61
+ this._networkErrors--;
62
+ }
63
+ }
64
+ push(event, tenantId) {
65
+ if (tenantId !== void 0) event._tenantId = tenantId;
66
+ this._lastEventAt = event.timestamp;
67
+ if (this._count < MAX_SIZE) {
68
+ const slot = (this._head + this._count) % MAX_SIZE;
69
+ this._ring[slot] = event;
70
+ this._count++;
71
+ this._incrementCounters(event);
72
+ return;
73
+ }
74
+ const isHighPriority = event.type !== "console" || event.level !== "log";
75
+ let evictIdx = this._head;
76
+ if (isHighPriority) {
77
+ for (let i = 0; i < this._count; i++) {
78
+ const idx = (this._head + i) % MAX_SIZE;
79
+ const candidate = this._ring[idx];
80
+ if (candidate?.type === "console" && candidate.level === "log") {
81
+ evictIdx = idx;
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ const evicted = this._ring[evictIdx];
87
+ if (evicted) this._decrementCounters(evicted);
88
+ this._ring[evictIdx] = event;
89
+ this._incrementCounters(event);
90
+ if (evictIdx === this._head) {
91
+ this._head = (this._head + 1) % MAX_SIZE;
92
+ }
93
+ }
94
+ getCounters(tenantId) {
95
+ if (tenantId === void 0) {
96
+ return { errors: this._errors, warnings: this._warnings, networkErrors: this._networkErrors };
97
+ }
98
+ let errors = 0, warnings = 0, networkErrors = 0;
99
+ for (const e of this._iterate()) {
100
+ if (e._tenantId !== tenantId) continue;
101
+ if (e.type === "console") {
102
+ if (e.level === "error") errors++;
103
+ else if (e.level === "warn") warnings++;
104
+ } else if (e.type === "network" && (e.status >= 400 || e.status === 0 || e.error)) {
105
+ networkErrors++;
106
+ }
107
+ }
108
+ return { errors, warnings, networkErrors };
109
+ }
110
+ *_iterate() {
111
+ for (let i = 0; i < this._count; i++) {
112
+ yield this._ring[(this._head + i) % MAX_SIZE];
113
+ }
114
+ }
115
+ getLogs(limit = 50, level, since, tenantId) {
116
+ const cap = Math.min(limit, _getEffectiveBufferSize());
117
+ const results = [];
118
+ for (const e of this._iterate()) {
119
+ if (e.type !== "console") continue;
120
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
121
+ if (level && e.level !== level) continue;
122
+ if (since !== void 0 && e.timestamp < since) continue;
123
+ results.push(e);
124
+ }
125
+ return results.slice(-cap);
126
+ }
127
+ getNetwork(limit = 50, statusFilter, since, tenantId) {
128
+ const cap = Math.min(limit, _getEffectiveBufferSize());
129
+ const results = [];
130
+ for (const e of this._iterate()) {
131
+ if (e.type !== "network") continue;
132
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
133
+ if (statusFilter !== void 0 && e.status !== statusFilter) continue;
134
+ if (since !== void 0 && e.timestamp < since) continue;
135
+ results.push(e);
136
+ }
137
+ return results.slice(-cap);
138
+ }
139
+ getContext(limit = 10, since, tenantId) {
140
+ const cap = Math.min(limit, _getEffectiveBufferSize());
141
+ const results = [];
142
+ for (const e of this._iterate()) {
143
+ if (e.type !== "context") continue;
144
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
145
+ if (since !== void 0 && e.timestamp < since) continue;
146
+ results.push(e);
147
+ }
148
+ return results.slice(-cap);
149
+ }
150
+ getWebSockets(limit = 50, connectionUrl, since, tenantId) {
151
+ const cap = Math.min(limit, _getEffectiveBufferSize());
152
+ const results = [];
153
+ for (const e of this._iterate()) {
154
+ if (e.type !== "websocket") continue;
155
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
156
+ if (connectionUrl && !e.url.includes(connectionUrl)) continue;
157
+ if (since !== void 0 && e.timestamp < since) continue;
158
+ results.push(e);
159
+ }
160
+ return results.slice(-cap);
161
+ }
162
+ getWebSocketCount(tenantId) {
163
+ let count = 0;
164
+ for (const e of this._iterate()) {
165
+ if (e.type !== "websocket") continue;
166
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
167
+ count++;
168
+ }
169
+ return count;
170
+ }
171
+ getSSE(limit = 50, connectionUrl, since, tenantId) {
172
+ const cap = Math.min(limit, _getEffectiveBufferSize());
173
+ const results = [];
174
+ for (const e of this._iterate()) {
175
+ if (e.type !== "sse") continue;
176
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
177
+ if (connectionUrl && !e.url.includes(connectionUrl)) continue;
178
+ if (since !== void 0 && e.timestamp < since) continue;
179
+ results.push(e);
180
+ }
181
+ return results.slice(-cap);
182
+ }
183
+ getDiagnostics(limit = 50, severity, since, tenantId) {
184
+ const cap = Math.min(limit, _getEffectiveBufferSize());
185
+ const results = [];
186
+ for (const e of this._iterate()) {
187
+ if (e.type !== "diagnostic") continue;
188
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
189
+ if (severity && e.severity !== severity) continue;
190
+ if (since !== void 0 && e.timestamp < since) continue;
191
+ results.push(e);
192
+ }
193
+ return results.slice(-cap);
194
+ }
195
+ getTerminalOutput(limit = 50, terminalName, since, tenantId) {
196
+ const cap = Math.min(limit, _getEffectiveBufferSize());
197
+ const results = [];
198
+ for (const e of this._iterate()) {
199
+ if (e.type !== "terminal") continue;
200
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
201
+ if (terminalName && e.terminalName !== terminalName) continue;
202
+ if (since !== void 0 && e.timestamp < since) continue;
203
+ results.push(e);
204
+ }
205
+ return results.slice(-cap);
206
+ }
207
+ getTestResults(limit = 50, status, since, tenantId) {
208
+ const cap = Math.min(limit, _getEffectiveBufferSize());
209
+ const results = [];
210
+ for (const e of this._iterate()) {
211
+ if (e.type !== "test_result") continue;
212
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
213
+ if (status && e.status !== status) continue;
214
+ if (since !== void 0 && e.timestamp < since) continue;
215
+ results.push(e);
216
+ }
217
+ return results.slice(-cap);
218
+ }
219
+ getProcessExits(limit = 50, reason, since, tenantId) {
220
+ const cap = Math.min(limit, _getEffectiveBufferSize());
221
+ const results = [];
222
+ for (const e of this._iterate()) {
223
+ if (e.type !== "process_exit") continue;
224
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
225
+ if (reason && e.reason !== reason) continue;
226
+ if (since !== void 0 && e.timestamp < since) continue;
227
+ results.push(e);
228
+ }
229
+ return results.slice(-cap);
230
+ }
231
+ getCIEvents(limit = 50, status, since, tenantId) {
232
+ const cap = Math.min(limit, _getEffectiveBufferSize());
233
+ const results = [];
234
+ for (const e of this._iterate()) {
235
+ if (e.type !== "ci") continue;
236
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
237
+ if (status && e.status !== status) continue;
238
+ if (since !== void 0 && e.timestamp < since) continue;
239
+ results.push(e);
240
+ }
241
+ return results.slice(-cap);
242
+ }
243
+ getDeployments(limit = 50, environment, since, tenantId) {
244
+ const cap = Math.min(limit, _getEffectiveBufferSize());
245
+ const results = [];
246
+ for (const e of this._iterate()) {
247
+ if (e.type !== "deployment") continue;
248
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
249
+ if (environment && e.environment !== environment) continue;
250
+ if (since !== void 0 && e.timestamp < since) continue;
251
+ results.push(e);
252
+ }
253
+ return results.slice(-cap);
254
+ }
255
+ getBackendSpans(limit = 50, service, since, tenantId) {
256
+ const cap = Math.min(limit, _getEffectiveBufferSize());
257
+ const results = [];
258
+ for (const e of this._iterate()) {
259
+ if (e.type !== "backend_span") continue;
260
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
261
+ if (service && e.service !== service) continue;
262
+ if (since !== void 0 && e.timestamp < since) continue;
263
+ results.push(e);
264
+ }
265
+ return results.slice(-cap);
266
+ }
267
+ getBlastRadius(opts = {}) {
268
+ const { since, errorPattern, tenantId } = opts;
269
+ const patternRe = errorPattern ? new RegExp(errorPattern, "i") : null;
270
+ const sessions = /* @__PURE__ */ new Set();
271
+ const users = /* @__PURE__ */ new Set();
272
+ const userSessions = /* @__PURE__ */ new Map();
273
+ const browserCounts = {};
274
+ const osCounts = {};
275
+ const errorMsgCounts = {};
276
+ let firstSeenAt = null;
277
+ let lastSeenAt = null;
278
+ let errorCount = 0;
279
+ for (const e of this._iterate()) {
280
+ if (e.type !== "console" || e.level !== "error") continue;
281
+ if (tenantId !== void 0 && e._tenantId !== tenantId) continue;
282
+ if (since !== void 0 && e.timestamp < since) continue;
283
+ if (patternRe) {
284
+ const msgText = e.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
285
+ if (!patternRe.test(msgText)) continue;
286
+ }
287
+ errorCount++;
288
+ if (firstSeenAt === null || e.timestamp < firstSeenAt) firstSeenAt = e.timestamp;
289
+ if (lastSeenAt === null || e.timestamp > lastSeenAt) lastSeenAt = e.timestamp;
290
+ if (e.sessionId) sessions.add(e.sessionId);
291
+ if (e.userId) {
292
+ users.add(e.userId);
293
+ if (e.sessionId) {
294
+ let set = userSessions.get(e.userId);
295
+ if (!set) {
296
+ set = /* @__PURE__ */ new Set();
297
+ userSessions.set(e.userId, set);
298
+ }
299
+ set.add(e.sessionId);
300
+ }
301
+ }
302
+ if (e.userAgent) {
303
+ const browser = parseBrowser(e.userAgent);
304
+ const os = parseOS(e.userAgent);
305
+ browserCounts[browser] = (browserCounts[browser] ?? 0) + 1;
306
+ osCounts[os] = (osCounts[os] ?? 0) + 1;
307
+ }
308
+ const msg = e.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").slice(0, 120);
309
+ errorMsgCounts[msg] = (errorMsgCounts[msg] ?? 0) + 1;
310
+ }
311
+ let returningUserSessions = 0;
312
+ for (const sessionSet of userSessions.values()) {
313
+ if (sessionSet.size > 1) returningUserSessions += sessionSet.size - 1;
314
+ }
315
+ let correlatedDeploy = null;
316
+ if (firstSeenAt !== null) {
317
+ const deploys = this.getDeployments(50);
318
+ const prior = deploys.filter((d) => d.timestamp <= firstSeenAt && d.status === "success").sort((a, b) => b.timestamp - a.timestamp);
319
+ correlatedDeploy = prior[0]?.sha ?? null;
320
+ }
321
+ const topErrors = Object.entries(errorMsgCounts).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([message, count]) => ({ message, count }));
322
+ return {
323
+ affectedSessions: sessions.size,
324
+ affectedUsers: users.size,
325
+ returningUserSessions,
326
+ errorCount,
327
+ firstSeenAt,
328
+ lastSeenAt,
329
+ browserSegments: browserCounts,
330
+ osSegments: osCounts,
331
+ topErrors,
332
+ correlatedDeploy,
333
+ durationMs: firstSeenAt !== null ? Date.now() - firstSeenAt : null
334
+ };
335
+ }
336
+ clear() {
337
+ this._ring.fill(void 0);
338
+ this._head = 0;
339
+ this._count = 0;
340
+ this._errors = 0;
341
+ this._warnings = 0;
342
+ this._networkErrors = 0;
343
+ this._lastLocalStorageByUrl.clear();
344
+ this._lastEventAt = null;
345
+ this._clearedAt = Date.now();
346
+ }
347
+ getLocalStorageDiff(current, url) {
348
+ const prev = this._lastLocalStorageByUrl.get(url) || {};
349
+ const changed = /* @__PURE__ */ new Set();
350
+ for (const [key, val] of Object.entries(current)) {
351
+ if (prev[key] !== val) {
352
+ changed.add(key);
353
+ }
354
+ }
355
+ for (const key of Object.keys(prev)) {
356
+ if (!(key in current)) {
357
+ changed.add(key);
358
+ }
359
+ }
360
+ this._lastLocalStorageByUrl.set(url, { ...current });
361
+ return { full: current, changed };
362
+ }
363
+ getSignals(tenantId) {
364
+ const candidates = [];
365
+ const logs = this.getLogs(200, void 0, void 0, tenantId);
366
+ const network = this.getNetwork(200, void 0, void 0, tenantId);
367
+ const contexts = this.getContext(20, void 0, tenantId);
368
+ const latestCtx = contexts.length > 0 ? contexts[contexts.length - 1] : null;
369
+ const ls = latestCtx?.localStorage ?? {};
370
+ const authOk = network.filter((n) => AUTH_URL_RE.test(n.url) && n.status === 200);
371
+ if (authOk.length > 0 && latestCtx) {
372
+ const missingKeys = AUTH_KEYS.filter(
373
+ (k) => !(k in ls) || ls[k] === "null" || ls[k] === "" || ls[k] === "undefined"
374
+ );
375
+ if (missingKeys.length > 0) {
376
+ const call = authOk[authOk.length - 1];
377
+ const key = missingKeys[0];
378
+ const hasErrors = logs.some((e) => e.level === "error");
379
+ const confidence = hasErrors ? 0.9 : 0.72;
380
+ candidates.push({
381
+ kind: "auth_token_not_stored",
382
+ message: `${call.method} ${call.url} \u2192 200 but \`localStorage.${key}\` is null \u2014 reads will get null`,
383
+ action: `Check the ${call.url} response handler: call \`localStorage.setItem('${key}', ...)\` before navigating away`,
384
+ count: authOk.length,
385
+ confidence,
386
+ suggestedTool: "quick_check"
387
+ });
388
+ }
389
+ }
390
+ const authFail = network.filter(
391
+ (n) => AUTH_URL_RE.test(n.url) && (n.status >= 400 || n.status === 0 || !!n.error)
392
+ );
393
+ for (const call of authFail) {
394
+ const label = call.status === 0 ? `network error (${call.error ?? "no response"})` : `HTTP ${call.status}`;
395
+ const confidence = call.status >= 500 || call.status === 0 ? 0.85 : 0.65;
396
+ candidates.push({
397
+ kind: "auth_500",
398
+ message: `${call.method} ${call.url} failed with ${label} \u2014 users cannot log in`,
399
+ action: `Check the server logs for ${call.url} \u2014 ${call.status >= 500 ? "server-side error" : "client sent bad request"}`,
400
+ count: 1,
401
+ confidence,
402
+ suggestedTool: "quick_check"
403
+ });
404
+ }
405
+ if (contexts.length >= 2) {
406
+ const prev = contexts[contexts.length - 2].localStorage;
407
+ const curr = ls;
408
+ const clearedKeys = Object.keys(prev).filter(
409
+ (k) => prev[k] && prev[k] !== "null" && prev[k] !== "undefined" && (!(k in curr) || curr[k] === "null" || curr[k] === "" || curr[k] === "undefined")
410
+ );
411
+ if (clearedKeys.length > 0) {
412
+ const key = clearedKeys[0];
413
+ const confidence = AUTH_KEYS.includes(key) ? 0.8 : 0.55;
414
+ candidates.push({
415
+ kind: "storage_cleared",
416
+ message: `\`localStorage.${key}\` was set, then cleared between page events \u2014 may cause null-read crash`,
417
+ action: `Find where \`localStorage.removeItem('${key}')\` or \`localStorage.clear()\` is called and guard it`,
418
+ count: clearedKeys.length,
419
+ confidence,
420
+ suggestedTool: "quick_check"
421
+ });
422
+ }
423
+ }
424
+ const netErrUrls = /* @__PURE__ */ new Map();
425
+ for (const n of network) {
426
+ if (n.status >= 400 || n.status === 0 || n.error) {
427
+ const prev = netErrUrls.get(n.url);
428
+ netErrUrls.set(n.url, {
429
+ count: (prev?.count ?? 0) + 1,
430
+ status: n.status,
431
+ error: n.error
432
+ });
433
+ }
434
+ }
435
+ for (const [url, { count, status, error }] of netErrUrls) {
436
+ if (count >= 3) {
437
+ if (AUTH_URL_RE.test(url)) continue;
438
+ const label = status === 0 ? `network error (${error ?? "no response"})` : `HTTP ${status}`;
439
+ const confidence = Math.min(0.88, 0.5 + (count - 3) * 0.08);
440
+ candidates.push({
441
+ kind: "repeated_network_error",
442
+ message: `${url} failing ${count}\xD7 with ${label} \u2014 dependent state will be uninitialised`,
443
+ action: `Guard every read of the response data with a null/error check, or retry with back-off`,
444
+ count,
445
+ confidence,
446
+ suggestedTool: "quick_check"
447
+ });
448
+ }
449
+ }
450
+ const warnEvents = logs.filter((e) => e.level === "warn");
451
+ if (warnEvents.length >= 5) {
452
+ const firstMsg = warnEvents[0].args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").slice(0, 80);
453
+ const confidence = Math.min(0.75, 0.45 + (warnEvents.length - 5) * 0.04);
454
+ candidates.push({
455
+ kind: "warn_spike",
456
+ message: `${warnEvents.length} warnings \u2014 e.g. "${firstMsg}" \u2014 may escalate to crash`,
457
+ action: `Run explain_warning on the first warning to understand the escalation path`,
458
+ count: warnEvents.length,
459
+ confidence,
460
+ suggestedTool: "explain_warning"
461
+ });
462
+ }
463
+ const errMsgs = /* @__PURE__ */ new Map();
464
+ for (const e of logs.filter((l) => l.level === "error")) {
465
+ const msg = e.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").slice(0, 100);
466
+ errMsgs.set(msg, (errMsgs.get(msg) ?? 0) + 1);
467
+ }
468
+ for (const [msg, count] of errMsgs) {
469
+ if (count >= 3) {
470
+ const confidence = Math.min(0.92, 0.62 + (count - 3) * 0.08);
471
+ candidates.push({
472
+ kind: "repeated_error",
473
+ message: `"${msg}" \u2014 thrown ${count}\xD7 \u2014 not a one-off, it's a code path bug`,
474
+ action: `Use reconstruct_context to find the root cause \u2014 this error fires on every execution of a specific path`,
475
+ count,
476
+ confidence,
477
+ suggestedTool: "quick_check"
478
+ });
479
+ }
480
+ }
481
+ const slowReqs = network.filter((n) => n.duration > 2e3).sort((a, b) => b.duration - a.duration);
482
+ if (slowReqs.length >= 3) {
483
+ const worst = slowReqs[0];
484
+ const confidence = Math.min(0.72, 0.45 + (slowReqs.length - 3) * 0.04);
485
+ candidates.push({
486
+ kind: "slow_requests",
487
+ message: `${slowReqs.length} slow requests \u2014 worst: ${worst.method} ${worst.url} (${worst.duration}ms) \u2014 may cause race conditions`,
488
+ action: `Run session_summary to see all slow endpoints, then check for sequential fetches that could run in parallel`,
489
+ count: slowReqs.length,
490
+ confidence,
491
+ suggestedTool: "session_summary"
492
+ });
493
+ }
494
+ const seen = /* @__PURE__ */ new Set();
495
+ return candidates.filter((s) => s.confidence >= MIN_SIGNAL_CONFIDENCE).sort((a, b) => b.confidence - a.confidence).filter((s) => {
496
+ if (seen.has(s.kind)) return false;
497
+ seen.add(s.kind);
498
+ return true;
499
+ });
500
+ }
501
+ size(tenantId) {
502
+ if (tenantId === void 0) return this._count;
503
+ let n = 0;
504
+ for (const e of this._iterate()) {
505
+ if (e._tenantId === tenantId) n++;
506
+ }
507
+ return n;
508
+ }
509
+ serialize(tenantId) {
510
+ if (tenantId === void 0) return Array.from(this._iterate());
511
+ return Array.from(this._iterate()).filter((e) => e._tenantId === tenantId);
512
+ }
513
+ rehydrate(events, tenantId) {
514
+ for (const event of events) this.push(event, tenantId);
515
+ }
516
+ lastEventAt(tenantId) {
517
+ if (tenantId === void 0) return this._lastEventAt;
518
+ let last = null;
519
+ for (const e of this._iterate()) {
520
+ if (e._tenantId !== tenantId) continue;
521
+ if (last === null || e.timestamp > last) last = e.timestamp;
522
+ }
523
+ return last;
524
+ }
525
+ clearedAt() {
526
+ return this._clearedAt;
527
+ }
528
+ }
529
+ function parseBrowser(ua) {
530
+ if (/Edg\//.test(ua)) return "Edge";
531
+ if (/OPR\//.test(ua)) return "Opera";
532
+ if (/Firefox\//.test(ua)) return "Firefox";
533
+ if (/Chrome\//.test(ua)) return "Chrome";
534
+ if (/Safari\//.test(ua) && !/Chrome/.test(ua)) return "Safari";
535
+ if (/MSIE|Trident/.test(ua)) return "IE";
536
+ return "Other";
537
+ }
538
+ function parseOS(ua) {
539
+ if (/iPhone|iPad/.test(ua)) return "iOS";
540
+ if (/Android/.test(ua)) return "Android";
541
+ if (/Mac OS X/.test(ua) && !/iPhone|iPad/.test(ua)) return "macOS";
542
+ if (/Windows/.test(ua)) return "Windows";
543
+ if (/Linux/.test(ua)) return "Linux";
544
+ return "Other";
545
+ }
546
+ let store = new RingBuffer();
547
+ function setStore(s) {
548
+ store = s;
549
+ }
550
+ function getStaleErrors(thresholdMs = 60 * 60 * 1e3) {
551
+ const cutoff = Date.now() - thresholdMs;
552
+ const logs = store.getLogs(500, "error");
553
+ const grouped = /* @__PURE__ */ new Map();
554
+ for (const ev of logs) {
555
+ const key = String(ev.args[0] ?? "").slice(0, 120);
556
+ if (!key) continue;
557
+ const existing = grouped.get(key);
558
+ if (!existing) {
559
+ grouped.set(key, { firstSeenAt: ev.timestamp, count: 1 });
560
+ } else {
561
+ if (ev.timestamp < existing.firstSeenAt) existing.firstSeenAt = ev.timestamp;
562
+ existing.count += 1;
563
+ }
564
+ }
565
+ const now = Date.now();
566
+ const stale = [];
567
+ for (const [message, { firstSeenAt, count }] of grouped) {
568
+ if (firstSeenAt < cutoff) {
569
+ stale.push({ message, firstSeenAt, ageMs: now - firstSeenAt, count });
570
+ }
571
+ }
572
+ return stale.sort((a, b) => a.firstSeenAt - b.firstSeenAt);
573
+ }
574
+ export {
575
+ AUTH_KEYS,
576
+ AUTH_URL_RE,
577
+ BackendSpanEventSchema,
578
+ BrowserEventSchema,
579
+ CIEventSchema,
580
+ ConsoleEventSchema,
581
+ ContextSnapshotSchema,
582
+ DeploymentEventSchema,
583
+ DiagnosticEventSchema,
584
+ LogLevelSchema,
585
+ NetworkEventSchema,
586
+ ProcessExitEventSchema,
587
+ SSEEventSchema,
588
+ TerminalOutputEventSchema,
589
+ TestResultEventSchema,
590
+ WebSocketEventSchema,
591
+ WebSocketFrameSchema,
592
+ getDefaultTenantId,
593
+ getStaleErrors,
594
+ parseBrowser,
595
+ parseOS,
596
+ setBufferSizeGetter,
597
+ setStore,
598
+ store
599
+ };
@@ -0,0 +1,93 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { DATA_DIR, zeroRetentionMode } from "./paths.js";
4
+ import logger from "./logger.js";
5
+ const BYPASS_FILE = path.join(DATA_DIR, "bypass-tracker.json");
6
+ const BLOCK_WINDOW_MS = 6e4;
7
+ const REFINEMENT_THRESHOLD = 5;
8
+ const _recentBlocks = /* @__PURE__ */ new Map();
9
+ let _entries = {};
10
+ let _loaded = false;
11
+ let _testingMode = false;
12
+ function _resetBypassTrackerForTesting() {
13
+ _entries = {};
14
+ _loaded = true;
15
+ _testingMode = true;
16
+ _recentBlocks.clear();
17
+ }
18
+ function load() {
19
+ if (_testingMode || _loaded) return;
20
+ _loaded = true;
21
+ if (!fs.existsSync(BYPASS_FILE)) {
22
+ _entries = {};
23
+ return;
24
+ }
25
+ try {
26
+ const raw = JSON.parse(fs.readFileSync(BYPASS_FILE, "utf8"));
27
+ if (raw?.version === 1 && raw.entries && typeof raw.entries === "object") {
28
+ _entries = raw.entries;
29
+ }
30
+ } catch {
31
+ _entries = {};
32
+ }
33
+ }
34
+ function persist() {
35
+ if (zeroRetentionMode() || _testingMode) return;
36
+ try {
37
+ fs.mkdirSync(DATA_DIR, { recursive: true, mode: 448 });
38
+ const tmp = `${BYPASS_FILE}.tmp.${process.pid}`;
39
+ fs.writeFileSync(tmp, JSON.stringify({ version: 1, entries: _entries }), "utf8");
40
+ fs.renameSync(tmp, BYPASS_FILE);
41
+ } catch (err) {
42
+ logger.warn({ err }, "bypass-tracker: persist failed");
43
+ }
44
+ }
45
+ function trackBlock(toolName, triggeredRules) {
46
+ _recentBlocks.set(toolName, { triggeredRules, blockedAt: Date.now() });
47
+ }
48
+ function trackSuccessfulCall(toolName) {
49
+ load();
50
+ const recent = _recentBlocks.get(toolName);
51
+ if (!recent) return;
52
+ const elapsed = Date.now() - recent.blockedAt;
53
+ if (elapsed > BLOCK_WINDOW_MS) {
54
+ _recentBlocks.delete(toolName);
55
+ return;
56
+ }
57
+ _recentBlocks.delete(toolName);
58
+ logger.info(
59
+ { toolName, ruleIds: recent.triggeredRules, elapsedMs: elapsed },
60
+ "bypass-tracker: block-then-bypass detected"
61
+ );
62
+ for (const ruleId of recent.triggeredRules) {
63
+ const entry = _entries[ruleId] ?? { ruleId, count: 0, toolNames: [], lastBypassAt: 0 };
64
+ entry.count++;
65
+ entry.lastBypassAt = Date.now();
66
+ if (!entry.toolNames.includes(toolName)) entry.toolNames.push(toolName);
67
+ _entries[ruleId] = entry;
68
+ }
69
+ persist();
70
+ }
71
+ function getRefinementCandidates() {
72
+ load();
73
+ return Object.values(_entries).filter((e) => e.count >= REFINEMENT_THRESHOLD).sort((a, b) => b.count - a.count).map((e) => ({
74
+ ruleId: e.ruleId,
75
+ bypassCount: e.count,
76
+ toolNames: e.toolNames,
77
+ lastBypassAt: e.lastBypassAt,
78
+ recommendation: `Rule "${e.ruleId}" has been blocked then immediately bypassed ${e.count} times. Consider narrowing its conditions in ~/.mergen/enterprise-policy.json to reduce false positives.`
79
+ }));
80
+ }
81
+ function getBypassStats() {
82
+ load();
83
+ const totalBypasses = Object.values(_entries).reduce((sum, e) => sum + e.count, 0);
84
+ return { totalBypasses, uniqueRules: Object.keys(_entries).length };
85
+ }
86
+ export {
87
+ REFINEMENT_THRESHOLD,
88
+ _resetBypassTrackerForTesting,
89
+ getBypassStats,
90
+ getRefinementCandidates,
91
+ trackBlock,
92
+ trackSuccessfulCall
93
+ };