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/dist/index.js CHANGED
@@ -1,40 +1,5 @@
1
- // src/constants.ts
2
- var SCHEMA_VERSION = 4;
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/config.ts
53
- import { promises as fs3 } from "fs";
54
- import path4 from "path";
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/schemas.ts
57
- import path3 from "path";
58
- import { z } from "zod";
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 fs } from "fs";
62
- import path from "path";
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 path.posix.isAbsolute(normalized) || /^[a-zA-Z]:[\\/]/.test(normalized) || normalized.startsWith("//");
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(path.resolve(root));
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 = path.relative(root, absolutePath);
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) && !path.isAbsolute(candidate)) return false;
124
- const relative = path.relative(path.resolve(root), path.resolve(candidate));
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 = path.resolve(candidate);
339
+ const absolute = path2.resolve(candidate);
129
340
  if (!isPathInside(root, absolute)) return false;
130
341
  try {
131
- const stat = await fs.lstat(absolute);
342
+ const stat = await fs2.lstat(absolute);
132
343
  if (stat.isSymbolicLink() || !stat.isFile()) return false;
133
- const [realRoot, realFile] = await Promise.all([fs.realpath(root), fs.realpath(absolute)]);
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 = path.resolve(candidate);
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 fs.readFile(absolute);
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 = path.resolve(root);
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 = path.win32.isAbsolute(trimmed) && !path.posix.isAbsolute(trimmed);
162
- if (foreignWindowsPath && path.posix.isAbsolute(normalizedRoot)) return "<outside-root>";
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 = path.posix.normalize(toPosixPath(value));
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/stable.ts
199
- import { promises as fs2 } from "fs";
200
- import path2 from "path";
201
- function compareText(a, b) {
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
- function safeRecordEntry(value) {
205
- if (Array.isArray(value)) {
206
- return value.map((entry) => safeRecordEntry(entry));
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
- if (value !== null && typeof value === "object") {
209
- const record = value;
210
- const next = {};
211
- for (const key of Object.keys(record).sort(compareText)) {
212
- const child = record[key];
213
- if (child !== void 0) {
214
- next[key] = safeRecordEntry(child);
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
- return next;
507
+ for (const site of nav.tests) tests.set(`${site.file}:${site.line}`, site);
218
508
  }
219
- return value;
220
- }
221
- function stableValue(value) {
222
- if (Array.isArray(value)) {
223
- return safeRecordEntry(value);
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
- if (value !== null && typeof value === "object") {
226
- return safeRecordEntry(value);
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
- return value;
229
- }
230
- function stableJson(value, pretty = false) {
231
- return `${JSON.stringify(stableValue(value), null, pretty ? 2 : void 0)}
232
- `;
233
- }
234
- function stableJsonLine(value) {
235
- return JSON.stringify(stableValue(value));
236
- }
237
- async function atomicWriteIfChanged(filePath, content) {
238
- const canonicalBytes = Buffer.from(content, "utf8");
239
- try {
240
- if ((await fs2.readFile(filePath)).equals(canonicalBytes)) return false;
241
- } catch (error) {
242
- if (!isNotFound(error) && !isPermissionDenied(error)) throw error;
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
- await fs2.mkdir(path2.dirname(filePath), { recursive: true });
245
- const temporary = path2.join(
246
- path2.dirname(filePath),
247
- `.${path2.basename(filePath)}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`
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
- try {
250
- await fs2.writeFile(temporary, canonicalBytes, { mode: 420 });
251
- await fs2.rename(temporary, filePath);
252
- } catch (error) {
253
- await fs2.rm(temporary, { force: true }).catch(() => void 0);
254
- throw error;
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
- return true;
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 isNotFound(error) {
259
- return error instanceof Error && "code" in error && error.code === "ENOENT";
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 isPermissionDenied(error) {
262
- return error instanceof Error && "code" in error && (error.code === "EACCES" || error.code === "EPERM");
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
- async function mapLimit(values, limit, operation) {
265
- const results = new Array(values.length);
266
- let cursor = 0;
267
- const workers = Array.from({ length: Math.min(Math.max(1, limit), values.length) }, async () => {
268
- while (cursor < values.length) {
269
- const index = cursor++;
270
- const value = values[index];
271
- if (value === void 0) return;
272
- results[index] = await operation(value, index);
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
- await Promise.all(workers);
276
- return results;
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(path3.posix.dirname(unit.config))) {
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, path16) {
1566
+ function addDuplicateIssue(context, seen, value, path18) {
970
1567
  if (seen.has(value)) {
971
- context.addIssue({ code: "custom", path: path16, message: `duplicate value ${value}` });
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 = path4.dirname(configPath);
1010
- const relativeRoot = toPosixPath(path4.relative(configDirectory, root) || ".");
1011
- const outputValue = output ? path4.isAbsolute(output) ? toPosixPath(path4.relative(root, output)) : toPosixPath(output) : DEFAULT_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 = path4.resolve(start);
1621
+ let current = path6.resolve(start);
1025
1622
  try {
1026
- if (!(await fs3.stat(current)).isDirectory()) current = path4.dirname(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 !== path4.dirname(current)) {
1033
- const packagePath = path4.join(current, "package.json");
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 = path4.dirname(current);
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 ${path4.resolve(start)}. Pass --root explicitly.`
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 path4.join(base, DEFAULT_OUTPUT, "config.json");
1047
- return path4.resolve(base, config);
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 = path4.resolve(options.root ?? process.cwd());
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) : path4.join(
1053
- options.output ? path4.resolve(discoveredRoot, options.output) : path4.join(discoveredRoot, DEFAULT_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 ? path4.resolve(path4.dirname(configPath), parsed.root) : discoveredRoot;
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 = path4.isAbsolute(outputValue) ? path4.normalize(outputValue) : path4.resolve(root, outputValue);
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 = path4.resolve(options.root ?? process.cwd());
1692
+ const requestedStart = path6.resolve(options.root ?? process.cwd());
1096
1693
  const root = await findProjectRoot(requestedStart);
1097
- const defaultOutputDir = options.output ? path4.resolve(root, options.output) : path4.join(root, DEFAULT_OUTPUT);
1098
- const configPath = options.config ? path4.resolve(requestedStart, options.config) : path4.join(defaultOutputDir, "config.json");
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 path15 from "path";
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 path5 from "path";
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 compact = value.replace(/\s+/g, " ").trim();
1309
- return compact.length > maxLength ? `${compact.slice(0, maxLength - 1)}\u2026` : compact;
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(path5.resolve(context.root, binding.resolved));
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(path5.resolve(context.root, binding.resolved));
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 path6 from "path";
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(path6.extname(context.relativePath).toLowerCase())) return false;
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 = path6.resolve(context.root, candidateRelative);
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 path7 from "path";
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 = path7.extname(context.relativePath).toLowerCase();
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 behavior = collectComponentBehavior(context, body, profile);
3161
- if (behavior.state.length > 0) facts.state = behavior.state.join(",");
3162
- if (behavior.memo.length > 0) facts.memo = behavior.memo.join(",");
3163
- if (behavior.effects.length > 0) facts.effects = behavior.effects.join(",");
3164
- if (behavior.customHooks.length > 0) facts.hooks = behavior.customHooks.join(",");
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 = path7.relative(context.root, sourceFile.fileName).split(path7.sep).join("/");
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("../") || path7.isAbsolute(relative) || context.indexedSourcePaths && !context.indexedSourcePaths.has(relative)) {
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 = path7.posix.normalize(
3292
- path7.posix.join(path7.posix.dirname(context.relativePath), source.source)
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 = path7.resolve(context.root, candidate);
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 path14 from "path";
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 path8 from "path";
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 = path8.resolve(realRoot, logical);
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(path8.posix.dirname(node.relativePath));
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
- path8.posix.basename(node.relativePath)
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(path8.dirname(node.absolutePath), source.absolutePath)
4152
+ (node) => !node.allowJs && isPathInside(path10.dirname(node.absolutePath), source.absolutePath)
3562
4153
  );
3563
4154
  const underAllowingConfig = nodes.some(
3564
- (node) => node.allowJs && isPathInside(path8.dirname(node.absolutePath), source.absolutePath)
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(path8.posix.dirname(node.relativePath));
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, path8.resolve(root, candidate))).sort(compareText);
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 = path8.resolve(root, configured);
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(path8.dirname(absolutePath));
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
- path8.dirname(absolutePath),
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, path8.dirname(absolutePath));
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, path8.dirname(absolutePath), parsed.options),
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 = path8.resolve(value);
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 ?? path8.join(unitDirectory, "tsconfig.json");
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 = path8.resolve(start);
4379
+ let current = path10.resolve(start);
3789
4380
  while (isPathInside(root, current)) {
3790
- values.push(path8.join(current, "node_modules", "@types"));
3791
- if (current === path8.resolve(root)) break;
3792
- const parent = path8.dirname(current);
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 = path8.resolve(base, target);
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(path8.dirname(a.absolutePath), sourceFile, root);
3821
- const directoryDepthB = ancestorDepth(path8.dirname(b.absolutePath), sourceFile, root);
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 = path8.resolve(root, boundary);
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 = path8.extname(filePath).toLowerCase();
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 = path8.resolve(candidate);
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 = path8.resolve(fileName);
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(path8.dirname(ts6.getDefaultLibFilePath({})));
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(path8.resolve(absolute, entry))).sort(compareText);
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(path8.resolve(absolute, entry))).sort(compareText);
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 path8.isAbsolute(candidate) ? path8.resolve(candidate) : path8.resolve(this.root, candidate);
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(path8.relative(directory, path8.resolve(directory, entry)))).sort(compareText);
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: path8.join(root, "tsconfig.fallback.json"),
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 path11 from "path";
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 path9 from "path";
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 = path9.join(realDirectory, "package.json");
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 : path9.basename(realDirectory),
4161
- path: normalizeStoredPath(path9.relative(selectedRoot, realDirectory)),
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, path9.join(root, marker))) return true;
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, path9.join(root, "package.json"))).toString("utf8")
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 path10 from "path";
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(path10.posix.dirname(normalizedRelative));
4809
+ const directory = normalizeStoredPath(path12.posix.dirname(normalizedRelative));
4219
4810
  if (directory !== "." && isIgnoredDirectory(directory, rules)) continue;
4220
- const absolute = path10.resolve(root, relative);
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, path11.join(root, "package.json"))).toString("utf8")
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, path11.join(root, workspace.path, "package.json"))).toString("utf8")
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: path11.resolve(root, relative),
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 = path11.extname(filePath).slice(1).toLowerCase();
4943
+ const extension = path13.extname(filePath).slice(1).toLowerCase();
4353
4944
  return extension;
4354
4945
  }
4355
4946
 
4356
4947
  // src/semantic.ts
4357
- import path12 from "path";
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 ? path12.dirname(path12.resolve(input.root, input.tsconfig)) : input.root;
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) => path12.resolve(file)))].sort(
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 = path12.dirname(configPath);
4441
- const packageJsonPath = path12.join(packageRoot, "package.json");
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) => path12.resolve(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 = path12.resolve(packageRoot, target);
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(path12.resolve(rootDir, path12.relative(outDir, absoluteTarget)));
5070
+ bases.push(path14.resolve(rootDir, path14.relative(outDir, absoluteTarget)));
4480
5071
  }
4481
5072
  bases.push(absoluteTarget);
4482
5073
  }
4483
- bases.push(path12.resolve(rootDir, subpath || "index"));
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 = path12.resolve(candidate);
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) => path12.join(stem, `index${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 = path12.resolve(root, tsconfig);
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, path12.dirname(absolute), options);
5238
+ anchorCompilerOptions(root, path14.dirname(absolute), options);
4648
5239
  return {
4649
5240
  options,
4650
5241
  ...projectReferences.length > 0 ? { projectReferences } : {},
4651
- pathAliases: pathAliases(root, path12.dirname(absolute), options),
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 = path12.resolve(configPath);
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(path12.dirname(absolute)),
4668
- path12.dirname(absolute),
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, path12.dirname(absolute), parsed.options);
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 ?? path12.join(unitDirectory, "tsconfig.json");
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 = path12.resolve(start);
5305
+ let current = path14.resolve(start);
4715
5306
  while (isPathInside(root, current)) {
4716
- values.push(path12.join(current, "node_modules", "@types"));
4717
- if (current === path12.resolve(root)) break;
4718
- const parent = path12.dirname(current);
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) => path12.resolve(base, value)).filter((value) => isPathInside(root, value)).map((value) => normalizeStoredPath(relativePosix(root, value))).filter((value, index, entries) => entries.indexOf(value) === index).sort(compareText)
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 path13 from "path";
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(path13.join(resolved.outputDir, name), content) || wrote;
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
- path13.join(outputDir, "manifest.json"),
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(path13.join(outputDir, name))]
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
- path13.join(outputDir, "manifest.json"),
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 = path14.resolve(root, relative);
5244
- const candidate = path14.relative(root, absolute);
5245
- if (candidate === ".." || candidate.startsWith(`..${path14.sep}`) || path14.isAbsolute(candidate)) {
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(path14.dirname(ts8.getDefaultLibFilePath({})));
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 = path14.resolve(root, relative);
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 = path14.resolve(root, relative);
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 = path14.resolve(root, relative);
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 = path14.resolve(absolute, candidate);
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 = path14.resolve(root, relative);
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(path14.dirname(ts8.getDefaultLibFilePath({})));
5403
- const absolute = path14.resolve(libraryRoot, relative);
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(path15.resolve(resolved.root, file.path));
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
- path15.resolve(options.root, unit.record.root),
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
  };