gitnexus 1.4.6 → 1.4.8
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 +22 -1
- package/dist/cli/ai-context.d.ts +1 -1
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +54 -21
- package/dist/cli/index.js +2 -1
- package/dist/cli/setup.js +78 -1
- package/dist/config/supported-languages.d.ts +30 -0
- package/dist/config/supported-languages.js +30 -0
- package/dist/core/embeddings/embedder.d.ts +6 -1
- package/dist/core/embeddings/embedder.js +65 -5
- package/dist/core/embeddings/embedding-pipeline.js +11 -9
- package/dist/core/embeddings/http-client.d.ts +31 -0
- package/dist/core/embeddings/http-client.js +179 -0
- package/dist/core/embeddings/index.d.ts +1 -0
- package/dist/core/embeddings/index.js +1 -0
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +4 -3
- package/dist/core/ingestion/ast-helpers.d.ts +80 -0
- package/dist/core/ingestion/ast-helpers.js +738 -0
- package/dist/core/ingestion/call-analysis.d.ts +73 -0
- package/dist/core/ingestion/call-analysis.js +490 -0
- package/dist/core/ingestion/call-processor.d.ts +55 -2
- package/dist/core/ingestion/call-processor.js +673 -108
- package/dist/core/ingestion/call-routing.d.ts +23 -2
- package/dist/core/ingestion/call-routing.js +21 -0
- package/dist/core/ingestion/entry-point-scoring.js +36 -26
- package/dist/core/ingestion/framework-detection.d.ts +10 -2
- package/dist/core/ingestion/framework-detection.js +49 -12
- package/dist/core/ingestion/heritage-processor.js +47 -49
- package/dist/core/ingestion/import-processor.d.ts +1 -1
- package/dist/core/ingestion/import-processor.js +103 -194
- package/dist/core/ingestion/import-resolution.d.ts +101 -0
- package/dist/core/ingestion/import-resolution.js +251 -0
- package/dist/core/ingestion/language-config.d.ts +3 -0
- package/dist/core/ingestion/language-config.js +13 -0
- package/dist/core/ingestion/markdown-processor.d.ts +17 -0
- package/dist/core/ingestion/markdown-processor.js +124 -0
- package/dist/core/ingestion/mro-processor.js +8 -3
- package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
- package/dist/core/ingestion/named-binding-extraction.js +89 -79
- package/dist/core/ingestion/parsing-processor.d.ts +3 -2
- package/dist/core/ingestion/parsing-processor.js +27 -60
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/pipeline.js +425 -4
- package/dist/core/ingestion/resolution-context.d.ts +5 -0
- package/dist/core/ingestion/resolution-context.js +7 -4
- package/dist/core/ingestion/resolvers/index.d.ts +1 -1
- package/dist/core/ingestion/resolvers/index.js +1 -1
- package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
- package/dist/core/ingestion/resolvers/jvm.js +25 -9
- package/dist/core/ingestion/resolvers/php.d.ts +14 -0
- package/dist/core/ingestion/resolvers/php.js +43 -3
- package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
- package/dist/core/ingestion/resolvers/utils.js +16 -0
- package/dist/core/ingestion/symbol-table.d.ts +29 -3
- package/dist/core/ingestion/symbol-table.js +42 -9
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
- package/dist/core/ingestion/tree-sitter-queries.js +243 -2
- package/dist/core/ingestion/type-env.d.ts +28 -1
- package/dist/core/ingestion/type-env.js +451 -72
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +146 -2
- package/dist/core/ingestion/type-extractors/csharp.js +189 -16
- package/dist/core/ingestion/type-extractors/go.js +45 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
- package/dist/core/ingestion/type-extractors/index.js +1 -1
- package/dist/core/ingestion/type-extractors/jvm.js +244 -69
- package/dist/core/ingestion/type-extractors/php.js +31 -4
- package/dist/core/ingestion/type-extractors/python.js +89 -17
- package/dist/core/ingestion/type-extractors/ruby.js +17 -2
- package/dist/core/ingestion/type-extractors/rust.js +72 -4
- package/dist/core/ingestion/type-extractors/shared.d.ts +12 -2
- package/dist/core/ingestion/type-extractors/shared.js +115 -13
- package/dist/core/ingestion/type-extractors/swift.js +7 -6
- package/dist/core/ingestion/type-extractors/types.d.ts +54 -11
- package/dist/core/ingestion/type-extractors/typescript.js +171 -9
- package/dist/core/ingestion/utils.d.ts +2 -95
- package/dist/core/ingestion/utils.js +3 -892
- package/dist/core/ingestion/workers/parse-worker.d.ts +36 -11
- package/dist/core/ingestion/workers/parse-worker.js +116 -95
- package/dist/core/lbug/csv-generator.js +18 -1
- package/dist/core/lbug/lbug-adapter.d.ts +12 -0
- package/dist/core/lbug/lbug-adapter.js +71 -4
- package/dist/core/lbug/schema.d.ts +6 -4
- package/dist/core/lbug/schema.js +27 -3
- package/dist/mcp/core/embedder.js +11 -3
- package/dist/mcp/core/lbug-adapter.d.ts +22 -0
- package/dist/mcp/core/lbug-adapter.js +178 -23
- package/dist/mcp/local/local-backend.d.ts +22 -0
- package/dist/mcp/local/local-backend.js +136 -32
- package/dist/mcp/resources.js +13 -0
- package/dist/mcp/server.js +26 -4
- package/dist/mcp/tools.js +17 -7
- package/dist/server/api.d.ts +19 -1
- package/dist/server/api.js +66 -6
- package/dist/storage/git.d.ts +12 -0
- package/dist/storage/git.js +21 -0
- package/package.json +12 -4
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
|
2
|
+
import { type MixedChainStep } from '../utils.js';
|
|
2
3
|
import type { ConstructorBinding } from '../type-env.js';
|
|
4
|
+
import type { NamedBinding } from '../import-resolution.js';
|
|
5
|
+
import type { NodeLabel } from '../../graph/types.js';
|
|
3
6
|
interface ParsedNode {
|
|
4
7
|
id: string;
|
|
5
8
|
label: string;
|
|
@@ -14,6 +17,7 @@ interface ParsedNode {
|
|
|
14
17
|
astFrameworkReason?: string;
|
|
15
18
|
description?: string;
|
|
16
19
|
parameterCount?: number;
|
|
20
|
+
requiredParameterCount?: number;
|
|
17
21
|
returnType?: string;
|
|
18
22
|
};
|
|
19
23
|
}
|
|
@@ -21,7 +25,7 @@ interface ParsedRelationship {
|
|
|
21
25
|
id: string;
|
|
22
26
|
sourceId: string;
|
|
23
27
|
targetId: string;
|
|
24
|
-
type: 'DEFINES' | 'HAS_METHOD';
|
|
28
|
+
type: 'DEFINES' | 'HAS_METHOD' | 'HAS_PROPERTY';
|
|
25
29
|
confidence: number;
|
|
26
30
|
reason: string;
|
|
27
31
|
}
|
|
@@ -29,9 +33,12 @@ interface ParsedSymbol {
|
|
|
29
33
|
filePath: string;
|
|
30
34
|
name: string;
|
|
31
35
|
nodeId: string;
|
|
32
|
-
type:
|
|
36
|
+
type: NodeLabel;
|
|
33
37
|
parameterCount?: number;
|
|
38
|
+
requiredParameterCount?: number;
|
|
39
|
+
parameterTypes?: string[];
|
|
34
40
|
returnType?: string;
|
|
41
|
+
declaredType?: string;
|
|
35
42
|
ownerId?: string;
|
|
36
43
|
}
|
|
37
44
|
export interface ExtractedImport {
|
|
@@ -39,10 +46,7 @@ export interface ExtractedImport {
|
|
|
39
46
|
rawImportPath: string;
|
|
40
47
|
language: SupportedLanguages;
|
|
41
48
|
/** Named bindings from the import (e.g., import {User as U} → [{local:'U', exported:'User'}]) */
|
|
42
|
-
namedBindings?:
|
|
43
|
-
local: string;
|
|
44
|
-
exported: string;
|
|
45
|
-
}[];
|
|
49
|
+
namedBindings?: NamedBinding[];
|
|
46
50
|
}
|
|
47
51
|
export interface ExtractedCall {
|
|
48
52
|
filePath: string;
|
|
@@ -57,13 +61,25 @@ export interface ExtractedCall {
|
|
|
57
61
|
/** Resolved type name of the receiver (e.g., 'User' for user.save() when user: User) */
|
|
58
62
|
receiverTypeName?: string;
|
|
59
63
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* `
|
|
64
|
+
* Unified mixed chain when the receiver is a chain of field accesses and/or method calls.
|
|
65
|
+
* Steps are ordered base-first (innermost to outermost). Examples:
|
|
66
|
+
* `svc.getUser().save()` → chain=[{kind:'call',name:'getUser'}], receiverName='svc'
|
|
67
|
+
* `user.address.save()` → chain=[{kind:'field',name:'address'}], receiverName='user'
|
|
68
|
+
* `svc.getUser().address.save()` → chain=[{kind:'call',name:'getUser'},{kind:'field',name:'address'}]
|
|
64
69
|
* Length is capped at MAX_CHAIN_DEPTH (3).
|
|
65
70
|
*/
|
|
66
|
-
|
|
71
|
+
receiverMixedChain?: MixedChainStep[];
|
|
72
|
+
}
|
|
73
|
+
export interface ExtractedAssignment {
|
|
74
|
+
filePath: string;
|
|
75
|
+
/** generateId of enclosing function, or generateId('File', filePath) for top-level */
|
|
76
|
+
sourceId: string;
|
|
77
|
+
/** Receiver text (e.g., 'user' from user.address = value) */
|
|
78
|
+
receiverText: string;
|
|
79
|
+
/** Property name being written (e.g., 'address') */
|
|
80
|
+
propertyName: string;
|
|
81
|
+
/** Resolved type name of the receiver if available from TypeEnv */
|
|
82
|
+
receiverTypeName?: string;
|
|
67
83
|
}
|
|
68
84
|
export interface ExtractedHeritage {
|
|
69
85
|
filePath: string;
|
|
@@ -87,15 +103,24 @@ export interface FileConstructorBindings {
|
|
|
87
103
|
filePath: string;
|
|
88
104
|
bindings: ConstructorBinding[];
|
|
89
105
|
}
|
|
106
|
+
/** File-scope type bindings from TypeEnv fixpoint — used for cross-file ExportedTypeMap. */
|
|
107
|
+
export interface FileTypeEnvBindings {
|
|
108
|
+
filePath: string;
|
|
109
|
+
/** [varName, typeName] pairs from file scope (scope = '') */
|
|
110
|
+
bindings: [string, string][];
|
|
111
|
+
}
|
|
90
112
|
export interface ParseWorkerResult {
|
|
91
113
|
nodes: ParsedNode[];
|
|
92
114
|
relationships: ParsedRelationship[];
|
|
93
115
|
symbols: ParsedSymbol[];
|
|
94
116
|
imports: ExtractedImport[];
|
|
95
117
|
calls: ExtractedCall[];
|
|
118
|
+
assignments: ExtractedAssignment[];
|
|
96
119
|
heritage: ExtractedHeritage[];
|
|
97
120
|
routes: ExtractedRoute[];
|
|
98
121
|
constructorBindings: FileConstructorBindings[];
|
|
122
|
+
/** File-scope type bindings from TypeEnv fixpoint for exported symbol collection. */
|
|
123
|
+
typeEnvBindings: FileTypeEnvBindings[];
|
|
99
124
|
skippedLanguages: Record<string, number>;
|
|
100
125
|
fileCount: number;
|
|
101
126
|
}
|
|
@@ -28,15 +28,15 @@ try {
|
|
|
28
28
|
Kotlin = _require('tree-sitter-kotlin');
|
|
29
29
|
}
|
|
30
30
|
catch { }
|
|
31
|
-
import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode,
|
|
31
|
+
import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, getLabelFromCaptures, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, } from '../utils.js';
|
|
32
32
|
import { buildTypeEnv } from '../type-env.js';
|
|
33
33
|
import { isNodeExported } from '../export-detection.js';
|
|
34
34
|
import { detectFrameworkFromAST } from '../framework-detection.js';
|
|
35
35
|
import { typeConfigs } from '../type-extractors/index.js';
|
|
36
36
|
import { generateId } from '../../../lib/utils.js';
|
|
37
|
-
import {
|
|
38
|
-
import { appendKotlinWildcard } from '../resolvers/index.js';
|
|
37
|
+
import { namedBindingExtractors, preprocessImportPath } from '../import-resolution.js';
|
|
39
38
|
import { callRouters } from '../call-routing.js';
|
|
39
|
+
import { extractPropertyDeclaredType } from '../type-extractors/shared.js';
|
|
40
40
|
// ============================================================================
|
|
41
41
|
// Worker-local parser + language map
|
|
42
42
|
// ============================================================================
|
|
@@ -96,61 +96,7 @@ const findEnclosingFunctionId = (node, filePath) => {
|
|
|
96
96
|
}
|
|
97
97
|
return null;
|
|
98
98
|
};
|
|
99
|
-
//
|
|
100
|
-
// Label detection from capture map
|
|
101
|
-
// ============================================================================
|
|
102
|
-
const getLabelFromCaptures = (captureMap) => {
|
|
103
|
-
// Skip imports (handled separately) and calls
|
|
104
|
-
if (captureMap['import'] || captureMap['call'])
|
|
105
|
-
return null;
|
|
106
|
-
if (!captureMap['name'])
|
|
107
|
-
return null;
|
|
108
|
-
if (captureMap['definition.function'])
|
|
109
|
-
return 'Function';
|
|
110
|
-
if (captureMap['definition.class'])
|
|
111
|
-
return 'Class';
|
|
112
|
-
if (captureMap['definition.interface'])
|
|
113
|
-
return 'Interface';
|
|
114
|
-
if (captureMap['definition.method'])
|
|
115
|
-
return 'Method';
|
|
116
|
-
if (captureMap['definition.struct'])
|
|
117
|
-
return 'Struct';
|
|
118
|
-
if (captureMap['definition.enum'])
|
|
119
|
-
return 'Enum';
|
|
120
|
-
if (captureMap['definition.namespace'])
|
|
121
|
-
return 'Namespace';
|
|
122
|
-
if (captureMap['definition.module'])
|
|
123
|
-
return 'Module';
|
|
124
|
-
if (captureMap['definition.trait'])
|
|
125
|
-
return 'Trait';
|
|
126
|
-
if (captureMap['definition.impl'])
|
|
127
|
-
return 'Impl';
|
|
128
|
-
if (captureMap['definition.type'])
|
|
129
|
-
return 'TypeAlias';
|
|
130
|
-
if (captureMap['definition.const'])
|
|
131
|
-
return 'Const';
|
|
132
|
-
if (captureMap['definition.static'])
|
|
133
|
-
return 'Static';
|
|
134
|
-
if (captureMap['definition.typedef'])
|
|
135
|
-
return 'Typedef';
|
|
136
|
-
if (captureMap['definition.macro'])
|
|
137
|
-
return 'Macro';
|
|
138
|
-
if (captureMap['definition.union'])
|
|
139
|
-
return 'Union';
|
|
140
|
-
if (captureMap['definition.property'])
|
|
141
|
-
return 'Property';
|
|
142
|
-
if (captureMap['definition.record'])
|
|
143
|
-
return 'Record';
|
|
144
|
-
if (captureMap['definition.delegate'])
|
|
145
|
-
return 'Delegate';
|
|
146
|
-
if (captureMap['definition.annotation'])
|
|
147
|
-
return 'Annotation';
|
|
148
|
-
if (captureMap['definition.constructor'])
|
|
149
|
-
return 'Constructor';
|
|
150
|
-
if (captureMap['definition.template'])
|
|
151
|
-
return 'Template';
|
|
152
|
-
return 'CodeElement';
|
|
153
|
-
};
|
|
99
|
+
// Label detection moved to shared getLabelFromCaptures in utils.ts
|
|
154
100
|
// DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
|
|
155
101
|
// ============================================================================
|
|
156
102
|
// Process a batch of files
|
|
@@ -162,9 +108,11 @@ const processBatch = (files, onProgress) => {
|
|
|
162
108
|
symbols: [],
|
|
163
109
|
imports: [],
|
|
164
110
|
calls: [],
|
|
111
|
+
assignments: [],
|
|
165
112
|
heritage: [],
|
|
166
113
|
routes: [],
|
|
167
114
|
constructorBindings: [],
|
|
115
|
+
typeEnvBindings: [],
|
|
168
116
|
skippedLanguages: {},
|
|
169
117
|
fileCount: 0,
|
|
170
118
|
};
|
|
@@ -725,13 +673,6 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
725
673
|
}
|
|
726
674
|
result.fileCount++;
|
|
727
675
|
onFileProcessed?.();
|
|
728
|
-
// Build per-file type environment + constructor bindings in a single AST walk.
|
|
729
|
-
// Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
|
|
730
|
-
const typeEnv = buildTypeEnv(tree, language);
|
|
731
|
-
const callRouter = callRouters[language];
|
|
732
|
-
if (typeEnv.constructorBindings.length > 0) {
|
|
733
|
-
result.constructorBindings.push({ filePath: file.path, bindings: [...typeEnv.constructorBindings] });
|
|
734
|
-
}
|
|
735
676
|
let matches;
|
|
736
677
|
try {
|
|
737
678
|
matches = query.matches(tree.rootNode);
|
|
@@ -740,6 +681,51 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
740
681
|
console.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
741
682
|
continue;
|
|
742
683
|
}
|
|
684
|
+
// Pre-pass: extract heritage from query matches to build parentMap for buildTypeEnv.
|
|
685
|
+
// Heritage edges (EXTENDS/IMPLEMENTS) are created by heritage-processor which runs
|
|
686
|
+
// in PARALLEL with call-processor, so the graph edges don't exist when buildTypeEnv
|
|
687
|
+
// runs. This pre-pass makes parent class information available for type resolution.
|
|
688
|
+
const fileParentMap = new Map();
|
|
689
|
+
for (const match of matches) {
|
|
690
|
+
const captureMap = {};
|
|
691
|
+
for (const c of match.captures) {
|
|
692
|
+
captureMap[c.name] = c.node;
|
|
693
|
+
}
|
|
694
|
+
if (captureMap['heritage.class'] && captureMap['heritage.extends']) {
|
|
695
|
+
const className = captureMap['heritage.class'].text;
|
|
696
|
+
const parentName = captureMap['heritage.extends'].text;
|
|
697
|
+
// Skip Go named fields (only anonymous fields are struct embedding)
|
|
698
|
+
const extendsNode = captureMap['heritage.extends'];
|
|
699
|
+
const fieldDecl = extendsNode.parent;
|
|
700
|
+
if (fieldDecl?.type === 'field_declaration' && fieldDecl.childForFieldName('name'))
|
|
701
|
+
continue;
|
|
702
|
+
let parents = fileParentMap.get(className);
|
|
703
|
+
if (!parents) {
|
|
704
|
+
parents = [];
|
|
705
|
+
fileParentMap.set(className, parents);
|
|
706
|
+
}
|
|
707
|
+
if (!parents.includes(parentName))
|
|
708
|
+
parents.push(parentName);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// Build per-file type environment + constructor bindings in a single AST walk.
|
|
712
|
+
// Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
|
|
713
|
+
const parentMap = fileParentMap;
|
|
714
|
+
const typeEnv = buildTypeEnv(tree, language, { parentMap });
|
|
715
|
+
const callRouter = callRouters[language];
|
|
716
|
+
if (typeEnv.constructorBindings.length > 0) {
|
|
717
|
+
result.constructorBindings.push({ filePath: file.path, bindings: [...typeEnv.constructorBindings] });
|
|
718
|
+
}
|
|
719
|
+
// Extract file-scope bindings for ExportedTypeMap (closes worker/sequential quality gap).
|
|
720
|
+
// Sequential path uses collectExportedBindings(typeEnv) directly; worker path serializes
|
|
721
|
+
// these bindings so the main thread can merge them into ExportedTypeMap.
|
|
722
|
+
const fileScope = typeEnv.env.get('');
|
|
723
|
+
if (fileScope && fileScope.size > 0) {
|
|
724
|
+
const bindings = [];
|
|
725
|
+
for (const [name, type] of fileScope)
|
|
726
|
+
bindings.push([name, type]);
|
|
727
|
+
result.typeEnvBindings.push({ filePath: file.path, bindings });
|
|
728
|
+
}
|
|
743
729
|
for (const match of matches) {
|
|
744
730
|
const captureMap = {};
|
|
745
731
|
for (const c of match.captures) {
|
|
@@ -747,10 +733,11 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
747
733
|
}
|
|
748
734
|
// Extract import paths before skipping
|
|
749
735
|
if (captureMap['import'] && captureMap['import.source']) {
|
|
750
|
-
const rawImportPath =
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const
|
|
736
|
+
const rawImportPath = preprocessImportPath(captureMap['import.source'].text, captureMap['import'], language);
|
|
737
|
+
if (!rawImportPath)
|
|
738
|
+
continue;
|
|
739
|
+
const extractor = namedBindingExtractors[language];
|
|
740
|
+
const namedBindings = extractor ? extractor(captureMap['import']) : undefined;
|
|
754
741
|
result.imports.push({
|
|
755
742
|
filePath: file.path,
|
|
756
743
|
rawImportPath,
|
|
@@ -759,6 +746,28 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
759
746
|
});
|
|
760
747
|
continue;
|
|
761
748
|
}
|
|
749
|
+
// Extract assignment sites (field write access)
|
|
750
|
+
if (captureMap['assignment'] && captureMap['assignment.receiver'] && captureMap['assignment.property']) {
|
|
751
|
+
const receiverText = captureMap['assignment.receiver'].text;
|
|
752
|
+
const propertyName = captureMap['assignment.property'].text;
|
|
753
|
+
if (receiverText && propertyName) {
|
|
754
|
+
const srcId = findEnclosingFunctionId(captureMap['assignment'], file.path)
|
|
755
|
+
|| generateId('File', file.path);
|
|
756
|
+
let receiverTypeName;
|
|
757
|
+
if (typeEnv) {
|
|
758
|
+
receiverTypeName = typeEnv.lookup(receiverText, captureMap['assignment']) ?? undefined;
|
|
759
|
+
}
|
|
760
|
+
result.assignments.push({
|
|
761
|
+
filePath: file.path,
|
|
762
|
+
sourceId: srcId,
|
|
763
|
+
receiverText,
|
|
764
|
+
propertyName,
|
|
765
|
+
...(receiverTypeName ? { receiverTypeName } : {}),
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
if (!captureMap['call'])
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
762
771
|
// Extract call sites
|
|
763
772
|
if (captureMap['call']) {
|
|
764
773
|
const callNameNode = captureMap['call.name'];
|
|
@@ -811,6 +820,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
811
820
|
nodeId,
|
|
812
821
|
type: 'Property',
|
|
813
822
|
...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
|
|
823
|
+
...(item.declaredType ? { declaredType: item.declaredType } : {}),
|
|
814
824
|
});
|
|
815
825
|
const fileId = generateId('File', file.path);
|
|
816
826
|
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
@@ -824,10 +834,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
824
834
|
});
|
|
825
835
|
if (propEnclosingClassId) {
|
|
826
836
|
result.relationships.push({
|
|
827
|
-
id: generateId('
|
|
837
|
+
id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
|
|
828
838
|
sourceId: propEnclosingClassId,
|
|
829
839
|
targetId: nodeId,
|
|
830
|
-
type: '
|
|
840
|
+
type: 'HAS_PROPERTY',
|
|
831
841
|
confidence: 1.0,
|
|
832
842
|
reason: '',
|
|
833
843
|
});
|
|
@@ -844,26 +854,19 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
844
854
|
const callForm = inferCallForm(callNode, callNameNode);
|
|
845
855
|
let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
|
|
846
856
|
let receiverTypeName = receiverName ? typeEnv.lookup(receiverName, callNode) : undefined;
|
|
847
|
-
let
|
|
848
|
-
// When the receiver is a
|
|
849
|
-
// extractReceiverName returns undefined
|
|
850
|
-
//
|
|
851
|
-
// We capture the base receiver name so processCallsFromExtracted can look it up
|
|
852
|
-
// from constructor bindings. receiverTypeName is intentionally left unset here —
|
|
853
|
-
// the chain resolver in processCallsFromExtracted needs the base type as input and
|
|
854
|
-
// produces the final receiver type as output.
|
|
857
|
+
let receiverMixedChain;
|
|
858
|
+
// When the receiver is a complex expression (call chain, field chain, or mixed),
|
|
859
|
+
// extractReceiverName returns undefined. Walk the receiver node to build a unified
|
|
860
|
+
// mixed chain for deferred resolution in processCallsFromExtracted.
|
|
855
861
|
if (callForm === 'member' && receiverName === undefined && !receiverTypeName) {
|
|
856
862
|
const receiverNode = extractReceiverNode(callNameNode);
|
|
857
|
-
if (receiverNode
|
|
858
|
-
const extracted =
|
|
859
|
-
if (extracted) {
|
|
860
|
-
|
|
861
|
-
// Set receiverName to the base object so Step 1 in processCallsFromExtracted
|
|
862
|
-
// can resolve it via constructor bindings to a base type for the chain.
|
|
863
|
+
if (receiverNode) {
|
|
864
|
+
const extracted = extractMixedChain(receiverNode);
|
|
865
|
+
if (extracted && extracted.chain.length > 0) {
|
|
866
|
+
receiverMixedChain = extracted.chain;
|
|
863
867
|
receiverName = extracted.baseReceiverName;
|
|
864
|
-
//
|
|
865
|
-
// and annotated parameters
|
|
866
|
-
// This sets a base type that chain resolution (Step 2) will use as input.
|
|
868
|
+
// Try the type environment immediately for the base receiver
|
|
869
|
+
// (covers explicitly-typed locals and annotated parameters).
|
|
867
870
|
if (receiverName) {
|
|
868
871
|
receiverTypeName = typeEnv.lookup(receiverName, callNode);
|
|
869
872
|
}
|
|
@@ -878,7 +881,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
878
881
|
...(callForm !== undefined ? { callForm } : {}),
|
|
879
882
|
...(receiverName !== undefined ? { receiverName } : {}),
|
|
880
883
|
...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
|
|
881
|
-
...(
|
|
884
|
+
...(receiverMixedChain !== undefined ? { receiverMixedChain } : {}),
|
|
882
885
|
});
|
|
883
886
|
}
|
|
884
887
|
}
|
|
@@ -923,7 +926,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
923
926
|
continue;
|
|
924
927
|
}
|
|
925
928
|
}
|
|
926
|
-
const nodeLabel = getLabelFromCaptures(captureMap);
|
|
929
|
+
const nodeLabel = getLabelFromCaptures(captureMap, language);
|
|
927
930
|
if (!nodeLabel)
|
|
928
931
|
continue;
|
|
929
932
|
const nameNode = captureMap['name'];
|
|
@@ -947,10 +950,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
947
950
|
? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
|
|
948
951
|
: null;
|
|
949
952
|
let parameterCount;
|
|
953
|
+
let requiredParameterCount;
|
|
954
|
+
let parameterTypes;
|
|
950
955
|
let returnType;
|
|
956
|
+
let declaredType;
|
|
951
957
|
if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
|
|
952
958
|
const sig = extractMethodSignature(definitionNode);
|
|
953
959
|
parameterCount = sig.parameterCount;
|
|
960
|
+
requiredParameterCount = sig.requiredParameterCount;
|
|
961
|
+
parameterTypes = sig.parameterTypes;
|
|
954
962
|
returnType = sig.returnType;
|
|
955
963
|
// Language-specific return type fallback (e.g. Ruby YARD @return [Type])
|
|
956
964
|
// Also upgrades uninformative AST types like PHP `array` with PHPDoc `@return User[]`
|
|
@@ -963,6 +971,11 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
963
971
|
}
|
|
964
972
|
}
|
|
965
973
|
}
|
|
974
|
+
else if (nodeLabel === 'Property' && definitionNode) {
|
|
975
|
+
// Extract the declared type for property/field nodes.
|
|
976
|
+
// Walk the definition node for type annotation children.
|
|
977
|
+
declaredType = extractPropertyDeclaredType(definitionNode);
|
|
978
|
+
}
|
|
966
979
|
result.nodes.push({
|
|
967
980
|
id: nodeId,
|
|
968
981
|
label: nodeLabel,
|
|
@@ -979,6 +992,8 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
979
992
|
} : {}),
|
|
980
993
|
...(description !== undefined ? { description } : {}),
|
|
981
994
|
...(parameterCount !== undefined ? { parameterCount } : {}),
|
|
995
|
+
...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
|
|
996
|
+
...(parameterTypes !== undefined ? { parameterTypes } : {}),
|
|
982
997
|
...(returnType !== undefined ? { returnType } : {}),
|
|
983
998
|
},
|
|
984
999
|
});
|
|
@@ -992,7 +1007,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
992
1007
|
nodeId,
|
|
993
1008
|
type: nodeLabel,
|
|
994
1009
|
...(parameterCount !== undefined ? { parameterCount } : {}),
|
|
1010
|
+
...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
|
|
1011
|
+
...(parameterTypes !== undefined ? { parameterTypes } : {}),
|
|
995
1012
|
...(returnType !== undefined ? { returnType } : {}),
|
|
1013
|
+
...(declaredType !== undefined ? { declaredType } : {}),
|
|
996
1014
|
...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
|
|
997
1015
|
});
|
|
998
1016
|
const fileId = generateId('File', file.path);
|
|
@@ -1005,13 +1023,14 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1005
1023
|
confidence: 1.0,
|
|
1006
1024
|
reason: '',
|
|
1007
1025
|
});
|
|
1008
|
-
// ── HAS_METHOD: link
|
|
1026
|
+
// ── HAS_METHOD / HAS_PROPERTY: link member to enclosing class ──
|
|
1009
1027
|
if (enclosingClassId) {
|
|
1028
|
+
const memberEdgeType = nodeLabel === 'Property' ? 'HAS_PROPERTY' : 'HAS_METHOD';
|
|
1010
1029
|
result.relationships.push({
|
|
1011
|
-
id: generateId(
|
|
1030
|
+
id: generateId(memberEdgeType, `${enclosingClassId}->${nodeId}`),
|
|
1012
1031
|
sourceId: enclosingClassId,
|
|
1013
1032
|
targetId: nodeId,
|
|
1014
|
-
type:
|
|
1033
|
+
type: memberEdgeType,
|
|
1015
1034
|
confidence: 1.0,
|
|
1016
1035
|
reason: '',
|
|
1017
1036
|
});
|
|
@@ -1030,7 +1049,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1030
1049
|
/** Accumulated result across sub-batches */
|
|
1031
1050
|
let accumulated = {
|
|
1032
1051
|
nodes: [], relationships: [], symbols: [],
|
|
1033
|
-
imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
|
|
1052
|
+
imports: [], calls: [], assignments: [], heritage: [], routes: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0,
|
|
1034
1053
|
};
|
|
1035
1054
|
let cumulativeProcessed = 0;
|
|
1036
1055
|
const mergeResult = (target, src) => {
|
|
@@ -1039,9 +1058,11 @@ const mergeResult = (target, src) => {
|
|
|
1039
1058
|
target.symbols.push(...src.symbols);
|
|
1040
1059
|
target.imports.push(...src.imports);
|
|
1041
1060
|
target.calls.push(...src.calls);
|
|
1061
|
+
target.assignments.push(...src.assignments);
|
|
1042
1062
|
target.heritage.push(...src.heritage);
|
|
1043
1063
|
target.routes.push(...src.routes);
|
|
1044
1064
|
target.constructorBindings.push(...src.constructorBindings);
|
|
1065
|
+
target.typeEnvBindings.push(...src.typeEnvBindings);
|
|
1045
1066
|
for (const [lang, count] of Object.entries(src.skippedLanguages)) {
|
|
1046
1067
|
target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
|
|
1047
1068
|
}
|
|
@@ -1064,7 +1085,7 @@ parentPort.on('message', (msg) => {
|
|
|
1064
1085
|
if (msg && msg.type === 'flush') {
|
|
1065
1086
|
parentPort.postMessage({ type: 'result', data: accumulated });
|
|
1066
1087
|
// Reset for potential reuse
|
|
1067
|
-
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0 };
|
|
1088
|
+
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], assignments: [], heritage: [], routes: [], constructorBindings: [], typeEnvBindings: [], skippedLanguages: {}, fileCount: 0 };
|
|
1068
1089
|
cumulativeProcessed = 0;
|
|
1069
1090
|
return;
|
|
1070
1091
|
}
|
|
@@ -208,6 +208,8 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
|
|
|
208
208
|
const codeElemWriter = new BufferedCSVWriter(path.join(csvDir, 'codeelement.csv'), codeElementHeader);
|
|
209
209
|
const communityWriter = new BufferedCSVWriter(path.join(csvDir, 'community.csv'), 'id,label,heuristicLabel,keywords,description,enrichedBy,cohesion,symbolCount');
|
|
210
210
|
const processWriter = new BufferedCSVWriter(path.join(csvDir, 'process.csv'), 'id,label,heuristicLabel,processType,stepCount,communities,entryPointId,terminalId');
|
|
211
|
+
// Section nodes have an extra 'level' column
|
|
212
|
+
const sectionWriter = new BufferedCSVWriter(path.join(csvDir, 'section.csv'), 'id,name,filePath,startLine,endLine,level,content,description');
|
|
211
213
|
// Multi-language node types share the same CSV shape (no isExported column)
|
|
212
214
|
const multiLangHeader = 'id,name,filePath,startLine,endLine,content,description';
|
|
213
215
|
const MULTI_LANG_TYPES = ['Struct', 'Enum', 'Macro', 'Typedef', 'Union', 'Namespace', 'Trait', 'Impl',
|
|
@@ -292,6 +294,20 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
|
|
|
292
294
|
].join(','));
|
|
293
295
|
break;
|
|
294
296
|
}
|
|
297
|
+
case 'Section': {
|
|
298
|
+
const content = await extractContent(node, contentCache);
|
|
299
|
+
await sectionWriter.addRow([
|
|
300
|
+
escapeCSVField(node.id),
|
|
301
|
+
escapeCSVField(node.properties.name || ''),
|
|
302
|
+
escapeCSVField(node.properties.filePath || ''),
|
|
303
|
+
escapeCSVNumber(node.properties.startLine, -1),
|
|
304
|
+
escapeCSVNumber(node.properties.endLine, -1),
|
|
305
|
+
escapeCSVNumber(node.properties.level, 1),
|
|
306
|
+
escapeCSVField(content),
|
|
307
|
+
escapeCSVField(node.properties.description || ''),
|
|
308
|
+
].join(','));
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
295
311
|
default: {
|
|
296
312
|
// Code element nodes (Function, Class, Interface, CodeElement)
|
|
297
313
|
const writer = codeWriterMap[node.label];
|
|
@@ -329,7 +345,7 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
|
|
|
329
345
|
}
|
|
330
346
|
}
|
|
331
347
|
// Finish all node writers
|
|
332
|
-
const allWriters = [fileWriter, folderWriter, functionWriter, classWriter, interfaceWriter, methodWriter, codeElemWriter, communityWriter, processWriter, ...multiLangWriters.values()];
|
|
348
|
+
const allWriters = [fileWriter, folderWriter, functionWriter, classWriter, interfaceWriter, methodWriter, codeElemWriter, communityWriter, processWriter, sectionWriter, ...multiLangWriters.values()];
|
|
333
349
|
await Promise.all(allWriters.map(w => w.finish()));
|
|
334
350
|
// --- Stream relationship CSV ---
|
|
335
351
|
const relCsvPath = path.join(csvDir, 'relations.csv');
|
|
@@ -353,6 +369,7 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
|
|
|
353
369
|
['Interface', interfaceWriter], ['Method', methodWriter],
|
|
354
370
|
['CodeElement', codeElemWriter],
|
|
355
371
|
['Community', communityWriter], ['Process', processWriter],
|
|
372
|
+
['Section', sectionWriter],
|
|
356
373
|
...Array.from(multiLangWriters.entries()).map(([name, w]) => [name, w]),
|
|
357
374
|
];
|
|
358
375
|
for (const [name, writer] of tableMap) {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import lbug from '@ladybugdb/core';
|
|
2
2
|
import { KnowledgeGraph } from '../graph/types.js';
|
|
3
|
+
/** Expose the current Database for pool adapter reuse in tests. */
|
|
4
|
+
export declare const getDatabase: () => lbug.Database | null;
|
|
5
|
+
/**
|
|
6
|
+
* Return true when the error message indicates that another process holds
|
|
7
|
+
* an exclusive lock on the LadybugDB file (e.g. `gitnexus analyze` or
|
|
8
|
+
* `gitnexus serve` running at the same time).
|
|
9
|
+
*/
|
|
10
|
+
export declare const isDbBusyError: (err: unknown) => boolean;
|
|
3
11
|
export declare const initLbug: (dbPath: string) => Promise<{
|
|
4
12
|
db: lbug.Database;
|
|
5
13
|
conn: lbug.Connection;
|
|
@@ -7,6 +15,10 @@ export declare const initLbug: (dbPath: string) => Promise<{
|
|
|
7
15
|
/**
|
|
8
16
|
* Execute multiple queries against one repo DB atomically.
|
|
9
17
|
* While the callback runs, no other request can switch the active DB.
|
|
18
|
+
*
|
|
19
|
+
* Automatically retries up to DB_LOCK_RETRY_ATTEMPTS times when the
|
|
20
|
+
* database is busy (e.g. `gitnexus analyze` holds the write lock).
|
|
21
|
+
* Each retry waits DB_LOCK_RETRY_DELAY_MS * attempt milliseconds.
|
|
10
22
|
*/
|
|
11
23
|
export declare const withLbugDb: <T>(dbPath: string, operation: () => Promise<T>) => Promise<T>;
|
|
12
24
|
export type LbugProgressCallback = (message: string) => void;
|