@praneeth_54/agentdoctor 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/CHANGELOG.md +86 -3
  2. package/README.md +490 -290
  3. package/dist/agent/chat/deterministic.d.ts +6 -0
  4. package/dist/agent/chat/deterministic.js +180 -0
  5. package/dist/agent/chat/service.js +8 -14
  6. package/dist/agent/context/retrieve.d.ts +2 -1
  7. package/dist/agent/context/retrieve.js +14 -1
  8. package/dist/agent/index.d.ts +2 -0
  9. package/dist/agent/index.js +1 -0
  10. package/dist/agent/loop.d.ts +2 -0
  11. package/dist/agent/loop.js +31 -0
  12. package/dist/agent/roles.d.ts +12 -0
  13. package/dist/agent/roles.js +138 -0
  14. package/dist/agent/runtime.d.ts +21 -5
  15. package/dist/agent/runtime.js +141 -28
  16. package/dist/agent/tools/execute.d.ts +2 -0
  17. package/dist/agent/tools/execute.js +49 -43
  18. package/dist/agent/tools/write.d.ts +1 -1
  19. package/dist/agent/tools/write.js +18 -4
  20. package/dist/ai/config.js +1 -0
  21. package/dist/ai/providers/adversarial-local.d.ts +7 -0
  22. package/dist/ai/providers/adversarial-local.js +146 -0
  23. package/dist/ai/redact.d.ts +1 -0
  24. package/dist/ai/redact.js +47 -7
  25. package/dist/ai/types.d.ts +1 -1
  26. package/dist/cli/commands/agent.d.ts +2 -0
  27. package/dist/cli/commands/agent.js +32 -11
  28. package/dist/cli/commands/architecture.js +7 -2
  29. package/dist/cli/commands/brain.js +7 -2
  30. package/dist/cli/commands/chat.js +15 -7
  31. package/dist/cli/commands/complete.js +13 -3
  32. package/dist/cli/commands/fix.js +6 -0
  33. package/dist/cli/commands/learn.js +7 -2
  34. package/dist/cli/commands/mcp.js +7 -3
  35. package/dist/cli/commands/platform.js +7 -2
  36. package/dist/cli/commands/policy-graph-run.js +7 -0
  37. package/dist/cli/commands/product.d.ts +19 -0
  38. package/dist/cli/commands/product.js +157 -0
  39. package/dist/cli/commands/scan.js +6 -0
  40. package/dist/cli/commands/start.d.ts +15 -0
  41. package/dist/cli/commands/start.js +86 -0
  42. package/dist/cli/commands/v2.js +61 -11
  43. package/dist/cli/commands/verify.js +6 -0
  44. package/dist/cli/program.js +141 -0
  45. package/dist/cli/safe-root.d.ts +17 -0
  46. package/dist/cli/safe-root.js +32 -0
  47. package/dist/constants.d.ts +1 -1
  48. package/dist/constants.js +1 -1
  49. package/dist/core/brain-cli/service.js +5 -2
  50. package/dist/core/monorepo/detect.js +10 -0
  51. package/dist/core/secrets/scan.js +9 -0
  52. package/dist/core/understanding/brain/storage/store.d.ts +4 -0
  53. package/dist/core/understanding/brain/storage/store.js +22 -2
  54. package/dist/dashboard/page.d.ts +2 -0
  55. package/dist/dashboard/page.js +53 -0
  56. package/dist/dashboard/server.js +117 -140
  57. package/dist/dashboard/ui/client.d.ts +2 -0
  58. package/dist/dashboard/ui/client.js +2 -0
  59. package/dist/dashboard/ui/styles.d.ts +2 -0
  60. package/dist/dashboard/ui/styles.js +138 -0
  61. package/dist/discovery/files.js +13 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +2 -0
  64. package/dist/intelligence/graph/build.js +46 -38
  65. package/dist/intelligence/graph/incremental.d.ts +2 -0
  66. package/dist/intelligence/graph/incremental.js +9 -0
  67. package/dist/intelligence/resolve/imports.js +1 -1
  68. package/dist/languages/dart.d.ts +10 -0
  69. package/dist/languages/dart.js +99 -0
  70. package/dist/languages/go.d.ts +13 -7
  71. package/dist/languages/go.js +84 -24
  72. package/dist/languages/index.d.ts +5 -1
  73. package/dist/languages/index.js +13 -36
  74. package/dist/languages/java.d.ts +10 -0
  75. package/dist/languages/java.js +80 -0
  76. package/dist/languages/kotlin.d.ts +10 -0
  77. package/dist/languages/kotlin.js +85 -0
  78. package/dist/languages/rust.d.ts +10 -0
  79. package/dist/languages/rust.js +93 -0
  80. package/dist/languages/types.d.ts +4 -3
  81. package/dist/mcp/agent/registry.d.ts +1 -1
  82. package/dist/mcp/agent/registry.js +109 -23
  83. package/dist/mcp/intelligence/handlers.d.ts +3 -0
  84. package/dist/mcp/intelligence/handlers.js +28 -0
  85. package/dist/mcp/intelligence/path-safety.d.ts +6 -1
  86. package/dist/mcp/intelligence/path-safety.js +55 -2
  87. package/dist/mcp/intelligence/registry.d.ts +1 -1
  88. package/dist/mcp/intelligence/registry.js +30 -1
  89. package/dist/platform/graph/build.js +9 -0
  90. package/dist/product/api/doctor.d.ts +14 -0
  91. package/dist/product/api/doctor.js +185 -0
  92. package/dist/product/api/openapi.d.ts +7 -0
  93. package/dist/product/api/openapi.js +122 -0
  94. package/dist/product/approval/model.d.ts +30 -0
  95. package/dist/product/approval/model.js +64 -0
  96. package/dist/product/approval/session.d.ts +49 -0
  97. package/dist/product/approval/session.js +134 -0
  98. package/dist/product/database/doctor.d.ts +30 -0
  99. package/dist/product/database/doctor.js +184 -0
  100. package/dist/product/decisions/ledger.d.ts +36 -0
  101. package/dist/product/decisions/ledger.js +141 -0
  102. package/dist/product/deps/analyze.d.ts +46 -0
  103. package/dist/product/deps/analyze.js +137 -0
  104. package/dist/product/deps/lockfiles.d.ts +25 -0
  105. package/dist/product/deps/lockfiles.js +200 -0
  106. package/dist/product/discovery/roots.d.ts +52 -0
  107. package/dist/product/discovery/roots.js +231 -0
  108. package/dist/product/dna/build.d.ts +50 -0
  109. package/dist/product/dna/build.js +255 -0
  110. package/dist/product/eval/lab.d.ts +17 -0
  111. package/dist/product/eval/lab.js +218 -0
  112. package/dist/product/events/doctor.d.ts +21 -0
  113. package/dist/product/events/doctor.js +147 -0
  114. package/dist/product/evidence-scan.d.ts +12 -0
  115. package/dist/product/evidence-scan.js +46 -0
  116. package/dist/product/evolution/timeline.d.ts +29 -0
  117. package/dist/product/evolution/timeline.js +123 -0
  118. package/dist/product/features/intelligence.d.ts +23 -0
  119. package/dist/product/features/intelligence.js +158 -0
  120. package/dist/product/forensic/mode.d.ts +25 -0
  121. package/dist/product/forensic/mode.js +70 -0
  122. package/dist/product/graph/enrich-languages.d.ts +20 -0
  123. package/dist/product/graph/enrich-languages.js +168 -0
  124. package/dist/product/health/code-health.d.ts +20 -0
  125. package/dist/product/health/code-health.js +149 -0
  126. package/dist/product/index.d.ts +72 -0
  127. package/dist/product/index.js +36 -0
  128. package/dist/product/ledger/change-ledger.d.ts +23 -0
  129. package/dist/product/ledger/change-ledger.js +64 -0
  130. package/dist/product/map/software-map.d.ts +17 -0
  131. package/dist/product/map/software-map.js +96 -0
  132. package/dist/product/memory/institutional.d.ts +16 -0
  133. package/dist/product/memory/institutional.js +92 -0
  134. package/dist/product/ops/incident.d.ts +20 -0
  135. package/dist/product/ops/incident.js +61 -0
  136. package/dist/product/ops/infra.d.ts +15 -0
  137. package/dist/product/ops/infra.js +112 -0
  138. package/dist/product/org/model.d.ts +34 -0
  139. package/dist/product/org/model.js +195 -0
  140. package/dist/product/privacy/doctor.d.ts +13 -0
  141. package/dist/product/privacy/doctor.js +90 -0
  142. package/dist/product/requirements/trace.d.ts +21 -0
  143. package/dist/product/requirements/trace.js +166 -0
  144. package/dist/product/search/index.d.ts +29 -0
  145. package/dist/product/search/index.js +116 -0
  146. package/dist/product/search/software-search.d.ts +19 -0
  147. package/dist/product/search/software-search.js +105 -0
  148. package/dist/product/security/doctor.d.ts +23 -0
  149. package/dist/product/security/doctor.js +124 -0
  150. package/dist/product/self/diagnose.d.ts +15 -0
  151. package/dist/product/self/diagnose.js +82 -0
  152. package/dist/product/techdebt/roadmap.d.ts +20 -0
  153. package/dist/product/techdebt/roadmap.js +118 -0
  154. package/dist/product/testbrain/analyze.d.ts +29 -0
  155. package/dist/product/testbrain/analyze.js +129 -0
  156. package/dist/product/truth.d.ts +12 -0
  157. package/dist/product/truth.js +30 -0
  158. package/dist/product/twin/digital-twin.d.ts +23 -0
  159. package/dist/product/twin/digital-twin.js +52 -0
  160. package/dist/product/twin/store.d.ts +21 -0
  161. package/dist/product/twin/store.js +78 -0
  162. package/dist/product/whatif/engine.d.ts +28 -0
  163. package/dist/product/whatif/engine.js +91 -0
  164. package/dist/project/ownership.d.ts +65 -0
  165. package/dist/project/ownership.js +169 -0
  166. package/dist/utils/fs.js +10 -2
  167. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import type { LanguageAdapter, ParseResult } from "./types.js";
2
- /** Languages with real AST backends today (or honest unsupported stubs). */
2
+ /** Languages with real parsers (compiler AST or lightweight line scanners). */
3
3
  export declare const languageAdapters: LanguageAdapter[];
4
4
  export declare function getAdapterForFile(filePath: string): LanguageAdapter | null;
5
5
  export declare function parseSourceFile(filePath: string, source: string): Promise<ParseResult>;
@@ -8,4 +8,8 @@ export { typescriptAdapter, javascriptAdapter } from "./typescript.js";
8
8
  export { pythonAdapter, pythonAvailable } from "./python.js";
9
9
  export { phpAdapter, phpAvailable } from "./php.js";
10
10
  export { goAdapter, goAvailable } from "./go.js";
11
+ export { javaAdapter, parseJavaSource } from "./java.js";
12
+ export { kotlinAdapter, parseKotlinSource } from "./kotlin.js";
13
+ export { rustAdapter, parseRustSource } from "./rust.js";
14
+ export { dartAdapter, parseDartSource } from "./dart.js";
11
15
  export type { LanguageAdapter, ParseResult, LanguageId } from "./types.js";
@@ -3,48 +3,21 @@ import { javascriptAdapter, typescriptAdapter } from "./typescript.js";
3
3
  import { pythonAdapter } from "./python.js";
4
4
  import { phpAdapter } from "./php.js";
5
5
  import { goAdapter } from "./go.js";
6
- function unsupportedAdapter(id, extensions, reason) {
7
- return {
8
- id,
9
- extensions,
10
- capabilities: () => ({
11
- parse: "unsupported",
12
- symbols: "unsupported",
13
- definitions: "unsupported",
14
- references: "unsupported",
15
- imports: "unsupported",
16
- calls: "unsupported",
17
- diagnostics: "unsupported",
18
- }),
19
- async parse(filePath, _source) {
20
- return {
21
- language: id,
22
- file: filePath,
23
- ok: false,
24
- capabilities: this.capabilities(),
25
- symbols: [],
26
- imports: [],
27
- calls: [],
28
- diagnostics: [reason],
29
- limitations: [
30
- reason,
31
- "No regex fallback is offered under an AST claim — capability remains unsupported until a mature parser is integrated",
32
- ],
33
- };
34
- },
35
- };
36
- }
37
- /** Languages with real AST backends today (or honest unsupported stubs). */
6
+ import { javaAdapter } from "./java.js";
7
+ import { kotlinAdapter } from "./kotlin.js";
8
+ import { rustAdapter } from "./rust.js";
9
+ import { dartAdapter } from "./dart.js";
10
+ /** Languages with real parsers (compiler AST or lightweight line scanners). */
38
11
  export const languageAdapters = [
39
12
  typescriptAdapter,
40
13
  javascriptAdapter,
41
14
  pythonAdapter,
42
15
  phpAdapter,
43
16
  goAdapter,
44
- unsupportedAdapter("java", [".java"], "Java AST requires an external parser (e.g. tree-sitter-java or javaparser); not bundled"),
45
- unsupportedAdapter("kotlin", [".kt", ".kts"], "Kotlin AST requires an external parser; not bundled"),
46
- unsupportedAdapter("rust", [".rs"], "Rust AST requires syn/rustc or tree-sitter-rust; not bundled"),
47
- unsupportedAdapter("dart", [".dart"], "Dart AST requires Dart analyzer APIs; not bundled"),
17
+ javaAdapter,
18
+ kotlinAdapter,
19
+ rustAdapter,
20
+ dartAdapter,
48
21
  ];
49
22
  export function getAdapterForFile(filePath) {
50
23
  const lang = detectLanguage(filePath);
@@ -74,3 +47,7 @@ export { typescriptAdapter, javascriptAdapter } from "./typescript.js";
74
47
  export { pythonAdapter, pythonAvailable } from "./python.js";
75
48
  export { phpAdapter, phpAvailable } from "./php.js";
76
49
  export { goAdapter, goAvailable } from "./go.js";
50
+ export { javaAdapter, parseJavaSource } from "./java.js";
51
+ export { kotlinAdapter, parseKotlinSource } from "./kotlin.js";
52
+ export { rustAdapter, parseRustSource } from "./rust.js";
53
+ export { dartAdapter, parseDartSource } from "./dart.js";
@@ -0,0 +1,10 @@
1
+ import type { LanguageAdapter, LanguageImport, LanguageSymbol } from "./types.js";
2
+ export interface JavaParseSummary {
3
+ packageName: string;
4
+ imports: LanguageImport[];
5
+ symbols: LanguageSymbol[];
6
+ limitations: string[];
7
+ }
8
+ /** Lightweight line-oriented Java scan — not javaparser/tree-sitter. */
9
+ export declare function parseJavaSource(filePath: string, source: string): JavaParseSummary;
10
+ export declare const javaAdapter: LanguageAdapter;
@@ -0,0 +1,80 @@
1
+ /** Lightweight line-oriented Java scan — not javaparser/tree-sitter. */
2
+ export function parseJavaSource(filePath, source) {
3
+ const lines = source.split(/\r?\n/);
4
+ const limitations = [
5
+ "Java parsing uses a lightweight line scanner — not javaparser or tree-sitter-java; calls/types are not fully resolved.",
6
+ "Full compiler or tree-sitter-java integration remains an EXTERNAL enhancement path (not bundled).",
7
+ ];
8
+ let packageName = "";
9
+ const imports = [];
10
+ const symbols = [];
11
+ for (let i = 0; i < lines.length; i++) {
12
+ const trimmed = lines[i].trim();
13
+ if (!packageName && trimmed.startsWith("package ") && trimmed.endsWith(";")) {
14
+ packageName = trimmed.slice("package ".length, -1).trim();
15
+ }
16
+ if (trimmed.startsWith("import ") && trimmed.endsWith(";") && !trimmed.includes(" static ")) {
17
+ const spec = trimmed.slice("import ".length, -1).trim();
18
+ if (spec)
19
+ imports.push({ fromFile: filePath, specifier: spec, evidence: "ast" });
20
+ }
21
+ const typeMatch = /^(?:public\s+|private\s+|protected\s+)?(?:abstract\s+)?(?:class|interface|enum|record)\s+(\w+)/.exec(trimmed);
22
+ if (typeMatch) {
23
+ const kind = trimmed.includes("interface")
24
+ ? "interface"
25
+ : trimmed.includes("enum")
26
+ ? "enum"
27
+ : "class";
28
+ symbols.push({
29
+ id: `java:${kind}:${filePath}:${typeMatch[1]}`,
30
+ name: typeMatch[1],
31
+ kind,
32
+ file: filePath,
33
+ line: i + 1,
34
+ evidence: "ast",
35
+ });
36
+ }
37
+ const methodMatch = /^(?:public|private|protected|static|\s)+[\w<>,[\].?\s]+\s+(\w+)\s*\([^;]*\)\s*(?:throws\s+[\w\s,]+)?\s*\{?\s*$/.exec(trimmed);
38
+ if (methodMatch && !trimmed.startsWith("if ") && !trimmed.startsWith("for ")) {
39
+ symbols.push({
40
+ id: `java:method:${filePath}:${methodMatch[1]}`,
41
+ name: methodMatch[1],
42
+ kind: "method",
43
+ file: filePath,
44
+ line: i + 1,
45
+ evidence: "ast",
46
+ });
47
+ }
48
+ }
49
+ return { packageName, imports, symbols, limitations };
50
+ }
51
+ const JAVA_CAPABILITIES = () => ({
52
+ parse: "supported",
53
+ symbols: "supported",
54
+ definitions: "unsupported",
55
+ references: "unsupported",
56
+ imports: "supported",
57
+ calls: "partial",
58
+ diagnostics: "unsupported",
59
+ types: "partial",
60
+ });
61
+ export const javaAdapter = {
62
+ id: "java",
63
+ extensions: [".java"],
64
+ capabilities: JAVA_CAPABILITIES,
65
+ async parse(filePath, source) {
66
+ const summary = parseJavaSource(filePath, source);
67
+ const ok = Boolean(summary.packageName || summary.symbols.length > 0);
68
+ return {
69
+ language: "java",
70
+ file: filePath,
71
+ ok,
72
+ capabilities: javaAdapter.capabilities(),
73
+ symbols: summary.symbols,
74
+ imports: summary.imports,
75
+ calls: [],
76
+ diagnostics: ok ? [] : ["no package or type declarations detected"],
77
+ limitations: summary.limitations,
78
+ };
79
+ },
80
+ };
@@ -0,0 +1,10 @@
1
+ import type { LanguageAdapter, LanguageImport, LanguageSymbol } from "./types.js";
2
+ export interface KotlinParseSummary {
3
+ packageName: string;
4
+ imports: LanguageImport[];
5
+ symbols: LanguageSymbol[];
6
+ limitations: string[];
7
+ }
8
+ /** Lightweight line-oriented Kotlin scan — not Kotlin compiler AST. */
9
+ export declare function parseKotlinSource(filePath: string, source: string): KotlinParseSummary;
10
+ export declare const kotlinAdapter: LanguageAdapter;
@@ -0,0 +1,85 @@
1
+ /** Lightweight line-oriented Kotlin scan — not Kotlin compiler AST. */
2
+ export function parseKotlinSource(filePath, source) {
3
+ const lines = source.split(/\r?\n/);
4
+ const limitations = [
5
+ "Kotlin parsing uses a lightweight line scanner — not the Kotlin compiler or tree-sitter-kotlin; calls/types are partial.",
6
+ "Full compiler or tree-sitter-kotlin integration remains an EXTERNAL enhancement path (not bundled).",
7
+ ];
8
+ let packageName = "";
9
+ const imports = [];
10
+ const symbols = [];
11
+ for (let i = 0; i < lines.length; i++) {
12
+ const trimmed = lines[i].trim();
13
+ if (!packageName && trimmed.startsWith("package ")) {
14
+ packageName = trimmed.slice("package ".length).split(/\s+/)[0] ?? "";
15
+ }
16
+ if (trimmed.startsWith("import ") && !trimmed.startsWith("import(")) {
17
+ const spec = trimmed
18
+ .slice("import ".length)
19
+ .replace(/\s+as\s+\w+$/, "")
20
+ .trim();
21
+ if (spec)
22
+ imports.push({ fromFile: filePath, specifier: spec, evidence: "ast" });
23
+ }
24
+ const typeMatch = /^(?:data\s+|sealed\s+|open\s+|abstract\s+|inner\s+)?(?:class|interface|object|enum class)\s+(\w+)/.exec(trimmed);
25
+ if (typeMatch) {
26
+ let kind = "class";
27
+ if (trimmed.includes("interface"))
28
+ kind = "interface";
29
+ else if (trimmed.includes("object"))
30
+ kind = "object";
31
+ else if (trimmed.includes("enum class"))
32
+ kind = "enum";
33
+ symbols.push({
34
+ id: `kotlin:${kind}:${filePath}:${typeMatch[1]}`,
35
+ name: typeMatch[1],
36
+ kind,
37
+ file: filePath,
38
+ line: i + 1,
39
+ evidence: "ast",
40
+ });
41
+ }
42
+ const funMatch = /^(?:private\s+|public\s+|internal\s+|protected\s+)?fun\s+(?:<[^>]+>\s+)?(\w+)\s*\(/.exec(trimmed);
43
+ if (funMatch) {
44
+ symbols.push({
45
+ id: `kotlin:function:${filePath}:${funMatch[1]}`,
46
+ name: funMatch[1],
47
+ kind: "function",
48
+ file: filePath,
49
+ line: i + 1,
50
+ evidence: "ast",
51
+ });
52
+ }
53
+ }
54
+ return { packageName, imports, symbols, limitations };
55
+ }
56
+ const KOTLIN_CAPABILITIES = () => ({
57
+ parse: "supported",
58
+ symbols: "supported",
59
+ definitions: "unsupported",
60
+ references: "unsupported",
61
+ imports: "supported",
62
+ calls: "partial",
63
+ diagnostics: "unsupported",
64
+ types: "partial",
65
+ });
66
+ export const kotlinAdapter = {
67
+ id: "kotlin",
68
+ extensions: [".kt", ".kts"],
69
+ capabilities: KOTLIN_CAPABILITIES,
70
+ async parse(filePath, source) {
71
+ const summary = parseKotlinSource(filePath, source);
72
+ const ok = Boolean(summary.packageName || summary.symbols.length > 0);
73
+ return {
74
+ language: "kotlin",
75
+ file: filePath,
76
+ ok,
77
+ capabilities: kotlinAdapter.capabilities(),
78
+ symbols: summary.symbols,
79
+ imports: summary.imports,
80
+ calls: [],
81
+ diagnostics: ok ? [] : ["no package or declarations detected"],
82
+ limitations: summary.limitations,
83
+ };
84
+ },
85
+ };
@@ -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, "supported" | "unsupported">>;
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, "supported" | "unsupported">>;
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;
@@ -1,6 +1,6 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
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", "agent_plan", "change_verify"];
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
4
  export type AgentMcpToolName = (typeof AGENT_MCP_TOOL_NAMES)[number];
5
5
  export declare function listAgentMcpTools(): Tool[];
6
6
  export declare function invokeAgentMcpTool(root: string, name: string, args: Record<string, unknown>, options?: {
@@ -4,6 +4,8 @@ import { buildAgentPlan } from "../../agent/plan.js";
4
4
  import { executeAgentTool, newToolCall } from "../../agent/tools/index.js";
5
5
  import { retrieveProjectContext } from "../../agent/context/retrieve.js";
6
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";
7
9
  export const AGENT_MCP_TOOL_NAMES = [
8
10
  "project_context",
9
11
  "project_ask",
@@ -11,6 +13,7 @@ export const AGENT_MCP_TOOL_NAMES = [
11
13
  "file_read",
12
14
  "file_create",
13
15
  "file_edit",
16
+ "approval_issue",
14
17
  "agent_plan",
15
18
  "change_verify",
16
19
  ];
@@ -58,30 +61,53 @@ export function listAgentMcpTools() {
58
61
  },
59
62
  {
60
63
  name: "file_create",
61
- description: "WRITE: Create file (requires approved=true). Path-safe; never shell. Model cannot self-approve.",
64
+ description: "WRITE: Create file. Requires approvalToken from issueApprovalGrant / CLI --approve. Bare approved=true is rejected.",
62
65
  inputSchema: {
63
66
  type: "object",
64
67
  properties: {
65
68
  path: { type: "string" },
66
69
  content: { type: "string" },
67
- approved: { type: "boolean" },
70
+ approvalToken: { type: "string" },
71
+ approved: {
72
+ type: "boolean",
73
+ description: "Legacy flag — insufficient alone; approvalToken required",
74
+ },
68
75
  },
69
- required: ["path", "content", "approved"],
76
+ required: ["path", "content", "approvalToken"],
70
77
  additionalProperties: false,
71
78
  },
72
79
  },
73
80
  {
74
81
  name: "file_edit",
75
- description: "WRITE: Edit file (requires approved=true). Path-safe.",
82
+ description: "WRITE: Edit file. Requires approvalToken from issueApprovalGrant / CLI --approve.",
76
83
  inputSchema: {
77
84
  type: "object",
78
85
  properties: {
79
86
  path: { type: "string" },
80
87
  content: { type: "string" },
81
88
  oldContent: { type: "string" },
82
- approved: { type: "boolean" },
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"] },
83
109
  },
84
- required: ["path", "approved"],
110
+ required: ["action", "resources", "planHash"],
85
111
  additionalProperties: false,
86
112
  },
87
113
  },
@@ -126,18 +152,6 @@ export async function invokeAgentMcpTool(root, name, args, options) {
126
152
  };
127
153
  }
128
154
  const provider = options?.provider ?? createModelProvider(loadAiConfig());
129
- if (provider.id === "none") {
130
- return {
131
- structured: {
132
- ok: false,
133
- error: {
134
- code: "provider_none",
135
- message: "AI chat is not configured. Set AGENTDOCTOR_AI_PROVIDER=mock (or openai-compatible). Silent mock fallback is disabled.",
136
- },
137
- },
138
- isError: true,
139
- };
140
- }
141
155
  const chat = new ChatService({
142
156
  root,
143
157
  provider,
@@ -147,7 +161,7 @@ export async function invokeAgentMcpTool(root, name, args, options) {
147
161
  const response = await chat.ask(question);
148
162
  return {
149
163
  structured: { ok: response.status === "ok", response },
150
- isError: response.status !== "ok",
164
+ isError: response.status !== "ok" && response.status !== "provider-none",
151
165
  };
152
166
  }
153
167
  finally {
@@ -163,11 +177,23 @@ export async function invokeAgentMcpTool(root, name, args, options) {
163
177
  return { structured: result, isError: !result.ok };
164
178
  }
165
179
  case "file_create": {
166
- if (args.approved !== true) {
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) {
167
193
  return {
168
194
  structured: {
169
195
  ok: false,
170
- error: { code: "approval_required", message: "approved=true required" },
196
+ error: { code: "approval_required", message: gate.reason },
171
197
  },
172
198
  isError: true,
173
199
  };
@@ -176,11 +202,23 @@ export async function invokeAgentMcpTool(root, name, args, options) {
176
202
  return { structured: result, isError: !result.ok };
177
203
  }
178
204
  case "file_edit": {
179
- if (args.approved !== true) {
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) {
180
218
  return {
181
219
  structured: {
182
220
  ok: false,
183
- error: { code: "approval_required", message: "approved=true required" },
221
+ error: { code: "approval_required", message: gate.reason },
184
222
  },
185
223
  isError: true,
186
224
  };
@@ -192,6 +230,54 @@ export async function invokeAgentMcpTool(root, name, args, options) {
192
230
  }), { allowWrite: true, approvedByHuman: true });
193
231
  return { structured: result, isError: !result.ok };
194
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
+ }
195
281
  case "agent_plan": {
196
282
  const goal = typeof args.goal === "string" ? args.goal : "";
197
283
  const plan = await buildAgentPlan({ root, goal });
@@ -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>;