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,152 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { trackCall } from "./tools-state.js";
|
|
5
|
+
import { checkBoundaries, formatBoundaryReport } from "./arch-boundaries.js";
|
|
6
|
+
import { scoreChangeRisk, formatRiskReport } from "./change-risk.js";
|
|
7
|
+
import { buildGraph, queryGraph, invalidateGraph } from "./arch-graph.js";
|
|
8
|
+
import { critiqueImplementation, formatCritiqueReport } from "./impl-critic.js";
|
|
9
|
+
function defaultSrcDir() {
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const candidates = [
|
|
12
|
+
path.resolve(cwd, "src"),
|
|
13
|
+
path.resolve(cwd, "server/src"),
|
|
14
|
+
path.resolve(cwd, "../src")
|
|
15
|
+
];
|
|
16
|
+
for (const c of candidates) {
|
|
17
|
+
if (existsSync(c)) return c;
|
|
18
|
+
}
|
|
19
|
+
return path.resolve(cwd, "src");
|
|
20
|
+
}
|
|
21
|
+
function registerArchTools(server) {
|
|
22
|
+
server.registerTool(
|
|
23
|
+
"check_arch_violations",
|
|
24
|
+
{
|
|
25
|
+
description: "Scan source files for architectural boundary violations \u2014 e.g. sensor/* importing from routes/*. Run this BEFORE and AFTER any code change. Returns a list of violations with file, zone, and rationale. All violations are errors that must be resolved before merging. Default rules: sensor cannot import routes or intelligence; intelligence cannot import routes; datadog cannot import routes or intelligence.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
files: z.array(z.string()).optional().describe("Specific files to check (absolute paths). Omit to scan the full source tree."),
|
|
28
|
+
srcDir: z.string().optional().describe("Root of the TypeScript source directory. Defaults to ./src or server/src.")
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
async ({ files, srcDir }) => {
|
|
32
|
+
trackCall("check_arch_violations");
|
|
33
|
+
const dir = srcDir ?? defaultSrcDir();
|
|
34
|
+
const result = checkBoundaries({
|
|
35
|
+
srcDir: dir,
|
|
36
|
+
singleFile: files?.length === 1 ? files[0] : void 0
|
|
37
|
+
});
|
|
38
|
+
return { content: [{ type: "text", text: formatBoundaryReport(result, dir) }] };
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
server.registerTool(
|
|
42
|
+
"score_change_risk",
|
|
43
|
+
{
|
|
44
|
+
description: "Compute a change risk score (0\u2013100) for a proposed edit before making it. Factors: files touched, subsystems crossed, public API exposure, test blast radius, untested files, critical entry points. Score \u2265 70 \u2192 HIGH RISK, requires human approval. Score 40\u201369 \u2192 MEDIUM, file a confidence report and rollback plan first. Score < 40 \u2192 LOW, safe to proceed. Call this as part of Phase 0 discovery alongside discover_repo_context and report_confidence.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
files: z.array(z.string()).min(1).describe("Files you plan to modify (absolute or relative paths from srcDir)"),
|
|
47
|
+
srcDir: z.string().optional().describe("Root of the TypeScript source directory. Defaults to ./src or server/src.")
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
async ({ files, srcDir }) => {
|
|
51
|
+
trackCall("score_change_risk");
|
|
52
|
+
const dir = srcDir ?? defaultSrcDir();
|
|
53
|
+
const absoluteFiles = files.map((f) => path.isAbsolute(f) ? f : path.resolve(dir, f));
|
|
54
|
+
const report = scoreChangeRisk(absoluteFiles, dir);
|
|
55
|
+
return { content: [{ type: "text", text: formatRiskReport(report) }] };
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
server.registerTool(
|
|
59
|
+
"query_arch_graph",
|
|
60
|
+
{
|
|
61
|
+
description: 'Traverse the architectural dependency graph of the codebase. Ask "what depends on rollback.ts?" to find all files that would break if rollback.ts changes. Ask "what does routes/incidents.ts depend on?" to understand its dependencies before refactoring. Also returns a zone-level summary of which architectural layers are involved.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
file: z.string().describe("File to query (filename, relative path, or absolute path)"),
|
|
64
|
+
direction: z.enum(["depends-on", "depended-by"]).describe('"depends-on": what does this file import? "depended-by": what imports this file?'),
|
|
65
|
+
maxDepth: z.number().int().min(1).max(6).optional().describe("Traversal depth (default 3). Higher values find more transitive dependents."),
|
|
66
|
+
srcDir: z.string().optional().describe("Root of the TypeScript source directory. Defaults to ./src or server/src.")
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async ({ file, direction, maxDepth = 3, srcDir }) => {
|
|
70
|
+
trackCall("query_arch_graph");
|
|
71
|
+
const dir = srcDir ?? defaultSrcDir();
|
|
72
|
+
const graph = buildGraph(dir);
|
|
73
|
+
let resolvedFile;
|
|
74
|
+
if (path.isAbsolute(file) && graph.forward.has(file)) {
|
|
75
|
+
resolvedFile = file;
|
|
76
|
+
} else {
|
|
77
|
+
resolvedFile = graph.files.find(
|
|
78
|
+
(f) => f.endsWith(`/${file}`) || f.endsWith(`/${file}.ts`) || path.basename(f) === file || path.basename(f) === `${file}.ts`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (!resolvedFile) {
|
|
82
|
+
const similar = graph.files.filter((f) => path.basename(f).toLowerCase().includes(path.basename(file).toLowerCase().replace(".ts", ""))).slice(0, 5).map((f) => path.relative(dir, f));
|
|
83
|
+
return {
|
|
84
|
+
content: [{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: `File not found in graph: \`${file}\`
|
|
87
|
+
|
|
88
|
+
Did you mean one of these?
|
|
89
|
+
${similar.map((s) => ` - ${s}`).join("\n") || " (no similar files found)"}`
|
|
90
|
+
}]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const result = queryGraph(graph, { file: resolvedFile, direction, maxDepth });
|
|
94
|
+
const relFile = path.relative(dir, resolvedFile);
|
|
95
|
+
const { getZone, ZONE_DISPLAY: ZD } = await import("./arch-graph.js");
|
|
96
|
+
const lines = [
|
|
97
|
+
`## Dependency Graph: \`${relFile}\``,
|
|
98
|
+
"",
|
|
99
|
+
`**Direction:** ${direction} | **Depth:** ${maxDepth}`,
|
|
100
|
+
""
|
|
101
|
+
];
|
|
102
|
+
if (result.direct.length === 0) {
|
|
103
|
+
lines.push(`No direct ${direction === "depends-on" ? "imports" : "importers"} found.`);
|
|
104
|
+
} else {
|
|
105
|
+
lines.push(`### Direct (${result.direct.length})`);
|
|
106
|
+
for (const f of result.direct) {
|
|
107
|
+
lines.push(` - \`${path.relative(dir, f)}\` _(${getZone(f)})_`);
|
|
108
|
+
}
|
|
109
|
+
lines.push("");
|
|
110
|
+
if (result.transitive.length > 0) {
|
|
111
|
+
lines.push(`### Transitive up to depth ${maxDepth} (${result.transitive.length})`);
|
|
112
|
+
for (const f of result.transitive) {
|
|
113
|
+
lines.push(` - \`${path.relative(dir, f)}\` _(${getZone(f)})_`);
|
|
114
|
+
}
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
const allDeps = [...result.direct, ...result.transitive];
|
|
118
|
+
const zoneCount = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const f of allDeps) {
|
|
120
|
+
const z2 = getZone(f);
|
|
121
|
+
zoneCount.set(z2, (zoneCount.get(z2) ?? 0) + 1);
|
|
122
|
+
}
|
|
123
|
+
lines.push("### By architectural zone");
|
|
124
|
+
for (const [zone, count] of zoneCount) {
|
|
125
|
+
lines.push(` - **${ZD[zone] ?? zone}**: ${count} file(s)`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
server.registerTool(
|
|
132
|
+
"critique_implementation",
|
|
133
|
+
{
|
|
134
|
+
description: 'Post-implementation critic \u2014 run this after writing or modifying code. Checks for: architectural boundary violations, ADR violations, security anti-patterns (empty catch, eval, XSS, command injection), hard-coded return values, and missing workflow steps (confidence report). Errors must be resolved before merging; warnings should be addressed. This is the "senior staff engineer review" step in the structured agent workflow.',
|
|
135
|
+
inputSchema: {
|
|
136
|
+
files: z.array(z.string()).min(1).describe("Files that were modified (absolute or relative paths from srcDir)"),
|
|
137
|
+
srcDir: z.string().optional().describe("Root of the TypeScript source directory. Defaults to ./src or server/src.")
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
async ({ files, srcDir }) => {
|
|
141
|
+
trackCall("critique_implementation");
|
|
142
|
+
const dir = srcDir ?? defaultSrcDir();
|
|
143
|
+
const absoluteFiles = files.map((f) => path.isAbsolute(f) ? f : path.resolve(dir, f));
|
|
144
|
+
invalidateGraph(dir);
|
|
145
|
+
const report = critiqueImplementation({ files: absoluteFiles, srcDir: dir });
|
|
146
|
+
return { content: [{ type: "text", text: formatCritiqueReport(report) }] };
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
registerArchTools
|
|
152
|
+
};
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { store } from "../sensor/buffer.js";
|
|
3
|
+
import { buildCausalChain, fixActionToCommand } from "./causal.js";
|
|
4
|
+
import { getRecords, recordVerdict, classifyVerdict, recordRemediationVerdict, isCorpusSeeded, getRealVerdictCount } from "./calibration.js";
|
|
5
|
+
import { executeRemediation, extractCommand } from "./autonomy.js";
|
|
6
|
+
import { deriveRollback, executeRollback } from "./rollback.js";
|
|
7
|
+
import { getAutopilotLevel, autopilotLevelPermits, classifyCommandRisk, autopilotLevelDescription } from "./action-risk.js";
|
|
8
|
+
import { hasRecentOverride } from "./override-corpus.js";
|
|
9
|
+
import { getStatsForTag } from "./calibration.js";
|
|
10
|
+
import { trackCall, withTierGate, getActivePlanId } from "./tools-state.js";
|
|
11
|
+
import { getTierForTool } from "./tool-manifest.js";
|
|
12
|
+
import { incidentStore } from "../sensor/incident-store.js";
|
|
13
|
+
import { captureSnapshot } from "./incident-replay.js";
|
|
14
|
+
import { postThreadReply } from "./slack.js";
|
|
15
|
+
import { consumeIncident } from "./usage.js";
|
|
16
|
+
import { generatePostmortem } from "./postmortem-store.js";
|
|
17
|
+
import { runAgentPipeline, renderPipelineStages } from "./agent-pipeline.js";
|
|
18
|
+
import { planningGate } from "./planning-gate.js";
|
|
19
|
+
import { isShadowMode } from "./execution-mode.js";
|
|
20
|
+
import { getExecutionThreshold } from "./threshold-optimizer.js";
|
|
21
|
+
import logger from "../sensor/logger.js";
|
|
22
|
+
import { trace, SpanStatusCode } from "@opentelemetry/api";
|
|
23
|
+
const TEAM_UPGRADE_INCIDENT_THRESHOLD = 5;
|
|
24
|
+
const AUTO_EXECUTE_CONFIDENCE_THRESHOLD = () => {
|
|
25
|
+
const base = getExecutionThreshold();
|
|
26
|
+
const provisional = isCorpusSeeded() || getRealVerdictCount() < 10;
|
|
27
|
+
return provisional ? Math.min(0.95, base + 0.05) : base;
|
|
28
|
+
};
|
|
29
|
+
const tracer = trace.getTracer("mergen-agent");
|
|
30
|
+
function registerAutonomyTools(server) {
|
|
31
|
+
server.registerTool(
|
|
32
|
+
"execute_fix",
|
|
33
|
+
{
|
|
34
|
+
description: "Execute the fix command embedded in a hypothesis fixHint. Requires confirm=true \u2014 always show the user what will be executed before calling this. After execution, automatically validates whether the fix resolved the issue. Returns: the command executed, stdout/stderr, exit code, and RESOLVED/PARTIAL/REGRESSED verdict. Only call this for HIGH-confidence diagnoses (confidence >= 0.85). Set dry_run=true to preview the command without running it.",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
pid: z.string().describe("Prediction id from reconstruct_context \u2014 identifies the hypothesis to fix"),
|
|
37
|
+
confirm: z.boolean().optional().describe("Must be true to proceed. Set true only after showing the user what will be executed."),
|
|
38
|
+
since: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER).optional().describe("Unix ms timestamp from before the issue \u2014 used to validate the fix. If omitted, uses now - 60s."),
|
|
39
|
+
dry_run: z.enum(["true", "false"]).optional().describe('Pass "true" to print the command that would be executed without running it.'),
|
|
40
|
+
cwd: z.string().optional().describe("Working directory for the command. Defaults to process cwd."),
|
|
41
|
+
actor: z.string().optional().describe("Identity of the engineer executing the fix (email or username). Used for RBAC and audit log."),
|
|
42
|
+
service: z.string().optional().describe('Service name (e.g. "api", "auth"). Used for override corpus lookup. If omitted, corpus check is skipped.')
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
withTierGate(getTierForTool("execute_fix"), async ({ pid, confirm, since, dry_run, cwd, actor, service }) => {
|
|
46
|
+
trackCall("execute_fix");
|
|
47
|
+
const isDryRun = dry_run === "true";
|
|
48
|
+
const isShadow = isShadowMode();
|
|
49
|
+
if (isShadow && !isDryRun) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: '\u{1F441}\uFE0F **Shadow mode** \u2014 execute_fix is suppressed. No command was run. Pass `dry_run: "true"` to preview the command that would execute.'
|
|
54
|
+
}]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!confirm) {
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: [
|
|
62
|
+
"\u26A0\uFE0F **confirm required**",
|
|
63
|
+
"",
|
|
64
|
+
"You must pass `confirm: true` after showing the user what command will be run.",
|
|
65
|
+
"Check the hypothesis fixHint for the command, then ask the user to confirm, then call again with confirm=true."
|
|
66
|
+
].join("\n")
|
|
67
|
+
}]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const records = getRecords();
|
|
71
|
+
const prediction = records.find((r) => r.pid === pid);
|
|
72
|
+
if (!prediction) {
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: `No prediction found for pid \`${pid}\`. Run reconstruct_context first.` }],
|
|
75
|
+
isError: true
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const tenantId = process.env.MERGEN_TENANT_ID;
|
|
79
|
+
const logs = store.getLogs(200, void 0, void 0, tenantId);
|
|
80
|
+
const network = store.getNetwork(200, void 0, void 0, tenantId);
|
|
81
|
+
const contexts = store.getContext(20, void 0, tenantId);
|
|
82
|
+
const terminal = store.getTerminalOutput(100, void 0, void 0, tenantId);
|
|
83
|
+
const processExits = store.getProcessExits(20, void 0, void 0, tenantId);
|
|
84
|
+
const causal = await buildCausalChain(logs, network, contexts, void 0, terminal, processExits, [], []);
|
|
85
|
+
const hyp = causal.hypotheses.find((h) => h.tag === prediction.tag);
|
|
86
|
+
const fixHint = hyp?.fixHint ?? null;
|
|
87
|
+
if (!fixHint) {
|
|
88
|
+
return {
|
|
89
|
+
content: [{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: [
|
|
92
|
+
`No fixHint available for detector \`${prediction.tag}\`.`,
|
|
93
|
+
"",
|
|
94
|
+
"This hypothesis does not have an auto-executable fix \u2014 apply the fix manually and call `validate_fix` afterwards."
|
|
95
|
+
].join("\n")
|
|
96
|
+
}]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const command = hyp?.fixAction ? fixActionToCommand(hyp.fixAction) : extractCommand(fixHint);
|
|
100
|
+
if (!command) {
|
|
101
|
+
return {
|
|
102
|
+
content: [{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: [
|
|
105
|
+
`**Fix hint:** ${fixHint}`,
|
|
106
|
+
"",
|
|
107
|
+
"No executable command found in this fixHint \u2014 the fix must be applied manually.",
|
|
108
|
+
"Apply the change, then call `validate_fix` to verify."
|
|
109
|
+
].join("\n")
|
|
110
|
+
}]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const commandTier = classifyCommandRisk(command);
|
|
114
|
+
if (!isDryRun && !autopilotLevelPermits(command, getAutopilotLevel())) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `\u26A0\uFE0F **Command blocked** \u2014 tier \`${commandTier}\` exceeds MERGEN_AUTOPILOT_LEVEL=${getAutopilotLevel()}. Apply manually or raise the autopilot level.`
|
|
119
|
+
}],
|
|
120
|
+
isError: true
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (!isDryRun && service) {
|
|
124
|
+
const now = /* @__PURE__ */ new Date();
|
|
125
|
+
if (hasRecentOverride(prediction.tag, service, now.getUTCDay(), now.getUTCHours())) {
|
|
126
|
+
return {
|
|
127
|
+
content: [{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: [
|
|
130
|
+
`\u{1F6AB} **Blocked by override corpus** \u2014 \`${prediction.tag}\` for service \`${service}\` has been overridden in a similar time window before.`,
|
|
131
|
+
"",
|
|
132
|
+
"Review the override history at `GET /override-corpus` and either apply the fix manually or ask an operator to re-review the override at `POST /overrides/:id/review`."
|
|
133
|
+
].join("\n")
|
|
134
|
+
}],
|
|
135
|
+
isError: true
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const resolvedActor = actor ?? process.env.MERGEN_MCP_ACTOR ?? "mcp-client";
|
|
140
|
+
const beforeTs = since ?? Date.now() - 6e4;
|
|
141
|
+
const execResult = await executeRemediation(command, { cwd, dryRun: isDryRun, actor: resolvedActor });
|
|
142
|
+
if (execResult.blocked) {
|
|
143
|
+
return {
|
|
144
|
+
content: [{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: [
|
|
147
|
+
`\u{1F6AB} **Command blocked by safety filter**`,
|
|
148
|
+
"",
|
|
149
|
+
`Command: \`${command}\``,
|
|
150
|
+
`Reason: ${execResult.blockReason}`,
|
|
151
|
+
"",
|
|
152
|
+
"Apply this fix manually and call `validate_fix` afterwards."
|
|
153
|
+
].join("\n")
|
|
154
|
+
}],
|
|
155
|
+
isError: true
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const lines = [
|
|
159
|
+
dry_run ? `## Dry Run \u2014 would execute` : `## Fix Executed`,
|
|
160
|
+
"",
|
|
161
|
+
`**Command:** \`${command}\``,
|
|
162
|
+
`**Exit code:** ${execResult.exitCode ?? "killed"}`,
|
|
163
|
+
`**Duration:** ${execResult.durationMs}ms`,
|
|
164
|
+
execResult.timedOut ? "\u26A0\uFE0F Command timed out (60s limit)" : "",
|
|
165
|
+
""
|
|
166
|
+
].filter((l) => l !== void 0);
|
|
167
|
+
if (execResult.stdout.trim()) {
|
|
168
|
+
lines.push("**stdout:**", "```", execResult.stdout.trim().slice(0, 2e3), "```", "");
|
|
169
|
+
}
|
|
170
|
+
if (execResult.stderr.trim()) {
|
|
171
|
+
lines.push("**stderr:**", "```", execResult.stderr.trim().slice(0, 1e3), "```", "");
|
|
172
|
+
}
|
|
173
|
+
if (isDryRun) {
|
|
174
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
175
|
+
}
|
|
176
|
+
if (execResult.ok) {
|
|
177
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
178
|
+
const windowMs = 6e4;
|
|
179
|
+
const windowStart = beforeTs - windowMs;
|
|
180
|
+
const logsBefore = store.getLogs(200, "error", windowStart).filter((e) => e.timestamp < beforeTs);
|
|
181
|
+
const netBefore = store.getNetwork(200, void 0, windowStart).filter(
|
|
182
|
+
(e) => e.timestamp < beforeTs && (e.status >= 400 || !!e.error)
|
|
183
|
+
);
|
|
184
|
+
const logsAfter = store.getLogs(200, "error", beforeTs);
|
|
185
|
+
const netAfter = store.getNetwork(200, void 0, beforeTs).filter((e) => e.status >= 400 || !!e.error);
|
|
186
|
+
const errsBefore = logsBefore.length + netBefore.length;
|
|
187
|
+
const errsAfter = logsAfter.length + netAfter.length;
|
|
188
|
+
let verdict;
|
|
189
|
+
let status;
|
|
190
|
+
if (errsAfter === 0 && errsBefore > 0) {
|
|
191
|
+
verdict = "correct";
|
|
192
|
+
status = "RESOLVED";
|
|
193
|
+
} else if (errsAfter === 0 && errsBefore === 0) {
|
|
194
|
+
verdict = "correct";
|
|
195
|
+
status = "CLEAN";
|
|
196
|
+
} else if (errsAfter < errsBefore) {
|
|
197
|
+
verdict = "partial";
|
|
198
|
+
status = "PARTIAL";
|
|
199
|
+
} else {
|
|
200
|
+
verdict = "wrong";
|
|
201
|
+
status = errsAfter > errsBefore ? "REGRESSED" : "UNRESOLVED";
|
|
202
|
+
}
|
|
203
|
+
if (!prediction.verdict) recordVerdict(pid, verdict);
|
|
204
|
+
lines.push(
|
|
205
|
+
`## Validation \u2014 ${status}`,
|
|
206
|
+
"",
|
|
207
|
+
`| | Before | After |`,
|
|
208
|
+
`|---|---|---|`,
|
|
209
|
+
`| Console errors | ${logsBefore.length} | ${logsAfter.length} |`,
|
|
210
|
+
`| Network errors | ${netBefore.length} | ${netAfter.length} |`,
|
|
211
|
+
`| **Total** | **${errsBefore}** | **${errsAfter}** |`,
|
|
212
|
+
"",
|
|
213
|
+
verdict === "correct" ? "\u2705 Fix confirmed \u2014 issue resolved." : verdict === "partial" ? "\u26A0\uFE0F Partial improvement \u2014 some errors remain." : status === "REGRESSED" ? "\u274C REGRESSED \u2014 more errors after fix than before. Consider reverting." : "\u274C UNRESOLVED \u2014 errors persist. Review the fix and try again."
|
|
214
|
+
);
|
|
215
|
+
if (status === "REGRESSED") {
|
|
216
|
+
const rollback = deriveRollback(command, execResult.stdout);
|
|
217
|
+
if (rollback.type === "command") {
|
|
218
|
+
lines.push("", `**Auto-rollback:** attempting \`${rollback.command}\`\u2026`);
|
|
219
|
+
const rb = await executeRollback(rollback, { cwd, actor: resolvedActor });
|
|
220
|
+
lines.push(rb.ok ? `\u21A9\uFE0F Rollback succeeded: \`${rb.message}\`` : `\u{1F534} Rollback failed: ${rb.message} \u2014 manual intervention required`);
|
|
221
|
+
} else {
|
|
222
|
+
lines.push("", `**Rollback not available:** ${rollback.reason} \u2014 manual revert required.`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
lines.push("", `\u274C Command failed (exit ${execResult.exitCode}). Fix was not applied \u2014 no validation run.`);
|
|
227
|
+
}
|
|
228
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
server.registerTool(
|
|
232
|
+
"triage_incident",
|
|
233
|
+
{
|
|
234
|
+
description: "Full autonomous incident triage loop. Pulls recent errors, runs causal analysis, and \u2014 if auto_execute=true and confidence is HIGH (>=85%) \u2014 executes the fix command and validates the result. Returns a structured incident report suitable for a Slack war room or PagerDuty note. Use this for on-call triage when the engineer is not in the IDE. Set auto_execute=false (default) to get the diagnosis only, without running anything.",
|
|
235
|
+
inputSchema: {
|
|
236
|
+
service: z.string().optional().describe('Service name to focus on (e.g. "api", "frontend"). If omitted, analyzes all recent events.'),
|
|
237
|
+
since: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER).optional().describe("Unix ms \u2014 only look at events after this timestamp. Defaults to now - 5 minutes."),
|
|
238
|
+
auto_execute: z.enum(["true", "false"]).optional().describe(
|
|
239
|
+
'Pass "true" to automatically execute the fix command when confidence >= 0.85. Default omitted / "false" \u2014 diagnosis only, nothing executed.'
|
|
240
|
+
),
|
|
241
|
+
cwd: z.string().optional().describe("Working directory for any fix command. Defaults to process cwd."),
|
|
242
|
+
actor: z.string().optional().describe("Identity of the engineer (email or username). Used for RBAC and audit log."),
|
|
243
|
+
tenant_id: z.string().optional().describe("Cloud mode: restrict analysis to this tenant's events. Required when MERGEN_CLOUD_MODE=true.")
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
async ({ service, since, auto_execute, cwd, actor, tenant_id }) => {
|
|
247
|
+
trackCall("triage_incident");
|
|
248
|
+
const shouldAutoExecute = auto_execute === "true";
|
|
249
|
+
const tenantId = tenant_id ?? process.env.MERGEN_TENANT_ID;
|
|
250
|
+
const incidentResult = await consumeIncident();
|
|
251
|
+
if (!incidentResult.allowed) {
|
|
252
|
+
return {
|
|
253
|
+
content: [{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: [
|
|
256
|
+
`\u26D4 ${incidentResult.notice ?? "Monthly incident limit reached."}`,
|
|
257
|
+
"",
|
|
258
|
+
"\u2192 https://mergen.app/pricing"
|
|
259
|
+
].join("\n")
|
|
260
|
+
}],
|
|
261
|
+
isError: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const sinceTs = since ?? Date.now() - 5 * 6e4;
|
|
265
|
+
logger.info({ service, sinceTs, auto_execute, tenantId }, "triage_incident: starting");
|
|
266
|
+
const span = tracer.startSpan("mergen.triage_incident", {
|
|
267
|
+
attributes: {
|
|
268
|
+
"mergen.service": service ?? "all",
|
|
269
|
+
"mergen.auto_execute": shouldAutoExecute,
|
|
270
|
+
"mergen.tenant_id": tenantId ?? ""
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
try {
|
|
274
|
+
const logs = store.getLogs(200, void 0, sinceTs, tenantId);
|
|
275
|
+
const network = store.getNetwork(200, void 0, sinceTs, tenantId);
|
|
276
|
+
const contexts = store.getContext(20, sinceTs, tenantId);
|
|
277
|
+
const terminal = store.getTerminalOutput(100, void 0, sinceTs, tenantId);
|
|
278
|
+
const processExits = store.getProcessExits(20, void 0, sinceTs, tenantId);
|
|
279
|
+
const ciEvents = store.getCIEvents(20, void 0, sinceTs, tenantId);
|
|
280
|
+
const deployments = store.getDeployments(10, void 0, sinceTs, tenantId);
|
|
281
|
+
if (logs.length === 0 && network.filter((n) => n.status >= 400).length === 0) {
|
|
282
|
+
span.setAttribute("mergen.status", "clean_buffer");
|
|
283
|
+
return {
|
|
284
|
+
content: [{
|
|
285
|
+
type: "text",
|
|
286
|
+
text: [
|
|
287
|
+
"## Triage Report \u2014 No Issues Found",
|
|
288
|
+
"",
|
|
289
|
+
service ? `No errors found for service \`${service}\` in the last ${Math.round((Date.now() - sinceTs) / 6e4)} minutes.` : `No errors found in the last ${Math.round((Date.now() - sinceTs) / 6e4)} minutes.`,
|
|
290
|
+
"",
|
|
291
|
+
"Buffer is clean. If you expected errors, verify the browser extension is connected and capturing events."
|
|
292
|
+
].join("\n")
|
|
293
|
+
}]
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const causal = await buildCausalChain(logs, network, contexts, sinceTs, terminal, processExits, ciEvents, deployments);
|
|
297
|
+
const topHyp = causal.hypotheses[0];
|
|
298
|
+
const errorCount = logs.filter((e) => e.level === "error").length;
|
|
299
|
+
const netErrors = network.filter((n) => n.status >= 400 || n.error).length;
|
|
300
|
+
if (topHyp) {
|
|
301
|
+
span.setAttribute("mergen.incident_tag", topHyp.tag ?? "");
|
|
302
|
+
span.setAttribute("mergen.confidence_score", topHyp.confidenceScore ?? 0);
|
|
303
|
+
span.setAttribute("mergen.summary", topHyp.summary ?? "");
|
|
304
|
+
if (topHyp.fixHint) {
|
|
305
|
+
span.setAttribute("mergen.fix_hint", topHyp.fixHint);
|
|
306
|
+
}
|
|
307
|
+
captureSnapshot({
|
|
308
|
+
pid: topHyp.pid ?? `mcp-${Date.now()}`,
|
|
309
|
+
capturedAt: Date.now(),
|
|
310
|
+
firedAt: sinceTs,
|
|
311
|
+
logs,
|
|
312
|
+
// `network` is narrowed to the local NetEventView here (see the
|
|
313
|
+
// TS2589 note above); cast back to captureSnapshot's real element
|
|
314
|
+
// type — the runtime value is the full NetworkEvent from the buffer.
|
|
315
|
+
network,
|
|
316
|
+
contexts,
|
|
317
|
+
terminal,
|
|
318
|
+
processExits,
|
|
319
|
+
ciEvents,
|
|
320
|
+
deployments,
|
|
321
|
+
infraEvents: [],
|
|
322
|
+
originalTag: topHyp.tag ?? null,
|
|
323
|
+
originalConfidenceScore: topHyp.confidenceScore ?? null,
|
|
324
|
+
originalFixHint: topHyp.fixHint ?? null
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const lines = [
|
|
328
|
+
`## Triage Report${service ? ` \u2014 ${service}` : ""}`,
|
|
329
|
+
"",
|
|
330
|
+
`**Window:** ${new Date(sinceTs).toISOString()} \u2192 now`,
|
|
331
|
+
`**Console errors:** ${errorCount} | **Network errors:** ${netErrors}`,
|
|
332
|
+
""
|
|
333
|
+
];
|
|
334
|
+
if (!topHyp) {
|
|
335
|
+
span.setAttribute("mergen.status", "no_hypothesis");
|
|
336
|
+
lines.push("No hypothesis generated. Errors may be unrelated or below detector threshold.");
|
|
337
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
338
|
+
}
|
|
339
|
+
const pct = Math.round((topHyp.confidenceScore ?? 0) * 100);
|
|
340
|
+
const calStats = getStatsForTag(topHyp.tag);
|
|
341
|
+
const calibrationLabel = calStats?.isEmpirical ? `calibrated \u2014 ${calStats.verdicts} verdicts on this installation` : "estimated \u2014 runs /feedback after resolution to calibrate";
|
|
342
|
+
lines.push(
|
|
343
|
+
`### Root Cause \u2014 ${topHyp.confidence} (${pct}%)`,
|
|
344
|
+
`_Confidence source: ${calibrationLabel}_`,
|
|
345
|
+
"",
|
|
346
|
+
topHyp.summary,
|
|
347
|
+
""
|
|
348
|
+
);
|
|
349
|
+
if (topHyp.evidence?.length) {
|
|
350
|
+
lines.push("**Evidence:**");
|
|
351
|
+
topHyp.evidence.slice(0, 4).forEach((e) => lines.push(`- ${e}`));
|
|
352
|
+
lines.push("");
|
|
353
|
+
}
|
|
354
|
+
if (topHyp.fixHint) {
|
|
355
|
+
lines.push(`**Fix:** ${topHyp.fixHint}`, "");
|
|
356
|
+
}
|
|
357
|
+
const pipeline = runAgentPipeline(causal, {
|
|
358
|
+
service,
|
|
359
|
+
executionThreshold: AUTO_EXECUTE_CONFIDENCE_THRESHOLD()
|
|
360
|
+
});
|
|
361
|
+
lines.push("", renderPipelineStages(pipeline.stages), "");
|
|
362
|
+
const command = pipeline.plan?.command ?? (topHyp.fixAction ? fixActionToCommand(topHyp.fixAction) : null) ?? (topHyp.fixHint ? extractCommand(topHyp.fixHint) : null);
|
|
363
|
+
const execConfidence = topHyp.remediationConfidence ?? topHyp.confidenceScore ?? 0;
|
|
364
|
+
const execPct = Math.round(execConfidence * 100);
|
|
365
|
+
const autopilotLevel = getAutopilotLevel();
|
|
366
|
+
const levelPermits = !command || autopilotLevelPermits(command, autopilotLevel);
|
|
367
|
+
const isShadow = isShadowMode();
|
|
368
|
+
const gate = planningGate(topHyp, service ?? "unknown", AUTO_EXECUTE_CONFIDENCE_THRESHOLD());
|
|
369
|
+
const canAutoExecute = shouldAutoExecute && !isShadow && command && pipeline.verdict === "proceed" && gate.execute && levelPermits;
|
|
370
|
+
span.setAttribute("mergen.pipeline.verdict", pipeline.verdict ?? "");
|
|
371
|
+
if (command) {
|
|
372
|
+
span.setAttribute("mergen.command", command);
|
|
373
|
+
}
|
|
374
|
+
if (isShadow && shouldAutoExecute && command) {
|
|
375
|
+
lines.push(`\u{1F441}\uFE0F **Shadow mode** \u2014 would execute \`${command}\` (remediation: ${execPct}%). No action taken.`, "");
|
|
376
|
+
}
|
|
377
|
+
if (shouldAutoExecute && !canAutoExecute && !isShadow) {
|
|
378
|
+
if (!command) {
|
|
379
|
+
lines.push("\u26A0\uFE0F Auto-execute skipped \u2014 no executable command found in fixHint.");
|
|
380
|
+
} else if (pipeline.verdict === "block") {
|
|
381
|
+
lines.push(`\u26A0\uFE0F Auto-execute blocked by governance pipeline: ${pipeline.blockReason ?? "see pipeline stages above"}`);
|
|
382
|
+
} else if (!gate.execute) {
|
|
383
|
+
lines.push(`\u26A0\uFE0F Auto-execute blocked by planning gate: ${gate.reason}`);
|
|
384
|
+
} else if (!levelPermits) {
|
|
385
|
+
const commandTier = classifyCommandRisk(command);
|
|
386
|
+
lines.push(`\u26A0\uFE0F Auto-execute skipped \u2014 MERGEN_AUTOPILOT_LEVEL=${autopilotLevel} permits ${autopilotLevelDescription(autopilotLevel)}. This command is \`${commandTier}\` tier. Set MERGEN_AUTOPILOT_LEVEL=full to enable.`);
|
|
387
|
+
} else if (topHyp.remediationConfidence !== void 0 && topHyp.remediationConfidence < AUTO_EXECUTE_CONFIDENCE_THRESHOLD()) {
|
|
388
|
+
lines.push(`\u26A0\uFE0F Auto-execute skipped \u2014 remediation confidence ${execPct}% is below the 85% threshold (diagnosis: ${pct}%). The root cause is identified with high confidence but the fix has variable reliability \u2014 apply manually.`);
|
|
389
|
+
} else {
|
|
390
|
+
lines.push(`\u26A0\uFE0F Auto-execute skipped \u2014 confidence ${pct}% is below the 85% threshold for autonomous action.`);
|
|
391
|
+
}
|
|
392
|
+
lines.push("");
|
|
393
|
+
}
|
|
394
|
+
if (canAutoExecute && command) {
|
|
395
|
+
span.setAttribute("mergen.execution.attempted", true);
|
|
396
|
+
lines.push(`### Auto-Execute`, "", `Running: \`${command}\``, "");
|
|
397
|
+
if (topHyp.pid) {
|
|
398
|
+
void postThreadReply(topHyp.pid, `\u2699\uFE0F *Mergen auto-executing fix* (diagnosis: ${pct}%, remediation: ${execPct}%)
|
|
399
|
+
\`${command}\``);
|
|
400
|
+
}
|
|
401
|
+
const resolvedActor = actor ?? process.env.MERGEN_MCP_ACTOR ?? "mcp-client";
|
|
402
|
+
const execResult = await executeRemediation(command, { cwd, actor: resolvedActor });
|
|
403
|
+
if (execResult.blocked) {
|
|
404
|
+
span.setAttribute("mergen.execution.blocked", true);
|
|
405
|
+
span.setAttribute("mergen.execution.block_reason", execResult.blockReason ?? "");
|
|
406
|
+
lines.push(`\u{1F6AB} **Blocked:** ${execResult.blockReason}`, "", "Apply manually and validate.");
|
|
407
|
+
} else if (!execResult.ok) {
|
|
408
|
+
span.setAttribute("mergen.execution.ok", false);
|
|
409
|
+
span.setAttribute("mergen.execution.exit_code", execResult.exitCode ?? -1);
|
|
410
|
+
lines.push(`\u274C Command failed (exit ${execResult.exitCode}).`);
|
|
411
|
+
if (execResult.stderr.trim()) {
|
|
412
|
+
lines.push("", "```", execResult.stderr.trim().slice(0, 500), "```");
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
span.setAttribute("mergen.execution.ok", true);
|
|
416
|
+
span.setAttribute("mergen.execution.exit_code", 0);
|
|
417
|
+
lines.push(`\u2705 Executed (${execResult.durationMs}ms, exit 0)`);
|
|
418
|
+
if (execResult.stdout.trim()) {
|
|
419
|
+
lines.push("", "```", execResult.stdout.trim().slice(0, 500), "```");
|
|
420
|
+
}
|
|
421
|
+
lines.push("");
|
|
422
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
423
|
+
const logsAfter = store.getLogs(200, "error", sinceTs, tenantId);
|
|
424
|
+
const netAfter = store.getNetwork(200, void 0, sinceTs, tenantId).filter((n) => n.status >= 400 || n.error);
|
|
425
|
+
const afterCount = logsAfter.length + netAfter.length;
|
|
426
|
+
const beforeCount = errorCount + netErrors;
|
|
427
|
+
const verdict = classifyVerdict(beforeCount, afterCount);
|
|
428
|
+
span.setAttribute("mergen.execution.verdict", verdict);
|
|
429
|
+
if (topHyp.pid) {
|
|
430
|
+
const existing = getRecords().find((r) => r.pid === topHyp.pid);
|
|
431
|
+
if (existing && !existing.verdict) recordVerdict(topHyp.pid, verdict);
|
|
432
|
+
recordRemediationVerdict(topHyp.pid, verdict);
|
|
433
|
+
if (verdict === "correct") {
|
|
434
|
+
const resolvedAt = Date.now();
|
|
435
|
+
const inc = incidentStore.upsert(topHyp.pid, {
|
|
436
|
+
status: "resolved",
|
|
437
|
+
resolvedAt,
|
|
438
|
+
resolvedAutonomously: true,
|
|
439
|
+
causallyCorrect: true
|
|
440
|
+
});
|
|
441
|
+
generatePostmortem({
|
|
442
|
+
pid: topHyp.pid,
|
|
443
|
+
tag: topHyp.tag ?? "unknown",
|
|
444
|
+
service: service ?? "unknown",
|
|
445
|
+
rootCause: topHyp.summary ?? "",
|
|
446
|
+
fixCommand: command,
|
|
447
|
+
confidence: topHyp.confidenceScore ?? 0,
|
|
448
|
+
mttrMs: inc.createdAt ? resolvedAt - inc.createdAt : null,
|
|
449
|
+
resolvedAutonomously: true,
|
|
450
|
+
evidence: topHyp.evidence,
|
|
451
|
+
fixHint: topHyp.fixHint
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (afterCount === 0) {
|
|
456
|
+
lines.push("### Validation \u2014 RESOLVED", "", "\u2705 Zero errors after fix.");
|
|
457
|
+
if (topHyp.pid) void postThreadReply(topHyp.pid, "\u2705 *RESOLVED* \u2014 zero errors after autonomous fix.");
|
|
458
|
+
} else if (afterCount < beforeCount) {
|
|
459
|
+
lines.push("### Validation \u2014 PARTIAL", "", `\u26A0\uFE0F ${afterCount} errors remain (down from ${beforeCount}).`);
|
|
460
|
+
if (topHyp.pid) void postThreadReply(topHyp.pid, `\u26A0\uFE0F *PARTIAL* \u2014 ${afterCount} errors remain (down from ${beforeCount}).`);
|
|
461
|
+
} else if (afterCount > beforeCount) {
|
|
462
|
+
lines.push("### Validation \u2014 REGRESSED", "", `\u274C ${afterCount} errors (up from ${beforeCount}). Consider reverting.`);
|
|
463
|
+
if (topHyp.pid) void postThreadReply(topHyp.pid, `\u274C *REGRESSED* \u2014 ${afterCount} errors (up from ${beforeCount}). Consider reverting.`);
|
|
464
|
+
} else {
|
|
465
|
+
lines.push("### Validation \u2014 UNRESOLVED", "", `\u274C ${afterCount} errors remain (same as before).`);
|
|
466
|
+
if (topHyp.pid) void postThreadReply(topHyp.pid, `\u274C *UNRESOLVED* \u2014 ${afterCount} errors remain.`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
} else if (command) {
|
|
470
|
+
lines.push(`**Command to run:** \`${command}\``);
|
|
471
|
+
lines.push("", `Call \`execute_fix(pid: "${topHyp.pid ?? "unknown"}", confirm: true)\` to execute, or run manually.`);
|
|
472
|
+
}
|
|
473
|
+
const analyzedCount = incidentStore.list(void 0, 200).length;
|
|
474
|
+
if (getActivePlanId() === "free" && analyzedCount >= TEAM_UPGRADE_INCIDENT_THRESHOLD) {
|
|
475
|
+
lines.push(
|
|
476
|
+
"",
|
|
477
|
+
"---",
|
|
478
|
+
`> **You've analyzed ${analyzedCount} incidents with Mergen.** The override corpus you've built is your team's most valuable asset \u2014 it prevents repeat failures automatically. The **Team plan ($2,500/mo, 20 seats included)** shares it across your on-call rotation, adds incident replay, and unlocks the shadow analytics PDF your CISO needs before approving autopilot.`,
|
|
479
|
+
`> \u2192 https://mergen.app/pricing`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
483
|
+
} catch (err) {
|
|
484
|
+
span.recordException(err);
|
|
485
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
486
|
+
throw err;
|
|
487
|
+
} finally {
|
|
488
|
+
span.end();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
export {
|
|
494
|
+
registerAutonomyTools
|
|
495
|
+
};
|