@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.
Files changed (168) hide show
  1. package/CHANGELOG.md +141 -4
  2. package/README.md +329 -153
  3. package/dist/agent/approvals.d.ts +24 -0
  4. package/dist/agent/approvals.js +64 -0
  5. package/dist/agent/chat/index.d.ts +7 -0
  6. package/dist/agent/chat/index.js +5 -0
  7. package/dist/agent/chat/memory.d.ts +44 -0
  8. package/dist/agent/chat/memory.js +103 -0
  9. package/dist/agent/chat/project-summary.d.ts +16 -0
  10. package/dist/agent/chat/project-summary.js +70 -0
  11. package/dist/agent/chat/prompts.d.ts +6 -0
  12. package/dist/agent/chat/prompts.js +39 -0
  13. package/dist/agent/chat/response.d.ts +19 -0
  14. package/dist/agent/chat/response.js +109 -0
  15. package/dist/agent/chat/service.d.ts +42 -0
  16. package/dist/agent/chat/service.js +252 -0
  17. package/dist/agent/chat/types.d.ts +48 -0
  18. package/dist/agent/chat/types.js +1 -0
  19. package/dist/agent/context/retrieve.d.ts +21 -0
  20. package/dist/agent/context/retrieve.js +117 -0
  21. package/dist/agent/context/truth.d.ts +6 -0
  22. package/dist/agent/context/truth.js +19 -0
  23. package/dist/agent/context/types.d.ts +23 -0
  24. package/dist/agent/context/types.js +4 -0
  25. package/dist/agent/index.d.ts +26 -0
  26. package/dist/agent/index.js +14 -0
  27. package/dist/agent/loop.d.ts +51 -0
  28. package/dist/agent/loop.js +222 -0
  29. package/dist/agent/modes.d.ts +18 -0
  30. package/dist/agent/modes.js +102 -0
  31. package/dist/agent/plan.d.ts +30 -0
  32. package/dist/agent/plan.js +121 -0
  33. package/dist/agent/runtime.d.ts +67 -0
  34. package/dist/agent/runtime.js +180 -0
  35. package/dist/agent/state.d.ts +30 -0
  36. package/dist/agent/state.js +95 -0
  37. package/dist/agent/student.d.ts +56 -0
  38. package/dist/agent/student.js +230 -0
  39. package/dist/agent/tools/execute.d.ts +18 -0
  40. package/dist/agent/tools/execute.js +355 -0
  41. package/dist/agent/tools/index.d.ts +6 -0
  42. package/dist/agent/tools/index.js +5 -0
  43. package/dist/agent/tools/registry.d.ts +7 -0
  44. package/dist/agent/tools/registry.js +212 -0
  45. package/dist/agent/tools/run.d.ts +24 -0
  46. package/dist/agent/tools/run.js +44 -0
  47. package/dist/agent/tools/types.d.ts +32 -0
  48. package/dist/agent/tools/types.js +10 -0
  49. package/dist/agent/tools/write.d.ts +24 -0
  50. package/dist/agent/tools/write.js +121 -0
  51. package/dist/agent/verify.d.ts +29 -0
  52. package/dist/agent/verify.js +210 -0
  53. package/dist/ai/config.d.ts +22 -0
  54. package/dist/ai/config.js +68 -0
  55. package/dist/ai/index.d.ts +17 -0
  56. package/dist/ai/index.js +52 -0
  57. package/dist/ai/providers/mock.d.ts +20 -0
  58. package/dist/ai/providers/mock.js +84 -0
  59. package/dist/ai/providers/none.d.ts +6 -0
  60. package/dist/ai/providers/none.js +23 -0
  61. package/dist/ai/providers/openai-compatible.d.ts +21 -0
  62. package/dist/ai/providers/openai-compatible.js +151 -0
  63. package/dist/ai/redact.d.ts +8 -0
  64. package/dist/ai/redact.js +21 -0
  65. package/dist/ai/types.d.ts +71 -0
  66. package/dist/ai/types.js +6 -0
  67. package/dist/architecture/contract.d.ts +64 -0
  68. package/dist/architecture/contract.js +373 -0
  69. package/dist/assurance/change.d.ts +211 -0
  70. package/dist/assurance/change.js +624 -0
  71. package/dist/assurance/proof.d.ts +136 -0
  72. package/dist/assurance/proof.js +338 -0
  73. package/dist/auth/index.d.ts +81 -0
  74. package/dist/auth/index.js +130 -0
  75. package/dist/auth/rbac.d.ts +9 -0
  76. package/dist/auth/rbac.js +26 -0
  77. package/dist/cli/commands/agent.d.ts +27 -0
  78. package/dist/cli/commands/agent.js +153 -0
  79. package/dist/cli/commands/architecture.d.ts +6 -0
  80. package/dist/cli/commands/architecture.js +109 -0
  81. package/dist/cli/commands/assurance.d.ts +67 -0
  82. package/dist/cli/commands/assurance.js +210 -0
  83. package/dist/cli/commands/chat.d.ts +14 -0
  84. package/dist/cli/commands/chat.js +155 -0
  85. package/dist/cli/commands/complete.js +2 -2
  86. package/dist/cli/commands/learn.d.ts +12 -0
  87. package/dist/cli/commands/learn.js +107 -0
  88. package/dist/cli/commands/platform.d.ts +2 -0
  89. package/dist/cli/commands/platform.js +5 -1
  90. package/dist/cli/commands/policy-graph-run.d.ts +40 -0
  91. package/dist/cli/commands/policy-graph-run.js +200 -0
  92. package/dist/cli/commands/workspace.d.ts +9 -0
  93. package/dist/cli/commands/workspace.js +82 -0
  94. package/dist/cli/program.js +627 -17
  95. package/dist/constants.d.ts +1 -1
  96. package/dist/constants.js +1 -1
  97. package/dist/contracts/index.d.ts +1 -1
  98. package/dist/contracts/index.js +2 -2
  99. package/dist/core/brain-product/init.js +5 -2
  100. package/dist/core/secrets/scan.d.ts +7 -1
  101. package/dist/core/secrets/scan.js +9 -9
  102. package/dist/coverage/cobertura.d.ts +6 -0
  103. package/dist/coverage/cobertura.js +52 -0
  104. package/dist/coverage/istanbul.d.ts +7 -0
  105. package/dist/coverage/istanbul.js +134 -0
  106. package/dist/coverage/lcov.d.ts +7 -0
  107. package/dist/coverage/lcov.js +59 -0
  108. package/dist/coverage/load.d.ts +9 -0
  109. package/dist/coverage/load.js +39 -0
  110. package/dist/coverage/types.d.ts +23 -0
  111. package/dist/coverage/types.js +5 -0
  112. package/dist/dashboard/server.d.ts +7 -1
  113. package/dist/dashboard/server.js +110 -6
  114. package/dist/enforcement/runner.d.ts +52 -3
  115. package/dist/enforcement/runner.js +467 -16
  116. package/dist/index.d.ts +36 -2
  117. package/dist/index.js +23 -2
  118. package/dist/intelligence/graph/build.d.ts +2 -0
  119. package/dist/intelligence/graph/build.js +49 -5
  120. package/dist/intelligence/graph/incremental.d.ts +68 -0
  121. package/dist/intelligence/graph/incremental.js +234 -0
  122. package/dist/intelligence/resolve/imports.d.ts +36 -0
  123. package/dist/intelligence/resolve/imports.js +245 -0
  124. package/dist/languages/go.d.ts +9 -0
  125. package/dist/languages/go.js +50 -0
  126. package/dist/languages/index.d.ts +11 -0
  127. package/dist/languages/index.js +76 -0
  128. package/dist/languages/php.d.ts +8 -0
  129. package/dist/languages/php.js +202 -0
  130. package/dist/languages/python.d.ts +11 -0
  131. package/dist/languages/python.js +180 -0
  132. package/dist/languages/types.d.ts +42 -0
  133. package/dist/languages/types.js +26 -0
  134. package/dist/languages/typescript.d.ts +3 -0
  135. package/dist/languages/typescript.js +95 -0
  136. package/dist/mcp/agent/registry.d.ts +13 -0
  137. package/dist/mcp/agent/registry.js +234 -0
  138. package/dist/mcp/agentdoctor/server.js +8 -1
  139. package/dist/mcp/intelligence/handlers.d.ts +5 -0
  140. package/dist/mcp/intelligence/handlers.js +229 -40
  141. package/dist/mcp/intelligence/path-safety.d.ts +2 -4
  142. package/dist/mcp/intelligence/path-safety.js +25 -44
  143. package/dist/mcp/intelligence/registry.d.ts +1 -1
  144. package/dist/mcp/intelligence/registry.js +69 -1
  145. package/dist/platform/firewall/evaluate.d.ts +6 -3
  146. package/dist/platform/firewall/evaluate.js +66 -15
  147. package/dist/platform/graph/build.js +19 -23
  148. package/dist/platform/health/analyze.js +23 -17
  149. package/dist/platform/index.js +2 -0
  150. package/dist/platform/test-impact/analyze.d.ts +21 -3
  151. package/dist/platform/test-impact/analyze.js +214 -28
  152. package/dist/platform/tokens/plan.js +39 -20
  153. package/dist/platform/types.d.ts +9 -0
  154. package/dist/policy/compose.d.ts +34 -0
  155. package/dist/policy/compose.js +118 -0
  156. package/dist/policy/packs.js +33 -1
  157. package/dist/security/paths.d.ts +21 -0
  158. package/dist/security/paths.js +133 -0
  159. package/dist/storage/postgres.d.ts +26 -0
  160. package/dist/storage/postgres.js +90 -0
  161. package/dist/storage/provider.d.ts +2 -7
  162. package/dist/storage/provider.js +22 -16
  163. package/dist/storage/sqlite.d.ts +33 -0
  164. package/dist/storage/sqlite.js +77 -0
  165. package/dist/team/auth.js +1 -1
  166. package/dist/workspace/index.d.ts +69 -0
  167. package/dist/workspace/index.js +220 -0
  168. 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 = String(args.query ?? "")
25
- .toLowerCase()
26
- .trim();
58
+ const query = requireString(args, "query")?.toLowerCase() ?? "";
27
59
  if (!query) {
28
- return { ok: false, error: { code: "invalid_argument", message: "query required" } };
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 = String(args.name ?? "").trim();
79
+ const name = requireString(args, "name");
48
80
  if (!name) {
49
- return { ok: false, error: { code: "invalid_argument", message: "name required" } };
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 = String(args.target ?? "").trim();
96
+ const target = requireString(args, "target");
65
97
  if (!target) {
66
- return { ok: false, error: { code: "invalid_argument", message: "target required" } };
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 = String(args.symbol ?? "").trim();
120
+ const symbol = requireString(args, "symbol");
92
121
  if (!symbol) {
93
- return { ok: false, error: { code: "invalid_argument", message: "symbol required" } };
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 = String(args.symbol ?? "").trim();
146
+ const symbol = requireString(args, "symbol");
118
147
  if (!symbol) {
119
- return { ok: false, error: { code: "invalid_argument", message: "symbol required" } };
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 = String(args.query ?? "").trim();
177
+ const query = typeof args.query === "string" ? args.query.trim() : "";
149
178
  if (!query) {
150
- return { ok: true, records: list.filter((r) => r.status === "approved").slice(0, 50) };
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 = String(args.command ?? "").trim();
189
+ const command = requireString(args, "command");
158
190
  if (!command) {
159
- return { ok: false, error: { code: "invalid_argument", message: "command required" } };
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
- * Decode a single layer of URI encoding. Rejects malformed sequences by returning null.
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 { isPathInsideRoot, sanitizeForOutput } from "../../utils/path.js";
3
- /**
4
- * Decode a single layer of URI encoding. Rejects malformed sequences by returning null.
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
- // Reject any .. segment forms before resolve (prevents src/x/../../etc tricks).
49
- const normalizedSlashes = candidate.replace(/\\/g, "/");
50
- if (normalizedSlashes === ".." ||
51
- normalizedSlashes.includes("/../") ||
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
- // Always reject Windows drive-absolute forms (even on POSIX hosts).
59
- if (/^[A-Za-z]:[\\/]/.test(candidate)) {
60
- throw new Error("path escapes repository root");
61
- }
62
- // Resolve relative to root; absolute candidates resolve to themselves.
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
- // Prefer decoded form for relative path returns when path-like.
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 resolved = path.isAbsolute(primary) ? path.resolve(primary) : path.resolve(root, primary);
81
- return path.relative(root, resolved).split(path.sep).join("/");
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
- * Invalid file + failClosed (file flag or option) → deny-all.
42
- * Invalid file + default → documented fallback DEFAULT_POLICY.
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;