@duytransipher/gitnexus 1.4.6-sipher.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/LICENSE +73 -0
- package/README.md +261 -0
- package/dist/cli/ai-context.d.ts +23 -0
- package/dist/cli/ai-context.js +265 -0
- package/dist/cli/analyze.d.ts +12 -0
- package/dist/cli/analyze.js +345 -0
- package/dist/cli/augment.d.ts +13 -0
- package/dist/cli/augment.js +33 -0
- package/dist/cli/clean.d.ts +10 -0
- package/dist/cli/clean.js +60 -0
- package/dist/cli/eval-server.d.ts +37 -0
- package/dist/cli/eval-server.js +389 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +137 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +30 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +36 -0
- package/dist/cli/serve.d.ts +4 -0
- package/dist/cli/serve.js +6 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +367 -0
- package/dist/cli/sipher-patched.d.ts +2 -0
- package/dist/cli/sipher-patched.js +77 -0
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +36 -0
- package/dist/cli/tool.d.ts +60 -0
- package/dist/cli/tool.js +180 -0
- package/dist/cli/wiki.d.ts +15 -0
- package/dist/cli/wiki.js +365 -0
- package/dist/config/ignore-service.d.ts +26 -0
- package/dist/config/ignore-service.js +284 -0
- package/dist/config/supported-languages.d.ts +15 -0
- package/dist/config/supported-languages.js +16 -0
- package/dist/core/augmentation/engine.d.ts +26 -0
- package/dist/core/augmentation/engine.js +240 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +251 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
- package/dist/core/embeddings/embedding-pipeline.js +356 -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 +66 -0
- package/dist/core/graph/types.d.ts +66 -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 +35 -0
- package/dist/core/ingestion/call-processor.d.ts +23 -0
- package/dist/core/ingestion/call-processor.js +793 -0
- package/dist/core/ingestion/call-routing.d.ts +68 -0
- package/dist/core/ingestion/call-routing.js +129 -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 +312 -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 +40 -0
- package/dist/core/ingestion/entry-point-scoring.js +353 -0
- 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.d.ts +28 -0
- package/dist/core/ingestion/filesystem-walker.js +81 -0
- package/dist/core/ingestion/framework-detection.d.ts +54 -0
- package/dist/core/ingestion/framework-detection.js +411 -0
- package/dist/core/ingestion/heritage-processor.d.ts +28 -0
- package/dist/core/ingestion/heritage-processor.js +251 -0
- package/dist/core/ingestion/import-processor.d.ts +34 -0
- package/dist/core/ingestion/import-processor.js +398 -0
- 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 +19 -0
- package/dist/core/ingestion/parsing-processor.js +315 -0
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +401 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +315 -0
- 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/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +63 -0
- package/dist/core/ingestion/symbol-table.js +85 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
- package/dist/core/ingestion/tree-sitter-queries.js +888 -0
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +613 -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 +455 -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 +456 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
- package/dist/core/ingestion/type-extractors/shared.js +810 -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/types.js +1 -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 +138 -0
- package/dist/core/ingestion/utils.js +1290 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
- package/dist/core/ingestion/workers/parse-worker.js +1126 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
- package/dist/core/ingestion/workers/worker-pool.js +128 -0
- package/dist/core/lbug/csv-generator.d.ts +33 -0
- package/dist/core/lbug/csv-generator.js +366 -0
- package/dist/core/lbug/lbug-adapter.d.ts +103 -0
- package/dist/core/lbug/lbug-adapter.js +769 -0
- package/dist/core/lbug/schema.d.ts +53 -0
- package/dist/core/lbug/schema.js +430 -0
- package/dist/core/search/bm25-index.d.ts +23 -0
- package/dist/core/search/bm25-index.js +96 -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 +5 -0
- package/dist/core/tree-sitter/parser-loader.js +63 -0
- package/dist/core/wiki/generator.d.ts +120 -0
- package/dist/core/wiki/generator.js +939 -0
- package/dist/core/wiki/graph-queries.d.ts +80 -0
- package/dist/core/wiki/graph-queries.js +238 -0
- package/dist/core/wiki/html-viewer.d.ts +10 -0
- package/dist/core/wiki/html-viewer.js +297 -0
- package/dist/core/wiki/llm-client.d.ts +43 -0
- package/dist/core/wiki/llm-client.js +186 -0
- package/dist/core/wiki/prompts.d.ts +53 -0
- package/dist/core/wiki/prompts.js +174 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +108 -0
- package/dist/mcp/core/lbug-adapter.d.ts +57 -0
- package/dist/mcp/core/lbug-adapter.js +455 -0
- package/dist/mcp/local/local-backend.d.ts +181 -0
- package/dist/mcp/local/local-backend.js +1722 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +411 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +296 -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 +292 -0
- package/dist/server/api.d.ts +10 -0
- package/dist/server/api.js +344 -0
- package/dist/server/mcp-http.d.ts +13 -0
- package/dist/server/mcp-http.js +100 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +138 -0
- package/dist/storage/repo-manager.js +299 -0
- package/dist/types/pipeline.d.ts +32 -0
- package/dist/types/pipeline.js +18 -0
- package/dist/unreal/bridge.d.ts +4 -0
- package/dist/unreal/bridge.js +113 -0
- package/dist/unreal/config.d.ts +6 -0
- package/dist/unreal/config.js +55 -0
- package/dist/unreal/types.d.ts +105 -0
- package/dist/unreal/types.js +1 -0
- package/hooks/claude/gitnexus-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/package.json +100 -0
- package/scripts/ensure-cli-executable.cjs +21 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/scripts/setup-unreal-gitnexus.ps1 +191 -0
- package/skills/gitnexus-cli.md +82 -0
- package/skills/gitnexus-debugging.md +89 -0
- package/skills/gitnexus-exploring.md +78 -0
- package/skills/gitnexus-guide.md +64 -0
- package/skills/gitnexus-impact-analysis.md +97 -0
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +121 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import Parser from 'tree-sitter';
|
|
2
|
+
import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
3
|
+
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
|
|
4
|
+
import { generateId } from '../../lib/utils.js';
|
|
5
|
+
import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop } from './utils.js';
|
|
6
|
+
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
7
|
+
import { extractNamedBindings } from './named-binding-extraction.js';
|
|
8
|
+
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';
|
|
12
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
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
|
+
}
|
|
25
|
+
export function buildImportResolutionContext(allPaths) {
|
|
26
|
+
const allFileList = allPaths;
|
|
27
|
+
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
|
28
|
+
const allFilePaths = new Set(allFileList);
|
|
29
|
+
const suffixIndex = buildSuffixIndex(normalizedFileList, allFileList);
|
|
30
|
+
return { allFilePaths, allFileList, normalizedFileList, suffixIndex, resolveCache: new Map() };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Shared language dispatch for import resolution.
|
|
34
|
+
* Used by both processImports and processImportsFromExtracted.
|
|
35
|
+
*/
|
|
36
|
+
function resolveLanguageImport(filePath, rawImportPath, language, configs, ctx) {
|
|
37
|
+
const { allFilePaths, allFileList, normalizedFileList, index, resolveCache } = ctx;
|
|
38
|
+
const { tsconfigPaths, goModule, composerConfig, swiftPackageConfig, csharpConfigs } = configs;
|
|
39
|
+
// JVM languages (Java + Kotlin): handle wildcards and member imports
|
|
40
|
+
if (language === SupportedLanguages.Java || language === SupportedLanguages.Kotlin) {
|
|
41
|
+
const exts = language === SupportedLanguages.Java ? ['.java'] : KOTLIN_EXTENSIONS;
|
|
42
|
+
if (rawImportPath.endsWith('.*')) {
|
|
43
|
+
const matchedFiles = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
44
|
+
if (matchedFiles.length === 0 && language === SupportedLanguages.Kotlin) {
|
|
45
|
+
const javaMatches = resolveJvmWildcard(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
46
|
+
if (javaMatches.length > 0)
|
|
47
|
+
return { kind: 'files', files: javaMatches };
|
|
48
|
+
}
|
|
49
|
+
if (matchedFiles.length > 0)
|
|
50
|
+
return { kind: 'files', files: matchedFiles };
|
|
51
|
+
// Fall through to standard resolution
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
let memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, exts, index);
|
|
55
|
+
if (!memberResolved && language === SupportedLanguages.Kotlin) {
|
|
56
|
+
memberResolved = resolveJvmMemberImport(rawImportPath, normalizedFileList, allFileList, ['.java'], index);
|
|
57
|
+
}
|
|
58
|
+
if (memberResolved)
|
|
59
|
+
return { kind: 'files', files: [memberResolved] };
|
|
60
|
+
// Fall through to standard resolution
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Go: handle package-level imports
|
|
64
|
+
if (language === SupportedLanguages.Go && goModule && rawImportPath.startsWith(goModule.modulePath)) {
|
|
65
|
+
const pkgSuffix = resolveGoPackageDir(rawImportPath, goModule);
|
|
66
|
+
if (pkgSuffix) {
|
|
67
|
+
const pkgFiles = resolveGoPackage(rawImportPath, goModule, normalizedFileList, allFileList);
|
|
68
|
+
if (pkgFiles.length > 0) {
|
|
69
|
+
return { kind: 'package', files: pkgFiles, dirSuffix: pkgSuffix };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Fall through if no files found (package might be external)
|
|
73
|
+
}
|
|
74
|
+
// C#: handle namespace-based imports (using directives)
|
|
75
|
+
if (language === SupportedLanguages.CSharp && csharpConfigs.length > 0) {
|
|
76
|
+
const resolvedFiles = resolveCSharpImport(rawImportPath, csharpConfigs, normalizedFileList, allFileList, index);
|
|
77
|
+
if (resolvedFiles.length > 1) {
|
|
78
|
+
const dirSuffix = resolveCSharpNamespaceDir(rawImportPath, csharpConfigs);
|
|
79
|
+
if (dirSuffix) {
|
|
80
|
+
return { kind: 'package', files: resolvedFiles, dirSuffix };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (resolvedFiles.length > 0)
|
|
84
|
+
return { kind: 'files', files: resolvedFiles };
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
// PHP: handle namespace-based imports (use statements)
|
|
88
|
+
if (language === SupportedLanguages.PHP) {
|
|
89
|
+
const resolved = resolvePhpImport(rawImportPath, composerConfig, allFilePaths, normalizedFileList, allFileList, index);
|
|
90
|
+
return resolved ? { kind: 'files', files: [resolved] } : null;
|
|
91
|
+
}
|
|
92
|
+
// Swift: handle module imports
|
|
93
|
+
if (language === SupportedLanguages.Swift && swiftPackageConfig) {
|
|
94
|
+
const targetDir = swiftPackageConfig.targets.get(rawImportPath);
|
|
95
|
+
if (targetDir) {
|
|
96
|
+
const dirPrefix = targetDir + '/';
|
|
97
|
+
const files = [];
|
|
98
|
+
for (let i = 0; i < normalizedFileList.length; i++) {
|
|
99
|
+
if (normalizedFileList[i].startsWith(dirPrefix) && normalizedFileList[i].endsWith('.swift')) {
|
|
100
|
+
files.push(allFileList[i]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (files.length > 0)
|
|
104
|
+
return { kind: 'files', files };
|
|
105
|
+
}
|
|
106
|
+
return null; // External framework (Foundation, UIKit, etc.)
|
|
107
|
+
}
|
|
108
|
+
// Python: relative imports (PEP 328) + proximity-based bare imports
|
|
109
|
+
// Falls through to standard suffix resolution when proximity finds no match.
|
|
110
|
+
if (language === SupportedLanguages.Python) {
|
|
111
|
+
const resolved = resolvePythonImport(filePath, rawImportPath, allFilePaths);
|
|
112
|
+
if (resolved)
|
|
113
|
+
return { kind: 'files', files: [resolved] };
|
|
114
|
+
if (rawImportPath.startsWith('.'))
|
|
115
|
+
return null; // relative but unresolved — don't suffix-match
|
|
116
|
+
}
|
|
117
|
+
// Ruby: require / require_relative
|
|
118
|
+
if (language === SupportedLanguages.Ruby) {
|
|
119
|
+
const resolved = resolveRubyImport(rawImportPath, normalizedFileList, allFileList, index);
|
|
120
|
+
return resolved ? { kind: 'files', files: [resolved] } : null;
|
|
121
|
+
}
|
|
122
|
+
// Rust: expand top-level grouped imports: use {crate::a, crate::b}
|
|
123
|
+
if (language === SupportedLanguages.Rust && rawImportPath.startsWith('{') && rawImportPath.endsWith('}')) {
|
|
124
|
+
const inner = rawImportPath.slice(1, -1);
|
|
125
|
+
const parts = inner.split(',').map(p => p.trim()).filter(Boolean);
|
|
126
|
+
const resolved = [];
|
|
127
|
+
for (const part of parts) {
|
|
128
|
+
const r = resolveRustImport(filePath, part, allFilePaths);
|
|
129
|
+
if (r)
|
|
130
|
+
resolved.push(r);
|
|
131
|
+
}
|
|
132
|
+
return resolved.length > 0 ? { kind: 'files', files: resolved } : null;
|
|
133
|
+
}
|
|
134
|
+
// Standard single-file resolution
|
|
135
|
+
const resolvedPath = resolveImportPath(filePath, rawImportPath, allFilePaths, allFileList, normalizedFileList, resolveCache, language, tsconfigPaths, index);
|
|
136
|
+
return resolvedPath ? { kind: 'files', files: [resolvedPath] } : null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
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.
|
|
142
|
+
*/
|
|
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);
|
|
150
|
+
}
|
|
151
|
+
if (!packageMap.has(filePath))
|
|
152
|
+
packageMap.set(filePath, new Set());
|
|
153
|
+
packageMap.get(filePath).add(result.dirSuffix);
|
|
154
|
+
}
|
|
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);
|
|
160
|
+
}
|
|
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 });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// MAIN IMPORT PROCESSOR
|
|
175
|
+
// ============================================================================
|
|
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;
|
|
180
|
+
// Use allPaths (full repo) when available for cross-chunk resolution, else fall back to chunk files
|
|
181
|
+
const allFileList = allPaths ?? files.map(f => f.path);
|
|
182
|
+
const allFilePaths = new Set(allFileList);
|
|
183
|
+
const parser = await loadParser();
|
|
184
|
+
const logSkipped = isVerboseIngestionEnabled();
|
|
185
|
+
const skippedByLang = logSkipped ? new Map() : null;
|
|
186
|
+
const resolveCache = new Map();
|
|
187
|
+
// Pre-compute normalized file list once (forward slashes)
|
|
188
|
+
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
|
|
189
|
+
// Build suffix index for O(1) lookups
|
|
190
|
+
const index = buildSuffixIndex(normalizedFileList, allFileList);
|
|
191
|
+
// Track import statistics
|
|
192
|
+
let totalImportsFound = 0;
|
|
193
|
+
let totalImportsResolved = 0;
|
|
194
|
+
// Load language-specific configs once before the file loop
|
|
195
|
+
const effectiveRoot = repoRoot || '';
|
|
196
|
+
const configs = {
|
|
197
|
+
tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
|
|
198
|
+
goModule: await loadGoModulePath(effectiveRoot),
|
|
199
|
+
composerConfig: await loadComposerConfig(effectiveRoot),
|
|
200
|
+
swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
|
|
201
|
+
csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
|
|
202
|
+
};
|
|
203
|
+
const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
|
|
204
|
+
// Helper: add an IMPORTS edge to the graph only (no ImportMap update)
|
|
205
|
+
const addImportGraphEdge = (filePath, resolvedPath) => {
|
|
206
|
+
const sourceId = generateId('File', filePath);
|
|
207
|
+
const targetId = generateId('File', resolvedPath);
|
|
208
|
+
const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
|
|
209
|
+
totalImportsResolved++;
|
|
210
|
+
graph.addRelationship({
|
|
211
|
+
id: relId,
|
|
212
|
+
sourceId,
|
|
213
|
+
targetId,
|
|
214
|
+
type: 'IMPORTS',
|
|
215
|
+
confidence: 1.0,
|
|
216
|
+
reason: '',
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
// Helper: add an IMPORTS edge + update import map
|
|
220
|
+
const addImportEdge = (filePath, resolvedPath) => {
|
|
221
|
+
addImportGraphEdge(filePath, resolvedPath);
|
|
222
|
+
if (!importMap.has(filePath)) {
|
|
223
|
+
importMap.set(filePath, new Set());
|
|
224
|
+
}
|
|
225
|
+
importMap.get(filePath).add(resolvedPath);
|
|
226
|
+
};
|
|
227
|
+
for (let i = 0; i < files.length; i++) {
|
|
228
|
+
const file = files[i];
|
|
229
|
+
onProgress?.(i + 1, files.length);
|
|
230
|
+
if (i % 20 === 0)
|
|
231
|
+
await yieldToEventLoop();
|
|
232
|
+
// 1. Check language support first
|
|
233
|
+
const language = getLanguageFromFilename(file.path);
|
|
234
|
+
if (!language)
|
|
235
|
+
continue;
|
|
236
|
+
if (!isLanguageAvailable(language)) {
|
|
237
|
+
if (skippedByLang) {
|
|
238
|
+
skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const queryStr = LANGUAGE_QUERIES[language];
|
|
243
|
+
if (!queryStr)
|
|
244
|
+
continue;
|
|
245
|
+
// 2. ALWAYS load the language before querying (parser is stateful)
|
|
246
|
+
await loadLanguage(language, file.path);
|
|
247
|
+
// 3. Get AST (Try Cache First)
|
|
248
|
+
let tree = astCache.get(file.path);
|
|
249
|
+
let wasReparsed = false;
|
|
250
|
+
if (!tree) {
|
|
251
|
+
try {
|
|
252
|
+
tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
|
|
253
|
+
}
|
|
254
|
+
catch (parseError) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
wasReparsed = true;
|
|
258
|
+
// Cache re-parsed tree so call/heritage phases get hits
|
|
259
|
+
astCache.set(file.path, tree);
|
|
260
|
+
}
|
|
261
|
+
let query;
|
|
262
|
+
let matches;
|
|
263
|
+
try {
|
|
264
|
+
const lang = parser.getLanguage();
|
|
265
|
+
query = new Parser.Query(lang, queryStr);
|
|
266
|
+
matches = query.matches(tree.rootNode);
|
|
267
|
+
}
|
|
268
|
+
catch (queryError) {
|
|
269
|
+
if (isDev) {
|
|
270
|
+
console.group(`🔴 Query Error: ${file.path}`);
|
|
271
|
+
console.log('Language:', language);
|
|
272
|
+
console.log('Query (first 200 chars):', queryStr.substring(0, 200) + '...');
|
|
273
|
+
console.log('Error:', queryError?.message || queryError);
|
|
274
|
+
console.log('File content (first 300 chars):', file.content.substring(0, 300));
|
|
275
|
+
console.log('AST root type:', tree.rootNode?.type);
|
|
276
|
+
console.log('AST has errors:', tree.rootNode?.hasError);
|
|
277
|
+
console.groupEnd();
|
|
278
|
+
}
|
|
279
|
+
if (wasReparsed)
|
|
280
|
+
tree.delete?.();
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
matches.forEach(match => {
|
|
284
|
+
const captureMap = {};
|
|
285
|
+
match.captures.forEach(c => captureMap[c.name] = c.node);
|
|
286
|
+
if (captureMap['import']) {
|
|
287
|
+
const sourceNode = captureMap['import.source'];
|
|
288
|
+
if (!sourceNode) {
|
|
289
|
+
if (isDev) {
|
|
290
|
+
console.log(`⚠️ Import captured but no source node in ${file.path}`);
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
// Clean path (remove quotes and angle brackets for C/C++ includes)
|
|
295
|
+
const rawImportPath = language === SupportedLanguages.Kotlin
|
|
296
|
+
? appendKotlinWildcard(sourceNode.text.replace(/['"<>]/g, ''), captureMap['import'])
|
|
297
|
+
: sourceNode.text.replace(/['"<>]/g, '');
|
|
298
|
+
totalImportsFound++;
|
|
299
|
+
const result = resolveLanguageImport(file.path, rawImportPath, language, configs, resolveCtx);
|
|
300
|
+
const bindings = namedImportMap ? extractNamedBindings(captureMap['import'], language) : undefined;
|
|
301
|
+
applyImportResult(result, file.path, importMap, packageMap, addImportEdge, addImportGraphEdge, bindings, namedImportMap);
|
|
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);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
// Tree is now owned by the LRU cache — no manual delete needed
|
|
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
|
+
}
|
|
324
|
+
if (isDev) {
|
|
325
|
+
console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
// ============================================================================
|
|
329
|
+
// FAST PATH: Resolve pre-extracted imports (no parsing needed)
|
|
330
|
+
// ============================================================================
|
|
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;
|
|
337
|
+
let totalImportsFound = 0;
|
|
338
|
+
let totalImportsResolved = 0;
|
|
339
|
+
const effectiveRoot = repoRoot || '';
|
|
340
|
+
const configs = {
|
|
341
|
+
tsconfigPaths: await loadTsconfigPaths(effectiveRoot),
|
|
342
|
+
goModule: await loadGoModulePath(effectiveRoot),
|
|
343
|
+
composerConfig: await loadComposerConfig(effectiveRoot),
|
|
344
|
+
swiftPackageConfig: await loadSwiftPackageConfig(effectiveRoot),
|
|
345
|
+
csharpConfigs: await loadCSharpProjectConfig(effectiveRoot),
|
|
346
|
+
};
|
|
347
|
+
const resolveCtx = { allFilePaths, allFileList, normalizedFileList, index, resolveCache };
|
|
348
|
+
// Helper: add an IMPORTS edge to the graph only (no ImportMap update)
|
|
349
|
+
const addImportGraphEdge = (filePath, resolvedPath) => {
|
|
350
|
+
const sourceId = generateId('File', filePath);
|
|
351
|
+
const targetId = generateId('File', resolvedPath);
|
|
352
|
+
const relId = generateId('IMPORTS', `${filePath}->${resolvedPath}`);
|
|
353
|
+
totalImportsResolved++;
|
|
354
|
+
graph.addRelationship({
|
|
355
|
+
id: relId,
|
|
356
|
+
sourceId,
|
|
357
|
+
targetId,
|
|
358
|
+
type: 'IMPORTS',
|
|
359
|
+
confidence: 1.0,
|
|
360
|
+
reason: '',
|
|
361
|
+
});
|
|
362
|
+
};
|
|
363
|
+
const addImportEdge = (filePath, resolvedPath) => {
|
|
364
|
+
addImportGraphEdge(filePath, resolvedPath);
|
|
365
|
+
if (!importMap.has(filePath)) {
|
|
366
|
+
importMap.set(filePath, new Set());
|
|
367
|
+
}
|
|
368
|
+
importMap.get(filePath).add(resolvedPath);
|
|
369
|
+
};
|
|
370
|
+
// Group by file for progress reporting (users see file count, not import count)
|
|
371
|
+
const importsByFile = new Map();
|
|
372
|
+
for (const imp of extractedImports) {
|
|
373
|
+
let list = importsByFile.get(imp.filePath);
|
|
374
|
+
if (!list) {
|
|
375
|
+
list = [];
|
|
376
|
+
importsByFile.set(imp.filePath, list);
|
|
377
|
+
}
|
|
378
|
+
list.push(imp);
|
|
379
|
+
}
|
|
380
|
+
const totalFiles = importsByFile.size;
|
|
381
|
+
let filesProcessed = 0;
|
|
382
|
+
for (const [filePath, fileImports] of importsByFile) {
|
|
383
|
+
filesProcessed++;
|
|
384
|
+
if (filesProcessed % 100 === 0) {
|
|
385
|
+
onProgress?.(filesProcessed, totalFiles);
|
|
386
|
+
await yieldToEventLoop();
|
|
387
|
+
}
|
|
388
|
+
for (const imp of fileImports) {
|
|
389
|
+
totalImportsFound++;
|
|
390
|
+
const result = resolveLanguageImport(filePath, imp.rawImportPath, imp.language, configs, resolveCtx);
|
|
391
|
+
applyImportResult(result, filePath, importMap, packageMap, addImportEdge, addImportGraphEdge, imp.namedBindings, namedImportMap);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
onProgress?.(totalFiles, totalFiles);
|
|
395
|
+
if (isDev) {
|
|
396
|
+
console.log(`📊 Import processing (fast path): ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** TypeScript path alias config parsed from tsconfig.json */
|
|
2
|
+
export interface TsconfigPaths {
|
|
3
|
+
/** Map of alias prefix -> target prefix (e.g., "@/" -> "src/") */
|
|
4
|
+
aliases: Map<string, string>;
|
|
5
|
+
/** Base URL for path resolution (relative to repo root) */
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
}
|
|
8
|
+
/** Go module config parsed from go.mod */
|
|
9
|
+
export interface GoModuleConfig {
|
|
10
|
+
/** Module path (e.g., "github.com/user/repo") */
|
|
11
|
+
modulePath: string;
|
|
12
|
+
}
|
|
13
|
+
/** PHP Composer PSR-4 autoload config */
|
|
14
|
+
export interface ComposerConfig {
|
|
15
|
+
/** Map of namespace prefix -> directory (e.g., "App\\" -> "app/") */
|
|
16
|
+
psr4: Map<string, string>;
|
|
17
|
+
}
|
|
18
|
+
/** C# project config parsed from .csproj files */
|
|
19
|
+
export interface CSharpProjectConfig {
|
|
20
|
+
/** Root namespace from <RootNamespace> or assembly name (default: project directory name) */
|
|
21
|
+
rootNamespace: string;
|
|
22
|
+
/** Directory containing the .csproj file */
|
|
23
|
+
projectDir: string;
|
|
24
|
+
}
|
|
25
|
+
/** Swift Package Manager module config */
|
|
26
|
+
export interface SwiftPackageConfig {
|
|
27
|
+
/** Map of target name -> source directory path (e.g., "SiuperModel" -> "Package/Sources/SiuperModel") */
|
|
28
|
+
targets: Map<string, string>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse tsconfig.json to extract path aliases.
|
|
32
|
+
* Tries tsconfig.json, tsconfig.app.json, tsconfig.base.json in order.
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadTsconfigPaths(repoRoot: string): Promise<TsconfigPaths | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Parse go.mod to extract module path.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadGoModulePath(repoRoot: string): Promise<GoModuleConfig | null>;
|
|
39
|
+
/** Parse composer.json to extract PSR-4 autoload mappings (including autoload-dev). */
|
|
40
|
+
export declare function loadComposerConfig(repoRoot: string): Promise<ComposerConfig | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Parse .csproj files to extract RootNamespace.
|
|
43
|
+
* Scans the repo root for .csproj files and returns configs for each.
|
|
44
|
+
*/
|
|
45
|
+
export declare function loadCSharpProjectConfig(repoRoot: string): Promise<CSharpProjectConfig[]>;
|
|
46
|
+
export declare function loadSwiftPackageConfig(repoRoot: string): Promise<SwiftPackageConfig | null>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// LANGUAGE-SPECIFIC CONFIG LOADERS
|
|
6
|
+
// ============================================================================
|
|
7
|
+
/**
|
|
8
|
+
* Parse tsconfig.json to extract path aliases.
|
|
9
|
+
* Tries tsconfig.json, tsconfig.app.json, tsconfig.base.json in order.
|
|
10
|
+
*/
|
|
11
|
+
export async function loadTsconfigPaths(repoRoot) {
|
|
12
|
+
const candidates = ['tsconfig.json', 'tsconfig.app.json', 'tsconfig.base.json'];
|
|
13
|
+
for (const filename of candidates) {
|
|
14
|
+
try {
|
|
15
|
+
const tsconfigPath = path.join(repoRoot, filename);
|
|
16
|
+
const raw = await fs.readFile(tsconfigPath, 'utf-8');
|
|
17
|
+
// Strip JSON comments (// and /* */ style) for robustness
|
|
18
|
+
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
19
|
+
const tsconfig = JSON.parse(stripped);
|
|
20
|
+
const compilerOptions = tsconfig.compilerOptions;
|
|
21
|
+
if (!compilerOptions?.paths)
|
|
22
|
+
continue;
|
|
23
|
+
const baseUrl = compilerOptions.baseUrl || '.';
|
|
24
|
+
const aliases = new Map();
|
|
25
|
+
for (const [pattern, targets] of Object.entries(compilerOptions.paths)) {
|
|
26
|
+
if (!Array.isArray(targets) || targets.length === 0)
|
|
27
|
+
continue;
|
|
28
|
+
const target = targets[0];
|
|
29
|
+
// Convert glob patterns: "@/*" -> "@/", "src/*" -> "src/"
|
|
30
|
+
const aliasPrefix = pattern.endsWith('/*') ? pattern.slice(0, -1) : pattern;
|
|
31
|
+
const targetPrefix = target.endsWith('/*') ? target.slice(0, -1) : target;
|
|
32
|
+
aliases.set(aliasPrefix, targetPrefix);
|
|
33
|
+
}
|
|
34
|
+
if (aliases.size > 0) {
|
|
35
|
+
if (isDev) {
|
|
36
|
+
console.log(`📦 Loaded ${aliases.size} path aliases from ${filename}`);
|
|
37
|
+
}
|
|
38
|
+
return { aliases, baseUrl };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// File doesn't exist or isn't valid JSON - try next
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse go.mod to extract module path.
|
|
49
|
+
*/
|
|
50
|
+
export async function loadGoModulePath(repoRoot) {
|
|
51
|
+
try {
|
|
52
|
+
const goModPath = path.join(repoRoot, 'go.mod');
|
|
53
|
+
const content = await fs.readFile(goModPath, 'utf-8');
|
|
54
|
+
const match = content.match(/^module\s+(\S+)/m);
|
|
55
|
+
if (match) {
|
|
56
|
+
if (isDev) {
|
|
57
|
+
console.log(`📦 Loaded Go module path: ${match[1]}`);
|
|
58
|
+
}
|
|
59
|
+
return { modulePath: match[1] };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// No go.mod
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/** Parse composer.json to extract PSR-4 autoload mappings (including autoload-dev). */
|
|
68
|
+
export async function loadComposerConfig(repoRoot) {
|
|
69
|
+
try {
|
|
70
|
+
const composerPath = path.join(repoRoot, 'composer.json');
|
|
71
|
+
const raw = await fs.readFile(composerPath, 'utf-8');
|
|
72
|
+
const composer = JSON.parse(raw);
|
|
73
|
+
const psr4Raw = composer.autoload?.['psr-4'] ?? {};
|
|
74
|
+
const psr4Dev = composer['autoload-dev']?.['psr-4'] ?? {};
|
|
75
|
+
const merged = { ...psr4Raw, ...psr4Dev };
|
|
76
|
+
const psr4 = new Map();
|
|
77
|
+
for (const [ns, dir] of Object.entries(merged)) {
|
|
78
|
+
const nsNorm = ns.replace(/\\+$/, '');
|
|
79
|
+
const dirNorm = dir.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
80
|
+
psr4.set(nsNorm, dirNorm);
|
|
81
|
+
}
|
|
82
|
+
if (isDev) {
|
|
83
|
+
console.log(`📦 Loaded ${psr4.size} PSR-4 mappings from composer.json`);
|
|
84
|
+
}
|
|
85
|
+
return { psr4 };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parse .csproj files to extract RootNamespace.
|
|
93
|
+
* Scans the repo root for .csproj files and returns configs for each.
|
|
94
|
+
*/
|
|
95
|
+
export async function loadCSharpProjectConfig(repoRoot) {
|
|
96
|
+
const configs = [];
|
|
97
|
+
// BFS scan for .csproj files up to 5 levels deep, cap at 100 dirs to avoid runaway scanning
|
|
98
|
+
const scanQueue = [{ dir: repoRoot, depth: 0 }];
|
|
99
|
+
const maxDepth = 5;
|
|
100
|
+
const maxDirs = 100;
|
|
101
|
+
let dirsScanned = 0;
|
|
102
|
+
while (scanQueue.length > 0 && dirsScanned < maxDirs) {
|
|
103
|
+
const { dir, depth } = scanQueue.shift();
|
|
104
|
+
dirsScanned++;
|
|
105
|
+
try {
|
|
106
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (entry.isDirectory() && depth < maxDepth) {
|
|
109
|
+
// Skip common non-project directories
|
|
110
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'bin' || entry.name === 'obj')
|
|
111
|
+
continue;
|
|
112
|
+
scanQueue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
|
|
113
|
+
}
|
|
114
|
+
if (entry.isFile() && entry.name.endsWith('.csproj')) {
|
|
115
|
+
try {
|
|
116
|
+
const csprojPath = path.join(dir, entry.name);
|
|
117
|
+
const content = await fs.readFile(csprojPath, 'utf-8');
|
|
118
|
+
const nsMatch = content.match(/<RootNamespace>\s*([^<]+)\s*<\/RootNamespace>/);
|
|
119
|
+
const rootNamespace = nsMatch
|
|
120
|
+
? nsMatch[1].trim()
|
|
121
|
+
: entry.name.replace(/\.csproj$/, '');
|
|
122
|
+
const projectDir = path.relative(repoRoot, dir).replace(/\\/g, '/');
|
|
123
|
+
configs.push({ rootNamespace, projectDir });
|
|
124
|
+
if (isDev) {
|
|
125
|
+
console.log(`📦 Loaded C# project: ${entry.name} (namespace: ${rootNamespace}, dir: ${projectDir})`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Can't read .csproj
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Can't read directory
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return configs;
|
|
139
|
+
}
|
|
140
|
+
export async function loadSwiftPackageConfig(repoRoot) {
|
|
141
|
+
// Swift imports are module-name based (e.g., `import SiuperModel`)
|
|
142
|
+
// SPM convention: Sources/<TargetName>/ or Package/Sources/<TargetName>/
|
|
143
|
+
// We scan for these directories to build a target map
|
|
144
|
+
const targets = new Map();
|
|
145
|
+
const sourceDirs = ['Sources', 'Package/Sources', 'src'];
|
|
146
|
+
for (const sourceDir of sourceDirs) {
|
|
147
|
+
try {
|
|
148
|
+
const fullPath = path.join(repoRoot, sourceDir);
|
|
149
|
+
const entries = await fs.readdir(fullPath, { withFileTypes: true });
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
if (entry.isDirectory()) {
|
|
152
|
+
targets.set(entry.name, sourceDir + '/' + entry.name);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Directory doesn't exist
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (targets.size > 0) {
|
|
161
|
+
if (isDev) {
|
|
162
|
+
console.log(`📦 Loaded ${targets.size} Swift package targets`);
|
|
163
|
+
}
|
|
164
|
+
return { targets };
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MRO (Method Resolution Order) Processor
|
|
3
|
+
*
|
|
4
|
+
* Walks the inheritance DAG (EXTENDS/IMPLEMENTS edges), collects methods from
|
|
5
|
+
* each ancestor via HAS_METHOD edges, detects method-name collisions across
|
|
6
|
+
* parents, and applies language-specific resolution rules to emit OVERRIDES edges.
|
|
7
|
+
*
|
|
8
|
+
* Language-specific rules:
|
|
9
|
+
* - C++: leftmost base class in declaration order wins
|
|
10
|
+
* - C#/Java: class method wins over interface default; multiple interface
|
|
11
|
+
* methods with same name are ambiguous (null resolution)
|
|
12
|
+
* - Python: C3 linearization determines MRO; first in linearized order wins
|
|
13
|
+
* - Rust: no auto-resolution — requires qualified syntax, resolvedTo = null
|
|
14
|
+
* - Default: single inheritance — first definition wins
|
|
15
|
+
*
|
|
16
|
+
* OVERRIDES edge direction: Class → Method (not Method → Method).
|
|
17
|
+
* The source is the child class that inherits conflicting methods,
|
|
18
|
+
* the target is the winning ancestor method node.
|
|
19
|
+
* Cypher: MATCH (c:Class)-[r:CodeRelation {type: 'OVERRIDES'}]->(m:Method)
|
|
20
|
+
*/
|
|
21
|
+
import { KnowledgeGraph } from '../graph/types.js';
|
|
22
|
+
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
23
|
+
export interface MROEntry {
|
|
24
|
+
classId: string;
|
|
25
|
+
className: string;
|
|
26
|
+
language: SupportedLanguages;
|
|
27
|
+
mro: string[];
|
|
28
|
+
ambiguities: MethodAmbiguity[];
|
|
29
|
+
}
|
|
30
|
+
export interface MethodAmbiguity {
|
|
31
|
+
methodName: string;
|
|
32
|
+
definedIn: Array<{
|
|
33
|
+
classId: string;
|
|
34
|
+
className: string;
|
|
35
|
+
methodId: string;
|
|
36
|
+
}>;
|
|
37
|
+
resolvedTo: string | null;
|
|
38
|
+
reason: string;
|
|
39
|
+
}
|
|
40
|
+
export interface MROResult {
|
|
41
|
+
entries: MROEntry[];
|
|
42
|
+
overrideEdges: number;
|
|
43
|
+
ambiguityCount: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function computeMRO(graph: KnowledgeGraph): MROResult;
|