code-graph-context 0.1.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/.env.example +14 -0
- package/LICENSE +21 -0
- package/README.md +870 -0
- package/dist/constants.js +1 -0
- package/dist/core/config/fairsquare-framework-schema.js +832 -0
- package/dist/core/config/graph-v2.js +1595 -0
- package/dist/core/config/nestjs-framework-schema.js +894 -0
- package/dist/core/config/schema.js +799 -0
- package/dist/core/embeddings/embeddings.service.js +26 -0
- package/dist/core/embeddings/natural-language-to-cypher.service.js +148 -0
- package/dist/core/parsers/parser-factory.js +102 -0
- package/dist/core/parsers/typescript-parser-v2.js +590 -0
- package/dist/core/parsers/typescript-parser.js +717 -0
- package/dist/mcp/constants.js +141 -0
- package/dist/mcp/handlers/graph-generator.handler.js +143 -0
- package/dist/mcp/handlers/traversal.handler.js +304 -0
- package/dist/mcp/mcp.server.js +47 -0
- package/dist/mcp/services.js +158 -0
- package/dist/mcp/tools/hello.tool.js +13 -0
- package/dist/mcp/tools/index.js +24 -0
- package/dist/mcp/tools/natural-language-to-cypher.tool.js +59 -0
- package/dist/mcp/tools/parse-typescript-project.tool.js +101 -0
- package/dist/mcp/tools/search-codebase.tool.js +97 -0
- package/dist/mcp/tools/test-neo4j-connection.tool.js +39 -0
- package/dist/mcp/tools/traverse-from-node.tool.js +97 -0
- package/dist/mcp/utils.js +152 -0
- package/dist/parsers/cypher-result.parser.js +44 -0
- package/dist/storage/neo4j/neo4j.service.js +277 -0
- package/dist/utils/test.js +19 -0
- package/package.json +81 -0
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FairSquare Custom Framework Schema
|
|
3
|
+
*
|
|
4
|
+
* Detects custom patterns:
|
|
5
|
+
* - @Injectable([deps]) with dependency array
|
|
6
|
+
* - Controller with HTTP method conventions (get, post, put, delete)
|
|
7
|
+
* - Repository pattern (extends Repository)
|
|
8
|
+
* - Custom permission managers
|
|
9
|
+
*/
|
|
10
|
+
import { Node } from 'ts-morph';
|
|
11
|
+
import { CoreNodeType } from './schema.js';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// FAIRSQUARE SEMANTIC TYPES
|
|
14
|
+
// ============================================================================
|
|
15
|
+
export var FairSquareSemanticNodeType;
|
|
16
|
+
(function (FairSquareSemanticNodeType) {
|
|
17
|
+
// Core FairSquare Types
|
|
18
|
+
FairSquareSemanticNodeType["FS_CONTROLLER"] = "Controller";
|
|
19
|
+
FairSquareSemanticNodeType["FS_SERVICE"] = "Service";
|
|
20
|
+
FairSquareSemanticNodeType["FS_REPOSITORY"] = "Repository";
|
|
21
|
+
FairSquareSemanticNodeType["FS_DAL"] = "DAL";
|
|
22
|
+
FairSquareSemanticNodeType["FS_PERMISSION_MANAGER"] = "PermissionManager";
|
|
23
|
+
FairSquareSemanticNodeType["FS_VENDOR_CLIENT"] = "VendorClient";
|
|
24
|
+
// HTTP & Routing
|
|
25
|
+
FairSquareSemanticNodeType["FS_ROUTE_DEFINITION"] = "RouteDefinition";
|
|
26
|
+
})(FairSquareSemanticNodeType || (FairSquareSemanticNodeType = {}));
|
|
27
|
+
export var FairSquareSemanticEdgeType;
|
|
28
|
+
(function (FairSquareSemanticEdgeType) {
|
|
29
|
+
// Dependency Injection
|
|
30
|
+
FairSquareSemanticEdgeType["FS_INJECTS"] = "INJECTS";
|
|
31
|
+
// Repository Pattern
|
|
32
|
+
FairSquareSemanticEdgeType["FS_REPOSITORY_USES_DAL"] = "USES_DAL";
|
|
33
|
+
// HTTP Routing
|
|
34
|
+
FairSquareSemanticEdgeType["FS_ROUTES_TO"] = "ROUTES_TO";
|
|
35
|
+
FairSquareSemanticEdgeType["FS_ROUTES_TO_HANDLER"] = "ROUTES_TO_HANDLER";
|
|
36
|
+
// Permissions
|
|
37
|
+
FairSquareSemanticEdgeType["FS_PROTECTED_BY"] = "PROTECTED_BY";
|
|
38
|
+
FairSquareSemanticEdgeType["FS_INTERNAL_API_CALL"] = "INTERNAL_API_CALL";
|
|
39
|
+
})(FairSquareSemanticEdgeType || (FairSquareSemanticEdgeType = {}));
|
|
40
|
+
// Common labels used across FairSquare schema
|
|
41
|
+
export var FairSquareLabel;
|
|
42
|
+
(function (FairSquareLabel) {
|
|
43
|
+
FairSquareLabel["FAIRSQUARE"] = "FairSquare";
|
|
44
|
+
FairSquareLabel["BUSINESS_LOGIC"] = "BusinessLogic";
|
|
45
|
+
FairSquareLabel["DATA_ACCESS"] = "DataAccess";
|
|
46
|
+
FairSquareLabel["DATABASE"] = "Database";
|
|
47
|
+
FairSquareLabel["SECURITY"] = "Security";
|
|
48
|
+
FairSquareLabel["EXTERNAL_INTEGRATION"] = "ExternalIntegration";
|
|
49
|
+
FairSquareLabel["HTTP_ENDPOINT"] = "HttpEndpoint";
|
|
50
|
+
})(FairSquareLabel || (FairSquareLabel = {}));
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// CONTEXT EXTRACTORS
|
|
53
|
+
// ============================================================================
|
|
54
|
+
/**
|
|
55
|
+
* Extract Injectable decorator dependencies
|
|
56
|
+
* @Injectable([Dep1, Dep2]) → ['Dep1', 'Dep2']
|
|
57
|
+
*/
|
|
58
|
+
const extractInjectableDependencies = (parsedNode, _allNodes, _sharedContext) => {
|
|
59
|
+
const node = parsedNode.sourceNode;
|
|
60
|
+
if (!node || !Node.isClassDeclaration(node))
|
|
61
|
+
return {};
|
|
62
|
+
const decorators = node.getDecorators();
|
|
63
|
+
for (const decorator of decorators) {
|
|
64
|
+
if (decorator.getName() === 'Injectable') {
|
|
65
|
+
const args = decorator.getArguments();
|
|
66
|
+
if (args.length > 0 && Node.isArrayLiteralExpression(args[0])) {
|
|
67
|
+
const elements = args[0].getElements();
|
|
68
|
+
const dependencies = elements.map((el) => el.getText());
|
|
69
|
+
return {
|
|
70
|
+
dependencies,
|
|
71
|
+
dependencyCount: dependencies.length,
|
|
72
|
+
hasInjection: dependencies.length > 0,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Extract repository DAL dependencies
|
|
81
|
+
* Uses the dependencies array from @Injectable([...]) decorator
|
|
82
|
+
*/
|
|
83
|
+
const extractRepositoryDals = (parsedNode, _allNodes, _sharedContext) => {
|
|
84
|
+
const dependencies = parsedNode.properties.context?.dependencies ?? [];
|
|
85
|
+
// Filter for DAL-related dependencies
|
|
86
|
+
const dalDeps = dependencies.filter((dep) => dep.toLowerCase().includes('dal') || dep.toLowerCase().endsWith('dal'));
|
|
87
|
+
return {
|
|
88
|
+
dals: dalDeps,
|
|
89
|
+
dalCount: dalDeps.length,
|
|
90
|
+
usesDALPattern: dalDeps.length > 0,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Extract permission manager usage
|
|
95
|
+
* Uses the dependencies array from @Injectable([...]) decorator
|
|
96
|
+
*/
|
|
97
|
+
const extractPermissionManager = (parsedNode, _allNodes, _sharedContext) => {
|
|
98
|
+
const dependencies = parsedNode.properties.context?.dependencies ?? [];
|
|
99
|
+
// Find permission manager dependency
|
|
100
|
+
const permissionDep = dependencies.find((dep) => dep.toLowerCase().includes('permission'));
|
|
101
|
+
if (permissionDep) {
|
|
102
|
+
return {
|
|
103
|
+
permissionManager: permissionDep,
|
|
104
|
+
hasPermissionManager: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {};
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Extract monorepo project name from file path
|
|
111
|
+
* Supports patterns like: packages/project-name/..., apps/project-name/...
|
|
112
|
+
*/
|
|
113
|
+
const extractMonorepoProject = (parsedNode, _allNodes, _sharedContext) => {
|
|
114
|
+
const node = parsedNode.sourceNode;
|
|
115
|
+
if (!node || !Node.isClassDeclaration(node))
|
|
116
|
+
return {};
|
|
117
|
+
const filePath = node.getSourceFile().getFilePath();
|
|
118
|
+
// Match common monorepo patterns
|
|
119
|
+
const monorepoPatterns = [
|
|
120
|
+
/\/packages\/([^/]+)\//,
|
|
121
|
+
/\/apps\/([^/]+)\//,
|
|
122
|
+
/\/libs\/([^/]+)\//,
|
|
123
|
+
/\/components\/([^/]+)\//,
|
|
124
|
+
];
|
|
125
|
+
for (const pattern of monorepoPatterns) {
|
|
126
|
+
const match = filePath.match(pattern);
|
|
127
|
+
if (match?.[1]) {
|
|
128
|
+
return {
|
|
129
|
+
monorepoProject: match[1],
|
|
130
|
+
isMonorepoPackage: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {};
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Extract route definitions from .routes.ts files
|
|
138
|
+
* Parses ModuleRoute[] arrays to get explicit route mappings
|
|
139
|
+
*/
|
|
140
|
+
const extractRouteDefinitions = (parsedNode, _allNodes, _sharedContext) => {
|
|
141
|
+
const node = parsedNode.sourceNode;
|
|
142
|
+
if (!node || !Node.isVariableDeclaration(node))
|
|
143
|
+
return {};
|
|
144
|
+
// Get the initializer (the array)
|
|
145
|
+
const initializer = node.getInitializer();
|
|
146
|
+
if (!initializer || !Node.isArrayLiteralExpression(initializer)) {
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
const routes = [];
|
|
150
|
+
// Loop through each object in the array
|
|
151
|
+
for (const element of initializer.getElements()) {
|
|
152
|
+
if (!Node.isObjectLiteralExpression(element))
|
|
153
|
+
continue;
|
|
154
|
+
const routeData = {};
|
|
155
|
+
// Extract each property from the route object
|
|
156
|
+
for (const prop of element.getProperties()) {
|
|
157
|
+
if (!Node.isPropertyAssignment(prop))
|
|
158
|
+
continue;
|
|
159
|
+
const propName = prop.getName();
|
|
160
|
+
const propValue = prop.getInitializer();
|
|
161
|
+
if (!propValue)
|
|
162
|
+
continue;
|
|
163
|
+
// Extract based on property type
|
|
164
|
+
switch (propName) {
|
|
165
|
+
case 'method':
|
|
166
|
+
case 'path':
|
|
167
|
+
case 'handler':
|
|
168
|
+
// String values
|
|
169
|
+
routeData[propName] = propValue.getText().replace(/['"]/g, '');
|
|
170
|
+
break;
|
|
171
|
+
case 'authenticated':
|
|
172
|
+
// Boolean value
|
|
173
|
+
routeData[propName] = propValue.getText() === 'true';
|
|
174
|
+
break;
|
|
175
|
+
case 'controller':
|
|
176
|
+
// Identifier (class reference)
|
|
177
|
+
routeData.controllerName = propValue.getText();
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Only add if we got meaningful data
|
|
182
|
+
if (routeData.method && routeData.path) {
|
|
183
|
+
routes.push(routeData);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
routes,
|
|
188
|
+
routeCount: routes.length,
|
|
189
|
+
isRouteFile: true,
|
|
190
|
+
fileName: node.getSourceFile().getBaseName(),
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// FRAMEWORK ENHANCEMENTS
|
|
195
|
+
// ============================================================================
|
|
196
|
+
export const FAIRSQUARE_FRAMEWORK_SCHEMA = {
|
|
197
|
+
name: 'FairSquare Custom Framework',
|
|
198
|
+
version: '1.0.0',
|
|
199
|
+
description: 'Custom FairSquare dependency injection and repository patterns',
|
|
200
|
+
enhances: [CoreNodeType.CLASS_DECLARATION, CoreNodeType.METHOD_DECLARATION],
|
|
201
|
+
metadata: {
|
|
202
|
+
targetLanguages: ['typescript'],
|
|
203
|
+
dependencies: ['@fairsquare/core', '@fairsquare/server'],
|
|
204
|
+
parseVariablesFrom: ['**/*.routes.ts', '**/*.route.ts'],
|
|
205
|
+
},
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// GLOBAL CONTEXT EXTRACTORS (run on all nodes)
|
|
208
|
+
// ============================================================================
|
|
209
|
+
contextExtractors: [
|
|
210
|
+
{
|
|
211
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
212
|
+
extractor: extractInjectableDependencies,
|
|
213
|
+
priority: 10,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// NODE ENHANCEMENTS
|
|
218
|
+
// ============================================================================
|
|
219
|
+
enhancements: {
|
|
220
|
+
// FairSquare Controller
|
|
221
|
+
fairsquareController: {
|
|
222
|
+
name: 'FairSquare Controller',
|
|
223
|
+
targetCoreType: CoreNodeType.CLASS_DECLARATION,
|
|
224
|
+
semanticType: FairSquareSemanticNodeType.FS_CONTROLLER,
|
|
225
|
+
priority: 100,
|
|
226
|
+
detectionPatterns: [
|
|
227
|
+
{
|
|
228
|
+
type: 'classname',
|
|
229
|
+
pattern: /Controller$/,
|
|
230
|
+
confidence: 0.7,
|
|
231
|
+
priority: 5,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
type: 'function',
|
|
235
|
+
pattern: (parsedNode) => {
|
|
236
|
+
const node = parsedNode.sourceNode;
|
|
237
|
+
if (!node || !Node.isClassDeclaration(node))
|
|
238
|
+
return false;
|
|
239
|
+
const baseClass = node.getExtends();
|
|
240
|
+
const result = baseClass?.getText() === 'Controller';
|
|
241
|
+
return result;
|
|
242
|
+
},
|
|
243
|
+
confidence: 1.0,
|
|
244
|
+
priority: 10,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
contextExtractors: [
|
|
248
|
+
{
|
|
249
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
250
|
+
extractor: extractInjectableDependencies,
|
|
251
|
+
priority: 10,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
255
|
+
extractor: extractPermissionManager,
|
|
256
|
+
priority: 8,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
260
|
+
extractor: (parsedNode, allNodes, sharedContext) => {
|
|
261
|
+
// Store vendor controllers in shared context for later lookup
|
|
262
|
+
const controllerName = parsedNode.properties.name;
|
|
263
|
+
// Check if this is a vendor controller
|
|
264
|
+
if (controllerName.includes('Vendor') || parsedNode.properties.filePath.includes('modules/vendor')) {
|
|
265
|
+
// Extract vendor name: ExperianVendorController → experian
|
|
266
|
+
let vendorName = '';
|
|
267
|
+
if (controllerName.endsWith('VendorController')) {
|
|
268
|
+
vendorName = controllerName.replace('VendorController', '').toLowerCase();
|
|
269
|
+
}
|
|
270
|
+
else if (controllerName.endsWith('Controller')) {
|
|
271
|
+
vendorName = controllerName.replace('Controller', '').toLowerCase();
|
|
272
|
+
}
|
|
273
|
+
if (vendorName) {
|
|
274
|
+
// Initialize map if not exists
|
|
275
|
+
if (!sharedContext?.has('vendorControllers')) {
|
|
276
|
+
sharedContext?.set('vendorControllers', new Map());
|
|
277
|
+
}
|
|
278
|
+
const vendorControllerMap = sharedContext?.get('vendorControllers');
|
|
279
|
+
vendorControllerMap.set(vendorName, parsedNode);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return {};
|
|
283
|
+
},
|
|
284
|
+
priority: 5,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
additionalRelationships: [
|
|
288
|
+
FairSquareSemanticEdgeType.FS_INJECTS,
|
|
289
|
+
FairSquareSemanticEdgeType.FS_PROTECTED_BY,
|
|
290
|
+
],
|
|
291
|
+
neo4j: {
|
|
292
|
+
additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.BUSINESS_LOGIC],
|
|
293
|
+
primaryLabel: FairSquareSemanticNodeType.FS_CONTROLLER,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
// FairSquare Service
|
|
297
|
+
fairsquareService: {
|
|
298
|
+
name: 'FairSquare Service',
|
|
299
|
+
targetCoreType: CoreNodeType.CLASS_DECLARATION,
|
|
300
|
+
semanticType: FairSquareSemanticNodeType.FS_SERVICE,
|
|
301
|
+
priority: 90,
|
|
302
|
+
detectionPatterns: [
|
|
303
|
+
{
|
|
304
|
+
type: 'classname',
|
|
305
|
+
pattern: /Service$/,
|
|
306
|
+
confidence: 0.8,
|
|
307
|
+
priority: 5,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: 'decorator',
|
|
311
|
+
pattern: 'Injectable',
|
|
312
|
+
confidence: 0.9,
|
|
313
|
+
priority: 8,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
type: 'function',
|
|
317
|
+
pattern: (parsedNode) => {
|
|
318
|
+
const node = parsedNode.sourceNode;
|
|
319
|
+
if (!node || !Node.isClassDeclaration(node))
|
|
320
|
+
return false;
|
|
321
|
+
const name = node.getName() ?? '';
|
|
322
|
+
const hasInjectable = node.getDecorators().some((d) => d.getName() === 'Injectable');
|
|
323
|
+
return name.endsWith('Service') && hasInjectable;
|
|
324
|
+
},
|
|
325
|
+
confidence: 1.0,
|
|
326
|
+
priority: 10,
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
contextExtractors: [
|
|
330
|
+
{
|
|
331
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
332
|
+
extractor: extractInjectableDependencies,
|
|
333
|
+
priority: 10,
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
additionalRelationships: [FairSquareSemanticEdgeType.FS_INJECTS],
|
|
337
|
+
neo4j: {
|
|
338
|
+
additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.BUSINESS_LOGIC],
|
|
339
|
+
primaryLabel: FairSquareSemanticNodeType.FS_SERVICE,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
// FairSquare Repository
|
|
343
|
+
fairsquareRepository: {
|
|
344
|
+
name: 'FairSquare Repository',
|
|
345
|
+
targetCoreType: CoreNodeType.CLASS_DECLARATION,
|
|
346
|
+
semanticType: FairSquareSemanticNodeType.FS_REPOSITORY,
|
|
347
|
+
priority: 95,
|
|
348
|
+
detectionPatterns: [
|
|
349
|
+
{
|
|
350
|
+
type: 'classname',
|
|
351
|
+
pattern: /Repository$/,
|
|
352
|
+
confidence: 0.7,
|
|
353
|
+
priority: 5,
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
type: 'function',
|
|
357
|
+
pattern: (parsedNode) => {
|
|
358
|
+
const node = parsedNode.sourceNode;
|
|
359
|
+
if (!node || !Node.isClassDeclaration(node))
|
|
360
|
+
return false;
|
|
361
|
+
const baseClass = node.getExtends();
|
|
362
|
+
return baseClass?.getText() === 'Repository';
|
|
363
|
+
},
|
|
364
|
+
confidence: 1.0,
|
|
365
|
+
priority: 10,
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
contextExtractors: [
|
|
369
|
+
{
|
|
370
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
371
|
+
extractor: extractInjectableDependencies,
|
|
372
|
+
priority: 10,
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
376
|
+
extractor: extractRepositoryDals,
|
|
377
|
+
priority: 9,
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
additionalRelationships: [FairSquareSemanticEdgeType.FS_REPOSITORY_USES_DAL],
|
|
381
|
+
neo4j: {
|
|
382
|
+
additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.DATA_ACCESS],
|
|
383
|
+
primaryLabel: FairSquareSemanticNodeType.FS_REPOSITORY,
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
// FairSquare DAL (Data Access Layer)
|
|
387
|
+
fairsquareDAL: {
|
|
388
|
+
name: 'FairSquare DAL',
|
|
389
|
+
targetCoreType: CoreNodeType.CLASS_DECLARATION,
|
|
390
|
+
semanticType: FairSquareSemanticNodeType.FS_DAL,
|
|
391
|
+
priority: 85,
|
|
392
|
+
detectionPatterns: [
|
|
393
|
+
{
|
|
394
|
+
type: 'classname',
|
|
395
|
+
pattern: /DAL$/,
|
|
396
|
+
confidence: 1.0,
|
|
397
|
+
priority: 10,
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
contextExtractors: [],
|
|
401
|
+
additionalRelationships: [],
|
|
402
|
+
neo4j: {
|
|
403
|
+
additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.DATA_ACCESS, FairSquareLabel.DATABASE],
|
|
404
|
+
primaryLabel: FairSquareSemanticNodeType.FS_DAL,
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
// FairSquare Permission Manager
|
|
408
|
+
fairsquarePermissionManager: {
|
|
409
|
+
name: 'FairSquare Permission Manager',
|
|
410
|
+
targetCoreType: CoreNodeType.CLASS_DECLARATION,
|
|
411
|
+
semanticType: FairSquareSemanticNodeType.FS_PERMISSION_MANAGER,
|
|
412
|
+
priority: 80,
|
|
413
|
+
detectionPatterns: [
|
|
414
|
+
{
|
|
415
|
+
type: 'classname',
|
|
416
|
+
pattern: /PermissionManager$/,
|
|
417
|
+
confidence: 1.0,
|
|
418
|
+
priority: 10,
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
contextExtractors: [],
|
|
422
|
+
additionalRelationships: [],
|
|
423
|
+
neo4j: {
|
|
424
|
+
additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.SECURITY],
|
|
425
|
+
primaryLabel: FairSquareSemanticNodeType.FS_PERMISSION_MANAGER,
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
// FairSquare Vendor Client
|
|
429
|
+
fairsquareVendorClient: {
|
|
430
|
+
name: 'FairSquare Vendor Client',
|
|
431
|
+
targetCoreType: CoreNodeType.CLASS_DECLARATION,
|
|
432
|
+
semanticType: FairSquareSemanticNodeType.FS_VENDOR_CLIENT,
|
|
433
|
+
priority: 75,
|
|
434
|
+
detectionPatterns: [
|
|
435
|
+
{
|
|
436
|
+
type: 'classname',
|
|
437
|
+
pattern: /Client$/,
|
|
438
|
+
confidence: 0.6,
|
|
439
|
+
priority: 3,
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
type: 'filename',
|
|
443
|
+
pattern: /vendor-client/,
|
|
444
|
+
confidence: 0.9,
|
|
445
|
+
priority: 8,
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
type: 'function',
|
|
449
|
+
pattern: (parsedNode) => {
|
|
450
|
+
const node = parsedNode.sourceNode;
|
|
451
|
+
if (!node || !Node.isClassDeclaration(node))
|
|
452
|
+
return false;
|
|
453
|
+
const filePath = node.getSourceFile().getFilePath();
|
|
454
|
+
return filePath.includes('vendor-client') || filePath.includes('component-vendor');
|
|
455
|
+
},
|
|
456
|
+
confidence: 1.0,
|
|
457
|
+
priority: 10,
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
contextExtractors: [
|
|
461
|
+
{
|
|
462
|
+
nodeType: CoreNodeType.CLASS_DECLARATION,
|
|
463
|
+
extractor: extractMonorepoProject,
|
|
464
|
+
priority: 10,
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
additionalRelationships: [],
|
|
468
|
+
neo4j: {
|
|
469
|
+
additionalLabels: [FairSquareLabel.FAIRSQUARE, FairSquareLabel.EXTERNAL_INTEGRATION],
|
|
470
|
+
primaryLabel: FairSquareSemanticNodeType.FS_VENDOR_CLIENT,
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
// FairSquare Route Definition
|
|
474
|
+
fairsquareRouteDefinition: {
|
|
475
|
+
name: 'FairSquare Route Definition',
|
|
476
|
+
targetCoreType: CoreNodeType.VARIABLE_DECLARATION,
|
|
477
|
+
semanticType: FairSquareSemanticNodeType.FS_ROUTE_DEFINITION,
|
|
478
|
+
priority: 110,
|
|
479
|
+
detectionPatterns: [
|
|
480
|
+
{
|
|
481
|
+
type: 'function',
|
|
482
|
+
pattern: (parsedNode) => {
|
|
483
|
+
const node = parsedNode.sourceNode;
|
|
484
|
+
if (!node || !Node.isVariableDeclaration(node))
|
|
485
|
+
return false;
|
|
486
|
+
const name = node.getName();
|
|
487
|
+
const typeNode = node.getTypeNode();
|
|
488
|
+
// Check if variable name ends with "Routes" AND has type ModuleRoute[]
|
|
489
|
+
return !!name.endsWith('Routes') && !!typeNode?.getText().includes('ModuleRoute');
|
|
490
|
+
},
|
|
491
|
+
confidence: 1.0,
|
|
492
|
+
priority: 10,
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
contextExtractors: [
|
|
496
|
+
{
|
|
497
|
+
nodeType: CoreNodeType.VARIABLE_DECLARATION,
|
|
498
|
+
extractor: extractRouteDefinitions,
|
|
499
|
+
priority: 10,
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
additionalRelationships: [
|
|
503
|
+
FairSquareSemanticEdgeType.FS_ROUTES_TO,
|
|
504
|
+
FairSquareSemanticEdgeType.FS_ROUTES_TO_HANDLER,
|
|
505
|
+
],
|
|
506
|
+
neo4j: {
|
|
507
|
+
additionalLabels: [FairSquareLabel.FAIRSQUARE],
|
|
508
|
+
primaryLabel: FairSquareSemanticNodeType.FS_ROUTE_DEFINITION,
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
// HTTP Endpoint (Controller methods)
|
|
512
|
+
// fairsquareHttpEndpoint: {
|
|
513
|
+
// name: 'FairSquare HTTP Endpoint',
|
|
514
|
+
// targetCoreType: CoreNodeType.METHOD_DECLARATION,
|
|
515
|
+
// semanticType: FairSquareSemanticNodeType.FS_HTTP_ENDPOINT as any,
|
|
516
|
+
// priority: 100,
|
|
517
|
+
//
|
|
518
|
+
// detectionPatterns: [
|
|
519
|
+
// {
|
|
520
|
+
// type: 'function',
|
|
521
|
+
// pattern: (node: Node) => {
|
|
522
|
+
// if (!Node.isMethodDeclaration(node)) return false;
|
|
523
|
+
// const methodName = node.getName().toLowerCase();
|
|
524
|
+
// const httpMethods = ['get', 'post', 'put', 'delete', 'patch'];
|
|
525
|
+
//
|
|
526
|
+
// // Check if method is HTTP verb AND parent is Controller
|
|
527
|
+
// const parent = node.getParent();
|
|
528
|
+
// const isController =
|
|
529
|
+
// Node.isClassDeclaration(parent) &&
|
|
530
|
+
// (parent.getName()?.endsWith('Controller') || parent.getExtends()?.getText() === 'Controller');
|
|
531
|
+
//
|
|
532
|
+
// return httpMethods.includes(methodName) && isController;
|
|
533
|
+
// },
|
|
534
|
+
// confidence: 1.0,
|
|
535
|
+
// priority: 10,
|
|
536
|
+
// },
|
|
537
|
+
// ],
|
|
538
|
+
//
|
|
539
|
+
// contextExtractors: [
|
|
540
|
+
// {
|
|
541
|
+
// nodeType: CoreNodeType.METHOD_DECLARATION,
|
|
542
|
+
// extractor: extractHttpEndpoint,
|
|
543
|
+
// priority: 10,
|
|
544
|
+
// },
|
|
545
|
+
// ],
|
|
546
|
+
//
|
|
547
|
+
// additionalRelationships: [FairSquareSemanticEdgeType.FS_EXPOSES_HTTP as any],
|
|
548
|
+
//
|
|
549
|
+
// neo4j: {
|
|
550
|
+
// additionalLabels: ['FairSquare', 'HttpEndpoint', 'API'],
|
|
551
|
+
// primaryLabel: 'FairSquareHttpEndpoint',
|
|
552
|
+
// },
|
|
553
|
+
// },
|
|
554
|
+
},
|
|
555
|
+
// ============================================================================
|
|
556
|
+
// EDGE ENHANCEMENTS (Relationship detection)
|
|
557
|
+
// ============================================================================
|
|
558
|
+
edgeEnhancements: {
|
|
559
|
+
// @Injectable([Dep1, Dep2]) creates INJECTS edges
|
|
560
|
+
injectableDependencies: {
|
|
561
|
+
name: 'Injectable Dependencies',
|
|
562
|
+
semanticType: FairSquareSemanticEdgeType.FS_INJECTS,
|
|
563
|
+
relationshipWeight: 0.95, // Critical - FairSquare DI is core architecture
|
|
564
|
+
detectionPattern: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
565
|
+
// FILTER: Only create INJECTS edges between ClassDeclarations
|
|
566
|
+
if (parsedSourceNode.coreType !== CoreNodeType.CLASS_DECLARATION ||
|
|
567
|
+
parsedTargetNode.coreType !== CoreNodeType.CLASS_DECLARATION) {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
// Source has @Injectable([Target])
|
|
571
|
+
const sourceContext = parsedSourceNode.properties.context;
|
|
572
|
+
const targetName = parsedTargetNode.properties.name;
|
|
573
|
+
if (!sourceContext?.dependencies)
|
|
574
|
+
return false;
|
|
575
|
+
// Use exact match to avoid false positives from substring matching
|
|
576
|
+
return sourceContext.dependencies.some((dep) => {
|
|
577
|
+
// Remove quotes and whitespace from dependency string
|
|
578
|
+
const cleanDep = dep.replace(/['"]/g, '').trim();
|
|
579
|
+
return cleanDep === targetName;
|
|
580
|
+
});
|
|
581
|
+
},
|
|
582
|
+
contextExtractor: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => ({
|
|
583
|
+
injectionType: 'constructor',
|
|
584
|
+
framework: 'fairsquare',
|
|
585
|
+
targetDependency: parsedTargetNode.properties.name,
|
|
586
|
+
}),
|
|
587
|
+
neo4j: {
|
|
588
|
+
relationshipType: 'INJECTS',
|
|
589
|
+
direction: 'OUTGOING',
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
// Repository uses DAL
|
|
593
|
+
repositoryUsesDAL: {
|
|
594
|
+
name: 'Repository Uses DAL',
|
|
595
|
+
semanticType: FairSquareSemanticEdgeType.FS_REPOSITORY_USES_DAL,
|
|
596
|
+
relationshipWeight: 0.85, // High - data access layer relationships
|
|
597
|
+
detectionPattern: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
598
|
+
const isSourceRepo = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_REPOSITORY;
|
|
599
|
+
const isTargetDAL = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_DAL;
|
|
600
|
+
if (!isSourceRepo || !isTargetDAL)
|
|
601
|
+
return false;
|
|
602
|
+
// Check if Repository injects this DAL
|
|
603
|
+
const sourceDals = parsedSourceNode.properties.context?.dals ?? [];
|
|
604
|
+
const targetName = parsedTargetNode.properties.name;
|
|
605
|
+
// Use exact match to avoid false positives
|
|
606
|
+
return sourceDals.some((dal) => {
|
|
607
|
+
const cleanDal = dal.replace(/['"]/g, '').trim();
|
|
608
|
+
return cleanDal === targetName;
|
|
609
|
+
});
|
|
610
|
+
},
|
|
611
|
+
contextExtractor: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => ({
|
|
612
|
+
dalName: parsedTargetNode.properties.name,
|
|
613
|
+
repositoryName: parsedSourceNode.properties.name,
|
|
614
|
+
}),
|
|
615
|
+
neo4j: {
|
|
616
|
+
relationshipType: 'USES_DAL',
|
|
617
|
+
direction: 'OUTGOING',
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
// Controller uses PermissionManager
|
|
621
|
+
controllerProtectedBy: {
|
|
622
|
+
name: 'Controller Protected By Permission Manager',
|
|
623
|
+
semanticType: FairSquareSemanticEdgeType.FS_PROTECTED_BY,
|
|
624
|
+
relationshipWeight: 0.88, // High - security/authorization is critical
|
|
625
|
+
detectionPattern: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
626
|
+
const isSourceController = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_CONTROLLER;
|
|
627
|
+
const isTargetPermissionManager = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_PERMISSION_MANAGER;
|
|
628
|
+
if (!isSourceController || !isTargetPermissionManager)
|
|
629
|
+
return false;
|
|
630
|
+
const sourcePermManager = parsedSourceNode.properties.context?.permissionManager;
|
|
631
|
+
const targetName = parsedTargetNode.properties.name;
|
|
632
|
+
if (!sourcePermManager)
|
|
633
|
+
return false;
|
|
634
|
+
// Use exact match to avoid false positives
|
|
635
|
+
const cleanPermManager = sourcePermManager.replace(/['"]/g, '').trim();
|
|
636
|
+
return cleanPermManager === targetName;
|
|
637
|
+
},
|
|
638
|
+
contextExtractor: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => ({
|
|
639
|
+
permissionManagerName: parsedTargetNode.properties.name,
|
|
640
|
+
controllerName: parsedSourceNode.properties.name,
|
|
641
|
+
}),
|
|
642
|
+
neo4j: {
|
|
643
|
+
relationshipType: 'PROTECTED_BY',
|
|
644
|
+
direction: 'OUTGOING',
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
// Route definition routes to Controller
|
|
648
|
+
routeToController: {
|
|
649
|
+
name: 'Route To Controller',
|
|
650
|
+
semanticType: FairSquareSemanticEdgeType.FS_ROUTES_TO,
|
|
651
|
+
relationshipWeight: 0.92, // Critical - HTTP routing is primary entry point
|
|
652
|
+
detectionPattern: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
653
|
+
const isSourceRoute = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_ROUTE_DEFINITION;
|
|
654
|
+
const isTargetController = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_CONTROLLER;
|
|
655
|
+
if (!isSourceRoute || !isTargetController)
|
|
656
|
+
return false;
|
|
657
|
+
// Check if any route in the definition references this controller
|
|
658
|
+
const routes = parsedSourceNode.properties.context?.routes ?? [];
|
|
659
|
+
const targetName = parsedTargetNode.properties.name;
|
|
660
|
+
return routes.some((route) => route.controllerName === targetName);
|
|
661
|
+
},
|
|
662
|
+
contextExtractor: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
663
|
+
const routes = parsedSourceNode.properties.context?.routes ?? [];
|
|
664
|
+
const targetName = parsedTargetNode.properties.name;
|
|
665
|
+
const relevantRoutes = routes.filter((r) => r.controllerName === targetName);
|
|
666
|
+
return {
|
|
667
|
+
routeCount: relevantRoutes.length,
|
|
668
|
+
routes: relevantRoutes,
|
|
669
|
+
methods: relevantRoutes.map((r) => r.method),
|
|
670
|
+
paths: relevantRoutes.map((r) => r.path),
|
|
671
|
+
routeFile: parsedSourceNode.properties.context?.fileName,
|
|
672
|
+
};
|
|
673
|
+
},
|
|
674
|
+
neo4j: {
|
|
675
|
+
relationshipType: 'ROUTES_TO',
|
|
676
|
+
direction: 'OUTGOING',
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
// Route definition routes to Handler method
|
|
680
|
+
routeToHandlerMethod: {
|
|
681
|
+
name: 'Route To Handler Method',
|
|
682
|
+
semanticType: FairSquareSemanticEdgeType.FS_ROUTES_TO_HANDLER,
|
|
683
|
+
relationshipWeight: 0.9, // Critical - direct route to handler method
|
|
684
|
+
detectionPattern: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
685
|
+
const isSourceRoute = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_ROUTE_DEFINITION;
|
|
686
|
+
const isTargetMethod = parsedTargetNode.coreType === CoreNodeType.METHOD_DECLARATION;
|
|
687
|
+
if (!isSourceRoute || !isTargetMethod)
|
|
688
|
+
return false;
|
|
689
|
+
// Check if any route in the definition references this method as handler
|
|
690
|
+
const routes = parsedSourceNode.properties.context?.routes ?? [];
|
|
691
|
+
const targetMethodName = parsedTargetNode.properties.name;
|
|
692
|
+
// Find routes that match this method name
|
|
693
|
+
const matchingRoutes = routes.filter((route) => route.handler === targetMethodName);
|
|
694
|
+
if (matchingRoutes.length === 0)
|
|
695
|
+
return false;
|
|
696
|
+
// CRITICAL FIX: Verify the method belongs to the correct controller
|
|
697
|
+
// Find the parent class of this method by checking the AST node
|
|
698
|
+
const targetNode = parsedTargetNode.sourceNode;
|
|
699
|
+
if (!targetNode || !Node.isMethodDeclaration(targetNode))
|
|
700
|
+
return false;
|
|
701
|
+
const parentClass = targetNode.getParent();
|
|
702
|
+
if (!parentClass || !Node.isClassDeclaration(parentClass))
|
|
703
|
+
return false;
|
|
704
|
+
const parentClassName = parentClass.getName();
|
|
705
|
+
if (!parentClassName)
|
|
706
|
+
return false;
|
|
707
|
+
// Check if any matching route's controller name matches the parent class
|
|
708
|
+
const isHandler = matchingRoutes.some((route) => route.controllerName === parentClassName);
|
|
709
|
+
// If this method is a route handler AND is public, add HttpEndpoint label to the target node
|
|
710
|
+
if (isHandler) {
|
|
711
|
+
// Only add HttpEndpoint label to public methods (not private/protected)
|
|
712
|
+
const isPublicMethod = parsedTargetNode.properties.visibility === 'public';
|
|
713
|
+
if (isPublicMethod &&
|
|
714
|
+
!parsedTargetNode.labels.includes(FairSquareLabel.HTTP_ENDPOINT) &&
|
|
715
|
+
parsedTargetNode.properties) {
|
|
716
|
+
parsedTargetNode.labels.push(FairSquareLabel.HTTP_ENDPOINT);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return isHandler;
|
|
720
|
+
},
|
|
721
|
+
contextExtractor: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
722
|
+
const routes = parsedSourceNode.properties.context?.routes ?? [];
|
|
723
|
+
const targetMethodName = parsedTargetNode.properties.name;
|
|
724
|
+
const matchingRoute = routes.find((r) => r.handler === targetMethodName);
|
|
725
|
+
return {
|
|
726
|
+
method: matchingRoute?.method,
|
|
727
|
+
path: matchingRoute?.path,
|
|
728
|
+
authenticated: matchingRoute?.authenticated,
|
|
729
|
+
handler: targetMethodName,
|
|
730
|
+
controllerName: matchingRoute?.controllerName,
|
|
731
|
+
routeFile: parsedSourceNode.properties.context?.fileName,
|
|
732
|
+
};
|
|
733
|
+
},
|
|
734
|
+
neo4j: {
|
|
735
|
+
relationshipType: 'ROUTES_TO_HANDLER',
|
|
736
|
+
direction: 'OUTGOING',
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
internalApiCall: {
|
|
740
|
+
name: 'Internal API Call',
|
|
741
|
+
semanticType: FairSquareSemanticEdgeType.FS_INTERNAL_API_CALL,
|
|
742
|
+
relationshipWeight: 0.82, // High - internal service communication
|
|
743
|
+
detectionPattern: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
744
|
+
// Service → VendorController (through VendorClient)
|
|
745
|
+
const isSourceService = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_SERVICE;
|
|
746
|
+
const isTargetController = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_CONTROLLER;
|
|
747
|
+
if (!isSourceService || !isTargetController)
|
|
748
|
+
return false;
|
|
749
|
+
// Get vendor controller map
|
|
750
|
+
const vendorControllerMap = sharedContext?.get('vendorControllers');
|
|
751
|
+
if (!vendorControllerMap)
|
|
752
|
+
return false;
|
|
753
|
+
// Check if target is a vendor controller
|
|
754
|
+
let vendorName = '';
|
|
755
|
+
for (const [name, controllerNode] of vendorControllerMap) {
|
|
756
|
+
if (controllerNode.id === parsedTargetNode.id) {
|
|
757
|
+
vendorName = name;
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (!vendorName)
|
|
762
|
+
return false;
|
|
763
|
+
// Check if service uses the corresponding VendorClient
|
|
764
|
+
const sourceNode = parsedSourceNode.sourceNode;
|
|
765
|
+
if (!sourceNode || !Node.isClassDeclaration(sourceNode))
|
|
766
|
+
return false;
|
|
767
|
+
const expectedClientName = `${vendorName.charAt(0).toUpperCase() + vendorName.slice(1)}Client`;
|
|
768
|
+
const properties = sourceNode.getProperties();
|
|
769
|
+
for (const prop of properties) {
|
|
770
|
+
const typeNode = prop.getTypeNode();
|
|
771
|
+
if (typeNode?.getText() === expectedClientName) {
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
const initializer = prop.getInitializer();
|
|
775
|
+
if (initializer && Node.isNewExpression(initializer)) {
|
|
776
|
+
if (initializer.getExpression().getText() === expectedClientName) {
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
},
|
|
783
|
+
contextExtractor: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
784
|
+
const vendorControllerMap = sharedContext?.get('vendorControllers');
|
|
785
|
+
let vendorName = '';
|
|
786
|
+
for (const [name, controllerNode] of vendorControllerMap) {
|
|
787
|
+
if (controllerNode.id === parsedTargetNode.id) {
|
|
788
|
+
vendorName = name;
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
serviceName: parsedSourceNode.properties.name,
|
|
794
|
+
vendorController: parsedTargetNode.properties.name,
|
|
795
|
+
vendorClient: `${vendorName.charAt(0).toUpperCase() + vendorName.slice(1)}Client`,
|
|
796
|
+
};
|
|
797
|
+
},
|
|
798
|
+
neo4j: {
|
|
799
|
+
relationshipType: 'INTERNAL_API_CALL',
|
|
800
|
+
direction: 'OUTGOING',
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
usesRepository: {
|
|
804
|
+
name: 'Uses Repository',
|
|
805
|
+
semanticType: 'USES_REPOSITORY',
|
|
806
|
+
relationshipWeight: 0.8, // High - service to repository data access
|
|
807
|
+
detectionPattern: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => {
|
|
808
|
+
// Service → Repository
|
|
809
|
+
const isSourceService = parsedSourceNode.semanticType === FairSquareSemanticNodeType.FS_SERVICE;
|
|
810
|
+
const isTargetRepository = parsedTargetNode.semanticType === FairSquareSemanticNodeType.FS_REPOSITORY;
|
|
811
|
+
if (!isSourceService || !isTargetRepository)
|
|
812
|
+
return false;
|
|
813
|
+
// Check if Service injects this Repository
|
|
814
|
+
const sourceDependencies = parsedSourceNode.properties.context?.dependencies ?? [];
|
|
815
|
+
const targetName = parsedTargetNode.properties.name;
|
|
816
|
+
// Use exact match to avoid false positives
|
|
817
|
+
return sourceDependencies.some((dep) => {
|
|
818
|
+
const cleanDep = dep.replace(/['"]/g, '').trim();
|
|
819
|
+
return cleanDep === targetName;
|
|
820
|
+
});
|
|
821
|
+
},
|
|
822
|
+
contextExtractor: (parsedSourceNode, parsedTargetNode, allParsedNodes, sharedContext) => ({
|
|
823
|
+
repositoryName: parsedTargetNode.properties.name,
|
|
824
|
+
serviceName: parsedSourceNode.properties.name,
|
|
825
|
+
}),
|
|
826
|
+
neo4j: {
|
|
827
|
+
relationshipType: 'USES_REPOSITORY',
|
|
828
|
+
direction: 'OUTGOING',
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
};
|