@praneeth_54/agentdoctor 2.0.1 → 3.0.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 +123 -1
- package/README.md +510 -260
- package/dist/agent/approvals.d.ts +24 -0
- package/dist/agent/approvals.js +64 -0
- package/dist/agent/chat/deterministic.d.ts +6 -0
- package/dist/agent/chat/deterministic.js +180 -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 +246 -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 +28 -0
- package/dist/agent/index.js +15 -0
- package/dist/agent/loop.d.ts +53 -0
- package/dist/agent/loop.js +253 -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/roles.d.ts +12 -0
- package/dist/agent/roles.js +138 -0
- package/dist/agent/runtime.d.ts +83 -0
- package/dist/agent/runtime.js +293 -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 +20 -0
- package/dist/agent/tools/execute.js +365 -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 +69 -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 +9 -0
- package/dist/ai/redact.js +61 -0
- package/dist/ai/types.d.ts +71 -0
- package/dist/ai/types.js +6 -0
- package/dist/cli/commands/agent.d.ts +29 -0
- package/dist/cli/commands/agent.js +164 -0
- package/dist/cli/commands/chat.d.ts +14 -0
- package/dist/cli/commands/chat.js +153 -0
- package/dist/cli/commands/learn.d.ts +12 -0
- package/dist/cli/commands/learn.js +107 -0
- package/dist/cli/commands/product.d.ts +19 -0
- package/dist/cli/commands/product.js +147 -0
- package/dist/cli/commands/start.d.ts +15 -0
- package/dist/cli/commands/start.js +80 -0
- package/dist/cli/program.js +233 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/dashboard/server.d.ts +6 -0
- package/dist/dashboard/server.js +333 -43
- package/dist/enforcement/runner.d.ts +5 -0
- package/dist/enforcement/runner.js +51 -4
- package/dist/index.d.ts +17 -0
- package/dist/index.js +10 -0
- package/dist/intelligence/graph/build.js +36 -8
- package/dist/intelligence/resolve/imports.js +1 -1
- package/dist/languages/dart.d.ts +10 -0
- package/dist/languages/dart.js +99 -0
- package/dist/languages/go.d.ts +13 -7
- package/dist/languages/go.js +91 -26
- package/dist/languages/index.d.ts +5 -1
- package/dist/languages/index.js +13 -36
- package/dist/languages/java.d.ts +10 -0
- package/dist/languages/java.js +80 -0
- package/dist/languages/kotlin.d.ts +10 -0
- package/dist/languages/kotlin.js +85 -0
- package/dist/languages/php.d.ts +1 -0
- package/dist/languages/php.js +11 -2
- package/dist/languages/python.d.ts +6 -2
- package/dist/languages/python.js +39 -9
- package/dist/languages/rust.d.ts +10 -0
- package/dist/languages/rust.js +93 -0
- package/dist/languages/types.d.ts +4 -3
- package/dist/mcp/agent/registry.d.ts +13 -0
- package/dist/mcp/agent/registry.js +320 -0
- package/dist/mcp/agentdoctor/server.js +8 -1
- package/dist/mcp/intelligence/handlers.d.ts +3 -0
- package/dist/mcp/intelligence/handlers.js +28 -0
- package/dist/mcp/intelligence/registry.d.ts +1 -1
- package/dist/mcp/intelligence/registry.js +30 -1
- package/dist/product/api/doctor.d.ts +14 -0
- package/dist/product/api/doctor.js +185 -0
- package/dist/product/api/openapi.d.ts +7 -0
- package/dist/product/api/openapi.js +122 -0
- package/dist/product/approval/model.d.ts +30 -0
- package/dist/product/approval/model.js +64 -0
- package/dist/product/approval/session.d.ts +49 -0
- package/dist/product/approval/session.js +134 -0
- package/dist/product/database/doctor.d.ts +30 -0
- package/dist/product/database/doctor.js +184 -0
- package/dist/product/decisions/ledger.d.ts +24 -0
- package/dist/product/decisions/ledger.js +110 -0
- package/dist/product/deps/analyze.d.ts +46 -0
- package/dist/product/deps/analyze.js +137 -0
- package/dist/product/deps/lockfiles.d.ts +25 -0
- package/dist/product/deps/lockfiles.js +200 -0
- package/dist/product/discovery/roots.d.ts +42 -0
- package/dist/product/discovery/roots.js +215 -0
- package/dist/product/dna/build.d.ts +50 -0
- package/dist/product/dna/build.js +255 -0
- package/dist/product/eval/lab.d.ts +17 -0
- package/dist/product/eval/lab.js +218 -0
- package/dist/product/events/doctor.d.ts +21 -0
- package/dist/product/events/doctor.js +147 -0
- package/dist/product/evidence-scan.d.ts +12 -0
- package/dist/product/evidence-scan.js +46 -0
- package/dist/product/evolution/timeline.d.ts +29 -0
- package/dist/product/evolution/timeline.js +123 -0
- package/dist/product/features/intelligence.d.ts +23 -0
- package/dist/product/features/intelligence.js +158 -0
- package/dist/product/forensic/mode.d.ts +25 -0
- package/dist/product/forensic/mode.js +70 -0
- package/dist/product/graph/enrich-languages.d.ts +20 -0
- package/dist/product/graph/enrich-languages.js +193 -0
- package/dist/product/health/code-health.d.ts +20 -0
- package/dist/product/health/code-health.js +149 -0
- package/dist/product/index.d.ts +69 -0
- package/dist/product/index.js +35 -0
- package/dist/product/ledger/change-ledger.d.ts +23 -0
- package/dist/product/ledger/change-ledger.js +64 -0
- package/dist/product/map/software-map.d.ts +17 -0
- package/dist/product/map/software-map.js +95 -0
- package/dist/product/memory/institutional.d.ts +16 -0
- package/dist/product/memory/institutional.js +87 -0
- package/dist/product/ops/incident.d.ts +20 -0
- package/dist/product/ops/incident.js +61 -0
- package/dist/product/ops/infra.d.ts +15 -0
- package/dist/product/ops/infra.js +112 -0
- package/dist/product/org/model.d.ts +34 -0
- package/dist/product/org/model.js +195 -0
- package/dist/product/privacy/doctor.d.ts +13 -0
- package/dist/product/privacy/doctor.js +90 -0
- package/dist/product/requirements/trace.d.ts +21 -0
- package/dist/product/requirements/trace.js +166 -0
- package/dist/product/search/index.d.ts +29 -0
- package/dist/product/search/index.js +116 -0
- package/dist/product/search/software-search.d.ts +19 -0
- package/dist/product/search/software-search.js +100 -0
- package/dist/product/security/doctor.d.ts +23 -0
- package/dist/product/security/doctor.js +124 -0
- package/dist/product/self/diagnose.d.ts +15 -0
- package/dist/product/self/diagnose.js +82 -0
- package/dist/product/techdebt/roadmap.d.ts +20 -0
- package/dist/product/techdebt/roadmap.js +118 -0
- package/dist/product/testbrain/analyze.d.ts +29 -0
- package/dist/product/testbrain/analyze.js +129 -0
- package/dist/product/truth.d.ts +12 -0
- package/dist/product/truth.js +30 -0
- package/dist/product/twin/digital-twin.d.ts +23 -0
- package/dist/product/twin/digital-twin.js +52 -0
- package/dist/product/twin/store.d.ts +21 -0
- package/dist/product/twin/store.js +70 -0
- package/dist/product/whatif/engine.d.ts +28 -0
- package/dist/product/whatif/engine.js +70 -0
- package/dist/security/paths.js +43 -15
- package/dist/utils/fs.js +10 -2
- package/package.json +1 -1
package/dist/languages/php.js
CHANGED
|
@@ -3,13 +3,20 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
function sid(kind, key) {
|
|
4
4
|
return `${kind}:${createHash("sha256").update(key).digest("hex").slice(0, 12)}`;
|
|
5
5
|
}
|
|
6
|
+
const PHP_PROBE_TIMEOUT_MS = 5_000;
|
|
7
|
+
const PHP_EXTRACT_TIMEOUT_MS = 15_000;
|
|
6
8
|
/**
|
|
7
9
|
* Real PHP token extractor via `php -r` + token_get_all (requires `php` on PATH).
|
|
8
10
|
* Not regex. If php is missing, capabilities are unsupported.
|
|
11
|
+
* Probe timeout skips hanging Windows Store / stub `php` binaries.
|
|
9
12
|
*/
|
|
10
13
|
export function phpAvailable() {
|
|
11
|
-
const r = spawnSync("php", ["-r", "echo PHP_VERSION;"], {
|
|
12
|
-
|
|
14
|
+
const r = spawnSync("php", ["-r", "echo PHP_VERSION;"], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
timeout: PHP_PROBE_TIMEOUT_MS,
|
|
17
|
+
windowsHide: true,
|
|
18
|
+
});
|
|
19
|
+
return r.status === 0 && !r.error && Boolean(r.stdout?.trim());
|
|
13
20
|
}
|
|
14
21
|
const PHP_EXTRACTOR = `
|
|
15
22
|
$source = stream_get_contents(STDIN);
|
|
@@ -117,6 +124,8 @@ export const phpAdapter = {
|
|
|
117
124
|
input: source,
|
|
118
125
|
encoding: "utf8",
|
|
119
126
|
maxBuffer: 8 * 1024 * 1024,
|
|
127
|
+
timeout: PHP_EXTRACT_TIMEOUT_MS,
|
|
128
|
+
windowsHide: true,
|
|
120
129
|
});
|
|
121
130
|
if (r.status !== 0 || !r.stdout) {
|
|
122
131
|
return {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { LanguageAdapter } from "./types.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
3
|
+
* Resolve a working CPython binary, or null if none responds in time.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolvePythonBin(): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Real Python AST via the CPython `ast` module (requires python on PATH).
|
|
8
|
+
* Not regex. If no interpreter is available, capabilities are unsupported.
|
|
5
9
|
*/
|
|
6
10
|
export declare function pythonAvailable(): boolean;
|
|
7
11
|
export declare const pythonAdapter: LanguageAdapter;
|
package/dist/languages/python.js
CHANGED
|
@@ -3,13 +3,40 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
function sid(kind, key) {
|
|
4
4
|
return `${kind}:${createHash("sha256").update(key).digest("hex").slice(0, 12)}`;
|
|
5
5
|
}
|
|
6
|
+
const PYTHON_PROBE_TIMEOUT_MS = 5_000;
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
|
|
8
|
+
* Prefer real interpreters; skip hanging Windows Store `python3` stubs via timeout.
|
|
9
|
+
*/
|
|
10
|
+
function pythonCandidates() {
|
|
11
|
+
return process.platform === "win32" ? ["python", "py", "python3"] : ["python3", "python"];
|
|
12
|
+
}
|
|
13
|
+
let cachedPythonBin;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a working CPython binary, or null if none responds in time.
|
|
16
|
+
*/
|
|
17
|
+
export function resolvePythonBin() {
|
|
18
|
+
if (cachedPythonBin !== undefined)
|
|
19
|
+
return cachedPythonBin;
|
|
20
|
+
for (const bin of pythonCandidates()) {
|
|
21
|
+
const r = spawnSync(bin, ["-c", "import ast, json"], {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
timeout: PYTHON_PROBE_TIMEOUT_MS,
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
});
|
|
26
|
+
if (r.status === 0 && !r.error) {
|
|
27
|
+
cachedPythonBin = bin;
|
|
28
|
+
return bin;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
cachedPythonBin = null;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Real Python AST via the CPython `ast` module (requires python on PATH).
|
|
36
|
+
* Not regex. If no interpreter is available, capabilities are unsupported.
|
|
9
37
|
*/
|
|
10
38
|
export function pythonAvailable() {
|
|
11
|
-
|
|
12
|
-
return r.status === 0;
|
|
39
|
+
return resolvePythonBin() !== null;
|
|
13
40
|
}
|
|
14
41
|
const PY_EXTRACTOR = `
|
|
15
42
|
import ast, json, sys
|
|
@@ -59,7 +86,8 @@ export const pythonAdapter = {
|
|
|
59
86
|
};
|
|
60
87
|
},
|
|
61
88
|
async parse(filePath, source) {
|
|
62
|
-
|
|
89
|
+
const pythonBin = resolvePythonBin();
|
|
90
|
+
if (!pythonBin) {
|
|
63
91
|
return {
|
|
64
92
|
language: "python",
|
|
65
93
|
file: filePath,
|
|
@@ -68,14 +96,16 @@ export const pythonAdapter = {
|
|
|
68
96
|
symbols: [],
|
|
69
97
|
imports: [],
|
|
70
98
|
calls: [],
|
|
71
|
-
diagnostics: ["
|
|
72
|
-
limitations: ["Python AST adapter requires python3 with stdlib ast module"],
|
|
99
|
+
diagnostics: ["python not available on PATH"],
|
|
100
|
+
limitations: ["Python AST adapter requires python3/python with stdlib ast module"],
|
|
73
101
|
};
|
|
74
102
|
}
|
|
75
|
-
const r = spawnSync(
|
|
103
|
+
const r = spawnSync(pythonBin, ["-c", PY_EXTRACTOR, filePath], {
|
|
76
104
|
input: source,
|
|
77
105
|
encoding: "utf8",
|
|
78
106
|
maxBuffer: 8 * 1024 * 1024,
|
|
107
|
+
timeout: 15_000,
|
|
108
|
+
windowsHide: true,
|
|
79
109
|
});
|
|
80
110
|
if (r.status !== 0 || !r.stdout) {
|
|
81
111
|
return {
|
|
@@ -86,7 +116,7 @@ export const pythonAdapter = {
|
|
|
86
116
|
symbols: [],
|
|
87
117
|
imports: [],
|
|
88
118
|
calls: [],
|
|
89
|
-
diagnostics: [r.stderr || "
|
|
119
|
+
diagnostics: [r.stderr || r.error?.message || "python extractor failed"],
|
|
90
120
|
limitations: [],
|
|
91
121
|
};
|
|
92
122
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LanguageAdapter, LanguageImport, LanguageSymbol } from "./types.js";
|
|
2
|
+
export interface RustParseSummary {
|
|
3
|
+
moduleName: string;
|
|
4
|
+
imports: LanguageImport[];
|
|
5
|
+
symbols: LanguageSymbol[];
|
|
6
|
+
limitations: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Lightweight line-oriented Rust scan — not syn/rustc AST. */
|
|
9
|
+
export declare function parseRustSource(filePath: string, source: string): RustParseSummary;
|
|
10
|
+
export declare const rustAdapter: LanguageAdapter;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Lightweight line-oriented Rust scan — not syn/rustc AST. */
|
|
2
|
+
export function parseRustSource(filePath, source) {
|
|
3
|
+
const lines = source.split(/\r?\n/);
|
|
4
|
+
const limitations = [
|
|
5
|
+
"Rust parsing uses a lightweight line scanner — not syn or tree-sitter-rust; calls/types are partial.",
|
|
6
|
+
"Full rustc/syn or tree-sitter-rust integration remains an EXTERNAL enhancement path (not bundled).",
|
|
7
|
+
];
|
|
8
|
+
let moduleName = "";
|
|
9
|
+
const imports = [];
|
|
10
|
+
const symbols = [];
|
|
11
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12
|
+
const trimmed = lines[i].trim();
|
|
13
|
+
if (!moduleName && trimmed.startsWith("mod ") && !trimmed.includes("{")) {
|
|
14
|
+
moduleName = trimmed.slice("mod ".length).split(/\s+/)[0]?.replace(";", "") ?? "";
|
|
15
|
+
}
|
|
16
|
+
if (trimmed.startsWith("use ") && trimmed.endsWith(";")) {
|
|
17
|
+
const spec = trimmed.slice("use ".length, -1).trim();
|
|
18
|
+
if (spec)
|
|
19
|
+
imports.push({ fromFile: filePath, specifier: spec, evidence: "ast" });
|
|
20
|
+
}
|
|
21
|
+
const structMatch = /^(?:pub\s+)?(?:struct|enum|trait|type)\s+(\w+)/.exec(trimmed);
|
|
22
|
+
if (structMatch) {
|
|
23
|
+
let kind = "struct";
|
|
24
|
+
if (trimmed.includes("enum"))
|
|
25
|
+
kind = "enum";
|
|
26
|
+
else if (trimmed.includes("trait"))
|
|
27
|
+
kind = "trait";
|
|
28
|
+
else if (trimmed.includes("type"))
|
|
29
|
+
kind = "type";
|
|
30
|
+
symbols.push({
|
|
31
|
+
id: `rust:${kind}:${filePath}:${structMatch[1]}`,
|
|
32
|
+
name: structMatch[1],
|
|
33
|
+
kind,
|
|
34
|
+
file: filePath,
|
|
35
|
+
line: i + 1,
|
|
36
|
+
evidence: "ast",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const fnMatch = /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/.exec(trimmed);
|
|
40
|
+
if (fnMatch) {
|
|
41
|
+
symbols.push({
|
|
42
|
+
id: `rust:function:${filePath}:${fnMatch[1]}`,
|
|
43
|
+
name: fnMatch[1],
|
|
44
|
+
kind: "function",
|
|
45
|
+
file: filePath,
|
|
46
|
+
line: i + 1,
|
|
47
|
+
evidence: "ast",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const implMatch = /^impl(?:<[^>]+>)?\s+(?:\w+::)*(\w+)/.exec(trimmed);
|
|
51
|
+
if (implMatch) {
|
|
52
|
+
symbols.push({
|
|
53
|
+
id: `rust:impl:${filePath}:${implMatch[1]}`,
|
|
54
|
+
name: implMatch[1],
|
|
55
|
+
kind: "impl",
|
|
56
|
+
file: filePath,
|
|
57
|
+
line: i + 1,
|
|
58
|
+
evidence: "ast",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { moduleName, imports, symbols, limitations };
|
|
63
|
+
}
|
|
64
|
+
const RUST_CAPABILITIES = () => ({
|
|
65
|
+
parse: "supported",
|
|
66
|
+
symbols: "supported",
|
|
67
|
+
definitions: "unsupported",
|
|
68
|
+
references: "unsupported",
|
|
69
|
+
imports: "supported",
|
|
70
|
+
calls: "partial",
|
|
71
|
+
diagnostics: "unsupported",
|
|
72
|
+
types: "partial",
|
|
73
|
+
});
|
|
74
|
+
export const rustAdapter = {
|
|
75
|
+
id: "rust",
|
|
76
|
+
extensions: [".rs"],
|
|
77
|
+
capabilities: RUST_CAPABILITIES,
|
|
78
|
+
async parse(filePath, source) {
|
|
79
|
+
const summary = parseRustSource(filePath, source);
|
|
80
|
+
const ok = Boolean(summary.moduleName || summary.symbols.length > 0 || summary.imports.length > 0);
|
|
81
|
+
return {
|
|
82
|
+
language: "rust",
|
|
83
|
+
file: filePath,
|
|
84
|
+
ok,
|
|
85
|
+
capabilities: rustAdapter.capabilities(),
|
|
86
|
+
symbols: summary.symbols,
|
|
87
|
+
imports: summary.imports,
|
|
88
|
+
calls: [],
|
|
89
|
+
diagnostics: ok ? [] : ["no mod, use, or declarations detected"],
|
|
90
|
+
limitations: summary.limitations,
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Unsupported capabilities must be reported as unsupported, never as inferred AST.
|
|
4
4
|
*/
|
|
5
5
|
export type LanguageId = "typescript" | "javascript" | "python" | "java" | "kotlin" | "go" | "rust" | "php" | "dart" | "unknown";
|
|
6
|
-
export type AdapterCapability = "parse" | "symbols" | "definitions" | "references" | "imports" | "calls" | "diagnostics";
|
|
6
|
+
export type AdapterCapability = "parse" | "symbols" | "definitions" | "references" | "imports" | "calls" | "diagnostics" | "types";
|
|
7
7
|
export interface LanguageSymbol {
|
|
8
8
|
id: string;
|
|
9
9
|
name: string;
|
|
@@ -22,11 +22,12 @@ export interface LanguageCall {
|
|
|
22
22
|
callee: string;
|
|
23
23
|
evidence: "ast" | "unsupported";
|
|
24
24
|
}
|
|
25
|
+
export type AdapterCapabilityLevel = "supported" | "unsupported" | "partial";
|
|
25
26
|
export interface ParseResult {
|
|
26
27
|
language: LanguageId;
|
|
27
28
|
file: string;
|
|
28
29
|
ok: boolean;
|
|
29
|
-
capabilities: Partial<Record<AdapterCapability,
|
|
30
|
+
capabilities: Partial<Record<AdapterCapability, AdapterCapabilityLevel>>;
|
|
30
31
|
symbols: LanguageSymbol[];
|
|
31
32
|
imports: LanguageImport[];
|
|
32
33
|
calls: LanguageCall[];
|
|
@@ -36,7 +37,7 @@ export interface ParseResult {
|
|
|
36
37
|
export interface LanguageAdapter {
|
|
37
38
|
id: LanguageId;
|
|
38
39
|
extensions: string[];
|
|
39
|
-
capabilities(): Partial<Record<AdapterCapability,
|
|
40
|
+
capabilities(): Partial<Record<AdapterCapability, AdapterCapabilityLevel>>;
|
|
40
41
|
parse(filePath: string, source: string): Promise<ParseResult>;
|
|
41
42
|
}
|
|
42
43
|
export declare function detectLanguage(filePath: string): LanguageId;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type { ModelProvider } from "../../ai/types.js";
|
|
3
|
+
export declare const AGENT_MCP_TOOL_NAMES: readonly ["project_context", "project_ask", "code_search", "file_read", "file_create", "file_edit", "approval_issue", "agent_plan", "change_verify"];
|
|
4
|
+
export type AgentMcpToolName = (typeof AGENT_MCP_TOOL_NAMES)[number];
|
|
5
|
+
export declare function listAgentMcpTools(): Tool[];
|
|
6
|
+
export declare function invokeAgentMcpTool(root: string, name: string, args: Record<string, unknown>, options?: {
|
|
7
|
+
provider?: ModelProvider;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
structured: unknown;
|
|
10
|
+
isError: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
/** Convenience for tests */
|
|
13
|
+
export declare function projectFingerprint(root: string): Promise<unknown>;
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { createModelProvider, loadAiConfig } from "../../ai/index.js";
|
|
2
|
+
import { ChatService } from "../../agent/chat/service.js";
|
|
3
|
+
import { buildAgentPlan } from "../../agent/plan.js";
|
|
4
|
+
import { executeAgentTool, newToolCall } from "../../agent/tools/index.js";
|
|
5
|
+
import { retrieveProjectContext } from "../../agent/context/retrieve.js";
|
|
6
|
+
import { summarizeProjectForChat } from "../../agent/chat/project-summary.js";
|
|
7
|
+
import { consumeApprovalGrant, issueApprovalGrant, hashPlanPayload, hashFileWritePlan, } from "../../product/approval/session.js";
|
|
8
|
+
import { assertForensicReadOnly } from "../../product/forensic/mode.js";
|
|
9
|
+
export const AGENT_MCP_TOOL_NAMES = [
|
|
10
|
+
"project_context",
|
|
11
|
+
"project_ask",
|
|
12
|
+
"code_search",
|
|
13
|
+
"file_read",
|
|
14
|
+
"file_create",
|
|
15
|
+
"file_edit",
|
|
16
|
+
"approval_issue",
|
|
17
|
+
"agent_plan",
|
|
18
|
+
"change_verify",
|
|
19
|
+
];
|
|
20
|
+
export function listAgentMcpTools() {
|
|
21
|
+
return [
|
|
22
|
+
{
|
|
23
|
+
name: "project_context",
|
|
24
|
+
description: "READ: Budgeted project context pack for a query (path-safe).",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: { query: { type: "string" } },
|
|
28
|
+
required: ["query"],
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "project_ask",
|
|
34
|
+
description: "READ: One-shot Project Chat ask (requires AI provider; no file writes).",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: { question: { type: "string" } },
|
|
38
|
+
required: ["question"],
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "code_search",
|
|
44
|
+
description: "READ: Search code via AgentDoctor graph search.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: { query: { type: "string" } },
|
|
48
|
+
required: ["query"],
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "file_read",
|
|
54
|
+
description: "READ: Path-safe file read inside workspace.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: { path: { type: "string" } },
|
|
58
|
+
required: ["path"],
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "file_create",
|
|
64
|
+
description: "WRITE: Create file. Requires approvalToken from issueApprovalGrant / CLI --approve. Bare approved=true is rejected.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
path: { type: "string" },
|
|
69
|
+
content: { type: "string" },
|
|
70
|
+
approvalToken: { type: "string" },
|
|
71
|
+
approved: {
|
|
72
|
+
type: "boolean",
|
|
73
|
+
description: "Legacy flag — insufficient alone; approvalToken required",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
required: ["path", "content", "approvalToken"],
|
|
77
|
+
additionalProperties: false,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "file_edit",
|
|
82
|
+
description: "WRITE: Edit file. Requires approvalToken from issueApprovalGrant / CLI --approve.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
path: { type: "string" },
|
|
87
|
+
content: { type: "string" },
|
|
88
|
+
oldContent: { type: "string" },
|
|
89
|
+
approvalToken: { type: "string" },
|
|
90
|
+
approved: {
|
|
91
|
+
type: "boolean",
|
|
92
|
+
description: "Legacy flag — insufficient alone; approvalToken required",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: ["path", "approvalToken"],
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "approval_issue",
|
|
101
|
+
description: "TRUSTED: Issue a short-lived approval grant for writes. Requires AGENTDOCTOR_MCP_TRUSTED_APPROVE=1 in the MCP server environment (not model-settable).",
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
action: { type: "string" },
|
|
106
|
+
resources: { type: "array", items: { type: "string" } },
|
|
107
|
+
planHash: { type: "string" },
|
|
108
|
+
risk: { type: "string", enum: ["LOW", "MEDIUM", "HIGH", "CRITICAL"] },
|
|
109
|
+
},
|
|
110
|
+
required: ["action", "resources", "planHash"],
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "agent_plan",
|
|
116
|
+
description: "READ: Build a change plan without modifying files.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: { goal: { type: "string" } },
|
|
120
|
+
required: ["goal"],
|
|
121
|
+
additionalProperties: false,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "change_verify",
|
|
126
|
+
description: "READ: Produce evidence/proof for current changes (may write evidence artifacts).",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: { changeId: { type: "string" } },
|
|
130
|
+
additionalProperties: false,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
export async function invokeAgentMcpTool(root, name, args, options) {
|
|
136
|
+
try {
|
|
137
|
+
switch (name) {
|
|
138
|
+
case "project_context": {
|
|
139
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
140
|
+
const bundle = await retrieveProjectContext({ root, query });
|
|
141
|
+
return { structured: { ok: true, bundle }, isError: false };
|
|
142
|
+
}
|
|
143
|
+
case "project_ask": {
|
|
144
|
+
const question = typeof args.question === "string" ? args.question : "";
|
|
145
|
+
if (!question) {
|
|
146
|
+
return {
|
|
147
|
+
structured: {
|
|
148
|
+
ok: false,
|
|
149
|
+
error: { code: "invalid_argument", message: "question required" },
|
|
150
|
+
},
|
|
151
|
+
isError: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const provider = options?.provider ?? createModelProvider(loadAiConfig());
|
|
155
|
+
const chat = new ChatService({
|
|
156
|
+
root,
|
|
157
|
+
provider,
|
|
158
|
+
persistAudit: false,
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
const response = await chat.ask(question);
|
|
162
|
+
return {
|
|
163
|
+
structured: { ok: response.status === "ok", response },
|
|
164
|
+
isError: response.status !== "ok" && response.status !== "provider-none",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
await chat.end();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
case "code_search": {
|
|
172
|
+
const result = await executeAgentTool(root, newToolCall("mcp", "search_code", { query: args.query }));
|
|
173
|
+
return { structured: result, isError: !result.ok };
|
|
174
|
+
}
|
|
175
|
+
case "file_read": {
|
|
176
|
+
const result = await executeAgentTool(root, newToolCall("mcp", "read_file", { path: args.path }));
|
|
177
|
+
return { structured: result, isError: !result.ok };
|
|
178
|
+
}
|
|
179
|
+
case "file_create": {
|
|
180
|
+
assertForensicReadOnly(process.env.AGENTDOCTOR_FORENSIC_MODE === "1");
|
|
181
|
+
const token = typeof args.approvalToken === "string" ? args.approvalToken : undefined;
|
|
182
|
+
const pathArg = typeof args.path === "string" ? args.path : "";
|
|
183
|
+
const content = typeof args.content === "string" ? args.content : "";
|
|
184
|
+
const planHash = hashFileWritePlan("file_create", pathArg, content);
|
|
185
|
+
const gate = await consumeApprovalGrant({
|
|
186
|
+
root,
|
|
187
|
+
token,
|
|
188
|
+
action: "file_create",
|
|
189
|
+
resources: [pathArg],
|
|
190
|
+
requirePlanHash: planHash,
|
|
191
|
+
});
|
|
192
|
+
if (!gate.ok) {
|
|
193
|
+
return {
|
|
194
|
+
structured: {
|
|
195
|
+
ok: false,
|
|
196
|
+
error: { code: "approval_required", message: gate.reason },
|
|
197
|
+
},
|
|
198
|
+
isError: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const result = await executeAgentTool(root, newToolCall("mcp", "create_file", { path: args.path, content: args.content }), { allowWrite: true, approvedByHuman: true });
|
|
202
|
+
return { structured: result, isError: !result.ok };
|
|
203
|
+
}
|
|
204
|
+
case "file_edit": {
|
|
205
|
+
assertForensicReadOnly(process.env.AGENTDOCTOR_FORENSIC_MODE === "1");
|
|
206
|
+
const token = typeof args.approvalToken === "string" ? args.approvalToken : undefined;
|
|
207
|
+
const pathArg = typeof args.path === "string" ? args.path : "";
|
|
208
|
+
const content = typeof args.content === "string" ? args.content : "";
|
|
209
|
+
const planHash = hashFileWritePlan("file_edit", pathArg, content);
|
|
210
|
+
const gate = await consumeApprovalGrant({
|
|
211
|
+
root,
|
|
212
|
+
token,
|
|
213
|
+
action: "file_edit",
|
|
214
|
+
resources: [pathArg],
|
|
215
|
+
requirePlanHash: planHash,
|
|
216
|
+
});
|
|
217
|
+
if (!gate.ok) {
|
|
218
|
+
return {
|
|
219
|
+
structured: {
|
|
220
|
+
ok: false,
|
|
221
|
+
error: { code: "approval_required", message: gate.reason },
|
|
222
|
+
},
|
|
223
|
+
isError: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const result = await executeAgentTool(root, newToolCall("mcp", "edit_file", {
|
|
227
|
+
path: args.path,
|
|
228
|
+
content: args.content,
|
|
229
|
+
oldContent: args.oldContent,
|
|
230
|
+
}), { allowWrite: true, approvedByHuman: true });
|
|
231
|
+
return { structured: result, isError: !result.ok };
|
|
232
|
+
}
|
|
233
|
+
case "approval_issue": {
|
|
234
|
+
if (process.env.AGENTDOCTOR_MCP_TRUSTED_APPROVE !== "1") {
|
|
235
|
+
return {
|
|
236
|
+
structured: {
|
|
237
|
+
ok: false,
|
|
238
|
+
error: {
|
|
239
|
+
code: "trust_required",
|
|
240
|
+
message: "approval_issue requires AGENTDOCTOR_MCP_TRUSTED_APPROVE=1 in the MCP host environment",
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
isError: true,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const resources = Array.isArray(args.resources)
|
|
247
|
+
? args.resources.filter((r) => typeof r === "string")
|
|
248
|
+
: [];
|
|
249
|
+
const action = typeof args.action === "string" ? args.action : "file_edit";
|
|
250
|
+
const planHash = typeof args.planHash === "string" && args.planHash.length >= 8
|
|
251
|
+
? args.planHash
|
|
252
|
+
: hashPlanPayload(action);
|
|
253
|
+
const risk = (typeof args.risk === "string" ? args.risk : "MEDIUM");
|
|
254
|
+
try {
|
|
255
|
+
const grant = await issueApprovalGrant({
|
|
256
|
+
root,
|
|
257
|
+
action,
|
|
258
|
+
resources,
|
|
259
|
+
risk,
|
|
260
|
+
planHash,
|
|
261
|
+
actor: "mcp-trusted-host",
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
structured: { ok: true, token: grant.token, expiresAt: grant.expiresAt, planHash },
|
|
265
|
+
isError: false,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
return {
|
|
270
|
+
structured: {
|
|
271
|
+
ok: false,
|
|
272
|
+
error: {
|
|
273
|
+
code: "invalid_grant",
|
|
274
|
+
message: error instanceof Error ? error.message : String(error),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
isError: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
case "agent_plan": {
|
|
282
|
+
const goal = typeof args.goal === "string" ? args.goal : "";
|
|
283
|
+
const plan = await buildAgentPlan({ root, goal });
|
|
284
|
+
return { structured: { ok: true, plan }, isError: false };
|
|
285
|
+
}
|
|
286
|
+
case "change_verify": {
|
|
287
|
+
const { verifyChange } = await import("../../assurance/change.js");
|
|
288
|
+
const result = await verifyChange({
|
|
289
|
+
root,
|
|
290
|
+
...(typeof args.changeId === "string" ? { changeId: args.changeId } : {}),
|
|
291
|
+
});
|
|
292
|
+
return { structured: { ok: true, ...result }, isError: false };
|
|
293
|
+
}
|
|
294
|
+
default:
|
|
295
|
+
return {
|
|
296
|
+
structured: {
|
|
297
|
+
ok: false,
|
|
298
|
+
error: { code: "invalid_argument", message: `unknown agent tool: ${name}` },
|
|
299
|
+
},
|
|
300
|
+
isError: true,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
structured: {
|
|
307
|
+
ok: false,
|
|
308
|
+
error: {
|
|
309
|
+
code: "internal",
|
|
310
|
+
message: error instanceof Error ? error.message : String(error),
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
isError: true,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/** Convenience for tests */
|
|
318
|
+
export async function projectFingerprint(root) {
|
|
319
|
+
return summarizeProjectForChat(root);
|
|
320
|
+
}
|
|
@@ -6,6 +6,7 @@ import { resolveRepoRoot } from "../../utils/path.js";
|
|
|
6
6
|
import { BrainMcpSession } from "../brain/session.js";
|
|
7
7
|
import { BRAIN_MCP_TOOL_NAMES, invokeBrainMcpTool, listBrainMcpTools, } from "../brain/tools/registry.js";
|
|
8
8
|
import { INTELLIGENCE_MCP_TOOL_NAMES, invokeIntelligenceMcpTool, listIntelligenceMcpTools, } from "../intelligence/registry.js";
|
|
9
|
+
import { AGENT_MCP_TOOL_NAMES, invokeAgentMcpTool, listAgentMcpTools } from "../agent/registry.js";
|
|
9
10
|
/**
|
|
10
11
|
* Combined AgentDoctor MCP server: preserves all Brain tool names and adds
|
|
11
12
|
* intelligence tools. No arbitrary shell execution; policy_evaluate is evaluate-only.
|
|
@@ -19,6 +20,7 @@ export async function startAgentDoctorMcpServer(options) {
|
|
|
19
20
|
await session.initialize();
|
|
20
21
|
const brainNames = new Set(BRAIN_MCP_TOOL_NAMES);
|
|
21
22
|
const intelNames = new Set(INTELLIGENCE_MCP_TOOL_NAMES);
|
|
23
|
+
const agentNames = new Set(AGENT_MCP_TOOL_NAMES);
|
|
22
24
|
const server = new Server({
|
|
23
25
|
name: "agentdoctor",
|
|
24
26
|
version: PACKAGE_VERSION,
|
|
@@ -28,7 +30,7 @@ export async function startAgentDoctorMcpServer(options) {
|
|
|
28
30
|
},
|
|
29
31
|
});
|
|
30
32
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
31
|
-
tools: [...listBrainMcpTools(), ...listIntelligenceMcpTools()],
|
|
33
|
+
tools: [...listBrainMcpTools(), ...listIntelligenceMcpTools(), ...listAgentMcpTools()],
|
|
32
34
|
}));
|
|
33
35
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
34
36
|
const name = request.params.name;
|
|
@@ -45,6 +47,11 @@ export async function startAgentDoctorMcpServer(options) {
|
|
|
45
47
|
structured = result.structured;
|
|
46
48
|
isError = result.isError;
|
|
47
49
|
}
|
|
50
|
+
else if (agentNames.has(name)) {
|
|
51
|
+
const result = await invokeAgentMcpTool(root, name, args);
|
|
52
|
+
structured = result.structured;
|
|
53
|
+
isError = result.isError;
|
|
54
|
+
}
|
|
48
55
|
else {
|
|
49
56
|
structured = {
|
|
50
57
|
ok: false,
|
|
@@ -14,3 +14,6 @@ export declare function handleArchitectureCheckTool(rootInput: string): Promise<
|
|
|
14
14
|
export declare function handleProofInspectTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
15
15
|
export declare function handleEvidenceInspectTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
16
16
|
export declare function handleGraphQueryTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|
|
17
|
+
export declare function handleProjectDnaTool(rootInput: string): Promise<unknown>;
|
|
18
|
+
export declare function handleSoftwareMapTool(rootInput: string): Promise<unknown>;
|
|
19
|
+
export declare function handleWhatIfTool(rootInput: string, args: Record<string, unknown>): Promise<unknown>;
|