@praneeth_54/agentdoctor 2.0.0 → 2.1.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/CHANGELOG.md +141 -4
- package/README.md +329 -153
- package/dist/agent/approvals.d.ts +24 -0
- package/dist/agent/approvals.js +64 -0
- package/dist/agent/chat/index.d.ts +7 -0
- package/dist/agent/chat/index.js +5 -0
- package/dist/agent/chat/memory.d.ts +44 -0
- package/dist/agent/chat/memory.js +103 -0
- package/dist/agent/chat/project-summary.d.ts +16 -0
- package/dist/agent/chat/project-summary.js +70 -0
- package/dist/agent/chat/prompts.d.ts +6 -0
- package/dist/agent/chat/prompts.js +39 -0
- package/dist/agent/chat/response.d.ts +19 -0
- package/dist/agent/chat/response.js +109 -0
- package/dist/agent/chat/service.d.ts +42 -0
- package/dist/agent/chat/service.js +252 -0
- package/dist/agent/chat/types.d.ts +48 -0
- package/dist/agent/chat/types.js +1 -0
- package/dist/agent/context/retrieve.d.ts +21 -0
- package/dist/agent/context/retrieve.js +117 -0
- package/dist/agent/context/truth.d.ts +6 -0
- package/dist/agent/context/truth.js +19 -0
- package/dist/agent/context/types.d.ts +23 -0
- package/dist/agent/context/types.js +4 -0
- package/dist/agent/index.d.ts +26 -0
- package/dist/agent/index.js +14 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +222 -0
- package/dist/agent/modes.d.ts +18 -0
- package/dist/agent/modes.js +102 -0
- package/dist/agent/plan.d.ts +30 -0
- package/dist/agent/plan.js +121 -0
- package/dist/agent/runtime.d.ts +67 -0
- package/dist/agent/runtime.js +180 -0
- package/dist/agent/state.d.ts +30 -0
- package/dist/agent/state.js +95 -0
- package/dist/agent/student.d.ts +56 -0
- package/dist/agent/student.js +230 -0
- package/dist/agent/tools/execute.d.ts +18 -0
- package/dist/agent/tools/execute.js +355 -0
- package/dist/agent/tools/index.d.ts +6 -0
- package/dist/agent/tools/index.js +5 -0
- package/dist/agent/tools/registry.d.ts +7 -0
- package/dist/agent/tools/registry.js +212 -0
- package/dist/agent/tools/run.d.ts +24 -0
- package/dist/agent/tools/run.js +44 -0
- package/dist/agent/tools/types.d.ts +32 -0
- package/dist/agent/tools/types.js +10 -0
- package/dist/agent/tools/write.d.ts +24 -0
- package/dist/agent/tools/write.js +121 -0
- package/dist/agent/verify.d.ts +29 -0
- package/dist/agent/verify.js +210 -0
- package/dist/ai/config.d.ts +22 -0
- package/dist/ai/config.js +68 -0
- package/dist/ai/index.d.ts +17 -0
- package/dist/ai/index.js +52 -0
- package/dist/ai/providers/mock.d.ts +20 -0
- package/dist/ai/providers/mock.js +84 -0
- package/dist/ai/providers/none.d.ts +6 -0
- package/dist/ai/providers/none.js +23 -0
- package/dist/ai/providers/openai-compatible.d.ts +21 -0
- package/dist/ai/providers/openai-compatible.js +151 -0
- package/dist/ai/redact.d.ts +8 -0
- package/dist/ai/redact.js +21 -0
- package/dist/ai/types.d.ts +71 -0
- package/dist/ai/types.js +6 -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/agent.d.ts +27 -0
- package/dist/cli/commands/agent.js +153 -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/chat.d.ts +14 -0
- package/dist/cli/commands/chat.js +155 -0
- package/dist/cli/commands/complete.js +2 -2
- package/dist/cli/commands/learn.d.ts +12 -0
- package/dist/cli/commands/learn.js +107 -0
- package/dist/cli/commands/platform.d.ts +2 -0
- package/dist/cli/commands/platform.js +5 -1
- 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/workspace.d.ts +9 -0
- package/dist/cli/commands/workspace.js +82 -0
- package/dist/cli/program.js +627 -17
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.js +2 -2
- package/dist/core/brain-product/init.js +5 -2
- package/dist/core/secrets/scan.d.ts +7 -1
- package/dist/core/secrets/scan.js +9 -9
- 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 +7 -1
- package/dist/dashboard/server.js +110 -6
- package/dist/enforcement/runner.d.ts +52 -3
- package/dist/enforcement/runner.js +467 -16
- package/dist/index.d.ts +36 -2
- package/dist/index.js +23 -2
- package/dist/intelligence/graph/build.d.ts +2 -0
- package/dist/intelligence/graph/build.js +49 -5
- 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/languages/go.d.ts +9 -0
- package/dist/languages/go.js +50 -0
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.js +76 -0
- package/dist/languages/php.d.ts +8 -0
- package/dist/languages/php.js +202 -0
- package/dist/languages/python.d.ts +11 -0
- package/dist/languages/python.js +180 -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/agent/registry.d.ts +13 -0
- package/dist/mcp/agent/registry.js +234 -0
- package/dist/mcp/agentdoctor/server.js +8 -1
- package/dist/mcp/intelligence/handlers.d.ts +5 -0
- package/dist/mcp/intelligence/handlers.js +229 -40
- package/dist/mcp/intelligence/path-safety.d.ts +2 -4
- package/dist/mcp/intelligence/path-safety.js +25 -44
- package/dist/mcp/intelligence/registry.d.ts +1 -1
- package/dist/mcp/intelligence/registry.js +69 -1
- package/dist/platform/firewall/evaluate.d.ts +6 -3
- package/dist/platform/firewall/evaluate.js +66 -15
- package/dist/platform/graph/build.js +19 -23
- package/dist/platform/health/analyze.js +23 -17
- package/dist/platform/index.js +2 -0
- package/dist/platform/test-impact/analyze.d.ts +21 -3
- package/dist/platform/test-impact/analyze.js +214 -28
- package/dist/platform/tokens/plan.js +39 -20
- package/dist/platform/types.d.ts +9 -0
- package/dist/policy/compose.d.ts +34 -0
- package/dist/policy/compose.js +118 -0
- package/dist/policy/packs.js +33 -1
- package/dist/security/paths.d.ts +21 -0
- package/dist/security/paths.js +133 -0
- package/dist/storage/postgres.d.ts +26 -0
- package/dist/storage/postgres.js +90 -0
- package/dist/storage/provider.d.ts +2 -7
- package/dist/storage/provider.js +22 -16
- package/dist/storage/sqlite.d.ts +33 -0
- package/dist/storage/sqlite.js +77 -0
- package/dist/team/auth.js +1 -1
- package/dist/workspace/index.d.ts +69 -0
- package/dist/workspace/index.js +220 -0
- package/package.json +9 -3
|
@@ -1,15 +1,49 @@
|
|
|
1
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";
|
|
2
5
|
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
6
|
+
import { graphStatus } from "../../intelligence/graph/incremental.js";
|
|
3
7
|
import { analyzeGitIntelligence } from "../../intelligence/git/analyze.js";
|
|
4
8
|
import { listKnowledge, retrieveAuthoritative } from "../../knowledge/store.js";
|
|
5
9
|
import { analyzeRenameImpact, analyzeTestImpact, buildRepositoryGraph, evaluateAgentAction, } from "../../platform/index.js";
|
|
10
|
+
import { redactSecrets } from "../../platform/security/redact.js";
|
|
6
11
|
import { resolveRepoRoot } from "../../utils/path.js";
|
|
7
12
|
import { CONTRACTS_VERSION } from "../../contracts/index.js";
|
|
8
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
|
+
}
|
|
9
43
|
export async function handleRepoOverview(rootInput) {
|
|
10
44
|
const root = resolveRepoRoot(rootInput);
|
|
11
45
|
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
12
|
-
return {
|
|
46
|
+
return redactDeep({
|
|
13
47
|
ok: true,
|
|
14
48
|
contractsVersion: CONTRACTS_VERSION,
|
|
15
49
|
root,
|
|
@@ -17,15 +51,13 @@ export async function handleRepoOverview(rootInput) {
|
|
|
17
51
|
nodeCount: graph.nodes.length,
|
|
18
52
|
edgeCount: graph.edges.length,
|
|
19
53
|
limitations: graph.limitations,
|
|
20
|
-
};
|
|
54
|
+
});
|
|
21
55
|
}
|
|
22
56
|
export async function handleCodebaseSearch(rootInput, args) {
|
|
23
57
|
const root = resolveRepoRoot(rootInput);
|
|
24
|
-
const query =
|
|
25
|
-
.toLowerCase()
|
|
26
|
-
.trim();
|
|
58
|
+
const query = requireString(args, "query")?.toLowerCase() ?? "";
|
|
27
59
|
if (!query) {
|
|
28
|
-
return
|
|
60
|
+
return invalidArgument("query required");
|
|
29
61
|
}
|
|
30
62
|
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
31
63
|
const hits = graph.nodes
|
|
@@ -33,47 +65,44 @@ export async function handleCodebaseSearch(rootInput, args) {
|
|
|
33
65
|
n.id.toLowerCase().includes(query) ||
|
|
34
66
|
(n.path ?? "").toLowerCase().includes(query))
|
|
35
67
|
.slice(0, 50);
|
|
36
|
-
return {
|
|
68
|
+
return redactDeep({
|
|
37
69
|
ok: true,
|
|
38
70
|
query,
|
|
39
71
|
hits,
|
|
40
72
|
confidence: graph.builder === "typescript-ast" ? 0.85 : 0.55,
|
|
41
73
|
evidenceKind: graph.builder === "typescript-ast" ? "observed" : "inferred",
|
|
42
74
|
limitations: graph.limitations,
|
|
43
|
-
};
|
|
75
|
+
});
|
|
44
76
|
}
|
|
45
77
|
export async function handleSymbolLookup(rootInput, args) {
|
|
46
78
|
const root = resolveRepoRoot(rootInput);
|
|
47
|
-
const name =
|
|
79
|
+
const name = requireString(args, "name");
|
|
48
80
|
if (!name) {
|
|
49
|
-
return
|
|
81
|
+
return invalidArgument("name required");
|
|
50
82
|
}
|
|
51
83
|
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
52
84
|
const matches = graph.nodes.filter((n) => (n.kind === "function" || n.kind === "class" || n.kind === "module") &&
|
|
53
85
|
(n.label === name || n.label.endsWith(`.${name}`)));
|
|
54
|
-
return {
|
|
86
|
+
return redactDeep({
|
|
55
87
|
ok: true,
|
|
56
88
|
name,
|
|
57
89
|
matches,
|
|
58
90
|
confidence: matches.length > 0 ? 0.8 : 0.2,
|
|
59
91
|
limitations: ["Import/call resolution is best-effort; unsupported languages omitted."],
|
|
60
|
-
};
|
|
92
|
+
});
|
|
61
93
|
}
|
|
62
94
|
export async function handleDependencyLookup(rootInput, args) {
|
|
63
95
|
const root = resolveRepoRoot(rootInput);
|
|
64
|
-
const target =
|
|
96
|
+
const target = requireString(args, "target");
|
|
65
97
|
if (!target) {
|
|
66
|
-
return
|
|
98
|
+
return invalidArgument("target required");
|
|
67
99
|
}
|
|
68
100
|
let safeRelative = null;
|
|
69
101
|
try {
|
|
70
102
|
safeRelative = assertSafeRepoTarget(root, target);
|
|
71
103
|
}
|
|
72
104
|
catch {
|
|
73
|
-
return
|
|
74
|
-
ok: false,
|
|
75
|
-
error: { code: "path_escape", message: "path escapes repository root" },
|
|
76
|
-
};
|
|
105
|
+
return pathEscape();
|
|
77
106
|
}
|
|
78
107
|
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
79
108
|
const lookupKeys = [target, safeRelative].filter((v) => Boolean(v));
|
|
@@ -81,82 +110,85 @@ export async function handleDependencyLookup(rootInput, args) {
|
|
|
81
110
|
(n.path !== undefined && lookupKeys.includes(n.path)) ||
|
|
82
111
|
lookupKeys.includes(n.label));
|
|
83
112
|
if (!node) {
|
|
84
|
-
return { ok: true, target, edges: [], limitations: ["Target not found in graph"] };
|
|
113
|
+
return redactDeep({ ok: true, target, edges: [], limitations: ["Target not found in graph"] });
|
|
85
114
|
}
|
|
86
115
|
const edges = graph.edges.filter((e) => e.from === node.id || e.to === node.id);
|
|
87
|
-
return { ok: true, target: node, edges, confidence: 0.75 };
|
|
116
|
+
return redactDeep({ ok: true, target: node, edges, confidence: 0.75 });
|
|
88
117
|
}
|
|
89
118
|
export async function handleCallGraphLookup(rootInput, args) {
|
|
90
119
|
const root = resolveRepoRoot(rootInput);
|
|
91
|
-
const symbol =
|
|
120
|
+
const symbol = requireString(args, "symbol");
|
|
92
121
|
if (!symbol) {
|
|
93
|
-
return
|
|
122
|
+
return invalidArgument("symbol required");
|
|
94
123
|
}
|
|
95
124
|
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
96
125
|
const calls = graph.edges.filter((e) => e.kind === "calls" && (e.from.includes(symbol) || e.to.includes(symbol)));
|
|
97
|
-
return {
|
|
126
|
+
return redactDeep({
|
|
98
127
|
ok: true,
|
|
99
128
|
symbol,
|
|
100
129
|
calls,
|
|
101
130
|
confidence: graph.builder === "typescript-ast" ? 0.7 : 0.4,
|
|
102
131
|
limitations: ["Call edges may be incomplete for dynamic/dispatch patterns."],
|
|
103
|
-
};
|
|
132
|
+
});
|
|
104
133
|
}
|
|
105
134
|
export async function handleTestImpactTool(rootInput) {
|
|
106
135
|
const root = resolveRepoRoot(rootInput);
|
|
107
136
|
const impact = await analyzeTestImpact(root);
|
|
108
|
-
return {
|
|
137
|
+
return redactDeep({
|
|
109
138
|
ok: true,
|
|
110
139
|
impact,
|
|
111
140
|
resultKind: impact.gitAvailable ? "graph-inferred" : "unknown",
|
|
112
141
|
limitations: ["Coverage-backed mapping requires external coverage data (not bundled)."],
|
|
113
|
-
};
|
|
142
|
+
});
|
|
114
143
|
}
|
|
115
144
|
export async function handleRefactorImpactTool(rootInput, args) {
|
|
116
145
|
const root = resolveRepoRoot(rootInput);
|
|
117
|
-
const symbol =
|
|
146
|
+
const symbol = requireString(args, "symbol");
|
|
118
147
|
if (!symbol) {
|
|
119
|
-
return
|
|
148
|
+
return invalidArgument("symbol required");
|
|
120
149
|
}
|
|
121
150
|
const graph = await buildRepositoryGraph(root);
|
|
122
151
|
const impact = await analyzeRenameImpact({ root, symbol, graph });
|
|
123
|
-
return { ok: true, impact };
|
|
152
|
+
return redactDeep({ ok: true, impact });
|
|
124
153
|
}
|
|
125
154
|
export async function handleCodeHealthTool(rootInput) {
|
|
126
155
|
const root = resolveRepoRoot(rootInput);
|
|
127
156
|
const report = await analyzeGitIntelligence(root);
|
|
128
|
-
return {
|
|
157
|
+
return redactDeep({
|
|
129
158
|
ok: true,
|
|
130
159
|
report,
|
|
131
160
|
methodDisclosure: report.hotspots[0]?.method ?? "see per-metric method fields",
|
|
132
161
|
limitations: report.limitations,
|
|
133
|
-
};
|
|
162
|
+
});
|
|
134
163
|
}
|
|
135
164
|
export async function handleArchitectureTool(rootInput) {
|
|
136
165
|
const root = resolveRepoRoot(rootInput);
|
|
137
166
|
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
138
167
|
const views = buildC4Views(graph);
|
|
139
|
-
return {
|
|
168
|
+
return redactDeep({
|
|
140
169
|
ok: true,
|
|
141
170
|
views,
|
|
142
171
|
label: "Inferred/proposed from graph evidence — not approved architecture facts",
|
|
143
|
-
};
|
|
172
|
+
});
|
|
144
173
|
}
|
|
145
174
|
export async function handleKnowledgeTool(rootInput, args) {
|
|
146
175
|
const root = resolveRepoRoot(rootInput);
|
|
147
176
|
const list = await listKnowledge(root);
|
|
148
|
-
const query =
|
|
177
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
149
178
|
if (!query) {
|
|
150
|
-
return {
|
|
179
|
+
return redactDeep({
|
|
180
|
+
ok: true,
|
|
181
|
+
records: list.filter((r) => r.status === "approved").slice(0, 50),
|
|
182
|
+
});
|
|
151
183
|
}
|
|
152
184
|
const result = retrieveAuthoritative(list, query);
|
|
153
|
-
return { ok: true, ...result };
|
|
185
|
+
return redactDeep({ ok: true, ...result });
|
|
154
186
|
}
|
|
155
187
|
export async function handlePolicyEvalTool(rootInput, args) {
|
|
156
188
|
const root = resolveRepoRoot(rootInput);
|
|
157
|
-
const command =
|
|
189
|
+
const command = requireString(args, "command");
|
|
158
190
|
if (!command) {
|
|
159
|
-
return
|
|
191
|
+
return invalidArgument("command required");
|
|
160
192
|
}
|
|
161
193
|
const verdict = await evaluateAgentAction(root, {
|
|
162
194
|
actionId: `mcp_${Date.now()}`,
|
|
@@ -166,10 +198,167 @@ export async function handlePolicyEvalTool(rootInput, args) {
|
|
|
166
198
|
params: { command },
|
|
167
199
|
repositoryRoot: root,
|
|
168
200
|
});
|
|
169
|
-
return {
|
|
201
|
+
return redactDeep({
|
|
170
202
|
ok: true,
|
|
171
203
|
verdict,
|
|
172
204
|
executionResult: "not-executed",
|
|
173
205
|
notice: "Evaluate-only: AgentDoctor MCP does not execute shell commands.",
|
|
174
|
-
};
|
|
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
|
+
});
|
|
175
364
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
export declare function tryDecodeUriComponent(value: string): string | null;
|
|
1
|
+
import { tryDecodeUriComponent } from "../../security/paths.js";
|
|
2
|
+
export { tryDecodeUriComponent };
|
|
5
3
|
/**
|
|
6
4
|
* Validate a user-supplied repository-relative or in-repo target.
|
|
7
5
|
* Returns a normalized path relative to root (POSIX) when the target is path-like and safe,
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
export function tryDecodeUriComponent(value) {
|
|
7
|
-
try {
|
|
8
|
-
return decodeURIComponent(value);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
2
|
+
import { PathEscapeError, rejectHostilePathInput, resolveSafeRepoPath, tryDecodeUriComponent, } from "../../security/paths.js";
|
|
3
|
+
import { sanitizeForOutput } from "../../utils/path.js";
|
|
4
|
+
export { tryDecodeUriComponent };
|
|
14
5
|
/**
|
|
15
6
|
* Validate a user-supplied repository-relative or in-repo target.
|
|
16
7
|
* Returns a normalized path relative to root (POSIX) when the target is path-like and safe,
|
|
@@ -33,10 +24,8 @@ export function assertSafeRepoTarget(root, rawTarget) {
|
|
|
33
24
|
throw new Error("invalid target");
|
|
34
25
|
}
|
|
35
26
|
const candidates = decoded === cleaned ? [cleaned] : [cleaned, decoded];
|
|
27
|
+
let pathLike = false;
|
|
36
28
|
for (const candidate of candidates) {
|
|
37
|
-
if (candidate.includes("\0")) {
|
|
38
|
-
throw new Error("invalid target");
|
|
39
|
-
}
|
|
40
29
|
const looksLikePath = path.isAbsolute(candidate) ||
|
|
41
30
|
candidate.includes("..") ||
|
|
42
31
|
candidate.includes("/") ||
|
|
@@ -45,38 +34,30 @@ export function assertSafeRepoTarget(root, rawTarget) {
|
|
|
45
34
|
if (!looksLikePath) {
|
|
46
35
|
continue;
|
|
47
36
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
normalizedSlashes.startsWith("../") ||
|
|
53
|
-
normalizedSlashes.endsWith("/..") ||
|
|
54
|
-
normalizedSlashes.includes("/..") ||
|
|
55
|
-
/(^|\/)\.\.($|\/)/.test(normalizedSlashes)) {
|
|
56
|
-
throw new Error("path escapes repository root");
|
|
37
|
+
pathLike = true;
|
|
38
|
+
try {
|
|
39
|
+
rejectHostilePathInput(candidate);
|
|
40
|
+
resolveSafeRepoPath(root, candidate);
|
|
57
41
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const resolved = path.isAbsolute(candidate)
|
|
64
|
-
? path.resolve(candidate)
|
|
65
|
-
: path.resolve(root, candidate);
|
|
66
|
-
if (!isPathInsideRoot(root, resolved)) {
|
|
67
|
-
throw new Error("path escapes repository root");
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof PathEscapeError) {
|
|
44
|
+
throw new Error(error.message);
|
|
45
|
+
}
|
|
46
|
+
throw new Error("invalid target");
|
|
68
47
|
}
|
|
69
48
|
}
|
|
70
|
-
|
|
71
|
-
const primary = candidates[candidates.length - 1];
|
|
72
|
-
const looksLikePath = path.isAbsolute(primary) ||
|
|
73
|
-
primary.includes("..") ||
|
|
74
|
-
primary.includes("/") ||
|
|
75
|
-
primary.includes("\\") ||
|
|
76
|
-
/^[A-Za-z]:[\\/]/.test(primary);
|
|
77
|
-
if (!looksLikePath) {
|
|
49
|
+
if (!pathLike) {
|
|
78
50
|
return null;
|
|
79
51
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
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
|
+
}
|
|
82
63
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
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", "knowledge_retrieve", "policy_evaluate"];
|
|
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
3
|
export type IntelligenceMcpToolName = (typeof INTELLIGENCE_MCP_TOOL_NAMES)[number];
|
|
4
4
|
export declare function listIntelligenceMcpTools(): Tool[];
|
|
5
5
|
export declare function invokeIntelligenceMcpTool(root: string, name: string, args: Record<string, unknown>): Promise<{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handleArchitectureTool, handleCallGraphLookup, handleCodeHealthTool, handleCodebaseSearch, handleDependencyLookup, handleKnowledgeTool, handlePolicyEvalTool, handleRefactorImpactTool, handleRepoOverview, handleSymbolLookup, handleTestImpactTool, } from "./handlers.js";
|
|
1
|
+
import { handleArchitectureCheckTool, handleArchitectureTool, handleCallGraphLookup, handleChangeAnalyzeTool, handleCodeHealthTool, handleCodebaseSearch, handleDependencyLookup, handleEvidenceInspectTool, handleGraphQueryTool, handleKnowledgeTool, handlePolicyEvalTool, handleProofInspectTool, handleRefactorImpactTool, handleRepoOverview, handleSymbolLookup, handleTestImpactTool, } from "./handlers.js";
|
|
2
2
|
export const INTELLIGENCE_MCP_TOOL_NAMES = [
|
|
3
3
|
"repo_overview",
|
|
4
4
|
"codebase_search",
|
|
@@ -9,8 +9,13 @@ export const INTELLIGENCE_MCP_TOOL_NAMES = [
|
|
|
9
9
|
"refactor_impact",
|
|
10
10
|
"code_health",
|
|
11
11
|
"architecture_info",
|
|
12
|
+
"architecture_check",
|
|
12
13
|
"knowledge_retrieve",
|
|
13
14
|
"policy_evaluate",
|
|
15
|
+
"change_analyze",
|
|
16
|
+
"proof_inspect",
|
|
17
|
+
"evidence_inspect",
|
|
18
|
+
"graph_query",
|
|
14
19
|
];
|
|
15
20
|
const emptyObjectSchema = {
|
|
16
21
|
type: "object",
|
|
@@ -89,6 +94,11 @@ export function listIntelligenceMcpTools() {
|
|
|
89
94
|
description: "READ: C4-style views inferred from graph evidence (labeled proposed/inferred).",
|
|
90
95
|
inputSchema: emptyObjectSchema,
|
|
91
96
|
},
|
|
97
|
+
{
|
|
98
|
+
name: "architecture_check",
|
|
99
|
+
description: "READ: Check import edges against .agentdoctor/architecture.json|.yml contract (repo-local).",
|
|
100
|
+
inputSchema: emptyObjectSchema,
|
|
101
|
+
},
|
|
92
102
|
{
|
|
93
103
|
name: "knowledge_retrieve",
|
|
94
104
|
description: "READ: Governed knowledge retrieval. Abstains when no approved authoritative record matches.",
|
|
@@ -108,6 +118,54 @@ export function listIntelligenceMcpTools() {
|
|
|
108
118
|
additionalProperties: false,
|
|
109
119
|
},
|
|
110
120
|
},
|
|
121
|
+
{
|
|
122
|
+
name: "change_analyze",
|
|
123
|
+
description: "READ: Change assurance assessment (evidence/heuristics). Never claims verified engineering correctness.",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
since: { type: "string" },
|
|
128
|
+
changeId: { type: "string" },
|
|
129
|
+
coveragePath: { type: "string", description: "Repo-relative coverage file path" },
|
|
130
|
+
},
|
|
131
|
+
additionalProperties: false,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "proof_inspect",
|
|
136
|
+
description: "READ: Inspect ChangeProof by proofId or changeId (hash integrity metadata only).",
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: { id: { type: "string" } },
|
|
140
|
+
required: ["id"],
|
|
141
|
+
additionalProperties: false,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "evidence_inspect",
|
|
146
|
+
description: "READ: Inspect change evidence bundle artifacts for a changeId.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: { changeId: { type: "string" } },
|
|
150
|
+
required: ["changeId"],
|
|
151
|
+
additionalProperties: false,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "graph_query",
|
|
156
|
+
description: "READ: Substring query over intelligence graph nodes/edges. Optional kind/path filters; path validated.",
|
|
157
|
+
inputSchema: {
|
|
158
|
+
type: "object",
|
|
159
|
+
properties: {
|
|
160
|
+
query: { type: "string" },
|
|
161
|
+
kind: { type: "string" },
|
|
162
|
+
path: { type: "string" },
|
|
163
|
+
limit: { type: "number" },
|
|
164
|
+
},
|
|
165
|
+
required: ["query"],
|
|
166
|
+
additionalProperties: false,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
111
169
|
];
|
|
112
170
|
}
|
|
113
171
|
export async function invokeIntelligenceMcpTool(root, name, args) {
|
|
@@ -131,10 +189,20 @@ export async function invokeIntelligenceMcpTool(root, name, args) {
|
|
|
131
189
|
return { structured: await handleCodeHealthTool(root), isError: false };
|
|
132
190
|
case "architecture_info":
|
|
133
191
|
return { structured: await handleArchitectureTool(root), isError: false };
|
|
192
|
+
case "architecture_check":
|
|
193
|
+
return { structured: await handleArchitectureCheckTool(root), isError: false };
|
|
134
194
|
case "knowledge_retrieve":
|
|
135
195
|
return { structured: await handleKnowledgeTool(root, args), isError: false };
|
|
136
196
|
case "policy_evaluate":
|
|
137
197
|
return { structured: await handlePolicyEvalTool(root, args), isError: false };
|
|
198
|
+
case "change_analyze":
|
|
199
|
+
return { structured: await handleChangeAnalyzeTool(root, args), isError: false };
|
|
200
|
+
case "proof_inspect":
|
|
201
|
+
return { structured: await handleProofInspectTool(root, args), isError: false };
|
|
202
|
+
case "evidence_inspect":
|
|
203
|
+
return { structured: await handleEvidenceInspectTool(root, args), isError: false };
|
|
204
|
+
case "graph_query":
|
|
205
|
+
return { structured: await handleGraphQueryTool(root, args), isError: false };
|
|
138
206
|
default:
|
|
139
207
|
return {
|
|
140
208
|
structured: {
|
|
@@ -26,8 +26,9 @@ export interface FirewallPolicyDocument {
|
|
|
26
26
|
}
|
|
27
27
|
export interface PolicyLoadResult {
|
|
28
28
|
policy: FirewallPolicyDocument;
|
|
29
|
-
source: "file" | "default" | "fail-closed-deny";
|
|
29
|
+
source: "file" | "default" | "fail-closed-deny" | "composed";
|
|
30
30
|
validationError?: string;
|
|
31
|
+
composeSources?: Array<"builtin" | "repo">;
|
|
31
32
|
}
|
|
32
33
|
declare const EVALUATE_ONLY = "Evaluate-only: no commands are executed and no agents are intercepted.";
|
|
33
34
|
declare const DEFAULT_POLICY: FirewallPolicyDocument;
|
|
@@ -38,8 +39,10 @@ export declare function validateFirewallPolicy(raw: unknown): {
|
|
|
38
39
|
};
|
|
39
40
|
/**
|
|
40
41
|
* Load local action-policy document.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* Layers: composed builtin pack + `.agentdoctor/policy.json` (fail-closed on malformed repo policy),
|
|
43
|
+
* then optional platform `firewall-policy.json` overlay (highest precedence when valid).
|
|
44
|
+
* Invalid platform file + failClosed → deny-all.
|
|
45
|
+
* Invalid platform file + default → composed policy (or DEFAULT_POLICY when compose unavailable).
|
|
43
46
|
*/
|
|
44
47
|
export declare function loadFirewallPolicy(root: string, options?: {
|
|
45
48
|
failClosed?: boolean;
|