@veewo/gitnexus 1.3.11 → 1.4.6-rc
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 +37 -80
- package/dist/benchmark/agent-context/tool-runner.js +2 -2
- package/dist/benchmark/neonspark-candidates.js +3 -3
- package/dist/benchmark/tool-runner.js +2 -2
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +16 -12
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +68 -48
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +8 -1
- package/dist/cli/eval-server.js +30 -13
- package/dist/cli/index.js +28 -82
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/mcp.js +3 -1
- package/dist/cli/setup.js +87 -48
- package/dist/cli/setup.test.js +18 -13
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +3 -2
- package/dist/cli/tool.js +50 -16
- package/dist/cli/wiki.js +8 -4
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +4 -1
- package/dist/config/supported-languages.js +3 -2
- package/dist/core/augmentation/engine.js +94 -67
- package/dist/core/embeddings/embedder.d.ts +1 -1
- package/dist/core/embeddings/embedder.js +1 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
- package/dist/core/embeddings/embedding-pipeline.js +52 -25
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +7 -2
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/call-processor.d.ts +8 -6
- package/dist/core/ingestion/call-processor.js +468 -206
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +116 -23
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.d.ts +19 -4
- package/dist/core/ingestion/framework-detection.js +182 -6
- package/dist/core/ingestion/heritage-processor.d.ts +13 -5
- package/dist/core/ingestion/heritage-processor.js +109 -55
- package/dist/core/ingestion/import-processor.d.ts +16 -20
- package/dist/core/ingestion/import-processor.js +199 -579
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +4 -1
- package/dist/core/ingestion/parsing-processor.js +107 -109
- package/dist/core/ingestion/pipeline.d.ts +6 -3
- package/dist/core/ingestion/pipeline.js +208 -114
- package/dist/core/ingestion/process-processor.js +8 -2
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/symbol-table.d.ts +21 -1
- package/dist/core/ingestion/symbol-table.js +40 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +13 -10
- package/dist/core/ingestion/tree-sitter-queries.js +297 -7
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +611 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +383 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +467 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +681 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +549 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +406 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +449 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
- package/dist/core/ingestion/type-extractors/shared.js +703 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +494 -0
- package/dist/core/ingestion/utils.d.ts +103 -0
- package/dist/core/ingestion/utils.js +1085 -4
- package/dist/core/ingestion/workers/parse-worker.d.ts +51 -4
- package/dist/core/ingestion/workers/parse-worker.js +634 -222
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +12 -10
- package/dist/core/{kuzu → lbug}/csv-generator.js +82 -101
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +20 -25
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +150 -122
- package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
- package/dist/core/{kuzu → lbug}/schema.js +23 -22
- package/dist/core/lbug/schema.test.d.ts +1 -0
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +12 -11
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +6 -6
- package/dist/core/tree-sitter/parser-loader.d.ts +1 -0
- package/dist/core/tree-sitter/parser-loader.js +19 -0
- package/dist/core/wiki/generator.d.ts +2 -2
- package/dist/core/wiki/generator.js +6 -6
- package/dist/core/wiki/graph-queries.d.ts +4 -4
- package/dist/core/wiki/graph-queries.js +7 -7
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +11 -10
- package/dist/mcp/core/lbug-adapter.js +327 -0
- package/dist/mcp/local/local-backend.d.ts +21 -16
- package/dist/mcp/local/local-backend.js +306 -706
- package/dist/mcp/local/unity-parity-seed-loader.d.ts +6 -1
- package/dist/mcp/local/unity-parity-seed-loader.js +119 -9
- package/dist/mcp/local/unity-parity-seed-loader.test.js +95 -7
- package/dist/mcp/resources.js +2 -2
- package/dist/mcp/server.js +28 -13
- package/dist/mcp/staleness.js +2 -2
- package/dist/mcp/tools.js +12 -3
- package/dist/server/api.js +12 -12
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +1 -1
- package/dist/storage/git.js +4 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +74 -4
- package/dist/types/pipeline.d.ts +1 -1
- package/hooks/claude/gitnexus-hook.cjs +149 -46
- package/hooks/claude/pre-tool-use.sh +2 -1
- package/hooks/claude/session-start.sh +0 -0
- package/package.json +20 -4
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/skills/gitnexus-cli.md +8 -8
- package/skills/gitnexus-debugging.md +1 -1
- package/skills/gitnexus-exploring.md +1 -1
- package/skills/gitnexus-guide.md +1 -1
- package/skills/gitnexus-impact-analysis.md +1 -1
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/dist/cli/claude-hooks.d.ts +0 -22
- package/dist/cli/claude-hooks.js +0 -97
- package/dist/mcp/core/kuzu-adapter.js +0 -231
- /package/dist/core/{kuzu/csv-generator.test.d.ts → ingestion/type-extractors/types.js} +0 -0
- /package/dist/core/{kuzu/relationship-pair-buckets.test.d.ts → lbug/csv-generator.test.d.ts} +0 -0
- /package/dist/core/{kuzu → lbug}/csv-generator.test.js +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.d.ts +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.js +0 -0
- /package/dist/core/{kuzu/schema.test.d.ts → lbug/relationship-pair-buckets.test.d.ts} +0 -0
- /package/dist/core/{kuzu → lbug}/relationship-pair-buckets.test.js +0 -0
- /package/dist/core/{kuzu → lbug}/schema.test.js +0 -0
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import Parser from 'tree-sitter';
|
|
4
|
-
import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
2
|
+
import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
5
3
|
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
|
|
6
4
|
import { generateId } from '../../lib/utils.js';
|
|
7
|
-
import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
|
|
5
|
+
import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop } from './utils.js';
|
|
8
6
|
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
7
|
+
import { extractNamedBindings } from './named-binding-extraction.js';
|
|
8
|
+
import { getTreeSitterBufferSize } from './constants.js';
|
|
9
|
+
import { loadTsconfigPaths, loadGoModulePath, loadComposerConfig, loadCSharpProjectConfig, loadSwiftPackageConfig, } from './language-config.js';
|
|
10
|
+
import { buildSuffixIndex, resolveImportPath, appendKotlinWildcard, KOTLIN_EXTENSIONS, resolveJvmWildcard, resolveJvmMemberImport, resolveGoPackageDir, resolveGoPackage, resolveCSharpImport, resolveCSharpNamespaceDir, resolvePhpImport, resolveRustImport, resolveRubyImport, resolvePythonImport, } from './resolvers/index.js';
|
|
11
|
+
import { callRouters } from './call-routing.js';
|
|
9
12
|
const isDev = process.env.NODE_ENV === 'development';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Check if a file path is directly inside a package directory identified by its suffix.
|
|
15
|
+
* Used by the symbol resolver for Go and C# directory-level import matching.
|
|
16
|
+
*/
|
|
17
|
+
export function isFileInPackageDir(filePath, dirSuffix) {
|
|
18
|
+
// Prepend '/' so paths like "internal/auth/service.go" match suffix "/internal/auth/"
|
|
19
|
+
const normalized = '/' + filePath.replace(/\\/g, '/');
|
|
20
|
+
if (!normalized.includes(dirSuffix))
|
|
21
|
+
return false;
|
|
22
|
+
const afterDir = normalized.substring(normalized.indexOf(dirSuffix) + dirSuffix.length);
|
|
23
|
+
return !afterDir.includes('/');
|
|
24
|
+
}
|
|
14
25
|
export function buildImportResolutionContext(allPaths) {
|
|
15
26
|
const allFileList = allPaths;
|
|
16
27
|
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
|
@@ -19,499 +30,159 @@ export function buildImportResolutionContext(allPaths) {
|
|
|
19
30
|
return { allFilePaths, allFileList, normalizedFileList, suffixIndex, resolveCache: new Map() };
|
|
20
31
|
}
|
|
21
32
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
33
|
+
* Shared language dispatch for import resolution.
|
|
34
|
+
* Used by both processImports and processImportsFromExtracted.
|
|
24
35
|
*/
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 };
|
|
36
|
+
function resolveLanguageImport(filePath, rawImportPath, language, configs, ctx) {
|
|
37
|
+
const { allFilePaths, allFileList, normalizedFileList, index, resolveCache } = ctx;
|
|
38
|
+
const { tsconfigPaths, goModule, composerConfig, swiftPackageConfig, csharpConfigs } = configs;
|
|
39
|
+
// JVM languages (Java + Kotlin): handle wildcards and member imports
|
|
40
|
+
if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
|
|
41
|
+
const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
|
|
42
|
+
if (rawImportPath.endsWith('.*')) {
|
|
43
|
+
const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
44
|
+
if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
|
|
45
|
+
const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
46
|
+
if (javaMatches.length > 0)
|
|
47
|
+
return { kind: 'files', files: javaMatches };
|
|
53
48
|
}
|
|
49
|
+
if (matchedFiles.length > 0)
|
|
50
|
+
return { kind: 'files', files: matchedFiles };
|
|
51
|
+
// Fall through to standard resolution
|
|
54
52
|
}
|
|
55
|
-
|
|
56
|
-
|
|
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]}`);
|
|
53
|
+
else {
|
|
54
|
+
let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
55
|
+
if (!memberResolved && language === SupportedLanguages.Kotlin) {
|
|
56
|
+
memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
72
57
|
}
|
|
73
|
-
|
|
58
|
+
if (memberResolved)
|
|
59
|
+
return { kind: 'files', files: [memberResolved] };
|
|
60
|
+
// Fall through to standard resolution
|
|
74
61
|
}
|
|
75
62
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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);
|
|
63
|
+
// Go: handle package-level imports
|
|
64
|
+
if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
|
65
|
+
const pkgSuffix = resolveGoPackageDir(rawImportPath, goModule);
|
|
66
|
+
if (pkgSuffix) {
|
|
67
|
+
const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
|
|
68
|
+
if (pkgFiles.length > 0) {
|
|
69
|
+
return { kind: 'package', files: pkgFiles, dirSuffix: pkgSuffix };
|
|
178
70
|
}
|
|
179
71
|
}
|
|
72
|
+
// Fall through if no files found (package might be external)
|
|
180
73
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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;
|
|
74
|
+
// C#: handle namespace-based imports (using directives)
|
|
75
|
+
if (language === SupportedLanguages.CSharp && csharpConfigs.length > 0) {
|
|
76
|
+
const resolvedFiles = resolveCSharpImport(rawImportPath, csharpConfigs, normalizedFileList, allFileList, index);
|
|
77
|
+
if (resolvedFiles.length > 1) {
|
|
78
|
+
const dirSuffix = resolveCSharpNamespaceDir(rawImportPath, csharpConfigs);
|
|
79
|
+
if (dirSuffix) {
|
|
80
|
+
return { kind: 'package', files: resolvedFiles, dirSuffix };
|
|
201
81
|
}
|
|
202
82
|
}
|
|
83
|
+
if (resolvedFiles.length > 0)
|
|
84
|
+
return { kind: 'files', files: resolvedFiles };
|
|
203
85
|
return null;
|
|
204
86
|
}
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
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
|
-
}
|
|
87
|
+
// PHP: handle namespace-based imports (use statements)
|
|
88
|
+
if (language === SupportedLanguages.PHP) {
|
|
89
|
+
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
|
90
|
+
return resolved ? { kind: 'files', files: [resolved] } : null;
|
|
216
91
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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);
|
|
92
|
+
// Swift: handle module imports
|
|
93
|
+
if (language === SupportedLanguages.Swift && swiftPackageConfig) {
|
|
94
|
+
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
|
95
|
+
if (targetDir) {
|
|
96
|
+
const dirPrefix = targetDir + '/';
|
|
97
|
+
const files = [];
|
|
98
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
99
|
+
if (normalizedFileList[i].startsWith(dirPrefix) && normalizedFileList[i].endsWith('.swift')) {
|
|
100
|
+
files.push(allFileList[i]);
|
|
101
|
+
}
|
|
267
102
|
}
|
|
103
|
+
if (files.length > 0)
|
|
104
|
+
return { kind: 'files', files };
|
|
268
105
|
}
|
|
106
|
+
return null; // External framework (Foundation, UIKit, etc.)
|
|
269
107
|
}
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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);
|
|
108
|
+
// Python: relative imports (PEP 328) + proximity-based bare imports
|
|
109
|
+
// Falls through to standard suffix resolution when proximity finds no match.
|
|
110
|
+
if (language === SupportedLanguages.Python) {
|
|
111
|
+
const resolved = resolvePythonImport(filePath, rawImportPath, allFilePaths);
|
|
112
|
+
if (resolved)
|
|
113
|
+
return { kind: 'files', files: [resolved] };
|
|
114
|
+
if (rawImportPath.startsWith('.'))
|
|
115
|
+
return null; // relative but unresolved — don't suffix-match
|
|
343
116
|
}
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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';
|
|
117
|
+
// Ruby: require / require_relative
|
|
118
|
+
if (language === SupportedLanguages.Ruby) {
|
|
119
|
+
const resolved = resolveRubyImport(rawImportPath, normalizedFileList, allFileList, index);
|
|
120
|
+
return resolved ? { kind: 'files', files: [resolved] } : null;
|
|
376
121
|
}
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
}
|
|
122
|
+
// Rust: expand top-level grouped imports: use {crate::a, crate::b}
|
|
123
|
+
if (language === SupportedLanguages.Rust && rawImportPath.startsWith('{') && rawImportPath.endsWith('}')) {
|
|
124
|
+
const inner = rawImportPath.slice(1, -1);
|
|
125
|
+
const parts = inner.split(',').map(p => p.trim()).filter(Boolean);
|
|
126
|
+
const resolved = [];
|
|
127
|
+
for (const part of parts) {
|
|
128
|
+
const r = resolveRustImport(filePath, part, allFilePaths);
|
|
129
|
+
if (r)
|
|
130
|
+
resolved.push(r);
|
|
413
131
|
}
|
|
132
|
+
return resolved.length > 0 ? { kind: 'files', files: resolved } : null;
|
|
414
133
|
}
|
|
415
|
-
|
|
134
|
+
// Standard single-file resolution
|
|
135
|
+
const resolvedPath = resolveImportPath(filePath, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
|
|
136
|
+
return resolvedPath ? { kind: 'files', files: [resolvedPath] } : null;
|
|
416
137
|
}
|
|
417
138
|
/**
|
|
418
|
-
*
|
|
419
|
-
*
|
|
139
|
+
* Apply an ImportResult: emit graph edges and update ImportMap/PackageMap.
|
|
140
|
+
* If namedBindings are provided and the import resolves to a single file,
|
|
141
|
+
* also populate the NamedImportMap for precise Tier 2a resolution.
|
|
420
142
|
*/
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
}
|
|
143
|
+
function applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, namedBindings, namedImportMap) {
|
|
144
|
+
if (!result)
|
|
145
|
+
return;
|
|
146
|
+
if (result.kind === 'package' && packageMap) {
|
|
147
|
+
// Store directory suffix in PackageMap (skip ImportMap expansion)
|
|
148
|
+
for (const resolvedFile of result.files) {
|
|
149
|
+
addImportGraphEdge(filePath, resolvedFile);
|
|
442
150
|
}
|
|
151
|
+
if (!packageMap.has(filePath))
|
|
152
|
+
packageMap.set(filePath, new Set());
|
|
153
|
+
packageMap.get(filePath).add(result.dirSuffix);
|
|
443
154
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
}
|
|
155
|
+
else {
|
|
156
|
+
// 'files' kind, or 'package' without PackageMap — use ImportMap directly
|
|
157
|
+
const files = result.files;
|
|
158
|
+
for (const resolvedFile of files) {
|
|
159
|
+
addImportEdge(filePath, resolvedFile);
|
|
470
160
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
}
|
|
161
|
+
// Record named bindings for precise Tier 2a resolution
|
|
162
|
+
if (namedBindings && namedImportMap && files.length === 1) {
|
|
163
|
+
const resolvedFile = files[0];
|
|
164
|
+
if (!namedImportMap.has(filePath))
|
|
165
|
+
namedImportMap.set(filePath, new Map());
|
|
166
|
+
const fileBindings = namedImportMap.get(filePath);
|
|
167
|
+
for (const binding of namedBindings) {
|
|
168
|
+
fileBindings.set(binding.local, { sourcePath: resolvedFile, exportedName: binding.exported });
|
|
500
169
|
}
|
|
501
170
|
}
|
|
502
171
|
}
|
|
503
|
-
// Fallback: suffix matching (works without composer.json)
|
|
504
|
-
const pathParts = normalized.split('/').filter(Boolean);
|
|
505
|
-
return suffixResolve(pathParts, normalizedFileList, allFileList, index);
|
|
506
172
|
}
|
|
507
173
|
// ============================================================================
|
|
508
174
|
// MAIN IMPORT PROCESSOR
|
|
509
175
|
// ============================================================================
|
|
510
|
-
export const processImports = async (graph, files, astCache,
|
|
176
|
+
export const processImports = async (graph, files, astCache, ctx, onProgress, repoRoot, allPaths) => {
|
|
177
|
+
const importMap = ctx.importMap;
|
|
178
|
+
const packageMap = ctx.packageMap;
|
|
179
|
+
const namedImportMap = ctx.namedImportMap;
|
|
511
180
|
// Use allPaths (full repo) when available for cross-chunk resolution, else fall back to chunk files
|
|
512
181
|
const allFileList = allPaths ?? files.map(f => f.path);
|
|
513
182
|
const allFilePaths = new Set(allFileList);
|
|
514
183
|
const parser = await loadParser();
|
|
184
|
+
const logSkipped = isVerboseIngestionEnabled();
|
|
185
|
+
const skippedByLang = logSkipped ? new Map() : null;
|
|
515
186
|
const resolveCache = new Map();
|
|
516
187
|
// Pre-compute normalized file list once (forward slashes)
|
|
517
188
|
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
|
@@ -522,11 +193,16 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
522
193
|
let totalImportsResolved = 0;
|
|
523
194
|
// Load language-specific configs once before the file loop
|
|
524
195
|
const effectiveRoot = repoRoot || '';
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
196
|
+
const configs = {
|
|
197
|
+
tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
|
|
198
|
+
goModule: await loadGoModulePath(effectiveRoot),
|
|
199
|
+
composerConfig: await loadComposerConfig(effectiveRoot),
|
|
200
|
+
swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
|
|
201
|
+
csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
|
|
202
|
+
};
|
|
203
|
+
const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
|
|
204
|
+
// Helper: add an IMPORTS edge to the graph only (no ImportMap update)
|
|
205
|
+
const addImportGraphEdge = (filePath, resolvedPath) => {
|
|
530
206
|
const sourceId = generateId('File', filePath);
|
|
531
207
|
const targetId = generateId('File', resolvedPath);
|
|
532
208
|
const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
|
|
@@ -539,6 +215,10 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
539
215
|
confidence: 1.0,
|
|
540
216
|
reason: '',
|
|
541
217
|
});
|
|
218
|
+
};
|
|
219
|
+
// Helper: add an IMPORTS edge + update import map
|
|
220
|
+
const addImportEdge = (filePath, resolvedPath) => {
|
|
221
|
+
addImportGraphEdge(filePath, resolvedPath);
|
|
542
222
|
if (!importMap.has(filePath)) {
|
|
543
223
|
importMap.set(filePath, new Set());
|
|
544
224
|
}
|
|
@@ -553,6 +233,12 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
553
233
|
const language = getLanguageFromFilename(file.path);
|
|
554
234
|
if (!language)
|
|
555
235
|
continue;
|
|
236
|
+
if (!isLanguageAvailable(language)) {
|
|
237
|
+
if (skippedByLang) {
|
|
238
|
+
skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
556
242
|
const queryStr = LANGUAGE_QUERIES[language];
|
|
557
243
|
if (!queryStr)
|
|
558
244
|
continue;
|
|
@@ -563,7 +249,7 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
563
249
|
let wasReparsed = false;
|
|
564
250
|
if (!tree) {
|
|
565
251
|
try {
|
|
566
|
-
tree = parser.parse(file.content, undefined, { bufferSize:
|
|
252
|
+
tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
|
|
567
253
|
}
|
|
568
254
|
catch (parseError) {
|
|
569
255
|
continue;
|
|
@@ -606,53 +292,35 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
606
292
|
return;
|
|
607
293
|
}
|
|
608
294
|
// Clean path (remove quotes and angle brackets for C/C++ includes)
|
|
609
|
-
const rawImportPath =
|
|
295
|
+
const rawImportPath = language === SupportedLanguages.Kotlin
|
|
296
|
+
? appendKotlinWildcard(sourceNode.text.replace(/['"<>]/g, ''), captureMap['import'])
|
|
297
|
+
: sourceNode.text.replace(/['"<>]/g, '');
|
|
610
298
|
totalImportsFound++;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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);
|
|
299
|
+
const result = resolveLanguageImport(file.path, rawImportPath, language, configs, resolveCtx);
|
|
300
|
+
const bindings = namedImportMap ? extractNamedBindings(captureMap['import'], language) : undefined;
|
|
301
|
+
applyImportResult(result, file.path, importMap, packageMap, addImportEdge, addImportGraphEdge, bindings, namedImportMap);
|
|
302
|
+
}
|
|
303
|
+
// ---- Language-specific call-as-import routing (Ruby require, etc.) ----
|
|
304
|
+
if (captureMap['call']) {
|
|
305
|
+
const callNameNode = captureMap['call.name'];
|
|
306
|
+
if (callNameNode) {
|
|
307
|
+
const callRouter = callRouters[language];
|
|
308
|
+
const routed = callRouter(callNameNode.text, captureMap['call']);
|
|
309
|
+
if (routed && routed.kind === 'import') {
|
|
310
|
+
totalImportsFound++;
|
|
311
|
+
const result = resolveLanguageImport(file.path, routed.importPath, language, configs, resolveCtx);
|
|
312
|
+
applyImportResult(result, file.path, importMap, packageMap, addImportEdge, addImportGraphEdge);
|
|
644
313
|
}
|
|
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
314
|
}
|
|
652
315
|
}
|
|
653
316
|
});
|
|
654
317
|
// Tree is now owned by the LRU cache — no manual delete needed
|
|
655
318
|
}
|
|
319
|
+
if (skippedByLang && skippedByLang.size > 0) {
|
|
320
|
+
for (const [lang, count] of skippedByLang.entries()) {
|
|
321
|
+
console.warn(`[ingestion] Skipped ${count} ${lang} file(s) in import processing — ${lang} parser not available.`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
656
324
|
if (isDev) {
|
|
657
325
|
console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
|
|
658
326
|
}
|
|
@@ -660,16 +328,25 @@ export const processImports = async (graph, files, astCache, importMap, onProgre
|
|
|
660
328
|
// ============================================================================
|
|
661
329
|
// FAST PATH: Resolve pre-extracted imports (no parsing needed)
|
|
662
330
|
// ============================================================================
|
|
663
|
-
export const processImportsFromExtracted = async (graph, files, extractedImports,
|
|
664
|
-
const
|
|
665
|
-
const
|
|
331
|
+
export const processImportsFromExtracted = async (graph, files, extractedImports, ctx, onProgress, repoRoot, prebuiltCtx) => {
|
|
332
|
+
const importMap = ctx.importMap;
|
|
333
|
+
const packageMap = ctx.packageMap;
|
|
334
|
+
const namedImportMap = ctx.namedImportMap;
|
|
335
|
+
const importCtx = prebuiltCtx ?? buildImportResolutionContext(files.map(f => f.path));
|
|
336
|
+
const { allFilePaths, allFileList, normalizedFileList, suffixIndex: index, resolveCache } = importCtx;
|
|
666
337
|
let totalImportsFound = 0;
|
|
667
338
|
let totalImportsResolved = 0;
|
|
668
339
|
const effectiveRoot = repoRoot || '';
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
340
|
+
const configs = {
|
|
341
|
+
tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
|
|
342
|
+
goModule: await loadGoModulePath(effectiveRoot),
|
|
343
|
+
composerConfig: await loadComposerConfig(effectiveRoot),
|
|
344
|
+
swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
|
|
345
|
+
csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
|
|
346
|
+
};
|
|
347
|
+
const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
|
|
348
|
+
// Helper: add an IMPORTS edge to the graph only (no ImportMap update)
|
|
349
|
+
const addImportGraphEdge = (filePath, resolvedPath) => {
|
|
673
350
|
const sourceId = generateId('File', filePath);
|
|
674
351
|
const targetId = generateId('File', resolvedPath);
|
|
675
352
|
const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
|
|
@@ -682,6 +359,9 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
|
|
|
682
359
|
confidence: 1.0,
|
|
683
360
|
reason: '',
|
|
684
361
|
});
|
|
362
|
+
};
|
|
363
|
+
const addImportEdge = (filePath, resolvedPath) => {
|
|
364
|
+
addImportGraphEdge(filePath, resolvedPath);
|
|
685
365
|
if (!importMap.has(filePath)) {
|
|
686
366
|
importMap.set(filePath, new Set());
|
|
687
367
|
}
|
|
@@ -699,76 +379,16 @@ export const processImportsFromExtracted = async (graph, files, extractedImports
|
|
|
699
379
|
}
|
|
700
380
|
const totalFiles = importsByFile.size;
|
|
701
381
|
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
382
|
for (const [filePath, fileImports] of importsByFile) {
|
|
717
383
|
filesProcessed++;
|
|
718
384
|
if (filesProcessed % 100 === 0) {
|
|
719
385
|
onProgress?.(filesProcessed, totalFiles);
|
|
720
386
|
await yieldToEventLoop();
|
|
721
387
|
}
|
|
722
|
-
for (const
|
|
388
|
+
for (const imp of fileImports) {
|
|
723
389
|
totalImportsFound++;
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
}
|
|
390
|
+
const result = resolveLanguageImport(filePath, imp.rawImportPath, imp.language, configs, resolveCtx);
|
|
391
|
+
applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, imp.namedBindings, namedImportMap);
|
|
772
392
|
}
|
|
773
393
|
}
|
|
774
394
|
onProgress?.(totalFiles, totalFiles);
|