gitnexus 1.3.10 → 1.4.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 (97) hide show
  1. package/README.md +22 -2
  2. package/dist/cli/ai-context.d.ts +2 -1
  3. package/dist/cli/ai-context.js +33 -6
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +20 -2
  6. package/dist/cli/index.js +2 -0
  7. package/dist/cli/setup.js +17 -19
  8. package/dist/cli/skill-gen.d.ts +26 -0
  9. package/dist/cli/skill-gen.js +549 -0
  10. package/dist/core/graph/types.d.ts +5 -2
  11. package/dist/core/ingestion/call-processor.d.ts +5 -5
  12. package/dist/core/ingestion/call-processor.js +173 -260
  13. package/dist/core/ingestion/constants.d.ts +16 -0
  14. package/dist/core/ingestion/constants.js +16 -0
  15. package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
  16. package/dist/core/ingestion/entry-point-scoring.js +81 -22
  17. package/dist/core/ingestion/export-detection.d.ts +18 -0
  18. package/dist/core/ingestion/export-detection.js +230 -0
  19. package/dist/core/ingestion/framework-detection.d.ts +5 -1
  20. package/dist/core/ingestion/framework-detection.js +39 -8
  21. package/dist/core/ingestion/heritage-processor.d.ts +13 -4
  22. package/dist/core/ingestion/heritage-processor.js +92 -28
  23. package/dist/core/ingestion/import-processor.d.ts +17 -19
  24. package/dist/core/ingestion/import-processor.js +170 -695
  25. package/dist/core/ingestion/language-config.d.ts +46 -0
  26. package/dist/core/ingestion/language-config.js +167 -0
  27. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  28. package/dist/core/ingestion/mro-processor.js +369 -0
  29. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  30. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  31. package/dist/core/ingestion/parsing-processor.d.ts +1 -10
  32. package/dist/core/ingestion/parsing-processor.js +41 -177
  33. package/dist/core/ingestion/pipeline.js +41 -26
  34. package/dist/core/ingestion/process-processor.js +2 -1
  35. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  36. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  37. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  38. package/dist/core/ingestion/resolvers/go.js +42 -0
  39. package/dist/core/ingestion/resolvers/index.d.ts +16 -0
  40. package/dist/core/ingestion/resolvers/index.js +11 -0
  41. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  42. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  43. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  44. package/dist/core/ingestion/resolvers/php.js +35 -0
  45. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  46. package/dist/core/ingestion/resolvers/rust.js +73 -0
  47. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  48. package/dist/core/ingestion/resolvers/standard.js +145 -0
  49. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  50. package/dist/core/ingestion/resolvers/utils.js +120 -0
  51. package/dist/core/ingestion/symbol-resolver.d.ts +32 -0
  52. package/dist/core/ingestion/symbol-resolver.js +83 -0
  53. package/dist/core/ingestion/symbol-table.d.ts +12 -1
  54. package/dist/core/ingestion/symbol-table.js +19 -12
  55. package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
  56. package/dist/core/ingestion/tree-sitter-queries.js +114 -9
  57. package/dist/core/ingestion/type-env.d.ts +27 -0
  58. package/dist/core/ingestion/type-env.js +86 -0
  59. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  60. package/dist/core/ingestion/type-extractors/c-cpp.js +60 -0
  61. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  62. package/dist/core/ingestion/type-extractors/csharp.js +89 -0
  63. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  64. package/dist/core/ingestion/type-extractors/go.js +105 -0
  65. package/dist/core/ingestion/type-extractors/index.d.ts +21 -0
  66. package/dist/core/ingestion/type-extractors/index.js +29 -0
  67. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  68. package/dist/core/ingestion/type-extractors/jvm.js +121 -0
  69. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  70. package/dist/core/ingestion/type-extractors/php.js +31 -0
  71. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  72. package/dist/core/ingestion/type-extractors/python.js +41 -0
  73. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  74. package/dist/core/ingestion/type-extractors/rust.js +39 -0
  75. package/dist/core/ingestion/type-extractors/shared.d.ts +17 -0
  76. package/dist/core/ingestion/type-extractors/shared.js +97 -0
  77. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  78. package/dist/core/ingestion/type-extractors/swift.js +43 -0
  79. package/dist/core/ingestion/type-extractors/types.d.ts +14 -0
  80. package/dist/core/ingestion/type-extractors/types.js +1 -0
  81. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  82. package/dist/core/ingestion/type-extractors/typescript.js +46 -0
  83. package/dist/core/ingestion/utils.d.ts +67 -0
  84. package/dist/core/ingestion/utils.js +691 -4
  85. package/dist/core/ingestion/workers/parse-worker.d.ts +20 -3
  86. package/dist/core/ingestion/workers/parse-worker.js +84 -345
  87. package/dist/core/ingestion/workers/worker-pool.js +8 -0
  88. package/dist/core/kuzu/csv-generator.js +19 -3
  89. package/dist/core/kuzu/kuzu-adapter.js +5 -2
  90. package/dist/core/kuzu/schema.d.ts +3 -3
  91. package/dist/core/kuzu/schema.js +16 -1
  92. package/dist/core/search/bm25-index.js +2 -1
  93. package/dist/mcp/core/kuzu-adapter.js +6 -18
  94. package/dist/mcp/tools.js +12 -3
  95. package/hooks/claude/gitnexus-hook.cjs +149 -66
  96. package/package.json +1 -1
  97. package/skills/gitnexus-cli.md +1 -1
@@ -1,9 +1,10 @@
1
1
  import { createKnowledgeGraph } from '../graph/graph.js';
2
2
  import { processStructure } from './structure-processor.js';
3
3
  import { processParsing } from './parsing-processor.js';
4
- import { processImports, processImportsFromExtracted, createImportMap, buildImportResolutionContext } from './import-processor.js';
4
+ import { processImports, processImportsFromExtracted, createImportMap, createPackageMap, createNamedImportMap, buildImportResolutionContext } from './import-processor.js';
5
5
  import { processCalls, processCallsFromExtracted, processRoutesFromExtracted } from './call-processor.js';
6
6
  import { processHeritage, processHeritageFromExtracted } from './heritage-processor.js';
7
+ import { computeMRO } from './mro-processor.js';
7
8
  import { processCommunities } from './community-processor.js';
8
9
  import { processProcesses } from './process-processor.js';
9
10
  import { createSymbolTable } from './symbol-table.js';
@@ -12,6 +13,9 @@ import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
12
13
  import { getLanguageFromFilename } from './utils.js';
13
14
  import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
14
15
  import { createWorkerPool } from './workers/worker-pool.js';
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath, pathToFileURL } from 'node:url';
15
19
  const isDev = process.env.NODE_ENV === 'development';
16
20
  /** Max bytes of source content to load per parse chunk. Each chunk's source +
17
21
  * parsed ASTs + extracted records + worker serialization overhead all live in
@@ -25,6 +29,8 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
25
29
  const symbolTable = createSymbolTable();
26
30
  let astCache = createASTCache(AST_CACHE_CAP);
27
31
  const importMap = createImportMap();
32
+ const packageMap = createPackageMap();
33
+ const namedImportMap = createNamedImportMap();
28
34
  const cleanup = () => {
29
35
  astCache.clear();
30
36
  symbolTable.clear();
@@ -124,11 +130,21 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
124
130
  // Create worker pool once, reuse across chunks
125
131
  let workerPool;
126
132
  try {
127
- const workerUrl = new URL('./workers/parse-worker.js', import.meta.url);
133
+ let workerUrl = new URL('./workers/parse-worker.js', import.meta.url);
134
+ // When running under vitest, import.meta.url points to src/ where no .js exists.
135
+ // Fall back to the compiled dist/ worker so the pool can spawn real worker threads.
136
+ const thisDir = fileURLToPath(new URL('.', import.meta.url));
137
+ if (!fs.existsSync(fileURLToPath(workerUrl))) {
138
+ const distWorker = path.resolve(thisDir, '..', '..', '..', 'dist', 'core', 'ingestion', 'workers', 'parse-worker.js');
139
+ if (fs.existsSync(distWorker)) {
140
+ workerUrl = pathToFileURL(distWorker);
141
+ }
142
+ }
128
143
  workerPool = createWorkerPool(workerUrl);
129
144
  }
130
145
  catch (err) {
131
- // Worker pool creation failed — sequential fallback
146
+ if (isDev)
147
+ console.warn('Worker pool creation failed, using sequential fallback:', err.message);
132
148
  }
133
149
  let filesParsedSoFar = 0;
134
150
  // AST cache sized for one chunk (sequential fallback uses it for import/call/heritage)
@@ -165,22 +181,18 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
165
181
  }, workerPool);
166
182
  if (chunkWorkerData) {
167
183
  // Imports
168
- await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, importMap, undefined, repoPath, importCtx);
169
- // Calls — resolve immediately, then free the array
170
- if (chunkWorkerData.calls.length > 0) {
171
- await processCallsFromExtracted(graph, chunkWorkerData.calls, symbolTable, importMap);
172
- }
173
- // Heritage resolve immediately, then free
174
- if (chunkWorkerData.heritage.length > 0) {
175
- await processHeritageFromExtracted(graph, chunkWorkerData.heritage, symbolTable);
176
- }
177
- // Routes — resolve immediately (Laravel route→controller CALLS edges)
178
- if (chunkWorkerData.routes && chunkWorkerData.routes.length > 0) {
179
- await processRoutesFromExtracted(graph, chunkWorkerData.routes, symbolTable, importMap);
180
- }
184
+ await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, importMap, undefined, repoPath, importCtx, packageMap, namedImportMap);
185
+ // Calls + Heritage + Routes — resolve in parallel (no shared mutable state between them)
186
+ // This is safe because each writes disjoint relationship types into idempotent id-keyed Maps,
187
+ // and the single-threaded event loop prevents races between synchronous addRelationship calls.
188
+ await Promise.all([
189
+ processCallsFromExtracted(graph, chunkWorkerData.calls, symbolTable, importMap, packageMap, undefined, namedImportMap),
190
+ processHeritageFromExtracted(graph, chunkWorkerData.heritage, symbolTable, importMap, packageMap),
191
+ processRoutesFromExtracted(graph, chunkWorkerData.routes ?? [], symbolTable, importMap, packageMap),
192
+ ]);
181
193
  }
182
194
  else {
183
- await processImports(graph, chunkFiles, astCache, importMap, undefined, repoPath, allPaths);
195
+ await processImports(graph, chunkFiles, astCache, importMap, undefined, repoPath, allPaths, packageMap, namedImportMap);
184
196
  sequentialChunkPaths.push(chunkPaths);
185
197
  }
186
198
  filesParsedSoFar += chunkFiles.length;
@@ -199,8 +211,8 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
199
211
  .filter(p => chunkContents.has(p))
200
212
  .map(p => ({ path: p, content: chunkContents.get(p) }));
201
213
  astCache = createASTCache(chunkFiles.length);
202
- await processCalls(graph, chunkFiles, astCache, symbolTable, importMap);
203
- await processHeritage(graph, chunkFiles, astCache, symbolTable);
214
+ await processCalls(graph, chunkFiles, astCache, symbolTable, importMap, packageMap, undefined, namedImportMap);
215
+ await processHeritage(graph, chunkFiles, astCache, symbolTable, importMap, packageMap);
204
216
  astCache.clear();
205
217
  }
206
218
  // Free import resolution context — suffix index + resolve cache no longer needed
@@ -209,13 +221,16 @@ export const runPipelineFromRepo = async (repoPath, onProgress) => {
209
221
  importCtx.resolveCache.clear();
210
222
  importCtx.suffixIndex = null;
211
223
  importCtx.normalizedFileList = null;
212
- if (isDev) {
213
- let importsCount = 0;
214
- for (const r of graph.iterRelationships()) {
215
- if (r.type === 'IMPORTS')
216
- importsCount++;
217
- }
218
- console.log(`📊 Pipeline: graph has ${importsCount} IMPORTS, ${graph.relationshipCount} total relationships`);
224
+ // ── Phase 4.5: Method Resolution Order ──────────────────────────────
225
+ onProgress({
226
+ phase: 'parsing',
227
+ percent: 81,
228
+ message: 'Computing method resolution order...',
229
+ stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
230
+ });
231
+ const mroResult = computeMRO(graph);
232
+ if (isDev && mroResult.entries.length > 0) {
233
+ console.log(`🔀 MRO: ${mroResult.entries.length} classes analyzed, ${mroResult.ambiguityCount} ambiguities found, ${mroResult.overrideEdges} OVERRIDES edges`);
219
234
  }
220
235
  // ── Phase 5: Communities ───────────────────────────────────────────
221
236
  onProgress({
@@ -10,6 +10,7 @@
10
10
  * Processes help agents understand how features work through the codebase.
11
11
  */
12
12
  import { calculateEntryPointScore, isTestFile } from './entry-point-scoring.js';
13
+ import { SupportedLanguages } from '../../config/supported-languages.js';
13
14
  const isDev = process.env.NODE_ENV === 'development';
14
15
  const DEFAULT_CONFIG = {
15
16
  maxTraceDepth: 10,
@@ -178,7 +179,7 @@ const findEntryPoints = (graph, reverseCallsEdges, callsEdges) => {
178
179
  if (callees.length === 0)
179
180
  continue;
180
181
  // Calculate entry point score using new scoring system
181
- const { score: baseScore, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language || 'javascript', node.properties.isExported ?? false, callers.length, callees.length, filePath // Pass filePath for framework detection
182
+ const { score: baseScore, reasons } = calculateEntryPointScore(node.properties.name, node.properties.language ?? SupportedLanguages.JavaScript, node.properties.isExported ?? false, callers.length, callees.length, filePath // Pass filePath for framework detection
182
183
  );
183
184
  let score = baseScore;
184
185
  const astFrameworkMultiplier = node.properties.astFrameworkMultiplier ?? 1.0;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * C# namespace import resolution.
3
+ * Handles using-directive resolution via .csproj root namespace stripping.
4
+ */
5
+ import type { SuffixIndex } from './utils.js';
6
+ /** C# project config parsed from .csproj files */
7
+ export interface CSharpProjectConfig {
8
+ /** Root namespace from <RootNamespace> or assembly name (default: project directory name) */
9
+ rootNamespace: string;
10
+ /** Directory containing the .csproj file */
11
+ projectDir: string;
12
+ }
13
+ /**
14
+ * Resolve a C# using-directive import path to matching .cs files.
15
+ * Tries single-file match first, then directory match for namespace imports.
16
+ */
17
+ export declare function resolveCSharpImport(importPath: string, csharpConfigs: CSharpProjectConfig[], normalizedFileList: string[], allFileList: string[], index?: SuffixIndex): string[];
18
+ /**
19
+ * Compute the directory suffix for a C# namespace import (for PackageMap).
20
+ * Returns a suffix like "/ProjectDir/Models/" or null if no config matches.
21
+ */
22
+ export declare function resolveCSharpNamespaceDir(importPath: string, csharpConfigs: CSharpProjectConfig[]): string | null;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * C# namespace import resolution.
3
+ * Handles using-directive resolution via .csproj root namespace stripping.
4
+ */
5
+ import { suffixResolve } from './utils.js';
6
+ /**
7
+ * Resolve a C# using-directive import path to matching .cs files.
8
+ * Tries single-file match first, then directory match for namespace imports.
9
+ */
10
+ export function resolveCSharpImport(importPath, csharpConfigs, normalizedFileList, allFileList, index) {
11
+ const namespacePath = importPath.replace(/\./g, '/');
12
+ const results = [];
13
+ for (const config of csharpConfigs) {
14
+ const nsPath = config.rootNamespace.replace(/\./g, '/');
15
+ let relative;
16
+ if (namespacePath.startsWith(nsPath + '/')) {
17
+ relative = namespacePath.slice(nsPath.length + 1);
18
+ }
19
+ else if (namespacePath === nsPath) {
20
+ // The import IS the root namespace — resolve to all .cs files in project root
21
+ relative = '';
22
+ }
23
+ else {
24
+ continue;
25
+ }
26
+ const dirPrefix = config.projectDir
27
+ ? (relative ? config.projectDir + '/' + relative : config.projectDir)
28
+ : relative;
29
+ // 1. Try as single file: relative.cs (e.g., "Models/DlqMessage.cs")
30
+ if (relative) {
31
+ const candidate = dirPrefix + '.cs';
32
+ if (index) {
33
+ const result = index.get(candidate) || index.getInsensitive(candidate);
34
+ if (result)
35
+ return [result];
36
+ }
37
+ // Also try suffix match
38
+ const suffixResult = index?.get(relative + '.cs') || index?.getInsensitive(relative + '.cs');
39
+ if (suffixResult)
40
+ return [suffixResult];
41
+ }
42
+ // 2. Try as directory: all .cs files directly inside (namespace import)
43
+ if (index) {
44
+ const dirFiles = index.getFilesInDir(dirPrefix, '.cs');
45
+ for (const f of dirFiles) {
46
+ const normalized = f.replace(/\\/g, '/');
47
+ // Check it's a direct child by finding the dirPrefix and ensuring no deeper slashes
48
+ const prefixIdx = normalized.indexOf(dirPrefix + '/');
49
+ if (prefixIdx < 0)
50
+ continue;
51
+ const afterDir = normalized.substring(prefixIdx + dirPrefix.length + 1);
52
+ if (!afterDir.includes('/')) {
53
+ results.push(f);
54
+ }
55
+ }
56
+ if (results.length > 0)
57
+ return results;
58
+ }
59
+ // 3. Linear scan fallback for directory matching
60
+ if (results.length === 0) {
61
+ const dirTrail = dirPrefix + '/';
62
+ for (let i = 0; i < normalizedFileList.length; i++) {
63
+ const normalized = normalizedFileList[i];
64
+ if (!normalized.endsWith('.cs'))
65
+ continue;
66
+ const prefixIdx = normalized.indexOf(dirTrail);
67
+ if (prefixIdx < 0)
68
+ continue;
69
+ const afterDir = normalized.substring(prefixIdx + dirTrail.length);
70
+ if (!afterDir.includes('/')) {
71
+ results.push(allFileList[i]);
72
+ }
73
+ }
74
+ if (results.length > 0)
75
+ return results;
76
+ }
77
+ }
78
+ // Fallback: suffix matching without namespace stripping (single file)
79
+ const pathParts = namespacePath.split('/').filter(Boolean);
80
+ const fallback = suffixResolve(pathParts, normalizedFileList, allFileList, index);
81
+ return fallback ? [fallback] : [];
82
+ }
83
+ /**
84
+ * Compute the directory suffix for a C# namespace import (for PackageMap).
85
+ * Returns a suffix like "/ProjectDir/Models/" or null if no config matches.
86
+ */
87
+ export function resolveCSharpNamespaceDir(importPath, csharpConfigs) {
88
+ const namespacePath = importPath.replace(/\./g, '/');
89
+ for (const config of csharpConfigs) {
90
+ const nsPath = config.rootNamespace.replace(/\./g, '/');
91
+ let relative;
92
+ if (namespacePath.startsWith(nsPath + '/')) {
93
+ relative = namespacePath.slice(nsPath.length + 1);
94
+ }
95
+ else if (namespacePath === nsPath) {
96
+ relative = '';
97
+ }
98
+ else {
99
+ continue;
100
+ }
101
+ const dirPrefix = config.projectDir
102
+ ? (relative ? config.projectDir + '/' + relative : config.projectDir)
103
+ : relative;
104
+ if (!dirPrefix)
105
+ continue;
106
+ return '/' + dirPrefix + '/';
107
+ }
108
+ return null;
109
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Go package import resolution.
3
+ * Handles Go module path-based package imports.
4
+ */
5
+ /** Go module config parsed from go.mod */
6
+ export interface GoModuleConfig {
7
+ /** Module path (e.g., "github.com/user/repo") */
8
+ modulePath: string;
9
+ }
10
+ /**
11
+ * Extract the package directory suffix from a Go import path.
12
+ * Returns the suffix string (e.g., "/internal/auth/") or null if invalid.
13
+ */
14
+ export declare function resolveGoPackageDir(importPath: string, goModule: GoModuleConfig): string | null;
15
+ /**
16
+ * Resolve a Go internal package import to all .go files in the package directory.
17
+ * Returns an array of file paths.
18
+ */
19
+ export declare function resolveGoPackage(importPath: string, goModule: GoModuleConfig, normalizedFileList: string[], allFileList: string[]): string[];
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Go package import resolution.
3
+ * Handles Go module path-based package imports.
4
+ */
5
+ /**
6
+ * Extract the package directory suffix from a Go import path.
7
+ * Returns the suffix string (e.g., "/internal/auth/") or null if invalid.
8
+ */
9
+ export function resolveGoPackageDir(importPath, goModule) {
10
+ if (!importPath.startsWith(goModule.modulePath))
11
+ return null;
12
+ const relativePkg = importPath.slice(goModule.modulePath.length + 1);
13
+ if (!relativePkg)
14
+ return null;
15
+ return '/' + relativePkg + '/';
16
+ }
17
+ /**
18
+ * Resolve a Go internal package import to all .go files in the package directory.
19
+ * Returns an array of file paths.
20
+ */
21
+ export function resolveGoPackage(importPath, goModule, normalizedFileList, allFileList) {
22
+ if (!importPath.startsWith(goModule.modulePath))
23
+ return [];
24
+ // Strip module path to get relative package path
25
+ const relativePkg = importPath.slice(goModule.modulePath.length + 1); // e.g., "internal/auth"
26
+ if (!relativePkg)
27
+ return [];
28
+ const pkgSuffix = '/' + relativePkg + '/';
29
+ const matches = [];
30
+ for (let i = 0; i < normalizedFileList.length; i++) {
31
+ // Prepend '/' so paths like "internal/auth/service.go" match suffix "/internal/auth/"
32
+ const normalized = '/' + normalizedFileList[i];
33
+ // File must be directly in the package directory (not a subdirectory)
34
+ if (normalized.includes(pkgSuffix) && normalized.endsWith('.go') && !normalized.endsWith('_test.go')) {
35
+ const afterPkg = normalized.substring(normalized.indexOf(pkgSuffix) + pkgSuffix.length);
36
+ if (!afterPkg.includes('/')) {
37
+ matches.push(allFileList[i]);
38
+ }
39
+ }
40
+ }
41
+ return matches;
42
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Language-specific import resolvers.
3
+ * Extracted from import-processor.ts for maintainability.
4
+ */
5
+ export { EXTENSIONS, tryResolveWithExtensions, buildSuffixIndex, suffixResolve } from './utils.js';
6
+ export type { SuffixIndex } from './utils.js';
7
+ export { KOTLIN_EXTENSIONS, appendKotlinWildcard, resolveJvmWildcard, resolveJvmMemberImport } from './jvm.js';
8
+ export { resolveGoPackageDir, resolveGoPackage } from './go.js';
9
+ export type { GoModuleConfig } from './go.js';
10
+ export { resolveCSharpImport, resolveCSharpNamespaceDir } from './csharp.js';
11
+ export type { CSharpProjectConfig } from './csharp.js';
12
+ export { resolvePhpImport } from './php.js';
13
+ export type { ComposerConfig } from './php.js';
14
+ export { resolveRustImport, tryRustModulePath } from './rust.js';
15
+ export { resolveImportPath, RESOLVE_CACHE_CAP } from './standard.js';
16
+ export type { TsconfigPaths } from './standard.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Language-specific import resolvers.
3
+ * Extracted from import-processor.ts for maintainability.
4
+ */
5
+ export { EXTENSIONS, tryResolveWithExtensions, buildSuffixIndex, suffixResolve } from './utils.js';
6
+ export { KOTLIN_EXTENSIONS, appendKotlinWildcard, resolveJvmWildcard, resolveJvmMemberImport } from './jvm.js';
7
+ export { resolveGoPackageDir, resolveGoPackage } from './go.js';
8
+ export { resolveCSharpImport, resolveCSharpNamespaceDir } from './csharp.js';
9
+ export { resolvePhpImport } from './php.js';
10
+ export { resolveRustImport, tryRustModulePath } from './rust.js';
11
+ export { resolveImportPath, RESOLVE_CACHE_CAP } from './standard.js';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * JVM import resolution (Java + Kotlin).
3
+ * Handles wildcard imports, member/static imports, and Kotlin-specific patterns.
4
+ */
5
+ import type { SuffixIndex } from './utils.js';
6
+ /** Kotlin file extensions for JVM resolver reuse */
7
+ export declare const KOTLIN_EXTENSIONS: readonly string[];
8
+ /**
9
+ * Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
10
+ * Pure function — returns a new string without mutating the input.
11
+ */
12
+ export declare const appendKotlinWildcard: (importPath: string, importNode: any) => string;
13
+ /**
14
+ * Resolve a JVM wildcard import (com.example.*) to all matching files.
15
+ * Works for both Java (.java) and Kotlin (.kt, .kts).
16
+ */
17
+ export declare function resolveJvmWildcard(importPath: string, normalizedFileList: string[], allFileList: string[], extensions: readonly string[], index?: SuffixIndex): string[];
18
+ /**
19
+ * Try to resolve a JVM member/static import by stripping the member name.
20
+ * Java: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
21
+ * Kotlin: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
22
+ */
23
+ export declare function resolveJvmMemberImport(importPath: string, normalizedFileList: string[], allFileList: string[], extensions: readonly string[], index?: SuffixIndex): string | null;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * JVM import resolution (Java + Kotlin).
3
+ * Handles wildcard imports, member/static imports, and Kotlin-specific patterns.
4
+ */
5
+ /** Kotlin file extensions for JVM resolver reuse */
6
+ export const KOTLIN_EXTENSIONS = ['.kt', '.kts'];
7
+ /**
8
+ * Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
9
+ * Pure function — returns a new string without mutating the input.
10
+ */
11
+ export const appendKotlinWildcard = (importPath, importNode) => {
12
+ for (let i = 0; i < importNode.childCount; i++) {
13
+ if (importNode.child(i)?.type === 'wildcard_import') {
14
+ return importPath.endsWith('.*') ? importPath : `${importPath}.*`;
15
+ }
16
+ }
17
+ return importPath;
18
+ };
19
+ /**
20
+ * Resolve a JVM wildcard import (com.example.*) to all matching files.
21
+ * Works for both Java (.java) and Kotlin (.kt, .kts).
22
+ */
23
+ export function resolveJvmWildcard(importPath, normalizedFileList, allFileList, extensions, index) {
24
+ // "com.example.util.*" -> "com/example/util"
25
+ const packagePath = importPath.slice(0, -2).replace(/\./g, '/');
26
+ if (index) {
27
+ const candidates = extensions.flatMap(ext => index.getFilesInDir(packagePath, ext));
28
+ // Filter to only direct children (no subdirectories)
29
+ const packageSuffix = '/' + packagePath + '/';
30
+ return candidates.filter(f => {
31
+ const normalized = f.replace(/\\/g, '/');
32
+ const idx = normalized.indexOf(packageSuffix);
33
+ if (idx < 0)
34
+ return false;
35
+ const afterPkg = normalized.substring(idx + packageSuffix.length);
36
+ return !afterPkg.includes('/');
37
+ });
38
+ }
39
+ // Fallback: linear scan
40
+ const packageSuffix = '/' + packagePath + '/';
41
+ const matches = [];
42
+ for (let i = 0; i < normalizedFileList.length; i++) {
43
+ const normalized = normalizedFileList[i];
44
+ if (normalized.includes(packageSuffix) &&
45
+ extensions.some(ext => normalized.endsWith(ext))) {
46
+ const afterPackage = normalized.substring(normalized.indexOf(packageSuffix) + packageSuffix.length);
47
+ if (!afterPackage.includes('/')) {
48
+ matches.push(allFileList[i]);
49
+ }
50
+ }
51
+ }
52
+ return matches;
53
+ }
54
+ /**
55
+ * Try to resolve a JVM member/static import by stripping the member name.
56
+ * Java: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
57
+ * Kotlin: "com.example.Constants.VALUE" -> resolve "com.example.Constants"
58
+ */
59
+ export function resolveJvmMemberImport(importPath, normalizedFileList, allFileList, extensions, index) {
60
+ // Member imports: com.example.Constants.VALUE or com.example.Constants.*
61
+ // The last segment is a member name if it starts with lowercase, is ALL_CAPS, or is a wildcard
62
+ const segments = importPath.split('.');
63
+ if (segments.length < 3)
64
+ return null;
65
+ const lastSeg = segments[segments.length - 1];
66
+ if (lastSeg === '*' || /^[a-z]/.test(lastSeg) || /^[A-Z_]+$/.test(lastSeg)) {
67
+ const classPath = segments.slice(0, -1).join('/');
68
+ for (const ext of extensions) {
69
+ const classSuffix = classPath + ext;
70
+ if (index) {
71
+ const result = index.get(classSuffix) || index.getInsensitive(classSuffix);
72
+ if (result)
73
+ return result;
74
+ }
75
+ else {
76
+ const fullSuffix = '/' + classSuffix;
77
+ for (let i = 0; i < normalizedFileList.length; i++) {
78
+ if (normalizedFileList[i].endsWith(fullSuffix) ||
79
+ normalizedFileList[i].toLowerCase().endsWith(fullSuffix.toLowerCase())) {
80
+ return allFileList[i];
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ return null;
87
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * PHP PSR-4 import resolution.
3
+ * Handles use-statement resolution via composer.json autoload mappings.
4
+ */
5
+ import type { SuffixIndex } from './utils.js';
6
+ /** PHP Composer PSR-4 autoload config */
7
+ export interface ComposerConfig {
8
+ /** Map of namespace prefix -> directory (e.g., "App\\" -> "app/") */
9
+ psr4: Map<string, string>;
10
+ }
11
+ /**
12
+ * Resolve a PHP use-statement import path using PSR-4 mappings.
13
+ * e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
14
+ */
15
+ export declare function resolvePhpImport(importPath: string, composerConfig: ComposerConfig | null, allFiles: Set<string>, normalizedFileList: string[], allFileList: string[], index?: SuffixIndex): string | null;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * PHP PSR-4 import resolution.
3
+ * Handles use-statement resolution via composer.json autoload mappings.
4
+ */
5
+ import { suffixResolve } from './utils.js';
6
+ /**
7
+ * Resolve a PHP use-statement import path using PSR-4 mappings.
8
+ * e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
9
+ */
10
+ export function resolvePhpImport(importPath, composerConfig, allFiles, normalizedFileList, allFileList, index) {
11
+ // Normalize: replace backslashes with forward slashes
12
+ const normalized = importPath.replace(/\\/g, '/');
13
+ // Try PSR-4 resolution if composer.json was found
14
+ if (composerConfig) {
15
+ // Sort namespaces by length descending (longest match wins)
16
+ const sorted = [...composerConfig.psr4.entries()].sort((a, b) => b[0].length - a[0].length);
17
+ for (const [nsPrefix, dirPrefix] of sorted) {
18
+ const nsPrefixSlash = nsPrefix.replace(/\\/g, '/');
19
+ if (normalized.startsWith(nsPrefixSlash + '/') || normalized === nsPrefixSlash) {
20
+ const remainder = normalized.slice(nsPrefixSlash.length).replace(/^\//, '');
21
+ const filePath = dirPrefix + (remainder ? '/' + remainder : '') + '.php';
22
+ if (allFiles.has(filePath))
23
+ return filePath;
24
+ if (index) {
25
+ const result = index.getInsensitive(filePath);
26
+ if (result)
27
+ return result;
28
+ }
29
+ }
30
+ }
31
+ }
32
+ // Fallback: suffix matching (works without composer.json)
33
+ const pathParts = normalized.split('/').filter(Boolean);
34
+ return suffixResolve(pathParts, normalizedFileList, allFileList, index);
35
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Rust module import resolution.
3
+ * Handles crate::, super::, self:: prefix paths and :: separators.
4
+ */
5
+ /**
6
+ * Resolve Rust use-path to a file.
7
+ * Handles crate::, super::, self:: prefixes and :: path separators.
8
+ */
9
+ export declare function resolveRustImport(currentFile: string, importPath: string, allFiles: Set<string>): string | null;
10
+ /**
11
+ * Try to resolve a Rust module path to a file.
12
+ * Tries: path.rs, path/mod.rs, and with the last segment stripped
13
+ * (last segment might be a symbol name, not a module).
14
+ */
15
+ export declare function tryRustModulePath(modulePath: string, allFiles: Set<string>): string | null;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Rust module import resolution.
3
+ * Handles crate::, super::, self:: prefix paths and :: separators.
4
+ */
5
+ /**
6
+ * Resolve Rust use-path to a file.
7
+ * Handles crate::, super::, self:: prefixes and :: path separators.
8
+ */
9
+ export function resolveRustImport(currentFile, importPath, allFiles) {
10
+ let rustPath;
11
+ if (importPath.startsWith('crate::')) {
12
+ // crate:: resolves from src/ directory (standard Rust layout)
13
+ rustPath = importPath.slice(7).replace(/::/g, '/');
14
+ // Try from src/ (standard layout)
15
+ const fromSrc = tryRustModulePath('src/' + rustPath, allFiles);
16
+ if (fromSrc)
17
+ return fromSrc;
18
+ // Try from repo root (non-standard)
19
+ const fromRoot = tryRustModulePath(rustPath, allFiles);
20
+ if (fromRoot)
21
+ return fromRoot;
22
+ return null;
23
+ }
24
+ if (importPath.startsWith('super::')) {
25
+ // super:: = parent directory of current file's module
26
+ const currentDir = currentFile.split('/').slice(0, -1);
27
+ currentDir.pop(); // Go up one level for super::
28
+ rustPath = importPath.slice(7).replace(/::/g, '/');
29
+ const fullPath = [...currentDir, rustPath].join('/');
30
+ return tryRustModulePath(fullPath, allFiles);
31
+ }
32
+ if (importPath.startsWith('self::')) {
33
+ // self:: = current module's directory
34
+ const currentDir = currentFile.split('/').slice(0, -1);
35
+ rustPath = importPath.slice(6).replace(/::/g, '/');
36
+ const fullPath = [...currentDir, rustPath].join('/');
37
+ return tryRustModulePath(fullPath, allFiles);
38
+ }
39
+ // Bare path without prefix (e.g., from a use in a nested module)
40
+ // Convert :: to / and try suffix matching
41
+ if (importPath.includes('::')) {
42
+ rustPath = importPath.replace(/::/g, '/');
43
+ return tryRustModulePath(rustPath, allFiles);
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Try to resolve a Rust module path to a file.
49
+ * Tries: path.rs, path/mod.rs, and with the last segment stripped
50
+ * (last segment might be a symbol name, not a module).
51
+ */
52
+ export function tryRustModulePath(modulePath, allFiles) {
53
+ // Try direct: path.rs
54
+ if (allFiles.has(modulePath + '.rs'))
55
+ return modulePath + '.rs';
56
+ // Try directory: path/mod.rs
57
+ if (allFiles.has(modulePath + '/mod.rs'))
58
+ return modulePath + '/mod.rs';
59
+ // Try path/lib.rs (for crate root)
60
+ if (allFiles.has(modulePath + '/lib.rs'))
61
+ return modulePath + '/lib.rs';
62
+ // The last segment might be a symbol (function, struct, etc.), not a module.
63
+ // Strip it and try again.
64
+ const lastSlash = modulePath.lastIndexOf('/');
65
+ if (lastSlash > 0) {
66
+ const parentPath = modulePath.substring(0, lastSlash);
67
+ if (allFiles.has(parentPath + '.rs'))
68
+ return parentPath + '.rs';
69
+ if (allFiles.has(parentPath + '/mod.rs'))
70
+ return parentPath + '/mod.rs';
71
+ }
72
+ return null;
73
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Standard import path resolution.
3
+ * Handles relative imports, path alias rewriting, and generic suffix matching.
4
+ * Used as the fallback when language-specific resolvers don't match.
5
+ */
6
+ import type { SuffixIndex } from './utils.js';
7
+ import { SupportedLanguages } from '../../../config/supported-languages.js';
8
+ /** TypeScript path alias config parsed from tsconfig.json */
9
+ export interface TsconfigPaths {
10
+ /** Map of alias prefix -> target prefix (e.g., "@/" -> "src/") */
11
+ aliases: Map<string, string>;
12
+ /** Base URL for path resolution (relative to repo root) */
13
+ baseUrl: string;
14
+ }
15
+ /** Max entries in the resolve cache. Beyond this, entries are evicted.
16
+ * 100K entries ≈ 15MB — covers the most common import patterns. */
17
+ export declare const RESOLVE_CACHE_CAP = 100000;
18
+ /**
19
+ * Resolve an import path to a file path in the repository.
20
+ *
21
+ * Language-specific preprocessing is applied before the generic resolution:
22
+ * - TypeScript/JavaScript: rewrites tsconfig path aliases
23
+ * - Rust: converts crate::/super::/self:: to relative paths
24
+ *
25
+ * Java wildcards and Go package imports are handled separately in processImports
26
+ * because they resolve to multiple files.
27
+ */
28
+ export declare const resolveImportPath: (currentFile: string, importPath: string, allFiles: Set<string>, allFileList: string[], normalizedFileList: string[], resolveCache: Map<string, string | null>, language: SupportedLanguages, tsconfigPaths: TsconfigPaths | null, index?: SuffixIndex) => string | null;