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.
- package/LICENSE +57 -0
- package/README.md +96 -0
- package/dist/__stubs__/calibration.js +471 -0
- package/dist/__stubs__/causal.js +601 -0
- package/dist/__stubs__/closed-source.js +150 -0
- package/dist/__stubs__/license.js +35 -0
- package/dist/__stubs__/plans.js +197 -0
- package/dist/app.js +467 -0
- package/dist/cli.js +250 -0
- package/dist/commands/agent-identity.js +79 -0
- package/dist/commands/gate.js +107 -0
- package/dist/commands/github.js +632 -0
- package/dist/commands/incident.js +644 -0
- package/dist/commands/policy.js +710 -0
- package/dist/commands/scan.js +243 -0
- package/dist/commands/setup.js +1218 -0
- package/dist/commands/shared.js +109 -0
- package/dist/commands/team.js +809 -0
- package/dist/datadog/blame-attribution.js +228 -0
- package/dist/datadog/client.js +159 -0
- package/dist/datadog/compactor.js +169 -0
- package/dist/datadog/fingerprinter.js +34 -0
- package/dist/datadog/incident-state.js +21 -0
- package/dist/datadog/line-matcher.js +56 -0
- package/dist/datadog/memory-store.js +399 -0
- package/dist/datadog/otel-trace.js +67 -0
- package/dist/index.js +505 -0
- package/dist/intelligence/action-risk.js +132 -0
- package/dist/intelligence/activity-feed.js +40 -0
- package/dist/intelligence/agent-identity.js +115 -0
- package/dist/intelligence/agent-pipeline.js +329 -0
- package/dist/intelligence/agent-profiles.js +81 -0
- package/dist/intelligence/ai-commit.js +16 -0
- package/dist/intelligence/approval-analytics.js +42 -0
- package/dist/intelligence/approval-events.js +5 -0
- package/dist/intelligence/arch-boundaries.js +94 -0
- package/dist/intelligence/arch-graph.js +117 -0
- package/dist/intelligence/autonomy.js +397 -0
- package/dist/intelligence/azure-broker.js +57 -0
- package/dist/intelligence/azure-proxy-sessions.js +49 -0
- package/dist/intelligence/baseline.js +1 -0
- package/dist/intelligence/behavior-baseline.js +134 -0
- package/dist/intelligence/billing.js +1 -0
- package/dist/intelligence/blast-radius.js +224 -0
- package/dist/intelligence/bypass.js +157 -0
- package/dist/intelligence/calibration-classifier.js +77 -0
- package/dist/intelligence/calibration-git-sync.js +74 -0
- package/dist/intelligence/calibration.js +1 -0
- package/dist/intelligence/cascade-detector.js +58 -0
- package/dist/intelligence/case-study-generator.js +126 -0
- package/dist/intelligence/causal-graph.js +126 -0
- package/dist/intelligence/causal.js +1 -0
- package/dist/intelligence/change-risk.js +102 -0
- package/dist/intelligence/compliance-report.js +134 -0
- package/dist/intelligence/confidence-report.js +85 -0
- package/dist/intelligence/corpus-to-policy.js +140 -0
- package/dist/intelligence/credential-broker.js +112 -0
- package/dist/intelligence/credential-misuse.js +39 -0
- package/dist/intelligence/debug-sessions.js +108 -0
- package/dist/intelligence/decision-escalation.js +36 -0
- package/dist/intelligence/decision-memory.js +47 -0
- package/dist/intelligence/decision-similarity.js +85 -0
- package/dist/intelligence/default-safety-tests.js +53 -0
- package/dist/intelligence/degradation-watcher.js +108 -0
- package/dist/intelligence/detector-plugins.js +70 -0
- package/dist/intelligence/detectors.js +1 -0
- package/dist/intelligence/device-auth.js +138 -0
- package/dist/intelligence/diff-size.js +42 -0
- package/dist/intelligence/ebpf-verify.js +45 -0
- package/dist/intelligence/enterprise-policy-engine.js +1122 -0
- package/dist/intelligence/error-fingerprint.js +1 -0
- package/dist/intelligence/execution-gate.js +100 -0
- package/dist/intelligence/execution-mode.js +22 -0
- package/dist/intelligence/gate-analytics.js +256 -0
- package/dist/intelligence/gate-decision.js +64 -0
- package/dist/intelligence/gcp-broker.js +50 -0
- package/dist/intelligence/git-adr-sync.js +207 -0
- package/dist/intelligence/hitl-hold.js +460 -0
- package/dist/intelligence/hypothesis-history.js +93 -0
- package/dist/intelligence/idp-identity.js +83 -0
- package/dist/intelligence/impl-critic.js +216 -0
- package/dist/intelligence/incident-autopilot.js +622 -0
- package/dist/intelligence/incident-replay.js +162 -0
- package/dist/intelligence/incident-result-cache.js +33 -0
- package/dist/intelligence/incident-similarity.js +58 -0
- package/dist/intelligence/infra-detectors.js +312 -0
- package/dist/intelligence/license.js +185 -0
- package/dist/intelligence/llm-spokesperson.js +62 -0
- package/dist/intelligence/mcp-prompts.js +320 -0
- package/dist/intelligence/mcp-resources.js +171 -0
- package/dist/intelligence/normalize.js +108 -0
- package/dist/intelligence/notifications.js +83 -0
- package/dist/intelligence/oidc-issuer.js +87 -0
- package/dist/intelligence/override-corpus.js +597 -0
- package/dist/intelligence/planning-gate.js +84 -0
- package/dist/intelligence/plans.js +223 -0
- package/dist/intelligence/platt-scaling.js +164 -0
- package/dist/intelligence/policy-proposals.js +84 -0
- package/dist/intelligence/policy-suggester.js +161 -0
- package/dist/intelligence/policy-sync.js +103 -0
- package/dist/intelligence/policy-test-runner.js +35 -0
- package/dist/intelligence/postmortem-parser.js +181 -0
- package/dist/intelligence/postmortem-retrieval.js +224 -0
- package/dist/intelligence/postmortem-store.js +596 -0
- package/dist/intelligence/pr-commenter.js +81 -0
- package/dist/intelligence/pr-policy-gate.js +38 -0
- package/dist/intelligence/pr-shadow-analyzer.js +229 -0
- package/dist/intelligence/process-tree.js +108 -0
- package/dist/intelligence/prompts.js +1 -0
- package/dist/intelligence/replay.js +38 -0
- package/dist/intelligence/repro-steps.js +1 -0
- package/dist/intelligence/resource-extractors.js +0 -0
- package/dist/intelligence/resource-file-context.js +138 -0
- package/dist/intelligence/risk-score.js +22 -0
- package/dist/intelligence/rollback.js +137 -0
- package/dist/intelligence/runbook-updater.js +143 -0
- package/dist/intelligence/sandbox.js +194 -0
- package/dist/intelligence/script-trust.js +104 -0
- package/dist/intelligence/session-metrics.js +67 -0
- package/dist/intelligence/session-threat-tracker.js +231 -0
- package/dist/intelligence/shadow-digest-cron.js +81 -0
- package/dist/intelligence/shadow-log.js +284 -0
- package/dist/intelligence/shell-ast.js +145 -0
- package/dist/intelligence/siem-forward.js +104 -0
- package/dist/intelligence/slack-digest.js +151 -0
- package/dist/intelligence/slack-override-loop.js +217 -0
- package/dist/intelligence/slack-routing.js +67 -0
- package/dist/intelligence/slack.js +900 -0
- package/dist/intelligence/sql-ast.js +31 -0
- package/dist/intelligence/team.js +1 -0
- package/dist/intelligence/telemetry.js +76 -0
- package/dist/intelligence/threshold-optimizer.js +75 -0
- package/dist/intelligence/token-budget.js +66 -0
- package/dist/intelligence/tool-guard.js +708 -0
- package/dist/intelligence/tool-manifest 2.js +74 -0
- package/dist/intelligence/tool-manifest.js +125 -0
- package/dist/intelligence/tools-analysis.js +857 -0
- package/dist/intelligence/tools-arch.js +152 -0
- package/dist/intelligence/tools-autonomy.js +495 -0
- package/dist/intelligence/tools-blast-radius.js +140 -0
- package/dist/intelligence/tools-browser.js +431 -0
- package/dist/intelligence/tools-change-timeline.js +154 -0
- package/dist/intelligence/tools-credentials.js +117 -0
- package/dist/intelligence/tools-datadog.js +233 -0
- package/dist/intelligence/tools-debug-sessions.js +209 -0
- package/dist/intelligence/tools-discovery.js +284 -0
- package/dist/intelligence/tools-infra.js +612 -0
- package/dist/intelligence/tools-intent.js +128 -0
- package/dist/intelligence/tools-memory.js +514 -0
- package/dist/intelligence/tools-runbook.js +951 -0
- package/dist/intelligence/tools-sessions.js +107 -0
- package/dist/intelligence/tools-state.js +232 -0
- package/dist/intelligence/tools-utility.js +430 -0
- package/dist/intelligence/tools-validate.js +215 -0
- package/dist/intelligence/tools.js +1 -0
- package/dist/intelligence/trace-context.js +11 -0
- package/dist/intelligence/unclassified-clusters.js +87 -0
- package/dist/intelligence/usage.js +195 -0
- package/dist/routes/active-authors.js +63 -0
- package/dist/routes/activity-feed.js +21 -0
- package/dist/routes/adr.js +117 -0
- package/dist/routes/agent-activity.js +184 -0
- package/dist/routes/agent-blunders.js +163 -0
- package/dist/routes/agents.js +175 -0
- package/dist/routes/api-keys.js +65 -0
- package/dist/routes/arch.js +75 -0
- package/dist/routes/audit-export.js +93 -0
- package/dist/routes/billing-dashboard.js +158 -0
- package/dist/routes/billing-outcome.js +77 -0
- package/dist/routes/calibration.js +305 -0
- package/dist/routes/ci-gate-history.js +20 -0
- package/dist/routes/ci-gate.js +232 -0
- package/dist/routes/ci.js +293 -0
- package/dist/routes/compliance-report.js +24 -0
- package/dist/routes/confidence.js +30 -0
- package/dist/routes/credentials.js +119 -0
- package/dist/routes/dashboard.js +1407 -0
- package/dist/routes/decision-memory.js +42 -0
- package/dist/routes/demo.js +844 -0
- package/dist/routes/device-auth-stub.js +173 -0
- package/dist/routes/explain-why.js +39 -0
- package/dist/routes/gate-analytics.js +213 -0
- package/dist/routes/gate.js +28 -0
- package/dist/routes/github-webhook.js +364 -0
- package/dist/routes/habituation.js +30 -0
- package/dist/routes/health-integrations.js +243 -0
- package/dist/routes/heartbeats.js +45 -0
- package/dist/routes/hitl.js +364 -0
- package/dist/routes/impact-report.js +645 -0
- package/dist/routes/incident-webhook.js +63 -0
- package/dist/routes/incidents.js +366 -0
- package/dist/routes/layers.js +62 -0
- package/dist/routes/license.js +187 -0
- package/dist/routes/oidc-issuer.js +30 -0
- package/dist/routes/onboarding.js +170 -0
- package/dist/routes/otel.js +67 -0
- package/dist/routes/otlp-receiver.js +179 -0
- package/dist/routes/overrides.js +241 -0
- package/dist/routes/pagerduty.js +258 -0
- package/dist/routes/policies.js +809 -0
- package/dist/routes/policy-nl.js +169 -0
- package/dist/routes/postmortem.js +102 -0
- package/dist/routes/pr-shadow.js +47 -0
- package/dist/routes/rbac.js +61 -0
- package/dist/routes/replay.js +17 -0
- package/dist/routes/risk-report.js +164 -0
- package/dist/routes/runbooks.js +125 -0
- package/dist/routes/safety-test.js +174 -0
- package/dist/routes/sdk.js +222 -0
- package/dist/routes/sensor.js +819 -0
- package/dist/routes/sentry.js +140 -0
- package/dist/routes/sessions.js +43 -0
- package/dist/routes/setup-ui.js +594 -0
- package/dist/routes/shadow-report.js +107 -0
- package/dist/routes/slack-routing.js +171 -0
- package/dist/routes/team-usage.js +71 -0
- package/dist/routes/telemetry.js +28 -0
- package/dist/routes/tenants.js +199 -0
- package/dist/routes/tickets.js +167 -0
- package/dist/routes/validate.js +13 -0
- package/dist/routes/war-room.js +113 -0
- package/dist/scripts/check-arch.js +22 -0
- package/dist/seeds/community-corpus.js +176 -0
- package/dist/seeds/corpus.js +1224 -0
- package/dist/sensor/action-ledger.js +327 -0
- package/dist/sensor/adr-store.js +140 -0
- package/dist/sensor/agent-blunder-store.js +370 -0
- package/dist/sensor/agent-context-store.js +225 -0
- package/dist/sensor/agent-memory-store.js +231 -0
- package/dist/sensor/audit-fetch.js +23 -0
- package/dist/sensor/audit-log.js +273 -0
- package/dist/sensor/buffer-schemas.js +225 -0
- package/dist/sensor/buffer.js +599 -0
- package/dist/sensor/bypass-tracker.js +93 -0
- package/dist/sensor/ci-gate-history.js +70 -0
- package/dist/sensor/cloud-auth.js +183 -0
- package/dist/sensor/commit-context-store.js +235 -0
- package/dist/sensor/docker-log-stream.js +164 -0
- package/dist/sensor/docker-monitor.js +122 -0
- package/dist/sensor/extended-buffer.js +46 -0
- package/dist/sensor/feedback-token.js +41 -0
- package/dist/sensor/file-lock.js +6 -0
- package/dist/sensor/fs-watcher.js +106 -0
- package/dist/sensor/gate-heartbeat.js +122 -0
- package/dist/sensor/git-suspect.js +136 -0
- package/dist/sensor/habituation-store.js +77 -0
- package/dist/sensor/heartbeat-monitor.js +133 -0
- package/dist/sensor/incident-store.js +340 -0
- package/dist/sensor/infra-normalizer.js +513 -0
- package/dist/sensor/ingest.js +339 -0
- package/dist/sensor/jest-reporter.js +52 -0
- package/dist/sensor/k8s-events.js +132 -0
- package/dist/sensor/layer2-store.js +111 -0
- package/dist/sensor/layer3-store.js +230 -0
- package/dist/sensor/layer4-store.js +147 -0
- package/dist/sensor/logger.js +13 -0
- package/dist/sensor/otel-exporter.js +161 -0
- package/dist/sensor/paths.js +71 -0
- package/dist/sensor/policy-history.js +147 -0
- package/dist/sensor/pr-shadow-store.js +83 -0
- package/dist/sensor/process-watcher.js +162 -0
- package/dist/sensor/rbac.js +92 -0
- package/dist/sensor/redact.js +114 -0
- package/dist/sensor/redis-store.js +191 -0
- package/dist/sensor/route-reachability.js +64 -0
- package/dist/sensor/security-utils.js +18 -0
- package/dist/sensor/service-graph.js +170 -0
- package/dist/sensor/service-topology.js +227 -0
- package/dist/sensor/session-history.js +74 -0
- package/dist/sensor/session-persist.js +38 -0
- package/dist/sensor/shadow-promote.js +122 -0
- package/dist/sensor/sourcemap.js +281 -0
- package/dist/sensor/sqlite-store.js +205 -0
- package/dist/sensor/sso.js +164 -0
- package/dist/sensor/vitest-reporter.js +65 -0
- package/dist/sensor/watcher.js +48 -0
- package/dist/storage/interfaces.js +0 -0
- package/dist/storage/pg/pg-action-ledger.js +138 -0
- package/dist/storage/pg/pg-approval-store.js +107 -0
- package/dist/storage/pg/pg-blunder-store.js +193 -0
- package/dist/storage/pg/pg-ci-gate-history.js +83 -0
- package/dist/storage/pg/pg-client.js +24 -0
- package/dist/storage/pg/pg-event-store.js +63 -0
- package/dist/storage/pg/pg-incident-store.js +190 -0
- package/dist/storage/pg/pg-migrations.js +26 -0
- package/dist/storage/pg/pg-override-corpus.js +447 -0
- package/dist/storage/pg/pg-shadow-log.js +129 -0
- package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
- package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
- package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
- package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
- package/dist/storage/sqlite/sqlite-event-store.js +29 -0
- package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
- package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
- package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
- package/dist/storage/store-factory.js +55 -0
- package/dist/storage/store-registry.js +31 -0
- package/dist/update-checker.js +107 -0
- package/dist/workers/autopilot-worker.js +28 -0
- package/dist/workers/notification-worker.js +28 -0
- package/dist/workers/queues.js +58 -0
- package/dist/workers/validation-worker.js +24 -0
- package/dist/workers/worker-registry.js +18 -0
- package/package.json +123 -0
- package/sdk/mergen-inject.js +260 -0
- package/sdk/node.js +313 -0
- package/sdk/vite-plugin.ts +57 -0
- package/sdk/webpack-plugin.js +106 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { store } from "../sensor/buffer.js";
|
|
3
|
+
import logger from "../sensor/logger.js";
|
|
4
|
+
const ENABLED = process.env.MERGEN_DEGRADATION_WATCH !== "false";
|
|
5
|
+
const SEVERE_ERR_COUNT = Math.max(1, parseInt(process.env.MERGEN_SEVERE_ERR_COUNT ?? "10", 10));
|
|
6
|
+
const SEVERE_WINDOW_MS = Math.max(6e4, parseInt(process.env.MERGEN_SEVERE_WINDOW_MS ?? "300000", 10));
|
|
7
|
+
const SEVERE_DURATION_MS = Math.max(6e4, parseInt(process.env.MERGEN_SEVERE_DURATION_MS ?? "300000", 10));
|
|
8
|
+
const POLL_INTERVAL_MS = 6e4;
|
|
9
|
+
let _degradedSince = null;
|
|
10
|
+
let _notifiedForDegradedSince = null;
|
|
11
|
+
let _lastState = {
|
|
12
|
+
degraded: false,
|
|
13
|
+
degradedSince: null,
|
|
14
|
+
errorCount: 0,
|
|
15
|
+
durationLabel: null
|
|
16
|
+
};
|
|
17
|
+
function getDegradationState() {
|
|
18
|
+
return _lastState;
|
|
19
|
+
}
|
|
20
|
+
function humanDuration(ms) {
|
|
21
|
+
const minutes = Math.floor(ms / 6e4);
|
|
22
|
+
if (minutes < 60) return `${minutes} minute${minutes !== 1 ? "s" : ""}`;
|
|
23
|
+
const hours = Math.floor(minutes / 60);
|
|
24
|
+
const rem = minutes % 60;
|
|
25
|
+
return rem > 0 ? `${hours}h ${rem}m` : `${hours} hour${hours !== 1 ? "s" : ""}`;
|
|
26
|
+
}
|
|
27
|
+
function fireNotification(errorCount, durationMs) {
|
|
28
|
+
const dur = humanDuration(durationMs);
|
|
29
|
+
const body = `${errorCount} errors in the last window \u2014 sustained for ${dur}. Check Mergen.`;
|
|
30
|
+
const title = "Mergen \u2014 Degradation Alert";
|
|
31
|
+
process.stderr.write(`\x07
|
|
32
|
+
[MERGEN] ${title}: ${body}
|
|
33
|
+
`);
|
|
34
|
+
const platform = process.platform;
|
|
35
|
+
if (platform === "darwin") {
|
|
36
|
+
const escaped = body.replace(/'/g, "'\\''");
|
|
37
|
+
exec(`osascript -e 'display notification "${escaped}" with title "${title}" sound name "Submarine"'`, (err) => {
|
|
38
|
+
if (err) logger.debug({ err }, "degradation-watcher: osascript notification failed (stderr fallback used)");
|
|
39
|
+
});
|
|
40
|
+
} else if (platform === "linux") {
|
|
41
|
+
const escaped = body.replace(/"/g, '\\"');
|
|
42
|
+
exec(`notify-send "${title}" "${escaped}" --urgency=critical`, (err) => {
|
|
43
|
+
if (err) logger.debug({ err }, "degradation-watcher: notify-send failed (stderr fallback used)");
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function poll() {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const blastRadius = store.getBlastRadius({ since: now - SEVERE_WINDOW_MS });
|
|
50
|
+
const errCount = blastRadius.errorCount;
|
|
51
|
+
if (errCount >= SEVERE_ERR_COUNT) {
|
|
52
|
+
if (_degradedSince === null) {
|
|
53
|
+
_degradedSince = now;
|
|
54
|
+
logger.debug({ errCount, threshold: SEVERE_ERR_COUNT }, "degradation-watcher: severe degradation started");
|
|
55
|
+
}
|
|
56
|
+
const sustainedMs = now - _degradedSince;
|
|
57
|
+
const label = humanDuration(now - _degradedSince);
|
|
58
|
+
_lastState = {
|
|
59
|
+
degraded: true,
|
|
60
|
+
degradedSince: _degradedSince,
|
|
61
|
+
errorCount: errCount,
|
|
62
|
+
durationLabel: label
|
|
63
|
+
};
|
|
64
|
+
if (sustainedMs >= SEVERE_DURATION_MS && _notifiedForDegradedSince !== _degradedSince) {
|
|
65
|
+
_notifiedForDegradedSince = _degradedSince;
|
|
66
|
+
logger.info({ errCount, sustainedMs, threshold: SEVERE_ERR_COUNT }, "degradation-watcher: sustained severe degradation \u2014 firing notification");
|
|
67
|
+
fireNotification(errCount, sustainedMs);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
if (_degradedSince !== null) {
|
|
71
|
+
logger.debug({ previousDegradedSince: _degradedSince }, "degradation-watcher: degradation cleared");
|
|
72
|
+
}
|
|
73
|
+
_degradedSince = null;
|
|
74
|
+
_notifiedForDegradedSince = null;
|
|
75
|
+
_lastState = { degraded: false, degradedSince: null, errorCount: 0, durationLabel: null };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
let _timer = null;
|
|
79
|
+
function startDegradationWatcher() {
|
|
80
|
+
if (!ENABLED) {
|
|
81
|
+
logger.debug("degradation-watcher: disabled via MERGEN_DEGRADATION_WATCH=false");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (_timer !== null) return;
|
|
85
|
+
logger.debug(
|
|
86
|
+
{ severeErrCount: SEVERE_ERR_COUNT, severeWindowMs: SEVERE_WINDOW_MS, severeDurationMs: SEVERE_DURATION_MS },
|
|
87
|
+
"degradation-watcher: started"
|
|
88
|
+
);
|
|
89
|
+
_timer = setInterval(poll, POLL_INTERVAL_MS);
|
|
90
|
+
_timer.unref();
|
|
91
|
+
}
|
|
92
|
+
function stopDegradationWatcher() {
|
|
93
|
+
if (_timer) {
|
|
94
|
+
clearInterval(_timer);
|
|
95
|
+
_timer = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function _resetWatcherForTesting() {
|
|
99
|
+
_degradedSince = null;
|
|
100
|
+
_notifiedForDegradedSince = null;
|
|
101
|
+
_lastState = { degraded: false, degradedSince: null, errorCount: 0, durationLabel: null };
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
_resetWatcherForTesting,
|
|
105
|
+
getDegradationState,
|
|
106
|
+
startDegradationWatcher,
|
|
107
|
+
stopDegradationWatcher
|
|
108
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
import { DATA_DIR } from "../sensor/paths.js";
|
|
5
|
+
import logger from "../sensor/logger.js";
|
|
6
|
+
const PLUGINS_DIR = path.join(DATA_DIR, "detectors");
|
|
7
|
+
let _plugins = [];
|
|
8
|
+
let _loaded = false;
|
|
9
|
+
async function loadPlugins() {
|
|
10
|
+
if (_loaded) return;
|
|
11
|
+
_loaded = true;
|
|
12
|
+
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
13
|
+
try {
|
|
14
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true, mode: 448 });
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let files;
|
|
20
|
+
try {
|
|
21
|
+
files = fs.readdirSync(PLUGINS_DIR).filter((f) => f.endsWith(".js"));
|
|
22
|
+
} catch (err) {
|
|
23
|
+
logger.warn({ err }, "detector-plugins: could not read detectors directory");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const filePath = path.join(PLUGINS_DIR, file);
|
|
28
|
+
try {
|
|
29
|
+
const url = pathToFileURL(filePath).href;
|
|
30
|
+
const mod = await import(url);
|
|
31
|
+
const fn = mod.detect ?? mod.default;
|
|
32
|
+
if (typeof fn !== "function") {
|
|
33
|
+
logger.warn({ file }, "detector-plugins: no detect function exported \u2014 skipping");
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const pluginName = path.basename(file, ".js");
|
|
37
|
+
const wrapped = Object.assign(
|
|
38
|
+
(events) => fn(events),
|
|
39
|
+
{ name: `plugin:${pluginName}` }
|
|
40
|
+
);
|
|
41
|
+
_plugins.push(wrapped);
|
|
42
|
+
logger.info({ plugin: pluginName }, "detector-plugins: loaded");
|
|
43
|
+
} catch (err) {
|
|
44
|
+
logger.warn({ err, file }, "detector-plugins: failed to load plugin \u2014 skipping");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (_plugins.length > 0) {
|
|
48
|
+
logger.info({ count: _plugins.length, dir: PLUGINS_DIR }, "detector-plugins: plugins ready");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getPluginDetectors() {
|
|
52
|
+
return _plugins;
|
|
53
|
+
}
|
|
54
|
+
function runPlugins(events) {
|
|
55
|
+
const results = [];
|
|
56
|
+
for (const plugin of _plugins) {
|
|
57
|
+
try {
|
|
58
|
+
const h = plugin(events);
|
|
59
|
+
if (h) results.push(h);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
logger.warn({ err, plugin: plugin.name ?? "unknown" }, "detector-plugins: plugin threw");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
getPluginDetectors,
|
|
68
|
+
loadPlugins,
|
|
69
|
+
runPlugins
|
|
70
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../__stubs__/closed-source.js';
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const DEFAULT_DEPS = {
|
|
2
|
+
fetch: (...args) => fetch(...args),
|
|
3
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
4
|
+
now: () => Date.now()
|
|
5
|
+
};
|
|
6
|
+
const DEFAULT_INTERVAL_SECONDS = 5;
|
|
7
|
+
const MAX_EXPIRES_IN_SECONDS = 30 * 60;
|
|
8
|
+
class DeviceAuthError extends Error {
|
|
9
|
+
constructor(message, reason) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.reason = reason;
|
|
12
|
+
this.name = "DeviceAuthError";
|
|
13
|
+
}
|
|
14
|
+
reason;
|
|
15
|
+
}
|
|
16
|
+
function normalizeApiUrl(apiUrl) {
|
|
17
|
+
return apiUrl.replace(/\/+$/, "");
|
|
18
|
+
}
|
|
19
|
+
async function requestDeviceCode(apiUrl, deps = {}) {
|
|
20
|
+
const d = { ...DEFAULT_DEPS, ...deps };
|
|
21
|
+
let res;
|
|
22
|
+
try {
|
|
23
|
+
res = await d.fetch(`${normalizeApiUrl(apiUrl)}/v1/device/code`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "content-type": "application/json" },
|
|
26
|
+
body: JSON.stringify({ clientId: "mergen-cli" })
|
|
27
|
+
});
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throw new DeviceAuthError(
|
|
30
|
+
`Could not reach the activation server at ${apiUrl}: ${err.message}`,
|
|
31
|
+
"network"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
throw new DeviceAuthError(`Device code request failed (HTTP ${res.status})`, "protocol");
|
|
36
|
+
}
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
const userCode = data.user_code ?? data.userCode;
|
|
39
|
+
const deviceCode = data.device_code ?? data.deviceCode;
|
|
40
|
+
const verificationUri = data.verification_uri ?? data.verificationUri;
|
|
41
|
+
if (!deviceCode || !userCode || !verificationUri) {
|
|
42
|
+
throw new DeviceAuthError("Malformed device code response from activation server", "protocol");
|
|
43
|
+
}
|
|
44
|
+
const complete = data.verification_uri_complete ?? data.verificationUriComplete;
|
|
45
|
+
const interval = Number(data.interval ?? DEFAULT_INTERVAL_SECONDS) || DEFAULT_INTERVAL_SECONDS;
|
|
46
|
+
const expiresIn = Math.min(Number(data.expires_in ?? data.expiresIn ?? MAX_EXPIRES_IN_SECONDS) || MAX_EXPIRES_IN_SECONDS, MAX_EXPIRES_IN_SECONDS);
|
|
47
|
+
return {
|
|
48
|
+
deviceCode,
|
|
49
|
+
userCode,
|
|
50
|
+
verificationUri,
|
|
51
|
+
verificationUriComplete: complete ?? `${verificationUri}?code=${encodeURIComponent(userCode)}`,
|
|
52
|
+
interval: Math.max(1, interval),
|
|
53
|
+
expiresIn: Math.max(1, expiresIn)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function pollDeviceToken(apiUrl, deviceCode, deps = {}) {
|
|
57
|
+
const d = { ...DEFAULT_DEPS, ...deps };
|
|
58
|
+
let res;
|
|
59
|
+
try {
|
|
60
|
+
res = await d.fetch(`${normalizeApiUrl(apiUrl)}/v1/device/token`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: { "content-type": "application/json" },
|
|
63
|
+
body: JSON.stringify({ device_code: deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" })
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new DeviceAuthError(`Poll failed: ${err.message}`, "network");
|
|
67
|
+
}
|
|
68
|
+
const data = await res.json().catch(() => ({}));
|
|
69
|
+
const status = data.status?.toLowerCase();
|
|
70
|
+
const error = data.error?.toLowerCase();
|
|
71
|
+
if (status === "approved" || res.ok && (data.key || data.access_token)) {
|
|
72
|
+
const key = data.key ?? data.access_token;
|
|
73
|
+
if (!key) throw new DeviceAuthError("Approved but no license key returned", "protocol");
|
|
74
|
+
return {
|
|
75
|
+
status: "approved",
|
|
76
|
+
activation: {
|
|
77
|
+
key,
|
|
78
|
+
planId: data.planId ?? data.plan_id,
|
|
79
|
+
email: data.email ?? void 0
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (status === "denied" || error === "access_denied") return { status: "denied" };
|
|
84
|
+
if (status === "expired" || error === "expired_token") return { status: "expired" };
|
|
85
|
+
if (error === "slow_down") return { status: "slow_down" };
|
|
86
|
+
if (error === "authorization_pending" || status === "pending") return { status: "pending" };
|
|
87
|
+
if (error) throw new DeviceAuthError(`Activation server rejected the request (${error}).`, "protocol");
|
|
88
|
+
return { status: "pending" };
|
|
89
|
+
}
|
|
90
|
+
async function runDeviceAuthorization(apiUrl, cb = {}, deps = {}) {
|
|
91
|
+
const d = { ...DEFAULT_DEPS, ...deps };
|
|
92
|
+
const code = await requestDeviceCode(apiUrl, d);
|
|
93
|
+
cb.onPrompt?.(code);
|
|
94
|
+
const startedAt = d.now();
|
|
95
|
+
const deadline = startedAt + code.expiresIn * 1e3;
|
|
96
|
+
let intervalMs = code.interval * 1e3;
|
|
97
|
+
await d.sleep(intervalMs);
|
|
98
|
+
for (; ; ) {
|
|
99
|
+
if (d.now() >= deadline) {
|
|
100
|
+
throw new DeviceAuthError("Login timed out \u2014 the code expired before it was approved.", "expired");
|
|
101
|
+
}
|
|
102
|
+
let result;
|
|
103
|
+
try {
|
|
104
|
+
result = await pollDeviceToken(apiUrl, code.deviceCode, d);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err instanceof DeviceAuthError && err.reason === "network") {
|
|
107
|
+
cb.onPending?.(d.now() - startedAt);
|
|
108
|
+
await d.sleep(intervalMs);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
switch (result.status) {
|
|
114
|
+
case "approved":
|
|
115
|
+
return result.activation;
|
|
116
|
+
case "denied":
|
|
117
|
+
throw new DeviceAuthError("Login was denied in the browser.", "denied");
|
|
118
|
+
case "expired":
|
|
119
|
+
throw new DeviceAuthError("Login timed out \u2014 the code expired before it was approved.", "expired");
|
|
120
|
+
case "slow_down":
|
|
121
|
+
intervalMs += 5e3;
|
|
122
|
+
cb.onPending?.(d.now() - startedAt);
|
|
123
|
+
await d.sleep(intervalMs);
|
|
124
|
+
break;
|
|
125
|
+
case "pending":
|
|
126
|
+
default:
|
|
127
|
+
cb.onPending?.(d.now() - startedAt);
|
|
128
|
+
await d.sleep(intervalMs);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
DeviceAuthError,
|
|
135
|
+
pollDeviceToken,
|
|
136
|
+
requestDeviceCode,
|
|
137
|
+
runDeviceAuthorization
|
|
138
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const LARGE_DIFF_LINES = 500;
|
|
2
|
+
const LARGE_FILE_COUNT = 20;
|
|
3
|
+
function evaluateDiffSize(stats, context) {
|
|
4
|
+
const factors = [];
|
|
5
|
+
let score = 0;
|
|
6
|
+
const totalLines = Math.max(0, stats.additions) + Math.max(0, stats.deletions);
|
|
7
|
+
const filesChanged = Math.max(0, stats.filesChanged);
|
|
8
|
+
if (totalLines > LARGE_DIFF_LINES) {
|
|
9
|
+
const over = totalLines - LARGE_DIFF_LINES;
|
|
10
|
+
const delta = Math.min(60, 20 + Math.floor(over / 100) * 10);
|
|
11
|
+
score += delta;
|
|
12
|
+
factors.push({ label: "Large diff", delta, detail: `${totalLines} changed lines (threshold: ${LARGE_DIFF_LINES})` });
|
|
13
|
+
}
|
|
14
|
+
if (filesChanged > LARGE_FILE_COUNT) {
|
|
15
|
+
const delta = Math.min(30, (filesChanged - LARGE_FILE_COUNT) * 2);
|
|
16
|
+
score += delta;
|
|
17
|
+
factors.push({ label: "Many files touched", delta, detail: `${filesChanged} files (threshold: ${LARGE_FILE_COUNT})` });
|
|
18
|
+
}
|
|
19
|
+
if (context.actorIsAi && totalLines > LARGE_DIFF_LINES) {
|
|
20
|
+
const delta = 20;
|
|
21
|
+
score += delta;
|
|
22
|
+
factors.push({ label: "AI-authored large diff", delta, detail: "Agent-authored changes above the size threshold get extra scrutiny" });
|
|
23
|
+
}
|
|
24
|
+
const clampedScore = Math.min(100, score);
|
|
25
|
+
const level = clampedScore >= 70 ? "HIGH" : clampedScore >= 40 ? "MEDIUM" : "LOW";
|
|
26
|
+
const requiresApproval = clampedScore >= 70;
|
|
27
|
+
const recommendation = requiresApproval ? "This diff is unusually large for a single PR \u2014 split it into smaller, independently reviewable changes, or get explicit human sign-off before merging." : level === "MEDIUM" ? "This diff is larger than typical \u2014 a closer review is recommended before merging." : "Diff size is within normal range.";
|
|
28
|
+
return {
|
|
29
|
+
score: clampedScore,
|
|
30
|
+
level,
|
|
31
|
+
requiresApproval,
|
|
32
|
+
filesChanged,
|
|
33
|
+
additions: Math.max(0, stats.additions),
|
|
34
|
+
deletions: Math.max(0, stats.deletions),
|
|
35
|
+
totalLines,
|
|
36
|
+
factors,
|
|
37
|
+
recommendation
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
evaluateDiffSize
|
|
42
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import logger from "../sensor/logger.js";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
function isEbpfVerifyRequested() {
|
|
6
|
+
return process.env.MERGEN_EBPF_VERIFY === "true";
|
|
7
|
+
}
|
|
8
|
+
function getEbpfCapability() {
|
|
9
|
+
const requested = isEbpfVerifyRequested();
|
|
10
|
+
return { requested, platform: process.platform, supported: requested && process.platform === "linux" };
|
|
11
|
+
}
|
|
12
|
+
async function isBpftraceAvailable() {
|
|
13
|
+
try {
|
|
14
|
+
await execFileAsync("bpftrace", ["--version"], { timeout: 3e3 });
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function bpftraceStringLiteral(s) {
|
|
21
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
22
|
+
}
|
|
23
|
+
async function verifyNoExecve(commandHint, windowMs = 2e3) {
|
|
24
|
+
if (!isEbpfVerifyRequested()) return "disabled";
|
|
25
|
+
if (process.platform !== "linux") return "unsupported-platform";
|
|
26
|
+
if (!await isBpftraceAvailable()) return "unavailable";
|
|
27
|
+
const needle = bpftraceStringLiteral(commandHint);
|
|
28
|
+
const script = `tracepoint:syscalls:sys_enter_execve /strcontains(str(args->filename), "${needle}")/ { printf("MATCH\\n"); exit(); }`;
|
|
29
|
+
try {
|
|
30
|
+
const { stdout } = await execFileAsync(
|
|
31
|
+
"timeout",
|
|
32
|
+
[`${Math.ceil(windowMs / 1e3)}s`, "bpftrace", "-e", script],
|
|
33
|
+
{ timeout: windowMs + 1e3 }
|
|
34
|
+
);
|
|
35
|
+
return stdout.includes("MATCH") ? "verified-executed" : "verified-not-executed";
|
|
36
|
+
} catch (err) {
|
|
37
|
+
logger.warn({ err, commandHint }, "ebpf-verify: bpftrace trace failed or timed out \u2014 treating as unverified");
|
|
38
|
+
return "unavailable";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
getEbpfCapability,
|
|
43
|
+
isEbpfVerifyRequested,
|
|
44
|
+
verifyNoExecve
|
|
45
|
+
};
|