@praneeth_54/agentdoctor 2.0.0 → 2.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 (95) hide show
  1. package/CHANGELOG.md +73 -4
  2. package/README.md +283 -158
  3. package/dist/architecture/contract.d.ts +64 -0
  4. package/dist/architecture/contract.js +373 -0
  5. package/dist/assurance/change.d.ts +211 -0
  6. package/dist/assurance/change.js +624 -0
  7. package/dist/assurance/proof.d.ts +136 -0
  8. package/dist/assurance/proof.js +338 -0
  9. package/dist/auth/index.d.ts +81 -0
  10. package/dist/auth/index.js +130 -0
  11. package/dist/auth/rbac.d.ts +9 -0
  12. package/dist/auth/rbac.js +26 -0
  13. package/dist/cli/commands/architecture.d.ts +6 -0
  14. package/dist/cli/commands/architecture.js +109 -0
  15. package/dist/cli/commands/assurance.d.ts +67 -0
  16. package/dist/cli/commands/assurance.js +210 -0
  17. package/dist/cli/commands/complete.js +2 -2
  18. package/dist/cli/commands/platform.d.ts +2 -0
  19. package/dist/cli/commands/platform.js +5 -1
  20. package/dist/cli/commands/policy-graph-run.d.ts +40 -0
  21. package/dist/cli/commands/policy-graph-run.js +200 -0
  22. package/dist/cli/commands/workspace.d.ts +9 -0
  23. package/dist/cli/commands/workspace.js +82 -0
  24. package/dist/cli/program.js +535 -17
  25. package/dist/constants.d.ts +1 -1
  26. package/dist/constants.js +1 -1
  27. package/dist/contracts/index.d.ts +1 -1
  28. package/dist/contracts/index.js +2 -2
  29. package/dist/core/brain-product/init.js +5 -2
  30. package/dist/core/secrets/scan.d.ts +7 -1
  31. package/dist/core/secrets/scan.js +9 -9
  32. package/dist/coverage/cobertura.d.ts +6 -0
  33. package/dist/coverage/cobertura.js +52 -0
  34. package/dist/coverage/istanbul.d.ts +7 -0
  35. package/dist/coverage/istanbul.js +134 -0
  36. package/dist/coverage/lcov.d.ts +7 -0
  37. package/dist/coverage/lcov.js +59 -0
  38. package/dist/coverage/load.d.ts +9 -0
  39. package/dist/coverage/load.js +39 -0
  40. package/dist/coverage/types.d.ts +23 -0
  41. package/dist/coverage/types.js +5 -0
  42. package/dist/dashboard/server.d.ts +1 -1
  43. package/dist/dashboard/server.js +21 -5
  44. package/dist/enforcement/runner.d.ts +47 -3
  45. package/dist/enforcement/runner.js +420 -16
  46. package/dist/index.d.ts +23 -2
  47. package/dist/index.js +15 -2
  48. package/dist/intelligence/graph/build.d.ts +2 -0
  49. package/dist/intelligence/graph/build.js +49 -5
  50. package/dist/intelligence/graph/incremental.d.ts +68 -0
  51. package/dist/intelligence/graph/incremental.js +234 -0
  52. package/dist/intelligence/resolve/imports.d.ts +36 -0
  53. package/dist/intelligence/resolve/imports.js +245 -0
  54. package/dist/languages/go.d.ts +9 -0
  55. package/dist/languages/go.js +45 -0
  56. package/dist/languages/index.d.ts +11 -0
  57. package/dist/languages/index.js +76 -0
  58. package/dist/languages/php.d.ts +7 -0
  59. package/dist/languages/php.js +193 -0
  60. package/dist/languages/python.d.ts +7 -0
  61. package/dist/languages/python.js +150 -0
  62. package/dist/languages/types.d.ts +42 -0
  63. package/dist/languages/types.js +26 -0
  64. package/dist/languages/typescript.d.ts +3 -0
  65. package/dist/languages/typescript.js +95 -0
  66. package/dist/mcp/intelligence/handlers.d.ts +5 -0
  67. package/dist/mcp/intelligence/handlers.js +229 -40
  68. package/dist/mcp/intelligence/path-safety.d.ts +2 -4
  69. package/dist/mcp/intelligence/path-safety.js +25 -44
  70. package/dist/mcp/intelligence/registry.d.ts +1 -1
  71. package/dist/mcp/intelligence/registry.js +69 -1
  72. package/dist/platform/firewall/evaluate.d.ts +6 -3
  73. package/dist/platform/firewall/evaluate.js +66 -15
  74. package/dist/platform/graph/build.js +19 -23
  75. package/dist/platform/health/analyze.js +23 -17
  76. package/dist/platform/index.js +2 -0
  77. package/dist/platform/test-impact/analyze.d.ts +21 -3
  78. package/dist/platform/test-impact/analyze.js +214 -28
  79. package/dist/platform/tokens/plan.js +39 -20
  80. package/dist/platform/types.d.ts +9 -0
  81. package/dist/policy/compose.d.ts +34 -0
  82. package/dist/policy/compose.js +118 -0
  83. package/dist/policy/packs.js +33 -1
  84. package/dist/security/paths.d.ts +21 -0
  85. package/dist/security/paths.js +105 -0
  86. package/dist/storage/postgres.d.ts +26 -0
  87. package/dist/storage/postgres.js +90 -0
  88. package/dist/storage/provider.d.ts +2 -7
  89. package/dist/storage/provider.js +22 -16
  90. package/dist/storage/sqlite.d.ts +33 -0
  91. package/dist/storage/sqlite.js +77 -0
  92. package/dist/team/auth.js +1 -1
  93. package/dist/workspace/index.d.ts +69 -0
  94. package/dist/workspace/index.js +220 -0
  95. package/package.json +9 -3
@@ -0,0 +1,68 @@
1
+ import type { GraphEdge, GraphNode } from "../../platform/types.js";
2
+ import { type GraphBuilderMode } from "./build.js";
3
+ export declare const GRAPH_INDEX_SCHEMA = "1.0.0";
4
+ export interface GraphSnapshot {
5
+ schemaVersion: string;
6
+ agentDoctorVersion: string;
7
+ root: string;
8
+ generatedAt: string;
9
+ builder: GraphBuilderMode | string;
10
+ astFilesParsed: number;
11
+ fileHashes: Record<string, string>;
12
+ nodes: GraphNode[];
13
+ edges: GraphEdge[];
14
+ limitations: string[];
15
+ }
16
+ export interface GraphStatus {
17
+ present: boolean;
18
+ path: string;
19
+ schemaVersion?: string;
20
+ builder?: string;
21
+ nodeCount: number;
22
+ edgeCount: number;
23
+ fileCount: number;
24
+ generatedAt?: string;
25
+ staleFiles: string[];
26
+ missingFiles: string[];
27
+ corrupt?: boolean;
28
+ error?: string;
29
+ }
30
+ /**
31
+ * Full graph build and persist to `.agentdoctor/graph/index.json`.
32
+ */
33
+ export declare function graphBuild(options: {
34
+ root: string;
35
+ mode?: GraphBuilderMode;
36
+ }): Promise<{
37
+ snapshot: GraphSnapshot;
38
+ path: string;
39
+ rebuilt: boolean;
40
+ }>;
41
+ /**
42
+ * Force full rebuild (same as build, clears prior assumptions).
43
+ */
44
+ export declare function graphRebuild(options: {
45
+ root: string;
46
+ mode?: GraphBuilderMode;
47
+ }): Promise<{
48
+ snapshot: GraphSnapshot;
49
+ path: string;
50
+ rebuilt: boolean;
51
+ }>;
52
+ /**
53
+ * Update graph from git-changed files or content-hash comparison.
54
+ * On corruption → rebuild. When nothing changed → reuse snapshot.
55
+ */
56
+ export declare function graphUpdate(options: {
57
+ root: string;
58
+ mode?: GraphBuilderMode;
59
+ }): Promise<{
60
+ snapshot: GraphSnapshot;
61
+ path: string;
62
+ rebuilt: boolean;
63
+ reason: string;
64
+ changedFiles: string[];
65
+ }>;
66
+ export declare function graphStatus(options: {
67
+ root: string;
68
+ }): Promise<GraphStatus>;
@@ -0,0 +1,234 @@
1
+ import { createHash } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { PACKAGE_VERSION } from "../../constants.js";
6
+ import { resolveRepoRoot, toPosixRelative } from "../../utils/path.js";
7
+ import { atomicWriteTextFile } from "../../utils/fs.js";
8
+ import { buildIntelligenceGraph, listTsFiles } from "./build.js";
9
+ export const GRAPH_INDEX_SCHEMA = "1.0.0";
10
+ function indexPath(root) {
11
+ return path.join(root, ".agentdoctor", "graph", "index.json");
12
+ }
13
+ function sha256FileContent(content) {
14
+ return createHash("sha256").update(content).digest("hex");
15
+ }
16
+ async function hashFiles(root, absFiles) {
17
+ const out = {};
18
+ for (const abs of absFiles) {
19
+ const rel = toPosixRelative(root, abs);
20
+ try {
21
+ const buf = await fs.readFile(abs);
22
+ out[rel] = sha256FileContent(buf);
23
+ }
24
+ catch {
25
+ // skip unreadable
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ function gitChangedFiles(root) {
31
+ const run = (args) => {
32
+ const r = spawnSync("git", args, { cwd: root, encoding: "utf8" });
33
+ return r.status === 0 && typeof r.stdout === "string" ? r.stdout : null;
34
+ };
35
+ const staged = run(["diff", "--name-only", "--cached"]);
36
+ const unstaged = run(["diff", "--name-only"]);
37
+ const untracked = run(["ls-files", "--others", "--exclude-standard"]);
38
+ if (staged === null && unstaged === null && untracked === null)
39
+ return null;
40
+ const set = new Set();
41
+ for (const block of [staged, unstaged, untracked]) {
42
+ if (!block)
43
+ continue;
44
+ for (const line of block.split("\n")) {
45
+ const t = line.trim();
46
+ if (t)
47
+ set.add(t.split(path.sep).join("/"));
48
+ }
49
+ }
50
+ return [...set].sort();
51
+ }
52
+ function isValidSnapshot(raw) {
53
+ if (!raw || typeof raw !== "object")
54
+ return false;
55
+ const s = raw;
56
+ return (typeof s.schemaVersion === "string" &&
57
+ typeof s.root === "string" &&
58
+ typeof s.generatedAt === "string" &&
59
+ s.fileHashes !== null &&
60
+ typeof s.fileHashes === "object" &&
61
+ Array.isArray(s.nodes) &&
62
+ Array.isArray(s.edges));
63
+ }
64
+ async function loadSnapshot(root) {
65
+ const file = indexPath(root);
66
+ try {
67
+ const raw = await fs.readFile(file, "utf8");
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(raw);
71
+ }
72
+ catch {
73
+ return { ok: false, corrupt: true, error: "index.json is not valid JSON" };
74
+ }
75
+ if (!isValidSnapshot(parsed)) {
76
+ return { ok: false, corrupt: true, error: "index.json failed schema checks" };
77
+ }
78
+ return { ok: true, snapshot: parsed };
79
+ }
80
+ catch (error) {
81
+ return {
82
+ ok: false,
83
+ corrupt: false,
84
+ error: error instanceof Error ? error.message : "index missing",
85
+ };
86
+ }
87
+ }
88
+ async function persistSnapshot(root, snapshot) {
89
+ const file = indexPath(root);
90
+ await fs.mkdir(path.dirname(file), { recursive: true });
91
+ await atomicWriteTextFile(file, `${JSON.stringify(snapshot, null, 2)}\n`);
92
+ return file;
93
+ }
94
+ async function buildAndPersist(root, mode = "auto") {
95
+ const graph = await buildIntelligenceGraph({ root, mode });
96
+ const files = await listTsFiles(root);
97
+ const fileHashes = await hashFiles(root, files);
98
+ const snapshot = {
99
+ schemaVersion: GRAPH_INDEX_SCHEMA,
100
+ agentDoctorVersion: PACKAGE_VERSION,
101
+ root,
102
+ generatedAt: graph.generatedAt,
103
+ builder: graph.builder,
104
+ astFilesParsed: graph.astFilesParsed,
105
+ fileHashes,
106
+ nodes: graph.nodes,
107
+ edges: graph.edges,
108
+ limitations: [
109
+ ...graph.limitations,
110
+ "Incremental index persists file hashes for change detection",
111
+ ],
112
+ };
113
+ const saved = await persistSnapshot(root, snapshot);
114
+ return { snapshot, path: saved };
115
+ }
116
+ /**
117
+ * Full graph build and persist to `.agentdoctor/graph/index.json`.
118
+ */
119
+ export async function graphBuild(options) {
120
+ const root = resolveRepoRoot(options.root);
121
+ const result = await buildAndPersist(root, options.mode ?? "auto");
122
+ return { ...result, rebuilt: true };
123
+ }
124
+ /**
125
+ * Force full rebuild (same as build, clears prior assumptions).
126
+ */
127
+ export async function graphRebuild(options) {
128
+ return graphBuild(options);
129
+ }
130
+ /**
131
+ * Update graph from git-changed files or content-hash comparison.
132
+ * On corruption → rebuild. When nothing changed → reuse snapshot.
133
+ */
134
+ export async function graphUpdate(options) {
135
+ const root = resolveRepoRoot(options.root);
136
+ const loaded = await loadSnapshot(root);
137
+ if (!loaded.ok) {
138
+ if (loaded.corrupt) {
139
+ const result = await buildAndPersist(root, options.mode ?? "auto");
140
+ return {
141
+ ...result,
142
+ rebuilt: true,
143
+ reason: `corrupt index rebuilt: ${loaded.error}`,
144
+ changedFiles: Object.keys(result.snapshot.fileHashes),
145
+ };
146
+ }
147
+ const result = await buildAndPersist(root, options.mode ?? "auto");
148
+ return {
149
+ ...result,
150
+ rebuilt: true,
151
+ reason: "no prior index; built fresh",
152
+ changedFiles: Object.keys(result.snapshot.fileHashes),
153
+ };
154
+ }
155
+ const files = await listTsFiles(root);
156
+ const currentHashes = await hashFiles(root, files);
157
+ const prev = loaded.snapshot.fileHashes;
158
+ const changedFromHash = [];
159
+ for (const [rel, hash] of Object.entries(currentHashes)) {
160
+ if (prev[rel] !== hash)
161
+ changedFromHash.push(rel);
162
+ }
163
+ for (const rel of Object.keys(prev)) {
164
+ if (!(rel in currentHashes))
165
+ changedFromHash.push(rel);
166
+ }
167
+ const gitFiles = gitChangedFiles(root);
168
+ const tsGitChanged = gitFiles?.filter((f) => /\.(ts|tsx|mts|cts)$/i.test(f) && !f.endsWith(".d.ts")) ?? [];
169
+ const changedSet = new Set([...changedFromHash, ...tsGitChanged]);
170
+ const changedFiles = [...changedSet].sort();
171
+ if (changedFiles.length === 0) {
172
+ return {
173
+ snapshot: loaded.snapshot,
174
+ path: indexPath(root),
175
+ rebuilt: false,
176
+ reason: "file hashes unchanged; snapshot reused",
177
+ changedFiles: [],
178
+ };
179
+ }
180
+ // Incremental: drop nodes/edges for changed paths, re-run full builder for correctness
181
+ // (TypeScript program needs whole set; hash gate already skipped no-op updates).
182
+ const result = await buildAndPersist(root, options.mode ?? "auto");
183
+ return {
184
+ ...result,
185
+ rebuilt: true,
186
+ reason: `updated ${changedFiles.length} changed file(s)`,
187
+ changedFiles,
188
+ };
189
+ }
190
+ export async function graphStatus(options) {
191
+ const root = resolveRepoRoot(options.root);
192
+ const file = indexPath(root);
193
+ const loaded = await loadSnapshot(root);
194
+ if (!loaded.ok) {
195
+ return {
196
+ present: false,
197
+ path: file,
198
+ nodeCount: 0,
199
+ edgeCount: 0,
200
+ fileCount: 0,
201
+ corrupt: loaded.corrupt,
202
+ error: loaded.error,
203
+ staleFiles: [],
204
+ missingFiles: [],
205
+ };
206
+ }
207
+ const files = await listTsFiles(root);
208
+ const currentHashes = await hashFiles(root, files);
209
+ const prev = loaded.snapshot.fileHashes;
210
+ const staleFiles = [];
211
+ const missingFiles = [];
212
+ for (const [rel, hash] of Object.entries(currentHashes)) {
213
+ if (!(rel in prev))
214
+ staleFiles.push(rel);
215
+ else if (prev[rel] !== hash)
216
+ staleFiles.push(rel);
217
+ }
218
+ for (const rel of Object.keys(prev)) {
219
+ if (!(rel in currentHashes))
220
+ missingFiles.push(rel);
221
+ }
222
+ return {
223
+ present: true,
224
+ path: file,
225
+ schemaVersion: loaded.snapshot.schemaVersion,
226
+ builder: String(loaded.snapshot.builder),
227
+ nodeCount: loaded.snapshot.nodes.length,
228
+ edgeCount: loaded.snapshot.edges.length,
229
+ fileCount: Object.keys(prev).length,
230
+ generatedAt: loaded.snapshot.generatedAt,
231
+ staleFiles: staleFiles.sort(),
232
+ missingFiles: missingFiles.sort(),
233
+ };
234
+ }
@@ -0,0 +1,36 @@
1
+ export type ImportConfidence = "EXACT" | "RESOLVED" | "INFERRED" | "UNRESOLVED";
2
+ export interface TsconfigPathsConfig {
3
+ baseUrl: string;
4
+ paths: Record<string, string[]>;
5
+ configPath: string;
6
+ }
7
+ export interface ImportResolutionResult {
8
+ specifier: string;
9
+ fromFile: string;
10
+ resolvedPath: string | null;
11
+ confidence: ImportConfidence;
12
+ via?: string;
13
+ }
14
+ /**
15
+ * Read tsconfig.json paths/baseUrl when present. Returns null when absent or unreadable.
16
+ * Does not invent mappings.
17
+ */
18
+ export declare function loadTsconfigPaths(root: string): TsconfigPathsConfig | null;
19
+ /**
20
+ * Resolve an import specifier relative to `fromFile` (repo-relative POSIX).
21
+ * Never invents a path for UNRESOLVED — `resolvedPath` is null.
22
+ */
23
+ export declare function resolveImportSpecifier(options: {
24
+ root: string;
25
+ fromFile: string;
26
+ specifier: string;
27
+ tsconfig?: TsconfigPathsConfig | null;
28
+ fileExists?: (absolutePath: string) => boolean;
29
+ }): ImportResolutionResult;
30
+ /**
31
+ * Resolve many import edges; filters to those with a concrete resolved path.
32
+ */
33
+ export declare function resolveImportEdges(root: string, imports: Array<{
34
+ fromFile: string;
35
+ specifier: string;
36
+ }>, tsconfig?: TsconfigPathsConfig | null): ImportResolutionResult[];
@@ -0,0 +1,245 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const FILE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
4
+ function toPosix(p) {
5
+ return p.split(path.sep).join("/");
6
+ }
7
+ function fileExists(abs, existsFn) {
8
+ if (existsFn)
9
+ return existsFn(abs);
10
+ try {
11
+ return fs.statSync(abs).isFile();
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ function dirExists(abs, existsFn) {
18
+ if (existsFn) {
19
+ // Approximate: any index file under the dir.
20
+ return FILE_EXTENSIONS.some((ext) => existsFn(path.join(abs, `index${ext}`)));
21
+ }
22
+ try {
23
+ return fs.statSync(abs).isDirectory();
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Try exact file, extension variants, then index/barrel.
31
+ * Returns confidence EXACT when the written path exists as-is,
32
+ * RESOLVED when found via extension or index.
33
+ */
34
+ function resolveFilesystemCandidate(root, absWithoutExt, existsFn) {
35
+ const tryFile = (abs, confidence, via) => {
36
+ if (fileExists(abs, existsFn)) {
37
+ return { abs, confidence, via };
38
+ }
39
+ return null;
40
+ };
41
+ const exact = tryFile(absWithoutExt, "EXACT", "exact");
42
+ if (exact)
43
+ return exact;
44
+ for (const ext of FILE_EXTENSIONS) {
45
+ if (absWithoutExt.endsWith(ext))
46
+ continue;
47
+ const hit = tryFile(`${absWithoutExt}${ext}`, "RESOLVED", `extension:${ext}`);
48
+ if (hit)
49
+ return hit;
50
+ }
51
+ if (dirExists(absWithoutExt, existsFn)) {
52
+ for (const ext of FILE_EXTENSIONS) {
53
+ const hit = tryFile(path.join(absWithoutExt, `index${ext}`), "RESOLVED", `index:index${ext}`);
54
+ if (hit)
55
+ return hit;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ function stripJsonComments(text) {
61
+ // Minimal strip for // and /* */ outside strings — enough for typical tsconfig.
62
+ let out = "";
63
+ let i = 0;
64
+ let inString = null;
65
+ while (i < text.length) {
66
+ const ch = text[i];
67
+ if (inString) {
68
+ out += ch;
69
+ if (ch === "\\" && i + 1 < text.length) {
70
+ out += text[i + 1];
71
+ i += 2;
72
+ continue;
73
+ }
74
+ if (ch === inString)
75
+ inString = null;
76
+ i += 1;
77
+ continue;
78
+ }
79
+ if (ch === '"' || ch === "'") {
80
+ inString = ch;
81
+ out += ch;
82
+ i += 1;
83
+ continue;
84
+ }
85
+ if (ch === "/" && text[i + 1] === "/") {
86
+ i += 2;
87
+ while (i < text.length && text[i] !== "\n")
88
+ i += 1;
89
+ continue;
90
+ }
91
+ if (ch === "/" && text[i + 1] === "*") {
92
+ i += 2;
93
+ while (i + 1 < text.length && !(text[i] === "*" && text[i + 1] === "/"))
94
+ i += 1;
95
+ i += 2;
96
+ continue;
97
+ }
98
+ // Trailing commas before } or ]
99
+ if (ch === "," && (text.slice(i + 1).match(/^\s*[}\]]/) ?? false)) {
100
+ i += 1;
101
+ continue;
102
+ }
103
+ out += ch;
104
+ i += 1;
105
+ }
106
+ return out;
107
+ }
108
+ /**
109
+ * Read tsconfig.json paths/baseUrl when present. Returns null when absent or unreadable.
110
+ * Does not invent mappings.
111
+ */
112
+ export function loadTsconfigPaths(root) {
113
+ const configPath = path.join(root, "tsconfig.json");
114
+ let raw;
115
+ try {
116
+ raw = fs.readFileSync(configPath, "utf8");
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ try {
122
+ const parsed = JSON.parse(stripJsonComments(raw));
123
+ const baseUrl = parsed.compilerOptions?.baseUrl ?? ".";
124
+ const paths = parsed.compilerOptions?.paths ?? {};
125
+ return { baseUrl, paths, configPath };
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
131
+ function matchTsconfigPath(specifier, config) {
132
+ const entries = Object.entries(config.paths);
133
+ // Prefer longest pattern key for specificity.
134
+ entries.sort((a, b) => b[0].length - a[0].length);
135
+ for (const [pattern, targets] of entries) {
136
+ if (!targets.length)
137
+ continue;
138
+ if (pattern.includes("*")) {
139
+ const [prefix, suffix] = pattern.split("*");
140
+ if (typeof prefix === "string" &&
141
+ typeof suffix === "string" &&
142
+ specifier.startsWith(prefix) &&
143
+ specifier.endsWith(suffix)) {
144
+ const star = specifier.slice(prefix.length, specifier.length - suffix.length);
145
+ const target = targets[0];
146
+ const mapped = target.includes("*") ? target.replace("*", star) : target;
147
+ return { mapped, pattern };
148
+ }
149
+ }
150
+ else if (specifier === pattern) {
151
+ return { mapped: targets[0], pattern };
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ /**
157
+ * Resolve an import specifier relative to `fromFile` (repo-relative POSIX).
158
+ * Never invents a path for UNRESOLVED — `resolvedPath` is null.
159
+ */
160
+ export function resolveImportSpecifier(options) {
161
+ const { root, fromFile, specifier } = options;
162
+ const fromPosix = toPosix(fromFile);
163
+ const base = {
164
+ specifier,
165
+ fromFile: fromPosix,
166
+ };
167
+ if (!specifier || specifier.includes("\0")) {
168
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
169
+ }
170
+ const existsFn = options.fileExists;
171
+ const toRel = (abs) => toPosix(path.relative(root, abs));
172
+ // Relative imports
173
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
174
+ const fromDir = path.dirname(path.join(root, fromPosix));
175
+ const abs = path.resolve(fromDir, specifier);
176
+ // Containment: refuse escaping the repo when resolving relatives.
177
+ const rel = path.relative(root, abs);
178
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
179
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
180
+ }
181
+ const hit = resolveFilesystemCandidate(root, abs, existsFn);
182
+ if (!hit) {
183
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
184
+ }
185
+ return {
186
+ ...base,
187
+ resolvedPath: toRel(hit.abs),
188
+ confidence: hit.confidence,
189
+ via: hit.via,
190
+ };
191
+ }
192
+ // tsconfig paths / baseUrl (non-relative)
193
+ const tsconfig = options.tsconfig === undefined ? loadTsconfigPaths(root) : options.tsconfig;
194
+ if (tsconfig) {
195
+ const matched = matchTsconfigPath(specifier, tsconfig);
196
+ if (matched) {
197
+ const abs = path.resolve(root, tsconfig.baseUrl, matched.mapped);
198
+ const rel = path.relative(root, abs);
199
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
200
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
201
+ }
202
+ const hit = resolveFilesystemCandidate(root, abs, existsFn);
203
+ if (hit) {
204
+ return {
205
+ ...base,
206
+ resolvedPath: toRel(hit.abs),
207
+ confidence: "RESOLVED",
208
+ via: `tsconfig-paths:${matched.pattern}`,
209
+ };
210
+ }
211
+ // Pattern matched but file missing — do not invent.
212
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED", via: "tsconfig-paths-miss" };
213
+ }
214
+ // baseUrl bare specifier (e.g. "utils/foo" with baseUrl=src) — INFERRED only when found.
215
+ if (!specifier.startsWith(".") && !specifier.includes(":")) {
216
+ const abs = path.resolve(root, tsconfig.baseUrl, specifier);
217
+ const rel = path.relative(root, abs);
218
+ if (!rel.startsWith("..") && !path.isAbsolute(rel)) {
219
+ const hit = resolveFilesystemCandidate(root, abs, existsFn);
220
+ if (hit) {
221
+ return {
222
+ ...base,
223
+ resolvedPath: toRel(hit.abs),
224
+ confidence: "INFERRED",
225
+ via: "tsconfig-baseUrl",
226
+ };
227
+ }
228
+ }
229
+ }
230
+ }
231
+ // Package / URL / unknown — unresolved; never invent a repo edge.
232
+ return { ...base, resolvedPath: null, confidence: "UNRESOLVED" };
233
+ }
234
+ /**
235
+ * Resolve many import edges; filters to those with a concrete resolved path.
236
+ */
237
+ export function resolveImportEdges(root, imports, tsconfig) {
238
+ const cfg = tsconfig === undefined ? loadTsconfigPaths(root) : tsconfig;
239
+ return imports.map((item) => resolveImportSpecifier({
240
+ root,
241
+ fromFile: item.fromFile,
242
+ specifier: item.specifier,
243
+ tsconfig: cfg,
244
+ }));
245
+ }
@@ -0,0 +1,9 @@
1
+ import type { LanguageAdapter } from "./types.js";
2
+ /**
3
+ * Go toolchain bridge placeholder.
4
+ * Prefer real `go/ast` via `go run` when the Go toolchain is present; until an
5
+ * extractor is bundled, capability remains unsupported even if `go` exists.
6
+ * Today: always unsupported (no bundled extractor; avoids fake AST claims).
7
+ */
8
+ export declare function goAvailable(): boolean;
9
+ export declare const goAdapter: LanguageAdapter;
@@ -0,0 +1,45 @@
1
+ import { spawnSync } from "node:child_process";
2
+ /**
3
+ * Go toolchain bridge placeholder.
4
+ * Prefer real `go/ast` via `go run` when the Go toolchain is present; until an
5
+ * extractor is bundled, capability remains unsupported even if `go` exists.
6
+ * Today: always unsupported (no bundled extractor; avoids fake AST claims).
7
+ */
8
+ export function goAvailable() {
9
+ const r = spawnSync("go", ["version"], { encoding: "utf8" });
10
+ return r.status === 0;
11
+ }
12
+ export const goAdapter = {
13
+ id: "go",
14
+ extensions: [".go"],
15
+ capabilities: () => ({
16
+ parse: "unsupported",
17
+ symbols: "unsupported",
18
+ definitions: "unsupported",
19
+ references: "unsupported",
20
+ imports: "unsupported",
21
+ calls: "unsupported",
22
+ diagnostics: "unsupported",
23
+ }),
24
+ async parse(filePath, _source) {
25
+ const hasGo = goAvailable();
26
+ return {
27
+ language: "go",
28
+ file: filePath,
29
+ ok: false,
30
+ capabilities: goAdapter.capabilities(),
31
+ symbols: [],
32
+ imports: [],
33
+ calls: [],
34
+ diagnostics: [
35
+ hasGo
36
+ ? "Go toolchain present but AgentDoctor go/ast extractor is not bundled yet"
37
+ : "go not available on PATH",
38
+ ],
39
+ limitations: [
40
+ "Go AST requires a go/ast bridge or tree-sitter-go; not shipped in this package",
41
+ "Capability remains unsupported — no regex fallback under an AST claim",
42
+ ],
43
+ };
44
+ },
45
+ };
@@ -0,0 +1,11 @@
1
+ import type { LanguageAdapter, ParseResult } from "./types.js";
2
+ /** Languages with real AST backends today (or honest unsupported stubs). */
3
+ export declare const languageAdapters: LanguageAdapter[];
4
+ export declare function getAdapterForFile(filePath: string): LanguageAdapter | null;
5
+ export declare function parseSourceFile(filePath: string, source: string): Promise<ParseResult>;
6
+ export { detectLanguage } from "./types.js";
7
+ export { typescriptAdapter, javascriptAdapter } from "./typescript.js";
8
+ export { pythonAdapter, pythonAvailable } from "./python.js";
9
+ export { phpAdapter, phpAvailable } from "./php.js";
10
+ export { goAdapter, goAvailable } from "./go.js";
11
+ export type { LanguageAdapter, ParseResult, LanguageId } from "./types.js";