@praneeth_54/agentdoctor 1.1.1 → 2.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.
Files changed (170) hide show
  1. package/CHANGELOG.md +95 -7
  2. package/README.md +163 -538
  3. package/dist/agents/aider/adapter.d.ts +1 -0
  4. package/dist/agents/aider/adapter.js +1 -0
  5. package/dist/agents/aider/detector.d.ts +14 -0
  6. package/dist/agents/aider/detector.js +120 -0
  7. package/dist/agents/copilot/adapter.d.ts +1 -0
  8. package/dist/agents/copilot/adapter.js +1 -0
  9. package/dist/agents/copilot/detector.d.ts +13 -0
  10. package/dist/agents/copilot/detector.js +113 -0
  11. package/dist/agents/gemini/adapter.d.ts +1 -0
  12. package/dist/agents/gemini/adapter.js +1 -0
  13. package/dist/agents/gemini/detector.d.ts +13 -0
  14. package/dist/agents/gemini/detector.js +151 -0
  15. package/dist/agents/registry.js +13 -1
  16. package/dist/agents/types.d.ts +1 -1
  17. package/dist/agents/windsurf/adapter.d.ts +1 -0
  18. package/dist/agents/windsurf/adapter.js +1 -0
  19. package/dist/agents/windsurf/detector.d.ts +14 -0
  20. package/dist/agents/windsurf/detector.js +119 -0
  21. package/dist/architecture/c4.d.ts +22 -0
  22. package/dist/architecture/c4.js +62 -0
  23. package/dist/cli/commands/brain.d.ts +12 -0
  24. package/dist/cli/commands/brain.js +162 -0
  25. package/dist/cli/commands/complete.d.ts +23 -0
  26. package/dist/cli/commands/complete.js +232 -0
  27. package/dist/cli/commands/explain.js +1 -1
  28. package/dist/cli/commands/fix.js +22 -2
  29. package/dist/cli/commands/mcp.d.ts +8 -0
  30. package/dist/cli/commands/mcp.js +14 -0
  31. package/dist/cli/commands/platform.d.ts +15 -0
  32. package/dist/cli/commands/platform.js +191 -0
  33. package/dist/cli/commands/v2.d.ts +55 -0
  34. package/dist/cli/commands/v2.js +278 -0
  35. package/dist/cli/program.js +809 -2
  36. package/dist/constants.d.ts +3 -2
  37. package/dist/constants.js +5 -1
  38. package/dist/contracts/adapters.d.ts +7 -0
  39. package/dist/contracts/adapters.js +60 -0
  40. package/dist/contracts/index.d.ts +162 -0
  41. package/dist/contracts/index.js +18 -0
  42. package/dist/core/baseline/store.d.ts +48 -0
  43. package/dist/core/baseline/store.js +163 -0
  44. package/dist/core/brain-cli/service.d.ts +26 -0
  45. package/dist/core/brain-cli/service.js +175 -0
  46. package/dist/core/brain-product/init.d.ts +45 -0
  47. package/dist/core/brain-product/init.js +232 -0
  48. package/dist/core/changes/analyze.d.ts +41 -0
  49. package/dist/core/changes/analyze.js +223 -0
  50. package/dist/core/context-health/analyze.d.ts +15 -0
  51. package/dist/core/context-health/analyze.js +234 -0
  52. package/dist/core/fix/apply.d.ts +21 -4
  53. package/dist/core/fix/apply.js +118 -18
  54. package/dist/core/fix/backup.d.ts +30 -0
  55. package/dist/core/fix/backup.js +193 -0
  56. package/dist/core/fix/plan.d.ts +6 -3
  57. package/dist/core/fix/plan.js +113 -9
  58. package/dist/core/fix/render.d.ts +2 -0
  59. package/dist/core/fix/render.js +36 -21
  60. package/dist/core/fix/run.js +5 -1
  61. package/dist/core/fix/safe-target.d.ts +14 -0
  62. package/dist/core/fix/safe-target.js +81 -0
  63. package/dist/core/fix/types.d.ts +12 -1
  64. package/dist/core/fix/types.js +2 -0
  65. package/dist/core/fix/writers/claude-settings.js +3 -1
  66. package/dist/core/fix/writers/codex-config.d.ts +3 -0
  67. package/dist/core/fix/writers/codex-config.js +24 -3
  68. package/dist/core/fix/writers/cursorignore.js +3 -1
  69. package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
  70. package/dist/core/fix/writers/simple-ignore.js +68 -0
  71. package/dist/core/monorepo/detect.d.ts +13 -0
  72. package/dist/core/monorepo/detect.js +121 -0
  73. package/dist/core/rules/build-context.js +22 -3
  74. package/dist/core/rules/context/generated-directory.js +11 -1
  75. package/dist/core/rules/context/large-instruction-file.js +6 -0
  76. package/dist/core/rules/context/large-log-file.js +16 -1
  77. package/dist/core/rules/ignore.d.ts +11 -1
  78. package/dist/core/rules/ignore.js +22 -1
  79. package/dist/core/rules/instructions/empty-instructions.js +7 -0
  80. package/dist/core/rules/security/env-file-exposure.js +23 -2
  81. package/dist/core/rules/security/private-key-file.js +1 -1
  82. package/dist/core/scanner/scan.js +1 -1
  83. package/dist/core/schemas/validate.d.ts +6 -0
  84. package/dist/core/schemas/validate.js +62 -0
  85. package/dist/core/scoring/compute-scores.d.ts +1 -1
  86. package/dist/core/scoring/compute-scores.js +5 -1
  87. package/dist/core/scoring/placeholder.d.ts +1 -1
  88. package/dist/core/scoring/placeholder.js +5 -1
  89. package/dist/core/secrets/scan.d.ts +23 -0
  90. package/dist/core/secrets/scan.js +172 -0
  91. package/dist/dashboard/server.d.ts +24 -0
  92. package/dist/dashboard/server.js +386 -0
  93. package/dist/enforcement/runner.d.ts +23 -0
  94. package/dist/enforcement/runner.js +56 -0
  95. package/dist/index.d.ts +13 -0
  96. package/dist/index.js +12 -0
  97. package/dist/integrations/github/pr-analyze.d.ts +28 -0
  98. package/dist/integrations/github/pr-analyze.js +98 -0
  99. package/dist/integrations/local-ai/provider.d.ts +45 -0
  100. package/dist/integrations/local-ai/provider.js +104 -0
  101. package/dist/intelligence/git/analyze.d.ts +34 -0
  102. package/dist/intelligence/git/analyze.js +136 -0
  103. package/dist/intelligence/graph/build.d.ts +13 -0
  104. package/dist/intelligence/graph/build.js +215 -0
  105. package/dist/knowledge/store.d.ts +25 -0
  106. package/dist/knowledge/store.js +91 -0
  107. package/dist/mcp/agentdoctor/server.d.ts +16 -0
  108. package/dist/mcp/agentdoctor/server.js +67 -0
  109. package/dist/mcp/brain/session.js +7 -7
  110. package/dist/mcp/intelligence/handlers.d.ts +11 -0
  111. package/dist/mcp/intelligence/handlers.js +175 -0
  112. package/dist/mcp/intelligence/path-safety.d.ts +11 -0
  113. package/dist/mcp/intelligence/path-safety.js +82 -0
  114. package/dist/mcp/intelligence/registry.d.ts +8 -0
  115. package/dist/mcp/intelligence/registry.js +160 -0
  116. package/dist/ops/health.d.ts +18 -0
  117. package/dist/ops/health.js +51 -0
  118. package/dist/platform/ai-quality/analyze.d.ts +9 -0
  119. package/dist/platform/ai-quality/analyze.js +65 -0
  120. package/dist/platform/architecture/drift.d.ts +17 -0
  121. package/dist/platform/architecture/drift.js +74 -0
  122. package/dist/platform/auth/local.d.ts +15 -0
  123. package/dist/platform/auth/local.js +58 -0
  124. package/dist/platform/context-security/analyze.d.ts +6 -0
  125. package/dist/platform/context-security/analyze.js +97 -0
  126. package/dist/platform/firewall/evaluate.d.ts +54 -0
  127. package/dist/platform/firewall/evaluate.js +308 -0
  128. package/dist/platform/graph/build.d.ts +6 -0
  129. package/dist/platform/graph/build.js +193 -0
  130. package/dist/platform/health/analyze.d.ts +7 -0
  131. package/dist/platform/health/analyze.js +186 -0
  132. package/dist/platform/index.d.ts +27 -0
  133. package/dist/platform/index.js +87 -0
  134. package/dist/platform/knowledge/analyze.d.ts +20 -0
  135. package/dist/platform/knowledge/analyze.js +96 -0
  136. package/dist/platform/provenance/build.d.ts +44 -0
  137. package/dist/platform/provenance/build.js +65 -0
  138. package/dist/platform/readiness/scorecard.d.ts +5 -0
  139. package/dist/platform/readiness/scorecard.js +98 -0
  140. package/dist/platform/refactor/impact.d.ts +20 -0
  141. package/dist/platform/refactor/impact.js +60 -0
  142. package/dist/platform/reports/export.d.ts +12 -0
  143. package/dist/platform/reports/export.js +92 -0
  144. package/dist/platform/security/redact.d.ts +15 -0
  145. package/dist/platform/security/redact.js +87 -0
  146. package/dist/platform/sessions/store.d.ts +32 -0
  147. package/dist/platform/sessions/store.js +97 -0
  148. package/dist/platform/store.d.ts +6 -0
  149. package/dist/platform/store.js +57 -0
  150. package/dist/platform/test-impact/analyze.d.ts +32 -0
  151. package/dist/platform/test-impact/analyze.js +164 -0
  152. package/dist/platform/time-machine/compare.d.ts +17 -0
  153. package/dist/platform/time-machine/compare.js +62 -0
  154. package/dist/platform/tokens/plan.d.ts +28 -0
  155. package/dist/platform/tokens/plan.js +89 -0
  156. package/dist/platform/types.d.ts +111 -0
  157. package/dist/platform/types.js +5 -0
  158. package/dist/plugins/runtime.d.ts +36 -0
  159. package/dist/plugins/runtime.js +101 -0
  160. package/dist/plugins/sdk.d.ts +29 -0
  161. package/dist/plugins/sdk.js +114 -0
  162. package/dist/policy/packs.d.ts +9 -0
  163. package/dist/policy/packs.js +59 -0
  164. package/dist/reporters/terminal/report.js +38 -5
  165. package/dist/storage/provider.d.ts +35 -0
  166. package/dist/storage/provider.js +103 -0
  167. package/dist/team/auth.d.ts +35 -0
  168. package/dist/team/auth.js +70 -0
  169. package/dist/types/index.d.ts +5 -1
  170. package/package.json +8 -4
@@ -0,0 +1,215 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import ts from "typescript";
5
+ import { resolveRepoRoot, toPosixRelative } from "../../utils/path.js";
6
+ import { buildRepositoryGraph } from "../../platform/graph/build.js";
7
+ import { CONTRACTS_VERSION } from "../../contracts/index.js";
8
+ function nodeId(kind, key) {
9
+ return `${kind}:${createHash("sha1").update(key).digest("hex").slice(0, 12)}`;
10
+ }
11
+ async function listTsFiles(root, limit = 400) {
12
+ const out = [];
13
+ const skip = new Set(["node_modules", ".git", "dist", "coverage", ".agentdoctor", "vendor"]);
14
+ async function walk(dir) {
15
+ if (out.length >= limit)
16
+ return;
17
+ let entries;
18
+ try {
19
+ entries = await fs.readdir(dir, { withFileTypes: true });
20
+ }
21
+ catch {
22
+ return;
23
+ }
24
+ for (const e of entries) {
25
+ if (skip.has(e.name))
26
+ continue;
27
+ const abs = path.join(dir, e.name);
28
+ if (e.isSymbolicLink())
29
+ continue;
30
+ if (e.isDirectory()) {
31
+ await walk(abs);
32
+ continue;
33
+ }
34
+ if (/\.(ts|tsx|mts|cts)$/i.test(e.name) && !e.name.endsWith(".d.ts")) {
35
+ out.push(abs);
36
+ }
37
+ }
38
+ }
39
+ await walk(root);
40
+ return out.sort();
41
+ }
42
+ /**
43
+ * TypeScript compiler API based graph enrichment.
44
+ * Falls back to regex graph when mode=regex or on failure.
45
+ */
46
+ export async function buildIntelligenceGraph(options) {
47
+ const root = resolveRepoRoot(options.root);
48
+ const mode = options.mode ?? "auto";
49
+ if (mode === "regex") {
50
+ const g = await buildRepositoryGraph(root);
51
+ return { ...g, builder: "regex", astFilesParsed: 0 };
52
+ }
53
+ try {
54
+ const files = await listTsFiles(root);
55
+ if (files.length === 0) {
56
+ const g = await buildRepositoryGraph(root);
57
+ return {
58
+ ...g,
59
+ builder: "regex",
60
+ astFilesParsed: 0,
61
+ limitations: [
62
+ ...g.limitations,
63
+ "No TypeScript sources found for AST builder; used regex fallback",
64
+ ],
65
+ };
66
+ }
67
+ const program = ts.createProgram({
68
+ rootNames: files,
69
+ options: {
70
+ allowJs: false,
71
+ target: ts.ScriptTarget.ES2022,
72
+ module: ts.ModuleKind.ESNext,
73
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
74
+ skipLibCheck: true,
75
+ noEmit: true,
76
+ },
77
+ });
78
+ const nodes = [];
79
+ const edges = [];
80
+ const seen = new Set();
81
+ const pushNode = (n) => {
82
+ if (seen.has(n.id))
83
+ return;
84
+ seen.add(n.id);
85
+ nodes.push(n);
86
+ };
87
+ for (const sf of program.getSourceFiles()) {
88
+ if (sf.isDeclarationFile)
89
+ continue;
90
+ if (!files.includes(sf.fileName))
91
+ continue;
92
+ const rel = toPosixRelative(root, sf.fileName);
93
+ const fileId = nodeId("file", rel);
94
+ pushNode({ id: fileId, kind: "file", label: path.basename(rel), path: rel });
95
+ const visit = (node) => {
96
+ if (ts.isFunctionDeclaration(node) && node.name) {
97
+ const name = node.name.text;
98
+ pushNode({
99
+ id: nodeId("function", `${rel}:${name}`),
100
+ kind: "function",
101
+ label: name,
102
+ path: rel,
103
+ meta: { parser: "typescript-ast", analysisVersion: CONTRACTS_VERSION },
104
+ });
105
+ }
106
+ if (ts.isClassDeclaration(node) && node.name) {
107
+ const name = node.name.text;
108
+ const classId = nodeId("class", `${rel}:${name}`);
109
+ pushNode({
110
+ id: classId,
111
+ kind: "class",
112
+ label: name,
113
+ path: rel,
114
+ meta: { parser: "typescript-ast" },
115
+ });
116
+ if (node.heritageClauses) {
117
+ for (const h of node.heritageClauses) {
118
+ for (const t of h.types) {
119
+ const label = t.expression.getText(sf);
120
+ const toId = nodeId("dependency", `${rel}->${label}`);
121
+ pushNode({ id: toId, kind: "dependency", label, path: rel });
122
+ edges.push({
123
+ id: nodeId("edge", `${classId}->${toId}:${h.token}`),
124
+ from: classId,
125
+ to: toId,
126
+ kind: h.token === ts.SyntaxKind.ExtendsKeyword ? "extends" : "implements",
127
+ evidence: "verified",
128
+ });
129
+ }
130
+ }
131
+ }
132
+ }
133
+ if (ts.isInterfaceDeclaration(node) && node.name) {
134
+ pushNode({
135
+ id: nodeId("interface", `${rel}:${node.name.text}`),
136
+ kind: "module",
137
+ label: node.name.text,
138
+ path: rel,
139
+ meta: { symbolKind: "interface", parser: "typescript-ast" },
140
+ });
141
+ }
142
+ if (ts.isImportDeclaration(node) &&
143
+ node.moduleSpecifier &&
144
+ ts.isStringLiteral(node.moduleSpecifier)) {
145
+ const spec = node.moduleSpecifier.text;
146
+ const toId = nodeId("dependency", `${rel}->${spec}`);
147
+ pushNode({
148
+ id: toId,
149
+ kind: "dependency",
150
+ label: spec,
151
+ path: rel,
152
+ meta: { specifier: spec, parser: "typescript-ast" },
153
+ });
154
+ edges.push({
155
+ id: nodeId("edge", `${fileId}->${toId}`),
156
+ from: fileId,
157
+ to: toId,
158
+ kind: "imports",
159
+ evidence: spec.startsWith(".") ? "verified" : "inferred",
160
+ });
161
+ }
162
+ if (ts.isCallExpression(node) &&
163
+ ts.isIdentifier(node.expression) &&
164
+ node.expression.text.length > 1) {
165
+ const callee = node.expression.text;
166
+ const toId = nodeId("function", `call:${rel}:${callee}`);
167
+ pushNode({
168
+ id: toId,
169
+ kind: "function",
170
+ label: callee,
171
+ path: rel,
172
+ meta: { callSite: true, parser: "typescript-ast" },
173
+ });
174
+ edges.push({
175
+ id: nodeId("edge", `${fileId}->call:${callee}:${node.getStart(sf)}`),
176
+ from: fileId,
177
+ to: toId,
178
+ kind: "calls",
179
+ evidence: "inferred",
180
+ });
181
+ }
182
+ ts.forEachChild(node, visit);
183
+ };
184
+ visit(sf);
185
+ }
186
+ nodes.sort((a, b) => a.id.localeCompare(b.id));
187
+ edges.sort((a, b) => a.id.localeCompare(b.id));
188
+ return {
189
+ root,
190
+ generatedAt: new Date().toISOString(),
191
+ nodes,
192
+ edges,
193
+ builder: "typescript-ast",
194
+ astFilesParsed: files.length,
195
+ limitations: [
196
+ "TypeScript AST builder uses the TypeScript compiler API for .ts/.tsx only",
197
+ "Cross-file call resolution is identifier-based (not full type-checker binding)",
198
+ "Non-TypeScript languages fall back to regex graph when requested via auto+empty TS set",
199
+ `analysisVersion=${CONTRACTS_VERSION}`,
200
+ ],
201
+ };
202
+ }
203
+ catch (error) {
204
+ const g = await buildRepositoryGraph(root);
205
+ return {
206
+ ...g,
207
+ builder: "regex",
208
+ astFilesParsed: 0,
209
+ limitations: [
210
+ ...g.limitations,
211
+ `TypeScript AST builder failed; regex fallback used (${error instanceof Error ? error.message : String(error)})`,
212
+ ],
213
+ };
214
+ }
215
+ }
@@ -0,0 +1,25 @@
1
+ import type { KnowledgeRecordContract, KnowledgeStatus } from "../contracts/index.js";
2
+ import type { StorageProvider } from "../contracts/index.js";
3
+ export declare function createKnowledgeRecord(options: {
4
+ root: string;
5
+ title: string;
6
+ content: string;
7
+ status?: KnowledgeStatus;
8
+ owner?: string;
9
+ storage?: StorageProvider;
10
+ }): Promise<KnowledgeRecordContract>;
11
+ export declare function transitionKnowledge(options: {
12
+ root: string;
13
+ id: string;
14
+ to: KnowledgeStatus;
15
+ by?: string;
16
+ note?: string;
17
+ storage?: StorageProvider;
18
+ }): Promise<KnowledgeRecordContract>;
19
+ export declare function listKnowledge(root: string, storage?: StorageProvider): Promise<KnowledgeRecordContract[]>;
20
+ /** Authoritative retrieval abstains when no approved record matches. */
21
+ export declare function retrieveAuthoritative(records: KnowledgeRecordContract[], query: string): {
22
+ record: KnowledgeRecordContract | null;
23
+ abstain: boolean;
24
+ reason: string;
25
+ };
@@ -0,0 +1,91 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { FilesystemStorageProvider } from "../storage/provider.js";
3
+ import { resolveRepoRoot } from "../utils/path.js";
4
+ function kid(title) {
5
+ return `know_${createHash("sha1").update(title).digest("hex").slice(0, 12)}`;
6
+ }
7
+ export async function createKnowledgeRecord(options) {
8
+ const storage = options.storage ?? new FilesystemStorageProvider(options.root, ".agentdoctor/knowledge");
9
+ const record = {
10
+ id: kid(`${options.title}:${randomUUID()}`),
11
+ title: options.title,
12
+ content: options.content,
13
+ status: options.status ?? "draft",
14
+ owner: options.owner ?? null,
15
+ approver: null,
16
+ version: "1",
17
+ evidence: [
18
+ {
19
+ id: "ev_create",
20
+ kind: "proposed",
21
+ detail: "Created via knowledge governance API",
22
+ },
23
+ ],
24
+ relatedRepositoryId: resolveRepoRoot(options.root),
25
+ changeHistory: [
26
+ {
27
+ at: new Date().toISOString(),
28
+ note: "created",
29
+ to: options.status ?? "draft",
30
+ ...(options.owner ? { by: options.owner } : {}),
31
+ },
32
+ ],
33
+ };
34
+ await storage.set(`records/${record.id}.json`, `${JSON.stringify(record, null, 2)}\n`);
35
+ return record;
36
+ }
37
+ export async function transitionKnowledge(options) {
38
+ const storage = options.storage ?? new FilesystemStorageProvider(options.root, ".agentdoctor/knowledge");
39
+ const raw = await storage.get(`records/${options.id}.json`);
40
+ if (!raw)
41
+ throw new Error(`knowledge record not found: ${options.id}`);
42
+ const record = JSON.parse(raw);
43
+ const from = record.status;
44
+ // Authority: only pending-review/draft/proposed can become approved via explicit transition
45
+ if (options.to === "approved" && !["draft", "proposed", "pending-review"].includes(from)) {
46
+ throw new Error(`cannot approve from status ${from}`);
47
+ }
48
+ record.status = options.to;
49
+ if (options.to === "approved") {
50
+ record.approver = options.by ?? "local-approver";
51
+ record.effectiveAt = new Date().toISOString();
52
+ }
53
+ record.changeHistory.push({
54
+ at: new Date().toISOString(),
55
+ note: options.note ?? `transition ${from} -> ${options.to}`,
56
+ from,
57
+ to: options.to,
58
+ ...(options.by ? { by: options.by } : {}),
59
+ });
60
+ record.version = String(Number(record.version || "1") + 1);
61
+ await storage.set(`records/${record.id}.json`, `${JSON.stringify(record, null, 2)}\n`);
62
+ return record;
63
+ }
64
+ export async function listKnowledge(root, storage) {
65
+ const store = storage ?? new FilesystemStorageProvider(root, ".agentdoctor/knowledge");
66
+ const keys = await store.list("records");
67
+ const out = [];
68
+ for (const key of keys) {
69
+ if (!key.endsWith(".json"))
70
+ continue;
71
+ const raw = await store.get(key.startsWith("records/") ? key : `records/${key}`);
72
+ if (!raw)
73
+ continue;
74
+ out.push(JSON.parse(raw));
75
+ }
76
+ return out.sort((a, b) => a.id.localeCompare(b.id));
77
+ }
78
+ /** Authoritative retrieval abstains when no approved record matches. */
79
+ export function retrieveAuthoritative(records, query) {
80
+ const q = query.toLowerCase();
81
+ const approved = records.filter((r) => r.status === "approved" &&
82
+ (r.title.toLowerCase().includes(q) || r.content.toLowerCase().includes(q)));
83
+ if (approved.length === 0) {
84
+ return {
85
+ record: null,
86
+ abstain: true,
87
+ reason: "No approved authoritative knowledge matched the query",
88
+ };
89
+ }
90
+ return { record: approved[0], abstain: false, reason: "approved match" };
91
+ }
@@ -0,0 +1,16 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { BrainMcpSession } from "../brain/session.js";
3
+ export interface StartAgentDoctorMcpOptions {
4
+ root: string;
5
+ buildIfMissing?: boolean;
6
+ }
7
+ /**
8
+ * Combined AgentDoctor MCP server: preserves all Brain tool names and adds
9
+ * intelligence tools. No arbitrary shell execution; policy_evaluate is evaluate-only.
10
+ */
11
+ export declare function startAgentDoctorMcpServer(options: StartAgentDoctorMcpOptions): Promise<{
12
+ server: Server;
13
+ session: BrainMcpSession;
14
+ root: string;
15
+ }>;
16
+ export declare function runAgentDoctorMcpStdio(options: StartAgentDoctorMcpOptions): Promise<void>;
@@ -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 ${loaded.snapshot.id} from ${this.root}`);
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,11 @@
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>;
@@ -0,0 +1,175 @@
1
+ import { buildC4Views } from "../../architecture/c4.js";
2
+ import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
3
+ import { analyzeGitIntelligence } from "../../intelligence/git/analyze.js";
4
+ import { listKnowledge, retrieveAuthoritative } from "../../knowledge/store.js";
5
+ import { analyzeRenameImpact, analyzeTestImpact, buildRepositoryGraph, evaluateAgentAction, } from "../../platform/index.js";
6
+ import { resolveRepoRoot } from "../../utils/path.js";
7
+ import { CONTRACTS_VERSION } from "../../contracts/index.js";
8
+ import { assertSafeRepoTarget } from "./path-safety.js";
9
+ export async function handleRepoOverview(rootInput) {
10
+ const root = resolveRepoRoot(rootInput);
11
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
12
+ return {
13
+ ok: true,
14
+ contractsVersion: CONTRACTS_VERSION,
15
+ root,
16
+ builder: graph.builder,
17
+ nodeCount: graph.nodes.length,
18
+ edgeCount: graph.edges.length,
19
+ limitations: graph.limitations,
20
+ };
21
+ }
22
+ export async function handleCodebaseSearch(rootInput, args) {
23
+ const root = resolveRepoRoot(rootInput);
24
+ const query = String(args.query ?? "")
25
+ .toLowerCase()
26
+ .trim();
27
+ if (!query) {
28
+ return { ok: false, error: { code: "invalid_argument", message: "query required" } };
29
+ }
30
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
31
+ const hits = graph.nodes
32
+ .filter((n) => n.label.toLowerCase().includes(query) ||
33
+ n.id.toLowerCase().includes(query) ||
34
+ (n.path ?? "").toLowerCase().includes(query))
35
+ .slice(0, 50);
36
+ return {
37
+ ok: true,
38
+ query,
39
+ hits,
40
+ confidence: graph.builder === "typescript-ast" ? 0.85 : 0.55,
41
+ evidenceKind: graph.builder === "typescript-ast" ? "observed" : "inferred",
42
+ limitations: graph.limitations,
43
+ };
44
+ }
45
+ export async function handleSymbolLookup(rootInput, args) {
46
+ const root = resolveRepoRoot(rootInput);
47
+ const name = String(args.name ?? "").trim();
48
+ if (!name) {
49
+ return { ok: false, error: { code: "invalid_argument", message: "name required" } };
50
+ }
51
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
52
+ const matches = graph.nodes.filter((n) => (n.kind === "function" || n.kind === "class" || n.kind === "module") &&
53
+ (n.label === name || n.label.endsWith(`.${name}`)));
54
+ return {
55
+ ok: true,
56
+ name,
57
+ matches,
58
+ confidence: matches.length > 0 ? 0.8 : 0.2,
59
+ limitations: ["Import/call resolution is best-effort; unsupported languages omitted."],
60
+ };
61
+ }
62
+ export async function handleDependencyLookup(rootInput, args) {
63
+ const root = resolveRepoRoot(rootInput);
64
+ const target = String(args.target ?? "").trim();
65
+ if (!target) {
66
+ return { ok: false, error: { code: "invalid_argument", message: "target required" } };
67
+ }
68
+ let safeRelative = null;
69
+ try {
70
+ safeRelative = assertSafeRepoTarget(root, target);
71
+ }
72
+ catch {
73
+ return {
74
+ ok: false,
75
+ error: { code: "path_escape", message: "path escapes repository root" },
76
+ };
77
+ }
78
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
79
+ const lookupKeys = [target, safeRelative].filter((v) => Boolean(v));
80
+ const node = graph.nodes.find((n) => lookupKeys.includes(n.id) ||
81
+ (n.path !== undefined && lookupKeys.includes(n.path)) ||
82
+ lookupKeys.includes(n.label));
83
+ if (!node) {
84
+ return { ok: true, target, edges: [], limitations: ["Target not found in graph"] };
85
+ }
86
+ const edges = graph.edges.filter((e) => e.from === node.id || e.to === node.id);
87
+ return { ok: true, target: node, edges, confidence: 0.75 };
88
+ }
89
+ export async function handleCallGraphLookup(rootInput, args) {
90
+ const root = resolveRepoRoot(rootInput);
91
+ const symbol = String(args.symbol ?? "").trim();
92
+ if (!symbol) {
93
+ return { ok: false, error: { code: "invalid_argument", message: "symbol required" } };
94
+ }
95
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
96
+ const calls = graph.edges.filter((e) => e.kind === "calls" && (e.from.includes(symbol) || e.to.includes(symbol)));
97
+ return {
98
+ ok: true,
99
+ symbol,
100
+ calls,
101
+ confidence: graph.builder === "typescript-ast" ? 0.7 : 0.4,
102
+ limitations: ["Call edges may be incomplete for dynamic/dispatch patterns."],
103
+ };
104
+ }
105
+ export async function handleTestImpactTool(rootInput) {
106
+ const root = resolveRepoRoot(rootInput);
107
+ const impact = await analyzeTestImpact(root);
108
+ return {
109
+ ok: true,
110
+ impact,
111
+ resultKind: impact.gitAvailable ? "graph-inferred" : "unknown",
112
+ limitations: ["Coverage-backed mapping requires external coverage data (not bundled)."],
113
+ };
114
+ }
115
+ export async function handleRefactorImpactTool(rootInput, args) {
116
+ const root = resolveRepoRoot(rootInput);
117
+ const symbol = String(args.symbol ?? "").trim();
118
+ if (!symbol) {
119
+ return { ok: false, error: { code: "invalid_argument", message: "symbol required" } };
120
+ }
121
+ const graph = await buildRepositoryGraph(root);
122
+ const impact = await analyzeRenameImpact({ root, symbol, graph });
123
+ return { ok: true, impact };
124
+ }
125
+ export async function handleCodeHealthTool(rootInput) {
126
+ const root = resolveRepoRoot(rootInput);
127
+ const report = await analyzeGitIntelligence(root);
128
+ return {
129
+ ok: true,
130
+ report,
131
+ methodDisclosure: report.hotspots[0]?.method ?? "see per-metric method fields",
132
+ limitations: report.limitations,
133
+ };
134
+ }
135
+ export async function handleArchitectureTool(rootInput) {
136
+ const root = resolveRepoRoot(rootInput);
137
+ const graph = await buildIntelligenceGraph({ root, mode: "auto" });
138
+ const views = buildC4Views(graph);
139
+ return {
140
+ ok: true,
141
+ views,
142
+ label: "Inferred/proposed from graph evidence — not approved architecture facts",
143
+ };
144
+ }
145
+ export async function handleKnowledgeTool(rootInput, args) {
146
+ const root = resolveRepoRoot(rootInput);
147
+ const list = await listKnowledge(root);
148
+ const query = String(args.query ?? "").trim();
149
+ if (!query) {
150
+ return { ok: true, records: list.filter((r) => r.status === "approved").slice(0, 50) };
151
+ }
152
+ const result = retrieveAuthoritative(list, query);
153
+ return { ok: true, ...result };
154
+ }
155
+ export async function handlePolicyEvalTool(rootInput, args) {
156
+ const root = resolveRepoRoot(rootInput);
157
+ const command = String(args.command ?? "").trim();
158
+ if (!command) {
159
+ return { ok: false, error: { code: "invalid_argument", message: "command required" } };
160
+ }
161
+ const verdict = await evaluateAgentAction(root, {
162
+ actionId: `mcp_${Date.now()}`,
163
+ agentId: "agentdoctor-mcp",
164
+ timestamp: new Date().toISOString(),
165
+ type: "shell",
166
+ params: { command },
167
+ repositoryRoot: root,
168
+ });
169
+ return {
170
+ ok: true,
171
+ verdict,
172
+ executionResult: "not-executed",
173
+ notice: "Evaluate-only: AgentDoctor MCP does not execute shell commands.",
174
+ };
175
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Decode a single layer of URI encoding. Rejects malformed sequences by returning null.
3
+ */
4
+ export declare function tryDecodeUriComponent(value: string): string | null;
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 declare function assertSafeRepoTarget(root: string, rawTarget: string): string | null;