gitnexus 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/_shared/graph/types.d.ts +1 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +1 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +1 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +3 -1
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/mro-strategy.d.ts +19 -0
- package/dist/_shared/mro-strategy.d.ts.map +1 -0
- package/dist/_shared/mro-strategy.js +2 -0
- package/dist/_shared/mro-strategy.js.map +1 -0
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +28 -4
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +2 -1
- package/dist/cli/group.d.ts +2 -0
- package/dist/cli/group.js +233 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/serve.js +4 -1
- package/dist/cli/setup.js +34 -3
- package/dist/config/ignore-service.js +8 -3
- package/dist/core/augmentation/engine.js +1 -1
- package/dist/core/git-staleness.d.ts +13 -0
- package/dist/core/git-staleness.js +29 -0
- package/dist/core/group/bridge-db.d.ts +82 -0
- package/dist/core/group/bridge-db.js +460 -0
- package/dist/core/group/bridge-schema.d.ts +27 -0
- package/dist/core/group/bridge-schema.js +55 -0
- package/dist/core/group/config-parser.d.ts +3 -0
- package/dist/core/group/config-parser.js +83 -0
- package/dist/core/group/contract-extractor.d.ts +7 -0
- package/dist/core/group/contract-extractor.js +1 -0
- package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
- package/dist/core/group/extractors/grpc-extractor.js +264 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +428 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
- package/dist/core/group/extractors/topic-extractor.js +234 -0
- package/dist/core/group/matching.d.ts +13 -0
- package/dist/core/group/matching.js +198 -0
- package/dist/core/group/normalization.d.ts +3 -0
- package/dist/core/group/normalization.js +115 -0
- package/dist/core/group/service-boundary-detector.d.ts +8 -0
- package/dist/core/group/service-boundary-detector.js +155 -0
- package/dist/core/group/service.d.ts +46 -0
- package/dist/core/group/service.js +160 -0
- package/dist/core/group/storage.d.ts +9 -0
- package/dist/core/group/storage.js +91 -0
- package/dist/core/group/sync.d.ts +21 -0
- package/dist/core/group/sync.js +148 -0
- package/dist/core/group/types.d.ts +130 -0
- package/dist/core/group/types.js +1 -0
- package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
- package/dist/core/ingestion/binding-accumulator.js +332 -0
- package/dist/core/ingestion/call-processor.d.ts +155 -24
- package/dist/core/ingestion/call-processor.js +1129 -247
- package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/generic.js +135 -0
- package/dist/core/ingestion/class-types.d.ts +34 -0
- package/dist/core/ingestion/class-types.js +1 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
- package/dist/core/ingestion/entry-point-scoring.js +1 -0
- package/dist/core/ingestion/field-types.d.ts +2 -2
- package/dist/core/ingestion/filesystem-walker.js +8 -0
- package/dist/core/ingestion/framework-detection.d.ts +1 -0
- package/dist/core/ingestion/framework-detection.js +1 -0
- package/dist/core/ingestion/heritage-processor.d.ts +8 -15
- package/dist/core/ingestion/heritage-processor.js +15 -28
- package/dist/core/ingestion/import-processor.d.ts +1 -11
- package/dist/core/ingestion/import-processor.js +0 -12
- package/dist/core/ingestion/import-resolvers/utils.js +1 -0
- package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/vue.js +9 -0
- package/dist/core/ingestion/language-provider.d.ts +6 -3
- package/dist/core/ingestion/languages/c-cpp.js +168 -1
- package/dist/core/ingestion/languages/csharp.js +20 -0
- package/dist/core/ingestion/languages/dart.js +26 -4
- package/dist/core/ingestion/languages/go.js +22 -0
- package/dist/core/ingestion/languages/index.d.ts +1 -0
- package/dist/core/ingestion/languages/index.js +2 -0
- package/dist/core/ingestion/languages/java.js +17 -0
- package/dist/core/ingestion/languages/kotlin.js +24 -1
- package/dist/core/ingestion/languages/php.js +23 -11
- package/dist/core/ingestion/languages/python.js +9 -0
- package/dist/core/ingestion/languages/ruby.js +28 -0
- package/dist/core/ingestion/languages/rust.js +38 -0
- package/dist/core/ingestion/languages/swift.js +31 -0
- package/dist/core/ingestion/languages/typescript.d.ts +1 -0
- package/dist/core/ingestion/languages/typescript.js +52 -3
- package/dist/core/ingestion/languages/vue.d.ts +13 -0
- package/dist/core/ingestion/languages/vue.js +81 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
- package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
- package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
- package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
- package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
- package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
- package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
- package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
- package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +85 -8
- package/dist/core/ingestion/method-extractors/generic.js +38 -15
- package/dist/core/ingestion/method-types.d.ts +25 -0
- package/dist/core/ingestion/model/field-registry.d.ts +18 -0
- package/dist/core/ingestion/model/field-registry.js +22 -0
- package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
- package/dist/core/ingestion/model/heritage-map.js +159 -0
- package/dist/core/ingestion/model/index.d.ts +20 -0
- package/dist/core/ingestion/model/index.js +41 -0
- package/dist/core/ingestion/model/method-registry.d.ts +62 -0
- package/dist/core/ingestion/model/method-registry.js +130 -0
- package/dist/core/ingestion/model/registration-table.d.ts +139 -0
- package/dist/core/ingestion/model/registration-table.js +224 -0
- package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
- package/dist/core/ingestion/model/resolution-context.js +337 -0
- package/dist/core/ingestion/model/resolve.d.ts +56 -0
- package/dist/core/ingestion/model/resolve.js +242 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
- package/dist/core/ingestion/model/semantic-model.js +120 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
- package/dist/core/ingestion/model/symbol-table.js +206 -0
- package/dist/core/ingestion/model/type-registry.d.ts +39 -0
- package/dist/core/ingestion/model/type-registry.js +62 -0
- package/dist/core/ingestion/mro-processor.d.ts +4 -3
- package/dist/core/ingestion/mro-processor.js +310 -106
- package/dist/core/ingestion/parsing-processor.d.ts +5 -4
- package/dist/core/ingestion/parsing-processor.js +210 -85
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +192 -68
- package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
- package/dist/core/ingestion/tree-sitter-queries.js +21 -0
- package/dist/core/ingestion/type-env.d.ts +15 -2
- package/dist/core/ingestion/type-env.js +163 -102
- package/dist/core/ingestion/type-extractors/csharp.js +17 -0
- package/dist/core/ingestion/type-extractors/jvm.js +11 -0
- package/dist/core/ingestion/type-extractors/php.js +0 -55
- package/dist/core/ingestion/type-extractors/ruby.js +0 -32
- package/dist/core/ingestion/type-extractors/swift.js +13 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
- package/dist/core/ingestion/type-extractors/typescript.js +66 -69
- package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
- package/dist/core/ingestion/utils/ast-helpers.js +129 -572
- package/dist/core/ingestion/utils/method-props.d.ts +32 -0
- package/dist/core/ingestion/utils/method-props.js +147 -0
- package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
- package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
- package/dist/core/ingestion/workers/parse-worker.js +463 -198
- package/dist/core/lbug/lbug-adapter.d.ts +6 -0
- package/dist/core/lbug/lbug-adapter.js +68 -3
- package/dist/core/lbug/pool-adapter.d.ts +76 -0
- package/dist/core/lbug/pool-adapter.js +522 -0
- package/dist/core/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +1 -1
- package/dist/core/search/bm25-index.js +1 -1
- package/dist/core/tree-sitter/parser-loader.js +1 -0
- package/dist/core/wiki/graph-queries.js +1 -1
- package/dist/mcp/core/embedder.js +6 -5
- package/dist/mcp/core/lbug-adapter.d.ts +3 -63
- package/dist/mcp/core/lbug-adapter.js +3 -484
- package/dist/mcp/local/local-backend.d.ts +31 -2
- package/dist/mcp/local/local-backend.js +255 -46
- package/dist/mcp/resources.js +5 -4
- package/dist/mcp/staleness.d.ts +3 -13
- package/dist/mcp/staleness.js +2 -31
- package/dist/mcp/tools.js +80 -4
- package/dist/server/analyze-job.d.ts +2 -0
- package/dist/server/analyze-job.js +4 -0
- package/dist/server/api.d.ts +20 -1
- package/dist/server/api.js +306 -71
- package/dist/server/git-clone.d.ts +2 -1
- package/dist/server/git-clone.js +98 -5
- package/dist/storage/git.d.ts +13 -0
- package/dist/storage/git.js +25 -0
- package/dist/storage/repo-manager.js +1 -1
- package/package.json +8 -2
- package/scripts/patch-tree-sitter-swift.cjs +78 -0
- package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
- package/dist/core/ingestion/named-binding-processor.js +0 -42
- package/dist/core/ingestion/resolution-context.d.ts +0 -58
- package/dist/core/ingestion/resolution-context.js +0 -135
- package/dist/core/ingestion/symbol-table.d.ts +0 -79
- package/dist/core/ingestion/symbol-table.js +0 -115
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Walks the inheritance DAG (EXTENDS/IMPLEMENTS edges), collects methods from
|
|
5
5
|
* each ancestor via HAS_METHOD edges, detects method-name collisions across
|
|
6
|
-
* parents, and applies language-specific resolution rules to emit
|
|
6
|
+
* parents, and applies language-specific resolution rules to emit METHOD_OVERRIDES edges.
|
|
7
7
|
*
|
|
8
8
|
* Language-specific rules:
|
|
9
9
|
* - C++: leftmost base class in declaration order wins
|
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
* - Rust: no auto-resolution — requires qualified syntax, resolvedTo = null
|
|
14
14
|
* - Default: single inheritance — first definition wins
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* METHOD_OVERRIDES edge direction: Class → Method (not Method → Method).
|
|
17
17
|
* The source is the child class that inherits conflicting methods,
|
|
18
18
|
* the target is the winning ancestor method node.
|
|
19
|
-
* Cypher: MATCH (c:Class)-[r:CodeRelation {type: '
|
|
19
|
+
* Cypher: MATCH (c:Class)-[r:CodeRelation {type: 'METHOD_OVERRIDES'}]->(m:Method)
|
|
20
20
|
*/
|
|
21
21
|
import { generateId } from '../../lib/utils.js';
|
|
22
22
|
import { getProvider } from './languages/index.js';
|
|
23
|
+
import { c3Linearize, gatherAncestors } from './model/resolve.js';
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
// Internal helpers
|
|
25
26
|
// ---------------------------------------------------------------------------
|
|
@@ -57,99 +58,6 @@ function buildAdjacency(graph) {
|
|
|
57
58
|
});
|
|
58
59
|
return { parentMap, methodMap, parentEdgeType };
|
|
59
60
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Gather all ancestor IDs in BFS / topological order.
|
|
62
|
-
* Returns the linearized list of ancestor IDs (excluding the class itself).
|
|
63
|
-
*/
|
|
64
|
-
function gatherAncestors(classId, parentMap) {
|
|
65
|
-
const visited = new Set();
|
|
66
|
-
const order = [];
|
|
67
|
-
const queue = [...(parentMap.get(classId) ?? [])];
|
|
68
|
-
while (queue.length > 0) {
|
|
69
|
-
const id = queue.shift();
|
|
70
|
-
if (visited.has(id))
|
|
71
|
-
continue;
|
|
72
|
-
visited.add(id);
|
|
73
|
-
order.push(id);
|
|
74
|
-
const grandparents = parentMap.get(id);
|
|
75
|
-
if (grandparents) {
|
|
76
|
-
for (const gp of grandparents) {
|
|
77
|
-
if (!visited.has(gp))
|
|
78
|
-
queue.push(gp);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return order;
|
|
83
|
-
}
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// C3 linearization (Python MRO)
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
/**
|
|
88
|
-
* Compute C3 linearization for a class given a parentMap.
|
|
89
|
-
* Returns an array of ancestor IDs in C3 order (excluding the class itself),
|
|
90
|
-
* or null if linearization fails (inconsistent or cyclic hierarchy).
|
|
91
|
-
*/
|
|
92
|
-
function c3Linearize(classId, parentMap, cache, inProgress) {
|
|
93
|
-
if (cache.has(classId))
|
|
94
|
-
return cache.get(classId);
|
|
95
|
-
// Cycle detection: if we're already computing this class, the hierarchy is cyclic
|
|
96
|
-
const visiting = inProgress ?? new Set();
|
|
97
|
-
if (visiting.has(classId)) {
|
|
98
|
-
cache.set(classId, null);
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
visiting.add(classId);
|
|
102
|
-
const directParents = parentMap.get(classId);
|
|
103
|
-
if (!directParents || directParents.length === 0) {
|
|
104
|
-
visiting.delete(classId);
|
|
105
|
-
cache.set(classId, []);
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
// Compute linearization for each parent first
|
|
109
|
-
const parentLinearizations = [];
|
|
110
|
-
for (const pid of directParents) {
|
|
111
|
-
const pLin = c3Linearize(pid, parentMap, cache, visiting);
|
|
112
|
-
if (pLin === null) {
|
|
113
|
-
visiting.delete(classId);
|
|
114
|
-
cache.set(classId, null);
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
parentLinearizations.push([pid, ...pLin]);
|
|
118
|
-
}
|
|
119
|
-
// Add the direct parents list as the final sequence
|
|
120
|
-
const sequences = [...parentLinearizations, [...directParents]];
|
|
121
|
-
const result = [];
|
|
122
|
-
while (sequences.some((s) => s.length > 0)) {
|
|
123
|
-
// Find a good head: one that doesn't appear in the tail of any other sequence
|
|
124
|
-
let head = null;
|
|
125
|
-
for (const seq of sequences) {
|
|
126
|
-
if (seq.length === 0)
|
|
127
|
-
continue;
|
|
128
|
-
const candidate = seq[0];
|
|
129
|
-
const inTail = sequences.some((other) => other.length > 1 && other.indexOf(candidate, 1) !== -1);
|
|
130
|
-
if (!inTail) {
|
|
131
|
-
head = candidate;
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (head === null) {
|
|
136
|
-
// Inconsistent hierarchy
|
|
137
|
-
visiting.delete(classId);
|
|
138
|
-
cache.set(classId, null);
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
result.push(head);
|
|
142
|
-
// Remove the chosen head from all sequences
|
|
143
|
-
for (const seq of sequences) {
|
|
144
|
-
if (seq.length > 0 && seq[0] === head) {
|
|
145
|
-
seq.shift();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
visiting.delete(classId);
|
|
150
|
-
cache.set(classId, result);
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
61
|
/** Resolve by MRO order — first ancestor in linearized order wins. */
|
|
154
62
|
function resolveByMroOrder(methodName, defs, mroOrder, reasonPrefix) {
|
|
155
63
|
for (const ancestorId of mroOrder) {
|
|
@@ -212,6 +120,9 @@ export function computeMRO(graph) {
|
|
|
212
120
|
const entries = [];
|
|
213
121
|
let overrideEdges = 0;
|
|
214
122
|
let ambiguityCount = 0;
|
|
123
|
+
// Pre-computed maps to avoid redundant BFS in emitMethodImplementsEdges
|
|
124
|
+
const ancestorsMap = new Map();
|
|
125
|
+
const edgeTypesMap = new Map();
|
|
215
126
|
// Process every class that has at least one parent
|
|
216
127
|
for (const [classId, directParents] of parentMap) {
|
|
217
128
|
if (directParents.length === 0)
|
|
@@ -225,13 +136,16 @@ export function computeMRO(graph) {
|
|
|
225
136
|
const className = classNode.properties.name;
|
|
226
137
|
// Compute linearized MRO depending on language strategy
|
|
227
138
|
const provider = getProvider(language);
|
|
139
|
+
const ancestors = gatherAncestors(classId, parentMap);
|
|
140
|
+
ancestorsMap.set(classId, ancestors);
|
|
141
|
+
edgeTypesMap.set(classId, buildTransitiveEdgeTypes(classId, parentMap, parentEdgeType));
|
|
228
142
|
let mroOrder;
|
|
229
143
|
if (provider.mroStrategy === 'c3') {
|
|
230
144
|
const c3Result = c3Linearize(classId, parentMap, c3Cache);
|
|
231
|
-
mroOrder = c3Result ??
|
|
145
|
+
mroOrder = c3Result ?? ancestors;
|
|
232
146
|
}
|
|
233
147
|
else {
|
|
234
|
-
mroOrder =
|
|
148
|
+
mroOrder = ancestors;
|
|
235
149
|
}
|
|
236
150
|
// Get the parent names for the MRO entry
|
|
237
151
|
const mroNames = mroOrder
|
|
@@ -269,11 +183,9 @@ export function computeMRO(graph) {
|
|
|
269
183
|
}
|
|
270
184
|
// Detect collisions: methods defined in 2+ different ancestors
|
|
271
185
|
const ambiguities = [];
|
|
272
|
-
//
|
|
186
|
+
// Use pre-computed transitive edge types (only needed for implements-split languages)
|
|
273
187
|
const needsEdgeTypes = provider.mroStrategy === 'implements-split';
|
|
274
|
-
const classEdgeTypes = needsEdgeTypes
|
|
275
|
-
? buildTransitiveEdgeTypes(classId, parentMap, parentEdgeType)
|
|
276
|
-
: undefined;
|
|
188
|
+
const classEdgeTypes = needsEdgeTypes ? edgeTypesMap.get(classId) : undefined;
|
|
277
189
|
for (const [methodName, defs] of methodsByName) {
|
|
278
190
|
if (defs.length < 2)
|
|
279
191
|
continue;
|
|
@@ -317,13 +229,13 @@ export function computeMRO(graph) {
|
|
|
317
229
|
if (resolution.resolvedTo === null) {
|
|
318
230
|
ambiguityCount++;
|
|
319
231
|
}
|
|
320
|
-
// Emit
|
|
232
|
+
// Emit METHOD_OVERRIDES edge if resolution found
|
|
321
233
|
if (resolution.resolvedTo !== null) {
|
|
322
234
|
graph.addRelationship({
|
|
323
|
-
id: generateId('
|
|
235
|
+
id: generateId('METHOD_OVERRIDES', `${classId}->${resolution.resolvedTo}`),
|
|
324
236
|
sourceId: classId,
|
|
325
237
|
targetId: resolution.resolvedTo,
|
|
326
|
-
type: '
|
|
238
|
+
type: 'METHOD_OVERRIDES',
|
|
327
239
|
confidence: resolution.confidence,
|
|
328
240
|
reason: resolution.reason,
|
|
329
241
|
});
|
|
@@ -338,7 +250,299 @@ export function computeMRO(graph) {
|
|
|
338
250
|
ambiguities,
|
|
339
251
|
});
|
|
340
252
|
}
|
|
341
|
-
|
|
253
|
+
const methodImplementsEdges = emitMethodImplementsEdges(graph, parentMap, methodMap, parentEdgeType, ancestorsMap, edgeTypesMap);
|
|
254
|
+
return { entries, overrideEdges, ambiguityCount, methodImplementsEdges };
|
|
255
|
+
}
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// METHOD_IMPLEMENTS edge emission
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
/**
|
|
260
|
+
* Check if two parameter type arrays match.
|
|
261
|
+
* When either side has no type info, fall back to parameterCount comparison
|
|
262
|
+
* (arity-compatible matching). If both have parameterCount and they differ,
|
|
263
|
+
* return no match. If counts match, return confident match. If either count
|
|
264
|
+
* is undefined, return lenient (non-confident) match.
|
|
265
|
+
*
|
|
266
|
+
* Returns `{ match, confident }`:
|
|
267
|
+
* - Exact type match → `{ match: true, confident: true }`
|
|
268
|
+
* - Arity match (both have parameterCount, counts equal) → `{ match: true, confident: true }`
|
|
269
|
+
* - Lenient (either side lacks types AND lacks parameterCount) → `{ match: true, confident: false }`
|
|
270
|
+
* - No match → `{ match: false, confident: false }`
|
|
271
|
+
*/
|
|
272
|
+
function parameterTypesMatch(a, b, aParamCount, bParamCount) {
|
|
273
|
+
// If one side is variadic and the other isn't, types may match superficially
|
|
274
|
+
// but the methods aren't guaranteed to be interchangeable
|
|
275
|
+
if ((aParamCount === undefined) !== (bParamCount === undefined)) {
|
|
276
|
+
return { match: true, confident: false };
|
|
277
|
+
}
|
|
278
|
+
if (a.length === 0 || b.length === 0) {
|
|
279
|
+
// Fall back to arity check when type info is missing
|
|
280
|
+
if (aParamCount !== undefined && bParamCount !== undefined) {
|
|
281
|
+
return { match: aParamCount === bParamCount, confident: aParamCount === bParamCount };
|
|
282
|
+
}
|
|
283
|
+
return { match: true, confident: false }; // lenient when either count is unknown
|
|
284
|
+
}
|
|
285
|
+
if (a.length !== b.length)
|
|
286
|
+
return { match: false, confident: false };
|
|
287
|
+
const exact = a.every((t, i) => t === b[i]);
|
|
288
|
+
return { match: exact, confident: exact };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* For each concrete class that implements/extends an interface or trait,
|
|
292
|
+
* find methods in the class that implement methods defined in the interface
|
|
293
|
+
* and emit METHOD_IMPLEMENTS edges: ConcreteMethod → InterfaceMethod.
|
|
294
|
+
*
|
|
295
|
+
* Method node IDs include a `#<paramCount>` arity suffix, so overloaded
|
|
296
|
+
* methods with different parameter counts are distinct nodes in the graph.
|
|
297
|
+
* For same-arity overloads with different parameter types, a `~type1,type2`
|
|
298
|
+
* suffix is appended when type info is available (issue #651), producing
|
|
299
|
+
* distinct nodes that `parameterTypesMatch` can resolve to correct edges.
|
|
300
|
+
*/
|
|
301
|
+
function emitMethodImplementsEdges(graph, parentMap, methodMap, parentEdgeType, ancestorsMap, edgeTypesMap) {
|
|
302
|
+
let edgeCount = 0;
|
|
303
|
+
for (const [classId, parentIds] of parentMap) {
|
|
304
|
+
const classNode = graph.getNode(classId);
|
|
305
|
+
if (!classNode)
|
|
306
|
+
continue;
|
|
307
|
+
// Interfaces and traits declare contracts — they don't implement them
|
|
308
|
+
if (classNode.label === 'Interface' || classNode.label === 'Trait')
|
|
309
|
+
continue;
|
|
310
|
+
// Get this class's own methods
|
|
311
|
+
const ownMethodIds = methodMap.get(classId) ?? [];
|
|
312
|
+
// Build a lookup: methodName → Array<{methodId, parameterTypes, parameterCount}> for own methods
|
|
313
|
+
const ownMethodsByName = new Map();
|
|
314
|
+
for (const methodId of ownMethodIds) {
|
|
315
|
+
const methodNode = graph.getNode(methodId);
|
|
316
|
+
if (!methodNode || methodNode.label === 'Property')
|
|
317
|
+
continue;
|
|
318
|
+
// Abstract methods don't satisfy interface contracts
|
|
319
|
+
if (methodNode.properties.isAbstract === true)
|
|
320
|
+
continue;
|
|
321
|
+
const name = methodNode.properties.name;
|
|
322
|
+
const parameterTypes = methodNode.properties.parameterTypes ?? [];
|
|
323
|
+
const parameterCount = methodNode.properties.parameterCount;
|
|
324
|
+
let bucket = ownMethodsByName.get(name);
|
|
325
|
+
if (!bucket) {
|
|
326
|
+
bucket = [];
|
|
327
|
+
ownMethodsByName.set(name, bucket);
|
|
328
|
+
}
|
|
329
|
+
bucket.push({ methodId, parameterTypes, parameterCount });
|
|
330
|
+
}
|
|
331
|
+
// Use pre-computed ancestors and edge types; fall back to computing if missing (safety)
|
|
332
|
+
const allAncestors = ancestorsMap.get(classId) ?? gatherAncestors(classId, parentMap);
|
|
333
|
+
const ancestorEdgeTypes = edgeTypesMap.get(classId) ?? buildTransitiveEdgeTypes(classId, parentMap, parentEdgeType);
|
|
334
|
+
// Dedup set: avoid duplicate edges from diamond paths
|
|
335
|
+
const emitted = new Set();
|
|
336
|
+
// For each ancestor, check if it's an interface/trait or classified as IMPLEMENTS
|
|
337
|
+
for (const ancestorId of allAncestors) {
|
|
338
|
+
const ancestorNode = graph.getNode(ancestorId);
|
|
339
|
+
if (!ancestorNode)
|
|
340
|
+
continue;
|
|
341
|
+
const isInterfaceLike = ancestorNode.label === 'Interface' || ancestorNode.label === 'Trait';
|
|
342
|
+
const classifiedEdgeType = ancestorEdgeTypes.get(ancestorId);
|
|
343
|
+
if (!isInterfaceLike && classifiedEdgeType !== 'IMPLEMENTS')
|
|
344
|
+
continue;
|
|
345
|
+
// Get ancestor's methods
|
|
346
|
+
const ancestorMethodIds = methodMap.get(ancestorId) ?? [];
|
|
347
|
+
for (const ancestorMethodId of ancestorMethodIds) {
|
|
348
|
+
const ancestorMethodNode = graph.getNode(ancestorMethodId);
|
|
349
|
+
if (!ancestorMethodNode || ancestorMethodNode.label === 'Property')
|
|
350
|
+
continue;
|
|
351
|
+
const ancestorName = ancestorMethodNode.properties.name;
|
|
352
|
+
const ancestorParamTypes = ancestorMethodNode.properties.parameterTypes ?? [];
|
|
353
|
+
const ancestorParamCount = ancestorMethodNode.properties.parameterCount;
|
|
354
|
+
// Find matching method in own class by name + parameterTypes/arity
|
|
355
|
+
const candidates = ownMethodsByName.get(ancestorName);
|
|
356
|
+
// Unit 3: If no own method matches, walk the EXTENDS chain to find inherited concrete method
|
|
357
|
+
if (!candidates || candidates.length === 0) {
|
|
358
|
+
const inherited = findInheritedMethod(classId, ancestorName, ancestorParamTypes, ancestorParamCount, graph, parentMap, methodMap, parentEdgeType, ancestorMethodId);
|
|
359
|
+
if (inherited) {
|
|
360
|
+
const edgeKey = `${inherited.methodId}->${ancestorMethodId}`;
|
|
361
|
+
if (!emitted.has(edgeKey)) {
|
|
362
|
+
emitted.add(edgeKey);
|
|
363
|
+
graph.addRelationship({
|
|
364
|
+
id: generateId('METHOD_IMPLEMENTS', edgeKey),
|
|
365
|
+
sourceId: inherited.methodId,
|
|
366
|
+
targetId: ancestorMethodId,
|
|
367
|
+
type: 'METHOD_IMPLEMENTS',
|
|
368
|
+
confidence: inherited.confident ? 1.0 : 0.7,
|
|
369
|
+
reason: '',
|
|
370
|
+
});
|
|
371
|
+
edgeCount++;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
// Unit 4: Filter candidates by type/arity match, then check for ambiguity
|
|
377
|
+
const matching = [];
|
|
378
|
+
for (const c of candidates) {
|
|
379
|
+
const result = parameterTypesMatch(c.parameterTypes, ancestorParamTypes, c.parameterCount, ancestorParamCount);
|
|
380
|
+
if (result.match) {
|
|
381
|
+
matching.push({ ...c, confident: result.confident });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (matching.length === 0)
|
|
385
|
+
continue;
|
|
386
|
+
// If multiple candidates match at name+arity level, emit no edge (ambiguous)
|
|
387
|
+
if (matching.length > 1)
|
|
388
|
+
continue;
|
|
389
|
+
const winner = matching[0];
|
|
390
|
+
const edgeKey = `${winner.methodId}->${ancestorMethodId}`;
|
|
391
|
+
if (emitted.has(edgeKey))
|
|
392
|
+
continue;
|
|
393
|
+
emitted.add(edgeKey);
|
|
394
|
+
graph.addRelationship({
|
|
395
|
+
id: generateId('METHOD_IMPLEMENTS', edgeKey),
|
|
396
|
+
sourceId: winner.methodId,
|
|
397
|
+
targetId: ancestorMethodId,
|
|
398
|
+
type: 'METHOD_IMPLEMENTS',
|
|
399
|
+
confidence: winner.confident ? 1.0 : 0.7,
|
|
400
|
+
reason: '',
|
|
401
|
+
});
|
|
402
|
+
edgeCount++;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return edgeCount;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Walk the class's EXTENDS chain to find the nearest concrete method matching
|
|
410
|
+
* the given name and parameter signature. If the EXTENDS chain yields no match,
|
|
411
|
+
* fall back to IMPLEMENTS parents and check for non-abstract default methods
|
|
412
|
+
* (e.g. Java default interface methods, Kotlin interface defaults).
|
|
413
|
+
* Returns the first matching method found in BFS order, or null.
|
|
414
|
+
*/
|
|
415
|
+
function findInheritedMethod(classId, methodName, targetParamTypes, targetParamCount, graph, parentMap, methodMap, parentEdgeType,
|
|
416
|
+
/** Method ID to exclude from results (prevents self-edges when the ancestor
|
|
417
|
+
* method being matched lives on an IMPLEMENTS parent). */
|
|
418
|
+
excludeMethodId) {
|
|
419
|
+
const visited = new Set();
|
|
420
|
+
const queue = [];
|
|
421
|
+
// Seed with direct EXTENDS parents only
|
|
422
|
+
const directParents = parentMap.get(classId) ?? [];
|
|
423
|
+
const directEdges = parentEdgeType.get(classId);
|
|
424
|
+
for (const pid of directParents) {
|
|
425
|
+
const et = directEdges?.get(pid);
|
|
426
|
+
if (et === 'EXTENDS') {
|
|
427
|
+
// Also check that the parent is not an Interface/Trait
|
|
428
|
+
const parentNode = graph.getNode(pid);
|
|
429
|
+
if (parentNode && parentNode.label !== 'Interface' && parentNode.label !== 'Trait') {
|
|
430
|
+
queue.push(pid);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// Level-order BFS: process all ancestors at the current depth before
|
|
435
|
+
// advancing. Once any match is found at depth D, finish that depth and stop.
|
|
436
|
+
// Diamond dedup: same methodId via two paths at the same depth = 1 match.
|
|
437
|
+
let currentLevel = [...queue];
|
|
438
|
+
while (currentLevel.length > 0) {
|
|
439
|
+
const matches = new Map();
|
|
440
|
+
const nextLevel = [];
|
|
441
|
+
for (const ancestorId of currentLevel) {
|
|
442
|
+
if (visited.has(ancestorId))
|
|
443
|
+
continue;
|
|
444
|
+
visited.add(ancestorId);
|
|
445
|
+
// Check this ancestor's methods
|
|
446
|
+
const methods = methodMap.get(ancestorId) ?? [];
|
|
447
|
+
for (const mid of methods) {
|
|
448
|
+
const mNode = graph.getNode(mid);
|
|
449
|
+
if (!mNode || mNode.label === 'Property')
|
|
450
|
+
continue;
|
|
451
|
+
// Abstract inherited methods don't count as concrete implementations
|
|
452
|
+
if (mNode.properties.isAbstract === true)
|
|
453
|
+
continue;
|
|
454
|
+
if (mNode.properties.name !== methodName)
|
|
455
|
+
continue;
|
|
456
|
+
const mParamTypes = mNode.properties.parameterTypes ?? [];
|
|
457
|
+
const mParamCount = mNode.properties.parameterCount;
|
|
458
|
+
const ptResult = parameterTypesMatch(mParamTypes, targetParamTypes, mParamCount, targetParamCount);
|
|
459
|
+
if (ptResult.match) {
|
|
460
|
+
matches.set(mid, {
|
|
461
|
+
methodId: mid,
|
|
462
|
+
parameterTypes: mParamTypes,
|
|
463
|
+
confident: ptResult.confident,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// Collect EXTENDS parents for the next depth level
|
|
468
|
+
const grandparents = parentMap.get(ancestorId) ?? [];
|
|
469
|
+
const ancestorEdges = parentEdgeType.get(ancestorId);
|
|
470
|
+
for (const gp of grandparents) {
|
|
471
|
+
if (visited.has(gp))
|
|
472
|
+
continue;
|
|
473
|
+
const gpEdge = ancestorEdges?.get(gp);
|
|
474
|
+
if (gpEdge === 'EXTENDS') {
|
|
475
|
+
const gpNode = graph.getNode(gp);
|
|
476
|
+
if (gpNode && gpNode.label !== 'Interface' && gpNode.label !== 'Trait') {
|
|
477
|
+
nextLevel.push(gp);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// If any matches found at this depth, decide and stop
|
|
483
|
+
if (matches.size === 1)
|
|
484
|
+
return matches.values().next().value;
|
|
485
|
+
if (matches.size > 1)
|
|
486
|
+
return null; // ambiguous at same depth
|
|
487
|
+
currentLevel = nextLevel;
|
|
488
|
+
}
|
|
489
|
+
// ── Second pass: walk IMPLEMENTS parents AND their interface ancestry ──
|
|
490
|
+
// Only reached when the EXTENDS chain yielded no match.
|
|
491
|
+
// BFS through interface/trait hierarchy to find default (non-abstract) methods.
|
|
492
|
+
const implBfsQueue = [];
|
|
493
|
+
for (const pid of directParents) {
|
|
494
|
+
const et = directEdges?.get(pid);
|
|
495
|
+
if (et === 'IMPLEMENTS') {
|
|
496
|
+
implBfsQueue.push(pid);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Collect all matches from the IMPLEMENTS BFS — return null if ambiguous (>1 match)
|
|
500
|
+
const implMatches = [];
|
|
501
|
+
const implVisited = new Set();
|
|
502
|
+
while (implBfsQueue.length > 0) {
|
|
503
|
+
const ifaceId = implBfsQueue.shift();
|
|
504
|
+
if (implVisited.has(ifaceId))
|
|
505
|
+
continue;
|
|
506
|
+
implVisited.add(ifaceId);
|
|
507
|
+
// Only process Interface/Trait nodes — Dart `implements Class` does not
|
|
508
|
+
// inherit method bodies, so Class/Struct/Enum parents must be skipped.
|
|
509
|
+
const ifaceNode = graph.getNode(ifaceId);
|
|
510
|
+
if (!ifaceNode || (ifaceNode.label !== 'Interface' && ifaceNode.label !== 'Trait'))
|
|
511
|
+
continue;
|
|
512
|
+
// Check this interface/trait's methods for a non-abstract default
|
|
513
|
+
const methods = methodMap.get(ifaceId) ?? [];
|
|
514
|
+
for (const mid of methods) {
|
|
515
|
+
if (mid === excludeMethodId)
|
|
516
|
+
continue; // prevent self-edges
|
|
517
|
+
const mNode = graph.getNode(mid);
|
|
518
|
+
if (!mNode || mNode.label === 'Property')
|
|
519
|
+
continue;
|
|
520
|
+
if (mNode.properties.isAbstract === true)
|
|
521
|
+
continue;
|
|
522
|
+
if (mNode.properties.name !== methodName)
|
|
523
|
+
continue;
|
|
524
|
+
const mParamTypes = mNode.properties.parameterTypes ?? [];
|
|
525
|
+
const mParamCount = mNode.properties.parameterCount;
|
|
526
|
+
const ptResult = parameterTypesMatch(mParamTypes, targetParamTypes, mParamCount, targetParamCount);
|
|
527
|
+
if (ptResult.match) {
|
|
528
|
+
implMatches.push({
|
|
529
|
+
methodId: mid,
|
|
530
|
+
parameterTypes: mParamTypes,
|
|
531
|
+
confident: ptResult.confident,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// Walk this interface's parents (interface-extends-interface chains)
|
|
536
|
+
const ifaceParents = parentMap.get(ifaceId) ?? [];
|
|
537
|
+
for (const gp of ifaceParents) {
|
|
538
|
+
if (!implVisited.has(gp))
|
|
539
|
+
implBfsQueue.push(gp);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Ambiguous: multiple interfaces provide the same default method
|
|
543
|
+
if (implMatches.length === 1)
|
|
544
|
+
return implMatches[0];
|
|
545
|
+
return null; // 0 matches or ambiguous (>1)
|
|
342
546
|
}
|
|
343
547
|
/**
|
|
344
548
|
* Build transitive edge types for a class using BFS from the class to all ancestors.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { KnowledgeGraph } from '../graph/types.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { SymbolTableWriter } from './model/symbol-table.js';
|
|
3
3
|
import { ASTCache } from './ast-cache.js';
|
|
4
4
|
import { WorkerPool } from './workers/worker-pool.js';
|
|
5
|
-
import type { ExtractedImport, ExtractedCall, ExtractedAssignment,
|
|
5
|
+
import type { ExtractedImport, ExtractedCall, ExtractedAssignment, ExtractedRoute, ExtractedFetchCall, ExtractedDecoratorRoute, ExtractedToolDef, FileConstructorBindings, FileScopeBindings, ExtractedORMQuery } from './workers/parse-worker.js';
|
|
6
|
+
import type { ExtractedHeritage } from './model/heritage-map.js';
|
|
6
7
|
export type FileProgressCallback = (current: number, total: number, filePath: string) => void;
|
|
7
8
|
export interface WorkerExtractedData {
|
|
8
9
|
imports: ExtractedImport[];
|
|
@@ -15,9 +16,9 @@ export interface WorkerExtractedData {
|
|
|
15
16
|
toolDefs: ExtractedToolDef[];
|
|
16
17
|
ormQueries: ExtractedORMQuery[];
|
|
17
18
|
constructorBindings: FileConstructorBindings[];
|
|
18
|
-
|
|
19
|
+
fileScopeBindings: FileScopeBindings[];
|
|
19
20
|
}
|
|
20
21
|
export declare const processParsing: (graph: KnowledgeGraph, files: {
|
|
21
22
|
path: string;
|
|
22
23
|
content: string;
|
|
23
|
-
}[], symbolTable:
|
|
24
|
+
}[], symbolTable: SymbolTableWriter, astCache: ASTCache, onFileProgress?: FileProgressCallback, workerPool?: WorkerPool) => Promise<WorkerExtractedData | null>;
|