@veewo/gitnexus 1.3.4

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 (231) hide show
  1. package/README.md +234 -0
  2. package/dist/benchmark/agent-context/evaluators.d.ts +9 -0
  3. package/dist/benchmark/agent-context/evaluators.js +196 -0
  4. package/dist/benchmark/agent-context/evaluators.test.d.ts +1 -0
  5. package/dist/benchmark/agent-context/evaluators.test.js +39 -0
  6. package/dist/benchmark/agent-context/io.d.ts +2 -0
  7. package/dist/benchmark/agent-context/io.js +23 -0
  8. package/dist/benchmark/agent-context/io.test.d.ts +1 -0
  9. package/dist/benchmark/agent-context/io.test.js +19 -0
  10. package/dist/benchmark/agent-context/report.d.ts +2 -0
  11. package/dist/benchmark/agent-context/report.js +59 -0
  12. package/dist/benchmark/agent-context/report.test.d.ts +1 -0
  13. package/dist/benchmark/agent-context/report.test.js +85 -0
  14. package/dist/benchmark/agent-context/runner.d.ts +46 -0
  15. package/dist/benchmark/agent-context/runner.js +111 -0
  16. package/dist/benchmark/agent-context/runner.test.d.ts +1 -0
  17. package/dist/benchmark/agent-context/runner.test.js +79 -0
  18. package/dist/benchmark/agent-context/tool-runner.d.ts +7 -0
  19. package/dist/benchmark/agent-context/tool-runner.js +18 -0
  20. package/dist/benchmark/agent-context/tool-runner.test.d.ts +1 -0
  21. package/dist/benchmark/agent-context/tool-runner.test.js +11 -0
  22. package/dist/benchmark/agent-context/types.d.ts +40 -0
  23. package/dist/benchmark/agent-context/types.js +1 -0
  24. package/dist/benchmark/analyze-runner.d.ts +16 -0
  25. package/dist/benchmark/analyze-runner.js +51 -0
  26. package/dist/benchmark/analyze-runner.test.d.ts +1 -0
  27. package/dist/benchmark/analyze-runner.test.js +37 -0
  28. package/dist/benchmark/evaluators.d.ts +6 -0
  29. package/dist/benchmark/evaluators.js +10 -0
  30. package/dist/benchmark/evaluators.test.d.ts +1 -0
  31. package/dist/benchmark/evaluators.test.js +12 -0
  32. package/dist/benchmark/io.d.ts +7 -0
  33. package/dist/benchmark/io.js +25 -0
  34. package/dist/benchmark/io.test.d.ts +1 -0
  35. package/dist/benchmark/io.test.js +35 -0
  36. package/dist/benchmark/neonspark-candidates.d.ts +19 -0
  37. package/dist/benchmark/neonspark-candidates.js +94 -0
  38. package/dist/benchmark/neonspark-candidates.test.d.ts +1 -0
  39. package/dist/benchmark/neonspark-candidates.test.js +43 -0
  40. package/dist/benchmark/neonspark-materialize.d.ts +19 -0
  41. package/dist/benchmark/neonspark-materialize.js +111 -0
  42. package/dist/benchmark/neonspark-materialize.test.d.ts +1 -0
  43. package/dist/benchmark/neonspark-materialize.test.js +124 -0
  44. package/dist/benchmark/neonspark-sync.d.ts +3 -0
  45. package/dist/benchmark/neonspark-sync.js +53 -0
  46. package/dist/benchmark/neonspark-sync.test.d.ts +1 -0
  47. package/dist/benchmark/neonspark-sync.test.js +20 -0
  48. package/dist/benchmark/report.d.ts +1 -0
  49. package/dist/benchmark/report.js +7 -0
  50. package/dist/benchmark/runner.d.ts +48 -0
  51. package/dist/benchmark/runner.js +302 -0
  52. package/dist/benchmark/runner.test.d.ts +1 -0
  53. package/dist/benchmark/runner.test.js +50 -0
  54. package/dist/benchmark/scoring.d.ts +16 -0
  55. package/dist/benchmark/scoring.js +27 -0
  56. package/dist/benchmark/scoring.test.d.ts +1 -0
  57. package/dist/benchmark/scoring.test.js +24 -0
  58. package/dist/benchmark/tool-runner.d.ts +6 -0
  59. package/dist/benchmark/tool-runner.js +17 -0
  60. package/dist/benchmark/types.d.ts +36 -0
  61. package/dist/benchmark/types.js +1 -0
  62. package/dist/cli/ai-context.d.ts +22 -0
  63. package/dist/cli/ai-context.js +184 -0
  64. package/dist/cli/ai-context.test.d.ts +1 -0
  65. package/dist/cli/ai-context.test.js +30 -0
  66. package/dist/cli/analyze-multi-scope-regression.test.d.ts +1 -0
  67. package/dist/cli/analyze-multi-scope-regression.test.js +22 -0
  68. package/dist/cli/analyze-options.d.ts +7 -0
  69. package/dist/cli/analyze-options.js +56 -0
  70. package/dist/cli/analyze-options.test.d.ts +1 -0
  71. package/dist/cli/analyze-options.test.js +36 -0
  72. package/dist/cli/analyze.d.ts +14 -0
  73. package/dist/cli/analyze.js +384 -0
  74. package/dist/cli/augment.d.ts +13 -0
  75. package/dist/cli/augment.js +33 -0
  76. package/dist/cli/benchmark-agent-context.d.ts +29 -0
  77. package/dist/cli/benchmark-agent-context.js +61 -0
  78. package/dist/cli/benchmark-agent-context.test.d.ts +1 -0
  79. package/dist/cli/benchmark-agent-context.test.js +80 -0
  80. package/dist/cli/benchmark-unity.d.ts +15 -0
  81. package/dist/cli/benchmark-unity.js +31 -0
  82. package/dist/cli/benchmark-unity.test.d.ts +1 -0
  83. package/dist/cli/benchmark-unity.test.js +18 -0
  84. package/dist/cli/claude-hooks.d.ts +22 -0
  85. package/dist/cli/claude-hooks.js +97 -0
  86. package/dist/cli/clean.d.ts +10 -0
  87. package/dist/cli/clean.js +60 -0
  88. package/dist/cli/eval-server.d.ts +30 -0
  89. package/dist/cli/eval-server.js +372 -0
  90. package/dist/cli/index.d.ts +2 -0
  91. package/dist/cli/index.js +182 -0
  92. package/dist/cli/list.d.ts +6 -0
  93. package/dist/cli/list.js +33 -0
  94. package/dist/cli/mcp.d.ts +8 -0
  95. package/dist/cli/mcp.js +34 -0
  96. package/dist/cli/repo-manager-alias.test.d.ts +1 -0
  97. package/dist/cli/repo-manager-alias.test.js +40 -0
  98. package/dist/cli/scope-filter.test.d.ts +1 -0
  99. package/dist/cli/scope-filter.test.js +49 -0
  100. package/dist/cli/serve.d.ts +4 -0
  101. package/dist/cli/serve.js +6 -0
  102. package/dist/cli/setup.d.ts +8 -0
  103. package/dist/cli/setup.js +311 -0
  104. package/dist/cli/setup.test.d.ts +1 -0
  105. package/dist/cli/setup.test.js +31 -0
  106. package/dist/cli/status.d.ts +6 -0
  107. package/dist/cli/status.js +27 -0
  108. package/dist/cli/tool.d.ts +40 -0
  109. package/dist/cli/tool.js +94 -0
  110. package/dist/cli/version.test.d.ts +1 -0
  111. package/dist/cli/version.test.js +19 -0
  112. package/dist/cli/wiki.d.ts +15 -0
  113. package/dist/cli/wiki.js +361 -0
  114. package/dist/config/ignore-service.d.ts +1 -0
  115. package/dist/config/ignore-service.js +210 -0
  116. package/dist/config/supported-languages.d.ts +12 -0
  117. package/dist/config/supported-languages.js +15 -0
  118. package/dist/core/augmentation/engine.d.ts +26 -0
  119. package/dist/core/augmentation/engine.js +213 -0
  120. package/dist/core/embeddings/embedder.d.ts +60 -0
  121. package/dist/core/embeddings/embedder.js +251 -0
  122. package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
  123. package/dist/core/embeddings/embedding-pipeline.js +329 -0
  124. package/dist/core/embeddings/index.d.ts +9 -0
  125. package/dist/core/embeddings/index.js +9 -0
  126. package/dist/core/embeddings/text-generator.d.ts +24 -0
  127. package/dist/core/embeddings/text-generator.js +182 -0
  128. package/dist/core/embeddings/types.d.ts +87 -0
  129. package/dist/core/embeddings/types.js +32 -0
  130. package/dist/core/graph/graph.d.ts +2 -0
  131. package/dist/core/graph/graph.js +66 -0
  132. package/dist/core/graph/types.d.ts +61 -0
  133. package/dist/core/graph/types.js +1 -0
  134. package/dist/core/ingestion/ast-cache.d.ts +11 -0
  135. package/dist/core/ingestion/ast-cache.js +34 -0
  136. package/dist/core/ingestion/call-processor.d.ts +15 -0
  137. package/dist/core/ingestion/call-processor.js +327 -0
  138. package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
  139. package/dist/core/ingestion/cluster-enricher.js +170 -0
  140. package/dist/core/ingestion/community-processor.d.ts +39 -0
  141. package/dist/core/ingestion/community-processor.js +312 -0
  142. package/dist/core/ingestion/entry-point-scoring.d.ts +39 -0
  143. package/dist/core/ingestion/entry-point-scoring.js +260 -0
  144. package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
  145. package/dist/core/ingestion/filesystem-walker.js +80 -0
  146. package/dist/core/ingestion/framework-detection.d.ts +39 -0
  147. package/dist/core/ingestion/framework-detection.js +235 -0
  148. package/dist/core/ingestion/heritage-processor.d.ts +20 -0
  149. package/dist/core/ingestion/heritage-processor.js +197 -0
  150. package/dist/core/ingestion/import-processor.d.ts +38 -0
  151. package/dist/core/ingestion/import-processor.js +778 -0
  152. package/dist/core/ingestion/parsing-processor.d.ts +15 -0
  153. package/dist/core/ingestion/parsing-processor.js +291 -0
  154. package/dist/core/ingestion/pipeline.d.ts +5 -0
  155. package/dist/core/ingestion/pipeline.js +323 -0
  156. package/dist/core/ingestion/process-processor.d.ts +51 -0
  157. package/dist/core/ingestion/process-processor.js +309 -0
  158. package/dist/core/ingestion/scope-filter.d.ts +25 -0
  159. package/dist/core/ingestion/scope-filter.js +100 -0
  160. package/dist/core/ingestion/structure-processor.d.ts +2 -0
  161. package/dist/core/ingestion/structure-processor.js +36 -0
  162. package/dist/core/ingestion/symbol-table.d.ts +33 -0
  163. package/dist/core/ingestion/symbol-table.js +38 -0
  164. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -0
  165. package/dist/core/ingestion/tree-sitter-queries.js +398 -0
  166. package/dist/core/ingestion/utils.d.ts +10 -0
  167. package/dist/core/ingestion/utils.js +50 -0
  168. package/dist/core/ingestion/workers/parse-worker.d.ts +59 -0
  169. package/dist/core/ingestion/workers/parse-worker.js +672 -0
  170. package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
  171. package/dist/core/ingestion/workers/worker-pool.js +120 -0
  172. package/dist/core/kuzu/csv-generator.d.ts +29 -0
  173. package/dist/core/kuzu/csv-generator.js +336 -0
  174. package/dist/core/kuzu/kuzu-adapter.d.ts +101 -0
  175. package/dist/core/kuzu/kuzu-adapter.js +753 -0
  176. package/dist/core/kuzu/schema.d.ts +53 -0
  177. package/dist/core/kuzu/schema.js +407 -0
  178. package/dist/core/search/bm25-index.d.ts +23 -0
  179. package/dist/core/search/bm25-index.js +95 -0
  180. package/dist/core/search/hybrid-search.d.ts +49 -0
  181. package/dist/core/search/hybrid-search.js +118 -0
  182. package/dist/core/tree-sitter/parser-loader.d.ts +4 -0
  183. package/dist/core/tree-sitter/parser-loader.js +44 -0
  184. package/dist/core/wiki/generator.d.ts +110 -0
  185. package/dist/core/wiki/generator.js +786 -0
  186. package/dist/core/wiki/graph-queries.d.ts +80 -0
  187. package/dist/core/wiki/graph-queries.js +238 -0
  188. package/dist/core/wiki/html-viewer.d.ts +10 -0
  189. package/dist/core/wiki/html-viewer.js +297 -0
  190. package/dist/core/wiki/llm-client.d.ts +40 -0
  191. package/dist/core/wiki/llm-client.js +162 -0
  192. package/dist/core/wiki/prompts.d.ts +53 -0
  193. package/dist/core/wiki/prompts.js +174 -0
  194. package/dist/lib/utils.d.ts +1 -0
  195. package/dist/lib/utils.js +3 -0
  196. package/dist/mcp/core/embedder.d.ts +27 -0
  197. package/dist/mcp/core/embedder.js +108 -0
  198. package/dist/mcp/core/kuzu-adapter.d.ts +34 -0
  199. package/dist/mcp/core/kuzu-adapter.js +231 -0
  200. package/dist/mcp/local/local-backend.d.ts +160 -0
  201. package/dist/mcp/local/local-backend.js +1646 -0
  202. package/dist/mcp/resources.d.ts +31 -0
  203. package/dist/mcp/resources.js +407 -0
  204. package/dist/mcp/server.d.ts +23 -0
  205. package/dist/mcp/server.js +251 -0
  206. package/dist/mcp/staleness.d.ts +15 -0
  207. package/dist/mcp/staleness.js +29 -0
  208. package/dist/mcp/tools.d.ts +24 -0
  209. package/dist/mcp/tools.js +195 -0
  210. package/dist/server/api.d.ts +10 -0
  211. package/dist/server/api.js +344 -0
  212. package/dist/server/mcp-http.d.ts +13 -0
  213. package/dist/server/mcp-http.js +100 -0
  214. package/dist/storage/git.d.ts +6 -0
  215. package/dist/storage/git.js +32 -0
  216. package/dist/storage/repo-manager.d.ts +125 -0
  217. package/dist/storage/repo-manager.js +257 -0
  218. package/dist/types/pipeline.d.ts +34 -0
  219. package/dist/types/pipeline.js +18 -0
  220. package/hooks/claude/gitnexus-hook.cjs +135 -0
  221. package/hooks/claude/pre-tool-use.sh +78 -0
  222. package/hooks/claude/session-start.sh +42 -0
  223. package/package.json +92 -0
  224. package/skills/gitnexus-cli.md +82 -0
  225. package/skills/gitnexus-debugging.md +89 -0
  226. package/skills/gitnexus-exploring.md +78 -0
  227. package/skills/gitnexus-guide.md +64 -0
  228. package/skills/gitnexus-impact-analysis.md +97 -0
  229. package/skills/gitnexus-refactoring.md +121 -0
  230. package/vendor/leiden/index.cjs +355 -0
  231. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,778 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import Parser from 'tree-sitter';
4
+ import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
5
+ import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
6
+ import { generateId } from '../../lib/utils.js';
7
+ import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
8
+ import { SupportedLanguages } from '../../config/supported-languages.js';
9
+ const isDev = process.env.NODE_ENV === 'development';
10
+ export const createImportMap = () => new Map();
11
+ /** Max entries in the resolve cache. Beyond this, the cache is cleared to bound memory.
12
+ * 100K entries ≈ 15MB — covers the most common import patterns. */
13
+ const RESOLVE_CACHE_CAP = 100_000;
14
+ export function buildImportResolutionContext(allPaths) {
15
+ const allFileList = allPaths;
16
+ const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
17
+ const allFilePaths = new Set(allFileList);
18
+ const suffixIndex = buildSuffixIndex(normalizedFileList, allFileList);
19
+ return { allFilePaths, allFileList, normalizedFileList, suffixIndex, resolveCache: new Map() };
20
+ }
21
+ /**
22
+ * Parse tsconfig.json to extract path aliases.
23
+ * Tries tsconfig.json, tsconfig.app.json, tsconfig.base.json in order.
24
+ */
25
+ async function loadTsconfigPaths(repoRoot) {
26
+ const candidates = ['tsconfig.json', 'tsconfig.app.json', 'tsconfig.base.json'];
27
+ for (const filename of candidates) {
28
+ try {
29
+ const tsconfigPath = path.join(repoRoot, filename);
30
+ const raw = await fs.readFile(tsconfigPath, 'utf-8');
31
+ // Strip JSON comments (// and /* */ style) for robustness
32
+ const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
33
+ const tsconfig = JSON.parse(stripped);
34
+ const compilerOptions = tsconfig.compilerOptions;
35
+ if (!compilerOptions?.paths)
36
+ continue;
37
+ const baseUrl = compilerOptions.baseUrl || '.';
38
+ const aliases = new Map();
39
+ for (const [pattern, targets] of Object.entries(compilerOptions.paths)) {
40
+ if (!Array.isArray(targets) || targets.length === 0)
41
+ continue;
42
+ const target = targets[0];
43
+ // Convert glob patterns: "@/*" -> "@/", "src/*" -> "src/"
44
+ const aliasPrefix = pattern.endsWith('/*') ? pattern.slice(0, -1) : pattern;
45
+ const targetPrefix = target.endsWith('/*') ? target.slice(0, -1) : target;
46
+ aliases.set(aliasPrefix, targetPrefix);
47
+ }
48
+ if (aliases.size > 0) {
49
+ if (isDev) {
50
+ console.log(`📦 Loaded ${aliases.size} path aliases from ${filename}`);
51
+ }
52
+ return { aliases, baseUrl };
53
+ }
54
+ }
55
+ catch {
56
+ // File doesn't exist or isn't valid JSON - try next
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Parse go.mod to extract module path.
63
+ */
64
+ async function loadGoModulePath(repoRoot) {
65
+ try {
66
+ const goModPath = path.join(repoRoot, 'go.mod');
67
+ const content = await fs.readFile(goModPath, 'utf-8');
68
+ const match = content.match(/^module\s+(\S+)/m);
69
+ if (match) {
70
+ if (isDev) {
71
+ console.log(`📦 Loaded Go module path: ${match[1]}`);
72
+ }
73
+ return { modulePath: match[1] };
74
+ }
75
+ }
76
+ catch {
77
+ // No go.mod
78
+ }
79
+ return null;
80
+ }
81
+ async function loadComposerConfig(repoRoot) {
82
+ try {
83
+ const composerPath = path.join(repoRoot, 'composer.json');
84
+ const raw = await fs.readFile(composerPath, 'utf-8');
85
+ const composer = JSON.parse(raw);
86
+ const psr4Raw = composer.autoload?.['psr-4'] ?? {};
87
+ const psr4Dev = composer['autoload-dev']?.['psr-4'] ?? {};
88
+ const merged = { ...psr4Raw, ...psr4Dev };
89
+ const psr4 = new Map();
90
+ for (const [ns, dir] of Object.entries(merged)) {
91
+ const nsNorm = ns.replace(/\\+$/, '');
92
+ const dirNorm = dir.replace(/\\/g, '/').replace(/\/+$/, '');
93
+ psr4.set(nsNorm, dirNorm);
94
+ }
95
+ if (isDev) {
96
+ console.log(`📦 Loaded ${psr4.size} PSR-4 mappings from composer.json`);
97
+ }
98
+ return { psr4 };
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ // ============================================================================
105
+ // IMPORT PATH RESOLUTION
106
+ // ============================================================================
107
+ /** All file extensions to try during resolution */
108
+ const EXTENSIONS = [
109
+ '',
110
+ // TypeScript/JavaScript
111
+ '.tsx', '.ts', '.jsx', '.js', '/index.tsx', '/index.ts', '/index.jsx', '/index.js',
112
+ // Python
113
+ '.py', '/__init__.py',
114
+ // Java
115
+ '.java',
116
+ // C/C++
117
+ '.c', '.h', '.cpp', '.hpp', '.cc', '.cxx', '.hxx', '.hh',
118
+ // C#
119
+ '.cs',
120
+ // Go
121
+ '.go',
122
+ // Rust
123
+ '.rs', '/mod.rs',
124
+ // PHP
125
+ '.php', '.phtml',
126
+ ];
127
+ /**
128
+ * Try to match a path (with extensions) against the known file set.
129
+ * Returns the matched file path or null.
130
+ */
131
+ function tryResolveWithExtensions(basePath, allFiles) {
132
+ for (const ext of EXTENSIONS) {
133
+ const candidate = basePath + ext;
134
+ if (allFiles.has(candidate))
135
+ return candidate;
136
+ }
137
+ return null;
138
+ }
139
+ function buildSuffixIndex(normalizedFileList, allFileList) {
140
+ // Map: normalized suffix -> original file path
141
+ const exactMap = new Map();
142
+ // Map: lowercase suffix -> original file path
143
+ const lowerMap = new Map();
144
+ // Map: directory suffix -> list of file paths in that directory
145
+ const dirMap = new Map();
146
+ for (let i = 0; i < normalizedFileList.length; i++) {
147
+ const normalized = normalizedFileList[i];
148
+ const original = allFileList[i];
149
+ const parts = normalized.split('/');
150
+ // Index all suffixes: "a/b/c.java" -> ["c.java", "b/c.java", "a/b/c.java"]
151
+ for (let j = parts.length - 1; j >= 0; j--) {
152
+ const suffix = parts.slice(j).join('/');
153
+ // Only store first match (longest path wins for ambiguous suffixes)
154
+ if (!exactMap.has(suffix)) {
155
+ exactMap.set(suffix, original);
156
+ }
157
+ const lower = suffix.toLowerCase();
158
+ if (!lowerMap.has(lower)) {
159
+ lowerMap.set(lower, original);
160
+ }
161
+ }
162
+ // Index directory membership
163
+ const lastSlash = normalized.lastIndexOf('/');
164
+ if (lastSlash >= 0) {
165
+ // Build all directory suffixes
166
+ const dirParts = parts.slice(0, -1);
167
+ const fileName = parts[parts.length - 1];
168
+ const ext = fileName.substring(fileName.lastIndexOf('.'));
169
+ for (let j = dirParts.length - 1; j >= 0; j--) {
170
+ const dirSuffix = dirParts.slice(j).join('/');
171
+ const key = `${dirSuffix}:${ext}`;
172
+ let list = dirMap.get(key);
173
+ if (!list) {
174
+ list = [];
175
+ dirMap.set(key, list);
176
+ }
177
+ list.push(original);
178
+ }
179
+ }
180
+ }
181
+ return {
182
+ get: (suffix) => exactMap.get(suffix),
183
+ getInsensitive: (suffix) => lowerMap.get(suffix.toLowerCase()),
184
+ getFilesInDir: (dirSuffix, extension) => {
185
+ return dirMap.get(`${dirSuffix}:${extension}`) || [];
186
+ },
187
+ };
188
+ }
189
+ /**
190
+ * Suffix-based resolution using index. O(1) per lookup instead of O(files).
191
+ */
192
+ function suffixResolve(pathParts, normalizedFileList, allFileList, index) {
193
+ if (index) {
194
+ for (let i = 0; i < pathParts.length; i++) {
195
+ const suffix = pathParts.slice(i).join('/');
196
+ for (const ext of EXTENSIONS) {
197
+ const suffixWithExt = suffix + ext;
198
+ const result = index.get(suffixWithExt) || index.getInsensitive(suffixWithExt);
199
+ if (result)
200
+ return result;
201
+ }
202
+ }
203
+ return null;
204
+ }
205
+ // Fallback: linear scan (for backward compatibility)
206
+ for (let i = 0; i < pathParts.length; i++) {
207
+ const suffix = pathParts.slice(i).join('/');
208
+ for (const ext of EXTENSIONS) {
209
+ const suffixWithExt = suffix + ext;
210
+ const suffixPattern = '/' + suffixWithExt;
211
+ const matchIdx = normalizedFileList.findIndex(filePath => filePath.endsWith(suffixPattern) || filePath.toLowerCase().endsWith(suffixPattern.toLowerCase()));
212
+ if (matchIdx !== -1) {
213
+ return allFileList[matchIdx];
214
+ }
215
+ }
216
+ }
217
+ return null;
218
+ }
219
+ /**
220
+ * Resolve an import path to a file path in the repository.
221
+ *
222
+ * Language-specific preprocessing is applied before the generic resolution:
223
+ * - TypeScript/JavaScript: rewrites tsconfig path aliases
224
+ * - Rust: converts crate::/super::/self:: to relative paths
225
+ *
226
+ * Java wildcards and Go package imports are handled separately in processImports
227
+ * because they resolve to multiple files.
228
+ */
229
+ const resolveImportPath = (currentFile, importPath, allFiles, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index) => {
230
+ const cacheKey = `${currentFile}::${importPath}`;
231
+ if (resolveCache.has(cacheKey))
232
+ return resolveCache.get(cacheKey) ?? null;
233
+ const cache = (result) => {
234
+ // Evict oldest 20% when cap is reached instead of clearing all
235
+ if (resolveCache.size >= RESOLVE_CACHE_CAP) {
236
+ const evictCount = Math.floor(RESOLVE_CACHE_CAP * 0.2);
237
+ const iter = resolveCache.keys();
238
+ for (let i = 0; i < evictCount; i++) {
239
+ const key = iter.next().value;
240
+ if (key !== undefined)
241
+ resolveCache.delete(key);
242
+ }
243
+ }
244
+ resolveCache.set(cacheKey, result);
245
+ return result;
246
+ };
247
+ // ---- TypeScript/JavaScript: rewrite path aliases ----
248
+ if ((language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) &&
249
+ tsconfigPaths &&
250
+ !importPath.startsWith('.')) {
251
+ for (const [aliasPrefix, targetPrefix] of tsconfigPaths.aliases) {
252
+ if (importPath.startsWith(aliasPrefix)) {
253
+ const remainder = importPath.slice(aliasPrefix.length);
254
+ // Build the rewritten path relative to baseUrl
255
+ const rewritten = tsconfigPaths.baseUrl === '.'
256
+ ? targetPrefix + remainder
257
+ : tsconfigPaths.baseUrl + '/' + targetPrefix + remainder;
258
+ // Try direct resolution from repo root
259
+ const resolved = tryResolveWithExtensions(rewritten, allFiles);
260
+ if (resolved)
261
+ return cache(resolved);
262
+ // Try suffix matching as fallback
263
+ const parts = rewritten.split('/').filter(Boolean);
264
+ const suffixResult = suffixResolve(parts, normalizedFileList, allFileList, index);
265
+ if (suffixResult)
266
+ return cache(suffixResult);
267
+ }
268
+ }
269
+ }
270
+ // ---- Rust: convert module path syntax to file paths ----
271
+ if (language === SupportedLanguages.Rust) {
272
+ const rustResult = resolveRustImport(currentFile, importPath, allFiles);
273
+ if (rustResult)
274
+ return cache(rustResult);
275
+ // Fall through to generic resolution if Rust-specific didn't match
276
+ }
277
+ // ---- Generic relative import resolution (./ and ../) ----
278
+ const currentDir = currentFile.split('/').slice(0, -1);
279
+ const parts = importPath.split('/');
280
+ for (const part of parts) {
281
+ if (part === '.')
282
+ continue;
283
+ if (part === '..') {
284
+ currentDir.pop();
285
+ }
286
+ else {
287
+ currentDir.push(part);
288
+ }
289
+ }
290
+ const basePath = currentDir.join('/');
291
+ if (importPath.startsWith('.')) {
292
+ const resolved = tryResolveWithExtensions(basePath, allFiles);
293
+ return cache(resolved);
294
+ }
295
+ // ---- Generic package/absolute import resolution (suffix matching) ----
296
+ // Java wildcards are handled in processImports, not here
297
+ if (importPath.endsWith('.*')) {
298
+ return cache(null);
299
+ }
300
+ const pathLike = importPath.includes('/')
301
+ ? importPath
302
+ : importPath.replace(/\./g, '/');
303
+ const pathParts = pathLike.split('/').filter(Boolean);
304
+ const resolved = suffixResolve(pathParts, normalizedFileList, allFileList, index);
305
+ return cache(resolved);
306
+ };
307
+ // ============================================================================
308
+ // RUST MODULE RESOLUTION
309
+ // ============================================================================
310
+ /**
311
+ * Resolve Rust use-path to a file.
312
+ * Handles crate::, super::, self:: prefixes and :: path separators.
313
+ */
314
+ function resolveRustImport(currentFile, importPath, allFiles) {
315
+ let rustPath;
316
+ if (importPath.startsWith('crate::')) {
317
+ // crate:: resolves from src/ directory (standard Rust layout)
318
+ rustPath = importPath.slice(7).replace(/::/g, '/');
319
+ // Try from src/ (standard layout)
320
+ const fromSrc = tryRustModulePath('src/' + rustPath, allFiles);
321
+ if (fromSrc)
322
+ return fromSrc;
323
+ // Try from repo root (non-standard)
324
+ const fromRoot = tryRustModulePath(rustPath, allFiles);
325
+ if (fromRoot)
326
+ return fromRoot;
327
+ return null;
328
+ }
329
+ if (importPath.startsWith('super::')) {
330
+ // super:: = parent directory of current file's module
331
+ const currentDir = currentFile.split('/').slice(0, -1);
332
+ currentDir.pop(); // Go up one level for super::
333
+ rustPath = importPath.slice(7).replace(/::/g, '/');
334
+ const fullPath = [...currentDir, rustPath].join('/');
335
+ return tryRustModulePath(fullPath, allFiles);
336
+ }
337
+ if (importPath.startsWith('self::')) {
338
+ // self:: = current module's directory
339
+ const currentDir = currentFile.split('/').slice(0, -1);
340
+ rustPath = importPath.slice(6).replace(/::/g, '/');
341
+ const fullPath = [...currentDir, rustPath].join('/');
342
+ return tryRustModulePath(fullPath, allFiles);
343
+ }
344
+ // Bare path without prefix (e.g., from a use in a nested module)
345
+ // Convert :: to / and try suffix matching
346
+ if (importPath.includes('::')) {
347
+ rustPath = importPath.replace(/::/g, '/');
348
+ return tryRustModulePath(rustPath, allFiles);
349
+ }
350
+ return null;
351
+ }
352
+ /**
353
+ * Try to resolve a Rust module path to a file.
354
+ * Tries: path.rs, path/mod.rs, and with the last segment stripped
355
+ * (last segment might be a symbol name, not a module).
356
+ */
357
+ function tryRustModulePath(modulePath, allFiles) {
358
+ // Try direct: path.rs
359
+ if (allFiles.has(modulePath + '.rs'))
360
+ return modulePath + '.rs';
361
+ // Try directory: path/mod.rs
362
+ if (allFiles.has(modulePath + '/mod.rs'))
363
+ return modulePath + '/mod.rs';
364
+ // Try path/lib.rs (for crate root)
365
+ if (allFiles.has(modulePath + '/lib.rs'))
366
+ return modulePath + '/lib.rs';
367
+ // The last segment might be a symbol (function, struct, etc.), not a module.
368
+ // Strip it and try again.
369
+ const lastSlash = modulePath.lastIndexOf('/');
370
+ if (lastSlash > 0) {
371
+ const parentPath = modulePath.substring(0, lastSlash);
372
+ if (allFiles.has(parentPath + '.rs'))
373
+ return parentPath + '.rs';
374
+ if (allFiles.has(parentPath + '/mod.rs'))
375
+ return parentPath + '/mod.rs';
376
+ }
377
+ return null;
378
+ }
379
+ // ============================================================================
380
+ // JAVA MULTI-FILE RESOLUTION
381
+ // ============================================================================
382
+ /**
383
+ * Resolve a Java wildcard import (com.example.*) to all matching .java files.
384
+ * Returns an array of file paths.
385
+ */
386
+ function resolveJavaWildcard(importPath, normalizedFileList, allFileList, index) {
387
+ // "com.example.util.*" -> "com/example/util"
388
+ const packagePath = importPath.slice(0, -2).replace(/\./g, '/');
389
+ if (index) {
390
+ // Use directory index: get all .java files in this package directory
391
+ const candidates = index.getFilesInDir(packagePath, '.java');
392
+ // Filter to only direct children (no subdirectories)
393
+ const packageSuffix = '/' + packagePath + '/';
394
+ return candidates.filter(f => {
395
+ const normalized = f.replace(/\\/g, '/');
396
+ const idx = normalized.indexOf(packageSuffix);
397
+ if (idx < 0)
398
+ return false;
399
+ const afterPkg = normalized.substring(idx + packageSuffix.length);
400
+ return !afterPkg.includes('/');
401
+ });
402
+ }
403
+ // Fallback: linear scan
404
+ const packageSuffix = '/' + packagePath + '/';
405
+ const matches = [];
406
+ for (let i = 0; i < normalizedFileList.length; i++) {
407
+ const normalized = normalizedFileList[i];
408
+ if (normalized.includes(packageSuffix) && normalized.endsWith('.java')) {
409
+ const afterPackage = normalized.substring(normalized.indexOf(packageSuffix) + packageSuffix.length);
410
+ if (!afterPackage.includes('/')) {
411
+ matches.push(allFileList[i]);
412
+ }
413
+ }
414
+ }
415
+ return matches;
416
+ }
417
+ /**
418
+ * Try to resolve a Java static import by stripping the member name.
419
+ * "com.example.Constants.VALUE" -> resolve "com.example.Constants"
420
+ */
421
+ function resolveJavaStaticImport(importPath, normalizedFileList, allFileList, index) {
422
+ // Static imports look like: com.example.Constants.VALUE or com.example.Constants.*
423
+ // The last segment is a member name (field/method) if it starts with lowercase or is ALL_CAPS
424
+ const segments = importPath.split('.');
425
+ if (segments.length < 3)
426
+ return null;
427
+ const lastSeg = segments[segments.length - 1];
428
+ // If last segment is a wildcard or ALL_CAPS constant or starts with lowercase, strip it
429
+ if (lastSeg === '*' || /^[a-z]/.test(lastSeg) || /^[A-Z_]+$/.test(lastSeg)) {
430
+ const classPath = segments.slice(0, -1).join('/');
431
+ const classSuffix = classPath + '.java';
432
+ if (index) {
433
+ return index.get(classSuffix) || index.getInsensitive(classSuffix) || null;
434
+ }
435
+ // Fallback: linear scan
436
+ const fullSuffix = '/' + classSuffix;
437
+ for (let i = 0; i < normalizedFileList.length; i++) {
438
+ if (normalizedFileList[i].endsWith(fullSuffix) ||
439
+ normalizedFileList[i].toLowerCase().endsWith(fullSuffix.toLowerCase())) {
440
+ return allFileList[i];
441
+ }
442
+ }
443
+ }
444
+ return null;
445
+ }
446
+ // ============================================================================
447
+ // GO PACKAGE RESOLUTION
448
+ // ============================================================================
449
+ /**
450
+ * Resolve a Go internal package import to all .go files in the package directory.
451
+ * Returns an array of file paths.
452
+ */
453
+ function resolveGoPackage(importPath, goModule, normalizedFileList, allFileList) {
454
+ if (!importPath.startsWith(goModule.modulePath))
455
+ return [];
456
+ // Strip module path to get relative package path
457
+ const relativePkg = importPath.slice(goModule.modulePath.length + 1); // e.g., "internal/auth"
458
+ if (!relativePkg)
459
+ return [];
460
+ const pkgSuffix = '/' + relativePkg + '/';
461
+ const matches = [];
462
+ for (let i = 0; i < normalizedFileList.length; i++) {
463
+ const normalized = normalizedFileList[i];
464
+ // File must be directly in the package directory (not a subdirectory)
465
+ if (normalized.includes(pkgSuffix) && normalized.endsWith('.go') && !normalized.endsWith('_test.go')) {
466
+ const afterPkg = normalized.substring(normalized.indexOf(pkgSuffix) + pkgSuffix.length);
467
+ if (!afterPkg.includes('/')) {
468
+ matches.push(allFileList[i]);
469
+ }
470
+ }
471
+ }
472
+ return matches;
473
+ }
474
+ // ============================================================================
475
+ // PHP PSR-4 IMPORT RESOLUTION
476
+ // ============================================================================
477
+ /**
478
+ * Resolve a PHP use-statement import path using PSR-4 mappings.
479
+ * e.g. "App\Http\Controllers\UserController" -> "app/Http/Controllers/UserController.php"
480
+ */
481
+ function resolvePhpImport(importPath, composerConfig, allFiles, normalizedFileList, allFileList, index) {
482
+ // Normalize: replace backslashes with forward slashes
483
+ const normalized = importPath.replace(/\\/g, '/');
484
+ // Try PSR-4 resolution if composer.json was found
485
+ if (composerConfig) {
486
+ // Sort namespaces by length descending (longest match wins)
487
+ const sorted = [...composerConfig.psr4.entries()].sort((a, b) => b[0].length - a[0].length);
488
+ for (const [nsPrefix, dirPrefix] of sorted) {
489
+ const nsPrefixSlash = nsPrefix.replace(/\\/g, '/');
490
+ if (normalized.startsWith(nsPrefixSlash + '/') || normalized === nsPrefixSlash) {
491
+ const remainder = normalized.slice(nsPrefixSlash.length).replace(/^\//, '');
492
+ const filePath = dirPrefix + (remainder ? '/' + remainder : '') + '.php';
493
+ if (allFiles.has(filePath))
494
+ return filePath;
495
+ if (index) {
496
+ const result = index.getInsensitive(filePath);
497
+ if (result)
498
+ return result;
499
+ }
500
+ }
501
+ }
502
+ }
503
+ // Fallback: suffix matching (works without composer.json)
504
+ const pathParts = normalized.split('/').filter(Boolean);
505
+ return suffixResolve(pathParts, normalizedFileList, allFileList, index);
506
+ }
507
+ // ============================================================================
508
+ // MAIN IMPORT PROCESSOR
509
+ // ============================================================================
510
+ export const processImports = async (graph, files, astCache, importMap, onProgress, repoRoot, allPaths) => {
511
+ // Use allPaths (full repo) when available for cross-chunk resolution, else fall back to chunk files
512
+ const allFileList = allPaths ?? files.map(f => f.path);
513
+ const allFilePaths = new Set(allFileList);
514
+ const parser = await loadParser();
515
+ const resolveCache = new Map();
516
+ // Pre-compute normalized file list once (forward slashes)
517
+ const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
518
+ // Build suffix index for O(1) lookups
519
+ const index = buildSuffixIndex(normalizedFileList, allFileList);
520
+ // Track import statistics
521
+ let totalImportsFound = 0;
522
+ let totalImportsResolved = 0;
523
+ // Load language-specific configs once before the file loop
524
+ const effectiveRoot = repoRoot || '';
525
+ const tsconfigPaths = await loadTsconfigPaths(effectiveRoot);
526
+ const goModule = await loadGoModulePath(effectiveRoot);
527
+ const composerConfig = await loadComposerConfig(effectiveRoot);
528
+ // Helper: add an IMPORTS edge + update import map
529
+ const addImportEdge = (filePath, resolvedPath) => {
530
+ const sourceId = generateId('File', filePath);
531
+ const targetId = generateId('File', resolvedPath);
532
+ const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
533
+ totalImportsResolved++;
534
+ graph.addRelationship({
535
+ id: relId,
536
+ sourceId,
537
+ targetId,
538
+ type: 'IMPORTS',
539
+ confidence: 1.0,
540
+ reason: '',
541
+ });
542
+ if (!importMap.has(filePath)) {
543
+ importMap.set(filePath, new Set());
544
+ }
545
+ importMap.get(filePath).add(resolvedPath);
546
+ };
547
+ for (let i = 0; i < files.length; i++) {
548
+ const file = files[i];
549
+ onProgress?.(i + 1, files.length);
550
+ if (i % 20 === 0)
551
+ await yieldToEventLoop();
552
+ // 1. Check language support first
553
+ const language = getLanguageFromFilename(file.path);
554
+ if (!language)
555
+ continue;
556
+ const queryStr = LANGUAGE_QUERIES[language];
557
+ if (!queryStr)
558
+ continue;
559
+ // 2. ALWAYS load the language before querying (parser is stateful)
560
+ await loadLanguage(language, file.path);
561
+ // 3. Get AST (Try Cache First)
562
+ let tree = astCache.get(file.path);
563
+ let wasReparsed = false;
564
+ if (!tree) {
565
+ try {
566
+ tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
567
+ }
568
+ catch (parseError) {
569
+ continue;
570
+ }
571
+ wasReparsed = true;
572
+ // Cache re-parsed tree so call/heritage phases get hits
573
+ astCache.set(file.path, tree);
574
+ }
575
+ let query;
576
+ let matches;
577
+ try {
578
+ const lang = parser.getLanguage();
579
+ query = new Parser.Query(lang, queryStr);
580
+ matches = query.matches(tree.rootNode);
581
+ }
582
+ catch (queryError) {
583
+ if (isDev) {
584
+ console.group(`🔴 Query Error: ${file.path}`);
585
+ console.log('Language:', language);
586
+ console.log('Query (first 200 chars):', queryStr.substring(0, 200) + '...');
587
+ console.log('Error:', queryError?.message || queryError);
588
+ console.log('File content (first 300 chars):', file.content.substring(0, 300));
589
+ console.log('AST root type:', tree.rootNode?.type);
590
+ console.log('AST has errors:', tree.rootNode?.hasError);
591
+ console.groupEnd();
592
+ }
593
+ if (wasReparsed)
594
+ tree.delete?.();
595
+ continue;
596
+ }
597
+ matches.forEach(match => {
598
+ const captureMap = {};
599
+ match.captures.forEach(c => captureMap[c.name] = c.node);
600
+ if (captureMap['import']) {
601
+ const sourceNode = captureMap['import.source'];
602
+ if (!sourceNode) {
603
+ if (isDev) {
604
+ console.log(`⚠️ Import captured but no source node in ${file.path}`);
605
+ }
606
+ return;
607
+ }
608
+ // Clean path (remove quotes and angle brackets for C/C++ includes)
609
+ const rawImportPath = sourceNode.text.replace(/['"<>]/g, '');
610
+ totalImportsFound++;
611
+ // ---- Java: handle wildcards and static imports specially ----
612
+ if (language === SupportedLanguages.Java) {
613
+ if (rawImportPath.endsWith('.*')) {
614
+ const matchedFiles = resolveJavaWildcard(rawImportPath, normalizedFileList, allFileList, index);
615
+ for (const matchedFile of matchedFiles) {
616
+ addImportEdge(file.path, matchedFile);
617
+ }
618
+ return; // skip single-file resolution
619
+ }
620
+ // Try static import resolution (strip member name)
621
+ const staticResolved = resolveJavaStaticImport(rawImportPath, normalizedFileList, allFileList, index);
622
+ if (staticResolved) {
623
+ addImportEdge(file.path, staticResolved);
624
+ return;
625
+ }
626
+ // Fall through to normal resolution for regular Java imports
627
+ }
628
+ // ---- Go: handle package-level imports ----
629
+ if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
630
+ const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
631
+ if (pkgFiles.length > 0) {
632
+ for (const pkgFile of pkgFiles) {
633
+ addImportEdge(file.path, pkgFile);
634
+ }
635
+ return; // skip single-file resolution
636
+ }
637
+ // Fall through if no files found (package might be external)
638
+ }
639
+ // ---- PHP: handle namespace-based imports (use statements) ----
640
+ if (language === SupportedLanguages.PHP) {
641
+ const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
642
+ if (resolved) {
643
+ addImportEdge(file.path, resolved);
644
+ }
645
+ return;
646
+ }
647
+ // ---- Standard single-file resolution ----
648
+ const resolvedPath = resolveImportPath(file.path, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
649
+ if (resolvedPath) {
650
+ addImportEdge(file.path, resolvedPath);
651
+ }
652
+ }
653
+ });
654
+ // Tree is now owned by the LRU cache — no manual delete needed
655
+ }
656
+ if (isDev) {
657
+ console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
658
+ }
659
+ };
660
+ // ============================================================================
661
+ // FAST PATH: Resolve pre-extracted imports (no parsing needed)
662
+ // ============================================================================
663
+ export const processImportsFromExtracted = async (graph, files, extractedImports, importMap, onProgress, repoRoot, prebuiltCtx) => {
664
+ const ctx = prebuiltCtx ?? buildImportResolutionContext(files.map(f => f.path));
665
+ const { allFilePaths, allFileList, normalizedFileList, suffixIndex: index, resolveCache } = ctx;
666
+ let totalImportsFound = 0;
667
+ let totalImportsResolved = 0;
668
+ const effectiveRoot = repoRoot || '';
669
+ const tsconfigPaths = await loadTsconfigPaths(effectiveRoot);
670
+ const goModule = await loadGoModulePath(effectiveRoot);
671
+ const composerConfig = await loadComposerConfig(effectiveRoot);
672
+ const addImportEdge = (filePath, resolvedPath) => {
673
+ const sourceId = generateId('File', filePath);
674
+ const targetId = generateId('File', resolvedPath);
675
+ const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
676
+ totalImportsResolved++;
677
+ graph.addRelationship({
678
+ id: relId,
679
+ sourceId,
680
+ targetId,
681
+ type: 'IMPORTS',
682
+ confidence: 1.0,
683
+ reason: '',
684
+ });
685
+ if (!importMap.has(filePath)) {
686
+ importMap.set(filePath, new Set());
687
+ }
688
+ importMap.get(filePath).add(resolvedPath);
689
+ };
690
+ // Group by file for progress reporting (users see file count, not import count)
691
+ const importsByFile = new Map();
692
+ for (const imp of extractedImports) {
693
+ let list = importsByFile.get(imp.filePath);
694
+ if (!list) {
695
+ list = [];
696
+ importsByFile.set(imp.filePath, list);
697
+ }
698
+ list.push(imp);
699
+ }
700
+ const totalFiles = importsByFile.size;
701
+ let filesProcessed = 0;
702
+ // Pre-build a suffix index for O(1) suffix lookups instead of O(n) linear scans
703
+ const suffixIndex = new Map();
704
+ for (let i = 0; i < normalizedFileList.length; i++) {
705
+ const normalized = normalizedFileList[i];
706
+ // Index by last path segment (filename) for fast suffix matching
707
+ const lastSlash = normalized.lastIndexOf('/');
708
+ const filename = lastSlash >= 0 ? normalized.substring(lastSlash + 1) : normalized;
709
+ let list = suffixIndex.get(filename);
710
+ if (!list) {
711
+ list = [];
712
+ suffixIndex.set(filename, list);
713
+ }
714
+ list.push(allFileList[i]);
715
+ }
716
+ for (const [filePath, fileImports] of importsByFile) {
717
+ filesProcessed++;
718
+ if (filesProcessed % 100 === 0) {
719
+ onProgress?.(filesProcessed, totalFiles);
720
+ await yieldToEventLoop();
721
+ }
722
+ for (const { rawImportPath, language } of fileImports) {
723
+ totalImportsFound++;
724
+ // Check resolve cache first
725
+ const cacheKey = `${filePath}::${rawImportPath}`;
726
+ if (resolveCache.has(cacheKey)) {
727
+ const cached = resolveCache.get(cacheKey);
728
+ if (cached)
729
+ addImportEdge(filePath, cached);
730
+ continue;
731
+ }
732
+ // Java: handle wildcards and static imports
733
+ if (language === SupportedLanguages.Java) {
734
+ if (rawImportPath.endsWith('.*')) {
735
+ const matchedFiles = resolveJavaWildcard(rawImportPath, normalizedFileList, allFileList, index);
736
+ for (const matchedFile of matchedFiles) {
737
+ addImportEdge(filePath, matchedFile);
738
+ }
739
+ continue;
740
+ }
741
+ const staticResolved = resolveJavaStaticImport(rawImportPath, normalizedFileList, allFileList, index);
742
+ if (staticResolved) {
743
+ resolveCache.set(cacheKey, staticResolved);
744
+ addImportEdge(filePath, staticResolved);
745
+ continue;
746
+ }
747
+ }
748
+ // Go: handle package-level imports
749
+ if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
750
+ const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
751
+ if (pkgFiles.length > 0) {
752
+ for (const pkgFile of pkgFiles) {
753
+ addImportEdge(filePath, pkgFile);
754
+ }
755
+ continue;
756
+ }
757
+ }
758
+ // PHP: handle namespace-based imports (use statements)
759
+ if (language === SupportedLanguages.PHP) {
760
+ const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
761
+ if (resolved) {
762
+ resolveCache.set(cacheKey, resolved);
763
+ addImportEdge(filePath, resolved);
764
+ }
765
+ continue;
766
+ }
767
+ // Standard resolution (has its own internal cache)
768
+ const resolvedPath = resolveImportPath(filePath, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
769
+ if (resolvedPath) {
770
+ addImportEdge(filePath, resolvedPath);
771
+ }
772
+ }
773
+ }
774
+ onProgress?.(totalFiles, totalFiles);
775
+ if (isDev) {
776
+ console.log(`📊 Import processing (fast path): ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
777
+ }
778
+ };