gitnexus 1.0.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.
- package/README.md +181 -0
- package/dist/cli/ai-context.d.ts +21 -0
- package/dist/cli/ai-context.js +219 -0
- package/dist/cli/analyze.d.ts +10 -0
- package/dist/cli/analyze.js +118 -0
- package/dist/cli/clean.d.ts +8 -0
- package/dist/cli/clean.js +29 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +7 -0
- package/dist/cli/mcp.js +85 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.js +5 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +27 -0
- package/dist/config/ignore-service.d.ts +1 -0
- package/dist/config/ignore-service.js +208 -0
- package/dist/config/supported-languages.d.ts +11 -0
- package/dist/config/supported-languages.js +15 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +205 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +50 -0
- package/dist/core/embeddings/embedding-pipeline.js +321 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +61 -0
- package/dist/core/graph/types.d.ts +50 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +8 -0
- package/dist/core/ingestion/call-processor.js +269 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +269 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +39 -0
- package/dist/core/ingestion/entry-point-scoring.js +235 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +5 -0
- package/dist/core/ingestion/filesystem-walker.js +26 -0
- package/dist/core/ingestion/framework-detection.d.ts +38 -0
- package/dist/core/ingestion/framework-detection.js +183 -0
- package/dist/core/ingestion/heritage-processor.d.ts +14 -0
- package/dist/core/ingestion/heritage-processor.js +134 -0
- package/dist/core/ingestion/import-processor.d.ts +8 -0
- package/dist/core/ingestion/import-processor.js +490 -0
- package/dist/core/ingestion/parsing-processor.d.ts +8 -0
- package/dist/core/ingestion/parsing-processor.js +249 -0
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +228 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +278 -0
- package/dist/core/ingestion/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +33 -0
- package/dist/core/ingestion/symbol-table.js +38 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -0
- package/dist/core/ingestion/tree-sitter-queries.js +319 -0
- package/dist/core/ingestion/utils.d.ts +10 -0
- package/dist/core/ingestion/utils.js +44 -0
- package/dist/core/kuzu/csv-generator.d.ts +22 -0
- package/dist/core/kuzu/csv-generator.js +272 -0
- package/dist/core/kuzu/kuzu-adapter.d.ts +81 -0
- package/dist/core/kuzu/kuzu-adapter.js +568 -0
- package/dist/core/kuzu/schema.d.ts +53 -0
- package/dist/core/kuzu/schema.js +380 -0
- package/dist/core/search/bm25-index.d.ts +22 -0
- package/dist/core/search/bm25-index.js +52 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +4 -0
- package/dist/core/tree-sitter/parser-loader.js +42 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +93 -0
- package/dist/mcp/core/kuzu-adapter.d.ts +23 -0
- package/dist/mcp/core/kuzu-adapter.js +62 -0
- package/dist/mcp/local/local-backend.d.ts +73 -0
- package/dist/mcp/local/local-backend.js +752 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +279 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.js +130 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +160 -0
- package/dist/server/api.d.ts +6 -0
- package/dist/server/api.js +156 -0
- package/dist/storage/git.d.ts +7 -0
- package/dist/storage/git.js +39 -0
- package/dist/storage/repo-manager.d.ts +61 -0
- package/dist/storage/repo-manager.js +106 -0
- package/dist/types/pipeline.d.ts +28 -0
- package/dist/types/pipeline.js +16 -0
- package/package.json +80 -0
- package/skills/debugging.md +104 -0
- package/skills/exploring.md +112 -0
- package/skills/impact-analysis.md +114 -0
- package/skills/refactoring.md +119 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,490 @@
|
|
|
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 !== 'production';
|
|
10
|
+
export const createImportMap = () => new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Parse tsconfig.json to extract path aliases.
|
|
13
|
+
* Tries tsconfig.json, tsconfig.app.json, tsconfig.base.json in order.
|
|
14
|
+
*/
|
|
15
|
+
async function loadTsconfigPaths(repoRoot) {
|
|
16
|
+
const candidates = ['tsconfig.json', 'tsconfig.app.json', 'tsconfig.base.json'];
|
|
17
|
+
for (const filename of candidates) {
|
|
18
|
+
try {
|
|
19
|
+
const tsconfigPath = path.join(repoRoot, filename);
|
|
20
|
+
const raw = await fs.readFile(tsconfigPath, 'utf-8');
|
|
21
|
+
// Strip JSON comments (// and /* */ style) for robustness
|
|
22
|
+
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
23
|
+
const tsconfig = JSON.parse(stripped);
|
|
24
|
+
const compilerOptions = tsconfig.compilerOptions;
|
|
25
|
+
if (!compilerOptions?.paths)
|
|
26
|
+
continue;
|
|
27
|
+
const baseUrl = compilerOptions.baseUrl || '.';
|
|
28
|
+
const aliases = new Map();
|
|
29
|
+
for (const [pattern, targets] of Object.entries(compilerOptions.paths)) {
|
|
30
|
+
if (!Array.isArray(targets) || targets.length === 0)
|
|
31
|
+
continue;
|
|
32
|
+
const target = targets[0];
|
|
33
|
+
// Convert glob patterns: "@/*" -> "@/", "src/*" -> "src/"
|
|
34
|
+
const aliasPrefix = pattern.endsWith('/*') ? pattern.slice(0, -1) : pattern;
|
|
35
|
+
const targetPrefix = target.endsWith('/*') ? target.slice(0, -1) : target;
|
|
36
|
+
aliases.set(aliasPrefix, targetPrefix);
|
|
37
|
+
}
|
|
38
|
+
if (aliases.size > 0) {
|
|
39
|
+
if (isDev) {
|
|
40
|
+
console.log(`📦 Loaded ${aliases.size} path aliases from ${filename}`);
|
|
41
|
+
}
|
|
42
|
+
return { aliases, baseUrl };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// File doesn't exist or isn't valid JSON - try next
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse go.mod to extract module path.
|
|
53
|
+
*/
|
|
54
|
+
async function loadGoModulePath(repoRoot) {
|
|
55
|
+
try {
|
|
56
|
+
const goModPath = path.join(repoRoot, 'go.mod');
|
|
57
|
+
const content = await fs.readFile(goModPath, 'utf-8');
|
|
58
|
+
const match = content.match(/^module\s+(\S+)/m);
|
|
59
|
+
if (match) {
|
|
60
|
+
if (isDev) {
|
|
61
|
+
console.log(`📦 Loaded Go module path: ${match[1]}`);
|
|
62
|
+
}
|
|
63
|
+
return { modulePath: match[1] };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// No go.mod
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// IMPORT PATH RESOLUTION
|
|
73
|
+
// ============================================================================
|
|
74
|
+
/** All file extensions to try during resolution */
|
|
75
|
+
const EXTENSIONS = [
|
|
76
|
+
'',
|
|
77
|
+
// TypeScript/JavaScript
|
|
78
|
+
'.tsx', '.ts', '.jsx', '.js', '/index.tsx', '/index.ts', '/index.jsx', '/index.js',
|
|
79
|
+
// Python
|
|
80
|
+
'.py', '/__init__.py',
|
|
81
|
+
// Java
|
|
82
|
+
'.java',
|
|
83
|
+
// C/C++
|
|
84
|
+
'.c', '.h', '.cpp', '.hpp', '.cc', '.cxx', '.hxx', '.hh',
|
|
85
|
+
// C#
|
|
86
|
+
'.cs',
|
|
87
|
+
// Go
|
|
88
|
+
'.go',
|
|
89
|
+
// Rust
|
|
90
|
+
'.rs', '/mod.rs',
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* Try to match a path (with extensions) against the known file set.
|
|
94
|
+
* Returns the matched file path or null.
|
|
95
|
+
*/
|
|
96
|
+
function tryResolveWithExtensions(basePath, allFiles) {
|
|
97
|
+
for (const ext of EXTENSIONS) {
|
|
98
|
+
const candidate = basePath + ext;
|
|
99
|
+
if (allFiles.has(candidate))
|
|
100
|
+
return candidate;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Suffix-based resolution: try progressively shorter suffixes against all files.
|
|
106
|
+
* Used for package-style imports (Java, Python, etc.).
|
|
107
|
+
*/
|
|
108
|
+
function suffixResolve(pathParts, normalizedFileList, allFileList) {
|
|
109
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
110
|
+
const suffix = pathParts.slice(i).join('/');
|
|
111
|
+
for (const ext of EXTENSIONS) {
|
|
112
|
+
const suffixWithExt = suffix + ext;
|
|
113
|
+
const suffixPattern = '/' + suffixWithExt;
|
|
114
|
+
const matchIdx = normalizedFileList.findIndex(filePath => filePath.endsWith(suffixPattern) || filePath.toLowerCase().endsWith(suffixPattern.toLowerCase()));
|
|
115
|
+
if (matchIdx !== -1) {
|
|
116
|
+
return allFileList[matchIdx];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Resolve an import path to a file path in the repository.
|
|
124
|
+
*
|
|
125
|
+
* Language-specific preprocessing is applied before the generic resolution:
|
|
126
|
+
* - TypeScript/JavaScript: rewrites tsconfig path aliases
|
|
127
|
+
* - Rust: converts crate::/super::/self:: to relative paths
|
|
128
|
+
*
|
|
129
|
+
* Java wildcards and Go package imports are handled separately in processImports
|
|
130
|
+
* because they resolve to multiple files.
|
|
131
|
+
*/
|
|
132
|
+
const resolveImportPath = (currentFile, importPath, allFiles, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths) => {
|
|
133
|
+
const cacheKey = `${currentFile}::${importPath}`;
|
|
134
|
+
if (resolveCache.has(cacheKey))
|
|
135
|
+
return resolveCache.get(cacheKey) ?? null;
|
|
136
|
+
const cache = (result) => {
|
|
137
|
+
resolveCache.set(cacheKey, result);
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
// ---- TypeScript/JavaScript: rewrite path aliases ----
|
|
141
|
+
if ((language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript) &&
|
|
142
|
+
tsconfigPaths &&
|
|
143
|
+
!importPath.startsWith('.')) {
|
|
144
|
+
for (const [aliasPrefix, targetPrefix] of tsconfigPaths.aliases) {
|
|
145
|
+
if (importPath.startsWith(aliasPrefix)) {
|
|
146
|
+
const remainder = importPath.slice(aliasPrefix.length);
|
|
147
|
+
// Build the rewritten path relative to baseUrl
|
|
148
|
+
const rewritten = tsconfigPaths.baseUrl === '.'
|
|
149
|
+
? targetPrefix + remainder
|
|
150
|
+
: tsconfigPaths.baseUrl + '/' + targetPrefix + remainder;
|
|
151
|
+
// Try direct resolution from repo root
|
|
152
|
+
const resolved = tryResolveWithExtensions(rewritten, allFiles);
|
|
153
|
+
if (resolved)
|
|
154
|
+
return cache(resolved);
|
|
155
|
+
// Try suffix matching as fallback
|
|
156
|
+
const parts = rewritten.split('/').filter(Boolean);
|
|
157
|
+
const suffixResult = suffixResolve(parts, normalizedFileList, allFileList);
|
|
158
|
+
if (suffixResult)
|
|
159
|
+
return cache(suffixResult);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// ---- Rust: convert module path syntax to file paths ----
|
|
164
|
+
if (language === SupportedLanguages.Rust) {
|
|
165
|
+
const rustResult = resolveRustImport(currentFile, importPath, allFiles);
|
|
166
|
+
if (rustResult)
|
|
167
|
+
return cache(rustResult);
|
|
168
|
+
// Fall through to generic resolution if Rust-specific didn't match
|
|
169
|
+
}
|
|
170
|
+
// ---- Generic relative import resolution (./ and ../) ----
|
|
171
|
+
const currentDir = currentFile.split('/').slice(0, -1);
|
|
172
|
+
const parts = importPath.split('/');
|
|
173
|
+
for (const part of parts) {
|
|
174
|
+
if (part === '.')
|
|
175
|
+
continue;
|
|
176
|
+
if (part === '..') {
|
|
177
|
+
currentDir.pop();
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
currentDir.push(part);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const basePath = currentDir.join('/');
|
|
184
|
+
if (importPath.startsWith('.')) {
|
|
185
|
+
const resolved = tryResolveWithExtensions(basePath, allFiles);
|
|
186
|
+
return cache(resolved);
|
|
187
|
+
}
|
|
188
|
+
// ---- Generic package/absolute import resolution (suffix matching) ----
|
|
189
|
+
// Java wildcards are handled in processImports, not here
|
|
190
|
+
if (importPath.endsWith('.*')) {
|
|
191
|
+
return cache(null);
|
|
192
|
+
}
|
|
193
|
+
const pathLike = importPath.includes('/')
|
|
194
|
+
? importPath
|
|
195
|
+
: importPath.replace(/\./g, '/');
|
|
196
|
+
const pathParts = pathLike.split('/').filter(Boolean);
|
|
197
|
+
const resolved = suffixResolve(pathParts, normalizedFileList, allFileList);
|
|
198
|
+
return cache(resolved);
|
|
199
|
+
};
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// RUST MODULE RESOLUTION
|
|
202
|
+
// ============================================================================
|
|
203
|
+
/**
|
|
204
|
+
* Resolve Rust use-path to a file.
|
|
205
|
+
* Handles crate::, super::, self:: prefixes and :: path separators.
|
|
206
|
+
*/
|
|
207
|
+
function resolveRustImport(currentFile, importPath, allFiles) {
|
|
208
|
+
let rustPath;
|
|
209
|
+
if (importPath.startsWith('crate::')) {
|
|
210
|
+
// crate:: resolves from src/ directory (standard Rust layout)
|
|
211
|
+
rustPath = importPath.slice(7).replace(/::/g, '/');
|
|
212
|
+
// Try from src/ (standard layout)
|
|
213
|
+
const fromSrc = tryRustModulePath('src/' + rustPath, allFiles);
|
|
214
|
+
if (fromSrc)
|
|
215
|
+
return fromSrc;
|
|
216
|
+
// Try from repo root (non-standard)
|
|
217
|
+
const fromRoot = tryRustModulePath(rustPath, allFiles);
|
|
218
|
+
if (fromRoot)
|
|
219
|
+
return fromRoot;
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (importPath.startsWith('super::')) {
|
|
223
|
+
// super:: = parent directory of current file's module
|
|
224
|
+
const currentDir = currentFile.split('/').slice(0, -1);
|
|
225
|
+
currentDir.pop(); // Go up one level for super::
|
|
226
|
+
rustPath = importPath.slice(7).replace(/::/g, '/');
|
|
227
|
+
const fullPath = [...currentDir, rustPath].join('/');
|
|
228
|
+
return tryRustModulePath(fullPath, allFiles);
|
|
229
|
+
}
|
|
230
|
+
if (importPath.startsWith('self::')) {
|
|
231
|
+
// self:: = current module's directory
|
|
232
|
+
const currentDir = currentFile.split('/').slice(0, -1);
|
|
233
|
+
rustPath = importPath.slice(6).replace(/::/g, '/');
|
|
234
|
+
const fullPath = [...currentDir, rustPath].join('/');
|
|
235
|
+
return tryRustModulePath(fullPath, allFiles);
|
|
236
|
+
}
|
|
237
|
+
// Bare path without prefix (e.g., from a use in a nested module)
|
|
238
|
+
// Convert :: to / and try suffix matching
|
|
239
|
+
if (importPath.includes('::')) {
|
|
240
|
+
rustPath = importPath.replace(/::/g, '/');
|
|
241
|
+
return tryRustModulePath(rustPath, allFiles);
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Try to resolve a Rust module path to a file.
|
|
247
|
+
* Tries: path.rs, path/mod.rs, and with the last segment stripped
|
|
248
|
+
* (last segment might be a symbol name, not a module).
|
|
249
|
+
*/
|
|
250
|
+
function tryRustModulePath(modulePath, allFiles) {
|
|
251
|
+
// Try direct: path.rs
|
|
252
|
+
if (allFiles.has(modulePath + '.rs'))
|
|
253
|
+
return modulePath + '.rs';
|
|
254
|
+
// Try directory: path/mod.rs
|
|
255
|
+
if (allFiles.has(modulePath + '/mod.rs'))
|
|
256
|
+
return modulePath + '/mod.rs';
|
|
257
|
+
// Try path/lib.rs (for crate root)
|
|
258
|
+
if (allFiles.has(modulePath + '/lib.rs'))
|
|
259
|
+
return modulePath + '/lib.rs';
|
|
260
|
+
// The last segment might be a symbol (function, struct, etc.), not a module.
|
|
261
|
+
// Strip it and try again.
|
|
262
|
+
const lastSlash = modulePath.lastIndexOf('/');
|
|
263
|
+
if (lastSlash > 0) {
|
|
264
|
+
const parentPath = modulePath.substring(0, lastSlash);
|
|
265
|
+
if (allFiles.has(parentPath + '.rs'))
|
|
266
|
+
return parentPath + '.rs';
|
|
267
|
+
if (allFiles.has(parentPath + '/mod.rs'))
|
|
268
|
+
return parentPath + '/mod.rs';
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// JAVA MULTI-FILE RESOLUTION
|
|
274
|
+
// ============================================================================
|
|
275
|
+
/**
|
|
276
|
+
* Resolve a Java wildcard import (com.example.*) to all matching .java files.
|
|
277
|
+
* Returns an array of file paths.
|
|
278
|
+
*/
|
|
279
|
+
function resolveJavaWildcard(importPath, normalizedFileList, allFileList) {
|
|
280
|
+
// "com.example.util.*" -> "com/example/util"
|
|
281
|
+
const packagePath = importPath.slice(0, -2).replace(/\./g, '/');
|
|
282
|
+
const packageSuffix = '/' + packagePath + '/';
|
|
283
|
+
const matches = [];
|
|
284
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
285
|
+
const normalized = normalizedFileList[i];
|
|
286
|
+
if (normalized.includes(packageSuffix) && normalized.endsWith('.java')) {
|
|
287
|
+
// Ensure the file is directly in the package (not a subdirectory)
|
|
288
|
+
const afterPackage = normalized.substring(normalized.indexOf(packageSuffix) + packageSuffix.length);
|
|
289
|
+
if (!afterPackage.includes('/')) {
|
|
290
|
+
matches.push(allFileList[i]);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return matches;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Try to resolve a Java static import by stripping the member name.
|
|
298
|
+
* "com.example.Constants.VALUE" -> resolve "com.example.Constants"
|
|
299
|
+
*/
|
|
300
|
+
function resolveJavaStaticImport(importPath, normalizedFileList, allFileList) {
|
|
301
|
+
// Static imports look like: com.example.Constants.VALUE or com.example.Constants.*
|
|
302
|
+
// The last segment is a member name (field/method) if it starts with lowercase or is ALL_CAPS
|
|
303
|
+
const segments = importPath.split('.');
|
|
304
|
+
if (segments.length < 3)
|
|
305
|
+
return null;
|
|
306
|
+
const lastSeg = segments[segments.length - 1];
|
|
307
|
+
// If last segment is a wildcard or ALL_CAPS constant or starts with lowercase, strip it
|
|
308
|
+
if (lastSeg === '*' || /^[a-z]/.test(lastSeg) || /^[A-Z_]+$/.test(lastSeg)) {
|
|
309
|
+
const classPath = segments.slice(0, -1).join('/');
|
|
310
|
+
const classSuffix = '/' + classPath + '.java';
|
|
311
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
312
|
+
if (normalizedFileList[i].endsWith(classSuffix) ||
|
|
313
|
+
normalizedFileList[i].toLowerCase().endsWith(classSuffix.toLowerCase())) {
|
|
314
|
+
return allFileList[i];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// GO PACKAGE RESOLUTION
|
|
322
|
+
// ============================================================================
|
|
323
|
+
/**
|
|
324
|
+
* Resolve a Go internal package import to all .go files in the package directory.
|
|
325
|
+
* Returns an array of file paths.
|
|
326
|
+
*/
|
|
327
|
+
function resolveGoPackage(importPath, goModule, normalizedFileList, allFileList) {
|
|
328
|
+
if (!importPath.startsWith(goModule.modulePath))
|
|
329
|
+
return [];
|
|
330
|
+
// Strip module path to get relative package path
|
|
331
|
+
const relativePkg = importPath.slice(goModule.modulePath.length + 1); // e.g., "internal/auth"
|
|
332
|
+
if (!relativePkg)
|
|
333
|
+
return [];
|
|
334
|
+
const pkgSuffix = '/' + relativePkg + '/';
|
|
335
|
+
const matches = [];
|
|
336
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
337
|
+
const normalized = normalizedFileList[i];
|
|
338
|
+
// File must be directly in the package directory (not a subdirectory)
|
|
339
|
+
if (normalized.includes(pkgSuffix) && normalized.endsWith('.go') && !normalized.endsWith('_test.go')) {
|
|
340
|
+
const afterPkg = normalized.substring(normalized.indexOf(pkgSuffix) + pkgSuffix.length);
|
|
341
|
+
if (!afterPkg.includes('/')) {
|
|
342
|
+
matches.push(allFileList[i]);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return matches;
|
|
347
|
+
}
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// MAIN IMPORT PROCESSOR
|
|
350
|
+
// ============================================================================
|
|
351
|
+
export const processImports = async (graph, files, astCache, importMap, onProgress, repoRoot) => {
|
|
352
|
+
// Create a Set of all file paths for fast lookup during resolution
|
|
353
|
+
const allFilePaths = new Set(files.map(f => f.path));
|
|
354
|
+
const parser = await loadParser();
|
|
355
|
+
const resolveCache = new Map();
|
|
356
|
+
const allFileList = files.map(f => f.path);
|
|
357
|
+
// Pre-compute normalized file list once (forward slashes)
|
|
358
|
+
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
|
359
|
+
// Track import statistics
|
|
360
|
+
let totalImportsFound = 0;
|
|
361
|
+
let totalImportsResolved = 0;
|
|
362
|
+
// Load language-specific configs once before the file loop
|
|
363
|
+
const effectiveRoot = repoRoot || '';
|
|
364
|
+
const tsconfigPaths = await loadTsconfigPaths(effectiveRoot);
|
|
365
|
+
const goModule = await loadGoModulePath(effectiveRoot);
|
|
366
|
+
// Helper: add an IMPORTS edge + update import map
|
|
367
|
+
const addImportEdge = (filePath, resolvedPath) => {
|
|
368
|
+
const sourceId = generateId('File', filePath);
|
|
369
|
+
const targetId = generateId('File', resolvedPath);
|
|
370
|
+
const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
|
|
371
|
+
totalImportsResolved++;
|
|
372
|
+
graph.addRelationship({
|
|
373
|
+
id: relId,
|
|
374
|
+
sourceId,
|
|
375
|
+
targetId,
|
|
376
|
+
type: 'IMPORTS',
|
|
377
|
+
confidence: 1.0,
|
|
378
|
+
reason: '',
|
|
379
|
+
});
|
|
380
|
+
if (!importMap.has(filePath)) {
|
|
381
|
+
importMap.set(filePath, new Set());
|
|
382
|
+
}
|
|
383
|
+
importMap.get(filePath).add(resolvedPath);
|
|
384
|
+
};
|
|
385
|
+
for (let i = 0; i < files.length; i++) {
|
|
386
|
+
const file = files[i];
|
|
387
|
+
onProgress?.(i + 1, files.length);
|
|
388
|
+
if (i % 20 === 0)
|
|
389
|
+
await yieldToEventLoop();
|
|
390
|
+
// 1. Check language support first
|
|
391
|
+
const language = getLanguageFromFilename(file.path);
|
|
392
|
+
if (!language)
|
|
393
|
+
continue;
|
|
394
|
+
const queryStr = LANGUAGE_QUERIES[language];
|
|
395
|
+
if (!queryStr)
|
|
396
|
+
continue;
|
|
397
|
+
// 2. ALWAYS load the language before querying (parser is stateful)
|
|
398
|
+
await loadLanguage(language, file.path);
|
|
399
|
+
// 3. Get AST (Try Cache First)
|
|
400
|
+
let tree = astCache.get(file.path);
|
|
401
|
+
let wasReparsed = false;
|
|
402
|
+
if (!tree) {
|
|
403
|
+
try {
|
|
404
|
+
tree = parser.parse(file.content, undefined, { bufferSize: 1024 * 256 });
|
|
405
|
+
}
|
|
406
|
+
catch (parseError) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
wasReparsed = true;
|
|
410
|
+
}
|
|
411
|
+
let query;
|
|
412
|
+
let matches;
|
|
413
|
+
try {
|
|
414
|
+
const lang = parser.getLanguage();
|
|
415
|
+
query = new Parser.Query(lang, queryStr);
|
|
416
|
+
matches = query.matches(tree.rootNode);
|
|
417
|
+
}
|
|
418
|
+
catch (queryError) {
|
|
419
|
+
if (isDev) {
|
|
420
|
+
console.group(`🔴 Query Error: ${file.path}`);
|
|
421
|
+
console.log('Language:', language);
|
|
422
|
+
console.log('Query (first 200 chars):', queryStr.substring(0, 200) + '...');
|
|
423
|
+
console.log('Error:', queryError?.message || queryError);
|
|
424
|
+
console.log('File content (first 300 chars):', file.content.substring(0, 300));
|
|
425
|
+
console.log('AST root type:', tree.rootNode?.type);
|
|
426
|
+
console.log('AST has errors:', tree.rootNode?.hasError);
|
|
427
|
+
console.groupEnd();
|
|
428
|
+
}
|
|
429
|
+
if (wasReparsed)
|
|
430
|
+
tree.delete?.();
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
matches.forEach(match => {
|
|
434
|
+
const captureMap = {};
|
|
435
|
+
match.captures.forEach(c => captureMap[c.name] = c.node);
|
|
436
|
+
if (captureMap['import']) {
|
|
437
|
+
const sourceNode = captureMap['import.source'];
|
|
438
|
+
if (!sourceNode) {
|
|
439
|
+
if (isDev) {
|
|
440
|
+
console.log(`⚠️ Import captured but no source node in ${file.path}`);
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
// Clean path (remove quotes and angle brackets for C/C++ includes)
|
|
445
|
+
const rawImportPath = sourceNode.text.replace(/['"<>]/g, '');
|
|
446
|
+
totalImportsFound++;
|
|
447
|
+
// ---- Java: handle wildcards and static imports specially ----
|
|
448
|
+
if (language === SupportedLanguages.Java) {
|
|
449
|
+
if (rawImportPath.endsWith('.*')) {
|
|
450
|
+
const matchedFiles = resolveJavaWildcard(rawImportPath, normalizedFileList, allFileList);
|
|
451
|
+
for (const matchedFile of matchedFiles) {
|
|
452
|
+
addImportEdge(file.path, matchedFile);
|
|
453
|
+
}
|
|
454
|
+
return; // skip single-file resolution
|
|
455
|
+
}
|
|
456
|
+
// Try static import resolution (strip member name)
|
|
457
|
+
const staticResolved = resolveJavaStaticImport(rawImportPath, normalizedFileList, allFileList);
|
|
458
|
+
if (staticResolved) {
|
|
459
|
+
addImportEdge(file.path, staticResolved);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// Fall through to normal resolution for regular Java imports
|
|
463
|
+
}
|
|
464
|
+
// ---- Go: handle package-level imports ----
|
|
465
|
+
if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
|
466
|
+
const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
|
|
467
|
+
if (pkgFiles.length > 0) {
|
|
468
|
+
for (const pkgFile of pkgFiles) {
|
|
469
|
+
addImportEdge(file.path, pkgFile);
|
|
470
|
+
}
|
|
471
|
+
return; // skip single-file resolution
|
|
472
|
+
}
|
|
473
|
+
// Fall through if no files found (package might be external)
|
|
474
|
+
}
|
|
475
|
+
// ---- Standard single-file resolution ----
|
|
476
|
+
const resolvedPath = resolveImportPath(file.path, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths);
|
|
477
|
+
if (resolvedPath) {
|
|
478
|
+
addImportEdge(file.path, resolvedPath);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
// If re-parsed just for this, delete the tree to save memory
|
|
483
|
+
if (wasReparsed) {
|
|
484
|
+
tree.delete?.();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (isDev) {
|
|
488
|
+
console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { KnowledgeGraph } from '../graph/types.js';
|
|
2
|
+
import { SymbolTable } from './symbol-table.js';
|
|
3
|
+
import { ASTCache } from './ast-cache.js';
|
|
4
|
+
export type FileProgressCallback = (current: number, total: number, filePath: string) => void;
|
|
5
|
+
export declare const processParsing: (graph: KnowledgeGraph, files: {
|
|
6
|
+
path: string;
|
|
7
|
+
content: string;
|
|
8
|
+
}[], symbolTable: SymbolTable, astCache: ASTCache, onFileProgress?: FileProgressCallback) => Promise<void>;
|