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,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { commitContextStore } from "../sensor/commit-context-store.js";
|
|
3
|
+
import { trackCall } from "./tools-state.js";
|
|
4
|
+
import { recordExplainWhy } from "./usage.js";
|
|
5
|
+
function registerIntentTools(server) {
|
|
6
|
+
server.registerTool(
|
|
7
|
+
"explain_why",
|
|
8
|
+
{
|
|
9
|
+
description: `Retrieves the causal intent behind a service's current behavior \u2014 the PR titles, business descriptions, linked tickets, and human approvers responsible for the code that exists today. Answers "Why does service X have a 30s timeout?" or "Who approved the retry logic?" without requiring the original author to be on-call. Data comes from the commit intent archive (GitHub PR webhooks). Returns nothing if no PRs have been captured yet \u2014 connect GitHub webhook to POST /webhooks/github first.`,
|
|
10
|
+
inputSchema: {
|
|
11
|
+
service: z.string().describe('Service or repo name to query (e.g. "api", "payments-worker", "org/repo").'),
|
|
12
|
+
behavior: z.string().optional().describe('Optional: specific behavior to explain (e.g. "30s timeout", "retry 3 times", "rate limit 100rps"). Filters results by relevance.'),
|
|
13
|
+
limit: z.number().int().min(1).max(20).optional().describe("Max PRs to return (default: 5).")
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
({ service, behavior, limit = 5 }) => {
|
|
17
|
+
trackCall("explain_why");
|
|
18
|
+
recordExplainWhy();
|
|
19
|
+
const contexts = commitContextStore.listByRepo(service, limit * 2);
|
|
20
|
+
if (contexts.length === 0) {
|
|
21
|
+
const total = commitContextStore.count();
|
|
22
|
+
if (total === 0) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: [
|
|
27
|
+
`## Intent Archive: ${service}`,
|
|
28
|
+
"",
|
|
29
|
+
"**No PRs captured yet.** Connect GitHub to start building the intent archive:",
|
|
30
|
+
"",
|
|
31
|
+
"```",
|
|
32
|
+
`mergen-server connect github --repo ${service.includes("/") ? service : `<org>/${service}`}`,
|
|
33
|
+
"```",
|
|
34
|
+
"",
|
|
35
|
+
"_Takes 30 seconds. Every PR merged after this populates the archive automatically._",
|
|
36
|
+
'_Next incident: ask "explain why this service has a 30s timeout" and get the original PR, author, and approver instantly._'
|
|
37
|
+
].join("\n")
|
|
38
|
+
}]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: [
|
|
45
|
+
`## Intent Archive: ${service}`,
|
|
46
|
+
"",
|
|
47
|
+
`_${total} total contexts in archive \u2014 none matched "${service}"._`,
|
|
48
|
+
"",
|
|
49
|
+
`Try the full repo name (e.g. \`org/${service}\`), or connect this repo:`,
|
|
50
|
+
"",
|
|
51
|
+
"```",
|
|
52
|
+
`mergen-server connect github --repo <org>/${service}`,
|
|
53
|
+
"```"
|
|
54
|
+
].join("\n")
|
|
55
|
+
}]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
let filtered = contexts;
|
|
59
|
+
if (behavior) {
|
|
60
|
+
const kw = behavior.toLowerCase();
|
|
61
|
+
filtered = contexts.filter((c) => {
|
|
62
|
+
const searchText = [c.prTitle, c.prBody, ...c.linkedIssues.map((i) => i.ref)].join(" ").toLowerCase();
|
|
63
|
+
return searchText.includes(kw);
|
|
64
|
+
});
|
|
65
|
+
if (filtered.length === 0) {
|
|
66
|
+
return {
|
|
67
|
+
content: [{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: [
|
|
70
|
+
`## Intent Archive: ${service}`,
|
|
71
|
+
"",
|
|
72
|
+
`_No PRs matched "${behavior}" across ${contexts.length} archived PR${contexts.length !== 1 ? "s" : ""} for ${service}._`,
|
|
73
|
+
"",
|
|
74
|
+
"Try a broader keyword, or omit `behavior` to see the most recent PRs."
|
|
75
|
+
].join("\n")
|
|
76
|
+
}]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const results = filtered.slice(0, limit);
|
|
81
|
+
const aiCount = results.filter((c) => c.aiGenerated).length;
|
|
82
|
+
const approvedCount = results.filter((c) => c.approvers.length > 0).length;
|
|
83
|
+
const lines = [
|
|
84
|
+
`## Intent Archive: ${service}`,
|
|
85
|
+
`_${results.length} PR${results.length !== 1 ? "s" : ""} \xB7 ${aiCount} AI-generated \xB7 ${approvedCount} with recorded approvals_`,
|
|
86
|
+
behavior ? `_Filtered by: "${behavior}"_` : "",
|
|
87
|
+
""
|
|
88
|
+
].filter(Boolean);
|
|
89
|
+
for (const ctx of results) {
|
|
90
|
+
const prRef = ctx.prNumber ? `PR #${ctx.prNumber}` : ctx.sha.slice(0, 7);
|
|
91
|
+
const aiTag = ctx.aiGenerated ? ` \u{1F916} ${ctx.aiTool ?? "AI-generated"}` : "";
|
|
92
|
+
const dateStr = ctx.mergedAt ? new Date(ctx.mergedAt).toISOString().slice(0, 10) : new Date(ctx.capturedAt).toISOString().slice(0, 10);
|
|
93
|
+
lines.push(`### ${prRef}${aiTag} \u2014 ${dateStr}`);
|
|
94
|
+
if (ctx.prTitle) lines.push(`**${ctx.prTitle}**`);
|
|
95
|
+
const meta = [];
|
|
96
|
+
if (ctx.author) meta.push(`Author: @${ctx.author}`);
|
|
97
|
+
if (ctx.approvers.length) meta.push(`Approved by: ${ctx.approvers.join(", ")}`);
|
|
98
|
+
else meta.push("\u26A0\uFE0F No recorded approvers");
|
|
99
|
+
if (ctx.branch) meta.push(`Branch: ${ctx.branch}`);
|
|
100
|
+
lines.push(`_${meta.join(" \xB7 ")}_`);
|
|
101
|
+
if (ctx.linkedIssues.length > 0) {
|
|
102
|
+
lines.push(`**Linked issues:** ${ctx.linkedIssues.slice(0, 5).map((i) => i.ref).join(", ")}`);
|
|
103
|
+
}
|
|
104
|
+
if (ctx.prBody) {
|
|
105
|
+
const summary = ctx.prBody.split("\n").filter((l) => l.trim() && !l.startsWith("#")).slice(0, 3).join(" ").slice(0, 400);
|
|
106
|
+
if (summary) lines.push(`> ${summary}`);
|
|
107
|
+
}
|
|
108
|
+
if (ctx.filesChanged.length > 0) {
|
|
109
|
+
const files = ctx.filesChanged.slice(0, 5).join(", ");
|
|
110
|
+
const more = ctx.filesChanged.length > 5 ? ` +${ctx.filesChanged.length - 5} more` : "";
|
|
111
|
+
lines.push(`_Files: ${files}${more}_`);
|
|
112
|
+
}
|
|
113
|
+
lines.push("");
|
|
114
|
+
}
|
|
115
|
+
lines.push("---");
|
|
116
|
+
lines.push(`_Source: commit intent archive \xB7 ${commitContextStore.count()} total contexts captured_`);
|
|
117
|
+
const feedbackId = `ew-${Date.now().toString(36)}`;
|
|
118
|
+
lines.push("");
|
|
119
|
+
lines.push(`_Was this helpful? \`mergen-server feedback ${feedbackId} --yes\` or \`mergen-server feedback ${feedbackId} --no\`_`);
|
|
120
|
+
return {
|
|
121
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
registerIntentTools
|
|
128
|
+
};
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { memoryStore, formatMttr } from "../datadog/memory-store.js";
|
|
3
|
+
import { agentContextStore as agentMemoryStore } from "../sensor/agent-context-store.js";
|
|
4
|
+
import { getOverrideSummary, getOverridesForTag } from "./override-corpus.js";
|
|
5
|
+
import { getStats, getStatsForTag, getRecords, isCorpusSeeded } from "./calibration.js";
|
|
6
|
+
import { trackCall } from "./tools-state.js";
|
|
7
|
+
function formatRecord(r, index) {
|
|
8
|
+
const firedAgo = Math.round((Date.now() - r.firedAt) / 6e4);
|
|
9
|
+
const mttr = r.mttrMs ? formatMttr(r.mttrMs) : "unresolved";
|
|
10
|
+
const fix = r.fixPrTitle ? `Fix: [${r.fixPrTitle}](${r.fixPrUrl ?? "#"}) (${r.resolutionType})` : r.fixSummary ? `Fix: ${r.fixSummary}` : `Fix: not captured (${r.resolutionType})`;
|
|
11
|
+
const location = r.implicatedFile ? `\`${r.implicatedFile}${r.implicatedLine ? ":" + r.implicatedLine : ""}\`` : "unknown";
|
|
12
|
+
return [
|
|
13
|
+
`**${index + 1}.** \`${new Date(r.firedAt).toISOString().slice(0, 16)}\` \xB7 ${firedAgo}m ago`,
|
|
14
|
+
` Service: \`${r.service}\` \xB7 Endpoint: \`${r.endpoint}\``,
|
|
15
|
+
` Error: ${r.errorMessage.slice(0, 120)}`,
|
|
16
|
+
` Location: ${location}`,
|
|
17
|
+
` MTTR: ${mttr} \xB7 ${fix}`,
|
|
18
|
+
r.deployedSha ? ` Deploy: \`${r.deployedSha.slice(0, 7)}\`` : ""
|
|
19
|
+
].filter(Boolean).join("\n");
|
|
20
|
+
}
|
|
21
|
+
function fmtMs(ms) {
|
|
22
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
23
|
+
const min = Math.floor(ms / 6e4);
|
|
24
|
+
const sec = Math.round(ms % 6e4 / 1e3);
|
|
25
|
+
return sec > 0 ? `${min}m ${sec}s` : `${min}m`;
|
|
26
|
+
}
|
|
27
|
+
function registerMemoryTools(server) {
|
|
28
|
+
server.registerTool(
|
|
29
|
+
"get_incident_history",
|
|
30
|
+
{
|
|
31
|
+
description: 'Returns the historical record of past incidents matching the current error pattern. Use this after get_incident_context to answer: "Have we seen this before? How was it fixed? How long did it take?" The fingerprint is a stable hash of error_type + service + endpoint \u2014 same class of bug, same fingerprint. Also returns benchmark statistics (p50 MTTR, most common fix type).',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
fingerprint: z.string().optional().describe("16-char hex fingerprint from a previous incident (shown in get_incident_context output)"),
|
|
34
|
+
service: z.string().optional().describe('Filter by service name instead of fingerprint (e.g. "api", "auth-service", "payments")'),
|
|
35
|
+
limit: z.number().int().min(1).max(20).optional().describe("Max past incidents to return (default 5)")
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
async ({ fingerprint, service, limit = 5 }) => {
|
|
39
|
+
trackCall("get_incident_history");
|
|
40
|
+
if (!memoryStore.isHealthy()) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: "\u26A0\uFE0F **Incident memory store is unavailable** (SQLite init failed \u2014 check logs for WASM or file error). History cannot be queried. New incidents will not be persisted until the store recovers on next restart."
|
|
45
|
+
}]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (!fingerprint && !service) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: [
|
|
53
|
+
"## Incident History",
|
|
54
|
+
"",
|
|
55
|
+
"Provide either `fingerprint` (from a Runtime Fact) or `service` to look up history.",
|
|
56
|
+
"",
|
|
57
|
+
"**Open incidents right now:**",
|
|
58
|
+
...memoryStore.listOpen().slice(0, 5).map((r, i) => formatRecord(r, i))
|
|
59
|
+
].join("\n")
|
|
60
|
+
}]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
let similar;
|
|
64
|
+
if (fingerprint) {
|
|
65
|
+
similar = memoryStore.findSimilar(fingerprint, limit);
|
|
66
|
+
} else {
|
|
67
|
+
const byService = memoryStore.listAll(500).filter((r) => r.service === service);
|
|
68
|
+
similar = byService.slice(0, limit);
|
|
69
|
+
if (similar.length > 0 && similar.length < limit) {
|
|
70
|
+
const fp2 = similar[0].fingerprint;
|
|
71
|
+
const extra = memoryStore.findSimilar(fp2, limit).filter(
|
|
72
|
+
(r) => !similar.some((s) => s.id === r.id)
|
|
73
|
+
);
|
|
74
|
+
similar = [...similar, ...extra].slice(0, limit);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (similar.length === 0) {
|
|
78
|
+
return {
|
|
79
|
+
content: [{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: fingerprint ? `No historical incidents found for fingerprint \`${fingerprint}\`.
|
|
82
|
+
|
|
83
|
+
This is the first time Mergen has seen this error pattern.` : `No historical incidents found for service \`${service}\`.`
|
|
84
|
+
}]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const fp = similar[0].fingerprint;
|
|
88
|
+
const stats = memoryStore.benchmarkStats(fp);
|
|
89
|
+
const lines = [
|
|
90
|
+
`## Incident History \u2014 fingerprint \`${fp}\``,
|
|
91
|
+
""
|
|
92
|
+
];
|
|
93
|
+
const mostRecent = similar[0];
|
|
94
|
+
const timeAgo = Math.round((Date.now() - mostRecent.firedAt) / 864e5);
|
|
95
|
+
const timeDesc = timeAgo === 0 ? "today" : timeAgo === 1 ? "yesterday" : `${timeAgo} days ago`;
|
|
96
|
+
const lastFixDesc = mostRecent.fixPrTitle ? `[${mostRecent.fixPrTitle}](${mostRecent.fixPrUrl ?? "#"})` : mostRecent.fixSummary ? mostRecent.fixSummary : `Fix pattern (${mostRecent.resolutionType})`;
|
|
97
|
+
lines.push(
|
|
98
|
+
`> \u{1F4A1} **Visible Intelligence: "Why did this break again?"**`,
|
|
99
|
+
`> `,
|
|
100
|
+
`> \u{1F449} **This error has happened before (${timeDesc}). Here's what you did last time \u2014 and why it worked:**`,
|
|
101
|
+
`> - **Last Fix:** ${lastFixDesc}`,
|
|
102
|
+
`> - **Resolution Type:** \`${mostRecent.resolutionType}\``,
|
|
103
|
+
`> - **Implicated File:** \`${mostRecent.implicatedFile || "unknown"}${mostRecent.implicatedLine ? ":" + mostRecent.implicatedLine : ""}\``,
|
|
104
|
+
""
|
|
105
|
+
);
|
|
106
|
+
if (stats && stats.occurrences > 1) {
|
|
107
|
+
const p50 = stats.p50MttrMs ? formatMttr(stats.p50MttrMs) : "N/A";
|
|
108
|
+
const p90 = stats.p90MttrMs ? formatMttr(stats.p90MttrMs) : "N/A";
|
|
109
|
+
const lastSeen = stats.lastSeenAt ? `${Math.round((Date.now() - stats.lastSeenAt) / 864e5)}d ago` : "unknown";
|
|
110
|
+
lines.push(
|
|
111
|
+
`**Seen ${stats.occurrences} time${stats.occurrences !== 1 ? "s" : ""} \xB7 p50 MTTR: ${p50} \xB7 p90 MTTR: ${p90}**`,
|
|
112
|
+
`Most common fix: **${stats.topResolutionType}** (${stats.topResolutionCount}/${stats.occurrences} times) \xB7 Last seen: ${lastSeen}`,
|
|
113
|
+
""
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
lines.push("**Past incidents (most recent first):**", "");
|
|
117
|
+
for (const [i, r] of similar.entries()) {
|
|
118
|
+
lines.push(formatRecord(r, i), "");
|
|
119
|
+
}
|
|
120
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
server.registerTool(
|
|
124
|
+
"list_open_incidents",
|
|
125
|
+
{
|
|
126
|
+
description: "Lists all currently open incidents tracked by Mergen (fired but not yet resolved). Use this to see what is actively burning and whether any GitHub PRs have been correlated.",
|
|
127
|
+
inputSchema: {}
|
|
128
|
+
},
|
|
129
|
+
async () => {
|
|
130
|
+
trackCall("list_open_incidents");
|
|
131
|
+
const open = memoryStore.listOpen();
|
|
132
|
+
if (open.length === 0) {
|
|
133
|
+
return { content: [{ type: "text", text: "## Open Incidents\n\nNo open incidents. All quiet." }] };
|
|
134
|
+
}
|
|
135
|
+
const lines = [`## Open Incidents (${open.length})`, ""];
|
|
136
|
+
for (const [i, r] of open.entries()) {
|
|
137
|
+
lines.push(formatRecord(r, i), "");
|
|
138
|
+
}
|
|
139
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
server.registerTool(
|
|
143
|
+
"store_agent_memory",
|
|
144
|
+
{
|
|
145
|
+
description: "Persist a key\u2013value memory that survives across sessions. Use this to remember patterns, constraints, or context that you should not have to re-discover the next time you are invoked. Tag memories with `service` and `errorFingerprint` to enable episodic recall \u2014 retrieving past context for the exact (service, error) pair you are working on.",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
key: z.string().min(1).max(200).describe("Short, human-readable identifier for the memory"),
|
|
148
|
+
value: z.string().min(1).max(8e3).describe("The content to remember (plain text or JSON)"),
|
|
149
|
+
agentId: z.string().optional().describe('Identifier for this agent. Defaults to "default"'),
|
|
150
|
+
ttlMs: z.number().int().min(0).optional().describe("Time-to-live in milliseconds. 0 = permanent"),
|
|
151
|
+
service: z.string().optional().describe('Service name for episodic indexing (e.g. "api", "auth-service")'),
|
|
152
|
+
errorFingerprint: z.string().optional().describe("Error fingerprint hash for episodic indexing")
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
async ({ key, value, agentId = "default", ttlMs = 0, service = "", errorFingerprint = "" }) => {
|
|
156
|
+
trackCall("store_agent_memory");
|
|
157
|
+
if (!agentMemoryStore.isHealthy()) {
|
|
158
|
+
return {
|
|
159
|
+
content: [{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: "\u26A0\uFE0F **Agent memory store is unavailable** (SQLite init failed). Memory was NOT persisted. Check server logs for the root cause."
|
|
162
|
+
}]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const entry = agentMemoryStore.store(agentId, key, value, ttlMs, service, errorFingerprint);
|
|
166
|
+
return {
|
|
167
|
+
content: [{ type: "text", text: `Memory stored: "${key}" [id=${entry.id}${service ? `, service=${service}` : ""}]` }]
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
server.registerTool(
|
|
172
|
+
"recall_agent_memory",
|
|
173
|
+
{
|
|
174
|
+
description: "Retrieve previously stored agent memories. Use this at the start of each session to recover patterns and constraints from prior runs. Filter by `service` + `errorFingerprint` for episodic recall \u2014 retrieving exactly the memories relevant to the current incident context.",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
key: z.string().optional().describe("Filter to memories with this exact key"),
|
|
177
|
+
agentId: z.string().optional().describe('Agent ID to scope the recall. Defaults to "default"'),
|
|
178
|
+
limit: z.number().int().min(1).max(50).optional().describe("Max entries to return (default 10)"),
|
|
179
|
+
service: z.string().optional().describe("Filter to memories tagged for this service"),
|
|
180
|
+
errorFingerprint: z.string().optional().describe("Filter to memories tagged for this error fingerprint")
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
async ({ key, agentId = "default", limit = 10, service, errorFingerprint }) => {
|
|
184
|
+
trackCall("recall_agent_memory");
|
|
185
|
+
if (!agentMemoryStore.isHealthy()) {
|
|
186
|
+
return {
|
|
187
|
+
content: [{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: "\u26A0\uFE0F **Agent memory store is unavailable** (SQLite init failed \u2014 check logs for WASM or file error). Memories cannot be retrieved. Call `store_agent_memory` to check if the issue has resolved."
|
|
190
|
+
}]
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const entries = agentMemoryStore.recall(agentId, key, limit, service, errorFingerprint);
|
|
194
|
+
if (entries.length === 0) {
|
|
195
|
+
return { content: [{ type: "text", text: "No memories found." }] };
|
|
196
|
+
}
|
|
197
|
+
const lines = [`## Agent Memories (${entries.length})`, ""];
|
|
198
|
+
for (const e of entries) {
|
|
199
|
+
const expiry = e.ttlMs > 0 ? ` _(expires ${new Date(e.storedAt + e.ttlMs).toISOString()})_` : "";
|
|
200
|
+
const ctx = [e.service, e.errorFingerprint].filter(Boolean).join("/");
|
|
201
|
+
lines.push(`### ${e.key}${ctx ? ` [${ctx}]` : ""}${expiry}`, e.value, "");
|
|
202
|
+
}
|
|
203
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
server.registerTool(
|
|
207
|
+
"list_agent_memory_keys",
|
|
208
|
+
{
|
|
209
|
+
description: "List all memory keys stored for an agent. Use this at the start of a session to discover what was previously stored before calling recall_agent_memory with a specific key. Without this tool, an agent would need to already know the key names \u2014 defeating the purpose of persistent memory.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
agentId: z.string().optional().describe('Agent ID to scope the listing. Defaults to "default".')
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
async ({ agentId = "default" }) => {
|
|
215
|
+
trackCall("list_agent_memory_keys");
|
|
216
|
+
if (!agentMemoryStore.isHealthy()) {
|
|
217
|
+
return {
|
|
218
|
+
content: [{
|
|
219
|
+
type: "text",
|
|
220
|
+
text: "\u26A0\uFE0F **Agent memory store is unavailable** (SQLite init failed). Keys cannot be listed."
|
|
221
|
+
}]
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const keys = agentMemoryStore.listKeys(agentId);
|
|
225
|
+
if (keys.length === 0) {
|
|
226
|
+
return {
|
|
227
|
+
content: [{
|
|
228
|
+
type: "text",
|
|
229
|
+
text: `No memories stored for agent \`${agentId}\`.
|
|
230
|
+
|
|
231
|
+
Call \`store_agent_memory\` to begin persisting context across sessions.`
|
|
232
|
+
}]
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const lines = [
|
|
236
|
+
`## Agent Memory Keys \u2014 \`${agentId}\` (${keys.length} key${keys.length !== 1 ? "s" : ""})`,
|
|
237
|
+
"",
|
|
238
|
+
...keys.map((k) => `- **${k.key}** \u2014 last updated ${new Date(k.lastStoredAt).toISOString().slice(0, 16)} UTC`),
|
|
239
|
+
"",
|
|
240
|
+
'_Call `recall_agent_memory(key: "...")` to retrieve the value for a specific key._'
|
|
241
|
+
];
|
|
242
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
server.registerTool(
|
|
246
|
+
"get_override_patterns",
|
|
247
|
+
{
|
|
248
|
+
description: `Returns the override corpus \u2014 all the times engineers said NO to Mergen's recommendations, categorised by why and when. Use this BEFORE proposing or executing a fix to understand operational constraints that are not encoded in code: Friday batch windows, compliance holds, cost ceilings, or on-call preferences. If a pattern shows "batch-window" overrides every Friday evening for a service, do not propose restarts on Friday evenings. This is the team's institutional knowledge in structured form.`,
|
|
249
|
+
inputSchema: {
|
|
250
|
+
service: z.string().optional().describe("Filter to override patterns for a specific service."),
|
|
251
|
+
tag: z.string().optional().describe('Filter to a specific failure mode tag (e.g. "connection_pool_exhausted").'),
|
|
252
|
+
limit: z.number().int().min(1).max(50).optional().describe("Max patterns to return (default 20).")
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
async ({ service, tag, limit = 20 }) => {
|
|
256
|
+
trackCall("get_override_patterns");
|
|
257
|
+
if (tag) {
|
|
258
|
+
const normalizedTag = tag.startsWith("infra_") ? tag : tag;
|
|
259
|
+
const events = getOverridesForTag(normalizedTag).filter((e) => !service || e.service === service).slice(-limit);
|
|
260
|
+
if (events.length === 0) {
|
|
261
|
+
return {
|
|
262
|
+
content: [{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: `## Override Patterns \u2014 \`${normalizedTag}\`
|
|
265
|
+
|
|
266
|
+
No overrides recorded for this failure mode${service ? ` on service \`${service}\`` : ""}.
|
|
267
|
+
|
|
268
|
+
_If Mergen has never been overridden for this (tag, service) pair, autonomous execution is not blocked by the corpus._`
|
|
269
|
+
}]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
273
|
+
const lines2 = [
|
|
274
|
+
`## Override Patterns \u2014 \`${normalizedTag}\`${service ? ` \xB7 service: \`${service}\`` : ""}`,
|
|
275
|
+
"",
|
|
276
|
+
`**${events.length} override${events.length !== 1 ? "s" : ""} recorded**`,
|
|
277
|
+
"",
|
|
278
|
+
"| Date | Service | Reason | Time (UTC) | Outcome |",
|
|
279
|
+
"|------|---------|--------|------------|---------|",
|
|
280
|
+
...events.map((e) => {
|
|
281
|
+
const date = new Date(e.recordedAt).toISOString().slice(0, 10);
|
|
282
|
+
const time = `${DAY_NAMES[e.dayOfWeek]} ${e.hourOfDay}:00`;
|
|
283
|
+
const outcome = e.outcome ?? "unknown";
|
|
284
|
+
return `| ${date} | ${e.service} | ${e.overrideReason} | ${time} | ${outcome} |`;
|
|
285
|
+
}),
|
|
286
|
+
"",
|
|
287
|
+
(() => {
|
|
288
|
+
if (events.length === 0) return "";
|
|
289
|
+
const counts = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const ev of events) counts.set(ev.overrideReason, (counts.get(ev.overrideReason) ?? 0) + 1);
|
|
291
|
+
let topReason = events[0].overrideReason;
|
|
292
|
+
let topCount = 0;
|
|
293
|
+
for (const [r, c] of counts) if (c > topCount) {
|
|
294
|
+
topReason = r;
|
|
295
|
+
topCount = c;
|
|
296
|
+
}
|
|
297
|
+
return `_Dominant pattern: \`${topReason}\` \u2014 check time pattern before proposing a fix._`;
|
|
298
|
+
})()
|
|
299
|
+
];
|
|
300
|
+
return { content: [{ type: "text", text: lines2.filter(Boolean).join("\n") }] };
|
|
301
|
+
}
|
|
302
|
+
const summaries = getOverrideSummary().filter((s) => !service || s.services.includes(service)).slice(0, limit);
|
|
303
|
+
if (summaries.length === 0) {
|
|
304
|
+
return {
|
|
305
|
+
content: [{
|
|
306
|
+
type: "text",
|
|
307
|
+
text: "## Override Patterns\n\nNo overrides recorded yet.\n\n_The override corpus grows automatically as engineers decline Mergen's recommendations. POST /overrides to record a manual override._"
|
|
308
|
+
}]
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const totalOverrides = summaries.reduce((s, x) => s + x.total, 0);
|
|
312
|
+
const lines = [
|
|
313
|
+
"## Override Patterns \u2014 Corpus Summary",
|
|
314
|
+
"",
|
|
315
|
+
`**${totalOverrides} total overrides** across **${summaries.length} failure modes**`,
|
|
316
|
+
"",
|
|
317
|
+
"| Failure Mode | Overrides | Dominant Reason | Time Pattern | Services |",
|
|
318
|
+
"|---|---|---|---|---|",
|
|
319
|
+
...summaries.map((s) => {
|
|
320
|
+
const tp = s.timePattern ?? "\u2014";
|
|
321
|
+
const sv = s.services.slice(0, 3).join(", ") + (s.services.length > 3 ? "\u2026" : "");
|
|
322
|
+
return `| \`${s.tag}\` | ${s.total} | ${s.dominantReason ?? "\u2014"} | ${tp} | ${sv} |`;
|
|
323
|
+
}),
|
|
324
|
+
"",
|
|
325
|
+
"---",
|
|
326
|
+
'_Call `get_override_patterns(tag: "...")` for full history of a specific failure mode._',
|
|
327
|
+
"_Before proposing a fix, check whether the (tag, service, time) combination has been overridden before._"
|
|
328
|
+
];
|
|
329
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
server.registerTool(
|
|
333
|
+
"get_detector_calibration",
|
|
334
|
+
{
|
|
335
|
+
description: "Returns calibration accuracy for Mergen's detectors \u2014 how often each diagnosis type has been correct, wrong, or partial based on production feedback. Use this to understand how reliable a specific hypothesis is before acting on it. A detector with 90% accuracy and 30+ verdicts is highly trusted. One with 50% accuracy or fewer than 5 verdicts is uncertain \u2014 require higher confidence or manual review. Also returns the 7-day accuracy trend to detect detectors that are drifting.",
|
|
336
|
+
inputSchema: {
|
|
337
|
+
tag: z.string().optional().describe('Specific failure mode tag to look up (e.g. "connection_pool_exhausted"). Omit to return all detectors.')
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
async ({ tag }) => {
|
|
341
|
+
trackCall("get_detector_calibration");
|
|
342
|
+
const seededWarning = isCorpusSeeded() ? "\n> \u26A0\uFE0F **SYNTHETIC PRIORS ACTIVE** \u2014 no production verdicts have been recorded yet (or all have expired). The accuracy figures below are developer estimates, not measurements from this system's incident history. Do not use them to gate autonomous execution decisions.\n" : "";
|
|
343
|
+
if (tag) {
|
|
344
|
+
const normalizedTag = tag.startsWith("infra_") ? tag : tag;
|
|
345
|
+
const stats = getStatsForTag(normalizedTag);
|
|
346
|
+
if (!stats) {
|
|
347
|
+
return {
|
|
348
|
+
content: [{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: [
|
|
351
|
+
`## Detector Calibration \u2014 \`${normalizedTag}\``,
|
|
352
|
+
seededWarning,
|
|
353
|
+
"_No calibration data yet for this detector._",
|
|
354
|
+
"",
|
|
355
|
+
`Calibration builds automatically as incidents of type \`${normalizedTag}\` are diagnosed and verdicts recorded.`,
|
|
356
|
+
"With fewer than 5 verdicts, execution decisions use the built-in prior (developer estimate)."
|
|
357
|
+
].filter(Boolean).join("\n")
|
|
358
|
+
}]
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const trustLabel = stats.trusted ? `\u2705 TRUSTED (${stats.verdicts} verdicts)` : `\u26A0\uFE0F PRIOR ESTIMATE (${stats.verdicts}/${5} verdicts needed)`;
|
|
362
|
+
const trendLabel = stats.trendDelta !== null ? stats.trendDelta > 0.05 ? `\u{1F4C8} Improving (+${Math.round(stats.trendDelta * 100)}% vs all-time)` : stats.trendDelta < -0.05 ? `\u{1F4C9} Degrading (${Math.round(stats.trendDelta * 100)}% vs all-time)` : `\u2192 Stable` : "\u2014 insufficient 7-day data";
|
|
363
|
+
const shouldAct = stats.accuracy >= 0.7 ? "\u2705 Proceed with `execute_fix` if confidence \u2265 threshold" : stats.accuracy >= 0.5 ? "\u26A0\uFE0F Mixed accuracy \u2014 require confidence \u2265 0.90 or manual review" : "\u{1F6AB} Poor accuracy \u2014 do not auto-execute; manual review required";
|
|
364
|
+
const tagRecs = getRecords().filter((r) => r.tag === normalizedTag);
|
|
365
|
+
const realVerdicts = tagRecs.filter((r) => !r.isBuiltinSeed && r.verdict !== null).length;
|
|
366
|
+
const syntheticVerdicts = tagRecs.filter((r) => r.isBuiltinSeed).length;
|
|
367
|
+
const verdictBreakdown = realVerdicts > 0 ? `${realVerdicts} real \xB7 ${syntheticVerdicts} synthetic priors` : `0 real \xB7 ${syntheticVerdicts} synthetic priors _(accuracy based on estimates only)_`;
|
|
368
|
+
const lines2 = [
|
|
369
|
+
`## Detector Calibration \u2014 \`${normalizedTag}\``,
|
|
370
|
+
seededWarning,
|
|
371
|
+
`**Status:** ${trustLabel}`,
|
|
372
|
+
`**All-time accuracy:** ${Math.round(stats.accuracy * 100)}%`,
|
|
373
|
+
`**7-day accuracy:** ${stats.accuracy7d !== null ? Math.round(stats.accuracy7d * 100) + "%" : "\u2014"}`,
|
|
374
|
+
`**Trend:** ${trendLabel}`,
|
|
375
|
+
`**Total predictions:** ${stats.predictions}`,
|
|
376
|
+
`**Verdicts:** ${stats.verdicts} (correct: ${Math.round(stats.diagnosisAccuracy * 100)}%, remediation: ${stats.remediationAccuracy !== null ? Math.round(stats.remediationAccuracy * 100) + "%" : "\u2014"})`,
|
|
377
|
+
`**Verdict breakdown:** ${verdictBreakdown}`,
|
|
378
|
+
"",
|
|
379
|
+
`**Recommended action:** ${shouldAct}`,
|
|
380
|
+
"",
|
|
381
|
+
stats.commonFailureModes.length > 0 ? [
|
|
382
|
+
"**Common reasons for wrong diagnoses:**",
|
|
383
|
+
...stats.commonFailureModes.slice(0, 3).map((m) => `- "${m.note}" (${m.count}\xD7)`)
|
|
384
|
+
].join("\n") : ""
|
|
385
|
+
].filter(Boolean);
|
|
386
|
+
const rationaleNotes = tagRecs.filter((r) => !r.isBuiltinSeed && r.note && (r.verdict === "correct" || r.verdict === "partial")).map((r) => r.note).slice(-5);
|
|
387
|
+
if (rationaleNotes.length > 0) {
|
|
388
|
+
lines2.push(
|
|
389
|
+
"",
|
|
390
|
+
"**Recorded fix rationale (why past fixes worked):**",
|
|
391
|
+
...rationaleNotes.map((n) => `- "${n}"`)
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
return { content: [{ type: "text", text: lines2.join("\n") }] };
|
|
395
|
+
}
|
|
396
|
+
const allStats = getStats().filter((s) => s.predictions > 0);
|
|
397
|
+
if (allStats.length === 0) {
|
|
398
|
+
return {
|
|
399
|
+
content: [{
|
|
400
|
+
type: "text",
|
|
401
|
+
text: "## Detector Calibration\n\nNo calibration data yet.\n\nCalibration builds automatically as incidents are diagnosed and resolved. Each correct/wrong verdict updates the accuracy scores shown here."
|
|
402
|
+
}]
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const trusted = allStats.filter((s) => s.trusted);
|
|
406
|
+
const untrusted = allStats.filter((s) => !s.trusted);
|
|
407
|
+
const avgAccuracy = trusted.length > 0 ? trusted.reduce((sum, s) => sum + s.accuracy, 0) / trusted.length : null;
|
|
408
|
+
const lines = [
|
|
409
|
+
"## Detector Calibration \u2014 All Detectors",
|
|
410
|
+
seededWarning,
|
|
411
|
+
`**${allStats.length} detectors tracked** \xB7 **${trusted.length} trusted** (\u22655 verdicts) \xB7 **${untrusted.length} on prior estimate**`,
|
|
412
|
+
avgAccuracy !== null ? `**Mean accuracy (trusted):** ${Math.round(avgAccuracy * 100)}%` : "",
|
|
413
|
+
"",
|
|
414
|
+
"### Trusted Detectors",
|
|
415
|
+
"",
|
|
416
|
+
"| Tag | Accuracy | 7d Trend | Verdicts | Suppress? |",
|
|
417
|
+
"|-----|----------|----------|----------|-----------|",
|
|
418
|
+
...trusted.sort((a, b) => b.accuracy - a.accuracy).map((s) => {
|
|
419
|
+
const acc7d = s.accuracy7d !== null ? Math.round(s.accuracy7d * 100) + "%" : "\u2014";
|
|
420
|
+
const trend = s.trendDelta !== null ? s.trendDelta > 0.05 ? "\u{1F4C8}" : s.trendDelta < -0.05 ? "\u{1F4C9}" : "\u2192" : "\u2014";
|
|
421
|
+
const suppress = s.shouldInterrupt ? "\u{1F6AB} YES" : "\u2014";
|
|
422
|
+
return `| \`${s.tag}\` | ${Math.round(s.accuracy * 100)}% | ${trend} ${acc7d} | ${s.verdicts} | ${suppress} |`;
|
|
423
|
+
}),
|
|
424
|
+
"",
|
|
425
|
+
untrusted.length > 0 ? [
|
|
426
|
+
"### On Prior Estimate (< 5 verdicts)",
|
|
427
|
+
"",
|
|
428
|
+
untrusted.map((s) => `- \`${s.tag}\`: ${s.predictions} predictions, ${s.verdicts} verdict${s.verdicts !== 1 ? "s" : ""}`).join("\n")
|
|
429
|
+
].join("\n") : "",
|
|
430
|
+
"",
|
|
431
|
+
"---",
|
|
432
|
+
"_A detector is suppressed (\u{1F6AB}) when accuracy falls below 20% on trusted data \u2014 Mergen will stop surfacing it until the corpus improves._"
|
|
433
|
+
].filter(Boolean);
|
|
434
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
server.registerTool(
|
|
438
|
+
"check_staged_changes",
|
|
439
|
+
{
|
|
440
|
+
description: "Checks a list of files or staged changes against the operational database to see if they have been involved in past outages or conflicts. Always call this BEFORE applying a change or proposing a fix to make sure your AI assistant is not confidently wrong and repeating a previous failure.",
|
|
441
|
+
inputSchema: {
|
|
442
|
+
files: z.array(z.string()).describe('List of files changed or intended to change (e.g. ["server/src/auth.ts", "package.json"])'),
|
|
443
|
+
prTitle: z.string().optional().describe("The proposed changes description or commit message"),
|
|
444
|
+
service: z.string().optional().describe("Service name if known")
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
async ({ files, prTitle = "", service = "" }) => {
|
|
448
|
+
trackCall("check_staged_changes");
|
|
449
|
+
const matchedIncidents = [];
|
|
450
|
+
const matchedOverrides = [];
|
|
451
|
+
if (memoryStore.isHealthy()) {
|
|
452
|
+
const allIncidents = memoryStore.listAll(1e3);
|
|
453
|
+
for (const file of files) {
|
|
454
|
+
const matching = allIncidents.filter(
|
|
455
|
+
(r) => r.implicatedFile && r.implicatedFile.toLowerCase().includes(file.toLowerCase())
|
|
456
|
+
);
|
|
457
|
+
matchedIncidents.push(...matching);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const summary = getOverrideSummary();
|
|
461
|
+
for (const item of summary) {
|
|
462
|
+
if (service && item.services.includes(service)) {
|
|
463
|
+
matchedOverrides.push(item);
|
|
464
|
+
} else {
|
|
465
|
+
const cleanTag = item.tag.replace(/_/g, " ").toLowerCase();
|
|
466
|
+
const cleanTitle = prTitle.toLowerCase();
|
|
467
|
+
if (cleanTitle.includes(cleanTag) || files.some((f) => f.toLowerCase().includes(item.tag.replace(/^infra_/, "").replace(/_/g, "")))) {
|
|
468
|
+
matchedOverrides.push(item);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (matchedIncidents.length === 0 && matchedOverrides.length === 0) {
|
|
473
|
+
return {
|
|
474
|
+
content: [{
|
|
475
|
+
type: "text",
|
|
476
|
+
text: "\u2705 **No past failures or override patterns match your proposed changes.** Safe to proceed."
|
|
477
|
+
}]
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const lines = [
|
|
481
|
+
`> \u{1F4A1} **Visible Intelligence: "My AI agent is confidently wrong"**`,
|
|
482
|
+
`> `,
|
|
483
|
+
`> \u26A0\uFE0F **Warning: Your proposed changes touch files or logic with a history of operational failures:**`
|
|
484
|
+
];
|
|
485
|
+
const uniqueIncidents = Array.from(new Map(matchedIncidents.map((i) => [i.id, i])).values());
|
|
486
|
+
for (const inc of uniqueIncidents.slice(0, 5)) {
|
|
487
|
+
const dateDesc = new Date(inc.firedAt).toISOString().slice(0, 10);
|
|
488
|
+
lines.push(
|
|
489
|
+
`> - **File:** \`${inc.implicatedFile}\` previously caused **Incident #${inc.id}** (${dateDesc})`,
|
|
490
|
+
`> *Error:* ${inc.errorMessage.slice(0, 100)}`,
|
|
491
|
+
`> *Last Fix:* ${inc.fixPrTitle || inc.fixSummary || "Not specified"}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
const uniqueOverrides = Array.from(new Map(matchedOverrides.map((o) => [o.tag, o])).values());
|
|
495
|
+
if (uniqueOverrides.length > 0) {
|
|
496
|
+
lines.push(`>`, `> \u{1F6AB} **Active constraints from past overrides:**`);
|
|
497
|
+
for (const ov of uniqueOverrides.slice(0, 5)) {
|
|
498
|
+
lines.push(
|
|
499
|
+
`> - \`${ov.tag}\` has been overridden **${ov.total} times** (Dominant reason: \`${ov.dominantReason || "unknown"}\`)`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
content: [{
|
|
505
|
+
type: "text",
|
|
506
|
+
text: lines.join("\n")
|
|
507
|
+
}]
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
export {
|
|
513
|
+
registerMemoryTools
|
|
514
|
+
};
|