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,38 @@
|
|
|
1
|
+
import { evaluateEnterprisePolicy } from "./enterprise-policy-engine.js";
|
|
2
|
+
const MAX_LINES_SCANNED = 2e3;
|
|
3
|
+
function addedLines(patch) {
|
|
4
|
+
return patch.split("\n").filter((line) => line.startsWith("+") && !line.startsWith("+++")).map((line) => line.slice(1).trim()).filter((line) => line.length > 0);
|
|
5
|
+
}
|
|
6
|
+
function evaluatePRDiff(files, service, policyOverride) {
|
|
7
|
+
const blockMatches = [];
|
|
8
|
+
const holdMatches = [];
|
|
9
|
+
let linesScanned = 0;
|
|
10
|
+
let truncated = false;
|
|
11
|
+
let verdict = "pass";
|
|
12
|
+
outer:
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
if (!file.patch) continue;
|
|
15
|
+
for (const line of addedLines(file.patch)) {
|
|
16
|
+
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
17
|
+
truncated = true;
|
|
18
|
+
break outer;
|
|
19
|
+
}
|
|
20
|
+
linesScanned++;
|
|
21
|
+
const result = evaluateEnterprisePolicy(
|
|
22
|
+
{ files: [file.filename], commands: [line], actor: "ci", service },
|
|
23
|
+
policyOverride
|
|
24
|
+
);
|
|
25
|
+
if (result.verdict === "block") {
|
|
26
|
+
verdict = "block";
|
|
27
|
+
blockMatches.push({ file: file.filename, line, verdict: "block", reasons: result.reasons, triggeredRules: result.triggeredRules });
|
|
28
|
+
} else if (result.verdict === "warn") {
|
|
29
|
+
if (verdict !== "block") verdict = "warn";
|
|
30
|
+
holdMatches.push({ file: file.filename, line, verdict: "warn", reasons: result.reasons, triggeredRules: result.triggeredRules });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { verdict, blockMatches, holdMatches, linesScanned, truncated };
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
evaluatePRDiff
|
|
38
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { commitContextStore } from "../sensor/commit-context-store.js";
|
|
3
|
+
import { incidentStore } from "../sensor/incident-store.js";
|
|
4
|
+
import { recordPRShadow } from "../sensor/pr-shadow-store.js";
|
|
5
|
+
import { getOverrideSummary } from "./override-corpus.js";
|
|
6
|
+
import { hybridSearch } from "./postmortem-retrieval.js";
|
|
7
|
+
import { memoryStore } from "../datadog/memory-store.js";
|
|
8
|
+
import logger from "../sensor/logger.js";
|
|
9
|
+
const STOP_WORDS = /* @__PURE__ */ new Set([
|
|
10
|
+
"the",
|
|
11
|
+
"and",
|
|
12
|
+
"for",
|
|
13
|
+
"are",
|
|
14
|
+
"but",
|
|
15
|
+
"not",
|
|
16
|
+
"you",
|
|
17
|
+
"all",
|
|
18
|
+
"can",
|
|
19
|
+
"was",
|
|
20
|
+
"one",
|
|
21
|
+
"have",
|
|
22
|
+
"from",
|
|
23
|
+
"had",
|
|
24
|
+
"has",
|
|
25
|
+
"his",
|
|
26
|
+
"her",
|
|
27
|
+
"its",
|
|
28
|
+
"add",
|
|
29
|
+
"fix",
|
|
30
|
+
"use",
|
|
31
|
+
"update",
|
|
32
|
+
"this",
|
|
33
|
+
"that",
|
|
34
|
+
"with",
|
|
35
|
+
"into",
|
|
36
|
+
"feat",
|
|
37
|
+
"chore",
|
|
38
|
+
"refactor",
|
|
39
|
+
"docs",
|
|
40
|
+
"test"
|
|
41
|
+
]);
|
|
42
|
+
function tokenize(text) {
|
|
43
|
+
return new Set(
|
|
44
|
+
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS.has(w))
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function overlapScore(query, doc) {
|
|
48
|
+
if (query.size === 0 || doc.size === 0) return 0;
|
|
49
|
+
let overlap = 0;
|
|
50
|
+
for (const t of query) {
|
|
51
|
+
if (doc.has(t)) overlap++;
|
|
52
|
+
}
|
|
53
|
+
return overlap / Math.max(query.size, 3);
|
|
54
|
+
}
|
|
55
|
+
function truncate(s, n) {
|
|
56
|
+
return s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
57
|
+
}
|
|
58
|
+
function buildComment(prTitle, matchedContextResults, matchedIncidentResults, operationalConstraints, fileDangerMatches) {
|
|
59
|
+
const lines = [
|
|
60
|
+
"## \u{1F50D} Mergen Context",
|
|
61
|
+
""
|
|
62
|
+
];
|
|
63
|
+
if (fileDangerMatches.length > 0) {
|
|
64
|
+
lines.push("**Files with production incident history:**");
|
|
65
|
+
for (const f of fileDangerMatches.slice(0, 5)) {
|
|
66
|
+
const parts = [];
|
|
67
|
+
if (f.memoryIncidents > 0) {
|
|
68
|
+
parts.push(`${f.memoryIncidents} tracked incident${f.memoryIncidents !== 1 ? "s" : ""}`);
|
|
69
|
+
}
|
|
70
|
+
if (f.postmortems.length > 0) {
|
|
71
|
+
const pm = f.postmortems[0];
|
|
72
|
+
parts.push(`corpus: ${truncate(pm.rootCause, 60)} (${Math.round(pm.confidence * 100)}% conf \xB7 ${pm.service})`);
|
|
73
|
+
}
|
|
74
|
+
lines.push(`- \`${f.file}\` \u2014 ${parts.join("; ")}`);
|
|
75
|
+
}
|
|
76
|
+
lines.push("");
|
|
77
|
+
}
|
|
78
|
+
if (matchedIncidentResults.length > 0) {
|
|
79
|
+
lines.push("**Related incidents from memory:**");
|
|
80
|
+
for (const inc of matchedIncidentResults.slice(0, 3)) {
|
|
81
|
+
const when = inc.daysAgo === 0 ? "today" : `${inc.daysAgo}d ago`;
|
|
82
|
+
const pct = Math.round(inc.confidence * 100);
|
|
83
|
+
const outcome = inc.causallyCorrect ? " \xB7 \u2705 fix confirmed" : inc.resolvedAutonomously ? " \xB7 \u2699\uFE0F auto-resolved" : "";
|
|
84
|
+
lines.push(`- ${inc.hypothesis} _(${pct}% confidence \xB7 ${when}${outcome})_`);
|
|
85
|
+
}
|
|
86
|
+
lines.push("");
|
|
87
|
+
}
|
|
88
|
+
if (matchedContextResults.length > 0) {
|
|
89
|
+
lines.push("**Historical PR context:**");
|
|
90
|
+
for (const ctx of matchedContextResults.slice(0, 3)) {
|
|
91
|
+
const ref = ctx.prNumber ? `#${ctx.prNumber}` : "direct push";
|
|
92
|
+
const when = ctx.daysAgo === 0 ? "today" : `${ctx.daysAgo}d ago`;
|
|
93
|
+
const aiTag = ctx.aiGenerated ? " \xB7 \u{1F916} AI-generated" : "";
|
|
94
|
+
lines.push(`- ${ref}: **${ctx.title}** \u2014 @${ctx.author} _(${when}${aiTag})_`);
|
|
95
|
+
if (ctx.rationale) {
|
|
96
|
+
const firstSentence = ctx.rationale.split(/[.\n]/)[0]?.trim();
|
|
97
|
+
if (firstSentence && firstSentence.length > 10) {
|
|
98
|
+
lines.push(` > ${firstSentence.slice(0, 160)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
}
|
|
104
|
+
if (operationalConstraints.length > 0) {
|
|
105
|
+
lines.push("**Operational constraints on this service:**");
|
|
106
|
+
for (const c of operationalConstraints.slice(0, 3)) {
|
|
107
|
+
lines.push(`- \`${c.tag}\` overridden ${c.count}\xD7 \u2014 top reason: \`${c.topReason}\``);
|
|
108
|
+
}
|
|
109
|
+
lines.push("");
|
|
110
|
+
}
|
|
111
|
+
lines.push("_[Mergen shadow mode \u2014 not posted yet]_");
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
async function analyzePRForShadow(input) {
|
|
115
|
+
const { repo, prNumber, prTitle, prBody, author, branch, action, changedFiles = [] } = input;
|
|
116
|
+
const serviceName = repo.split("/").pop() ?? repo;
|
|
117
|
+
const queryText = [prTitle, prBody ?? ""].join(" ");
|
|
118
|
+
const queryTokens = tokenize(queryText);
|
|
119
|
+
const historicalPRs = commitContextStore.listByRepo(repo, 30);
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
const DAY_MS = 864e5;
|
|
122
|
+
const contextMatches = [];
|
|
123
|
+
for (const ctx of historicalPRs) {
|
|
124
|
+
if (ctx.prNumber === prNumber) continue;
|
|
125
|
+
const docText = [ctx.prTitle ?? "", ctx.prBody ?? ""].join(" ");
|
|
126
|
+
const docTokens = tokenize(docText);
|
|
127
|
+
const score = overlapScore(queryTokens, docTokens);
|
|
128
|
+
if (score >= 0.15) {
|
|
129
|
+
contextMatches.push({
|
|
130
|
+
score,
|
|
131
|
+
title: ctx.prTitle ?? "(no title)",
|
|
132
|
+
author: ctx.author ?? "unknown",
|
|
133
|
+
prNumber: ctx.prNumber,
|
|
134
|
+
daysAgo: Math.floor((now - (ctx.mergedAt ?? ctx.capturedAt)) / DAY_MS),
|
|
135
|
+
rationale: ctx.prBody ?? null,
|
|
136
|
+
aiGenerated: ctx.aiGenerated ?? false
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
contextMatches.sort((a, b) => b.score - a.score);
|
|
141
|
+
const topContextMatches = contextMatches.slice(0, 5);
|
|
142
|
+
const contextScore = topContextMatches.length === 0 ? 0 : Math.min(1, topContextMatches[0].score * 1.5);
|
|
143
|
+
const recentIncidents = incidentStore.list(void 0, 100);
|
|
144
|
+
const incidentMatches = [];
|
|
145
|
+
const serviceNameLower = serviceName.toLowerCase();
|
|
146
|
+
for (const inc of recentIncidents) {
|
|
147
|
+
const serviceMatch = (inc.service ?? "").toLowerCase() === serviceNameLower || (inc.hypothesis ?? "").toLowerCase().includes(serviceNameLower);
|
|
148
|
+
if (serviceMatch) {
|
|
149
|
+
incidentMatches.push({
|
|
150
|
+
hypothesis: inc.hypothesis,
|
|
151
|
+
daysAgo: Math.floor((now - inc.createdAt) / DAY_MS),
|
|
152
|
+
confidence: inc.confidence,
|
|
153
|
+
resolvedAutonomously: inc.resolvedAutonomously ?? false,
|
|
154
|
+
causallyCorrect: inc.causallyCorrect ?? false
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
incidentMatches.sort((a, b) => a.daysAgo - b.daysAgo || b.confidence - a.confidence);
|
|
159
|
+
const topIncidentMatches = incidentMatches.slice(0, 3);
|
|
160
|
+
const incidentScore = topIncidentMatches.length === 0 ? 0 : Math.min(1, topIncidentMatches.length / 3 + topIncidentMatches[0].confidence * 0.5);
|
|
161
|
+
const fileDangerMatches = [];
|
|
162
|
+
for (const file of changedFiles.slice(0, 20)) {
|
|
163
|
+
const base = path.basename(file, path.extname(file));
|
|
164
|
+
const memIncidents = memoryStore.findByFile(file, 3);
|
|
165
|
+
let pmMatches = [];
|
|
166
|
+
if (base.length >= 3) {
|
|
167
|
+
try {
|
|
168
|
+
const pmResults = hybridSearch(base, { topK: 2, maxCorpus: 200 });
|
|
169
|
+
pmMatches = pmResults.map((r) => ({
|
|
170
|
+
rootCause: r.postmortem.rootCause,
|
|
171
|
+
confidence: r.postmortem.confidence,
|
|
172
|
+
service: r.postmortem.service
|
|
173
|
+
}));
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (memIncidents.length > 0 || pmMatches.length > 0) {
|
|
178
|
+
fileDangerMatches.push({ file, memoryIncidents: memIncidents.length, postmortems: pmMatches });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const fileDangerScore = fileDangerMatches.length > 0 ? Math.min(0.3, fileDangerMatches.length * 0.15) : 0;
|
|
182
|
+
const relevanceScore = Math.min(
|
|
183
|
+
1,
|
|
184
|
+
Math.round((contextScore * 0.5 + incidentScore * 0.3 + fileDangerScore * 0.2) * 1e3) / 1e3
|
|
185
|
+
);
|
|
186
|
+
const matchedIncidents = topIncidentMatches.length;
|
|
187
|
+
const matchedContexts = topContextMatches.length;
|
|
188
|
+
const triggeredBy = [];
|
|
189
|
+
if (matchedIncidents > 0) triggeredBy.push("incident_match");
|
|
190
|
+
if (topContextMatches.some((c) => c.score >= 0.3)) triggeredBy.push("context_match");
|
|
191
|
+
if (historicalPRs.length >= 10) triggeredBy.push("rich_history");
|
|
192
|
+
if (fileDangerMatches.length > 0) triggeredBy.push("file_danger_match");
|
|
193
|
+
const wouldHaveShown = matchedIncidents >= 1 && relevanceScore >= 0.7 || fileDangerMatches.length >= 1;
|
|
194
|
+
const overrideSummary = getOverrideSummary().filter((s) => s.services.includes(serviceName)).map((s) => ({
|
|
195
|
+
tag: s.tag,
|
|
196
|
+
topReason: s.dominantReason ?? "other",
|
|
197
|
+
count: s.total
|
|
198
|
+
}));
|
|
199
|
+
const wouldHaveComment = wouldHaveShown ? buildComment(prTitle, topContextMatches, topIncidentMatches, overrideSummary, fileDangerMatches) : null;
|
|
200
|
+
const result = recordPRShadow({
|
|
201
|
+
prId: `${repo}#${prNumber}`,
|
|
202
|
+
repo,
|
|
203
|
+
prNumber,
|
|
204
|
+
prTitle,
|
|
205
|
+
author,
|
|
206
|
+
branch,
|
|
207
|
+
action,
|
|
208
|
+
wouldHaveShown,
|
|
209
|
+
relevanceScore,
|
|
210
|
+
matchedIncidents,
|
|
211
|
+
matchedContexts,
|
|
212
|
+
triggeredBy,
|
|
213
|
+
wouldHaveComment
|
|
214
|
+
});
|
|
215
|
+
logger.info(
|
|
216
|
+
{
|
|
217
|
+
prId: result.prId,
|
|
218
|
+
wouldHaveShown,
|
|
219
|
+
relevanceScore,
|
|
220
|
+
matchedIncidents,
|
|
221
|
+
matchedContexts
|
|
222
|
+
},
|
|
223
|
+
"pr-shadow: analysis complete"
|
|
224
|
+
);
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
export {
|
|
228
|
+
analyzePRForShadow
|
|
229
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import logger from "../sensor/logger.js";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
async function listAllProcessesPosix() {
|
|
6
|
+
const { stdout } = await execFileAsync("ps", ["-A", "-o", "pid=,ppid=,command="]);
|
|
7
|
+
const rows = [];
|
|
8
|
+
for (const line of stdout.split("\n")) {
|
|
9
|
+
const trimmed = line.trim();
|
|
10
|
+
if (!trimmed) continue;
|
|
11
|
+
const match = trimmed.match(/^(\d+)\s+(\d+)\s+(.*)$/);
|
|
12
|
+
if (!match) continue;
|
|
13
|
+
rows.push({ pid: Number(match[1]), ppid: Number(match[2]), command: match[3] });
|
|
14
|
+
}
|
|
15
|
+
return rows;
|
|
16
|
+
}
|
|
17
|
+
async function listAllProcessesWindows() {
|
|
18
|
+
const { stdout } = await execFileAsync("powershell", [
|
|
19
|
+
"-NoProfile",
|
|
20
|
+
"-Command",
|
|
21
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine | ConvertTo-Json -Compress"
|
|
22
|
+
]);
|
|
23
|
+
const parsed = JSON.parse(stdout || "[]");
|
|
24
|
+
const list = Array.isArray(parsed) ? parsed : [parsed];
|
|
25
|
+
return list.map((p) => ({ pid: p.ProcessId, ppid: p.ParentProcessId, command: p.CommandLine ?? "" }));
|
|
26
|
+
}
|
|
27
|
+
async function listAllProcesses() {
|
|
28
|
+
return process.platform === "win32" ? listAllProcessesWindows() : listAllProcessesPosix();
|
|
29
|
+
}
|
|
30
|
+
async function listDescendants(rootPid) {
|
|
31
|
+
let all;
|
|
32
|
+
try {
|
|
33
|
+
all = await listAllProcesses();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
logger.warn({ err }, "process-tree: failed to list processes \u2014 descendant monitoring degraded to no-op");
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const p of all) {
|
|
40
|
+
if (!byParent.has(p.ppid)) byParent.set(p.ppid, []);
|
|
41
|
+
byParent.get(p.ppid).push(p);
|
|
42
|
+
}
|
|
43
|
+
const descendants = [];
|
|
44
|
+
const queue = [rootPid];
|
|
45
|
+
const seen = /* @__PURE__ */ new Set([rootPid]);
|
|
46
|
+
while (queue.length > 0) {
|
|
47
|
+
const pid = queue.shift();
|
|
48
|
+
for (const child of byParent.get(pid) ?? []) {
|
|
49
|
+
if (seen.has(child.pid)) continue;
|
|
50
|
+
seen.add(child.pid);
|
|
51
|
+
descendants.push(child);
|
|
52
|
+
queue.push(child.pid);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return descendants;
|
|
56
|
+
}
|
|
57
|
+
function watchDescendants(rootPid, opts) {
|
|
58
|
+
const intervalMs = opts.intervalMs ?? 250;
|
|
59
|
+
const seenPids = /* @__PURE__ */ new Set();
|
|
60
|
+
let stopped = false;
|
|
61
|
+
const tick = async () => {
|
|
62
|
+
if (stopped) return;
|
|
63
|
+
const descendants = await listDescendants(rootPid);
|
|
64
|
+
for (const d of descendants) {
|
|
65
|
+
if (!seenPids.has(d.pid)) {
|
|
66
|
+
seenPids.add(d.pid);
|
|
67
|
+
opts.onSpawn(d);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!stopped) {
|
|
71
|
+
handle = setTimeout(tick, intervalMs);
|
|
72
|
+
handle.unref?.();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
let handle = setTimeout(tick, intervalMs);
|
|
76
|
+
handle.unref?.();
|
|
77
|
+
return {
|
|
78
|
+
stop: () => {
|
|
79
|
+
stopped = true;
|
|
80
|
+
clearTimeout(handle);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function killTree(rootPid) {
|
|
85
|
+
if (process.platform === "win32") {
|
|
86
|
+
try {
|
|
87
|
+
await execFileAsync("taskkill", ["/PID", String(rootPid), "/T", "/F"]);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const descendants = await listDescendants(rootPid);
|
|
93
|
+
for (const d of [...descendants].reverse()) {
|
|
94
|
+
try {
|
|
95
|
+
process.kill(d.pid, "SIGKILL");
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
process.kill(rootPid, "SIGKILL");
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
killTree,
|
|
106
|
+
listDescendants,
|
|
107
|
+
watchDescendants
|
|
108
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../__stubs__/closed-source.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getGateEvents } from "./gate-analytics.js";
|
|
2
|
+
import { getStores } from "../storage/store-registry.js";
|
|
3
|
+
function fromGateEvent(e) {
|
|
4
|
+
return {
|
|
5
|
+
ts: e.ts,
|
|
6
|
+
source: "gate-event",
|
|
7
|
+
toolName: e.toolName,
|
|
8
|
+
command: e.command,
|
|
9
|
+
verdict: e.verdict,
|
|
10
|
+
blunderType: null,
|
|
11
|
+
triggeredRules: e.triggeredRules,
|
|
12
|
+
service: e.service,
|
|
13
|
+
agentId: e.agentId
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async function buildReplay(traceId) {
|
|
17
|
+
const gateEvents = getGateEvents().filter((e) => e.traceId === traceId);
|
|
18
|
+
const allBlunders = await getStores().blunders.list();
|
|
19
|
+
const blunders = allBlunders.filter((b) => b.traceId === traceId);
|
|
20
|
+
const entries = [
|
|
21
|
+
...gateEvents.map(fromGateEvent),
|
|
22
|
+
...blunders.map((b) => ({
|
|
23
|
+
ts: b.recordedAt,
|
|
24
|
+
source: "blunder-log",
|
|
25
|
+
toolName: null,
|
|
26
|
+
command: b.command,
|
|
27
|
+
verdict: null,
|
|
28
|
+
blunderType: b.blunderType,
|
|
29
|
+
triggeredRules: b.triggeredRules ?? null,
|
|
30
|
+
service: b.service,
|
|
31
|
+
agentId: b.agentId ?? null
|
|
32
|
+
}))
|
|
33
|
+
].sort((a, b) => a.ts - b.ts);
|
|
34
|
+
return { traceId, entries, gateEventCount: gateEvents.length, blunderCount: blunders.length };
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
buildReplay
|
|
38
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../__stubs__/closed-source.js';
|
|
Binary file
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { memoryStore, formatMttr } from "../datadog/memory-store.js";
|
|
4
|
+
import { getActiveIncident } from "../datadog/incident-state.js";
|
|
5
|
+
import { getCurrentTraceContext } from "../datadog/otel-trace.js";
|
|
6
|
+
import { hybridSearch } from "./postmortem-retrieval.js";
|
|
7
|
+
import { getOverrideSummary } from "./override-corpus.js";
|
|
8
|
+
import logger from "../sensor/logger.js";
|
|
9
|
+
function registerFileContextResource(server) {
|
|
10
|
+
const template = new ResourceTemplate("mergen://file/{+path}", { list: void 0 });
|
|
11
|
+
server.resource(
|
|
12
|
+
"file-context",
|
|
13
|
+
template,
|
|
14
|
+
async (uri, variables) => {
|
|
15
|
+
const filePath = decodeURIComponent(String(variables["path"] ?? ""));
|
|
16
|
+
if (!filePath) {
|
|
17
|
+
return {
|
|
18
|
+
contents: [{ uri: uri.href, mimeType: "text/plain", text: "# No file path provided" }]
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const sections = [];
|
|
22
|
+
const active = getActiveIncident();
|
|
23
|
+
if (active) {
|
|
24
|
+
const isImplicated = active.implicatedFile && (active.implicatedFile.endsWith(filePath) || filePath.endsWith(active.implicatedFile.split("/").pop() ?? ""));
|
|
25
|
+
if (isImplicated) {
|
|
26
|
+
sections.push(
|
|
27
|
+
`## \u{1F6A8} ACTIVE INCIDENT \u2014 THIS FILE IS IMPLICATED
|
|
28
|
+
|
|
29
|
+
**Alert:** ${active.alertTitle}
|
|
30
|
+
**Trace:** \`${active.traceId}\`
|
|
31
|
+
**File/Line:** \`${active.implicatedFile}:${active.implicatedLine ?? "?"}\`
|
|
32
|
+
**Fired:** ${new Date(active.firedAt).toISOString()}
|
|
33
|
+
|
|
34
|
+
> Do not refactor this file while the incident is open.
|
|
35
|
+
`
|
|
36
|
+
);
|
|
37
|
+
} else {
|
|
38
|
+
sections.push(
|
|
39
|
+
`## \u26A0\uFE0F ACTIVE INCIDENT (different file)
|
|
40
|
+
|
|
41
|
+
**Alert:** ${active.alertTitle} \u2014 implicated: \`${active.implicatedFile ?? "unknown"}\`
|
|
42
|
+
`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const history = memoryStore.findByFile(filePath, 8);
|
|
47
|
+
if (history.length > 0) {
|
|
48
|
+
const lines = [`## Production Incident History for \`${filePath}\`
|
|
49
|
+
`];
|
|
50
|
+
for (const rec of history) {
|
|
51
|
+
const age = formatAge(rec.firedAt);
|
|
52
|
+
const mttr = rec.mttrMs ? formatMttr(rec.mttrMs) : "open";
|
|
53
|
+
const pr = rec.fixPrUrl ? ` \u2192 [fix PR](${rec.fixPrUrl})` : "";
|
|
54
|
+
lines.push(
|
|
55
|
+
`- **${rec.pdAlertTitle}** (${age} ago, MTTR ${mttr})
|
|
56
|
+
\`${rec.errorType}: ${truncate(rec.errorMessage, 80)}\`${pr}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
sections.push(lines.join("\n"));
|
|
60
|
+
const topFp = history[0]?.fingerprint;
|
|
61
|
+
if (topFp) {
|
|
62
|
+
const bench = memoryStore.benchmarkStats(topFp);
|
|
63
|
+
if (bench && bench.occurrences >= 2) {
|
|
64
|
+
sections.push(
|
|
65
|
+
`## Pattern Stats (fingerprint \`${topFp}\`)
|
|
66
|
+
|
|
67
|
+
- Occurrences: **${bench.occurrences}**
|
|
68
|
+
- p50 MTTR: **${bench.p50MttrMs ? formatMttr(bench.p50MttrMs) : "n/a"}**
|
|
69
|
+
- p90 MTTR: **${bench.p90MttrMs ? formatMttr(bench.p90MttrMs) : "n/a"}**
|
|
70
|
+
- Most common fix: **${bench.topResolutionType}** (${bench.topResolutionCount}\xD7)
|
|
71
|
+
`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
sections.push(`## Production Incident History
|
|
77
|
+
|
|
78
|
+
No incidents on record for \`${filePath}\`.`);
|
|
79
|
+
}
|
|
80
|
+
const basename = path.basename(filePath, path.extname(filePath));
|
|
81
|
+
if (basename.length >= 3) {
|
|
82
|
+
const pmResults = hybridSearch(basename, { topK: 3, maxCorpus: 200 });
|
|
83
|
+
if (pmResults.length > 0) {
|
|
84
|
+
const lines = [`## Corpus Postmortems (related to \`${filePath}\`)
|
|
85
|
+
`];
|
|
86
|
+
for (const r of pmResults) {
|
|
87
|
+
const pm = r.postmortem;
|
|
88
|
+
const age = formatAge(pm.generatedAt);
|
|
89
|
+
const auto = pm.resolvedAutonomously ? " \xB7 auto-resolved" : "";
|
|
90
|
+
const fix = pm.fixCommand ? `
|
|
91
|
+
Fix: \`${truncate(pm.fixCommand, 80)}\`` : "";
|
|
92
|
+
lines.push(
|
|
93
|
+
`- **[${pm.service}] ${truncate(pm.rootCause, 100)}** _(${age} ago${auto}, ${Math.round(pm.confidence * 100)}% confidence)_${fix}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
sections.push(lines.join("\n"));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const overrides = getOverrideSummary().slice(0, 5);
|
|
100
|
+
if (overrides.length > 0) {
|
|
101
|
+
const lines = ["## Override Corpus \u2014 Active Constraints\n"];
|
|
102
|
+
for (const o of overrides) {
|
|
103
|
+
const timeNote = o.timePattern ? ` \xB7 ${o.timePattern}` : "";
|
|
104
|
+
const svcs = o.services.length > 0 ? ` \xB7 services: ${o.services.slice(0, 3).map((s) => `\`${s}\``).join(", ")}` : "";
|
|
105
|
+
lines.push(
|
|
106
|
+
`- \`${o.tag}\` \u2014 overridden **${o.total}\xD7** \xB7 top reason: \`${o.dominantReason ?? "other"}\`${timeNote}${svcs}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
lines.push("\n_Check if any of these constraints apply before modifying this file._");
|
|
110
|
+
sections.push(lines.join("\n"));
|
|
111
|
+
}
|
|
112
|
+
const ctx = getCurrentTraceContext();
|
|
113
|
+
sections.push(
|
|
114
|
+
`## Mergen Trace Context
|
|
115
|
+
|
|
116
|
+
\`traceparent: ${ctx.traceparent}\``
|
|
117
|
+
);
|
|
118
|
+
const text = sections.join("\n\n---\n\n");
|
|
119
|
+
logger.debug({ filePath, historyCount: history.length }, "file-context resource served");
|
|
120
|
+
return {
|
|
121
|
+
contents: [{ uri: uri.href, mimeType: "text/markdown", text }]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
function formatAge(ms) {
|
|
127
|
+
const diff = Date.now() - ms;
|
|
128
|
+
if (diff < 6e4) return `${Math.round(diff / 1e3)}s`;
|
|
129
|
+
if (diff < 36e5) return `${Math.round(diff / 6e4)}m`;
|
|
130
|
+
if (diff < 864e5) return `${Math.round(diff / 36e5)}h`;
|
|
131
|
+
return `${Math.round(diff / 864e5)}d`;
|
|
132
|
+
}
|
|
133
|
+
function truncate(s, n) {
|
|
134
|
+
return s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
registerFileContextResource
|
|
138
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function computeRiskScore(inputs, holdThreshold) {
|
|
2
|
+
let score = 0;
|
|
3
|
+
if (!inputs.blastReversible) score += inputs.blastDataAtRisk ? 0.4 : 0.2;
|
|
4
|
+
if (inputs.blastScope === "data-destructive") score += 0.15;
|
|
5
|
+
if (inputs.contaminated) score += 0.3;
|
|
6
|
+
if (inputs.scrutinyTier === "high") score += 0.3;
|
|
7
|
+
else if (inputs.scrutinyTier === "elevated") score += 0.15;
|
|
8
|
+
score += Math.min(0.15, inputs.similarIncidentCount * 0.05);
|
|
9
|
+
const riskScore = Math.min(1, Math.round(score * 1e3) / 1e3);
|
|
10
|
+
return { riskScore, escalateToHold: riskScore >= holdThreshold };
|
|
11
|
+
}
|
|
12
|
+
function getRiskHoldThreshold() {
|
|
13
|
+
const raw = process.env.MERGEN_RISK_HOLD_THRESHOLD;
|
|
14
|
+
if (!raw) return null;
|
|
15
|
+
const parsed = Number(raw);
|
|
16
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 1) return null;
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
computeRiskScore,
|
|
21
|
+
getRiskHoldThreshold
|
|
22
|
+
};
|