ucn 5.4.1 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.claude/skills/ucn/SKILL.md +58 -236
  2. package/.claude/skills/ucn/references/commands.md +144 -68
  3. package/.claude/skills/ucn/references/resolution-rules.md +171 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +27 -33
  5. package/README.md +18 -12
  6. package/cli/index.js +42 -16
  7. package/core/accessors.js +118 -3
  8. package/core/account.js +30 -0
  9. package/core/analysis.js +508 -264
  10. package/core/ast-analysis.js +76 -117
  11. package/core/async-producers.js +1729 -0
  12. package/core/bridge.js +1877 -268
  13. package/core/brief.js +6 -0
  14. package/core/build-worker.js +78 -14
  15. package/core/cache.js +522 -31
  16. package/core/callers.js +12053 -1047
  17. package/core/check.js +30 -38
  18. package/core/class-identity.js +1028 -0
  19. package/core/command-contracts.js +6 -6
  20. package/core/configuration-items.js +86 -0
  21. package/core/contract-membership.js +2262 -0
  22. package/core/cpp-scope.js +532 -0
  23. package/core/deadcode.js +171 -49
  24. package/core/decorator-binding.js +260 -0
  25. package/core/discovery.js +281 -164
  26. package/core/errors.js +52 -0
  27. package/core/execute.js +21 -9
  28. package/core/external-macros.js +326 -0
  29. package/core/graph-build.js +277 -8
  30. package/core/graph.js +35 -2
  31. package/core/ignore-rules.js +472 -0
  32. package/core/imports.js +504 -42
  33. package/core/index-ir.js +94 -11
  34. package/core/inprocess-clients.js +636 -0
  35. package/core/ir.js +87 -8
  36. package/core/macro-expansion.js +1416 -0
  37. package/core/output/analysis-ext.js +6 -6
  38. package/core/output/analysis.js +18 -2
  39. package/core/output/brief.js +3 -0
  40. package/core/output/doctor.js +4 -0
  41. package/core/output/endpoints.js +15 -6
  42. package/core/output/find.js +11 -2
  43. package/core/output/lines.js +8 -2
  44. package/core/output/refactoring.js +121 -14
  45. package/core/output/reporting.js +31 -3
  46. package/core/output/shared.js +14 -0
  47. package/core/output-budget.js +316 -122
  48. package/core/parallel-build.js +124 -47
  49. package/core/parser.js +3 -2
  50. package/core/project.js +457 -60
  51. package/core/provenance-facts.js +156 -12
  52. package/core/provenance-overload.js +30 -3
  53. package/core/provenance.js +104 -30
  54. package/core/pytest-fixtures.js +434 -0
  55. package/core/python-fixture-flow.js +17 -3
  56. package/core/python-modules.js +173 -0
  57. package/core/reflection.js +877 -0
  58. package/core/registry.js +1 -1
  59. package/core/reporting.js +234 -52
  60. package/core/request-helpers.js +561 -0
  61. package/core/route-graph.js +1922 -0
  62. package/core/route-lists.js +2414 -0
  63. package/core/rust-macro-expansion.js +2113 -0
  64. package/core/rust-modules.js +185 -0
  65. package/core/search.js +121 -29
  66. package/core/shared.js +19 -1
  67. package/core/test-name-bindings.js +256 -0
  68. package/core/type-denotation.js +1161 -0
  69. package/core/type-references.js +3229 -0
  70. package/core/verify.js +3360 -672
  71. package/languages/adapter.js +31 -5
  72. package/languages/c-family.js +4061 -212
  73. package/languages/c-macro-invocations.js +380 -0
  74. package/languages/c-preprocessor.js +254 -0
  75. package/languages/csharp.js +1406 -49
  76. package/languages/go.js +1243 -70
  77. package/languages/index.js +728 -6
  78. package/languages/java.js +473 -61
  79. package/languages/javascript.js +1270 -197
  80. package/languages/lexical-scope.js +1280 -0
  81. package/languages/python.js +1557 -72
  82. package/languages/rust-macro-rules.js +1020 -0
  83. package/languages/rust-value-flow.js +416 -0
  84. package/languages/rust.js +2237 -115
  85. package/languages/utils.js +298 -4
  86. package/mcp/server.js +25 -11
  87. package/package.json +1 -1
@@ -6,7 +6,23 @@
6
6
  */
7
7
 
8
8
  const path = require('path');
9
- const { resolveImport } = require('./imports');
9
+ const { resolveImport, resolveRustModuleFile, rustModDeclarationFiles, jsWorkspacePackages } = require('./imports');
10
+
11
+ const JS_WORKSPACE_LANGUAGES = new Set(['javascript', 'typescript', 'tsx']);
12
+
13
+ /**
14
+ * The project's in-repository JS/TS packages by name (fix #397), computed
15
+ * once per build and on first use after a cache load.
16
+ */
17
+ function jsWorkspacePackagesOf(index, refresh = false) {
18
+ if (!refresh && index._jsWorkspacePackages !== undefined) return index._jsWorkspacePackages;
19
+ const dirs = new Set();
20
+ for (const [fp, fe] of index.files) {
21
+ if (JS_WORKSPACE_LANGUAGES.has(fe.language)) dirs.add(path.dirname(fp));
22
+ }
23
+ index._jsWorkspacePackages = dirs.size > 0 ? jsWorkspacePackages(index.root, dirs) : null;
24
+ return index._jsWorkspacePackages;
25
+ }
10
26
  const { langTraits, getParser, safeParse } = require('../languages');
11
27
 
12
28
  function _javaPackageTypes(index) {
@@ -161,6 +177,32 @@ function _buildCSharpNamespaceIndex(index) {
161
177
  return namespaces;
162
178
  }
163
179
 
180
+ /**
181
+ * A C# file's using directives by the names C# resolves them to (fix #395):
182
+ * each written name resolved from the namespace its directive sits in,
183
+ * generic arguments of `using static T<X>` dropped. `written` collects the
184
+ * written spellings of names that resolved to something else.
185
+ */
186
+ function _csharpResolvedImports(index, fileEntry, written) {
187
+ const { csharpResolveUsingName } = require('./type-denotation');
188
+ const details = (fileEntry.importDetails || []).filter(detail => detail.module);
189
+ if (details.length === 0) return fileEntry.imports || [];
190
+ const out = [];
191
+ for (const detail of details) {
192
+ if (!detail.namespace) {
193
+ out.push(String(detail.module).replace(/<.*$/s, ''));
194
+ continue;
195
+ }
196
+ const resolved = csharpResolveUsingName(index, detail.module, detail.namespace).replace(/<.*$/s, '');
197
+ out.push(resolved);
198
+ if (resolved !== detail.module) {
199
+ if (!written.has(resolved)) written.set(resolved, []);
200
+ written.get(resolved).push(detail.module);
201
+ }
202
+ }
203
+ return out;
204
+ }
205
+
164
206
  function _resolveCSharpUsing(index, importModule, namespaceIndex = null, opts = {}) {
165
207
  const map = namespaceIndex || _buildCSharpNamespaceIndex(index);
166
208
  const matches = map.get(importModule) || [];
@@ -171,6 +213,105 @@ function _resolveCSharpUsing(index, importModule, namespaceIndex = null, opts =
171
213
  /**
172
214
  * Build import/export relationship graphs
173
215
  */
216
+ /** basename -> C/C++ project files carrying it (for include suffix matching). */
217
+ function _buildCIncludeSuffixIndex(index) {
218
+ const byBasename = new Map();
219
+ for (const [filePath, fileEntry] of index.files) {
220
+ if (fileEntry.language !== 'c' && fileEntry.language !== 'cpp') continue;
221
+ const base = path.basename(filePath);
222
+ if (!byBasename.has(base)) byBasename.set(base, []);
223
+ byBasename.get(base).push(filePath);
224
+ }
225
+ return byBasename;
226
+ }
227
+
228
+ function _resolveCIncludeBySuffix(byBasename, includeName, fromFile) {
229
+ const normalized = String(includeName || '').replace(/\\/g, '/');
230
+ if (!normalized || normalized.startsWith('../') || normalized.includes('/../')) return null;
231
+ const candidates = (byBasename.get(path.posix.basename(normalized)) || [])
232
+ .filter(file => file !== fromFile &&
233
+ file.split(path.sep).join('/').endsWith(`/${normalized}`));
234
+ return candidates.length === 1 ? candidates[0] : null;
235
+ }
236
+
237
+ function _isRustCrateRootFile(file) {
238
+ const base = path.basename(file);
239
+ return base === 'lib.rs' || base === 'main.rs';
240
+ }
241
+
242
+ /**
243
+ * Rust module files a `use` path names when a segment is reached through a
244
+ * glob re-export or a module re-export binding rather than a `mod` file
245
+ * (fix #369). Walks the module segments from the path root, at each module
246
+ * trying its child module file, then its `pub use m as seg` / `use x::seg`
247
+ * bindings, then its top-level glob imports (bounded depth). Returns the one
248
+ * module file that holds the final item (declared or re-exported), or null
249
+ * when the walk is ambiguous or leaves the project.
250
+ */
251
+ function _rustWalkUsePath(index, fromFile, spec) {
252
+ const segments = String(spec).split('::').filter(Boolean);
253
+ if (segments.length < 2) return null;
254
+ const root = segments[0];
255
+ let start;
256
+ try {
257
+ start = resolveRustModuleFile(root, fromFile, index.root);
258
+ } catch { start = null; }
259
+ if (!start || !index.files.has(start)) return null;
260
+ const childModule = (file, name) => {
261
+ const base = path.basename(file);
262
+ const dir = base === 'lib.rs' || base === 'main.rs' || base === 'mod.rs'
263
+ ? path.dirname(file) : path.join(path.dirname(file), path.basename(file, '.rs'));
264
+ for (const candidate of [path.join(dir, `${name}.rs`), path.join(dir, name, 'mod.rs')]) {
265
+ if (index.files.has(candidate)) {
266
+ const declares = (index.files.get(file)?.symbols || []).some(symbol =>
267
+ symbol.type === 'module' && symbol.name === name);
268
+ if (declares) return candidate;
269
+ }
270
+ }
271
+ return null;
272
+ };
273
+ const topLevel = (entry, line) => line == null || !(entry.symbols || []).some(symbol =>
274
+ symbol.type === 'module' && symbol.startLine < line && symbol.endLine >= line);
275
+ // Module files reachable as `name` inside module file `file`.
276
+ const lookup = (file, name, seen, depth) => {
277
+ const key = `${file}\0${name}`;
278
+ if (seen.has(key) || depth > 6) return [];
279
+ seen.add(key);
280
+ const entry = index.files.get(file);
281
+ if (!entry) return [];
282
+ const child = childModule(file, name);
283
+ if (child) return [child];
284
+ const out = [];
285
+ for (const binding of entry.importBindings || []) {
286
+ if ((binding.alias || binding.name) !== name || binding.name === '*') continue;
287
+ try {
288
+ const target = resolveRustModuleFile(binding.module, file, index.root);
289
+ if (target && index.files.has(target) && target !== file) out.push(target);
290
+ } catch { /* resolver gap */ }
291
+ }
292
+ if (out.length > 0) return out;
293
+ for (const detail of entry.importDetails || []) {
294
+ if (detail.type !== 'use-glob' || !detail.module || !topLevel(entry, detail.line)) continue;
295
+ let target;
296
+ try { target = resolveRustModuleFile(detail.module, file, index.root); } catch { target = null; }
297
+ if (!target || !index.files.has(target) || target === file) continue;
298
+ out.push(...lookup(target, name, seen, depth + 1));
299
+ }
300
+ return out;
301
+ };
302
+ let current = [start];
303
+ for (let i = 1; i < segments.length - 1; i++) {
304
+ const next = new Set();
305
+ for (const file of current) {
306
+ for (const hit of lookup(file, segments[i], new Set(), 0)) next.add(hit);
307
+ }
308
+ if (next.size !== 1) return null;
309
+ current = [...next];
310
+ }
311
+ const result = current[0];
312
+ return result && result !== fromFile ? result : null;
313
+ }
314
+
174
315
  function buildImportGraph(index) {
175
316
  index.importGraph.clear();
176
317
  index.exportGraph.clear();
@@ -198,6 +339,9 @@ function buildImportGraph(index) {
198
339
  }
199
340
  }
200
341
 
342
+ let cIncludeSuffixIndex = null;
343
+ const includeProbeCache = new Map(); // include search results for this build (fix #365)
344
+ const workspacePackages = jsWorkspacePackagesOf(index, true);
201
345
  for (const [filePath, fileEntry] of index.files) {
202
346
  const importedFiles = new Set();
203
347
  const seenModules = new Set();
@@ -208,10 +352,21 @@ function buildImportGraph(index) {
208
352
  // importGraph edges can't (a file importing the target for OTHER
209
353
  // names is not evidence about THIS name's module).
210
354
  const moduleResolved = {};
211
-
212
- const effectiveImports = fileEntry.language === 'csharp'
213
- ? [...(fileEntry.imports || []), ...csharpGlobalImports]
355
+ const includeFallback = {};
356
+
357
+ // C# using directives name what C# resolves them to from the
358
+ // namespace each is written in (fix #395): `using Internal;` inside
359
+ // `namespace Acme.Tests` imports Acme.Internal.
360
+ const csharpWritten = fileEntry.language === 'csharp' ? new Map() : null;
361
+ const effectiveImports = csharpWritten
362
+ ? [..._csharpResolvedImports(index, fileEntry, csharpWritten), ...csharpGlobalImports]
214
363
  : (fileEntry.imports || []);
364
+ // Rust `mod NAME;` declarations load files by the module layout
365
+ // rules (fix #377: foo.rs + foo/ children, #[path], inline-module
366
+ // nesting), never by a plain name lookup from the declaring directory.
367
+ const rustModFiles = fileEntry.language === 'rust'
368
+ ? rustModDeclarationFiles(filePath, fileEntry, candidate => index.files.has(candidate))
369
+ : null;
215
370
  for (const importModule of effectiveImports) {
216
371
  // Skip null modules (e.g., dynamic include! macros in Rust)
217
372
  if (!importModule) continue;
@@ -221,11 +376,16 @@ function buildImportGraph(index) {
221
376
  if (seenModules.has(importModule)) continue;
222
377
  seenModules.add(importModule);
223
378
 
224
- let resolved = resolveImport(importModule, filePath, {
379
+ const rustModTargets = rustModFiles &&
380
+ (fileEntry.importDetails || []).some(detail => detail.type === 'mod' && detail.module === importModule)
381
+ ? (rustModFiles.get(importModule) || []) : null;
382
+ let resolved = rustModTargets ? (rustModTargets[0] || null) : resolveImport(importModule, filePath, {
225
383
  aliases: index.config.aliases,
226
384
  includePaths: index.config.includePaths,
227
385
  language: fileEntry.language,
228
- root: index.root
386
+ root: index.root,
387
+ probeCache: includeProbeCache,
388
+ workspacePackages,
229
389
  });
230
390
 
231
391
  // Java package imports: resolve by progressive suffix matching
@@ -246,6 +406,22 @@ function buildImportGraph(index) {
246
406
  }
247
407
  }
248
408
 
409
+ // C/C++ quoted include that neither the including file's
410
+ // directory nor any known include directory resolves: the build
411
+ // adds an -I path the index cannot see (no compile database). A
412
+ // project header whose path ends with the include name, when
413
+ // exactly one such header exists, is the only file a compiler
414
+ // search could find inside the project. The edge is recorded as
415
+ // basename-resolved so consumers can weigh it below edges the
416
+ // resolver proved (fix #361).
417
+ if (!resolved && (fileEntry.language === 'c' || fileEntry.language === 'cpp') &&
418
+ importModule.startsWith('./')) {
419
+ if (!cIncludeSuffixIndex) cIncludeSuffixIndex = _buildCIncludeSuffixIndex(index);
420
+ resolved = _resolveCIncludeBySuffix(
421
+ cIncludeSuffixIndex, importModule.slice(2), filePath);
422
+ if (resolved) includeFallback[importModule] = true;
423
+ }
424
+
249
425
  let csharpFiles = null;
250
426
  if (!resolved && fileEntry.language === 'csharp') {
251
427
  const all = _resolveCSharpUsing(
@@ -256,13 +432,24 @@ function buildImportGraph(index) {
256
432
  }
257
433
  }
258
434
 
435
+ // Rust module paths through glob re-exports (fix #369): `use
436
+ // cursive::views::LinearLayout` where cursive's root is `pub use
437
+ // cursive_core::*` resolves past the facade crate root instead
438
+ // of stopping at it.
439
+ if (fileEntry.language === 'rust' && importModule.includes('::') &&
440
+ (!resolved || _isRustCrateRootFile(resolved))) {
441
+ const walked = _rustWalkUsePath(index, filePath, importModule);
442
+ if (walked) resolved = walked;
443
+ }
444
+
259
445
  if (resolved && index.files.has(resolved)) {
260
446
  moduleResolved[importModule] = path.relative(index.root, resolved);
261
447
  // For Go, a package import means all files in that directory are dependencies
262
448
  // (Go packages span multiple files in the same directory)
263
449
  const filesToLink = javaWildcardFiles
264
450
  ? [...javaWildcardFiles]
265
- : csharpFiles ? [...csharpFiles] : [resolved];
451
+ : csharpFiles ? [...csharpFiles]
452
+ : rustModTargets?.length > 1 ? [...rustModTargets] : [resolved];
266
453
  if (langTraits(fileEntry.language)?.packageScope === 'directory') {
267
454
  const pkgDir = path.dirname(resolved);
268
455
  const dirFiles = dirToGoFiles.get(pkgDir) || [];
@@ -286,6 +473,18 @@ function buildImportGraph(index) {
286
473
  }
287
474
  }
288
475
 
476
+ // The written spelling of a relative C# directive maps to the file
477
+ // its resolved name reaches (alias bindings keep the written text).
478
+ if (csharpWritten) {
479
+ for (const [resolvedName, writtens] of csharpWritten) {
480
+ const rel = moduleResolved[resolvedName];
481
+ if (!rel) continue;
482
+ for (const written of writtens) {
483
+ if (!moduleResolved[written]) moduleResolved[written] = rel;
484
+ }
485
+ }
486
+ }
487
+
289
488
  // Java same-package visibility needs no import declaration. Add only
290
489
  // AST-proven type/static-qualifier references, not a package clique.
291
490
  if (fileEntry.language === 'java') {
@@ -315,7 +514,8 @@ function buildImportGraph(index) {
315
514
  const resolved = resolveImport(spec, filePath, {
316
515
  aliases: index.config.aliases,
317
516
  language: fileEntry.language,
318
- root: index.root
517
+ root: index.root,
518
+ probeCache: includeProbeCache,
319
519
  });
320
520
  if (resolved && index.files.has(resolved)) {
321
521
  moduleResolved[spec] = path.relative(index.root, resolved);
@@ -330,7 +530,74 @@ function buildImportGraph(index) {
330
530
 
331
531
  index.importGraph.set(filePath, importedFiles);
332
532
  fileEntry.moduleResolved = moduleResolved;
533
+ if (langTraits(fileEntry.language)?.hasReceiverPackageCalls) {
534
+ _applyGoImportNames(index, filePath, fileEntry, moduleResolved, dirToGoFiles);
535
+ }
536
+ if (Object.keys(includeFallback).length > 0) {
537
+ fileEntry.includeFallback = includeFallback;
538
+ } else {
539
+ delete fileEntry.includeFallback;
540
+ }
541
+ }
542
+ }
543
+
544
+ /**
545
+ * The local name of a Go import that writes none is the package clause of
546
+ * the package it imports, which its path only suggests (fix #400):
547
+ * `example.com/m/internal/go-utils` may declare `package utils`. For a
548
+ * project package that clause is known; an outside package keeps the
549
+ * parser's path-derived name, which no query treats as proof (an
550
+ * identifier the file does not otherwise bind may name such an import).
551
+ * The file's call records were read with the parser's names: when the
552
+ * resolved names differ they are read again on first use (a build's callee
553
+ * index reads them). The parser's suggestion follows the Go tools'
554
+ * convention (goPathPackageName), so few project packages need this.
555
+ */
556
+ function _applyGoImportNames(index, filePath, fileEntry, moduleResolved, dirToGoFiles) {
557
+ const details = fileEntry.importDetails || [];
558
+ const names = fileEntry.importNames || [];
559
+ if (details.length === 0 || names.length !== details.length) return;
560
+ let next = null;
561
+ for (let i = 0; i < details.length; i++) {
562
+ const detail = details[i];
563
+ let name = detail.names?.[0];
564
+ if (detail.implicitName && moduleResolved[detail.module]) {
565
+ const rel = moduleResolved[detail.module];
566
+ const dir = path.dirname(path.isAbsolute(rel) ? rel : path.join(index.root, rel));
567
+ const clause = (dirToGoFiles.get(dir) || []).map(file => index.files.get(file))
568
+ .find(entry => entry?.packageName && !entry.relativePath?.endsWith('_test.go') &&
569
+ !entry.packageName.endsWith('_test'))?.packageName;
570
+ if (clause) name = clause;
571
+ }
572
+ if (name && names[i] !== name) {
573
+ if (!next) next = names.slice();
574
+ next[i] = name;
575
+ }
576
+ }
577
+ if (!next) return;
578
+ const changed = new Set();
579
+ for (let i = 0; i < next.length; i++) {
580
+ if (next[i] !== names[i]) {
581
+ if (names[i]) changed.add(names[i]);
582
+ changed.add(next[i]);
583
+ }
584
+ }
585
+ fileEntry.importNames = next;
586
+ for (const binding of fileEntry.importBindings || []) {
587
+ const at = details.findIndex(detail => detail.module === binding.module && detail.line === binding.line);
588
+ if (at >= 0 && details[at].implicitName) binding.name = next[at];
589
+ }
590
+ // An import name is only ever written as a qualifier (`name.X`): records
591
+ // of a file that never writes one of the changed names are unchanged.
592
+ let content = '';
593
+ try { content = index._readFile(filePath); } catch { content = null; }
594
+ if (content != null && ![...changed].some(name => content.includes(`${name}.`))) return;
595
+ // Drop the records read with the old names (loading this file's shard
596
+ // first, so a later shard load cannot bring them back).
597
+ if (index._callsCachePrepared && !index._callsCacheLoaded) {
598
+ require('./cache').ensureCallsShardForFile(index, filePath);
333
599
  }
600
+ if (index.callsCache.delete(filePath)) index.callsCacheDirty = true;
334
601
  }
335
602
 
336
603
  /**
@@ -504,8 +771,10 @@ function splitParentList(clause) {
504
771
  module.exports = {
505
772
  buildDirIndex,
506
773
  buildImportGraph,
774
+ jsWorkspacePackagesOf,
507
775
  buildInheritanceGraph,
508
776
  splitParentList,
509
777
  _resolveJavaPackageImport,
510
778
  _resolveCSharpUsing,
779
+ _rustWalkUsePath,
511
780
  };
package/core/graph.js CHANGED
@@ -83,7 +83,8 @@ function imports(index, filePath) {
83
83
  let resolvedPath = resolveImport(imp.module, normalizedPath, {
84
84
  aliases: index.config.aliases,
85
85
  language: fileEntry.language,
86
- root: index.root
86
+ root: index.root,
87
+ workspacePackages: require('./graph-build').jsWorkspacePackagesOf(index),
87
88
  });
88
89
 
89
90
  // Java package imports: resolve by progressive suffix matching
@@ -358,7 +359,8 @@ function fileExports(index, filePath, _visited) {
358
359
  const resolvedSrc = resolveImport(exp.source, absPath, {
359
360
  language: fileEntry.language,
360
361
  root: index.root,
361
- extensions: index.extensions
362
+ extensions: index.extensions,
363
+ workspacePackages: require('./graph-build').jsWorkspacePackagesOf(index),
362
364
  });
363
365
  if (resolvedSrc) {
364
366
  const sourceEntry = index.files.get(resolvedSrc);
@@ -367,6 +369,8 @@ function fileExports(index, filePath, _visited) {
367
369
  if (exp.type === 're-export-all') {
368
370
  const sourceExportsResult = fileExports(index, resolvedSrc, visited);
369
371
  for (const srcExp of sourceExportsResult) {
372
+ // `export *` never re-exports a default export.
373
+ if (srcExp.isDefault) continue;
370
374
  if (!matchedNames.has(srcExp.name)) {
371
375
  matchedNames.add(srcExp.name);
372
376
  // The entry belongs to the BARREL file —
@@ -482,6 +486,35 @@ function fileExports(index, filePath, _visited) {
482
486
  }
483
487
  }
484
488
 
489
+ // fix #367d: default exports of a VALUE that is not an indexed symbol
490
+ // (`const axios = createInstance(); export default axios;`,
491
+ // `module.exports = instance`, `export default { ... }`). Import sites
492
+ // bind these; leaving them out made `api` report no exports at all.
493
+ if (fileEntry.exportDetails) {
494
+ const matched = new Set(results.map(r => r.sourceName || r.name));
495
+ for (const exp of fileEntry.exportDetails) {
496
+ const defaultExport = exp.type === 'default' ||
497
+ (exp.type === 'module.exports' && exp.defaultLike);
498
+ if (!defaultExport || !exp.name || matched.has(exp.name)) continue;
499
+ if (fileEntry.symbols.some(symbol => symbol.name === exp.name && !symbol.className)) continue;
500
+ matched.add(exp.name);
501
+ const anonymous = exp.type === 'default' && exp.name === 'default';
502
+ results.push({
503
+ name: exp.name,
504
+ type: 'variable',
505
+ file: fileEntry.relativePath,
506
+ startLine: exp.line,
507
+ endLine: exp.line,
508
+ params: undefined,
509
+ returnType: null,
510
+ signature: exp.type === 'module.exports'
511
+ ? `module.exports = ${exp.localName || exp.name}`
512
+ : anonymous ? 'export default' : `export default ${exp.name}`,
513
+ isDefault: true,
514
+ });
515
+ }
516
+ }
517
+
485
518
  // Python __all__ re-exports: names listed in __all__ that come from imports
486
519
  // e.g. __init__.py: `from .utils import helper` + `__all__ = ["helper"]`
487
520
  // `helper` is in fileEntry.exports but not in fileEntry.symbols