code-graph-context 2.4.4 → 2.5.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 +454 -1103
- package/dist/cli/cli.js +0 -0
- package/dist/mcp/constants.js +274 -0
- package/dist/mcp/handlers/swarm-worker.handler.js +251 -0
- package/dist/mcp/handlers/task-decomposition.handler.js +294 -0
- package/dist/mcp/tools/index.js +13 -1
- package/dist/mcp/tools/swarm-claim-task.tool.js +331 -0
- package/dist/mcp/tools/swarm-complete-task.tool.js +421 -0
- package/dist/mcp/tools/swarm-constants.js +113 -0
- package/dist/mcp/tools/swarm-get-tasks.tool.js +419 -0
- package/dist/mcp/tools/swarm-orchestrate.tool.js +389 -0
- package/dist/mcp/tools/swarm-post-task.tool.js +220 -0
- package/package.json +2 -1
- package/dist/core/config/graph-v2.js +0 -1595
- package/dist/core/parsers/typescript-parser-v2.js +0 -590
- package/dist/core/utils/edge-factory.js +0 -37
- package/dist/mcp/handlers/file-change-detection.js +0 -105
- package/dist/mcp/services.js +0 -79
- package/dist/mcp/workers/parse-worker.js +0 -198
- package/dist/mcp/workers/worker.pool.js +0 -54
- package/dist/parsers/cypher-result.parser.js +0 -44
- package/dist/utils/file-utils.js +0 -20
- package/dist/utils/test.js +0 -19
|
@@ -1,590 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { minimatch } from 'minimatch';
|
|
4
|
-
import { Project, Node } from 'ts-morph';
|
|
5
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
-
import { CoreNodeType, CORE_TYPESCRIPT_SCHEMA, DEFAULT_PARSE_OPTIONS, NESTJS_FRAMEWORK_SCHEMA, CoreEdgeType, } from '../config/graph-v2.js';
|
|
7
|
-
export class TypeScriptParser {
|
|
8
|
-
workspacePath;
|
|
9
|
-
tsConfigPath;
|
|
10
|
-
project;
|
|
11
|
-
coreSchema;
|
|
12
|
-
parseConfig;
|
|
13
|
-
frameworkSchemas;
|
|
14
|
-
parsedNodes = new Map();
|
|
15
|
-
parsedEdges = new Map();
|
|
16
|
-
sharedContext = new Map(); // Shared context for custom data
|
|
17
|
-
constructor(workspacePath, tsConfigPath = 'tsconfig.json', coreSchema = CORE_TYPESCRIPT_SCHEMA, frameworkSchemas = [NESTJS_FRAMEWORK_SCHEMA], parseConfig = DEFAULT_PARSE_OPTIONS) {
|
|
18
|
-
this.workspacePath = workspacePath;
|
|
19
|
-
this.tsConfigPath = tsConfigPath;
|
|
20
|
-
this.coreSchema = coreSchema;
|
|
21
|
-
this.frameworkSchemas = frameworkSchemas;
|
|
22
|
-
this.parseConfig = parseConfig;
|
|
23
|
-
// Initialize with proper compiler options for NestJS
|
|
24
|
-
this.project = new Project({
|
|
25
|
-
tsConfigFilePath: tsConfigPath,
|
|
26
|
-
skipAddingFilesFromTsConfig: false,
|
|
27
|
-
compilerOptions: {
|
|
28
|
-
experimentalDecorators: true,
|
|
29
|
-
emitDecoratorMetadata: true,
|
|
30
|
-
target: 7,
|
|
31
|
-
module: 1,
|
|
32
|
-
esModuleInterop: true,
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
this.project.addSourceFilesAtPaths(path.join(workspacePath, '**/*.ts'));
|
|
36
|
-
}
|
|
37
|
-
async parseWorkspace() {
|
|
38
|
-
console.log('added', this.project.getSourceFiles().length, 'files to project');
|
|
39
|
-
console.log('🚀 Starting workspace parsing v2...');
|
|
40
|
-
const sourceFiles = this.project.getSourceFiles();
|
|
41
|
-
console.log(`📁 Found ${sourceFiles.length} TypeScript files`);
|
|
42
|
-
// Phase 1: Core parsing for ALL files
|
|
43
|
-
for (const sourceFile of sourceFiles) {
|
|
44
|
-
if (this.shouldSkipFile(sourceFile))
|
|
45
|
-
continue;
|
|
46
|
-
console.log(`📄 Parsing file: ${sourceFile.getFilePath()}`);
|
|
47
|
-
await this.parseCoreTypeScript(sourceFile);
|
|
48
|
-
}
|
|
49
|
-
// Phase 2: Apply context extractors
|
|
50
|
-
console.log('🔧 Applying context extractors...');
|
|
51
|
-
await this.applyContextExtractors();
|
|
52
|
-
// Phase 3: Framework enhancements
|
|
53
|
-
if (this.frameworkSchemas.length > 0) {
|
|
54
|
-
console.log('🎯 Applying framework enhancements...');
|
|
55
|
-
await this.applyFrameworkEnhancements();
|
|
56
|
-
}
|
|
57
|
-
// Phase 4: Edge enhancements
|
|
58
|
-
console.log('🔗 Applying edge enhancements...');
|
|
59
|
-
await this.applyEdgeEnhancements();
|
|
60
|
-
console.log(`✅ Parsing complete: ${this.parsedNodes.size} nodes, ${this.parsedEdges.size} edges`);
|
|
61
|
-
// Convert to Neo4j format
|
|
62
|
-
const neo4jNodes = Array.from(this.parsedNodes.values()).map(this.toNeo4jNode);
|
|
63
|
-
const neo4jEdges = Array.from(this.parsedEdges.values()).map(this.toNeo4jEdge);
|
|
64
|
-
return { nodes: neo4jNodes, edges: neo4jEdges };
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Check if variable declarations should be parsed for this file
|
|
68
|
-
* based on framework schema configurations
|
|
69
|
-
*/
|
|
70
|
-
shouldParseVariables(filePath) {
|
|
71
|
-
for (const schema of this.frameworkSchemas) {
|
|
72
|
-
const parsePatterns = schema.metadata.parseVariablesFrom;
|
|
73
|
-
if (parsePatterns) {
|
|
74
|
-
for (const pattern of parsePatterns) {
|
|
75
|
-
if (minimatch(filePath, pattern)) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
async parseCoreTypeScript(sourceFile) {
|
|
84
|
-
try {
|
|
85
|
-
// Create source file node
|
|
86
|
-
const sourceFileNode = this.createCoreNode(sourceFile, CoreNodeType.SOURCE_FILE);
|
|
87
|
-
this.addNode(sourceFileNode);
|
|
88
|
-
// Parse classes
|
|
89
|
-
for (const classDecl of sourceFile.getClasses()) {
|
|
90
|
-
const classNode = this.createCoreNode(classDecl, CoreNodeType.CLASS_DECLARATION);
|
|
91
|
-
this.addNode(classNode);
|
|
92
|
-
// File contains class relationship
|
|
93
|
-
const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, classNode.id);
|
|
94
|
-
this.addEdge(containsEdge);
|
|
95
|
-
// Parse class decorators
|
|
96
|
-
for (const decorator of classDecl.getDecorators()) {
|
|
97
|
-
const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
|
|
98
|
-
this.addNode(decoratorNode);
|
|
99
|
-
// Class decorated with decorator relationship
|
|
100
|
-
const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, classNode.id, decoratorNode.id);
|
|
101
|
-
this.addEdge(decoratedEdge);
|
|
102
|
-
}
|
|
103
|
-
// Parse methods
|
|
104
|
-
for (const method of classDecl.getMethods()) {
|
|
105
|
-
const methodNode = this.createCoreNode(method, CoreNodeType.METHOD_DECLARATION);
|
|
106
|
-
this.addNode(methodNode);
|
|
107
|
-
// Class has method relationship
|
|
108
|
-
const hasMethodEdge = this.createCoreEdge(CoreEdgeType.HAS_MEMBER, classNode.id, methodNode.id);
|
|
109
|
-
this.addEdge(hasMethodEdge);
|
|
110
|
-
// Parse method decorators
|
|
111
|
-
for (const decorator of method.getDecorators()) {
|
|
112
|
-
const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
|
|
113
|
-
this.addNode(decoratorNode);
|
|
114
|
-
// Method decorated with decorator relationship
|
|
115
|
-
const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, methodNode.id, decoratorNode.id);
|
|
116
|
-
this.addEdge(decoratedEdge);
|
|
117
|
-
}
|
|
118
|
-
// Parse method parameters
|
|
119
|
-
for (const param of method.getParameters()) {
|
|
120
|
-
const paramNode = this.createCoreNode(param, CoreNodeType.PARAMETER_DECLARATION);
|
|
121
|
-
this.addNode(paramNode);
|
|
122
|
-
// Method has parameter relationship
|
|
123
|
-
const hasParamEdge = this.createCoreEdge(CoreEdgeType.HAS_PARAMETER, methodNode.id, paramNode.id);
|
|
124
|
-
this.addEdge(hasParamEdge);
|
|
125
|
-
// Parse parameter decorators
|
|
126
|
-
for (const decorator of param.getDecorators()) {
|
|
127
|
-
const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
|
|
128
|
-
this.addNode(decoratorNode);
|
|
129
|
-
// Parameter decorated with decorator relationship
|
|
130
|
-
const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, paramNode.id, decoratorNode.id);
|
|
131
|
-
this.addEdge(decoratedEdge);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Parse properties
|
|
136
|
-
for (const property of classDecl.getProperties()) {
|
|
137
|
-
const propertyNode = this.createCoreNode(property, CoreNodeType.PROPERTY_DECLARATION);
|
|
138
|
-
this.addNode(propertyNode);
|
|
139
|
-
// Class has property relationship
|
|
140
|
-
const hasPropertyEdge = this.createCoreEdge(CoreEdgeType.HAS_MEMBER, classNode.id, propertyNode.id);
|
|
141
|
-
this.addEdge(hasPropertyEdge);
|
|
142
|
-
// Parse property decorators
|
|
143
|
-
for (const decorator of property.getDecorators()) {
|
|
144
|
-
const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
|
|
145
|
-
this.addNode(decoratorNode);
|
|
146
|
-
// Property decorated with decorator relationship
|
|
147
|
-
const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, propertyNode.id, decoratorNode.id);
|
|
148
|
-
this.addEdge(decoratedEdge);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// Parse interfaces
|
|
153
|
-
for (const interfaceDecl of sourceFile.getInterfaces()) {
|
|
154
|
-
const interfaceNode = this.createCoreNode(interfaceDecl, CoreNodeType.INTERFACE_DECLARATION);
|
|
155
|
-
this.addNode(interfaceNode);
|
|
156
|
-
// File contains interface relationship
|
|
157
|
-
const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, interfaceNode.id);
|
|
158
|
-
this.addEdge(containsEdge);
|
|
159
|
-
}
|
|
160
|
-
// Parse functions
|
|
161
|
-
for (const funcDecl of sourceFile.getFunctions()) {
|
|
162
|
-
const functionNode = this.createCoreNode(funcDecl, CoreNodeType.FUNCTION_DECLARATION);
|
|
163
|
-
this.addNode(functionNode);
|
|
164
|
-
// File contains function relationship
|
|
165
|
-
const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, functionNode.id);
|
|
166
|
-
this.addEdge(containsEdge);
|
|
167
|
-
// Parse function parameters
|
|
168
|
-
for (const param of funcDecl.getParameters()) {
|
|
169
|
-
const paramNode = this.createCoreNode(param, CoreNodeType.PARAMETER_DECLARATION);
|
|
170
|
-
this.addNode(paramNode);
|
|
171
|
-
// Function has parameter relationship
|
|
172
|
-
const hasParamEdge = this.createCoreEdge(CoreEdgeType.HAS_PARAMETER, functionNode.id, paramNode.id);
|
|
173
|
-
this.addEdge(hasParamEdge);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Parse imports
|
|
177
|
-
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
178
|
-
const importNode = this.createCoreNode(importDecl, CoreNodeType.IMPORT_DECLARATION);
|
|
179
|
-
this.addNode(importNode);
|
|
180
|
-
// File contains import relationship
|
|
181
|
-
const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, importNode.id);
|
|
182
|
-
this.addEdge(containsEdge);
|
|
183
|
-
}
|
|
184
|
-
// Parse variable declarations if framework schema specifies this file should have them parsed
|
|
185
|
-
if (this.shouldParseVariables(sourceFile.getFilePath())) {
|
|
186
|
-
for (const varStatement of sourceFile.getVariableStatements()) {
|
|
187
|
-
for (const varDecl of varStatement.getDeclarations()) {
|
|
188
|
-
const variableNode = this.createCoreNode(varDecl, CoreNodeType.VARIABLE_DECLARATION);
|
|
189
|
-
this.addNode(variableNode);
|
|
190
|
-
// File contains variable relationship
|
|
191
|
-
const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, variableNode.id);
|
|
192
|
-
this.addEdge(containsEdge);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
console.error(`Error parsing file ${sourceFile.getFilePath()}:`, error);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
createCoreNode(astNode, coreType) {
|
|
202
|
-
const nodeId = `${coreType}:${uuidv4()}`;
|
|
203
|
-
// Extract base properties using schema
|
|
204
|
-
const properties = {
|
|
205
|
-
id: nodeId,
|
|
206
|
-
name: this.extractNodeName(astNode, coreType),
|
|
207
|
-
coreType,
|
|
208
|
-
filePath: astNode.getSourceFile().getFilePath(),
|
|
209
|
-
startLine: astNode.getStartLineNumber(),
|
|
210
|
-
endLine: astNode.getEndLineNumber(),
|
|
211
|
-
sourceCode: astNode.getText(),
|
|
212
|
-
createdAt: new Date().toISOString(),
|
|
213
|
-
};
|
|
214
|
-
// Extract schema-defined properties
|
|
215
|
-
const coreNodeDef = this.coreSchema.nodeTypes[coreType];
|
|
216
|
-
if (coreNodeDef) {
|
|
217
|
-
for (const propDef of coreNodeDef.properties) {
|
|
218
|
-
try {
|
|
219
|
-
const value = this.extractProperty(astNode, propDef);
|
|
220
|
-
if (value !== undefined && propDef.name !== 'context') {
|
|
221
|
-
properties[propDef.name] = value;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
catch (error) {
|
|
225
|
-
console.warn(`Failed to extract core property ${propDef.name}:`, error);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
id: nodeId,
|
|
231
|
-
coreType,
|
|
232
|
-
labels: [...(coreNodeDef?.neo4j.labels || [])],
|
|
233
|
-
properties,
|
|
234
|
-
sourceNode: astNode,
|
|
235
|
-
skipEmbedding: coreNodeDef?.neo4j.skipEmbedding ?? false,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
async applyContextExtractors() {
|
|
239
|
-
console.log('🔧 Applying context extractors...');
|
|
240
|
-
// Apply global context extractors from framework schemas
|
|
241
|
-
for (const frameworkSchema of this.frameworkSchemas) {
|
|
242
|
-
for (const extractor of frameworkSchema.contextExtractors) {
|
|
243
|
-
await this.applyContextExtractor(extractor);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
async applyContextExtractor(extractor) {
|
|
248
|
-
for (const [nodeId, node] of this.parsedNodes) {
|
|
249
|
-
// Check if this extractor applies to this node
|
|
250
|
-
if (node.coreType !== extractor.nodeType)
|
|
251
|
-
continue;
|
|
252
|
-
if (extractor.semanticType && node.semanticType !== extractor.semanticType)
|
|
253
|
-
continue;
|
|
254
|
-
try {
|
|
255
|
-
const context = extractor.extractor(node, this.parsedNodes, this.sharedContext);
|
|
256
|
-
if (context && Object.keys(context).length > 0) {
|
|
257
|
-
// Merge context into node properties
|
|
258
|
-
node.properties.context ??= {};
|
|
259
|
-
Object.assign(node.properties.context, context);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
console.warn(`Failed to apply context extractor for ${nodeId}:`, error);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
createCoreEdge(relationshipType, sourceNodeId, targetNodeId) {
|
|
268
|
-
return {
|
|
269
|
-
id: `${relationshipType}:${uuidv4()}`,
|
|
270
|
-
relationshipType,
|
|
271
|
-
sourceNodeId,
|
|
272
|
-
targetNodeId,
|
|
273
|
-
properties: {
|
|
274
|
-
coreType: relationshipType,
|
|
275
|
-
source: 'ast',
|
|
276
|
-
confidence: 1.0,
|
|
277
|
-
filePath: '',
|
|
278
|
-
createdAt: new Date().toISOString(),
|
|
279
|
-
},
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
async applyFrameworkEnhancements() {
|
|
283
|
-
console.log('🎯 Starting framework enhancements...');
|
|
284
|
-
for (const frameworkSchema of this.frameworkSchemas) {
|
|
285
|
-
console.log(`📦 Applying framework schema: ${frameworkSchema.name}`);
|
|
286
|
-
await this.applyFrameworkSchema(frameworkSchema);
|
|
287
|
-
}
|
|
288
|
-
console.log('✅ Framework enhancements complete');
|
|
289
|
-
}
|
|
290
|
-
async applyFrameworkSchema(schema) {
|
|
291
|
-
// Sort enhancements by priority (highest first)
|
|
292
|
-
const sortedEnhancements = Object.values(schema.enhancements).sort((a, b) => b.priority - a.priority);
|
|
293
|
-
for (const [nodeId, coreNode] of this.parsedNodes) {
|
|
294
|
-
// Find applicable enhancements for this core node type
|
|
295
|
-
const applicableEnhancements = sortedEnhancements.filter((enhancement) => enhancement.targetCoreType === coreNode.coreType);
|
|
296
|
-
for (const enhancement of applicableEnhancements) {
|
|
297
|
-
if (this.matchesDetectionPatterns(coreNode, enhancement.detectionPatterns)) {
|
|
298
|
-
// Enhance the node!
|
|
299
|
-
this.enhanceNode(coreNode, enhancement);
|
|
300
|
-
break; // First match wins (highest priority)
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
matchesDetectionPatterns(node, patterns) {
|
|
306
|
-
return patterns.some((pattern) => {
|
|
307
|
-
try {
|
|
308
|
-
switch (pattern.type) {
|
|
309
|
-
case 'decorator':
|
|
310
|
-
return this.hasMatchingDecorator(node, pattern.pattern);
|
|
311
|
-
case 'filename':
|
|
312
|
-
if (pattern.pattern instanceof RegExp) {
|
|
313
|
-
return pattern.pattern.test(node.properties.filePath);
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
return node.properties.filePath.includes(pattern.pattern);
|
|
317
|
-
}
|
|
318
|
-
case 'function':
|
|
319
|
-
if (typeof pattern.pattern === 'function') {
|
|
320
|
-
return pattern.pattern(node);
|
|
321
|
-
}
|
|
322
|
-
return false;
|
|
323
|
-
case 'classname':
|
|
324
|
-
if (pattern.pattern instanceof RegExp) {
|
|
325
|
-
return pattern.pattern.test(node.properties.name);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
return node.properties.name.includes(pattern.pattern);
|
|
329
|
-
}
|
|
330
|
-
default:
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
catch (error) {
|
|
335
|
-
console.warn(`Error matching detection pattern:`, error);
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
hasMatchingDecorator(node, decoratorName) {
|
|
341
|
-
try {
|
|
342
|
-
const context = node.properties.context;
|
|
343
|
-
const decoratorNames = context?.decoratorNames;
|
|
344
|
-
return decoratorNames?.includes(decoratorName) || false;
|
|
345
|
-
}
|
|
346
|
-
catch (error) {
|
|
347
|
-
console.warn(`Error checking decorator ${decoratorName}:`, error);
|
|
348
|
-
return false;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
enhanceNode(coreNode, enhancement) {
|
|
352
|
-
try {
|
|
353
|
-
// Set semantic type (single, not array)
|
|
354
|
-
coreNode.semanticType = enhancement.semanticType;
|
|
355
|
-
coreNode.properties.semanticType = enhancement.semanticType;
|
|
356
|
-
// Add framework labels
|
|
357
|
-
enhancement.neo4j.additionalLabels.forEach((label) => {
|
|
358
|
-
if (!coreNode.labels.includes(label)) {
|
|
359
|
-
coreNode.labels.unshift(label);
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
// Override primary label if specified
|
|
363
|
-
if (enhancement.neo4j.primaryLabel) {
|
|
364
|
-
const oldPrimaryIndex = coreNode.labels.findIndex((label) => label === enhancement.neo4j.primaryLabel);
|
|
365
|
-
if (oldPrimaryIndex > -1) {
|
|
366
|
-
coreNode.labels.splice(oldPrimaryIndex, 1);
|
|
367
|
-
}
|
|
368
|
-
coreNode.labels.unshift(enhancement.neo4j.primaryLabel);
|
|
369
|
-
}
|
|
370
|
-
// Apply context extractors specific to this enhancement
|
|
371
|
-
for (const extractor of enhancement.contextExtractors) {
|
|
372
|
-
try {
|
|
373
|
-
const context = extractor.extractor(coreNode, this.parsedNodes, this.sharedContext);
|
|
374
|
-
if (context && Object.keys(context).length > 0) {
|
|
375
|
-
coreNode.properties.context ??= {};
|
|
376
|
-
Object.assign(coreNode.properties.context, context);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
console.warn(`Failed to apply enhancement context extractor:`, error);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
catch (error) {
|
|
385
|
-
console.error(`Error enhancing node ${coreNode.id}:`, error);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
async applyEdgeEnhancements() {
|
|
389
|
-
console.log('🔗 Applying edge enhancements...');
|
|
390
|
-
for (const frameworkSchema of this.frameworkSchemas) {
|
|
391
|
-
for (const edgeEnhancement of Object.values(frameworkSchema.edgeEnhancements)) {
|
|
392
|
-
await this.applyEdgeEnhancement(edgeEnhancement);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
async applyEdgeEnhancement(edgeEnhancement) {
|
|
397
|
-
try {
|
|
398
|
-
for (const [sourceId, sourceNode] of this.parsedNodes) {
|
|
399
|
-
for (const [targetId, targetNode] of this.parsedNodes) {
|
|
400
|
-
if (sourceId === targetId)
|
|
401
|
-
continue;
|
|
402
|
-
if (edgeEnhancement.detectionPattern(sourceNode, targetNode, this.parsedNodes, this.sharedContext)) {
|
|
403
|
-
// Extract context for this edge
|
|
404
|
-
let context = {};
|
|
405
|
-
if (edgeEnhancement.contextExtractor) {
|
|
406
|
-
context = edgeEnhancement.contextExtractor(sourceNode, targetNode, this.parsedNodes, this.sharedContext);
|
|
407
|
-
}
|
|
408
|
-
const edge = this.createFrameworkEdge(edgeEnhancement.semanticType, edgeEnhancement.neo4j.relationshipType, sourceId, targetId, context);
|
|
409
|
-
this.addEdge(edge);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
console.error(`Error applying edge enhancement ${edgeEnhancement.name}:`, error);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
createFrameworkEdge(semanticType, relationshipType, sourceNodeId, targetNodeId, context = {}) {
|
|
419
|
-
const edgeId = `${semanticType}:${uuidv4()}`;
|
|
420
|
-
const properties = {
|
|
421
|
-
coreType: semanticType, // This might need adjustment based on schema
|
|
422
|
-
semanticType,
|
|
423
|
-
source: 'pattern',
|
|
424
|
-
confidence: 0.8,
|
|
425
|
-
filePath: '',
|
|
426
|
-
createdAt: new Date().toISOString(),
|
|
427
|
-
context,
|
|
428
|
-
};
|
|
429
|
-
return {
|
|
430
|
-
id: edgeId,
|
|
431
|
-
relationshipType,
|
|
432
|
-
sourceNodeId,
|
|
433
|
-
targetNodeId,
|
|
434
|
-
properties,
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
extractProperty(astNode, propDef) {
|
|
438
|
-
const { method, source, defaultValue } = propDef.extraction;
|
|
439
|
-
try {
|
|
440
|
-
switch (method) {
|
|
441
|
-
case 'ast':
|
|
442
|
-
if (typeof source === 'string') {
|
|
443
|
-
const fn = astNode[source];
|
|
444
|
-
return typeof fn === 'function' ? fn.call(astNode) : defaultValue;
|
|
445
|
-
}
|
|
446
|
-
return defaultValue;
|
|
447
|
-
case 'function':
|
|
448
|
-
if (typeof source === 'function') {
|
|
449
|
-
return source(astNode);
|
|
450
|
-
}
|
|
451
|
-
return defaultValue;
|
|
452
|
-
case 'static':
|
|
453
|
-
return defaultValue;
|
|
454
|
-
case 'context':
|
|
455
|
-
// Context properties are handled by context extractors
|
|
456
|
-
return undefined;
|
|
457
|
-
default:
|
|
458
|
-
return defaultValue;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
catch (error) {
|
|
462
|
-
console.warn(`Failed to extract property ${propDef.name}:`, error);
|
|
463
|
-
return defaultValue;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
extractNodeName(astNode, coreType) {
|
|
467
|
-
try {
|
|
468
|
-
switch (coreType) {
|
|
469
|
-
case CoreNodeType.SOURCE_FILE:
|
|
470
|
-
if (Node.isSourceFile(astNode)) {
|
|
471
|
-
return astNode.getBaseName();
|
|
472
|
-
}
|
|
473
|
-
break;
|
|
474
|
-
case CoreNodeType.CLASS_DECLARATION:
|
|
475
|
-
if (Node.isClassDeclaration(astNode)) {
|
|
476
|
-
return astNode.getName() || 'AnonymousClass';
|
|
477
|
-
}
|
|
478
|
-
break;
|
|
479
|
-
case CoreNodeType.METHOD_DECLARATION:
|
|
480
|
-
if (Node.isMethodDeclaration(astNode)) {
|
|
481
|
-
return astNode.getName();
|
|
482
|
-
}
|
|
483
|
-
break;
|
|
484
|
-
case CoreNodeType.FUNCTION_DECLARATION:
|
|
485
|
-
if (Node.isFunctionDeclaration(astNode)) {
|
|
486
|
-
return astNode.getName() || 'AnonymousFunction';
|
|
487
|
-
}
|
|
488
|
-
break;
|
|
489
|
-
case CoreNodeType.INTERFACE_DECLARATION:
|
|
490
|
-
if (Node.isInterfaceDeclaration(astNode)) {
|
|
491
|
-
return astNode.getName();
|
|
492
|
-
}
|
|
493
|
-
break;
|
|
494
|
-
case CoreNodeType.PROPERTY_DECLARATION:
|
|
495
|
-
if (Node.isPropertyDeclaration(astNode)) {
|
|
496
|
-
return astNode.getName();
|
|
497
|
-
}
|
|
498
|
-
break;
|
|
499
|
-
case CoreNodeType.PARAMETER_DECLARATION:
|
|
500
|
-
if (Node.isParameterDeclaration(astNode)) {
|
|
501
|
-
return astNode.getName();
|
|
502
|
-
}
|
|
503
|
-
break;
|
|
504
|
-
case CoreNodeType.IMPORT_DECLARATION:
|
|
505
|
-
if (Node.isImportDeclaration(astNode)) {
|
|
506
|
-
return astNode.getModuleSpecifierValue();
|
|
507
|
-
}
|
|
508
|
-
break;
|
|
509
|
-
case CoreNodeType.DECORATOR:
|
|
510
|
-
if (Node.isDecorator(astNode)) {
|
|
511
|
-
return astNode.getName();
|
|
512
|
-
}
|
|
513
|
-
break;
|
|
514
|
-
default:
|
|
515
|
-
return astNode.getKindName();
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
catch (error) {
|
|
519
|
-
console.warn(`Error extracting name for ${coreType}:`, error);
|
|
520
|
-
}
|
|
521
|
-
return 'Unknown';
|
|
522
|
-
}
|
|
523
|
-
shouldSkipFile(sourceFile) {
|
|
524
|
-
const filePath = sourceFile.getFilePath();
|
|
525
|
-
const excludedPatterns = this.parseConfig.excludePatterns ?? [];
|
|
526
|
-
for (const pattern of excludedPatterns) {
|
|
527
|
-
if (filePath.includes(pattern) || filePath.match(new RegExp(pattern))) {
|
|
528
|
-
console.log(`⏭️ Skipping excluded file: ${filePath}`);
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
toNeo4jNode(parsedNode) {
|
|
535
|
-
return {
|
|
536
|
-
id: parsedNode.id,
|
|
537
|
-
labels: parsedNode.labels,
|
|
538
|
-
properties: parsedNode.properties,
|
|
539
|
-
skipEmbedding: parsedNode.skipEmbedding ?? false,
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
toNeo4jEdge(parsedEdge) {
|
|
543
|
-
return {
|
|
544
|
-
id: parsedEdge.id,
|
|
545
|
-
type: parsedEdge.relationshipType,
|
|
546
|
-
startNodeId: parsedEdge.sourceNodeId,
|
|
547
|
-
endNodeId: parsedEdge.targetNodeId,
|
|
548
|
-
properties: parsedEdge.properties,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
addNode(node) {
|
|
552
|
-
this.parsedNodes.set(node.id, node);
|
|
553
|
-
}
|
|
554
|
-
addEdge(edge) {
|
|
555
|
-
this.parsedEdges.set(edge.id, edge);
|
|
556
|
-
}
|
|
557
|
-
// Helper methods for statistics and debugging
|
|
558
|
-
getStats() {
|
|
559
|
-
const nodesByType = {};
|
|
560
|
-
const nodesBySemanticType = {};
|
|
561
|
-
for (const node of this.parsedNodes.values()) {
|
|
562
|
-
nodesByType[node.coreType] = (nodesByType[node.coreType] || 0) + 1;
|
|
563
|
-
if (node.semanticType) {
|
|
564
|
-
nodesBySemanticType[node.semanticType] = (nodesBySemanticType[node.semanticType] || 0) + 1;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
return {
|
|
568
|
-
totalNodes: this.parsedNodes.size,
|
|
569
|
-
totalEdges: this.parsedEdges.size,
|
|
570
|
-
nodesByType,
|
|
571
|
-
nodesBySemanticType,
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
exportToJson() {
|
|
575
|
-
const nodes = Array.from(this.parsedNodes.values()).map((node) => ({
|
|
576
|
-
id: node.id,
|
|
577
|
-
labels: node.labels,
|
|
578
|
-
properties: node.properties,
|
|
579
|
-
skipEmbedding: node.skipEmbedding ?? false,
|
|
580
|
-
}));
|
|
581
|
-
const edges = Array.from(this.parsedEdges.values()).map((edge) => ({
|
|
582
|
-
id: edge.id,
|
|
583
|
-
type: edge.relationshipType,
|
|
584
|
-
startNodeId: edge.sourceNodeId,
|
|
585
|
-
endNodeId: edge.targetNodeId,
|
|
586
|
-
properties: edge.properties,
|
|
587
|
-
}));
|
|
588
|
-
return { nodes, edges };
|
|
589
|
-
}
|
|
590
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edge Factory
|
|
3
|
-
* Shared utilities for creating framework edges with consistent ID generation and properties
|
|
4
|
-
*/
|
|
5
|
-
import crypto from 'crypto';
|
|
6
|
-
/**
|
|
7
|
-
* Generate a deterministic edge ID based on semantic type, source, and target.
|
|
8
|
-
* Uses SHA256 hash truncated to 16 characters for uniqueness.
|
|
9
|
-
*/
|
|
10
|
-
export const generateFrameworkEdgeId = (semanticType, sourceNodeId, targetNodeId) => {
|
|
11
|
-
const edgeIdentity = `${semanticType}::${sourceNodeId}::${targetNodeId}`;
|
|
12
|
-
const edgeHash = crypto.createHash('sha256').update(edgeIdentity).digest('hex').substring(0, 16);
|
|
13
|
-
return `${semanticType}:${edgeHash}`;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Create framework edge ID and properties.
|
|
17
|
-
* Returns common edge data that can be used to construct either ParsedEdge or Neo4jEdge.
|
|
18
|
-
*
|
|
19
|
-
* @param params - Edge parameters
|
|
20
|
-
* @returns Edge ID and properties object
|
|
21
|
-
*/
|
|
22
|
-
export const createFrameworkEdgeData = (params) => {
|
|
23
|
-
const { semanticType, sourceNodeId, targetNodeId, projectId, context = {}, relationshipWeight = 0.5 } = params;
|
|
24
|
-
const id = generateFrameworkEdgeId(semanticType, sourceNodeId, targetNodeId);
|
|
25
|
-
const properties = {
|
|
26
|
-
coreType: semanticType,
|
|
27
|
-
projectId,
|
|
28
|
-
semanticType,
|
|
29
|
-
source: 'pattern',
|
|
30
|
-
confidence: 0.8,
|
|
31
|
-
relationshipWeight,
|
|
32
|
-
filePath: '',
|
|
33
|
-
createdAt: new Date().toISOString(),
|
|
34
|
-
context,
|
|
35
|
-
};
|
|
36
|
-
return { id, properties };
|
|
37
|
-
};
|