@praneeth_54/agentdoctor 1.0.0 → 1.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.
- package/CHANGELOG.md +34 -5
- package/README.md +15 -12
- package/dist/cli/commands/brain-mcp.d.ts +9 -0
- package/dist/cli/commands/brain-mcp.js +38 -0
- package/dist/cli/program.js +13 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/core/understanding/architecture/index.d.ts +4 -0
- package/dist/core/understanding/architecture/index.js +3 -0
- package/dist/core/understanding/architecture/infer.d.ts +6 -0
- package/dist/core/understanding/architecture/infer.js +39 -0
- package/dist/core/understanding/architecture/models.d.ts +23 -0
- package/dist/core/understanding/architecture/models.js +127 -0
- package/dist/core/understanding/architecture/patterns/distribution.d.ts +2 -0
- package/dist/core/understanding/architecture/patterns/distribution.js +115 -0
- package/dist/core/understanding/architecture/patterns/index.d.ts +2 -0
- package/dist/core/understanding/architecture/patterns/index.js +8 -0
- package/dist/core/understanding/architecture/patterns/layered.d.ts +2 -0
- package/dist/core/understanding/architecture/patterns/layered.js +279 -0
- package/dist/core/understanding/architecture/patterns/structural.d.ts +2 -0
- package/dist/core/understanding/architecture/patterns/structural.js +197 -0
- package/dist/core/understanding/architecture/rules.d.ts +4 -0
- package/dist/core/understanding/architecture/rules.js +6 -0
- package/dist/core/understanding/architecture/types.d.ts +52 -0
- package/dist/core/understanding/architecture/types.js +1 -0
- package/dist/core/understanding/brain/build.d.ts +13 -0
- package/dist/core/understanding/brain/build.js +158 -0
- package/dist/core/understanding/brain/claims/index.d.ts +3 -0
- package/dist/core/understanding/brain/claims/index.js +2 -0
- package/dist/core/understanding/brain/claims/lifecycle.d.ts +16 -0
- package/dist/core/understanding/brain/claims/lifecycle.js +78 -0
- package/dist/core/understanding/brain/claims/types.d.ts +32 -0
- package/dist/core/understanding/brain/claims/types.js +25 -0
- package/dist/core/understanding/brain/components/index.d.ts +17 -0
- package/dist/core/understanding/brain/components/index.js +97 -0
- package/dist/core/understanding/brain/components/types.d.ts +16 -0
- package/dist/core/understanding/brain/components/types.js +8 -0
- package/dist/core/understanding/brain/confidence.d.ts +19 -0
- package/dist/core/understanding/brain/confidence.js +39 -0
- package/dist/core/understanding/brain/contract.d.ts +17 -0
- package/dist/core/understanding/brain/contract.js +100 -0
- package/dist/core/understanding/brain/contradictions/index.d.ts +12 -0
- package/dist/core/understanding/brain/contradictions/index.js +57 -0
- package/dist/core/understanding/brain/contradictions/types.d.ts +12 -0
- package/dist/core/understanding/brain/contradictions/types.js +8 -0
- package/dist/core/understanding/brain/delta.d.ts +25 -0
- package/dist/core/understanding/brain/delta.js +116 -0
- package/dist/core/understanding/brain/evidence/index.d.ts +3 -0
- package/dist/core/understanding/brain/evidence/index.js +2 -0
- package/dist/core/understanding/brain/evidence/redact.d.ts +4 -0
- package/dist/core/understanding/brain/evidence/redact.js +36 -0
- package/dist/core/understanding/brain/evidence/types.d.ts +34 -0
- package/dist/core/understanding/brain/evidence/types.js +35 -0
- package/dist/core/understanding/brain/explain.d.ts +17 -0
- package/dist/core/understanding/brain/explain.js +33 -0
- package/dist/core/understanding/brain/index.d.ts +28 -0
- package/dist/core/understanding/brain/index.js +16 -0
- package/dist/core/understanding/brain/migrate.d.ts +21 -0
- package/dist/core/understanding/brain/migrate.js +47 -0
- package/dist/core/understanding/brain/query.d.ts +60 -0
- package/dist/core/understanding/brain/query.js +92 -0
- package/dist/core/understanding/brain/security.d.ts +3 -0
- package/dist/core/understanding/brain/security.js +43 -0
- package/dist/core/understanding/brain/storage/index.d.ts +2 -0
- package/dist/core/understanding/brain/storage/index.js +1 -0
- package/dist/core/understanding/brain/storage/store.d.ts +53 -0
- package/dist/core/understanding/brain/storage/store.js +305 -0
- package/dist/core/understanding/brain/trace.d.ts +24 -0
- package/dist/core/understanding/brain/trace.js +207 -0
- package/dist/core/understanding/brain/types.d.ts +32 -0
- package/dist/core/understanding/brain/types.js +8 -0
- package/dist/core/understanding/brain/version.d.ts +13 -0
- package/dist/core/understanding/brain/version.js +7 -0
- package/dist/core/understanding/delta/compare.d.ts +26 -0
- package/dist/core/understanding/delta/compare.js +74 -0
- package/dist/core/understanding/delta/index.d.ts +2 -0
- package/dist/core/understanding/delta/index.js +1 -0
- package/dist/core/understanding/dependencies/discover.d.ts +7 -0
- package/dist/core/understanding/dependencies/discover.js +550 -0
- package/dist/core/understanding/dependencies/extract.d.ts +10 -0
- package/dist/core/understanding/dependencies/extract.js +189 -0
- package/dist/core/understanding/dependencies/index.d.ts +4 -0
- package/dist/core/understanding/dependencies/index.js +3 -0
- package/dist/core/understanding/dependencies/models.d.ts +12 -0
- package/dist/core/understanding/dependencies/models.js +125 -0
- package/dist/core/understanding/dependencies/types.d.ts +27 -0
- package/dist/core/understanding/dependencies/types.js +1 -0
- package/dist/core/understanding/domain/discover.d.ts +14 -0
- package/dist/core/understanding/domain/discover.js +90 -0
- package/dist/core/understanding/domain/index.d.ts +2 -0
- package/dist/core/understanding/domain/index.js +1 -0
- package/dist/core/understanding/entrypoints/discover.d.ts +6 -0
- package/dist/core/understanding/entrypoints/discover.js +62 -0
- package/dist/core/understanding/entrypoints/extract.d.ts +19 -0
- package/dist/core/understanding/entrypoints/extract.js +43 -0
- package/dist/core/understanding/entrypoints/index.d.ts +4 -0
- package/dist/core/understanding/entrypoints/index.js +3 -0
- package/dist/core/understanding/entrypoints/models.d.ts +26 -0
- package/dist/core/understanding/entrypoints/models.js +216 -0
- package/dist/core/understanding/entrypoints/types.d.ts +20 -0
- package/dist/core/understanding/entrypoints/types.js +1 -0
- package/dist/core/understanding/index.d.ts +32 -0
- package/dist/core/understanding/index.js +18 -0
- package/dist/core/understanding/mind/build.d.ts +16 -0
- package/dist/core/understanding/mind/build.js +134 -0
- package/dist/core/understanding/mind/index.d.ts +6 -0
- package/dist/core/understanding/mind/index.js +3 -0
- package/dist/core/understanding/mind/query.d.ts +33 -0
- package/dist/core/understanding/mind/query.js +39 -0
- package/dist/core/understanding/mind/types.d.ts +24 -0
- package/dist/core/understanding/mind/types.js +7 -0
- package/dist/core/understanding/model/builder.d.ts +9 -0
- package/dist/core/understanding/model/builder.js +209 -0
- package/dist/core/understanding/model/ids.d.ts +7 -0
- package/dist/core/understanding/model/ids.js +48 -0
- package/dist/core/understanding/model/index.d.ts +7 -0
- package/dist/core/understanding/model/index.js +6 -0
- package/dist/core/understanding/model/schema.d.ts +12 -0
- package/dist/core/understanding/model/schema.js +33 -0
- package/dist/core/understanding/model/serializer.d.ts +9 -0
- package/dist/core/understanding/model/serializer.js +46 -0
- package/dist/core/understanding/model/types.d.ts +110 -0
- package/dist/core/understanding/model/types.js +1 -0
- package/dist/core/understanding/model/validator.d.ts +5 -0
- package/dist/core/understanding/model/validator.js +126 -0
- package/dist/core/understanding/model/version.d.ts +9 -0
- package/dist/core/understanding/model/version.js +7 -0
- package/dist/core/understanding/ownership/discover.d.ts +12 -0
- package/dist/core/understanding/ownership/discover.js +242 -0
- package/dist/core/understanding/ownership/index.d.ts +2 -0
- package/dist/core/understanding/ownership/index.js +1 -0
- package/dist/core/understanding/ownership/types.d.ts +23 -0
- package/dist/core/understanding/ownership/types.js +1 -0
- package/dist/core/understanding/query/engine.d.ts +24 -0
- package/dist/core/understanding/query/engine.js +35 -0
- package/dist/core/understanding/query/errors.d.ts +13 -0
- package/dist/core/understanding/query/errors.js +26 -0
- package/dist/core/understanding/query/executor.d.ts +8 -0
- package/dist/core/understanding/query/executor.js +34 -0
- package/dist/core/understanding/query/index.d.ts +7 -0
- package/dist/core/understanding/query/index.js +5 -0
- package/dist/core/understanding/query/models.d.ts +102 -0
- package/dist/core/understanding/query/models.js +1 -0
- package/dist/core/understanding/query/query.d.ts +68 -0
- package/dist/core/understanding/query/query.js +145 -0
- package/dist/core/understanding/query/registry.d.ts +8 -0
- package/dist/core/understanding/query/registry.js +340 -0
- package/dist/core/understanding/query/types.d.ts +63 -0
- package/dist/core/understanding/query/types.js +1 -0
- package/dist/core/understanding/relationships/discover.d.ts +11 -0
- package/dist/core/understanding/relationships/discover.js +517 -0
- package/dist/core/understanding/relationships/extract.d.ts +14 -0
- package/dist/core/understanding/relationships/extract.js +165 -0
- package/dist/core/understanding/relationships/index.d.ts +4 -0
- package/dist/core/understanding/relationships/index.js +3 -0
- package/dist/core/understanding/relationships/models.d.ts +21 -0
- package/dist/core/understanding/relationships/models.js +185 -0
- package/dist/core/understanding/relationships/types.d.ts +42 -0
- package/dist/core/understanding/relationships/types.js +1 -0
- package/dist/core/understanding/risks/discover.d.ts +8 -0
- package/dist/core/understanding/risks/discover.js +155 -0
- package/dist/core/understanding/risks/index.d.ts +2 -0
- package/dist/core/understanding/risks/index.js +1 -0
- package/dist/core/understanding/risks/types.d.ts +26 -0
- package/dist/core/understanding/risks/types.js +1 -0
- package/dist/core/understanding/shared/domain-lexicon.d.ts +9 -0
- package/dist/core/understanding/shared/domain-lexicon.js +107 -0
- package/dist/core/understanding/shared/index.d.ts +2 -0
- package/dist/core/understanding/shared/index.js +2 -0
- package/dist/core/understanding/shared/tokens.d.ts +7 -0
- package/dist/core/understanding/shared/tokens.js +141 -0
- package/dist/core/understanding/snapshot/identity.d.ts +24 -0
- package/dist/core/understanding/snapshot/identity.js +79 -0
- package/dist/core/understanding/snapshot/index.d.ts +2 -0
- package/dist/core/understanding/snapshot/index.js +1 -0
- package/dist/core/understanding/types/index.d.ts +22 -0
- package/dist/core/understanding/types/index.js +1 -0
- package/dist/core/understanding/understand/formatter.d.ts +10 -0
- package/dist/core/understanding/understand/formatter.js +115 -0
- package/dist/core/understanding/understand/index.d.ts +4 -0
- package/dist/core/understanding/understand/index.js +3 -0
- package/dist/core/understanding/understand/service.d.ts +17 -0
- package/dist/core/understanding/understand/service.js +76 -0
- package/dist/core/understanding/understand/summary.d.ts +14 -0
- package/dist/core/understanding/understand/summary.js +39 -0
- package/dist/core/understanding/understand/types.d.ts +31 -0
- package/dist/core/understanding/understand/types.js +1 -0
- package/dist/mcp/brain/compile.d.ts +9 -0
- package/dist/mcp/brain/compile.js +42 -0
- package/dist/mcp/brain/errors.d.ts +12 -0
- package/dist/mcp/brain/errors.js +23 -0
- package/dist/mcp/brain/index.d.ts +11 -0
- package/dist/mcp/brain/index.js +8 -0
- package/dist/mcp/brain/provenance.d.ts +29 -0
- package/dist/mcp/brain/provenance.js +59 -0
- package/dist/mcp/brain/schemas.d.ts +13 -0
- package/dist/mcp/brain/schemas.js +105 -0
- package/dist/mcp/brain/security/root.d.ts +6 -0
- package/dist/mcp/brain/security/root.js +66 -0
- package/dist/mcp/brain/server.d.ts +12 -0
- package/dist/mcp/brain/server.js +46 -0
- package/dist/mcp/brain/session.d.ts +33 -0
- package/dist/mcp/brain/session.js +117 -0
- package/dist/mcp/brain/tools/handlers.d.ts +13 -0
- package/dist/mcp/brain/tools/handlers.js +483 -0
- package/dist/mcp/brain/tools/registry.d.ts +9 -0
- package/dist/mcp/brain/tools/registry.js +191 -0
- package/package.json +5 -1
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { discoverFiles } from "../../../discovery/files.js";
|
|
3
|
+
import { readJsonFile, readTextFile } from "../../../utils/fs.js";
|
|
4
|
+
import { extractReferences, refineRelationType } from "./extract.js";
|
|
5
|
+
import { LABEL_STOP_SEGMENTS, MANIFEST_FILES, RELATION_CONFIDENCE, RESOLVE_EXTENSIONS, clampConfidence, isSourceFile, titleCaseSegment, } from "./models.js";
|
|
6
|
+
function dirnamePosix(relativePath) {
|
|
7
|
+
const idx = relativePath.lastIndexOf("/");
|
|
8
|
+
if (idx < 0) {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
return relativePath.slice(0, idx);
|
|
12
|
+
}
|
|
13
|
+
function joinPosix(...parts) {
|
|
14
|
+
const joined = parts.filter((p, i) => (i === 0 ? p !== undefined : Boolean(p))).join("/");
|
|
15
|
+
const stack = [];
|
|
16
|
+
for (const part of joined.split("/")) {
|
|
17
|
+
if (!part || part === ".") {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (part === "..") {
|
|
21
|
+
stack.pop();
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
stack.push(part);
|
|
25
|
+
}
|
|
26
|
+
return stack.join("/");
|
|
27
|
+
}
|
|
28
|
+
function stripExtension(relativePath) {
|
|
29
|
+
return relativePath.replace(/\.[a-z0-9]+$/i, "");
|
|
30
|
+
}
|
|
31
|
+
function buildResolveMap(files) {
|
|
32
|
+
const map = new Map();
|
|
33
|
+
for (const relativePath of files) {
|
|
34
|
+
map.set(relativePath, relativePath);
|
|
35
|
+
const noExt = stripExtension(relativePath);
|
|
36
|
+
if (!map.has(noExt)) {
|
|
37
|
+
map.set(noExt, relativePath);
|
|
38
|
+
}
|
|
39
|
+
if (relativePath.endsWith("/index.ts") || relativePath.endsWith("/index.js")) {
|
|
40
|
+
const dir = dirnamePosix(relativePath);
|
|
41
|
+
if (dir && !map.has(dir)) {
|
|
42
|
+
map.set(dir, relativePath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (relativePath.endsWith("/__init__.py")) {
|
|
46
|
+
const dir = dirnamePosix(relativePath);
|
|
47
|
+
if (dir && !map.has(dir)) {
|
|
48
|
+
map.set(dir, relativePath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (relativePath.endsWith("/mod.rs")) {
|
|
52
|
+
const dir = dirnamePosix(relativePath);
|
|
53
|
+
if (dir && !map.has(dir)) {
|
|
54
|
+
map.set(dir, relativePath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return map;
|
|
59
|
+
}
|
|
60
|
+
function lookupResolved(resolveMap, candidate) {
|
|
61
|
+
const normalized = candidate.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
62
|
+
if (!normalized) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const candidates = [normalized];
|
|
66
|
+
// TypeScript ESM: import "./foo.js" often resolves to foo.ts
|
|
67
|
+
if (/\.(js|jsx|mjs|cjs)$/i.test(normalized)) {
|
|
68
|
+
candidates.push(normalized.replace(/\.js$/i, ".ts").replace(/\.jsx$/i, ".tsx"));
|
|
69
|
+
candidates.push(stripExtension(normalized));
|
|
70
|
+
}
|
|
71
|
+
for (const entry of candidates) {
|
|
72
|
+
const direct = resolveMap.get(entry);
|
|
73
|
+
if (direct) {
|
|
74
|
+
return direct;
|
|
75
|
+
}
|
|
76
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
77
|
+
const hit = resolveMap.get(entry + ext);
|
|
78
|
+
if (hit) {
|
|
79
|
+
return hit;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
async function loadPackages(root, relativePaths, maxReadBytes) {
|
|
86
|
+
const packages = [];
|
|
87
|
+
const aliases = [];
|
|
88
|
+
let goModule = null;
|
|
89
|
+
let goModuleRoot = null;
|
|
90
|
+
for (const relativePath of relativePaths) {
|
|
91
|
+
const base = relativePath.split("/").pop()?.toLowerCase() ?? "";
|
|
92
|
+
if (!MANIFEST_FILES.has(base)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const absolute = path.join(root, relativePath);
|
|
96
|
+
if (base === "package.json") {
|
|
97
|
+
const parsed = await readJsonFile(absolute, maxReadBytes);
|
|
98
|
+
if (!parsed.ok) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const pkgRoot = dirnamePosix(relativePath);
|
|
102
|
+
const depNames = new Set();
|
|
103
|
+
for (const bag of [
|
|
104
|
+
parsed.data.dependencies,
|
|
105
|
+
parsed.data.devDependencies,
|
|
106
|
+
parsed.data.peerDependencies,
|
|
107
|
+
parsed.data.optionalDependencies,
|
|
108
|
+
]) {
|
|
109
|
+
if (!bag) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
for (const name of Object.keys(bag)) {
|
|
113
|
+
depNames.add(name);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (parsed.data.name) {
|
|
117
|
+
packages.push({
|
|
118
|
+
name: parsed.data.name,
|
|
119
|
+
root: pkgRoot,
|
|
120
|
+
dependencies: [...depNames].sort((a, b) => a.localeCompare(b)),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (base === "tsconfig.json" || base === "tsconfig.base.json" || base === "jsconfig.json") {
|
|
126
|
+
const parsed = await readJsonFile(absolute, maxReadBytes);
|
|
127
|
+
if (!parsed.ok || !parsed.data.compilerOptions?.paths) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const configDir = dirnamePosix(relativePath);
|
|
131
|
+
for (const [pattern, targets] of Object.entries(parsed.data.compilerOptions.paths)) {
|
|
132
|
+
const target = targets[0];
|
|
133
|
+
if (!target) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const mappedTarget = joinPosix(configDir, target);
|
|
137
|
+
if (pattern.includes("*")) {
|
|
138
|
+
const [patternPrefix = "", patternSuffix = ""] = pattern.split("*");
|
|
139
|
+
const [targetPrefix = "", targetSuffix = ""] = mappedTarget.split("*");
|
|
140
|
+
aliases.push({
|
|
141
|
+
star: { patternPrefix, patternSuffix, targetPrefix, targetSuffix },
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
aliases.push({ exact: { pattern, target: mappedTarget } });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (base === "go.mod") {
|
|
151
|
+
const text = await readTextFile(absolute, maxReadBytes);
|
|
152
|
+
if (!text) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const match = /^module\s+(\S+)/m.exec(text);
|
|
156
|
+
if (match?.[1]) {
|
|
157
|
+
goModule = match[1];
|
|
158
|
+
goModuleRoot = dirnamePosix(relativePath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
aliases.sort((a, b) => {
|
|
163
|
+
const aKey = a.exact?.pattern ?? `${a.star?.patternPrefix ?? ""}*${a.star?.patternSuffix ?? ""}`;
|
|
164
|
+
const bKey = b.exact?.pattern ?? `${b.star?.patternPrefix ?? ""}*${b.star?.patternSuffix ?? ""}`;
|
|
165
|
+
return bKey.length - aKey.length || aKey.localeCompare(bKey);
|
|
166
|
+
});
|
|
167
|
+
return { packages, aliases, goModule, goModuleRoot };
|
|
168
|
+
}
|
|
169
|
+
function packageLabel(name) {
|
|
170
|
+
const short = name.includes("/") ? (name.split("/").pop() ?? name) : name;
|
|
171
|
+
return titleCaseSegment(short);
|
|
172
|
+
}
|
|
173
|
+
function moduleLabel(relativePath, index) {
|
|
174
|
+
for (const root of index.packageRoots) {
|
|
175
|
+
if (root === "") {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (relativePath === root || relativePath.startsWith(`${root}/`)) {
|
|
179
|
+
const pkg = [...index.packagesByName.values()].find((p) => p.root === root);
|
|
180
|
+
if (pkg) {
|
|
181
|
+
return packageLabel(pkg.name);
|
|
182
|
+
}
|
|
183
|
+
const segment = root
|
|
184
|
+
.split("/")
|
|
185
|
+
.filter((s) => !LABEL_STOP_SEGMENTS.has(s.toLowerCase()))
|
|
186
|
+
.pop();
|
|
187
|
+
if (segment) {
|
|
188
|
+
return titleCaseSegment(segment);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const parts = relativePath.split("/");
|
|
193
|
+
const file = parts[parts.length - 1] ?? relativePath;
|
|
194
|
+
const dirs = parts.slice(0, -1);
|
|
195
|
+
// Prefer the rightmost meaningful directory (domain/package folder).
|
|
196
|
+
for (let i = dirs.length - 1; i >= 0; i -= 1) {
|
|
197
|
+
const part = dirs[i] ?? "";
|
|
198
|
+
const lower = part.toLowerCase();
|
|
199
|
+
if (LABEL_STOP_SEGMENTS.has(lower)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (part.includes(".")) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (/^[a-zA-Z][\w-]*$/.test(part)) {
|
|
206
|
+
return titleCaseSegment(part);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return titleCaseSegment(stripExtension(file));
|
|
210
|
+
}
|
|
211
|
+
function resolveJsSpecifier(fromFile, specifier, index) {
|
|
212
|
+
if (specifier.startsWith(".")) {
|
|
213
|
+
const base = joinPosix(dirnamePosix(fromFile), specifier);
|
|
214
|
+
return { targetFile: lookupResolved(index.resolveMap, base), packageName: null };
|
|
215
|
+
}
|
|
216
|
+
for (const alias of index.aliases) {
|
|
217
|
+
if (alias.exact && specifier === alias.exact.pattern) {
|
|
218
|
+
const hit = lookupResolved(index.resolveMap, alias.exact.target);
|
|
219
|
+
if (hit) {
|
|
220
|
+
return { targetFile: hit, packageName: null };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (alias.star) {
|
|
224
|
+
const { patternPrefix, patternSuffix, targetPrefix, targetSuffix } = alias.star;
|
|
225
|
+
if (specifier.startsWith(patternPrefix) &&
|
|
226
|
+
specifier.endsWith(patternSuffix) &&
|
|
227
|
+
specifier.length >= patternPrefix.length + patternSuffix.length) {
|
|
228
|
+
const mid = specifier.slice(patternPrefix.length, specifier.length - patternSuffix.length);
|
|
229
|
+
const mapped = `${targetPrefix}${mid}${targetSuffix}`;
|
|
230
|
+
const hit = lookupResolved(index.resolveMap, mapped);
|
|
231
|
+
if (hit) {
|
|
232
|
+
return { targetFile: hit, packageName: null };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const pkg = index.packagesByName.get(specifier);
|
|
238
|
+
if (pkg) {
|
|
239
|
+
return { targetFile: null, packageName: pkg.name };
|
|
240
|
+
}
|
|
241
|
+
// @scope/name/subpath → package name @scope/name
|
|
242
|
+
if (specifier.startsWith("@")) {
|
|
243
|
+
const parts = specifier.split("/");
|
|
244
|
+
if (parts.length >= 2) {
|
|
245
|
+
const name = `${parts[0]}/${parts[1]}`;
|
|
246
|
+
const named = index.packagesByName.get(name);
|
|
247
|
+
if (named) {
|
|
248
|
+
const rest = parts.slice(2).join("/");
|
|
249
|
+
if (rest) {
|
|
250
|
+
const candidate = joinPosix(named.root, "src", rest);
|
|
251
|
+
const hit = lookupResolved(index.resolveMap, candidate) ??
|
|
252
|
+
lookupResolved(index.resolveMap, joinPosix(named.root, rest));
|
|
253
|
+
if (hit) {
|
|
254
|
+
return { targetFile: hit, packageName: null };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { targetFile: null, packageName: named.name };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return { targetFile: null, packageName: null };
|
|
262
|
+
}
|
|
263
|
+
function resolveDartSpecifier(fromFile, specifier, index) {
|
|
264
|
+
if (specifier.startsWith("package:")) {
|
|
265
|
+
const body = specifier.slice("package:".length);
|
|
266
|
+
const slash = body.indexOf("/");
|
|
267
|
+
const pkgName = slash >= 0 ? body.slice(0, slash) : body;
|
|
268
|
+
const rest = slash >= 0 ? body.slice(slash + 1) : "";
|
|
269
|
+
// Map package:foo/bar.dart → lib/bar.dart under a matching folder named foo, or pub name
|
|
270
|
+
for (const file of index.files) {
|
|
271
|
+
if (!file.endsWith(`/${rest}`) && file !== rest) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (file.includes(`/${pkgName}/`) || file.startsWith(`${pkgName}/`)) {
|
|
275
|
+
return { targetFile: file, packageName: null };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { targetFile: null, packageName: null };
|
|
279
|
+
}
|
|
280
|
+
if (specifier.startsWith("dart:")) {
|
|
281
|
+
return { targetFile: null, packageName: null };
|
|
282
|
+
}
|
|
283
|
+
const base = joinPosix(dirnamePosix(fromFile), specifier);
|
|
284
|
+
return { targetFile: lookupResolved(index.resolveMap, base), packageName: null };
|
|
285
|
+
}
|
|
286
|
+
function resolveJavaSpecifier(specifier, index) {
|
|
287
|
+
const asPath = specifier.replace(/\./g, "/");
|
|
288
|
+
const direct = lookupResolved(index.resolveMap, asPath) ??
|
|
289
|
+
lookupResolved(index.resolveMap, `src/main/java/${asPath}`);
|
|
290
|
+
if (direct) {
|
|
291
|
+
return direct;
|
|
292
|
+
}
|
|
293
|
+
for (const file of index.files) {
|
|
294
|
+
if (file.endsWith(`/${asPath}.java`) || stripExtension(file).endsWith(`/${asPath}`)) {
|
|
295
|
+
return file;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
function resolveGoSpecifier(specifier, index) {
|
|
301
|
+
if (index.goModule && specifier.startsWith(`${index.goModule}/`)) {
|
|
302
|
+
const rest = specifier.slice(index.goModule.length + 1);
|
|
303
|
+
const base = index.goModuleRoot ? joinPosix(index.goModuleRoot, rest) : rest;
|
|
304
|
+
const direct = lookupResolved(index.resolveMap, base) ?? lookupResolved(index.resolveMap, `${base}.go`);
|
|
305
|
+
if (direct) {
|
|
306
|
+
return direct;
|
|
307
|
+
}
|
|
308
|
+
for (const file of index.files) {
|
|
309
|
+
if (file.startsWith(`${base}/`) && file.endsWith(".go")) {
|
|
310
|
+
return file;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return lookupResolved(index.resolveMap, specifier);
|
|
315
|
+
}
|
|
316
|
+
function resolvePythonSpecifier(fromFile, specifier, index) {
|
|
317
|
+
if (specifier.startsWith(".")) {
|
|
318
|
+
let dots = 0;
|
|
319
|
+
while (specifier[dots] === ".") {
|
|
320
|
+
dots += 1;
|
|
321
|
+
}
|
|
322
|
+
const rest = specifier.slice(dots).replace(/\./g, "/");
|
|
323
|
+
let dir = dirnamePosix(fromFile);
|
|
324
|
+
for (let i = 1; i < dots; i += 1) {
|
|
325
|
+
dir = dirnamePosix(dir);
|
|
326
|
+
}
|
|
327
|
+
const base = rest ? joinPosix(dir, rest) : dir;
|
|
328
|
+
return lookupResolved(index.resolveMap, base);
|
|
329
|
+
}
|
|
330
|
+
const asPath = specifier.replace(/\./g, "/");
|
|
331
|
+
const direct = lookupResolved(index.resolveMap, asPath) ??
|
|
332
|
+
lookupResolved(index.resolveMap, joinPosix(dirnamePosix(fromFile), asPath));
|
|
333
|
+
if (direct) {
|
|
334
|
+
return direct;
|
|
335
|
+
}
|
|
336
|
+
for (const file of index.files) {
|
|
337
|
+
const stripped = stripExtension(file);
|
|
338
|
+
if (stripped === asPath || stripped.endsWith(`/${asPath}`)) {
|
|
339
|
+
return file;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
function resolveRustSpecifier(fromFile, specifier, index) {
|
|
345
|
+
if (specifier.startsWith("crate::")) {
|
|
346
|
+
const rest = specifier.slice("crate::".length).split("::")[0] ?? "";
|
|
347
|
+
const crateRoot = fromFile.includes("/src/")
|
|
348
|
+
? fromFile.slice(0, fromFile.indexOf("/src/") + "/src".length)
|
|
349
|
+
: dirnamePosix(fromFile);
|
|
350
|
+
return (lookupResolved(index.resolveMap, joinPosix(crateRoot, rest)) ??
|
|
351
|
+
lookupResolved(index.resolveMap, joinPosix(crateRoot, `${rest}.rs`)));
|
|
352
|
+
}
|
|
353
|
+
if (specifier.startsWith("super::")) {
|
|
354
|
+
const rest = specifier.slice("super::".length).split("::")[0] ?? "";
|
|
355
|
+
const base = joinPosix(dirnamePosix(dirnamePosix(fromFile)), rest);
|
|
356
|
+
return lookupResolved(index.resolveMap, base);
|
|
357
|
+
}
|
|
358
|
+
// bare mod name
|
|
359
|
+
if (/^[a-zA-Z_][\w]*$/.test(specifier)) {
|
|
360
|
+
return lookupResolved(index.resolveMap, joinPosix(dirnamePosix(fromFile), specifier));
|
|
361
|
+
}
|
|
362
|
+
const first = specifier.split("::")[0] ?? specifier;
|
|
363
|
+
return lookupResolved(index.resolveMap, joinPosix(dirnamePosix(fromFile), first));
|
|
364
|
+
}
|
|
365
|
+
function resolvePhpSpecifier(specifier, index) {
|
|
366
|
+
const asPath = specifier.replace(/^App\//, "app/").replace(/\\/g, "/");
|
|
367
|
+
return (lookupResolved(index.resolveMap, asPath) ??
|
|
368
|
+
lookupResolved(index.resolveMap, `${asPath}.php`) ??
|
|
369
|
+
[...index.files].find((f) => stripExtension(f).endsWith(`/${asPath.split("/").pop()}`)) ??
|
|
370
|
+
null);
|
|
371
|
+
}
|
|
372
|
+
function resolveReference(fromFile, ref, index) {
|
|
373
|
+
const { specifier } = ref;
|
|
374
|
+
const ext = fromFile.split(".").pop()?.toLowerCase() ?? "";
|
|
375
|
+
if (ext === "dart") {
|
|
376
|
+
return resolveDartSpecifier(fromFile, specifier, index);
|
|
377
|
+
}
|
|
378
|
+
if (ext === "java") {
|
|
379
|
+
return { targetFile: resolveJavaSpecifier(specifier, index), packageName: null };
|
|
380
|
+
}
|
|
381
|
+
if (ext === "go") {
|
|
382
|
+
return { targetFile: resolveGoSpecifier(specifier, index), packageName: null };
|
|
383
|
+
}
|
|
384
|
+
if (ext === "py") {
|
|
385
|
+
return { targetFile: resolvePythonSpecifier(fromFile, specifier, index), packageName: null };
|
|
386
|
+
}
|
|
387
|
+
if (ext === "rs") {
|
|
388
|
+
return { targetFile: resolveRustSpecifier(fromFile, specifier, index), packageName: null };
|
|
389
|
+
}
|
|
390
|
+
if (ext === "php") {
|
|
391
|
+
return { targetFile: resolvePhpSpecifier(specifier, index), packageName: null };
|
|
392
|
+
}
|
|
393
|
+
return resolveJsSpecifier(fromFile, specifier, index);
|
|
394
|
+
}
|
|
395
|
+
function evidencePhrase(fromFile, targetFile, packageName, type, specifier) {
|
|
396
|
+
if (targetFile) {
|
|
397
|
+
const verb = type === "require"
|
|
398
|
+
? "requires"
|
|
399
|
+
: type === "dynamic-import"
|
|
400
|
+
? "dynamically imports"
|
|
401
|
+
: type === "export"
|
|
402
|
+
? "re-exports"
|
|
403
|
+
: type === "route"
|
|
404
|
+
? "routes to"
|
|
405
|
+
: type === "module"
|
|
406
|
+
? "modules"
|
|
407
|
+
: "imports";
|
|
408
|
+
return `${fromFile} ${verb} ${targetFile}`;
|
|
409
|
+
}
|
|
410
|
+
if (packageName) {
|
|
411
|
+
return `${fromFile} references package ${packageName} via ${specifier}`;
|
|
412
|
+
}
|
|
413
|
+
return `${fromFile} references ${specifier}`;
|
|
414
|
+
}
|
|
415
|
+
function addEdge(bucket, edge) {
|
|
416
|
+
if (edge.from === edge.to &&
|
|
417
|
+
edge.type !== "service" &&
|
|
418
|
+
edge.type !== "repository" &&
|
|
419
|
+
edge.type !== "module") {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const key = `${edge.from}\0${edge.to}\0${edge.type}`;
|
|
423
|
+
const existing = bucket.get(key);
|
|
424
|
+
if (!existing) {
|
|
425
|
+
bucket.set(key, {
|
|
426
|
+
from: edge.from,
|
|
427
|
+
to: edge.to,
|
|
428
|
+
type: edge.type,
|
|
429
|
+
confidence: edge.confidence,
|
|
430
|
+
evidence: [edge.evidence],
|
|
431
|
+
});
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
existing.confidence = clampConfidence(Math.max(existing.confidence, edge.confidence));
|
|
435
|
+
if (!existing.evidence.includes(edge.evidence)) {
|
|
436
|
+
existing.evidence.push(edge.evidence);
|
|
437
|
+
existing.evidence.sort((a, b) => a.localeCompare(b));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Discover structural repository dependencies via deterministic import/reference heuristics.
|
|
442
|
+
* Isolated from scan — internal understanding API only.
|
|
443
|
+
*/
|
|
444
|
+
export async function discoverDependencies(options = {}) {
|
|
445
|
+
const started = performance.now();
|
|
446
|
+
const cwd = options.cwd ?? process.cwd();
|
|
447
|
+
const minConfidence = options.minConfidence ?? 0.7;
|
|
448
|
+
const maxReadBytes = options.maxReadBytes ?? 256 * 1024;
|
|
449
|
+
const discovery = await discoverFiles({ root: cwd });
|
|
450
|
+
const relativePaths = discovery.files.map((f) => f.relativePath);
|
|
451
|
+
const fileSet = new Set(relativePaths);
|
|
452
|
+
const { packages, aliases, goModule, goModuleRoot } = await loadPackages(cwd, relativePaths, maxReadBytes);
|
|
453
|
+
const packagesByName = new Map(packages.map((p) => [p.name, p]));
|
|
454
|
+
const packageRoots = [...new Set(packages.map((p) => p.root))].sort((a, b) => b.length - a.length || a.localeCompare(b));
|
|
455
|
+
const index = {
|
|
456
|
+
files: fileSet,
|
|
457
|
+
resolveMap: buildResolveMap(fileSet),
|
|
458
|
+
packagesByName,
|
|
459
|
+
packageRoots,
|
|
460
|
+
aliases,
|
|
461
|
+
goModule,
|
|
462
|
+
goModuleRoot,
|
|
463
|
+
};
|
|
464
|
+
const edges = new Map();
|
|
465
|
+
let filesInspected = 0;
|
|
466
|
+
// Manifest package → package edges (workspace / monorepo only)
|
|
467
|
+
for (const pkg of packages) {
|
|
468
|
+
for (const depName of pkg.dependencies) {
|
|
469
|
+
const target = packagesByName.get(depName);
|
|
470
|
+
if (!target) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const fromLabel = packageLabel(pkg.name);
|
|
474
|
+
const toLabel = packageLabel(target.name);
|
|
475
|
+
const manifestPath = pkg.root ? `${pkg.root}/package.json` : "package.json";
|
|
476
|
+
addEdge(edges, {
|
|
477
|
+
from: fromLabel,
|
|
478
|
+
to: toLabel,
|
|
479
|
+
type: "package",
|
|
480
|
+
confidence: RELATION_CONFIDENCE.package,
|
|
481
|
+
evidence: `${manifestPath} declares dependency on ${target.name}`,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
for (const file of discovery.files) {
|
|
486
|
+
if (!isSourceFile(file.relativePath)) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const content = await readTextFile(file.absolutePath, maxReadBytes);
|
|
490
|
+
if (content === null) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
filesInspected += 1;
|
|
494
|
+
const refs = extractReferences(file.relativePath, content);
|
|
495
|
+
const fromLabel = moduleLabel(file.relativePath, index);
|
|
496
|
+
for (const ref of refs) {
|
|
497
|
+
// Never invent edges: only emit when the specifier resolves inside the repo.
|
|
498
|
+
const resolved = resolveReference(file.relativePath, ref, index);
|
|
499
|
+
if (!resolved.targetFile && !resolved.packageName) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
let type = ref.type;
|
|
503
|
+
let toLabel;
|
|
504
|
+
if (resolved.targetFile) {
|
|
505
|
+
type = refineRelationType({
|
|
506
|
+
base: type,
|
|
507
|
+
sourcePath: file.relativePath,
|
|
508
|
+
targetPath: resolved.targetFile,
|
|
509
|
+
});
|
|
510
|
+
toLabel = moduleLabel(resolved.targetFile, index);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
toLabel = packageLabel(resolved.packageName);
|
|
514
|
+
}
|
|
515
|
+
const confidence = type === ref.type ? ref.confidence : clampConfidence(RELATION_CONFIDENCE[type]);
|
|
516
|
+
addEdge(edges, {
|
|
517
|
+
from: fromLabel,
|
|
518
|
+
to: toLabel,
|
|
519
|
+
type,
|
|
520
|
+
confidence,
|
|
521
|
+
evidence: evidencePhrase(file.relativePath, resolved.targetFile, resolved.packageName, type, ref.specifier),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const dependencies = [...edges.values()]
|
|
526
|
+
.filter((d) => d.confidence >= minConfidence && d.evidence.length > 0)
|
|
527
|
+
.sort((a, b) => {
|
|
528
|
+
if (b.confidence !== a.confidence) {
|
|
529
|
+
return b.confidence - a.confidence;
|
|
530
|
+
}
|
|
531
|
+
const fromCmp = a.from.localeCompare(b.from);
|
|
532
|
+
if (fromCmp !== 0) {
|
|
533
|
+
return fromCmp;
|
|
534
|
+
}
|
|
535
|
+
const toCmp = a.to.localeCompare(b.to);
|
|
536
|
+
if (toCmp !== 0) {
|
|
537
|
+
return toCmp;
|
|
538
|
+
}
|
|
539
|
+
return a.type.localeCompare(b.type);
|
|
540
|
+
});
|
|
541
|
+
return {
|
|
542
|
+
dependencies,
|
|
543
|
+
timingMs: Math.round(performance.now() - started),
|
|
544
|
+
filesConsidered: discovery.files.length,
|
|
545
|
+
filesInspected,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
export function scoreDependencyConfidence(type) {
|
|
549
|
+
return RELATION_CONFIDENCE[type];
|
|
550
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DependencyRelationType, ExtractedReference } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Extract dependency references from source text using deterministic regex heuristics.
|
|
4
|
+
*/
|
|
5
|
+
export declare function extractReferences(relativePath: string, content: string): ExtractedReference[];
|
|
6
|
+
export declare function refineRelationType(options: {
|
|
7
|
+
base: DependencyRelationType;
|
|
8
|
+
sourcePath: string;
|
|
9
|
+
targetPath: string;
|
|
10
|
+
}): DependencyRelationType;
|