@praneeth_54/agentdoctor 1.1.1 → 2.0.1
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/CHANGELOG.md +164 -7
- package/README.md +263 -513
- package/dist/agents/aider/adapter.d.ts +1 -0
- package/dist/agents/aider/adapter.js +1 -0
- package/dist/agents/aider/detector.d.ts +14 -0
- package/dist/agents/aider/detector.js +120 -0
- package/dist/agents/copilot/adapter.d.ts +1 -0
- package/dist/agents/copilot/adapter.js +1 -0
- package/dist/agents/copilot/detector.d.ts +13 -0
- package/dist/agents/copilot/detector.js +113 -0
- package/dist/agents/gemini/adapter.d.ts +1 -0
- package/dist/agents/gemini/adapter.js +1 -0
- package/dist/agents/gemini/detector.d.ts +13 -0
- package/dist/agents/gemini/detector.js +151 -0
- package/dist/agents/registry.js +13 -1
- package/dist/agents/types.d.ts +1 -1
- package/dist/agents/windsurf/adapter.d.ts +1 -0
- package/dist/agents/windsurf/adapter.js +1 -0
- package/dist/agents/windsurf/detector.d.ts +14 -0
- package/dist/agents/windsurf/detector.js +119 -0
- package/dist/architecture/c4.d.ts +22 -0
- package/dist/architecture/c4.js +62 -0
- package/dist/architecture/contract.d.ts +64 -0
- package/dist/architecture/contract.js +373 -0
- package/dist/assurance/change.d.ts +211 -0
- package/dist/assurance/change.js +624 -0
- package/dist/assurance/proof.d.ts +136 -0
- package/dist/assurance/proof.js +338 -0
- package/dist/auth/index.d.ts +81 -0
- package/dist/auth/index.js +130 -0
- package/dist/auth/rbac.d.ts +9 -0
- package/dist/auth/rbac.js +26 -0
- package/dist/cli/commands/architecture.d.ts +6 -0
- package/dist/cli/commands/architecture.js +109 -0
- package/dist/cli/commands/assurance.d.ts +67 -0
- package/dist/cli/commands/assurance.js +210 -0
- package/dist/cli/commands/brain.d.ts +12 -0
- package/dist/cli/commands/brain.js +162 -0
- package/dist/cli/commands/complete.d.ts +23 -0
- package/dist/cli/commands/complete.js +232 -0
- package/dist/cli/commands/explain.js +1 -1
- package/dist/cli/commands/fix.js +22 -2
- package/dist/cli/commands/mcp.d.ts +8 -0
- package/dist/cli/commands/mcp.js +14 -0
- package/dist/cli/commands/platform.d.ts +17 -0
- package/dist/cli/commands/platform.js +195 -0
- package/dist/cli/commands/policy-graph-run.d.ts +40 -0
- package/dist/cli/commands/policy-graph-run.js +200 -0
- package/dist/cli/commands/v2.d.ts +55 -0
- package/dist/cli/commands/v2.js +278 -0
- package/dist/cli/commands/workspace.d.ts +9 -0
- package/dist/cli/commands/workspace.js +82 -0
- package/dist/cli/program.js +1328 -3
- package/dist/constants.d.ts +3 -2
- package/dist/constants.js +5 -1
- package/dist/contracts/adapters.d.ts +7 -0
- package/dist/contracts/adapters.js +60 -0
- package/dist/contracts/index.d.ts +162 -0
- package/dist/contracts/index.js +18 -0
- package/dist/core/baseline/store.d.ts +48 -0
- package/dist/core/baseline/store.js +163 -0
- package/dist/core/brain-cli/service.d.ts +26 -0
- package/dist/core/brain-cli/service.js +175 -0
- package/dist/core/brain-product/init.d.ts +45 -0
- package/dist/core/brain-product/init.js +235 -0
- package/dist/core/changes/analyze.d.ts +41 -0
- package/dist/core/changes/analyze.js +223 -0
- package/dist/core/context-health/analyze.d.ts +15 -0
- package/dist/core/context-health/analyze.js +234 -0
- package/dist/core/fix/apply.d.ts +21 -4
- package/dist/core/fix/apply.js +118 -18
- package/dist/core/fix/backup.d.ts +30 -0
- package/dist/core/fix/backup.js +193 -0
- package/dist/core/fix/plan.d.ts +6 -3
- package/dist/core/fix/plan.js +113 -9
- package/dist/core/fix/render.d.ts +2 -0
- package/dist/core/fix/render.js +36 -21
- package/dist/core/fix/run.js +5 -1
- package/dist/core/fix/safe-target.d.ts +14 -0
- package/dist/core/fix/safe-target.js +81 -0
- package/dist/core/fix/types.d.ts +12 -1
- package/dist/core/fix/types.js +2 -0
- package/dist/core/fix/writers/claude-settings.js +3 -1
- package/dist/core/fix/writers/codex-config.d.ts +3 -0
- package/dist/core/fix/writers/codex-config.js +24 -3
- package/dist/core/fix/writers/cursorignore.js +3 -1
- package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
- package/dist/core/fix/writers/simple-ignore.js +68 -0
- package/dist/core/monorepo/detect.d.ts +13 -0
- package/dist/core/monorepo/detect.js +121 -0
- package/dist/core/rules/build-context.js +22 -3
- package/dist/core/rules/context/generated-directory.js +11 -1
- package/dist/core/rules/context/large-instruction-file.js +6 -0
- package/dist/core/rules/context/large-log-file.js +16 -1
- package/dist/core/rules/ignore.d.ts +11 -1
- package/dist/core/rules/ignore.js +22 -1
- package/dist/core/rules/instructions/empty-instructions.js +7 -0
- package/dist/core/rules/security/env-file-exposure.js +23 -2
- package/dist/core/rules/security/private-key-file.js +1 -1
- package/dist/core/scanner/scan.js +1 -1
- package/dist/core/schemas/validate.d.ts +6 -0
- package/dist/core/schemas/validate.js +62 -0
- package/dist/core/scoring/compute-scores.d.ts +1 -1
- package/dist/core/scoring/compute-scores.js +5 -1
- package/dist/core/scoring/placeholder.d.ts +1 -1
- package/dist/core/scoring/placeholder.js +5 -1
- package/dist/core/secrets/scan.d.ts +29 -0
- package/dist/core/secrets/scan.js +172 -0
- package/dist/coverage/cobertura.d.ts +6 -0
- package/dist/coverage/cobertura.js +52 -0
- package/dist/coverage/istanbul.d.ts +7 -0
- package/dist/coverage/istanbul.js +134 -0
- package/dist/coverage/lcov.d.ts +7 -0
- package/dist/coverage/lcov.js +59 -0
- package/dist/coverage/load.d.ts +9 -0
- package/dist/coverage/load.js +39 -0
- package/dist/coverage/types.d.ts +23 -0
- package/dist/coverage/types.js +5 -0
- package/dist/dashboard/server.d.ts +24 -0
- package/dist/dashboard/server.js +402 -0
- package/dist/enforcement/runner.d.ts +67 -0
- package/dist/enforcement/runner.js +460 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +25 -0
- package/dist/integrations/github/pr-analyze.d.ts +28 -0
- package/dist/integrations/github/pr-analyze.js +98 -0
- package/dist/integrations/local-ai/provider.d.ts +45 -0
- package/dist/integrations/local-ai/provider.js +104 -0
- package/dist/intelligence/git/analyze.d.ts +34 -0
- package/dist/intelligence/git/analyze.js +136 -0
- package/dist/intelligence/graph/build.d.ts +15 -0
- package/dist/intelligence/graph/build.js +259 -0
- package/dist/intelligence/graph/incremental.d.ts +68 -0
- package/dist/intelligence/graph/incremental.js +234 -0
- package/dist/intelligence/resolve/imports.d.ts +36 -0
- package/dist/intelligence/resolve/imports.js +245 -0
- package/dist/knowledge/store.d.ts +25 -0
- package/dist/knowledge/store.js +91 -0
- package/dist/languages/go.d.ts +9 -0
- package/dist/languages/go.js +45 -0
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.js +76 -0
- package/dist/languages/php.d.ts +7 -0
- package/dist/languages/php.js +193 -0
- package/dist/languages/python.d.ts +7 -0
- package/dist/languages/python.js +150 -0
- package/dist/languages/types.d.ts +42 -0
- package/dist/languages/types.js +26 -0
- package/dist/languages/typescript.d.ts +3 -0
- package/dist/languages/typescript.js +95 -0
- package/dist/mcp/agentdoctor/server.d.ts +16 -0
- package/dist/mcp/agentdoctor/server.js +67 -0
- package/dist/mcp/brain/session.js +7 -7
- package/dist/mcp/intelligence/handlers.d.ts +16 -0
- package/dist/mcp/intelligence/handlers.js +364 -0
- package/dist/mcp/intelligence/path-safety.d.ts +9 -0
- package/dist/mcp/intelligence/path-safety.js +63 -0
- package/dist/mcp/intelligence/registry.d.ts +8 -0
- package/dist/mcp/intelligence/registry.js +228 -0
- package/dist/ops/health.d.ts +18 -0
- package/dist/ops/health.js +51 -0
- package/dist/platform/ai-quality/analyze.d.ts +9 -0
- package/dist/platform/ai-quality/analyze.js +65 -0
- package/dist/platform/architecture/drift.d.ts +17 -0
- package/dist/platform/architecture/drift.js +74 -0
- package/dist/platform/auth/local.d.ts +15 -0
- package/dist/platform/auth/local.js +58 -0
- package/dist/platform/context-security/analyze.d.ts +6 -0
- package/dist/platform/context-security/analyze.js +97 -0
- package/dist/platform/firewall/evaluate.d.ts +57 -0
- package/dist/platform/firewall/evaluate.js +359 -0
- package/dist/platform/graph/build.d.ts +6 -0
- package/dist/platform/graph/build.js +189 -0
- package/dist/platform/health/analyze.d.ts +7 -0
- package/dist/platform/health/analyze.js +192 -0
- package/dist/platform/index.d.ts +27 -0
- package/dist/platform/index.js +89 -0
- package/dist/platform/knowledge/analyze.d.ts +20 -0
- package/dist/platform/knowledge/analyze.js +96 -0
- package/dist/platform/provenance/build.d.ts +44 -0
- package/dist/platform/provenance/build.js +65 -0
- package/dist/platform/readiness/scorecard.d.ts +5 -0
- package/dist/platform/readiness/scorecard.js +98 -0
- package/dist/platform/refactor/impact.d.ts +20 -0
- package/dist/platform/refactor/impact.js +60 -0
- package/dist/platform/reports/export.d.ts +12 -0
- package/dist/platform/reports/export.js +92 -0
- package/dist/platform/security/redact.d.ts +15 -0
- package/dist/platform/security/redact.js +87 -0
- package/dist/platform/sessions/store.d.ts +32 -0
- package/dist/platform/sessions/store.js +97 -0
- package/dist/platform/store.d.ts +6 -0
- package/dist/platform/store.js +57 -0
- package/dist/platform/test-impact/analyze.d.ts +50 -0
- package/dist/platform/test-impact/analyze.js +350 -0
- package/dist/platform/time-machine/compare.d.ts +17 -0
- package/dist/platform/time-machine/compare.js +62 -0
- package/dist/platform/tokens/plan.d.ts +28 -0
- package/dist/platform/tokens/plan.js +108 -0
- package/dist/platform/types.d.ts +120 -0
- package/dist/platform/types.js +5 -0
- package/dist/plugins/runtime.d.ts +36 -0
- package/dist/plugins/runtime.js +101 -0
- package/dist/plugins/sdk.d.ts +29 -0
- package/dist/plugins/sdk.js +114 -0
- package/dist/policy/compose.d.ts +34 -0
- package/dist/policy/compose.js +118 -0
- package/dist/policy/packs.d.ts +9 -0
- package/dist/policy/packs.js +91 -0
- package/dist/reporters/terminal/report.js +38 -5
- package/dist/security/paths.d.ts +21 -0
- package/dist/security/paths.js +105 -0
- package/dist/storage/postgres.d.ts +26 -0
- package/dist/storage/postgres.js +90 -0
- package/dist/storage/provider.d.ts +30 -0
- package/dist/storage/provider.js +109 -0
- package/dist/storage/sqlite.d.ts +33 -0
- package/dist/storage/sqlite.js +77 -0
- package/dist/team/auth.d.ts +35 -0
- package/dist/team/auth.js +70 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/workspace/index.d.ts +69 -0
- package/dist/workspace/index.js +220 -0
- package/package.json +15 -5
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { PACKAGE_VERSION } from "../../constants.js";
|
|
5
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
6
|
+
import { BrainMcpSession } from "../brain/session.js";
|
|
7
|
+
import { BRAIN_MCP_TOOL_NAMES, invokeBrainMcpTool, listBrainMcpTools, } from "../brain/tools/registry.js";
|
|
8
|
+
import { INTELLIGENCE_MCP_TOOL_NAMES, invokeIntelligenceMcpTool, listIntelligenceMcpTools, } from "../intelligence/registry.js";
|
|
9
|
+
/**
|
|
10
|
+
* Combined AgentDoctor MCP server: preserves all Brain tool names and adds
|
|
11
|
+
* intelligence tools. No arbitrary shell execution; policy_evaluate is evaluate-only.
|
|
12
|
+
*/
|
|
13
|
+
export async function startAgentDoctorMcpServer(options) {
|
|
14
|
+
const root = resolveRepoRoot(options.root);
|
|
15
|
+
const session = new BrainMcpSession({
|
|
16
|
+
root,
|
|
17
|
+
buildIfMissing: options.buildIfMissing !== false,
|
|
18
|
+
});
|
|
19
|
+
await session.initialize();
|
|
20
|
+
const brainNames = new Set(BRAIN_MCP_TOOL_NAMES);
|
|
21
|
+
const intelNames = new Set(INTELLIGENCE_MCP_TOOL_NAMES);
|
|
22
|
+
const server = new Server({
|
|
23
|
+
name: "agentdoctor",
|
|
24
|
+
version: PACKAGE_VERSION,
|
|
25
|
+
}, {
|
|
26
|
+
capabilities: {
|
|
27
|
+
tools: {},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
31
|
+
tools: [...listBrainMcpTools(), ...listIntelligenceMcpTools()],
|
|
32
|
+
}));
|
|
33
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
34
|
+
const name = request.params.name;
|
|
35
|
+
const args = (request.params.arguments ?? {});
|
|
36
|
+
let structured;
|
|
37
|
+
let isError = false;
|
|
38
|
+
if (brainNames.has(name)) {
|
|
39
|
+
const result = await invokeBrainMcpTool(session, name, args);
|
|
40
|
+
structured = result.structured;
|
|
41
|
+
isError = result.isError;
|
|
42
|
+
}
|
|
43
|
+
else if (intelNames.has(name)) {
|
|
44
|
+
const result = await invokeIntelligenceMcpTool(root, name, args);
|
|
45
|
+
structured = result.structured;
|
|
46
|
+
isError = result.isError;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
structured = {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: { code: "invalid_argument", message: `unknown tool: ${name}` },
|
|
52
|
+
};
|
|
53
|
+
isError = true;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: JSON.stringify(structured) }],
|
|
57
|
+
structuredContent: structured,
|
|
58
|
+
isError,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
const transport = new StdioServerTransport();
|
|
62
|
+
await server.connect(transport);
|
|
63
|
+
return { server, session, root };
|
|
64
|
+
}
|
|
65
|
+
export async function runAgentDoctorMcpStdio(options) {
|
|
66
|
+
await startAgentDoctorMcpServer(options);
|
|
67
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LocalBrainStore, } from "../../core/understanding/brain/index.js";
|
|
1
|
+
import { LocalBrainStore, redactBrainForStorage, } from "../../core/understanding/brain/index.js";
|
|
2
2
|
import { compileProjectBrain } from "./compile.js";
|
|
3
3
|
import { BrainMcpError } from "./errors.js";
|
|
4
4
|
import { resolveProjectRoot } from "./security/root.js";
|
|
@@ -32,8 +32,8 @@ export class BrainMcpSession {
|
|
|
32
32
|
throw new BrainMcpError("brain_corrupt", message);
|
|
33
33
|
}
|
|
34
34
|
if (loaded) {
|
|
35
|
-
this.brain = loaded;
|
|
36
|
-
this.log(`agentdoctor brain-mcp: loaded snapshot ${
|
|
35
|
+
this.brain = redactBrainForStorage(loaded);
|
|
36
|
+
this.log(`agentdoctor brain-mcp: loaded snapshot ${this.brain.snapshot.id} from ${this.root}`);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
if (!this.buildIfMissing) {
|
|
@@ -84,15 +84,15 @@ export class BrainMcpSession {
|
|
|
84
84
|
: new BrainMcpError("brain_corrupt", message || "failed to save snapshot");
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
this.brain = brain;
|
|
88
|
-
return brain;
|
|
87
|
+
this.brain = redactBrainForStorage(brain);
|
|
88
|
+
return this.brain;
|
|
89
89
|
}
|
|
90
90
|
async loadSnapshot(snapshotId) {
|
|
91
91
|
const store = this.getStore();
|
|
92
92
|
try {
|
|
93
93
|
const brain = await store.loadSnapshot(snapshotId);
|
|
94
|
-
this.brain = brain;
|
|
95
|
-
return brain;
|
|
94
|
+
this.brain = redactBrainForStorage(brain);
|
|
95
|
+
return this.brain;
|
|
96
96
|
}
|
|
97
97
|
catch (error) {
|
|
98
98
|
const message = error instanceof Error ? error.message : "failed to load snapshot";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function handleRepoOverview(rootInput: string): Promise<unknown>;
|
|
2
|
+
export declare function handleCodebaseSearch(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
3
|
+
export declare function handleSymbolLookup(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
4
|
+
export declare function handleDependencyLookup(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
5
|
+
export declare function handleCallGraphLookup(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
export declare function handleTestImpactTool(rootInput: string): Promise<unknown>;
|
|
7
|
+
export declare function handleRefactorImpactTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
8
|
+
export declare function handleCodeHealthTool(rootInput: string): Promise<unknown>;
|
|
9
|
+
export declare function handleArchitectureTool(rootInput: string): Promise<unknown>;
|
|
10
|
+
export declare function handleKnowledgeTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
11
|
+
export declare function handlePolicyEvalTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
12
|
+
export declare function handleChangeAnalyzeTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
13
|
+
export declare function handleArchitectureCheckTool(rootInput: string): Promise<unknown>;
|
|
14
|
+
export declare function handleProofInspectTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
15
|
+
export declare function handleEvidenceInspectTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
16
|
+
export declare function handleGraphQueryTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { buildC4Views } from "../../architecture/c4.js";
|
|
2
|
+
import { checkArchitectureAtRoot } from "../../architecture/contract.js";
|
|
3
|
+
import { analyzeChange, inspectEvidence } from "../../assurance/change.js";
|
|
4
|
+
import { inspectProof } from "../../assurance/proof.js";
|
|
5
|
+
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
6
|
+
import { graphStatus } from "../../intelligence/graph/incremental.js";
|
|
7
|
+
import { analyzeGitIntelligence } from "../../intelligence/git/analyze.js";
|
|
8
|
+
import { listKnowledge, retrieveAuthoritative } from "../../knowledge/store.js";
|
|
9
|
+
import { analyzeRenameImpact, analyzeTestImpact, buildRepositoryGraph, evaluateAgentAction, } from "../../platform/index.js";
|
|
10
|
+
import { redactSecrets } from "../../platform/security/redact.js";
|
|
11
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
12
|
+
import { CONTRACTS_VERSION } from "../../contracts/index.js";
|
|
13
|
+
import { assertSafeRepoTarget } from "./path-safety.js";
|
|
14
|
+
function requireString(args, key) {
|
|
15
|
+
const value = args[key];
|
|
16
|
+
if (typeof value !== "string")
|
|
17
|
+
return null;
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
20
|
+
}
|
|
21
|
+
function redactDeep(value) {
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
return redactSecrets(value).text;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
return value.map(redactDeep);
|
|
27
|
+
}
|
|
28
|
+
if (value && typeof value === "object") {
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [k, v] of Object.entries(value)) {
|
|
31
|
+
out[k] = redactDeep(v);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function invalidArgument(message) {
|
|
38
|
+
return { ok: false, error: { code: "invalid_argument", message } };
|
|
39
|
+
}
|
|
40
|
+
function pathEscape() {
|
|
41
|
+
return { ok: false, error: { code: "path_escape", message: "path escapes repository root" } };
|
|
42
|
+
}
|
|
43
|
+
export async function handleRepoOverview(rootInput) {
|
|
44
|
+
const root = resolveRepoRoot(rootInput);
|
|
45
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
46
|
+
return redactDeep({
|
|
47
|
+
ok: true,
|
|
48
|
+
contractsVersion: CONTRACTS_VERSION,
|
|
49
|
+
root,
|
|
50
|
+
builder: graph.builder,
|
|
51
|
+
nodeCount: graph.nodes.length,
|
|
52
|
+
edgeCount: graph.edges.length,
|
|
53
|
+
limitations: graph.limitations,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export async function handleCodebaseSearch(rootInput, args) {
|
|
57
|
+
const root = resolveRepoRoot(rootInput);
|
|
58
|
+
const query = requireString(args, "query")?.toLowerCase() ?? "";
|
|
59
|
+
if (!query) {
|
|
60
|
+
return invalidArgument("query required");
|
|
61
|
+
}
|
|
62
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
63
|
+
const hits = graph.nodes
|
|
64
|
+
.filter((n) => n.label.toLowerCase().includes(query) ||
|
|
65
|
+
n.id.toLowerCase().includes(query) ||
|
|
66
|
+
(n.path ?? "").toLowerCase().includes(query))
|
|
67
|
+
.slice(0, 50);
|
|
68
|
+
return redactDeep({
|
|
69
|
+
ok: true,
|
|
70
|
+
query,
|
|
71
|
+
hits,
|
|
72
|
+
confidence: graph.builder === "typescript-ast" ? 0.85 : 0.55,
|
|
73
|
+
evidenceKind: graph.builder === "typescript-ast" ? "observed" : "inferred",
|
|
74
|
+
limitations: graph.limitations,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function handleSymbolLookup(rootInput, args) {
|
|
78
|
+
const root = resolveRepoRoot(rootInput);
|
|
79
|
+
const name = requireString(args, "name");
|
|
80
|
+
if (!name) {
|
|
81
|
+
return invalidArgument("name required");
|
|
82
|
+
}
|
|
83
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
84
|
+
const matches = graph.nodes.filter((n) => (n.kind === "function" || n.kind === "class" || n.kind === "module") &&
|
|
85
|
+
(n.label === name || n.label.endsWith(`.${name}`)));
|
|
86
|
+
return redactDeep({
|
|
87
|
+
ok: true,
|
|
88
|
+
name,
|
|
89
|
+
matches,
|
|
90
|
+
confidence: matches.length > 0 ? 0.8 : 0.2,
|
|
91
|
+
limitations: ["Import/call resolution is best-effort; unsupported languages omitted."],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
export async function handleDependencyLookup(rootInput, args) {
|
|
95
|
+
const root = resolveRepoRoot(rootInput);
|
|
96
|
+
const target = requireString(args, "target");
|
|
97
|
+
if (!target) {
|
|
98
|
+
return invalidArgument("target required");
|
|
99
|
+
}
|
|
100
|
+
let safeRelative = null;
|
|
101
|
+
try {
|
|
102
|
+
safeRelative = assertSafeRepoTarget(root, target);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return pathEscape();
|
|
106
|
+
}
|
|
107
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
108
|
+
const lookupKeys = [target, safeRelative].filter((v) => Boolean(v));
|
|
109
|
+
const node = graph.nodes.find((n) => lookupKeys.includes(n.id) ||
|
|
110
|
+
(n.path !== undefined && lookupKeys.includes(n.path)) ||
|
|
111
|
+
lookupKeys.includes(n.label));
|
|
112
|
+
if (!node) {
|
|
113
|
+
return redactDeep({ ok: true, target, edges: [], limitations: ["Target not found in graph"] });
|
|
114
|
+
}
|
|
115
|
+
const edges = graph.edges.filter((e) => e.from === node.id || e.to === node.id);
|
|
116
|
+
return redactDeep({ ok: true, target: node, edges, confidence: 0.75 });
|
|
117
|
+
}
|
|
118
|
+
export async function handleCallGraphLookup(rootInput, args) {
|
|
119
|
+
const root = resolveRepoRoot(rootInput);
|
|
120
|
+
const symbol = requireString(args, "symbol");
|
|
121
|
+
if (!symbol) {
|
|
122
|
+
return invalidArgument("symbol required");
|
|
123
|
+
}
|
|
124
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
125
|
+
const calls = graph.edges.filter((e) => e.kind === "calls" && (e.from.includes(symbol) || e.to.includes(symbol)));
|
|
126
|
+
return redactDeep({
|
|
127
|
+
ok: true,
|
|
128
|
+
symbol,
|
|
129
|
+
calls,
|
|
130
|
+
confidence: graph.builder === "typescript-ast" ? 0.7 : 0.4,
|
|
131
|
+
limitations: ["Call edges may be incomplete for dynamic/dispatch patterns."],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
export async function handleTestImpactTool(rootInput) {
|
|
135
|
+
const root = resolveRepoRoot(rootInput);
|
|
136
|
+
const impact = await analyzeTestImpact(root);
|
|
137
|
+
return redactDeep({
|
|
138
|
+
ok: true,
|
|
139
|
+
impact,
|
|
140
|
+
resultKind: impact.gitAvailable ? "graph-inferred" : "unknown",
|
|
141
|
+
limitations: ["Coverage-backed mapping requires external coverage data (not bundled)."],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export async function handleRefactorImpactTool(rootInput, args) {
|
|
145
|
+
const root = resolveRepoRoot(rootInput);
|
|
146
|
+
const symbol = requireString(args, "symbol");
|
|
147
|
+
if (!symbol) {
|
|
148
|
+
return invalidArgument("symbol required");
|
|
149
|
+
}
|
|
150
|
+
const graph = await buildRepositoryGraph(root);
|
|
151
|
+
const impact = await analyzeRenameImpact({ root, symbol, graph });
|
|
152
|
+
return redactDeep({ ok: true, impact });
|
|
153
|
+
}
|
|
154
|
+
export async function handleCodeHealthTool(rootInput) {
|
|
155
|
+
const root = resolveRepoRoot(rootInput);
|
|
156
|
+
const report = await analyzeGitIntelligence(root);
|
|
157
|
+
return redactDeep({
|
|
158
|
+
ok: true,
|
|
159
|
+
report,
|
|
160
|
+
methodDisclosure: report.hotspots[0]?.method ?? "see per-metric method fields",
|
|
161
|
+
limitations: report.limitations,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
export async function handleArchitectureTool(rootInput) {
|
|
165
|
+
const root = resolveRepoRoot(rootInput);
|
|
166
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
167
|
+
const views = buildC4Views(graph);
|
|
168
|
+
return redactDeep({
|
|
169
|
+
ok: true,
|
|
170
|
+
views,
|
|
171
|
+
label: "Inferred/proposed from graph evidence — not approved architecture facts",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
export async function handleKnowledgeTool(rootInput, args) {
|
|
175
|
+
const root = resolveRepoRoot(rootInput);
|
|
176
|
+
const list = await listKnowledge(root);
|
|
177
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
178
|
+
if (!query) {
|
|
179
|
+
return redactDeep({
|
|
180
|
+
ok: true,
|
|
181
|
+
records: list.filter((r) => r.status === "approved").slice(0, 50),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const result = retrieveAuthoritative(list, query);
|
|
185
|
+
return redactDeep({ ok: true, ...result });
|
|
186
|
+
}
|
|
187
|
+
export async function handlePolicyEvalTool(rootInput, args) {
|
|
188
|
+
const root = resolveRepoRoot(rootInput);
|
|
189
|
+
const command = requireString(args, "command");
|
|
190
|
+
if (!command) {
|
|
191
|
+
return invalidArgument("command required");
|
|
192
|
+
}
|
|
193
|
+
const verdict = await evaluateAgentAction(root, {
|
|
194
|
+
actionId: `mcp_${Date.now()}`,
|
|
195
|
+
agentId: "agentdoctor-mcp",
|
|
196
|
+
timestamp: new Date().toISOString(),
|
|
197
|
+
type: "shell",
|
|
198
|
+
params: { command },
|
|
199
|
+
repositoryRoot: root,
|
|
200
|
+
});
|
|
201
|
+
return redactDeep({
|
|
202
|
+
ok: true,
|
|
203
|
+
verdict,
|
|
204
|
+
executionResult: "not-executed",
|
|
205
|
+
notice: "Evaluate-only: AgentDoctor MCP does not execute shell commands.",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
export async function handleChangeAnalyzeTool(rootInput, args) {
|
|
209
|
+
const root = resolveRepoRoot(rootInput);
|
|
210
|
+
const since = typeof args.since === "string" && args.since.trim() ? args.since.trim() : undefined;
|
|
211
|
+
const changeId = typeof args.changeId === "string" && args.changeId.trim() ? args.changeId.trim() : undefined;
|
|
212
|
+
if (args.coveragePath !== undefined) {
|
|
213
|
+
if (typeof args.coveragePath !== "string" || !args.coveragePath.trim()) {
|
|
214
|
+
return invalidArgument("coveragePath must be a non-empty string when provided");
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
assertSafeRepoTarget(root, args.coveragePath);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return pathEscape();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const assessment = await analyzeChange({
|
|
224
|
+
root,
|
|
225
|
+
...(since ? { since } : {}),
|
|
226
|
+
...(changeId ? { changeId } : {}),
|
|
227
|
+
...(typeof args.coveragePath === "string" ? { coveragePath: args.coveragePath } : {}),
|
|
228
|
+
});
|
|
229
|
+
return redactDeep({
|
|
230
|
+
ok: true,
|
|
231
|
+
assessment,
|
|
232
|
+
limitations: assessment.limitations,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
export async function handleArchitectureCheckTool(rootInput) {
|
|
236
|
+
const root = resolveRepoRoot(rootInput);
|
|
237
|
+
const graph = await buildRepositoryGraph(root);
|
|
238
|
+
const result = await checkArchitectureAtRoot(root, graph);
|
|
239
|
+
return redactDeep({
|
|
240
|
+
ok: true,
|
|
241
|
+
result,
|
|
242
|
+
violationCount: result.violations.length,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
export async function handleProofInspectTool(rootInput, args) {
|
|
246
|
+
const root = resolveRepoRoot(rootInput);
|
|
247
|
+
const id = requireString(args, "id");
|
|
248
|
+
if (!id) {
|
|
249
|
+
return invalidArgument("id required");
|
|
250
|
+
}
|
|
251
|
+
if (id.includes("/") || id.includes("\\") || id.includes("..")) {
|
|
252
|
+
try {
|
|
253
|
+
assertSafeRepoTarget(root, id);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return pathEscape();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const inspected = await inspectProof(root, id);
|
|
260
|
+
if (!inspected.ok) {
|
|
261
|
+
return {
|
|
262
|
+
ok: false,
|
|
263
|
+
error: { code: "not_found", message: inspected.error ?? "proof not found" },
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return redactDeep({
|
|
267
|
+
ok: true,
|
|
268
|
+
proof: inspected.proof,
|
|
269
|
+
path: inspected.path,
|
|
270
|
+
notice: "Integrity/inspect only — engineering correctness is not claimed",
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
export async function handleEvidenceInspectTool(rootInput, args) {
|
|
274
|
+
const root = resolveRepoRoot(rootInput);
|
|
275
|
+
const changeId = requireString(args, "changeId");
|
|
276
|
+
if (!changeId) {
|
|
277
|
+
return invalidArgument("changeId required");
|
|
278
|
+
}
|
|
279
|
+
if (changeId.includes("/") || changeId.includes("\\") || changeId.includes("..")) {
|
|
280
|
+
try {
|
|
281
|
+
assertSafeRepoTarget(root, changeId);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return pathEscape();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const inspected = await inspectEvidence({ root, changeId });
|
|
288
|
+
if (!inspected.ok) {
|
|
289
|
+
return {
|
|
290
|
+
ok: false,
|
|
291
|
+
error: { code: "not_found", message: inspected.error ?? "evidence not found" },
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return redactDeep({
|
|
295
|
+
ok: true,
|
|
296
|
+
directory: inspected.directory,
|
|
297
|
+
manifest: inspected.manifest,
|
|
298
|
+
presentFiles: inspected.presentFiles,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
export async function handleGraphQueryTool(rootInput, args) {
|
|
302
|
+
const root = resolveRepoRoot(rootInput);
|
|
303
|
+
const query = requireString(args, "query");
|
|
304
|
+
if (!query) {
|
|
305
|
+
return invalidArgument("query required");
|
|
306
|
+
}
|
|
307
|
+
const kindFilter = typeof args.kind === "string" && args.kind.trim() ? args.kind.trim().toLowerCase() : null;
|
|
308
|
+
const limitRaw = args.limit;
|
|
309
|
+
const limit = typeof limitRaw === "number" && Number.isFinite(limitRaw)
|
|
310
|
+
? Math.max(1, Math.min(100, Math.floor(limitRaw)))
|
|
311
|
+
: 25;
|
|
312
|
+
if (args.path !== undefined) {
|
|
313
|
+
if (typeof args.path !== "string" || !args.path.trim()) {
|
|
314
|
+
return invalidArgument("path must be a non-empty string when provided");
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
assertSafeRepoTarget(root, args.path);
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return pathEscape();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
324
|
+
const status = await graphStatus({ root });
|
|
325
|
+
const q = query.toLowerCase();
|
|
326
|
+
const pathFilter = typeof args.path === "string" ? args.path.trim().toLowerCase().replace(/\\/g, "/") : null;
|
|
327
|
+
const nodes = graph.nodes
|
|
328
|
+
.filter((n) => {
|
|
329
|
+
if (kindFilter && n.kind.toLowerCase() !== kindFilter)
|
|
330
|
+
return false;
|
|
331
|
+
if (pathFilter && !(n.path ?? "").toLowerCase().includes(pathFilter))
|
|
332
|
+
return false;
|
|
333
|
+
return (n.id.toLowerCase().includes(q) ||
|
|
334
|
+
n.label.toLowerCase().includes(q) ||
|
|
335
|
+
(n.path ?? "").toLowerCase().includes(q));
|
|
336
|
+
})
|
|
337
|
+
.slice(0, limit);
|
|
338
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
339
|
+
const edges = graph.edges
|
|
340
|
+
.filter((e) => nodeIds.has(e.from) ||
|
|
341
|
+
nodeIds.has(e.to) ||
|
|
342
|
+
e.from.toLowerCase().includes(q) ||
|
|
343
|
+
e.to.toLowerCase().includes(q) ||
|
|
344
|
+
e.kind.toLowerCase().includes(q))
|
|
345
|
+
.slice(0, limit);
|
|
346
|
+
return redactDeep({
|
|
347
|
+
ok: true,
|
|
348
|
+
query,
|
|
349
|
+
kind: kindFilter,
|
|
350
|
+
path: pathFilter,
|
|
351
|
+
nodes,
|
|
352
|
+
edges,
|
|
353
|
+
index: {
|
|
354
|
+
present: status.present,
|
|
355
|
+
nodeCount: status.nodeCount,
|
|
356
|
+
edgeCount: status.edgeCount,
|
|
357
|
+
corrupt: status.corrupt ?? false,
|
|
358
|
+
},
|
|
359
|
+
limitations: [
|
|
360
|
+
"Query is substring match over in-memory / rebuilt graph — not a production graph DB",
|
|
361
|
+
...graph.limitations,
|
|
362
|
+
],
|
|
363
|
+
});
|
|
364
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { tryDecodeUriComponent } from "../../security/paths.js";
|
|
2
|
+
export { tryDecodeUriComponent };
|
|
3
|
+
/**
|
|
4
|
+
* Validate a user-supplied repository-relative or in-repo target.
|
|
5
|
+
* Returns a normalized path relative to root (POSIX) when the target is path-like and safe,
|
|
6
|
+
* or null when the target is treated as a non-path identifier (graph id / label).
|
|
7
|
+
* Throws a safe Error (no outside path leakage) when a path-like target escapes the root.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertSafeRepoTarget(root: string, rawTarget: string): string | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { PathEscapeError, rejectHostilePathInput, resolveSafeRepoPath, tryDecodeUriComponent, } from "../../security/paths.js";
|
|
3
|
+
import { sanitizeForOutput } from "../../utils/path.js";
|
|
4
|
+
export { tryDecodeUriComponent };
|
|
5
|
+
/**
|
|
6
|
+
* Validate a user-supplied repository-relative or in-repo target.
|
|
7
|
+
* Returns a normalized path relative to root (POSIX) when the target is path-like and safe,
|
|
8
|
+
* or null when the target is treated as a non-path identifier (graph id / label).
|
|
9
|
+
* Throws a safe Error (no outside path leakage) when a path-like target escapes the root.
|
|
10
|
+
*/
|
|
11
|
+
export function assertSafeRepoTarget(root, rawTarget) {
|
|
12
|
+
if (typeof rawTarget !== "string" || rawTarget.includes("\0")) {
|
|
13
|
+
throw new Error("invalid target");
|
|
14
|
+
}
|
|
15
|
+
const cleaned = sanitizeForOutput(rawTarget).trim();
|
|
16
|
+
if (!cleaned) {
|
|
17
|
+
throw new Error("invalid target");
|
|
18
|
+
}
|
|
19
|
+
if (cleaned.includes("\0")) {
|
|
20
|
+
throw new Error("invalid target");
|
|
21
|
+
}
|
|
22
|
+
const decoded = tryDecodeUriComponent(cleaned);
|
|
23
|
+
if (decoded === null) {
|
|
24
|
+
throw new Error("invalid target");
|
|
25
|
+
}
|
|
26
|
+
const candidates = decoded === cleaned ? [cleaned] : [cleaned, decoded];
|
|
27
|
+
let pathLike = false;
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
const looksLikePath = path.isAbsolute(candidate) ||
|
|
30
|
+
candidate.includes("..") ||
|
|
31
|
+
candidate.includes("/") ||
|
|
32
|
+
candidate.includes("\\") ||
|
|
33
|
+
/^[A-Za-z]:[\\/]/.test(candidate);
|
|
34
|
+
if (!looksLikePath) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
pathLike = true;
|
|
38
|
+
try {
|
|
39
|
+
rejectHostilePathInput(candidate);
|
|
40
|
+
resolveSafeRepoPath(root, candidate);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof PathEscapeError) {
|
|
44
|
+
throw new Error(error.message);
|
|
45
|
+
}
|
|
46
|
+
throw new Error("invalid target");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!pathLike) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const primary = candidates[candidates.length - 1];
|
|
53
|
+
try {
|
|
54
|
+
const abs = resolveSafeRepoPath(root, primary);
|
|
55
|
+
return path.relative(root, abs).split(path.sep).join("/");
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error instanceof PathEscapeError) {
|
|
59
|
+
throw new Error(error.message);
|
|
60
|
+
}
|
|
61
|
+
throw new Error("invalid target");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export declare const INTELLIGENCE_MCP_TOOL_NAMES: readonly ["repo_overview", "codebase_search", "symbol_lookup", "dependency_lookup", "call_graph_lookup", "test_impact", "refactor_impact", "code_health", "architecture_info", "architecture_check", "knowledge_retrieve", "policy_evaluate", "change_analyze", "proof_inspect", "evidence_inspect", "graph_query"];
|
|
3
|
+
export type IntelligenceMcpToolName = (typeof INTELLIGENCE_MCP_TOOL_NAMES)[number];
|
|
4
|
+
export declare function listIntelligenceMcpTools(): Tool[];
|
|
5
|
+
export declare function invokeIntelligenceMcpTool(root: string, name: string, args: Record<string, unknown>): Promise<{
|
|
6
|
+
structured: unknown;
|
|
7
|
+
isError: boolean;
|
|
8
|
+
}>;
|