ai-code-map 0.1.0-alpha.2 → 0.1.0-alpha.3
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/AGENTS.md +26 -0
- package/CHANGELOG.md +22 -0
- package/README.md +141 -8
- package/dist/cli.js +806 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +173 -12
- package/dist/index.js +927 -263
- package/dist/index.js.map +1 -1
- package/docs/api.md +50 -1
- package/docs/backlog.md +1 -1
- package/docs/limitations.md +13 -1
- package/docs/post-alpha-roadmap.md +16 -0
- package/package.json +5 -2
- package/skills/ai-code-map/SKILL.md +39 -0
package/dist/index.js
CHANGED
|
@@ -1,40 +1,5 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
var CONFIG_VERSION = 1;
|
|
4
|
-
var ANALYZER_VERSION = "0.4.0";
|
|
5
|
-
var DEFAULT_SYNTAX_PARSER = "typescript-compiler-syntax";
|
|
6
|
-
var DEFAULT_SEMANTIC_RESOLVER = "typescript-program";
|
|
7
|
-
var DEFAULT_ANALYZER_NAMES = ["typescript-node", "react"];
|
|
8
|
-
var DEFAULT_PRODUCER_ID = "ai-code-map/default-pipeline";
|
|
9
|
-
var CUSTOM_PRODUCER_ID = "ai-code-map/custom-pipeline";
|
|
10
|
-
var PRODUCER_CONTRACT_VERSION = 1;
|
|
11
|
-
var DEFAULT_OUTPUT = ".ai/code-map";
|
|
12
|
-
var DEFAULT_INCLUDE = [
|
|
13
|
-
"src/**/*",
|
|
14
|
-
"app/**/*",
|
|
15
|
-
"pages/**/*",
|
|
16
|
-
"lib/**/*",
|
|
17
|
-
"server/**/*",
|
|
18
|
-
"*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"
|
|
19
|
-
];
|
|
20
|
-
var DEFAULT_EXCLUDE = [
|
|
21
|
-
"**/node_modules/**",
|
|
22
|
-
"**/.next/**",
|
|
23
|
-
"**/dist/**",
|
|
24
|
-
"**/build/**",
|
|
25
|
-
"**/coverage/**",
|
|
26
|
-
"**/.git/**",
|
|
27
|
-
"**/.ai/**",
|
|
28
|
-
"**/*.generated.*"
|
|
29
|
-
];
|
|
30
|
-
var MAP_COMPANION_ARTIFACTS = [
|
|
31
|
-
"overview.md",
|
|
32
|
-
"project.json",
|
|
33
|
-
"files.jsonl",
|
|
34
|
-
"symbols.jsonl",
|
|
35
|
-
"relations.jsonl",
|
|
36
|
-
"diagnostics.json"
|
|
37
|
-
];
|
|
1
|
+
// src/context.ts
|
|
2
|
+
import path3 from "path";
|
|
38
3
|
|
|
39
4
|
// src/errors.ts
|
|
40
5
|
var CodeMapError = class extends Error {
|
|
@@ -49,20 +14,266 @@ function errorMessage(error) {
|
|
|
49
14
|
return error instanceof Error ? error.message : String(error);
|
|
50
15
|
}
|
|
51
16
|
|
|
52
|
-
// src/
|
|
53
|
-
import { promises as
|
|
54
|
-
import
|
|
17
|
+
// src/utils/stable.ts
|
|
18
|
+
import { promises as fs } from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
function compareText(a, b) {
|
|
21
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
22
|
+
}
|
|
23
|
+
function safeRecordEntry(value) {
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return value.map((entry) => safeRecordEntry(entry));
|
|
26
|
+
}
|
|
27
|
+
if (value !== null && typeof value === "object") {
|
|
28
|
+
const record = value;
|
|
29
|
+
const next = {};
|
|
30
|
+
for (const key of Object.keys(record).sort(compareText)) {
|
|
31
|
+
const child = record[key];
|
|
32
|
+
if (child !== void 0) {
|
|
33
|
+
next[key] = safeRecordEntry(child);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function stableValue(value) {
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
return safeRecordEntry(value);
|
|
43
|
+
}
|
|
44
|
+
if (value !== null && typeof value === "object") {
|
|
45
|
+
return safeRecordEntry(value);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function stableJson(value, pretty = false) {
|
|
50
|
+
return `${JSON.stringify(stableValue(value), null, pretty ? 2 : void 0)}
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
function stableJsonLine(value) {
|
|
54
|
+
return JSON.stringify(stableValue(value));
|
|
55
|
+
}
|
|
56
|
+
async function atomicWriteIfChanged(filePath, content) {
|
|
57
|
+
const canonicalBytes = Buffer.from(content, "utf8");
|
|
58
|
+
try {
|
|
59
|
+
if ((await fs.readFile(filePath)).equals(canonicalBytes)) return false;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!isNotFound(error) && !isPermissionDenied(error)) throw error;
|
|
62
|
+
}
|
|
63
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
64
|
+
const temporary = path.join(
|
|
65
|
+
path.dirname(filePath),
|
|
66
|
+
`.${path.basename(filePath)}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`
|
|
67
|
+
);
|
|
68
|
+
try {
|
|
69
|
+
await fs.writeFile(temporary, canonicalBytes, { mode: 420 });
|
|
70
|
+
await fs.rename(temporary, filePath);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
await fs.rm(temporary, { force: true }).catch(() => void 0);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function isNotFound(error) {
|
|
78
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
79
|
+
}
|
|
80
|
+
function isPermissionDenied(error) {
|
|
81
|
+
return error instanceof Error && "code" in error && (error.code === "EACCES" || error.code === "EPERM");
|
|
82
|
+
}
|
|
83
|
+
async function mapLimit(values, limit, operation) {
|
|
84
|
+
const results = new Array(values.length);
|
|
85
|
+
let cursor = 0;
|
|
86
|
+
const workers = Array.from({ length: Math.min(Math.max(1, limit), values.length) }, async () => {
|
|
87
|
+
while (cursor < values.length) {
|
|
88
|
+
const index = cursor++;
|
|
89
|
+
const value = values[index];
|
|
90
|
+
if (value === void 0) return;
|
|
91
|
+
results[index] = await operation(value, index);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
await Promise.all(workers);
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
55
97
|
|
|
56
|
-
// src/
|
|
57
|
-
|
|
58
|
-
|
|
98
|
+
// src/search.ts
|
|
99
|
+
var DEFAULT_LIMIT = 10;
|
|
100
|
+
var MAX_LIMIT = 50;
|
|
101
|
+
function findSymbols(map, query, options = {}) {
|
|
102
|
+
const normalized = query.trim();
|
|
103
|
+
if (!normalized) throw new Error("Symbol search query must not be empty.");
|
|
104
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
105
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIMIT) {
|
|
106
|
+
throw new RangeError(`Symbol search limit must be an integer from 1 to ${MAX_LIMIT}.`);
|
|
107
|
+
}
|
|
108
|
+
const qualified = splitQualifiedSelector(normalized);
|
|
109
|
+
const queryWords = [...new Set(words(normalized))];
|
|
110
|
+
const ranked = map.symbols.map((symbol) => {
|
|
111
|
+
const nameWords = new Set(words(symbol.name));
|
|
112
|
+
const symbolWords = /* @__PURE__ */ new Set([...nameWords, ...words(symbol.file)]);
|
|
113
|
+
const unmatchedTerms = queryWords.filter((word) => !symbolWords.has(word));
|
|
114
|
+
const matched = queryWords.length - unmatchedTerms.length;
|
|
115
|
+
const nameMatches = queryWords.filter((word) => nameWords.has(word)).length;
|
|
116
|
+
let rank = matchRank(
|
|
117
|
+
symbol,
|
|
118
|
+
normalized,
|
|
119
|
+
qualified,
|
|
120
|
+
queryWords.length > 0 && unmatchedTerms.length === 0
|
|
121
|
+
);
|
|
122
|
+
if (rank === void 0 && !qualified && /\s/.test(normalized) && queryWords.length >= 3 && matched >= Math.max(2, Math.ceil(queryWords.length / 2)) && nameMatches > 0)
|
|
123
|
+
rank = 9;
|
|
124
|
+
return rank === void 0 ? void 0 : { symbol, rank, matched, nameMatches, unmatchedTerms };
|
|
125
|
+
}).filter((candidate) => candidate !== void 0).sort(
|
|
126
|
+
(left, right) => left.rank - right.rank || right.matched - left.matched || right.nameMatches - left.nameMatches || compareText(left.symbol.id, right.symbol.id)
|
|
127
|
+
);
|
|
128
|
+
const matches = ranked.slice(0, limit).map(({ symbol, rank, unmatchedTerms }) => ({
|
|
129
|
+
...toMatch(symbol),
|
|
130
|
+
...rank === 9 ? { unmatchedTerms } : {}
|
|
131
|
+
}));
|
|
132
|
+
return {
|
|
133
|
+
query: normalized,
|
|
134
|
+
total: ranked.length,
|
|
135
|
+
truncated: ranked.length > matches.length,
|
|
136
|
+
matches
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function matchRank(symbol, query, qualified, allWordsMatch) {
|
|
140
|
+
if (symbol.id === query) return 0;
|
|
141
|
+
if (symbol.name === query) return 1;
|
|
142
|
+
const lowerQuery = query.toLowerCase();
|
|
143
|
+
if (qualified) {
|
|
144
|
+
if (symbol.file !== qualified.file) return void 0;
|
|
145
|
+
const terminalName = qualified.member.split(".").at(-1) ?? qualified.member;
|
|
146
|
+
if (symbol.name === qualified.member || symbol.name === terminalName) return 2;
|
|
147
|
+
if (symbol.name.toLowerCase() === qualified.member.toLowerCase() || symbol.name.toLowerCase() === terminalName.toLowerCase()) {
|
|
148
|
+
return 3;
|
|
149
|
+
}
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
const lowerName = symbol.name.toLowerCase();
|
|
153
|
+
const lowerFile = symbol.file.toLowerCase();
|
|
154
|
+
if (lowerName === lowerQuery) return 3;
|
|
155
|
+
if (lowerName.startsWith(lowerQuery)) return 4;
|
|
156
|
+
if (lowerName.includes(lowerQuery)) return 5;
|
|
157
|
+
if (lowerFile.includes(lowerQuery)) return 6;
|
|
158
|
+
if (allWordsMatch) return 7;
|
|
159
|
+
if (/^[a-z0-9]+$/i.test(query)) {
|
|
160
|
+
const compactQuery = compact(query);
|
|
161
|
+
const compactName = compact(symbol.name);
|
|
162
|
+
if (compactQuery.length >= 4 && compactQuery.length <= 128 && compactName.length >= 4 && compactName.length <= 128 && editDistance(compactQuery, compactName) <= (compactQuery.length >= 8 ? 2 : 1)) {
|
|
163
|
+
return 8;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
function splitQualifiedSelector(value) {
|
|
169
|
+
const index = value.indexOf("#");
|
|
170
|
+
if (index <= 0 || index === value.length - 1) return void 0;
|
|
171
|
+
return { file: value.slice(0, index), member: value.slice(index + 1) };
|
|
172
|
+
}
|
|
173
|
+
function words(value) {
|
|
174
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).map((word) => {
|
|
175
|
+
if (word.length > 4 && word.endsWith("ies")) return `${word.slice(0, -3)}y`;
|
|
176
|
+
if (word.length > 3 && word.endsWith("s") && !/(ss|us|is)$/.test(word))
|
|
177
|
+
return word.slice(0, -1);
|
|
178
|
+
return word;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function compact(value) {
|
|
182
|
+
return value.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
183
|
+
}
|
|
184
|
+
function editDistance(left, right) {
|
|
185
|
+
if (Math.abs(left.length - right.length) > 2) return 3;
|
|
186
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
187
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
188
|
+
let diagonal = previous[0] ?? 0;
|
|
189
|
+
previous[0] = leftIndex;
|
|
190
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
191
|
+
const above = previous[rightIndex] ?? 0;
|
|
192
|
+
previous[rightIndex] = Math.min(
|
|
193
|
+
(previous[rightIndex - 1] ?? 0) + 1,
|
|
194
|
+
above + 1,
|
|
195
|
+
diagonal + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1)
|
|
196
|
+
);
|
|
197
|
+
diagonal = above;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return previous[right.length] ?? 0;
|
|
201
|
+
}
|
|
202
|
+
function toMatch(symbol) {
|
|
203
|
+
return {
|
|
204
|
+
id: symbol.id,
|
|
205
|
+
name: symbol.name,
|
|
206
|
+
kind: symbol.kind,
|
|
207
|
+
file: symbol.file,
|
|
208
|
+
range: symbol.range
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/read-navigation.ts
|
|
213
|
+
function buildReadNavigation(map, id) {
|
|
214
|
+
const symbols = new Map(map.symbols.map((symbol) => [symbol.id, symbol]));
|
|
215
|
+
const candidates = [];
|
|
216
|
+
const tests = /* @__PURE__ */ new Map();
|
|
217
|
+
for (const relation2 of map.relations) {
|
|
218
|
+
if (relation2.from !== id && relation2.to !== id) continue;
|
|
219
|
+
const classification = relation2.origin.classification;
|
|
220
|
+
if (classification !== "exact" && classification !== "framework-derived") continue;
|
|
221
|
+
if (["contains", "exports", "used-by"].includes(relation2.type)) continue;
|
|
222
|
+
const outgoing = relation2.from === id;
|
|
223
|
+
const target = outgoing ? relation2.to : relation2.from;
|
|
224
|
+
if (target === id) continue;
|
|
225
|
+
if (!outgoing && relation2.type === "calls" && classification === "exact" && /(?:^|\/)__tests__\/|\.(?:test|spec)\.[^/]+$/.test(relation2.origin.file)) {
|
|
226
|
+
const site = { file: relation2.origin.file, line: relation2.origin.line };
|
|
227
|
+
tests.set(`${site.file}:${site.line}`, site);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const symbol = symbols.get(target);
|
|
231
|
+
if (!symbol) continue;
|
|
232
|
+
candidates.push({
|
|
233
|
+
direction: outgoing ? "outgoing" : "incoming",
|
|
234
|
+
type: relation2.type,
|
|
235
|
+
id: symbol.id,
|
|
236
|
+
file: symbol.file,
|
|
237
|
+
startLine: symbol.range.startLine,
|
|
238
|
+
endLine: symbol.range.endLine,
|
|
239
|
+
classification
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
candidates.sort(
|
|
243
|
+
(a, b) => Number(a.type === "references") - Number(b.type === "references") || Number(a.direction === "incoming") - Number(b.direction === "incoming") || Number(a.classification !== "exact") - Number(b.classification !== "exact") || compareText(`${a.type}:${a.id}`, `${b.type}:${b.id}`)
|
|
244
|
+
);
|
|
245
|
+
const links = /* @__PURE__ */ new Map();
|
|
246
|
+
for (const link of candidates) {
|
|
247
|
+
const key = `${link.direction}:${link.id}`;
|
|
248
|
+
if (!links.has(key)) links.set(key, link);
|
|
249
|
+
}
|
|
250
|
+
const result = { links: [], tests: [], omittedLinks: 0, omittedTests: 0 };
|
|
251
|
+
let remaining = 3600;
|
|
252
|
+
for (const site of [...tests.values()].sort(
|
|
253
|
+
(a, b) => compareText(a.file, b.file) || a.line - b.line
|
|
254
|
+
)) {
|
|
255
|
+
const size = JSON.stringify(site).length;
|
|
256
|
+
if (result.tests.length < 4 && size <= Math.min(remaining, 900)) {
|
|
257
|
+
result.tests.push(site);
|
|
258
|
+
remaining -= size;
|
|
259
|
+
} else result.omittedTests++;
|
|
260
|
+
}
|
|
261
|
+
for (const link of links.values()) {
|
|
262
|
+
const size = JSON.stringify(link).length;
|
|
263
|
+
if (result.links.length < 8 && size <= remaining) {
|
|
264
|
+
result.links.push(link);
|
|
265
|
+
remaining -= size;
|
|
266
|
+
} else result.omittedLinks++;
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
59
270
|
|
|
60
271
|
// src/utils/path.ts
|
|
61
|
-
import { promises as
|
|
62
|
-
import
|
|
272
|
+
import { promises as fs2 } from "fs";
|
|
273
|
+
import path2 from "path";
|
|
63
274
|
function isAbsolutePath(value) {
|
|
64
275
|
const normalized = toPosixPath(value);
|
|
65
|
-
return
|
|
276
|
+
return path2.posix.isAbsolute(normalized) || /^[a-zA-Z]:[\\/]/.test(normalized) || normalized.startsWith("//");
|
|
66
277
|
}
|
|
67
278
|
function isFileSystemUri(value) {
|
|
68
279
|
return /^file:(?:\/\/|\/)/i.test(value.trim());
|
|
@@ -92,7 +303,7 @@ function looksLikeHostFilePath(value, root) {
|
|
|
92
303
|
}
|
|
93
304
|
const normalized = toPosixPath(trimmed);
|
|
94
305
|
if (root) {
|
|
95
|
-
const normalizedRoot = toPosixPath(
|
|
306
|
+
const normalizedRoot = toPosixPath(path2.resolve(root));
|
|
96
307
|
if (normalized === normalizedRoot || normalized.startsWith(`${normalizedRoot}/`) || normalized.includes(normalizedRoot))
|
|
97
308
|
return true;
|
|
98
309
|
}
|
|
@@ -110,7 +321,7 @@ function relativePosix(root, absolutePath) {
|
|
|
110
321
|
`Path ${absolutePath} is outside project root ${root}.`
|
|
111
322
|
);
|
|
112
323
|
}
|
|
113
|
-
const relative =
|
|
324
|
+
const relative = path2.relative(root, absolutePath);
|
|
114
325
|
if (relative === "" || isRelativeInside(relative)) {
|
|
115
326
|
return toPosixPath(relative || ".");
|
|
116
327
|
}
|
|
@@ -120,31 +331,31 @@ function relativePosix(root, absolutePath) {
|
|
|
120
331
|
);
|
|
121
332
|
}
|
|
122
333
|
function isPathInside(root, candidate) {
|
|
123
|
-
if (isAbsolutePath(candidate) && !
|
|
124
|
-
const relative =
|
|
334
|
+
if (isAbsolutePath(candidate) && !path2.isAbsolute(candidate)) return false;
|
|
335
|
+
const relative = path2.relative(path2.resolve(root), path2.resolve(candidate));
|
|
125
336
|
return relative === "" || isRelativeInside(relative);
|
|
126
337
|
}
|
|
127
338
|
async function isSafeRegularFile(root, candidate) {
|
|
128
|
-
const absolute =
|
|
339
|
+
const absolute = path2.resolve(candidate);
|
|
129
340
|
if (!isPathInside(root, absolute)) return false;
|
|
130
341
|
try {
|
|
131
|
-
const stat = await
|
|
342
|
+
const stat = await fs2.lstat(absolute);
|
|
132
343
|
if (stat.isSymbolicLink() || !stat.isFile()) return false;
|
|
133
|
-
const [realRoot, realFile] = await Promise.all([
|
|
344
|
+
const [realRoot, realFile] = await Promise.all([fs2.realpath(root), fs2.realpath(absolute)]);
|
|
134
345
|
return isPathInside(realRoot, realFile);
|
|
135
346
|
} catch {
|
|
136
347
|
return false;
|
|
137
348
|
}
|
|
138
349
|
}
|
|
139
350
|
async function readFileInsideRoot(root, candidate) {
|
|
140
|
-
const absolute =
|
|
351
|
+
const absolute = path2.resolve(candidate);
|
|
141
352
|
if (!await isSafeRegularFile(root, absolute)) {
|
|
142
353
|
throw new CodeMapError(
|
|
143
354
|
"PATH_OUTSIDE_ROOT",
|
|
144
355
|
`Refusing to read an unavailable, symbolic, or out-of-root file at ${absolute}.`
|
|
145
356
|
);
|
|
146
357
|
}
|
|
147
|
-
return
|
|
358
|
+
return fs2.readFile(absolute);
|
|
148
359
|
}
|
|
149
360
|
function isRelativeInside(relative) {
|
|
150
361
|
const normalized = toPosixPath(relative);
|
|
@@ -153,13 +364,13 @@ function isRelativeInside(relative) {
|
|
|
153
364
|
return normalized !== ".." && !normalized.startsWith("../") && !normalized.startsWith("..\\");
|
|
154
365
|
}
|
|
155
366
|
function redactAbsolutePathsInText(message, root) {
|
|
156
|
-
const normalizedRoot =
|
|
367
|
+
const normalizedRoot = path2.resolve(root);
|
|
157
368
|
const redact = (candidate) => {
|
|
158
369
|
const trimmed = candidate.replace(/[)\].,;:]+$/, "");
|
|
159
370
|
if (!isAbsolutePath(trimmed) && !isFileSystemUri(trimmed)) return candidate;
|
|
160
371
|
if (isFileSystemUri(trimmed)) return "<outside-root>";
|
|
161
|
-
const foreignWindowsPath =
|
|
162
|
-
if (foreignWindowsPath &&
|
|
372
|
+
const foreignWindowsPath = path2.win32.isAbsolute(trimmed) && !path2.posix.isAbsolute(trimmed);
|
|
373
|
+
if (foreignWindowsPath && path2.posix.isAbsolute(normalizedRoot)) return "<outside-root>";
|
|
163
374
|
try {
|
|
164
375
|
if (isPathInside(normalizedRoot, trimmed)) {
|
|
165
376
|
return relativePosix(normalizedRoot, trimmed);
|
|
@@ -188,95 +399,481 @@ function redactAbsolutePathsInText(message, root) {
|
|
|
188
399
|
).join("");
|
|
189
400
|
}
|
|
190
401
|
function normalizeStoredPath(value) {
|
|
191
|
-
const normalized =
|
|
402
|
+
const normalized = path2.posix.normalize(toPosixPath(value));
|
|
192
403
|
return normalized.startsWith("./") ? normalized.slice(2) : normalized;
|
|
193
404
|
}
|
|
194
405
|
function sanitizeModuleSpecifier(value) {
|
|
195
406
|
return isAbsolutePath(value) || isFileSystemUri(value) || hasEmbeddedAbsolutePath(value) ? "<absolute-import>" : value;
|
|
196
407
|
}
|
|
197
408
|
|
|
198
|
-
// src/utils/
|
|
199
|
-
import {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
409
|
+
// src/utils/hash.ts
|
|
410
|
+
import { createHash } from "crypto";
|
|
411
|
+
function hashText(value) {
|
|
412
|
+
return createHash("sha256").update(value).digest("hex");
|
|
203
413
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
414
|
+
|
|
415
|
+
// src/context.ts
|
|
416
|
+
var behavior = /* @__PURE__ */ new Set(["function", "method", "react-hook", "react-component", "class"]);
|
|
417
|
+
function budget(value, min, max, name) {
|
|
418
|
+
if (!Number.isInteger(value) || value < min || value > max)
|
|
419
|
+
throw new RangeError(`${name} must be an integer between ${min} and ${max}`);
|
|
420
|
+
return value;
|
|
421
|
+
}
|
|
422
|
+
async function buildSourceContext(map, query, options) {
|
|
423
|
+
const maxLines = budget(options.maxLines ?? 800, 1, 2e3, "context maxLines");
|
|
424
|
+
const maxChars = budget(options.maxChars ?? 32e3, 256, 64e3, "context maxChars");
|
|
425
|
+
const seeds = budget(options.seeds ?? 3, 1, 5, "context seeds");
|
|
426
|
+
const files = /* @__PURE__ */ new Map();
|
|
427
|
+
const indexedFiles = new Map(map.files.map((f) => [f.path, f]));
|
|
428
|
+
const sourceFile = async (symbol) => {
|
|
429
|
+
let file = files.get(symbol.file);
|
|
430
|
+
if (!file) {
|
|
431
|
+
const indexed = indexedFiles.get(symbol.file);
|
|
432
|
+
if (!indexed) throw new CodeMapError("SOURCE_NOT_FOUND", `No indexed file for ${symbol.id}`);
|
|
433
|
+
const bytes = await readFileInsideRoot(options.root, path3.resolve(options.root, symbol.file));
|
|
434
|
+
if (hashText(bytes) !== indexed.hash)
|
|
435
|
+
throw new CodeMapError(
|
|
436
|
+
"STALE_SOURCE",
|
|
437
|
+
`Source ${symbol.file} changed. Run ai-code-map scan.`
|
|
438
|
+
);
|
|
439
|
+
const lines = bytes.toString("utf8").split(/\r\n|\n|\r/);
|
|
440
|
+
if (lines.at(-1) === "") lines.pop();
|
|
441
|
+
file = { lines, included: /* @__PURE__ */ new Set() };
|
|
442
|
+
files.set(symbol.file, file);
|
|
443
|
+
}
|
|
444
|
+
validateRange(symbol, file.lines.length);
|
|
445
|
+
return file;
|
|
446
|
+
};
|
|
447
|
+
const found = findSymbols(map, query, { limit: 50 });
|
|
448
|
+
const exact = found.matches.filter((s) => s.id === query.trim() || s.name === query.trim());
|
|
449
|
+
let ranked = exact.length ? exact : [...found.matches].sort(
|
|
450
|
+
(a, b) => (a.unmatchedTerms?.length ?? 0) - (b.unmatchedTerms?.length ?? 0) || Number(!behavior.has(a.kind)) - Number(!behavior.has(b.kind))
|
|
451
|
+
);
|
|
452
|
+
const missing = ranked[0]?.unmatchedTerms?.length ?? 0;
|
|
453
|
+
if (!exact.length && missing > 0) {
|
|
454
|
+
let best = 0;
|
|
455
|
+
let promoted = [];
|
|
456
|
+
for (const match of ranked) {
|
|
457
|
+
if (!behavior.has(match.kind) || match.kind === "class" || match.unmatchedTerms?.length !== missing)
|
|
458
|
+
continue;
|
|
459
|
+
const file = await sourceFile(match);
|
|
460
|
+
const body = file.lines.slice(match.range.startLine - 1, match.range.endLine).join("\n").toLowerCase();
|
|
461
|
+
let score = 0;
|
|
462
|
+
for (const term of match.unmatchedTerms) {
|
|
463
|
+
let hits = 0;
|
|
464
|
+
for (let at = body.indexOf(term); at >= 0 && hits < 3; at = body.indexOf(term, at + term.length))
|
|
465
|
+
hits++;
|
|
466
|
+
if (hits) score += 10 + hits;
|
|
467
|
+
}
|
|
468
|
+
if (score > best) {
|
|
469
|
+
best = score;
|
|
470
|
+
promoted = [];
|
|
471
|
+
}
|
|
472
|
+
if (score === best) promoted.push(match);
|
|
473
|
+
}
|
|
474
|
+
if (best > 0) ranked = promoted;
|
|
207
475
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
476
|
+
const matches = ranked.slice(0, seeds);
|
|
477
|
+
const result = {
|
|
478
|
+
query,
|
|
479
|
+
matches,
|
|
480
|
+
moreMatches: found.total - matches.length,
|
|
481
|
+
selected: [],
|
|
482
|
+
snippets: [],
|
|
483
|
+
omitted: [],
|
|
484
|
+
fileReferences: [],
|
|
485
|
+
tests: [],
|
|
486
|
+
omittedNavigation: 0,
|
|
487
|
+
usedLines: 0,
|
|
488
|
+
usedChars: 0,
|
|
489
|
+
limits: { maxLines, maxChars, seeds }
|
|
490
|
+
};
|
|
491
|
+
const byId = new Map(map.symbols.map((s) => [s.id, s]));
|
|
492
|
+
const selected = new Map(matches.map((s) => [s.id, "match"]));
|
|
493
|
+
const outgoingNeighbors = /* @__PURE__ */ new Set();
|
|
494
|
+
const tests = /* @__PURE__ */ new Map();
|
|
495
|
+
for (const match of matches) {
|
|
496
|
+
const nav = buildReadNavigation(map, match.id);
|
|
497
|
+
result.omittedNavigation += nav.omittedLinks + nav.omittedTests;
|
|
498
|
+
for (const link of nav.links) {
|
|
499
|
+
if (link.direction === "outgoing") outgoingNeighbors.add(link.id);
|
|
500
|
+
if (selected.has(link.id)) continue;
|
|
501
|
+
if (link.type === "references" && byId.get(link.id)?.kind === "class") {
|
|
502
|
+
result.omittedNavigation++;
|
|
503
|
+
continue;
|
|
215
504
|
}
|
|
505
|
+
selected.set(link.id, "neighbor");
|
|
216
506
|
}
|
|
217
|
-
|
|
507
|
+
for (const site of nav.tests) tests.set(`${site.file}:${site.line}`, site);
|
|
218
508
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
509
|
+
const testSites = [...tests.values()].sort(
|
|
510
|
+
(a, b) => compareText(a.file, b.file) || a.line - b.line
|
|
511
|
+
);
|
|
512
|
+
result.tests = testSites.slice(0, 8);
|
|
513
|
+
result.omittedNavigation += Math.max(0, testSites.length - result.tests.length);
|
|
514
|
+
const roots = new Set(matches.map((match) => match.id));
|
|
515
|
+
const filesById = new Map(map.files.map((file) => [file.id, file]));
|
|
516
|
+
const references = /* @__PURE__ */ new Map();
|
|
517
|
+
for (const relation2 of map.relations) {
|
|
518
|
+
if (!roots.has(relation2.to) || !["calls", "references"].includes(relation2.type)) continue;
|
|
519
|
+
const origin = relation2.origin;
|
|
520
|
+
if (!["exact", "framework-derived"].includes(origin.classification)) continue;
|
|
521
|
+
const file = filesById.get(relation2.from);
|
|
522
|
+
if (!file || file.path !== origin.file || /(?:^|\/)__tests__\/|\.(?:test|spec)\.[^/]+$/.test(file.path))
|
|
523
|
+
continue;
|
|
524
|
+
references.set(`${file.path}:${origin.line}`, { file: file.path, line: origin.line });
|
|
224
525
|
}
|
|
225
|
-
|
|
226
|
-
|
|
526
|
+
const fileSites = [...references.values()].sort(
|
|
527
|
+
(a, b) => compareText(a.file, b.file) || a.line - b.line
|
|
528
|
+
);
|
|
529
|
+
result.fileReferences = fileSites.slice(0, 4);
|
|
530
|
+
result.omittedNavigation += Math.max(0, fileSites.length - result.fileReferences.length);
|
|
531
|
+
const requested = [];
|
|
532
|
+
const includeWindow = (id, name, file, first, last) => {
|
|
533
|
+
requested.push({ id, file: name, startLine: first, endLine: last });
|
|
534
|
+
for (let line = first; line <= last; line++) {
|
|
535
|
+
if (file.included.has(line)) continue;
|
|
536
|
+
const chars = (file.lines[line - 1]?.length ?? 0) + 1;
|
|
537
|
+
if (result.usedLines >= maxLines || result.usedChars + chars > maxChars) break;
|
|
538
|
+
file.included.add(line);
|
|
539
|
+
result.usedLines++;
|
|
540
|
+
result.usedChars += chars;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
for (const [id] of selected) {
|
|
544
|
+
const symbol = byId.get(id);
|
|
545
|
+
if (!symbol) throw new CodeMapError("SYMBOL_NOT_FOUND", `Missing context symbol: ${id}`);
|
|
546
|
+
const file = await sourceFile(symbol);
|
|
547
|
+
includeWindow(
|
|
548
|
+
id,
|
|
549
|
+
symbol.file,
|
|
550
|
+
file,
|
|
551
|
+
Math.max(1, symbol.range.startLine - 2),
|
|
552
|
+
Math.min(file.lines.length, symbol.range.endLine + 2)
|
|
553
|
+
);
|
|
227
554
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
555
|
+
for (const site of result.fileReferences) {
|
|
556
|
+
const indexed = indexedFiles.get(site.file);
|
|
557
|
+
if (!indexed) throw new CodeMapError("SOURCE_NOT_FOUND", `No indexed file for ${site.file}`);
|
|
558
|
+
const file = await sourceFile({
|
|
559
|
+
id: indexed.id,
|
|
560
|
+
file: site.file,
|
|
561
|
+
range: { startLine: site.line, endLine: site.line, startColumn: 1, endColumn: 1 }
|
|
562
|
+
});
|
|
563
|
+
const small2 = file.lines.length <= 80;
|
|
564
|
+
includeWindow(
|
|
565
|
+
indexed.id,
|
|
566
|
+
site.file,
|
|
567
|
+
file,
|
|
568
|
+
small2 ? 1 : Math.max(1, site.line - 8),
|
|
569
|
+
small2 ? file.lines.length : Math.min(file.lines.length, site.line + 8)
|
|
570
|
+
);
|
|
243
571
|
}
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
572
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
573
|
+
for (const relation2 of map.relations) {
|
|
574
|
+
if (!outgoingNeighbors.has(relation2.from) || selected.get(relation2.from) !== "neighbor" || selected.has(relation2.to) || !["calls", "references"].includes(relation2.type) || !["exact", "framework-derived"].includes(relation2.origin.classification))
|
|
575
|
+
continue;
|
|
576
|
+
const symbol = byId.get(relation2.to);
|
|
577
|
+
if (!symbol || symbol.kind === "class" || !behavior.has(symbol.kind) && symbol.kind !== "variable")
|
|
578
|
+
continue;
|
|
579
|
+
const call = relation2.type === "calls";
|
|
580
|
+
if (!dependencies.has(symbol.id) || call) dependencies.set(symbol.id, { symbol, call });
|
|
581
|
+
}
|
|
582
|
+
const small = [...dependencies.values()].filter(
|
|
583
|
+
({ symbol }) => symbol.range.endLine - symbol.range.startLine + 1 <= 80
|
|
248
584
|
);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
585
|
+
small.sort((a, b) => Number(b.call) - Number(a.call) || compareText(a.symbol.id, b.symbol.id));
|
|
586
|
+
result.omittedNavigation += dependencies.size - Math.min(small.length, 4);
|
|
587
|
+
for (const { symbol } of small.slice(0, 4)) {
|
|
588
|
+
const file = await sourceFile(symbol);
|
|
589
|
+
selected.set(symbol.id, "neighbor");
|
|
590
|
+
includeWindow(
|
|
591
|
+
symbol.id,
|
|
592
|
+
symbol.file,
|
|
593
|
+
file,
|
|
594
|
+
Math.max(1, symbol.range.startLine - 2),
|
|
595
|
+
Math.min(file.lines.length, symbol.range.endLine + 2)
|
|
596
|
+
);
|
|
255
597
|
}
|
|
256
|
-
|
|
598
|
+
result.selected = [...selected].map(([id, role]) => ({ id, role }));
|
|
599
|
+
for (const request of requested) {
|
|
600
|
+
const file = files.get(request.file);
|
|
601
|
+
if (!file) continue;
|
|
602
|
+
const last = request.endLine;
|
|
603
|
+
let missingStart;
|
|
604
|
+
for (let line = request.startLine; line <= last + 1; line++) {
|
|
605
|
+
const missing2 = line <= last && !file.included.has(line);
|
|
606
|
+
if (missing2 && missingStart === void 0) missingStart = line;
|
|
607
|
+
if (!missing2 && missingStart !== void 0) {
|
|
608
|
+
result.omitted.push({ ...request, startLine: missingStart, endLine: line - 1 });
|
|
609
|
+
missingStart = void 0;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
for (const [name, file] of files) {
|
|
614
|
+
const lines = [...file.included].sort((a, b) => a - b);
|
|
615
|
+
let start, end = 0;
|
|
616
|
+
const append = () => {
|
|
617
|
+
if (start === void 0) return;
|
|
618
|
+
result.snippets.push({
|
|
619
|
+
file: name,
|
|
620
|
+
startLine: start,
|
|
621
|
+
endLine: end,
|
|
622
|
+
source: file.lines.slice(start - 1, end).join("\n")
|
|
623
|
+
});
|
|
624
|
+
};
|
|
625
|
+
for (const line of lines) {
|
|
626
|
+
if (start === void 0 || line !== end + 1) {
|
|
627
|
+
append();
|
|
628
|
+
start = line;
|
|
629
|
+
}
|
|
630
|
+
end = line;
|
|
631
|
+
}
|
|
632
|
+
append();
|
|
633
|
+
}
|
|
634
|
+
return result;
|
|
257
635
|
}
|
|
258
|
-
function
|
|
259
|
-
|
|
636
|
+
function validateRange(symbol, length) {
|
|
637
|
+
const { startLine, endLine } = symbol.range;
|
|
638
|
+
if (!Number.isInteger(startLine) || !Number.isInteger(endLine) || startLine < 1 || endLine < startLine || endLine > length)
|
|
639
|
+
throw new CodeMapError(
|
|
640
|
+
"INVALID_SOURCE_RANGE",
|
|
641
|
+
`Mapped range is outside ${symbol.file}. Run ai-code-map scan.`
|
|
642
|
+
);
|
|
260
643
|
}
|
|
261
|
-
function
|
|
262
|
-
|
|
644
|
+
function formatSourceContext(result) {
|
|
645
|
+
if (!result.matches.length)
|
|
646
|
+
return "No indexed matches. Include action and subject words, or use find/targeted rg. No source was read.";
|
|
647
|
+
const lines = [
|
|
648
|
+
`Context: ${result.selected.length} selected symbols, ${result.usedLines}/${result.limits.maxLines} source lines; ${result.usedChars}/${result.limits.maxChars} source characters.`,
|
|
649
|
+
`Matches: ${result.matches.map((m) => `${m.name}${m.unmatchedTerms?.length ? ` (unmatched: ${m.unmatchedTerms.join(", ")})` : ""}`).join(", ")}.`,
|
|
650
|
+
"Scope: matches, direct exact/framework-derived neighbors, file-level reference sites and up to four small outgoing dependencies; reference-only classes are not expanded. Source determines behavior."
|
|
651
|
+
];
|
|
652
|
+
if (result.fileReferences.length)
|
|
653
|
+
lines.push(
|
|
654
|
+
`File reference sites: ${result.fileReferences.map((site) => `${site.file}:${site.line}`).join(", ")}.`
|
|
655
|
+
);
|
|
656
|
+
for (const snippet of result.snippets) {
|
|
657
|
+
lines.push(`
|
|
658
|
+
${snippet.file}:${snippet.startLine}-${snippet.endLine}`);
|
|
659
|
+
lines.push(...snippet.source.split("\n").map((line, i) => `${snippet.startLine + i}: ${line}`));
|
|
660
|
+
}
|
|
661
|
+
if (result.tests.length)
|
|
662
|
+
lines.push(
|
|
663
|
+
"\nTest call sites (navigation hints, not coverage):",
|
|
664
|
+
...result.tests.map((s) => `${s.file}:${s.line}`)
|
|
665
|
+
);
|
|
666
|
+
if (result.omitted.length)
|
|
667
|
+
lines.push(
|
|
668
|
+
"\nBudget omitted source; read only a needed missing range:",
|
|
669
|
+
...result.omitted.slice(0, 12).map((o) => `${o.file}:${o.startLine}-${o.endLine} (${o.id})`),
|
|
670
|
+
...result.omitted.length > 12 ? [`${result.omitted.length - 12} further omitted ranges; use --json for all.`] : []
|
|
671
|
+
);
|
|
672
|
+
lines.push(
|
|
673
|
+
`
|
|
674
|
+
Other search matches: ${result.moreMatches}; navigation entries omitted: ${result.omittedNavigation}. Follow up only for a missing fact; reuse source already returned.`
|
|
675
|
+
);
|
|
676
|
+
return lines.join("\n");
|
|
263
677
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
678
|
+
|
|
679
|
+
// src/read.ts
|
|
680
|
+
import path4 from "path";
|
|
681
|
+
async function readSymbolSource(map, id, options) {
|
|
682
|
+
const limit = options.limit ?? 80;
|
|
683
|
+
const offset = options.offset ?? 0;
|
|
684
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200)
|
|
685
|
+
throw new RangeError("read limit must be an integer between 1 and 200");
|
|
686
|
+
if (!Number.isSafeInteger(offset) || offset < 0)
|
|
687
|
+
throw new RangeError("read offset must be a nonnegative safe integer");
|
|
688
|
+
const symbol = map.symbols.find((item) => item.id === id);
|
|
689
|
+
if (!symbol)
|
|
690
|
+
throw new CodeMapError(
|
|
691
|
+
"SYMBOL_NOT_FOUND",
|
|
692
|
+
"Use an exact symbol ID returned by ai-code-map find."
|
|
693
|
+
);
|
|
694
|
+
const file = map.files.find((item) => item.path === symbol.file);
|
|
695
|
+
const absolute = path4.resolve(options.root, symbol.file);
|
|
696
|
+
if (!file)
|
|
697
|
+
throw new CodeMapError("SOURCE_NOT_FOUND", "Symbol has no indexed file. Run ai-code-map scan.");
|
|
698
|
+
const bytes = await readFileInsideRoot(options.root, absolute);
|
|
699
|
+
if (hashText(bytes) !== file.hash)
|
|
700
|
+
throw new CodeMapError(
|
|
701
|
+
"STALE_SOURCE",
|
|
702
|
+
`Source ${symbol.file} changed. Run ai-code-map scan before reading its mapped range.`
|
|
703
|
+
);
|
|
704
|
+
const lines = bytes.toString("utf8").split(/\r\n|\n|\r/);
|
|
705
|
+
if (lines.at(-1) === "") lines.pop();
|
|
706
|
+
const first = Math.max(1, symbol.range.startLine - 2);
|
|
707
|
+
const last = Math.min(lines.length, symbol.range.endLine + 2);
|
|
708
|
+
if (symbol.range.startLine < 1 || symbol.range.endLine > lines.length || first > last)
|
|
709
|
+
throw new CodeMapError(
|
|
710
|
+
"INVALID_SOURCE_RANGE",
|
|
711
|
+
"Mapped range is outside the source. Run ai-code-map scan."
|
|
712
|
+
);
|
|
713
|
+
if (offset >= last - first + 1) throw new RangeError("read offset is outside the symbol window");
|
|
714
|
+
const startLine = first + offset;
|
|
715
|
+
const endLine = Math.min(last, startLine + limit - 1);
|
|
716
|
+
const source = lines.slice(startLine - 1, endLine).join("\n");
|
|
717
|
+
if (source.length > 12e3)
|
|
718
|
+
throw new CodeMapError(
|
|
719
|
+
"SOURCE_WINDOW_TOO_LARGE",
|
|
720
|
+
"Source window exceeds 12000 characters. Reduce --limit; for a single oversized line use targeted source search."
|
|
721
|
+
);
|
|
722
|
+
const truncated = endLine < last;
|
|
723
|
+
return {
|
|
724
|
+
id: symbol.id,
|
|
725
|
+
file: symbol.file,
|
|
726
|
+
symbolRange: symbol.range,
|
|
727
|
+
startLine,
|
|
728
|
+
endLine,
|
|
729
|
+
source,
|
|
730
|
+
truncated,
|
|
731
|
+
...truncated ? { nextOffset: offset + endLine - startLine + 1 } : {},
|
|
732
|
+
...offset === 0 ? { navigation: buildReadNavigation(map, id) } : {}
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/project-map.ts
|
|
737
|
+
function buildProjectMap(map) {
|
|
738
|
+
const refs = /* @__PURE__ */ new Map();
|
|
739
|
+
const ref = (id) => {
|
|
740
|
+
const value = refs.get(id);
|
|
741
|
+
if (!value) throw new Error(`Unknown map endpoint: ${id}`);
|
|
742
|
+
return value;
|
|
743
|
+
};
|
|
744
|
+
const files = [...map.files].sort((a, b) => compareText(a.path, b.path)).map((file, index) => {
|
|
745
|
+
const key = `f${index + 1}`;
|
|
746
|
+
refs.set(file.id, key);
|
|
747
|
+
return { ref: key, id: file.id, path: file.path };
|
|
274
748
|
});
|
|
275
|
-
|
|
276
|
-
|
|
749
|
+
const symbols = [...map.symbols].sort((a, b) => compareText(a.id, b.id)).map((symbol, index) => {
|
|
750
|
+
const key = `s${index + 1}`;
|
|
751
|
+
refs.set(symbol.id, key);
|
|
752
|
+
return {
|
|
753
|
+
ref: key,
|
|
754
|
+
id: symbol.id,
|
|
755
|
+
name: symbol.name,
|
|
756
|
+
kind: symbol.kind,
|
|
757
|
+
file: ref(`file:${symbol.file}`),
|
|
758
|
+
range: { ...symbol.range }
|
|
759
|
+
};
|
|
760
|
+
});
|
|
761
|
+
const otherIds = /* @__PURE__ */ new Set();
|
|
762
|
+
for (const relation2 of map.relations) {
|
|
763
|
+
if (!refs.has(relation2.from)) otherIds.add(relation2.from);
|
|
764
|
+
if (!refs.has(relation2.to)) otherIds.add(relation2.to);
|
|
765
|
+
}
|
|
766
|
+
const endpoints = [...otherIds].sort(compareText).map((id, index) => {
|
|
767
|
+
const key = `e${index + 1}`;
|
|
768
|
+
refs.set(id, key);
|
|
769
|
+
return { ref: key, id };
|
|
770
|
+
});
|
|
771
|
+
const groups = /* @__PURE__ */ new Map();
|
|
772
|
+
for (const relation2 of map.relations) {
|
|
773
|
+
const from = ref(relation2.from);
|
|
774
|
+
const to = ref(relation2.to);
|
|
775
|
+
const classification = relation2.origin.classification;
|
|
776
|
+
const key = JSON.stringify([from, relation2.type, classification]);
|
|
777
|
+
let group = groups.get(key);
|
|
778
|
+
if (!group) {
|
|
779
|
+
group = { from, type: relation2.type, classification, targets: /* @__PURE__ */ new Map() };
|
|
780
|
+
groups.set(key, group);
|
|
781
|
+
}
|
|
782
|
+
group.targets.set(to, (group.targets.get(to) ?? 0) + 1);
|
|
783
|
+
}
|
|
784
|
+
const relations = [...groups.entries()].sort(([a], [b]) => compareText(a, b)).map(([, group]) => ({
|
|
785
|
+
...group,
|
|
786
|
+
targets: [...group.targets.entries()].sort(([a], [b]) => compareText(a, b)).map(([ref2, count]) => ({ ref: ref2, count }))
|
|
787
|
+
}));
|
|
788
|
+
return {
|
|
789
|
+
project: map.project.name,
|
|
790
|
+
counts: {
|
|
791
|
+
files: files.length,
|
|
792
|
+
symbols: symbols.length,
|
|
793
|
+
endpoints: endpoints.length,
|
|
794
|
+
relationGroups: relations.length,
|
|
795
|
+
relationRecords: map.relations.length
|
|
796
|
+
},
|
|
797
|
+
files,
|
|
798
|
+
symbols,
|
|
799
|
+
endpoints,
|
|
800
|
+
relations
|
|
801
|
+
};
|
|
277
802
|
}
|
|
803
|
+
function formatProjectMap(map) {
|
|
804
|
+
const { counts } = map;
|
|
805
|
+
return [
|
|
806
|
+
`Complete structural map: ${map.project}`,
|
|
807
|
+
`${counts.files} files; ${counts.symbols} symbols; ${counts.endpoints} other endpoints; ${counts.relationRecords} relation records in ${counts.relationGroups} adjacency groups.`,
|
|
808
|
+
"All indexed nodes and relations included. No source, signatures, facts or per-occurrence evidence.",
|
|
809
|
+
"f/s/e refs are local to this output. Read source using a symbol's stable ID, not its short ref.",
|
|
810
|
+
"Relations: FROM TYPE [evidence classification] -> TARGET[*occurrences]. Heuristic/derived links are not exact calls.",
|
|
811
|
+
"",
|
|
812
|
+
"Files (ref path)",
|
|
813
|
+
...map.files.map((file) => `${file.ref} ${file.path}`),
|
|
814
|
+
"",
|
|
815
|
+
"Symbols (ref file:start-end kind stable-ID)",
|
|
816
|
+
...map.symbols.map(
|
|
817
|
+
(symbol) => `${symbol.ref} ${symbol.file}:${symbol.range.startLine}-${symbol.range.endLine} ${symbol.kind} ${symbol.id}`
|
|
818
|
+
),
|
|
819
|
+
"",
|
|
820
|
+
"Other endpoints (ref ID)",
|
|
821
|
+
...map.endpoints.map((endpoint) => `${endpoint.ref} ${endpoint.id}`),
|
|
822
|
+
"",
|
|
823
|
+
"Relations",
|
|
824
|
+
...map.relations.map(
|
|
825
|
+
(group) => `${group.from} ${group.type} [${group.classification}] -> ${group.targets.map((target) => `${target.ref}${target.count > 1 ? `*${target.count}` : ""}`).join(",")}`
|
|
826
|
+
),
|
|
827
|
+
"",
|
|
828
|
+
"End of map. If this marker is missing, the calling tool may have truncated the output."
|
|
829
|
+
].join("\n");
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/constants.ts
|
|
833
|
+
var SCHEMA_VERSION = 4;
|
|
834
|
+
var CONFIG_VERSION = 1;
|
|
835
|
+
var ANALYZER_VERSION = "0.4.0";
|
|
836
|
+
var DEFAULT_SYNTAX_PARSER = "typescript-compiler-syntax";
|
|
837
|
+
var DEFAULT_SEMANTIC_RESOLVER = "typescript-program";
|
|
838
|
+
var DEFAULT_ANALYZER_NAMES = ["typescript-node", "react"];
|
|
839
|
+
var DEFAULT_PRODUCER_ID = "ai-code-map/default-pipeline";
|
|
840
|
+
var CUSTOM_PRODUCER_ID = "ai-code-map/custom-pipeline";
|
|
841
|
+
var PRODUCER_CONTRACT_VERSION = 1;
|
|
842
|
+
var DEFAULT_OUTPUT = ".ai/code-map";
|
|
843
|
+
var DEFAULT_INCLUDE = [
|
|
844
|
+
"src/**/*",
|
|
845
|
+
"app/**/*",
|
|
846
|
+
"pages/**/*",
|
|
847
|
+
"lib/**/*",
|
|
848
|
+
"server/**/*",
|
|
849
|
+
"*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"
|
|
850
|
+
];
|
|
851
|
+
var DEFAULT_EXCLUDE = [
|
|
852
|
+
"**/node_modules/**",
|
|
853
|
+
"**/.next/**",
|
|
854
|
+
"**/dist/**",
|
|
855
|
+
"**/build/**",
|
|
856
|
+
"**/coverage/**",
|
|
857
|
+
"**/.git/**",
|
|
858
|
+
"**/.ai/**",
|
|
859
|
+
"**/*.generated.*"
|
|
860
|
+
];
|
|
861
|
+
var MAP_COMPANION_ARTIFACTS = [
|
|
862
|
+
"overview.md",
|
|
863
|
+
"project.json",
|
|
864
|
+
"files.jsonl",
|
|
865
|
+
"symbols.jsonl",
|
|
866
|
+
"relations.jsonl",
|
|
867
|
+
"diagnostics.json"
|
|
868
|
+
];
|
|
869
|
+
|
|
870
|
+
// src/config.ts
|
|
871
|
+
import { promises as fs3 } from "fs";
|
|
872
|
+
import path6 from "path";
|
|
278
873
|
|
|
279
874
|
// src/schemas.ts
|
|
875
|
+
import path5 from "path";
|
|
876
|
+
import { z } from "zod";
|
|
280
877
|
var JsonValueSchema = z.json();
|
|
281
878
|
var LanguageSchema = z.enum(["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs"]);
|
|
282
879
|
var FrameworkSchema = z.enum(["typescript", "node", "react", "nextjs", "nestjs"]);
|
|
@@ -637,7 +1234,7 @@ var CodeMapSchema = CodeMapShapeSchema.superRefine((map, context) => {
|
|
|
637
1234
|
message: "configured compiler-unit ID must be derived from its config path"
|
|
638
1235
|
});
|
|
639
1236
|
}
|
|
640
|
-
if (unit.root !== normalizeStoredPath(
|
|
1237
|
+
if (unit.root !== normalizeStoredPath(path5.posix.dirname(unit.config))) {
|
|
641
1238
|
context.addIssue({
|
|
642
1239
|
code: "custom",
|
|
643
1240
|
path: ["project", "compilerUnits", index, "root"],
|
|
@@ -966,9 +1563,9 @@ var CodeMapSchema = CodeMapShapeSchema.superRefine((map, context) => {
|
|
|
966
1563
|
}
|
|
967
1564
|
}
|
|
968
1565
|
});
|
|
969
|
-
function addDuplicateIssue(context, seen, value,
|
|
1566
|
+
function addDuplicateIssue(context, seen, value, path18) {
|
|
970
1567
|
if (seen.has(value)) {
|
|
971
|
-
context.addIssue({ code: "custom", path:
|
|
1568
|
+
context.addIssue({ code: "custom", path: path18, message: `duplicate value ${value}` });
|
|
972
1569
|
}
|
|
973
1570
|
seen.add(value);
|
|
974
1571
|
}
|
|
@@ -1006,9 +1603,9 @@ function endpointSourceFile(endpoint, files, symbols) {
|
|
|
1006
1603
|
|
|
1007
1604
|
// src/config.ts
|
|
1008
1605
|
function createDefaultConfig(root, configPath, output) {
|
|
1009
|
-
const configDirectory =
|
|
1010
|
-
const relativeRoot = toPosixPath(
|
|
1011
|
-
const outputValue = output ?
|
|
1606
|
+
const configDirectory = path6.dirname(configPath);
|
|
1607
|
+
const relativeRoot = toPosixPath(path6.relative(configDirectory, root) || ".");
|
|
1608
|
+
const outputValue = output ? path6.isAbsolute(output) ? toPosixPath(path6.relative(root, output)) : toPosixPath(output) : DEFAULT_OUTPUT;
|
|
1012
1609
|
return {
|
|
1013
1610
|
version: CONFIG_VERSION,
|
|
1014
1611
|
root: relativeRoot,
|
|
@@ -1021,36 +1618,36 @@ function createDefaultConfig(root, configPath, output) {
|
|
|
1021
1618
|
};
|
|
1022
1619
|
}
|
|
1023
1620
|
async function findProjectRoot(start = process.cwd()) {
|
|
1024
|
-
let current =
|
|
1621
|
+
let current = path6.resolve(start);
|
|
1025
1622
|
try {
|
|
1026
|
-
if (!(await fs3.stat(current)).isDirectory()) current =
|
|
1623
|
+
if (!(await fs3.stat(current)).isDirectory()) current = path6.dirname(current);
|
|
1027
1624
|
} catch (error) {
|
|
1028
1625
|
throw new CodeMapError("ROOT_NOT_FOUND", `Cannot access project root candidate ${current}.`, {
|
|
1029
1626
|
cause: error
|
|
1030
1627
|
});
|
|
1031
1628
|
}
|
|
1032
|
-
while (current !==
|
|
1033
|
-
const packagePath =
|
|
1629
|
+
while (current !== path6.dirname(current)) {
|
|
1630
|
+
const packagePath = path6.join(current, "package.json");
|
|
1034
1631
|
if (await isSafeRegularFile(current, packagePath)) {
|
|
1035
1632
|
return await fs3.realpath(current);
|
|
1036
1633
|
}
|
|
1037
|
-
const parent =
|
|
1634
|
+
const parent = path6.dirname(current);
|
|
1038
1635
|
current = parent;
|
|
1039
1636
|
}
|
|
1040
1637
|
throw new CodeMapError(
|
|
1041
1638
|
"PROJECT_NOT_FOUND",
|
|
1042
|
-
`No package.json was found at or above ${
|
|
1639
|
+
`No package.json was found at or above ${path6.resolve(start)}. Pass --root explicitly.`
|
|
1043
1640
|
);
|
|
1044
1641
|
}
|
|
1045
1642
|
function resolveConfigArgument(base, config) {
|
|
1046
|
-
if (!config) return
|
|
1047
|
-
return
|
|
1643
|
+
if (!config) return path6.join(base, DEFAULT_OUTPUT, "config.json");
|
|
1644
|
+
return path6.resolve(base, config);
|
|
1048
1645
|
}
|
|
1049
1646
|
async function resolveConfiguration(options = {}) {
|
|
1050
|
-
const requestedStart =
|
|
1647
|
+
const requestedStart = path6.resolve(options.root ?? process.cwd());
|
|
1051
1648
|
const discoveredRoot = await findProjectRoot(requestedStart);
|
|
1052
|
-
const configPath = options.config ? resolveConfigArgument(requestedStart, options.config) :
|
|
1053
|
-
options.output ?
|
|
1649
|
+
const configPath = options.config ? resolveConfigArgument(requestedStart, options.config) : path6.join(
|
|
1650
|
+
options.output ? path6.resolve(discoveredRoot, options.output) : path6.join(discoveredRoot, DEFAULT_OUTPUT),
|
|
1054
1651
|
"config.json"
|
|
1055
1652
|
);
|
|
1056
1653
|
let parsed;
|
|
@@ -1076,7 +1673,7 @@ async function resolveConfiguration(options = {}) {
|
|
|
1076
1673
|
throw error;
|
|
1077
1674
|
}
|
|
1078
1675
|
}
|
|
1079
|
-
const configuredRoot = configExists ?
|
|
1676
|
+
const configuredRoot = configExists ? path6.resolve(path6.dirname(configPath), parsed.root) : discoveredRoot;
|
|
1080
1677
|
let root;
|
|
1081
1678
|
try {
|
|
1082
1679
|
root = await fs3.realpath(configuredRoot);
|
|
@@ -1088,14 +1685,14 @@ async function resolveConfiguration(options = {}) {
|
|
|
1088
1685
|
);
|
|
1089
1686
|
}
|
|
1090
1687
|
const outputValue = options.output ?? parsed.output;
|
|
1091
|
-
const outputDir =
|
|
1688
|
+
const outputDir = path6.isAbsolute(outputValue) ? path6.normalize(outputValue) : path6.resolve(root, outputValue);
|
|
1092
1689
|
return { root, outputDir, configPath, config: parsed, configExists };
|
|
1093
1690
|
}
|
|
1094
1691
|
async function initProject(options = {}) {
|
|
1095
|
-
const requestedStart =
|
|
1692
|
+
const requestedStart = path6.resolve(options.root ?? process.cwd());
|
|
1096
1693
|
const root = await findProjectRoot(requestedStart);
|
|
1097
|
-
const defaultOutputDir = options.output ?
|
|
1098
|
-
const configPath = options.config ?
|
|
1694
|
+
const defaultOutputDir = options.output ? path6.resolve(root, options.output) : path6.join(root, DEFAULT_OUTPUT);
|
|
1695
|
+
const configPath = options.config ? path6.resolve(requestedStart, options.config) : path6.join(defaultOutputDir, "config.json");
|
|
1099
1696
|
try {
|
|
1100
1697
|
await fs3.access(configPath);
|
|
1101
1698
|
if (!options.force) return { root, configPath, created: false };
|
|
@@ -1121,7 +1718,7 @@ import { performance } from "perf_hooks";
|
|
|
1121
1718
|
|
|
1122
1719
|
// src/analyze.ts
|
|
1123
1720
|
import { promises as fs9 } from "fs";
|
|
1124
|
-
import
|
|
1721
|
+
import path17 from "path";
|
|
1125
1722
|
import ts10 from "typescript";
|
|
1126
1723
|
|
|
1127
1724
|
// src/ids.ts
|
|
@@ -1172,15 +1769,9 @@ function symbolPrefix(kind) {
|
|
|
1172
1769
|
}
|
|
1173
1770
|
}
|
|
1174
1771
|
|
|
1175
|
-
// src/utils/hash.ts
|
|
1176
|
-
import { createHash } from "crypto";
|
|
1177
|
-
function hashText(value) {
|
|
1178
|
-
return createHash("sha256").update(value).digest("hex");
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
1772
|
// src/analyzers/core.ts
|
|
1182
1773
|
import { isBuiltin } from "module";
|
|
1183
|
-
import
|
|
1774
|
+
import path7 from "path";
|
|
1184
1775
|
import ts2 from "typescript";
|
|
1185
1776
|
|
|
1186
1777
|
// src/analyzers/helpers.ts
|
|
@@ -1305,8 +1896,8 @@ function makeSymbol(context, node, kind, options = {}) {
|
|
|
1305
1896
|
};
|
|
1306
1897
|
}
|
|
1307
1898
|
function compactText(value, maxLength = 500) {
|
|
1308
|
-
const
|
|
1309
|
-
return
|
|
1899
|
+
const compact2 = value.replace(/\s+/g, " ").trim();
|
|
1900
|
+
return compact2.length > maxLength ? `${compact2.slice(0, maxLength - 1)}\u2026` : compact2;
|
|
1310
1901
|
}
|
|
1311
1902
|
function visit(node, callback) {
|
|
1312
1903
|
callback(node);
|
|
@@ -1617,13 +2208,13 @@ function resolveImportedExpressionTarget(context, expression) {
|
|
|
1617
2208
|
const binding = importedExpressionBinding(context, expression, true);
|
|
1618
2209
|
if (!binding?.resolved) return void 0;
|
|
1619
2210
|
if (ts2.isIdentifier(expression)) {
|
|
1620
|
-
const sourceFile2 = context.program.getSourceFile(
|
|
2211
|
+
const sourceFile2 = context.program.getSourceFile(path7.resolve(context.root, binding.resolved));
|
|
1621
2212
|
if (!sourceFile2) return void 0;
|
|
1622
2213
|
const declaration2 = findExportedDeclaration(sourceFile2, binding.imported);
|
|
1623
2214
|
return declaration2 ? declarationToId(declaration2, binding.resolved) : void 0;
|
|
1624
2215
|
}
|
|
1625
2216
|
if (!ts2.isPropertyAccessExpression(expression)) return void 0;
|
|
1626
|
-
const sourceFile = context.program.getSourceFile(
|
|
2217
|
+
const sourceFile = context.program.getSourceFile(path7.resolve(context.root, binding.resolved));
|
|
1627
2218
|
if (!sourceFile) return void 0;
|
|
1628
2219
|
const declaration = findExportedDeclaration(sourceFile, expression.name.text);
|
|
1629
2220
|
return declaration ? declarationToId(declaration, binding.resolved) : void 0;
|
|
@@ -2569,7 +3160,7 @@ function relativeFromRoot(context, absolutePath) {
|
|
|
2569
3160
|
}
|
|
2570
3161
|
|
|
2571
3162
|
// src/analyzers/nextjs.ts
|
|
2572
|
-
import
|
|
3163
|
+
import path8 from "path";
|
|
2573
3164
|
import ts4 from "typescript";
|
|
2574
3165
|
var ROUTE_GROUP_SEGMENT = /^\(.*\)$/;
|
|
2575
3166
|
var ROUTE_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
@@ -2582,7 +3173,7 @@ var NextJsAnalyzer = class {
|
|
|
2582
3173
|
registeredNextJsAnalyzers.add(this);
|
|
2583
3174
|
}
|
|
2584
3175
|
supports(context) {
|
|
2585
|
-
if (!APP_FILES_EXTENSIONS.has(
|
|
3176
|
+
if (!APP_FILES_EXTENSIONS.has(path8.extname(context.relativePath).toLowerCase())) return false;
|
|
2586
3177
|
return context.relativePath.startsWith("app/") || context.relativePath.startsWith("pages/") || context.relativePath === "middleware.ts";
|
|
2587
3178
|
}
|
|
2588
3179
|
analyze(context) {
|
|
@@ -2829,7 +3420,7 @@ function collectContainingAppLayouts(relativePath, context) {
|
|
|
2829
3420
|
return candidates;
|
|
2830
3421
|
}
|
|
2831
3422
|
function programContainsSourceFile(context, candidateRelative) {
|
|
2832
|
-
const absolute =
|
|
3423
|
+
const absolute = path8.resolve(context.root, candidateRelative);
|
|
2833
3424
|
return context.program.getSourceFiles().some((source) => normalizeSourcePath(source.fileName) === normalizeSourcePath(absolute));
|
|
2834
3425
|
}
|
|
2835
3426
|
function normalizeSourcePath(value) {
|
|
@@ -2867,7 +3458,7 @@ function makeRelation2(from, type, to, context, node, confidence, explanation) {
|
|
|
2867
3458
|
}
|
|
2868
3459
|
|
|
2869
3460
|
// src/analyzers/react.ts
|
|
2870
|
-
import
|
|
3461
|
+
import path9 from "path";
|
|
2871
3462
|
import ts5 from "typescript";
|
|
2872
3463
|
var REACT_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
2873
3464
|
var REACT_BUILTIN_HOOKS = /* @__PURE__ */ new Set([
|
|
@@ -2884,7 +3475,7 @@ var ReactAnalyzer = class {
|
|
|
2884
3475
|
name = "react";
|
|
2885
3476
|
version = ANALYZER_VERSION;
|
|
2886
3477
|
supports(context) {
|
|
2887
|
-
const extension =
|
|
3478
|
+
const extension = path9.extname(context.relativePath).toLowerCase();
|
|
2888
3479
|
if (!REACT_EXTENSIONS.has(extension)) return false;
|
|
2889
3480
|
return hasDirective2(context.sourceFile, "use client") || hasDirective2(context.sourceFile, "use server") || hasReactImport(context.sourceFile) || hasAnyJsx(context.sourceFile);
|
|
2890
3481
|
}
|
|
@@ -3157,11 +3748,11 @@ function collectComponentFacts(context, name, parameters, body, profile) {
|
|
|
3157
3748
|
if (props.length > 0) {
|
|
3158
3749
|
facts.props = `${name.toLowerCase()}props:${props.join(",")}`;
|
|
3159
3750
|
}
|
|
3160
|
-
const
|
|
3161
|
-
if (
|
|
3162
|
-
if (
|
|
3163
|
-
if (
|
|
3164
|
-
if (
|
|
3751
|
+
const behavior2 = collectComponentBehavior(context, body, profile);
|
|
3752
|
+
if (behavior2.state.length > 0) facts.state = behavior2.state.join(",");
|
|
3753
|
+
if (behavior2.memo.length > 0) facts.memo = behavior2.memo.join(",");
|
|
3754
|
+
if (behavior2.effects.length > 0) facts.effects = behavior2.effects.join(",");
|
|
3755
|
+
if (behavior2.customHooks.length > 0) facts.hooks = behavior2.customHooks.join(",");
|
|
3165
3756
|
return facts;
|
|
3166
3757
|
}
|
|
3167
3758
|
function collectProps(context, parameter) {
|
|
@@ -3264,11 +3855,11 @@ function resolveReactCandidateWithChecker(context, candidate, kind) {
|
|
|
3264
3855
|
const sourceFile = declaration.getSourceFile();
|
|
3265
3856
|
let relative;
|
|
3266
3857
|
try {
|
|
3267
|
-
relative =
|
|
3858
|
+
relative = path9.relative(context.root, sourceFile.fileName).split(path9.sep).join("/");
|
|
3268
3859
|
} catch {
|
|
3269
3860
|
return void 0;
|
|
3270
3861
|
}
|
|
3271
|
-
if (relative === "" || relative === ".." || relative.startsWith("../") ||
|
|
3862
|
+
if (relative === "" || relative === ".." || relative.startsWith("../") || path9.isAbsolute(relative) || context.indexedSourcePaths && !context.indexedSourcePaths.has(relative)) {
|
|
3272
3863
|
return void 0;
|
|
3273
3864
|
}
|
|
3274
3865
|
return symbolId(kind, relative, declarationToSymbolName(declaration) ?? symbol.getName());
|
|
@@ -3288,8 +3879,8 @@ function resolveImportedReactSymbol(context, profile, importedName, kind) {
|
|
|
3288
3879
|
if (actualName) return symbolId(kind, relative2, actualName);
|
|
3289
3880
|
return symbolId(kind, relative2, source.imported);
|
|
3290
3881
|
}
|
|
3291
|
-
const relative =
|
|
3292
|
-
|
|
3882
|
+
const relative = path9.posix.normalize(
|
|
3883
|
+
path9.posix.join(path9.posix.dirname(context.relativePath), source.source)
|
|
3293
3884
|
);
|
|
3294
3885
|
const candidates = [
|
|
3295
3886
|
relative,
|
|
@@ -3304,7 +3895,7 @@ function resolveImportedReactSymbol(context, profile, importedName, kind) {
|
|
|
3304
3895
|
`${relative}/index.jsx`
|
|
3305
3896
|
];
|
|
3306
3897
|
for (const candidate of candidates) {
|
|
3307
|
-
const absolute =
|
|
3898
|
+
const absolute = path9.resolve(context.root, candidate);
|
|
3308
3899
|
const imported = context.program.getSourceFile(absolute);
|
|
3309
3900
|
if (imported) {
|
|
3310
3901
|
const declaration = findExportedDeclaration2(imported, source.imported);
|
|
@@ -3468,12 +4059,12 @@ function makeRelation3(from, type, to, context, node, confidence, explanation) {
|
|
|
3468
4059
|
|
|
3469
4060
|
// src/freshness.ts
|
|
3470
4061
|
import { promises as fs8 } from "fs";
|
|
3471
|
-
import
|
|
4062
|
+
import path16 from "path";
|
|
3472
4063
|
import ts8 from "typescript";
|
|
3473
4064
|
|
|
3474
4065
|
// src/compiler-units.ts
|
|
3475
4066
|
import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "fs";
|
|
3476
|
-
import
|
|
4067
|
+
import path10 from "path";
|
|
3477
4068
|
import fg from "fast-glob";
|
|
3478
4069
|
import ts6 from "typescript";
|
|
3479
4070
|
var CONFIG_PATTERNS = ["**/tsconfig.json", "**/jsconfig.json", "**/tsconfig.*.json"];
|
|
@@ -3500,7 +4091,7 @@ async function buildCompilerUnitGraph(root, config, sources, workspaces) {
|
|
|
3500
4091
|
if (!candidate) continue;
|
|
3501
4092
|
const logical = normalizeStoredPath(candidate);
|
|
3502
4093
|
if (parsedByPath.has(logical)) continue;
|
|
3503
|
-
const absolute =
|
|
4094
|
+
const absolute = path10.resolve(realRoot, logical);
|
|
3504
4095
|
const node = parseConfigNode(realRoot, absolute, fileSystem, diagnostics);
|
|
3505
4096
|
if (!node) continue;
|
|
3506
4097
|
parsedByPath.set(node.relativePath, node);
|
|
@@ -3522,11 +4113,11 @@ async function buildCompilerUnitGraph(root, config, sources, workspaces) {
|
|
|
3522
4113
|
const extendedDependencies = new Set([...parsedByPath.values()].flatMap((node) => node.extends));
|
|
3523
4114
|
const referenceTargets = new Set([...parsedByPath.values()].flatMap((node) => node.references));
|
|
3524
4115
|
for (const node of parsedByPath.values()) {
|
|
3525
|
-
const directory = normalizeStoredPath(
|
|
4116
|
+
const directory = normalizeStoredPath(path10.posix.dirname(node.relativePath));
|
|
3526
4117
|
const atBoundary = boundaryPaths.includes(directory);
|
|
3527
4118
|
const intersects = [...node.sourceFileKeys].some((file) => indexedKeys.has(file));
|
|
3528
4119
|
const conventional = ["tsconfig.json", "jsconfig.json"].includes(
|
|
3529
|
-
|
|
4120
|
+
path10.posix.basename(node.relativePath)
|
|
3530
4121
|
);
|
|
3531
4122
|
const extendsOnlyBase = extendedDependencies.has(node.relativePath) && !node.hasOwnFileSelection && !conventional && !referenceTargets.has(node.relativePath) && configured !== node.relativePath;
|
|
3532
4123
|
if (!extendsOnlyBase && (atBoundary || intersects)) seedPaths.add(node.relativePath);
|
|
@@ -3558,10 +4149,10 @@ async function buildCompilerUnitGraph(root, config, sources, workspaces) {
|
|
|
3558
4149
|
if (jsSource && !owner && !jsFallbackWarnings.has(source.relativePath)) {
|
|
3559
4150
|
jsFallbackWarnings.add(source.relativePath);
|
|
3560
4151
|
const underDisallowingConfig = nodes.some(
|
|
3561
|
-
(node) => !node.allowJs && isPathInside(
|
|
4152
|
+
(node) => !node.allowJs && isPathInside(path10.dirname(node.absolutePath), source.absolutePath)
|
|
3562
4153
|
);
|
|
3563
4154
|
const underAllowingConfig = nodes.some(
|
|
3564
|
-
(node) => node.allowJs && isPathInside(
|
|
4155
|
+
(node) => node.allowJs && isPathInside(path10.dirname(node.absolutePath), source.absolutePath)
|
|
3565
4156
|
);
|
|
3566
4157
|
const reason = selected.length > 0 || underDisallowingConfig ? "the applicable configuration does not allow JavaScript" : underAllowingConfig ? "the nearby JavaScript-enabled configuration does not include the file" : "no eligible configuration includes the file";
|
|
3567
4158
|
diagnostics.push({
|
|
@@ -3579,7 +4170,7 @@ async function buildCompilerUnitGraph(root, config, sources, workspaces) {
|
|
|
3579
4170
|
ownedByUnit.set(ownerId, owned);
|
|
3580
4171
|
}
|
|
3581
4172
|
const units = nodes.map((node) => {
|
|
3582
|
-
const rootPath = normalizeStoredPath(
|
|
4173
|
+
const rootPath = normalizeStoredPath(path10.posix.dirname(node.relativePath));
|
|
3583
4174
|
const id = configuredCompilerUnitId(node.relativePath);
|
|
3584
4175
|
return {
|
|
3585
4176
|
record: {
|
|
@@ -3635,11 +4226,11 @@ async function discoverConfigCandidates(root, config, diagnostics) {
|
|
|
3635
4226
|
const configured = configuredTsconfigPath(root, config.tsconfig, diagnostics);
|
|
3636
4227
|
if (configured) candidates.add(configured);
|
|
3637
4228
|
}
|
|
3638
|
-
return [...candidates].filter((candidate) => safeRegularFile(root,
|
|
4229
|
+
return [...candidates].filter((candidate) => safeRegularFile(root, path10.resolve(root, candidate))).sort(compareText);
|
|
3639
4230
|
}
|
|
3640
4231
|
function configuredTsconfigPath(root, configured, diagnostics) {
|
|
3641
4232
|
if (!configured) return void 0;
|
|
3642
|
-
const absolute =
|
|
4233
|
+
const absolute = path10.resolve(root, configured);
|
|
3643
4234
|
if (!isPathInside(root, absolute) || !safeRegularFile(root, absolute)) {
|
|
3644
4235
|
diagnostics.push(
|
|
3645
4236
|
graphDiagnostic("The configured TypeScript project is unavailable or unsafe.")
|
|
@@ -3663,13 +4254,13 @@ function parseConfigNode(root, absolutePath, fileSystem, diagnostics) {
|
|
|
3663
4254
|
);
|
|
3664
4255
|
return void 0;
|
|
3665
4256
|
}
|
|
3666
|
-
const host = fileSystem.parseHost(
|
|
4257
|
+
const host = fileSystem.parseHost(path10.dirname(absolutePath));
|
|
3667
4258
|
let parsed;
|
|
3668
4259
|
try {
|
|
3669
4260
|
parsed = ts6.parseJsonSourceFileConfigFileContent(
|
|
3670
4261
|
source,
|
|
3671
4262
|
host,
|
|
3672
|
-
|
|
4263
|
+
path10.dirname(absolutePath),
|
|
3673
4264
|
void 0,
|
|
3674
4265
|
absolutePath,
|
|
3675
4266
|
void 0,
|
|
@@ -3682,7 +4273,7 @@ function parseConfigNode(root, absolutePath, fileSystem, diagnostics) {
|
|
|
3682
4273
|
);
|
|
3683
4274
|
return void 0;
|
|
3684
4275
|
}
|
|
3685
|
-
const optionSafety = sanitizeCompilerOptions(parsed.options, root,
|
|
4276
|
+
const optionSafety = sanitizeCompilerOptions(parsed.options, root, path10.dirname(absolutePath));
|
|
3686
4277
|
if (!optionSafety.valid) {
|
|
3687
4278
|
diagnostics.push(
|
|
3688
4279
|
graphDiagnostic("A TypeScript configuration option escaped the selected root.", relativePath)
|
|
@@ -3734,7 +4325,7 @@ function parseConfigNode(root, absolutePath, fileSystem, diagnostics) {
|
|
|
3734
4325
|
sourceFileKeys: new Set(parsedSourceFiles.map(canonicalPath)),
|
|
3735
4326
|
references: [...new Set(references)].sort(compareText),
|
|
3736
4327
|
extends: extended,
|
|
3737
|
-
pathAliases: normalizePathAliases(root,
|
|
4328
|
+
pathAliases: normalizePathAliases(root, path10.dirname(absolutePath), parsed.options),
|
|
3738
4329
|
hasOwnFileSelection: Boolean(
|
|
3739
4330
|
rawConfig && (Object.prototype.hasOwnProperty.call(rawConfig, "files") || Object.prototype.hasOwnProperty.call(rawConfig, "include"))
|
|
3740
4331
|
),
|
|
@@ -3746,7 +4337,7 @@ function sanitizeCompilerOptions(options, root, unitDirectory) {
|
|
|
3746
4337
|
let valid = true;
|
|
3747
4338
|
const validatePath = (value) => {
|
|
3748
4339
|
if (!value) return value;
|
|
3749
|
-
const absolute =
|
|
4340
|
+
const absolute = path10.resolve(value);
|
|
3750
4341
|
if (!isPathInside(root, absolute)) {
|
|
3751
4342
|
valid = false;
|
|
3752
4343
|
return void 0;
|
|
@@ -3768,7 +4359,7 @@ function sanitizeCompilerOptions(options, root, unitDirectory) {
|
|
|
3768
4359
|
options.typeRoots = boundedTypeRoots(root, unitDirectory);
|
|
3769
4360
|
}
|
|
3770
4361
|
sanitizeCompilerPathAliases(options);
|
|
3771
|
-
options.configFilePath = options.configFilePath ??
|
|
4362
|
+
options.configFilePath = options.configFilePath ?? path10.join(unitDirectory, "tsconfig.json");
|
|
3772
4363
|
return { valid };
|
|
3773
4364
|
}
|
|
3774
4365
|
function sanitizeCompilerPathAliases(options) {
|
|
@@ -3785,11 +4376,11 @@ function sanitizeCompilerPathAliases(options) {
|
|
|
3785
4376
|
}
|
|
3786
4377
|
function boundedTypeRoots(root, start) {
|
|
3787
4378
|
const values = [];
|
|
3788
|
-
let current =
|
|
4379
|
+
let current = path10.resolve(start);
|
|
3789
4380
|
while (isPathInside(root, current)) {
|
|
3790
|
-
values.push(
|
|
3791
|
-
if (current ===
|
|
3792
|
-
const parent =
|
|
4381
|
+
values.push(path10.join(current, "node_modules", "@types"));
|
|
4382
|
+
if (current === path10.resolve(root)) break;
|
|
4383
|
+
const parent = path10.dirname(current);
|
|
3793
4384
|
if (parent === current) break;
|
|
3794
4385
|
current = parent;
|
|
3795
4386
|
}
|
|
@@ -3804,7 +4395,7 @@ function normalizePathAliases(root, configDirectory, options) {
|
|
|
3804
4395
|
const normalized = [];
|
|
3805
4396
|
for (const target of targets) {
|
|
3806
4397
|
if (!isSafeRelativePathPattern(target)) continue;
|
|
3807
|
-
const absolute =
|
|
4398
|
+
const absolute = path10.resolve(base, target);
|
|
3808
4399
|
if (!isPathInside(root, absolute)) continue;
|
|
3809
4400
|
normalized.push(relativePosix(root, absolute));
|
|
3810
4401
|
}
|
|
@@ -3817,8 +4408,8 @@ function chooseOwner(eligible, sourceFile, root, boundaries) {
|
|
|
3817
4408
|
const packageDepthA = deepestSharedBoundaryDepth(a.absolutePath, sourceFile, root, boundaries);
|
|
3818
4409
|
const packageDepthB = deepestSharedBoundaryDepth(b.absolutePath, sourceFile, root, boundaries);
|
|
3819
4410
|
if (packageDepthA !== packageDepthB) return packageDepthB - packageDepthA;
|
|
3820
|
-
const directoryDepthA = ancestorDepth(
|
|
3821
|
-
const directoryDepthB = ancestorDepth(
|
|
4411
|
+
const directoryDepthA = ancestorDepth(path10.dirname(a.absolutePath), sourceFile, root);
|
|
4412
|
+
const directoryDepthB = ancestorDepth(path10.dirname(b.absolutePath), sourceFile, root);
|
|
3822
4413
|
if (directoryDepthA !== directoryDepthB) return directoryDepthB - directoryDepthA;
|
|
3823
4414
|
return compareText(a.relativePath, b.relativePath);
|
|
3824
4415
|
})[0];
|
|
@@ -3826,7 +4417,7 @@ function chooseOwner(eligible, sourceFile, root, boundaries) {
|
|
|
3826
4417
|
function deepestSharedBoundaryDepth(configPath, sourcePath, root, boundaries) {
|
|
3827
4418
|
let depth = -1;
|
|
3828
4419
|
for (const boundary of boundaries) {
|
|
3829
|
-
const absolute =
|
|
4420
|
+
const absolute = path10.resolve(root, boundary);
|
|
3830
4421
|
if (isPathInside(absolute, configPath) && isPathInside(absolute, sourcePath)) {
|
|
3831
4422
|
depth = Math.max(depth, pathDepth(relativePosix(root, absolute)));
|
|
3832
4423
|
}
|
|
@@ -3840,12 +4431,12 @@ function pathDepth(value) {
|
|
|
3840
4431
|
return value === "." ? 0 : value.split("/").length;
|
|
3841
4432
|
}
|
|
3842
4433
|
function isJavaScriptSourceFile(filePath) {
|
|
3843
|
-
const extension =
|
|
4434
|
+
const extension = path10.extname(filePath).toLowerCase();
|
|
3844
4435
|
return extension === ".js" || extension === ".jsx" || extension === ".mjs" || extension === ".cjs";
|
|
3845
4436
|
}
|
|
3846
4437
|
function safeRegularFile(root, candidate) {
|
|
3847
4438
|
try {
|
|
3848
|
-
const absolute =
|
|
4439
|
+
const absolute = path10.resolve(candidate);
|
|
3849
4440
|
if (!isPathInside(root, absolute)) return false;
|
|
3850
4441
|
if (lstatSync(absolute).isSymbolicLink() || !statSync(absolute).isFile()) return false;
|
|
3851
4442
|
return isPathInside(realpathSync(root), realpathSync(absolute));
|
|
@@ -3858,7 +4449,7 @@ function canonicalPath(fileName) {
|
|
|
3858
4449
|
const real = realpathSync(fileName);
|
|
3859
4450
|
return ts6.sys.useCaseSensitiveFileNames ? real : real.toLowerCase();
|
|
3860
4451
|
} catch {
|
|
3861
|
-
const absolute =
|
|
4452
|
+
const absolute = path10.resolve(fileName);
|
|
3862
4453
|
return ts6.sys.useCaseSensitiveFileNames ? absolute : absolute.toLowerCase();
|
|
3863
4454
|
}
|
|
3864
4455
|
}
|
|
@@ -3885,7 +4476,7 @@ var BoundedTypeScriptFileSystem = class {
|
|
|
3885
4476
|
observedInputs = /* @__PURE__ */ new Map();
|
|
3886
4477
|
constructor(root) {
|
|
3887
4478
|
this.root = realpathSync(root);
|
|
3888
|
-
this.typeScriptLib = realpathSync(
|
|
4479
|
+
this.typeScriptLib = realpathSync(path10.dirname(ts6.getDefaultLibFilePath({})));
|
|
3889
4480
|
}
|
|
3890
4481
|
fileExists = (fileName) => {
|
|
3891
4482
|
const absolute = this.absolute(fileName);
|
|
@@ -3933,14 +4524,14 @@ var BoundedTypeScriptFileSystem = class {
|
|
|
3933
4524
|
const absolute = this.absolute(directory);
|
|
3934
4525
|
if (!this.isPermitted(absolute)) return [];
|
|
3935
4526
|
if (!this.directoryExists(absolute)) return [];
|
|
3936
|
-
const directories = ts6.sys.getDirectories(absolute).filter((entry) => this.isPermitted(
|
|
4527
|
+
const directories = ts6.sys.getDirectories(absolute).filter((entry) => this.isPermitted(path10.resolve(absolute, entry))).sort(compareText);
|
|
3937
4528
|
this.recordDirectoryListing(absolute, directories);
|
|
3938
4529
|
return directories;
|
|
3939
4530
|
};
|
|
3940
4531
|
readDirectory = (rootDir, extensions, excludes, includes, depth) => {
|
|
3941
4532
|
const absolute = this.absolute(rootDir);
|
|
3942
4533
|
if (!this.isPermitted(absolute)) return [];
|
|
3943
|
-
return ts6.sys.readDirectory(absolute, extensions, excludes, includes, depth).filter((entry) => this.isPermitted(
|
|
4534
|
+
return ts6.sys.readDirectory(absolute, extensions, excludes, includes, depth).filter((entry) => this.isPermitted(path10.resolve(absolute, entry))).sort(compareText);
|
|
3944
4535
|
};
|
|
3945
4536
|
realpath = (candidate) => {
|
|
3946
4537
|
const absolute = this.absolute(candidate);
|
|
@@ -4069,11 +4660,11 @@ var BoundedTypeScriptFileSystem = class {
|
|
|
4069
4660
|
}
|
|
4070
4661
|
}
|
|
4071
4662
|
absolute(candidate) {
|
|
4072
|
-
return
|
|
4663
|
+
return path10.isAbsolute(candidate) ? path10.resolve(candidate) : path10.resolve(this.root, candidate);
|
|
4073
4664
|
}
|
|
4074
4665
|
};
|
|
4075
4666
|
function semanticDirectoryListingHash(directory, entries) {
|
|
4076
|
-
const normalized = entries.map((entry) => normalizeStoredPath(
|
|
4667
|
+
const normalized = entries.map((entry) => normalizeStoredPath(path10.relative(directory, path10.resolve(directory, entry)))).sort(compareText);
|
|
4077
4668
|
return hashText(Buffer.from(normalized.join("\0"), "utf8"));
|
|
4078
4669
|
}
|
|
4079
4670
|
function semanticObservationKey(observation) {
|
|
@@ -4090,14 +4681,14 @@ function fallbackCompilerOptions(root) {
|
|
|
4090
4681
|
skipLibCheck: true,
|
|
4091
4682
|
jsx: ts6.JsxEmit.Preserve,
|
|
4092
4683
|
experimentalDecorators: true,
|
|
4093
|
-
configFilePath:
|
|
4684
|
+
configFilePath: path10.join(root, "tsconfig.fallback.json"),
|
|
4094
4685
|
typeRoots: boundedTypeRoots(root, root)
|
|
4095
4686
|
};
|
|
4096
4687
|
}
|
|
4097
4688
|
|
|
4098
4689
|
// src/discovery.ts
|
|
4099
4690
|
import { promises as fs6 } from "fs";
|
|
4100
|
-
import
|
|
4691
|
+
import path13 from "path";
|
|
4101
4692
|
import fg3 from "fast-glob";
|
|
4102
4693
|
|
|
4103
4694
|
// src/package-metadata.ts
|
|
@@ -4132,7 +4723,7 @@ function isSafeStoredPath(value) {
|
|
|
4132
4723
|
|
|
4133
4724
|
// src/workspaces.ts
|
|
4134
4725
|
import { promises as fs4 } from "fs";
|
|
4135
|
-
import
|
|
4726
|
+
import path11 from "path";
|
|
4136
4727
|
import { getPackages } from "@manypkg/get-packages";
|
|
4137
4728
|
var ManypkgWorkspaceDiscovery = class {
|
|
4138
4729
|
name = "manypkg";
|
|
@@ -4151,14 +4742,14 @@ var ManypkgWorkspaceDiscovery = class {
|
|
|
4151
4742
|
for (const workspace of discovered.packages) {
|
|
4152
4743
|
const realDirectory = await fs4.realpath(workspace.dir);
|
|
4153
4744
|
if (realDirectory === selectedRoot || !isPathInside(selectedRoot, realDirectory)) continue;
|
|
4154
|
-
const manifestPath =
|
|
4745
|
+
const manifestPath = path11.join(realDirectory, "package.json");
|
|
4155
4746
|
if (!await isSafeRegularFile(selectedRoot, manifestPath)) continue;
|
|
4156
4747
|
const manifest = JSON.parse(
|
|
4157
4748
|
(await readFileInsideRoot(selectedRoot, manifestPath)).toString("utf8")
|
|
4158
4749
|
);
|
|
4159
4750
|
packages.push({
|
|
4160
|
-
name: typeof manifest.name === "string" ? manifest.name :
|
|
4161
|
-
path: normalizeStoredPath(
|
|
4751
|
+
name: typeof manifest.name === "string" ? manifest.name : path11.basename(realDirectory),
|
|
4752
|
+
path: normalizeStoredPath(path11.relative(selectedRoot, realDirectory)),
|
|
4162
4753
|
entryPoints: packageEntryPoints(manifest)
|
|
4163
4754
|
});
|
|
4164
4755
|
}
|
|
@@ -4174,11 +4765,11 @@ function createWorkspaceDiscovery() {
|
|
|
4174
4765
|
}
|
|
4175
4766
|
async function isWorkspaceRoot(root) {
|
|
4176
4767
|
for (const marker of ["pnpm-workspace.yaml", "pnpm-workspace.yml", "lerna.json", "rush.json"]) {
|
|
4177
|
-
if (await isSafeRegularFile(root,
|
|
4768
|
+
if (await isSafeRegularFile(root, path11.join(root, marker))) return true;
|
|
4178
4769
|
}
|
|
4179
4770
|
try {
|
|
4180
4771
|
const packageJson = JSON.parse(
|
|
4181
|
-
(await readFileInsideRoot(root,
|
|
4772
|
+
(await readFileInsideRoot(root, path11.join(root, "package.json"))).toString("utf8")
|
|
4182
4773
|
);
|
|
4183
4774
|
return Array.isArray(packageJson.workspaces) || Boolean(
|
|
4184
4775
|
packageJson.workspaces && typeof packageJson.workspaces === "object" && "packages" in packageJson.workspaces
|
|
@@ -4190,7 +4781,7 @@ async function isWorkspaceRoot(root) {
|
|
|
4190
4781
|
|
|
4191
4782
|
// src/ignore.ts
|
|
4192
4783
|
import { promises as fs5 } from "fs";
|
|
4193
|
-
import
|
|
4784
|
+
import path12 from "path";
|
|
4194
4785
|
import fg2 from "fast-glob";
|
|
4195
4786
|
import createIgnore from "ignore";
|
|
4196
4787
|
var GitIgnoreAdapter = class {
|
|
@@ -4215,9 +4806,9 @@ async function loadIgnoreRules(root) {
|
|
|
4215
4806
|
const rules = [];
|
|
4216
4807
|
for (const relative of ignoreFiles.sort(compareIgnoreFiles)) {
|
|
4217
4808
|
const normalizedRelative = normalizeStoredPath(relative);
|
|
4218
|
-
const directory = normalizeStoredPath(
|
|
4809
|
+
const directory = normalizeStoredPath(path12.posix.dirname(normalizedRelative));
|
|
4219
4810
|
if (directory !== "." && isIgnoredDirectory(directory, rules)) continue;
|
|
4220
|
-
const absolute =
|
|
4811
|
+
const absolute = path12.resolve(root, relative);
|
|
4221
4812
|
const stat = await fs5.lstat(absolute);
|
|
4222
4813
|
if (stat.isSymbolicLink() || !stat.isFile()) continue;
|
|
4223
4814
|
const real = await fs5.realpath(absolute);
|
|
@@ -4268,7 +4859,7 @@ function isSafeRelativePath(value) {
|
|
|
4268
4859
|
var SOURCE_EXTENSION = /\.(?:[cm]?[jt]sx?)$/i;
|
|
4269
4860
|
async function readPackageMetadata(root) {
|
|
4270
4861
|
const raw = JSON.parse(
|
|
4271
|
-
(await readFileInsideRoot(root,
|
|
4862
|
+
(await readFileInsideRoot(root, path13.join(root, "package.json"))).toString("utf8")
|
|
4272
4863
|
);
|
|
4273
4864
|
const workspaceDiscovery = await createWorkspaceDiscovery().discover(root);
|
|
4274
4865
|
const dependencies = /* @__PURE__ */ new Set();
|
|
@@ -4277,7 +4868,7 @@ async function readPackageMetadata(root) {
|
|
|
4277
4868
|
try {
|
|
4278
4869
|
manifests.push(
|
|
4279
4870
|
JSON.parse(
|
|
4280
|
-
(await readFileInsideRoot(root,
|
|
4871
|
+
(await readFileInsideRoot(root, path13.join(root, workspace.path, "package.json"))).toString("utf8")
|
|
4281
4872
|
)
|
|
4282
4873
|
);
|
|
4283
4874
|
} catch {
|
|
@@ -4329,7 +4920,7 @@ async function discoverSourceFiles(root, config) {
|
|
|
4329
4920
|
});
|
|
4330
4921
|
const permitted = new Set(await createGitIgnoreAdapter().filter(root, matches));
|
|
4331
4922
|
const candidates = matches.filter((relative) => permitted.has(normalizeStoredPath(relative))).filter((relative) => SOURCE_EXTENSION.test(relative)).map((relative) => ({
|
|
4332
|
-
absolutePath:
|
|
4923
|
+
absolutePath: path13.resolve(root, relative),
|
|
4333
4924
|
relativePath: normalizeStoredPath(relative),
|
|
4334
4925
|
language: languageFromPath(relative)
|
|
4335
4926
|
})).sort((a, b) => compareText(a.relativePath, b.relativePath));
|
|
@@ -4349,12 +4940,12 @@ function prefixGlob(directory, pattern) {
|
|
|
4349
4940
|
return `${normalizeStoredPath(directory)}/${normalized}`;
|
|
4350
4941
|
}
|
|
4351
4942
|
function languageFromPath(filePath) {
|
|
4352
|
-
const extension =
|
|
4943
|
+
const extension = path13.extname(filePath).slice(1).toLowerCase();
|
|
4353
4944
|
return extension;
|
|
4354
4945
|
}
|
|
4355
4946
|
|
|
4356
4947
|
// src/semantic.ts
|
|
4357
|
-
import
|
|
4948
|
+
import path14 from "path";
|
|
4358
4949
|
import ts7 from "typescript";
|
|
4359
4950
|
var TYPESCRIPT_SEMANTIC_VERSION = `compiler-units-v4+typescript-${ts7.version}`;
|
|
4360
4951
|
var TypeScriptSemanticResolver = class {
|
|
@@ -4366,7 +4957,7 @@ var TypeScriptSemanticResolver = class {
|
|
|
4366
4957
|
version = TYPESCRIPT_SEMANTIC_VERSION;
|
|
4367
4958
|
resolve(input) {
|
|
4368
4959
|
const compiler = loadCompilerConfiguration(input.root, input.tsconfig);
|
|
4369
|
-
const currentDirectory = input.tsconfig ?
|
|
4960
|
+
const currentDirectory = input.tsconfig ? path14.dirname(path14.resolve(input.root, input.tsconfig)) : input.root;
|
|
4370
4961
|
const fileSystem = compiler.fileSystem;
|
|
4371
4962
|
const host = createBoundedCompilerHost(
|
|
4372
4963
|
input.root,
|
|
@@ -4401,7 +4992,7 @@ var TypeScriptSemanticResolver = class {
|
|
|
4401
4992
|
const resolvedModule = resolveModule(literal.text, containingFile);
|
|
4402
4993
|
return { resolvedModule };
|
|
4403
4994
|
});
|
|
4404
|
-
const rootNames = [...new Set(input.sourceFiles.map((file) =>
|
|
4995
|
+
const rootNames = [...new Set(input.sourceFiles.map((file) => path14.resolve(file)))].sort(
|
|
4405
4996
|
compareText
|
|
4406
4997
|
);
|
|
4407
4998
|
const program = ts7.createProgram({
|
|
@@ -4437,8 +5028,8 @@ function createProjectReferenceModuleResolver(root, references, fileSystem, pars
|
|
|
4437
5028
|
if (!isPathInside(root, configPath)) continue;
|
|
4438
5029
|
const parsed = parseCommand(root, configPath, fileSystem, parsedCommands);
|
|
4439
5030
|
if (!parsed) continue;
|
|
4440
|
-
const packageRoot =
|
|
4441
|
-
const packageJsonPath =
|
|
5031
|
+
const packageRoot = path14.dirname(configPath);
|
|
5032
|
+
const packageJsonPath = path14.join(packageRoot, "package.json");
|
|
4442
5033
|
const raw = fileSystem.readFile(packageJsonPath);
|
|
4443
5034
|
if (!raw) continue;
|
|
4444
5035
|
try {
|
|
@@ -4468,22 +5059,22 @@ function referencedPackageSource(referenced, subpath, fileSystem) {
|
|
|
4468
5059
|
const { parsed, root: packageRoot, manifest } = referenced;
|
|
4469
5060
|
const rootDir = parsed.options.rootDir ?? packageRoot;
|
|
4470
5061
|
if (!isPathInside(packageRoot, rootDir)) return void 0;
|
|
4471
|
-
const owned = new Set(parsed.fileNames.map((fileName) =>
|
|
5062
|
+
const owned = new Set(parsed.fileNames.map((fileName) => path14.resolve(fileName)));
|
|
4472
5063
|
const requestedTargets = packageEntryTargets(manifest, subpath);
|
|
4473
5064
|
const bases = [];
|
|
4474
5065
|
for (const target of requestedTargets) {
|
|
4475
|
-
const absoluteTarget =
|
|
5066
|
+
const absoluteTarget = path14.resolve(packageRoot, target);
|
|
4476
5067
|
if (!isPathInside(packageRoot, absoluteTarget)) continue;
|
|
4477
5068
|
const outDir = parsed.options.outDir;
|
|
4478
5069
|
if (outDir && isPathInside(outDir, absoluteTarget)) {
|
|
4479
|
-
bases.push(
|
|
5070
|
+
bases.push(path14.resolve(rootDir, path14.relative(outDir, absoluteTarget)));
|
|
4480
5071
|
}
|
|
4481
5072
|
bases.push(absoluteTarget);
|
|
4482
5073
|
}
|
|
4483
|
-
bases.push(
|
|
5074
|
+
bases.push(path14.resolve(rootDir, subpath || "index"));
|
|
4484
5075
|
for (const base of bases) {
|
|
4485
5076
|
for (const candidate of sourceModuleCandidates(base)) {
|
|
4486
|
-
const absolute =
|
|
5077
|
+
const absolute = path14.resolve(candidate);
|
|
4487
5078
|
if (isPathInside(packageRoot, absolute) && owned.has(absolute) && fileSystem.fileExists(absolute)) {
|
|
4488
5079
|
return fileSystem.realpath(absolute);
|
|
4489
5080
|
}
|
|
@@ -4539,7 +5130,7 @@ function sourceModuleCandidates(value) {
|
|
|
4539
5130
|
return [
|
|
4540
5131
|
value,
|
|
4541
5132
|
...extensions.map((extension) => `${stem}${extension}`),
|
|
4542
|
-
...extensions.map((extension) =>
|
|
5133
|
+
...extensions.map((extension) => path14.join(stem, `index${extension}`))
|
|
4543
5134
|
];
|
|
4544
5135
|
}
|
|
4545
5136
|
function normalizeModuleStem(value) {
|
|
@@ -4601,7 +5192,7 @@ function loadCompilerConfiguration(root, tsconfig) {
|
|
|
4601
5192
|
parsedCommands: /* @__PURE__ */ new Map()
|
|
4602
5193
|
};
|
|
4603
5194
|
}
|
|
4604
|
-
const absolute =
|
|
5195
|
+
const absolute = path14.resolve(root, tsconfig);
|
|
4605
5196
|
if (!isPathInside(root, absolute) || !fileSystem.fileExists(absolute)) {
|
|
4606
5197
|
const diagnostic = {
|
|
4607
5198
|
schemaVersion: SCHEMA_VERSION,
|
|
@@ -4644,11 +5235,11 @@ function loadCompilerConfiguration(root, tsconfig) {
|
|
|
4644
5235
|
return isPathInside(root, target) && fileSystem.fileExists(target);
|
|
4645
5236
|
});
|
|
4646
5237
|
const options = parsed.options;
|
|
4647
|
-
anchorCompilerOptions(root,
|
|
5238
|
+
anchorCompilerOptions(root, path14.dirname(absolute), options);
|
|
4648
5239
|
return {
|
|
4649
5240
|
options,
|
|
4650
5241
|
...projectReferences.length > 0 ? { projectReferences } : {},
|
|
4651
|
-
pathAliases: pathAliases(root,
|
|
5242
|
+
pathAliases: pathAliases(root, path14.dirname(absolute), options),
|
|
4652
5243
|
diagnostics: parsed.errors.map((diagnostic) => compilerDiagnostic(diagnostic, root)),
|
|
4653
5244
|
rawDiagnostics: parsed.errors,
|
|
4654
5245
|
fileSystem,
|
|
@@ -4656,7 +5247,7 @@ function loadCompilerConfiguration(root, tsconfig) {
|
|
|
4656
5247
|
};
|
|
4657
5248
|
}
|
|
4658
5249
|
function parseCommand(root, configPath, fileSystem, cache) {
|
|
4659
|
-
const absolute =
|
|
5250
|
+
const absolute = path14.resolve(configPath);
|
|
4660
5251
|
const cached = cache.get(absolute);
|
|
4661
5252
|
if (cached) return cached;
|
|
4662
5253
|
if (!isPathInside(root, absolute) || !fileSystem.fileExists(absolute)) return void 0;
|
|
@@ -4664,8 +5255,8 @@ function parseCommand(root, configPath, fileSystem, cache) {
|
|
|
4664
5255
|
const source = ts7.readJsonConfigFile(absolute, fileSystem.readFile);
|
|
4665
5256
|
const parsed = ts7.parseJsonSourceFileConfigFileContent(
|
|
4666
5257
|
source,
|
|
4667
|
-
fileSystem.parseHost(
|
|
4668
|
-
|
|
5258
|
+
fileSystem.parseHost(path14.dirname(absolute)),
|
|
5259
|
+
path14.dirname(absolute),
|
|
4669
5260
|
void 0,
|
|
4670
5261
|
absolute,
|
|
4671
5262
|
void 0,
|
|
@@ -4676,7 +5267,7 @@ function parseCommand(root, configPath, fileSystem, cache) {
|
|
|
4676
5267
|
const target = ts7.resolveProjectReferencePath(reference);
|
|
4677
5268
|
return isPathInside(root, target) && fileSystem.fileExists(target);
|
|
4678
5269
|
});
|
|
4679
|
-
anchorCompilerOptions(root,
|
|
5270
|
+
anchorCompilerOptions(root, path14.dirname(absolute), parsed.options);
|
|
4680
5271
|
cache.set(absolute, parsed);
|
|
4681
5272
|
return parsed;
|
|
4682
5273
|
} catch {
|
|
@@ -4701,7 +5292,7 @@ function createBoundedCompilerHost(root, currentDirectory, options, fileSystem,
|
|
|
4701
5292
|
return host;
|
|
4702
5293
|
}
|
|
4703
5294
|
function anchorCompilerOptions(root, unitDirectory, options) {
|
|
4704
|
-
options.configFilePath = options.configFilePath ??
|
|
5295
|
+
options.configFilePath = options.configFilePath ?? path14.join(unitDirectory, "tsconfig.json");
|
|
4705
5296
|
if (!options.typeRoots) options.typeRoots = boundedTypeRoots2(root, unitDirectory);
|
|
4706
5297
|
else options.typeRoots = options.typeRoots.filter((entry) => isPathInside(root, entry));
|
|
4707
5298
|
if (options.baseUrl && !isPathInside(root, options.baseUrl)) options.baseUrl = void 0;
|
|
@@ -4711,11 +5302,11 @@ function anchorCompilerOptions(root, unitDirectory, options) {
|
|
|
4711
5302
|
}
|
|
4712
5303
|
function boundedTypeRoots2(root, start) {
|
|
4713
5304
|
const values = [];
|
|
4714
|
-
let current =
|
|
5305
|
+
let current = path14.resolve(start);
|
|
4715
5306
|
while (isPathInside(root, current)) {
|
|
4716
|
-
values.push(
|
|
4717
|
-
if (current ===
|
|
4718
|
-
const parent =
|
|
5307
|
+
values.push(path14.join(current, "node_modules", "@types"));
|
|
5308
|
+
if (current === path14.resolve(root)) break;
|
|
5309
|
+
const parent = path14.dirname(current);
|
|
4719
5310
|
if (parent === current) break;
|
|
4720
5311
|
current = parent;
|
|
4721
5312
|
}
|
|
@@ -4727,7 +5318,7 @@ function pathAliases(root, configDirectory, options) {
|
|
|
4727
5318
|
return Object.fromEntries(
|
|
4728
5319
|
Object.entries(paths).filter(([alias]) => isSafeRelativePathPattern(alias)).sort(([a], [b]) => compareText(a, b)).map(([alias, values]) => [
|
|
4729
5320
|
alias,
|
|
4730
|
-
values.filter(isSafeRelativePathPattern).map((value) =>
|
|
5321
|
+
values.filter(isSafeRelativePathPattern).map((value) => path14.resolve(base, value)).filter((value) => isPathInside(root, value)).map((value) => normalizeStoredPath(relativePosix(root, value))).filter((value, index, entries) => entries.indexOf(value) === index).sort(compareText)
|
|
4731
5322
|
])
|
|
4732
5323
|
);
|
|
4733
5324
|
}
|
|
@@ -4760,7 +5351,7 @@ function sanitizeCompilerMessage(message, root) {
|
|
|
4760
5351
|
|
|
4761
5352
|
// src/storage.ts
|
|
4762
5353
|
import { promises as fs7 } from "fs";
|
|
4763
|
-
import
|
|
5354
|
+
import path15 from "path";
|
|
4764
5355
|
import { z as z2 } from "zod";
|
|
4765
5356
|
async function writeCodeMap(resolved, map) {
|
|
4766
5357
|
const validated = finalizeCodeMap(map);
|
|
@@ -4768,7 +5359,7 @@ async function writeCodeMap(resolved, map) {
|
|
|
4768
5359
|
await fs7.mkdir(resolved.outputDir, { recursive: true });
|
|
4769
5360
|
let wrote = false;
|
|
4770
5361
|
const write = async (name, content) => {
|
|
4771
|
-
wrote = await atomicWriteIfChanged(
|
|
5362
|
+
wrote = await atomicWriteIfChanged(path15.join(resolved.outputDir, name), content) || wrote;
|
|
4772
5363
|
};
|
|
4773
5364
|
for (const name of MAP_COMPANION_ARTIFACTS) await write(name, contents[name]);
|
|
4774
5365
|
await write("manifest.json", stableJson(validated.manifest, true));
|
|
@@ -4832,14 +5423,14 @@ async function loadCodeMap(options = {}) {
|
|
|
4832
5423
|
async function loadCodeMapFrom(outputDir) {
|
|
4833
5424
|
try {
|
|
4834
5425
|
const manifest = await readJson(
|
|
4835
|
-
|
|
5426
|
+
path15.join(outputDir, "manifest.json"),
|
|
4836
5427
|
"manifest",
|
|
4837
5428
|
ManifestRecordSchema
|
|
4838
5429
|
);
|
|
4839
5430
|
verifyManifestIntegrity(manifest, outputDir);
|
|
4840
5431
|
const rawEntries = await Promise.all(
|
|
4841
5432
|
MAP_COMPANION_ARTIFACTS.map(
|
|
4842
|
-
async (name) => [name, await fs7.readFile(
|
|
5433
|
+
async (name) => [name, await fs7.readFile(path15.join(outputDir, name))]
|
|
4843
5434
|
)
|
|
4844
5435
|
);
|
|
4845
5436
|
const raw = Object.fromEntries(rawEntries);
|
|
@@ -4935,7 +5526,7 @@ function verifyManifestIntegrity(manifest, outputDir) {
|
|
|
4935
5526
|
async function loadManifestFrom(outputDir) {
|
|
4936
5527
|
try {
|
|
4937
5528
|
const manifest = await readJson(
|
|
4938
|
-
|
|
5529
|
+
path15.join(outputDir, "manifest.json"),
|
|
4939
5530
|
"manifest",
|
|
4940
5531
|
ManifestRecordSchema
|
|
4941
5532
|
);
|
|
@@ -5240,9 +5831,9 @@ function semanticPackageInputs(workspaces) {
|
|
|
5240
5831
|
async function hashExistingInputs(root, relativePaths) {
|
|
5241
5832
|
const output = [];
|
|
5242
5833
|
for (const relative of [...new Set(relativePaths.map(normalizeStoredPath))].sort(compareText)) {
|
|
5243
|
-
const absolute =
|
|
5244
|
-
const candidate =
|
|
5245
|
-
if (candidate === ".." || candidate.startsWith(`..${
|
|
5834
|
+
const absolute = path16.resolve(root, relative);
|
|
5835
|
+
const candidate = path16.relative(root, absolute);
|
|
5836
|
+
if (candidate === ".." || candidate.startsWith(`..${path16.sep}`) || path16.isAbsolute(candidate)) {
|
|
5246
5837
|
continue;
|
|
5247
5838
|
}
|
|
5248
5839
|
try {
|
|
@@ -5297,7 +5888,7 @@ async function hashDiscoveredSources(files, concurrency) {
|
|
|
5297
5888
|
}
|
|
5298
5889
|
async function compareSemanticInputs(root, before, concurrency) {
|
|
5299
5890
|
const realRoot = await fs8.realpath(root);
|
|
5300
|
-
const toolchainRoot = await fs8.realpath(
|
|
5891
|
+
const toolchainRoot = await fs8.realpath(path16.dirname(ts8.getDefaultLibFilePath({})));
|
|
5301
5892
|
const current = await mapLimit(before, concurrency, async (input) => {
|
|
5302
5893
|
let changed;
|
|
5303
5894
|
switch (input.kind) {
|
|
@@ -5335,7 +5926,7 @@ async function compareSemanticInputs(root, before, concurrency) {
|
|
|
5335
5926
|
return current.filter((input) => input.changed).map((input) => input.path).sort(compareText);
|
|
5336
5927
|
}
|
|
5337
5928
|
async function hashSemanticFileInput(root, realRoot, relative) {
|
|
5338
|
-
const absolute =
|
|
5929
|
+
const absolute = path16.resolve(root, relative);
|
|
5339
5930
|
if (!isPathInside(root, absolute)) return null;
|
|
5340
5931
|
try {
|
|
5341
5932
|
const realInput = await fs8.realpath(absolute);
|
|
@@ -5348,7 +5939,7 @@ async function hashSemanticFileInput(root, realRoot, relative) {
|
|
|
5348
5939
|
}
|
|
5349
5940
|
}
|
|
5350
5941
|
async function semanticProbeState(root, realRoot, relative, targetKind) {
|
|
5351
|
-
const absolute =
|
|
5942
|
+
const absolute = path16.resolve(root, relative);
|
|
5352
5943
|
if (!isPathInside(root, absolute)) return false;
|
|
5353
5944
|
try {
|
|
5354
5945
|
const realInput = await fs8.realpath(absolute);
|
|
@@ -5365,7 +5956,7 @@ async function semanticProbeState(root, realRoot, relative, targetKind) {
|
|
|
5365
5956
|
}
|
|
5366
5957
|
}
|
|
5367
5958
|
async function hashSemanticDirectoryInput(root, realRoot, relative) {
|
|
5368
|
-
const absolute =
|
|
5959
|
+
const absolute = path16.resolve(root, relative);
|
|
5369
5960
|
if (!isPathInside(root, absolute)) return null;
|
|
5370
5961
|
try {
|
|
5371
5962
|
const realInput = await fs8.realpath(absolute);
|
|
@@ -5375,7 +5966,7 @@ async function hashSemanticDirectoryInput(root, realRoot, relative) {
|
|
|
5375
5966
|
const permitted = [];
|
|
5376
5967
|
for (const candidate of candidates) {
|
|
5377
5968
|
try {
|
|
5378
|
-
const resolved =
|
|
5969
|
+
const resolved = path16.resolve(absolute, candidate);
|
|
5379
5970
|
const realCandidate = await fs8.realpath(resolved);
|
|
5380
5971
|
if (isPathInside(realRoot, realCandidate)) permitted.push(candidate);
|
|
5381
5972
|
} catch {
|
|
@@ -5387,7 +5978,7 @@ async function hashSemanticDirectoryInput(root, realRoot, relative) {
|
|
|
5387
5978
|
}
|
|
5388
5979
|
}
|
|
5389
5980
|
async function semanticSymlinkTarget(root, realRoot, relative) {
|
|
5390
|
-
const absolute =
|
|
5981
|
+
const absolute = path16.resolve(root, relative);
|
|
5391
5982
|
if (!isPathInside(root, absolute)) return null;
|
|
5392
5983
|
try {
|
|
5393
5984
|
const realInput = await fs8.realpath(absolute);
|
|
@@ -5399,8 +5990,8 @@ async function semanticSymlinkTarget(root, realRoot, relative) {
|
|
|
5399
5990
|
}
|
|
5400
5991
|
async function hashToolchainFileInput(relative) {
|
|
5401
5992
|
try {
|
|
5402
|
-
const libraryRoot = await fs8.realpath(
|
|
5403
|
-
const absolute =
|
|
5993
|
+
const libraryRoot = await fs8.realpath(path16.dirname(ts8.getDefaultLibFilePath({})));
|
|
5994
|
+
const absolute = path16.resolve(libraryRoot, relative);
|
|
5404
5995
|
if (!isPathInside(libraryRoot, absolute)) return null;
|
|
5405
5996
|
const realInput = await fs8.realpath(absolute);
|
|
5406
5997
|
if (!isPathInside(libraryRoot, realInput) || !(await fs8.stat(realInput)).isFile()) return null;
|
|
@@ -5736,7 +6327,7 @@ async function analyzeProject(options) {
|
|
|
5736
6327
|
const result = await runCompilerUnitWorker(unitInput);
|
|
5737
6328
|
contribution = result.contribution;
|
|
5738
6329
|
for (const file of result.files) {
|
|
5739
|
-
const source = preparedByAbsolute.get(
|
|
6330
|
+
const source = preparedByAbsolute.get(path17.resolve(resolved.root, file.path));
|
|
5740
6331
|
if (source) source.file = file;
|
|
5741
6332
|
}
|
|
5742
6333
|
} else {
|
|
@@ -5849,7 +6440,7 @@ async function analyzeCompilerUnit(options) {
|
|
|
5849
6440
|
const { program, checker } = semantic;
|
|
5850
6441
|
const resolveModule = semantic.resolveModule ?? createBoundedModuleResolver(
|
|
5851
6442
|
options.root,
|
|
5852
|
-
|
|
6443
|
+
path17.resolve(options.root, unit.record.root),
|
|
5853
6444
|
program.getCompilerOptions()
|
|
5854
6445
|
);
|
|
5855
6446
|
const diagnostics = [...semantic.diagnostics];
|
|
@@ -6679,7 +7270,74 @@ function showSymbol(map, selector) {
|
|
|
6679
7270
|
}).filter((match) => match !== void 0)
|
|
6680
7271
|
};
|
|
6681
7272
|
}
|
|
6682
|
-
function formatShowResult(result) {
|
|
7273
|
+
function formatShowResult(result, options = {}) {
|
|
7274
|
+
const limit = options.limit ?? 12;
|
|
7275
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
|
|
7276
|
+
throw new RangeError("show limit must be an integer between 1 and 50");
|
|
7277
|
+
}
|
|
7278
|
+
if (options.full) return formatFullShowResult(result);
|
|
7279
|
+
let remaining = limit;
|
|
7280
|
+
const matches = result.matches.slice(0, 5);
|
|
7281
|
+
const blocks = matches.map(({ symbol, inbound, outbound }) => {
|
|
7282
|
+
const { startLine, startColumn, endLine, endColumn } = symbol.range;
|
|
7283
|
+
const lines = [
|
|
7284
|
+
`${symbol.name} [${symbol.kind}]`,
|
|
7285
|
+
`id: ${symbol.id}`,
|
|
7286
|
+
`path: ${symbol.file}:${startLine}:${startColumn}-${endLine}:${endColumn}`
|
|
7287
|
+
];
|
|
7288
|
+
if (symbol.signature) {
|
|
7289
|
+
const signature = symbol.signature.replace(/\s+/g, " ");
|
|
7290
|
+
lines.push(
|
|
7291
|
+
`signature: ${signature.length > 240 ? `${signature.slice(0, 240)}\u2026 [truncated]` : signature}`
|
|
7292
|
+
);
|
|
7293
|
+
}
|
|
7294
|
+
const entries = [
|
|
7295
|
+
...outbound.map((relation2) => ({ relation: relation2, direction: "->", target: relation2.to })),
|
|
7296
|
+
...inbound.map((relation2) => ({ relation: relation2, direction: "<-", target: relation2.from }))
|
|
7297
|
+
];
|
|
7298
|
+
const eligible = entries.filter(
|
|
7299
|
+
({ relation: relation2, target }) => !["heuristic", "derived"].includes(relation2.origin.classification) && !["contains", "exports", "used-by"].includes(relation2.type) && !target.startsWith("unresolved:")
|
|
7300
|
+
);
|
|
7301
|
+
const actionable = new Set(
|
|
7302
|
+
eligible.filter(({ relation: relation2 }) => relation2.type !== "references").map(({ direction, target }) => `${direction}:${target}`)
|
|
7303
|
+
);
|
|
7304
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7305
|
+
let filtered = entries.length - eligible.length;
|
|
7306
|
+
for (const { relation: relation2, direction, target } of eligible) {
|
|
7307
|
+
if (relation2.type === "references" && actionable.has(`${direction}:${target}`)) {
|
|
7308
|
+
filtered++;
|
|
7309
|
+
continue;
|
|
7310
|
+
}
|
|
7311
|
+
const key = `${direction}:${relation2.type}:${target}`;
|
|
7312
|
+
const group = groups.get(key);
|
|
7313
|
+
if (group) group.count++;
|
|
7314
|
+
else groups.set(key, { direction, target, type: relation2.type, count: 1 });
|
|
7315
|
+
}
|
|
7316
|
+
const ranked = [...groups.values()].sort(
|
|
7317
|
+
(a, b) => Number(a.target.startsWith("external:")) - Number(b.target.startsWith("external:")) || Number(a.type === "references") - Number(b.type === "references") || compareText(`${a.direction}:${a.type}:${a.target}`, `${b.direction}:${b.type}:${b.target}`)
|
|
7318
|
+
);
|
|
7319
|
+
const shown = ranked.slice(0, remaining);
|
|
7320
|
+
remaining -= shown.length;
|
|
7321
|
+
lines.push(
|
|
7322
|
+
`relations: ${shown.length}/${ranked.length} groups shown; ${ranked.length - shown.length} omitted; ${filtered} records filtered`
|
|
7323
|
+
);
|
|
7324
|
+
lines.push(
|
|
7325
|
+
...shown.map(
|
|
7326
|
+
({ direction, type, target, count }) => ` ${direction} ${type} ${direction} ${target}${count > 1 ? ` (${count} occurrences)` : ""}`
|
|
7327
|
+
)
|
|
7328
|
+
);
|
|
7329
|
+
return lines.join("\n");
|
|
7330
|
+
});
|
|
7331
|
+
if (matches.length < result.matches.length) {
|
|
7332
|
+
blocks.push(
|
|
7333
|
+
`Symbols: ${matches.length}/${result.matches.length} shown; ${result.matches.length - matches.length} omitted. Use an exact ID to narrow the selector.`
|
|
7334
|
+
);
|
|
7335
|
+
}
|
|
7336
|
+
if (matches.length)
|
|
7337
|
+
blocks.push("Compact view; use --full or --json for all details and evidence.");
|
|
7338
|
+
return blocks.join("\n\n");
|
|
7339
|
+
}
|
|
7340
|
+
function formatFullShowResult(result) {
|
|
6683
7341
|
return result.matches.map(({ symbol, inbound, outbound }) => {
|
|
6684
7342
|
const lines = [
|
|
6685
7343
|
`${symbol.name} [${symbol.kind}]`,
|
|
@@ -6744,6 +7402,8 @@ export {
|
|
|
6744
7402
|
TypeScriptParserAdapter,
|
|
6745
7403
|
TypeScriptSemanticResolver,
|
|
6746
7404
|
buildOverview,
|
|
7405
|
+
buildProjectMap,
|
|
7406
|
+
buildSourceContext,
|
|
6747
7407
|
checkFreshness,
|
|
6748
7408
|
computeManifestIntegrityHash,
|
|
6749
7409
|
createAnalyzer,
|
|
@@ -6753,9 +7413,13 @@ export {
|
|
|
6753
7413
|
createTypeScriptSemanticResolver,
|
|
6754
7414
|
createWorkspaceDiscovery,
|
|
6755
7415
|
findProjectRoot,
|
|
7416
|
+
findSymbols,
|
|
7417
|
+
formatProjectMap,
|
|
6756
7418
|
formatShowResult,
|
|
7419
|
+
formatSourceContext,
|
|
6757
7420
|
initProject,
|
|
6758
7421
|
loadCodeMap,
|
|
7422
|
+
readSymbolSource,
|
|
6759
7423
|
scanProject,
|
|
6760
7424
|
showSymbol
|
|
6761
7425
|
};
|