gitnexus 1.4.6 → 1.4.8

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 (99) hide show
  1. package/README.md +22 -1
  2. package/dist/cli/ai-context.d.ts +1 -1
  3. package/dist/cli/ai-context.js +1 -1
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +54 -21
  6. package/dist/cli/index.js +2 -1
  7. package/dist/cli/setup.js +78 -1
  8. package/dist/config/supported-languages.d.ts +30 -0
  9. package/dist/config/supported-languages.js +30 -0
  10. package/dist/core/embeddings/embedder.d.ts +6 -1
  11. package/dist/core/embeddings/embedder.js +65 -5
  12. package/dist/core/embeddings/embedding-pipeline.js +11 -9
  13. package/dist/core/embeddings/http-client.d.ts +31 -0
  14. package/dist/core/embeddings/http-client.js +179 -0
  15. package/dist/core/embeddings/index.d.ts +1 -0
  16. package/dist/core/embeddings/index.js +1 -0
  17. package/dist/core/embeddings/types.d.ts +1 -1
  18. package/dist/core/graph/types.d.ts +4 -3
  19. package/dist/core/ingestion/ast-helpers.d.ts +80 -0
  20. package/dist/core/ingestion/ast-helpers.js +738 -0
  21. package/dist/core/ingestion/call-analysis.d.ts +73 -0
  22. package/dist/core/ingestion/call-analysis.js +490 -0
  23. package/dist/core/ingestion/call-processor.d.ts +55 -2
  24. package/dist/core/ingestion/call-processor.js +673 -108
  25. package/dist/core/ingestion/call-routing.d.ts +23 -2
  26. package/dist/core/ingestion/call-routing.js +21 -0
  27. package/dist/core/ingestion/entry-point-scoring.js +36 -26
  28. package/dist/core/ingestion/framework-detection.d.ts +10 -2
  29. package/dist/core/ingestion/framework-detection.js +49 -12
  30. package/dist/core/ingestion/heritage-processor.js +47 -49
  31. package/dist/core/ingestion/import-processor.d.ts +1 -1
  32. package/dist/core/ingestion/import-processor.js +103 -194
  33. package/dist/core/ingestion/import-resolution.d.ts +101 -0
  34. package/dist/core/ingestion/import-resolution.js +251 -0
  35. package/dist/core/ingestion/language-config.d.ts +3 -0
  36. package/dist/core/ingestion/language-config.js +13 -0
  37. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  38. package/dist/core/ingestion/markdown-processor.js +124 -0
  39. package/dist/core/ingestion/mro-processor.js +8 -3
  40. package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
  41. package/dist/core/ingestion/named-binding-extraction.js +89 -79
  42. package/dist/core/ingestion/parsing-processor.d.ts +3 -2
  43. package/dist/core/ingestion/parsing-processor.js +27 -60
  44. package/dist/core/ingestion/pipeline.d.ts +10 -0
  45. package/dist/core/ingestion/pipeline.js +425 -4
  46. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  47. package/dist/core/ingestion/resolution-context.js +7 -4
  48. package/dist/core/ingestion/resolvers/index.d.ts +1 -1
  49. package/dist/core/ingestion/resolvers/index.js +1 -1
  50. package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
  51. package/dist/core/ingestion/resolvers/jvm.js +25 -9
  52. package/dist/core/ingestion/resolvers/php.d.ts +14 -0
  53. package/dist/core/ingestion/resolvers/php.js +43 -3
  54. package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
  55. package/dist/core/ingestion/resolvers/utils.js +16 -0
  56. package/dist/core/ingestion/symbol-table.d.ts +29 -3
  57. package/dist/core/ingestion/symbol-table.js +42 -9
  58. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
  59. package/dist/core/ingestion/tree-sitter-queries.js +243 -2
  60. package/dist/core/ingestion/type-env.d.ts +28 -1
  61. package/dist/core/ingestion/type-env.js +451 -72
  62. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  63. package/dist/core/ingestion/type-extractors/c-cpp.js +146 -2
  64. package/dist/core/ingestion/type-extractors/csharp.js +189 -16
  65. package/dist/core/ingestion/type-extractors/go.js +45 -0
  66. package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
  67. package/dist/core/ingestion/type-extractors/index.js +1 -1
  68. package/dist/core/ingestion/type-extractors/jvm.js +244 -69
  69. package/dist/core/ingestion/type-extractors/php.js +31 -4
  70. package/dist/core/ingestion/type-extractors/python.js +89 -17
  71. package/dist/core/ingestion/type-extractors/ruby.js +17 -2
  72. package/dist/core/ingestion/type-extractors/rust.js +72 -4
  73. package/dist/core/ingestion/type-extractors/shared.d.ts +12 -2
  74. package/dist/core/ingestion/type-extractors/shared.js +115 -13
  75. package/dist/core/ingestion/type-extractors/swift.js +7 -6
  76. package/dist/core/ingestion/type-extractors/types.d.ts +54 -11
  77. package/dist/core/ingestion/type-extractors/typescript.js +171 -9
  78. package/dist/core/ingestion/utils.d.ts +2 -95
  79. package/dist/core/ingestion/utils.js +3 -892
  80. package/dist/core/ingestion/workers/parse-worker.d.ts +36 -11
  81. package/dist/core/ingestion/workers/parse-worker.js +116 -95
  82. package/dist/core/lbug/csv-generator.js +18 -1
  83. package/dist/core/lbug/lbug-adapter.d.ts +12 -0
  84. package/dist/core/lbug/lbug-adapter.js +71 -4
  85. package/dist/core/lbug/schema.d.ts +6 -4
  86. package/dist/core/lbug/schema.js +27 -3
  87. package/dist/mcp/core/embedder.js +11 -3
  88. package/dist/mcp/core/lbug-adapter.d.ts +22 -0
  89. package/dist/mcp/core/lbug-adapter.js +178 -23
  90. package/dist/mcp/local/local-backend.d.ts +22 -0
  91. package/dist/mcp/local/local-backend.js +136 -32
  92. package/dist/mcp/resources.js +13 -0
  93. package/dist/mcp/server.js +26 -4
  94. package/dist/mcp/tools.js +17 -7
  95. package/dist/server/api.d.ts +19 -1
  96. package/dist/server/api.js +66 -6
  97. package/dist/storage/git.d.ts +12 -0
  98. package/dist/storage/git.js +21 -0
  99. package/package.json +12 -4
@@ -3,12 +3,11 @@ import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/pa
3
3
  import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
4
4
  import { generateId } from '../../lib/utils.js';
5
5
  import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop } from './utils.js';
6
- import { SupportedLanguages } from '../../config/supported-languages.js';
7
- import { extractNamedBindings } from './named-binding-extraction.js';
8
6
  import { getTreeSitterBufferSize } from './constants.js';
9
- import { loadTsconfigPaths, loadGoModulePath, loadComposerConfig, loadCSharpProjectConfig, loadSwiftPackageConfig, } from './language-config.js';
10
- import { buildSuffixIndex, resolveImportPath, appendKotlinWildcard, KOTLIN_EXTENSIONS, resolveJvmWildcard, resolveJvmMemberImport, resolveGoPackageDir, resolveGoPackage, resolveCSharpImport, resolveCSharpNamespaceDir, resolvePhpImport, resolveRustImport, resolveRubyImport, resolvePythonImport, } from './resolvers/index.js';
7
+ import { loadImportConfigs } from './language-config.js';
8
+ import { buildSuffixIndex } from './resolvers/index.js';
11
9
  import { callRouters } from './call-routing.js';
10
+ import { importResolvers, namedBindingExtractors, preprocessImportPath } from './import-resolution.js';
12
11
  const isDev = process.env.NODE_ENV === 'development';
13
12
  /**
14
13
  * Check if a file path is directly inside a package directory identified by its suffix.
@@ -26,121 +25,36 @@ export function buildImportResolutionContext(allPaths) {
26
25
  const allFileList = allPaths;
27
26
  const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
28
27
  const allFilePaths = new Set(allFileList);
29
- const suffixIndex = buildSuffixIndex(normalizedFileList, allFileList);
30
- return { allFilePaths, allFileList, normalizedFileList, suffixIndex, resolveCache: new Map() };
28
+ const index = buildSuffixIndex(normalizedFileList, allFileList);
29
+ return { allFilePaths, allFileList, normalizedFileList, index, resolveCache: new Map() };
31
30
  }
32
- /**
33
- * Shared language dispatch for import resolution.
34
- * Used by both processImports and processImportsFromExtracted.
35
- */
36
- function resolveLanguageImport(filePath, rawImportPath, language, configs, ctx) {
37
- const { allFilePaths, allFileList, normalizedFileList, index, resolveCache } = ctx;
38
- const { tsconfigPaths, goModule, composerConfig, swiftPackageConfig, csharpConfigs } = configs;
39
- // JVM languages (Java + Kotlin): handle wildcards and member imports
40
- if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
41
- const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
42
- if (rawImportPath.endsWith('.*')) {
43
- const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
44
- if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
45
- const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
46
- if (javaMatches.length > 0)
47
- return { kind: 'files', files: javaMatches };
48
- }
49
- if (matchedFiles.length > 0)
50
- return { kind: 'files', files: matchedFiles };
51
- // Fall through to standard resolution
52
- }
53
- else {
54
- let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
55
- if (!memberResolved && language === SupportedLanguages.Kotlin) {
56
- memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
57
- }
58
- if (memberResolved)
59
- return { kind: 'files', files: [memberResolved] };
60
- // Fall through to standard resolution
61
- }
62
- }
63
- // Go: handle package-level imports
64
- if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
65
- const pkgSuffix = resolveGoPackageDir(rawImportPath, goModule);
66
- if (pkgSuffix) {
67
- const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
68
- if (pkgFiles.length > 0) {
69
- return { kind: 'package', files: pkgFiles, dirSuffix: pkgSuffix };
70
- }
71
- }
72
- // Fall through if no files found (package might be external)
73
- }
74
- // C#: handle namespace-based imports (using directives)
75
- if (language === SupportedLanguages.CSharp && csharpConfigs.length > 0) {
76
- const resolvedFiles = resolveCSharpImport(rawImportPath, csharpConfigs, normalizedFileList, allFileList, index);
77
- if (resolvedFiles.length > 1) {
78
- const dirSuffix = resolveCSharpNamespaceDir(rawImportPath, csharpConfigs);
79
- if (dirSuffix) {
80
- return { kind: 'package', files: resolvedFiles, dirSuffix };
81
- }
82
- }
83
- if (resolvedFiles.length > 0)
84
- return { kind: 'files', files: resolvedFiles };
85
- return null;
86
- }
87
- // PHP: handle namespace-based imports (use statements)
88
- if (language === SupportedLanguages.PHP) {
89
- const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
90
- return resolved ? { kind: 'files', files: [resolved] } : null;
91
- }
92
- // Swift: handle module imports
93
- if (language === SupportedLanguages.Swift && swiftPackageConfig) {
94
- const targetDir = swiftPackageConfig.targets.get(rawImportPath);
95
- if (targetDir) {
96
- const dirPrefix = targetDir + '/';
97
- const files = [];
98
- for (let i = 0; i < normalizedFileList.length; i++) {
99
- if (normalizedFileList[i].startsWith(dirPrefix) && normalizedFileList[i].endsWith('.swift')) {
100
- files.push(allFileList[i]);
101
- }
102
- }
103
- if (files.length > 0)
104
- return { kind: 'files', files };
105
- }
106
- return null; // External framework (Foundation, UIKit, etc.)
107
- }
108
- // Python: relative imports (PEP 328) + proximity-based bare imports
109
- // Falls through to standard suffix resolution when proximity finds no match.
110
- if (language === SupportedLanguages.Python) {
111
- const resolved = resolvePythonImport(filePath, rawImportPath, allFilePaths);
112
- if (resolved)
113
- return { kind: 'files', files: [resolved] };
114
- if (rawImportPath.startsWith('.'))
115
- return null; // relative but unresolved — don't suffix-match
116
- }
117
- // Ruby: require / require_relative
118
- if (language === SupportedLanguages.Ruby) {
119
- const resolved = resolveRubyImport(rawImportPath, normalizedFileList, allFileList, index);
120
- return resolved ? { kind: 'files', files: [resolved] } : null;
121
- }
122
- // Rust: expand top-level grouped imports: use {crate::a, crate::b}
123
- if (language === SupportedLanguages.Rust && rawImportPath.startsWith('{') && rawImportPath.endsWith('}')) {
124
- const inner = rawImportPath.slice(1, -1);
125
- const parts = inner.split(',').map(p => p.trim()).filter(Boolean);
126
- const resolved = [];
127
- for (const part of parts) {
128
- const r = resolveRustImport(filePath, part, allFilePaths);
129
- if (r)
130
- resolved.push(r);
131
- }
132
- return resolved.length > 0 ? { kind: 'files', files: resolved } : null;
133
- }
134
- // Standard single-file resolution
135
- const resolvedPath = resolveImportPath(filePath, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
136
- return resolvedPath ? { kind: 'files', files: [resolvedPath] } : null;
31
+ // Config loaders extracted to ./language-config.ts (Phase 2 refactor)
32
+ // Resolver dispatch tables are in ./import-resolution.ts — imported above
33
+ /** Create IMPORTS edge helpers that share a resolved-count tracker. */
34
+ function createImportEdgeHelpers(graph, importMap) {
35
+ let totalImportsResolved = 0;
36
+ const addImportGraphEdge = (filePath, resolvedPath) => {
37
+ const sourceId = generateId('File', filePath);
38
+ const targetId = generateId('File', resolvedPath);
39
+ const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
40
+ totalImportsResolved++;
41
+ graph.addRelationship({ id: relId, sourceId, targetId, type: 'IMPORTS', confidence: 1.0, reason: '' });
42
+ };
43
+ const addImportEdge = (filePath, resolvedPath) => {
44
+ addImportGraphEdge(filePath, resolvedPath);
45
+ if (!importMap.has(filePath))
46
+ importMap.set(filePath, new Set());
47
+ importMap.get(filePath).add(resolvedPath);
48
+ };
49
+ return { addImportEdge, addImportGraphEdge, getResolvedCount: () => totalImportsResolved };
137
50
  }
138
51
  /**
139
52
  * Apply an ImportResult: emit graph edges and update ImportMap/PackageMap.
140
53
  * If namedBindings are provided and the import resolves to a single file,
141
54
  * also populate the NamedImportMap for precise Tier 2a resolution.
55
+ * Bindings tagged with `isModuleAlias` are routed to moduleAliasMap instead.
142
56
  */
143
- function applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, namedBindings, namedImportMap) {
57
+ function applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, namedBindings, namedImportMap, moduleAliasMap) {
144
58
  if (!result)
145
59
  return;
146
60
  if (result.kind === 'package' && packageMap) {
@@ -158,14 +72,66 @@ function applyImportResult(result, filePath, importMap, packageMap, addImportEdg
158
72
  for (const resolvedFile of files) {
159
73
  addImportEdge(filePath, resolvedFile);
160
74
  }
161
- // Record named bindings for precise Tier 2a resolution
162
- if (namedBindings && namedImportMap && files.length === 1) {
75
+ // Route module aliases (import X as Y) directly to moduleAliasMap.
76
+ // These are module-level aliases, not symbol bindings — they don't belong in namedImportMap.
77
+ if (namedBindings && moduleAliasMap && files.length === 1) {
163
78
  const resolvedFile = files[0];
79
+ for (const binding of namedBindings) {
80
+ if (!binding.isModuleAlias)
81
+ continue;
82
+ let aliasMap = moduleAliasMap.get(filePath);
83
+ if (!aliasMap) {
84
+ aliasMap = new Map();
85
+ moduleAliasMap.set(filePath, aliasMap);
86
+ }
87
+ aliasMap.set(binding.local, resolvedFile);
88
+ }
89
+ }
90
+ // Record named bindings for precise Tier 2a resolution.
91
+ // If the same local name is imported from multiple files (e.g., Java static imports
92
+ // of overloaded methods), remove the entry so resolution falls through to Tier 2a
93
+ // import-scoped which sees all candidates and can apply arity narrowing.
94
+ if (namedBindings && namedImportMap) {
164
95
  if (!namedImportMap.has(filePath))
165
96
  namedImportMap.set(filePath, new Map());
166
97
  const fileBindings = namedImportMap.get(filePath);
167
- for (const binding of namedBindings) {
168
- fileBindings.set(binding.local, { sourcePath: resolvedFile, exportedName: binding.exported });
98
+ if (files.length === 1) {
99
+ const resolvedFile = files[0];
100
+ for (const binding of namedBindings) {
101
+ if (binding.isModuleAlias)
102
+ continue; // already routed to moduleAliasMap
103
+ const existing = fileBindings.get(binding.local);
104
+ if (existing && existing.sourcePath !== resolvedFile) {
105
+ fileBindings.delete(binding.local);
106
+ }
107
+ else {
108
+ fileBindings.set(binding.local, { sourcePath: resolvedFile, exportedName: binding.exported });
109
+ }
110
+ }
111
+ }
112
+ else {
113
+ // Multi-file resolution (e.g., Rust `use crate::models::{User, Repo}`).
114
+ // Match each binding to a resolved file by comparing the lowercase binding name
115
+ // to the file's basename (without extension). If no match, skip the binding.
116
+ for (const binding of namedBindings) {
117
+ if (binding.isModuleAlias)
118
+ continue;
119
+ const lowerName = binding.exported.toLowerCase();
120
+ const matchedFile = files.find(f => {
121
+ const base = f.replace(/\\/g, '/').split('/').pop() ?? '';
122
+ const nameWithoutExt = base.substring(0, base.lastIndexOf('.')).toLowerCase();
123
+ return nameWithoutExt === lowerName;
124
+ });
125
+ if (matchedFile) {
126
+ const existing = fileBindings.get(binding.local);
127
+ if (existing && existing.sourcePath !== matchedFile) {
128
+ fileBindings.delete(binding.local);
129
+ }
130
+ else {
131
+ fileBindings.set(binding.local, { sourcePath: matchedFile, exportedName: binding.exported });
132
+ }
133
+ }
134
+ }
169
135
  }
170
136
  }
171
137
  }
@@ -177,6 +143,7 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
177
143
  const importMap = ctx.importMap;
178
144
  const packageMap = ctx.packageMap;
179
145
  const namedImportMap = ctx.namedImportMap;
146
+ const moduleAliasMap = ctx.moduleAliasMap;
180
147
  // Use allPaths (full repo) when available for cross-chunk resolution, else fall back to chunk files
181
148
  const allFileList = allPaths ?? files.map(f => f.path);
182
149
  const allFilePaths = new Set(allFileList);
@@ -190,40 +157,10 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
190
157
  const index = buildSuffixIndex(normalizedFileList, allFileList);
191
158
  // Track import statistics
192
159
  let totalImportsFound = 0;
193
- let totalImportsResolved = 0;
194
160
  // Load language-specific configs once before the file loop
195
- const effectiveRoot = repoRoot || '';
196
- const configs = {
197
- tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
198
- goModule: await loadGoModulePath(effectiveRoot),
199
- composerConfig: await loadComposerConfig(effectiveRoot),
200
- swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
201
- csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
202
- };
203
- const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
204
- // Helper: add an IMPORTS edge to the graph only (no ImportMap update)
205
- const addImportGraphEdge = (filePath, resolvedPath) => {
206
- const sourceId = generateId('File', filePath);
207
- const targetId = generateId('File', resolvedPath);
208
- const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
209
- totalImportsResolved++;
210
- graph.addRelationship({
211
- id: relId,
212
- sourceId,
213
- targetId,
214
- type: 'IMPORTS',
215
- confidence: 1.0,
216
- reason: '',
217
- });
218
- };
219
- // Helper: add an IMPORTS edge + update import map
220
- const addImportEdge = (filePath, resolvedPath) => {
221
- addImportGraphEdge(filePath, resolvedPath);
222
- if (!importMap.has(filePath)) {
223
- importMap.set(filePath, new Set());
224
- }
225
- importMap.get(filePath).add(resolvedPath);
226
- };
161
+ const configs = await loadImportConfigs(repoRoot || '');
162
+ const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache, configs };
163
+ const { addImportEdge, addImportGraphEdge, getResolvedCount } = createImportEdgeHelpers(graph, importMap);
227
164
  for (let i = 0; i < files.length; i++) {
228
165
  const file = files[i];
229
166
  onProgress?.(i + 1, files.length);
@@ -291,14 +228,14 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
291
228
  }
292
229
  return;
293
230
  }
294
- // Clean path (remove quotes and angle brackets for C/C++ includes)
295
- const rawImportPath = language === SupportedLanguages.Kotlin
296
- ? appendKotlinWildcard(sourceNode.text.replace(/['"<>]/g, ''), captureMap['import'])
297
- : sourceNode.text.replace(/['"<>]/g, '');
231
+ const rawImportPath = preprocessImportPath(sourceNode.text, captureMap['import'], language);
232
+ if (!rawImportPath)
233
+ return;
298
234
  totalImportsFound++;
299
- const result = resolveLanguageImport(file.path, rawImportPath, language, configs, resolveCtx);
300
- const bindings = namedImportMap ? extractNamedBindings(captureMap['import'], language) : undefined;
301
- applyImportResult(result, file.path, importMap, packageMap, addImportEdge, addImportGraphEdge, bindings, namedImportMap);
235
+ const result = importResolvers[language](rawImportPath, file.path, resolveCtx);
236
+ const extractor = namedBindingExtractors[language];
237
+ const bindings = namedImportMap && extractor ? extractor(captureMap['import']) : undefined;
238
+ applyImportResult(result, file.path, importMap, packageMap, addImportEdge, addImportGraphEdge, bindings, namedImportMap, moduleAliasMap);
302
239
  }
303
240
  // ---- Language-specific call-as-import routing (Ruby require, etc.) ----
304
241
  if (captureMap['call']) {
@@ -308,7 +245,7 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
308
245
  const routed = callRouter(callNameNode.text, captureMap['call']);
309
246
  if (routed && routed.kind === 'import') {
310
247
  totalImportsFound++;
311
- const result = resolveLanguageImport(file.path, routed.importPath, language, configs, resolveCtx);
248
+ const result = importResolvers[language](routed.importPath, file.path, resolveCtx);
312
249
  applyImportResult(result, file.path, importMap, packageMap, addImportEdge, addImportGraphEdge);
313
250
  }
314
251
  }
@@ -322,7 +259,7 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
322
259
  }
323
260
  }
324
261
  if (isDev) {
325
- console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
262
+ console.log(`📊 Import processing complete: ${getResolvedCount()}/${totalImportsFound} imports resolved to graph edges`);
326
263
  }
327
264
  };
328
265
  // ============================================================================
@@ -332,41 +269,13 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
332
269
  const importMap = ctx.importMap;
333
270
  const packageMap = ctx.packageMap;
334
271
  const namedImportMap = ctx.namedImportMap;
272
+ const moduleAliasMap = ctx.moduleAliasMap;
335
273
  const importCtx = prebuiltCtx ?? buildImportResolutionContext(files.map(f => f.path));
336
- const { allFilePaths, allFileList, normalizedFileList, suffixIndex: index, resolveCache } = importCtx;
274
+ const { allFilePaths, allFileList, normalizedFileList, index, resolveCache } = importCtx;
337
275
  let totalImportsFound = 0;
338
- let totalImportsResolved = 0;
339
- const effectiveRoot = repoRoot || '';
340
- const configs = {
341
- tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
342
- goModule: await loadGoModulePath(effectiveRoot),
343
- composerConfig: await loadComposerConfig(effectiveRoot),
344
- swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
345
- csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
346
- };
347
- const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
348
- // Helper: add an IMPORTS edge to the graph only (no ImportMap update)
349
- const addImportGraphEdge = (filePath, resolvedPath) => {
350
- const sourceId = generateId('File', filePath);
351
- const targetId = generateId('File', resolvedPath);
352
- const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
353
- totalImportsResolved++;
354
- graph.addRelationship({
355
- id: relId,
356
- sourceId,
357
- targetId,
358
- type: 'IMPORTS',
359
- confidence: 1.0,
360
- reason: '',
361
- });
362
- };
363
- const addImportEdge = (filePath, resolvedPath) => {
364
- addImportGraphEdge(filePath, resolvedPath);
365
- if (!importMap.has(filePath)) {
366
- importMap.set(filePath, new Set());
367
- }
368
- importMap.get(filePath).add(resolvedPath);
369
- };
276
+ const configs = await loadImportConfigs(repoRoot || '');
277
+ const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache, configs };
278
+ const { addImportEdge, addImportGraphEdge, getResolvedCount } = createImportEdgeHelpers(graph, importMap);
370
279
  // Group by file for progress reporting (users see file count, not import count)
371
280
  const importsByFile = new Map();
372
281
  for (const imp of extractedImports) {
@@ -387,12 +296,12 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
387
296
  }
388
297
  for (const imp of fileImports) {
389
298
  totalImportsFound++;
390
- const result = resolveLanguageImport(filePath, imp.rawImportPath, imp.language, configs, resolveCtx);
391
- applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, imp.namedBindings, namedImportMap);
299
+ const result = importResolvers[imp.language](imp.rawImportPath, filePath, resolveCtx);
300
+ applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, imp.namedBindings, namedImportMap, moduleAliasMap);
392
301
  }
393
302
  }
394
303
  onProgress?.(totalFiles, totalFiles);
395
304
  if (isDev) {
396
- console.log(`📊 Import processing (fast path): ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
305
+ console.log(`📊 Import processing (fast path): ${getResolvedCount()}/${totalImportsFound} imports resolved to graph edges`);
397
306
  }
398
307
  };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Import Resolution Dispatch
3
+ *
4
+ * Per-language dispatch table for import resolution and named binding extraction.
5
+ * Replaces the 120-line if-chain in resolveLanguageImport() and the 7-branch
6
+ * dispatch in extractNamedBindings() with a single table lookup each.
7
+ *
8
+ * Follows the existing ExportChecker / CallRouter pattern:
9
+ * - Function aliases (not interfaces) to avoid megamorphic inline-cache issues
10
+ * - `satisfies Record<SupportedLanguages, ...>` for compile-time exhaustiveness
11
+ * - Const dispatch table — configs are accessed via ctx.configs at call time
12
+ */
13
+ import { SupportedLanguages } from '../../config/supported-languages.js';
14
+ import type { SyntaxNode } from './utils.js';
15
+ import type { TsconfigPaths, GoModuleConfig, CSharpProjectConfig, ComposerConfig } from './resolvers/index.js';
16
+ import type { SwiftPackageConfig } from './language-config.js';
17
+ import { extractTsNamedBindings, extractPythonNamedBindings, extractKotlinNamedBindings, extractRustNamedBindings, extractPhpNamedBindings, extractCsharpNamedBindings, extractJavaNamedBindings } from './named-binding-extraction.js';
18
+ import type { ImportResolutionContext } from './import-processor.js';
19
+ /**
20
+ * Result of resolving an import via language-specific dispatch.
21
+ * - 'files': resolved to one or more files -> add to ImportMap
22
+ * - 'package': resolved to a directory -> add graph edges + store dirSuffix in PackageMap
23
+ * - null: no resolution (external dependency, etc.)
24
+ */
25
+ export type ImportResult = {
26
+ kind: 'files';
27
+ files: string[];
28
+ } | {
29
+ kind: 'package';
30
+ files: string[];
31
+ dirSuffix: string;
32
+ } | null;
33
+ /** Bundled language-specific configs loaded once per ingestion run. */
34
+ export interface ImportConfigs {
35
+ tsconfigPaths: TsconfigPaths | null;
36
+ goModule: GoModuleConfig | null;
37
+ composerConfig: ComposerConfig | null;
38
+ swiftPackageConfig: SwiftPackageConfig | null;
39
+ csharpConfigs: CSharpProjectConfig[];
40
+ }
41
+ /** Full context for import resolution: file lookups + language configs. */
42
+ export interface ResolveCtx extends ImportResolutionContext {
43
+ configs: ImportConfigs;
44
+ }
45
+ /** Per-language import resolver -- function alias matching ExportChecker/CallRouter pattern. */
46
+ export type ImportResolverFn = (rawImportPath: string, filePath: string, resolveCtx: ResolveCtx) => ImportResult;
47
+ /** A single named import binding: local name in the importing file and exported name from the source.
48
+ * When `isModuleAlias` is true, the binding represents a Python `import X as Y` module alias
49
+ * and is routed to moduleAliasMap instead of namedImportMap during import processing. */
50
+ export interface NamedBinding {
51
+ local: string;
52
+ exported: string;
53
+ isModuleAlias?: boolean;
54
+ }
55
+ /**
56
+ * Clean and preprocess a raw import source text into a resolved import path.
57
+ * Strips quotes/angle brackets (universal) and applies language-specific
58
+ * transformations (currently only Kotlin wildcard import detection).
59
+ */
60
+ export declare function preprocessImportPath(sourceText: string, importNode: SyntaxNode, language: SupportedLanguages): string | null;
61
+ /**
62
+ * Per-language import resolver dispatch table.
63
+ * Configs are accessed via ctx.configs at call time — no factory closure needed.
64
+ * Each resolver encapsulates the full resolution flow for its language, including
65
+ * fallthrough to standard resolution where appropriate.
66
+ */
67
+ export declare const importResolvers: {
68
+ javascript: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
69
+ typescript: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
70
+ python: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
71
+ java: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
72
+ c: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
73
+ cpp: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
74
+ csharp: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
75
+ go: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
76
+ ruby: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
77
+ rust: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
78
+ php: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
79
+ kotlin: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
80
+ swift: (raw: string, fp: string, ctx: ResolveCtx) => ImportResult;
81
+ };
82
+ /**
83
+ * Per-language named binding extractor dispatch table.
84
+ * Languages with whole-module import semantics (Go, Ruby, C/C++, Swift) return undefined --
85
+ * their bindings are synthesized post-parse by synthesizeWildcardImportBindings() in pipeline.ts.
86
+ */
87
+ export declare const namedBindingExtractors: {
88
+ javascript: typeof extractTsNamedBindings;
89
+ typescript: typeof extractTsNamedBindings;
90
+ python: typeof extractPythonNamedBindings;
91
+ java: typeof extractJavaNamedBindings;
92
+ c: any;
93
+ cpp: any;
94
+ csharp: typeof extractCsharpNamedBindings;
95
+ go: any;
96
+ ruby: any;
97
+ rust: typeof extractRustNamedBindings;
98
+ php: typeof extractPhpNamedBindings;
99
+ kotlin: typeof extractKotlinNamedBindings;
100
+ swift: any;
101
+ };