code-graph-context 2.0.1 → 2.2.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 +156 -2
- package/dist/constants.js +167 -0
- package/dist/core/config/fairsquare-framework-schema.js +9 -7
- package/dist/core/config/schema.js +41 -2
- package/dist/core/embeddings/natural-language-to-cypher.service.js +166 -110
- package/dist/core/parsers/typescript-parser.js +1039 -742
- package/dist/core/parsers/workspace-parser.js +175 -193
- package/dist/core/utils/code-normalizer.js +299 -0
- package/dist/core/utils/file-change-detection.js +17 -2
- package/dist/core/utils/file-utils.js +40 -5
- package/dist/core/utils/graph-factory.js +161 -0
- package/dist/core/utils/shared-utils.js +79 -0
- package/dist/core/workspace/workspace-detector.js +59 -5
- package/dist/mcp/constants.js +141 -8
- package/dist/mcp/handlers/graph-generator.handler.js +1 -0
- package/dist/mcp/handlers/incremental-parse.handler.js +3 -6
- package/dist/mcp/handlers/parallel-import.handler.js +136 -0
- package/dist/mcp/handlers/streaming-import.handler.js +14 -59
- package/dist/mcp/mcp.server.js +1 -1
- package/dist/mcp/services/job-manager.js +5 -8
- package/dist/mcp/services/watch-manager.js +7 -18
- package/dist/mcp/tools/detect-dead-code.tool.js +413 -0
- package/dist/mcp/tools/detect-duplicate-code.tool.js +450 -0
- package/dist/mcp/tools/impact-analysis.tool.js +20 -4
- package/dist/mcp/tools/index.js +4 -0
- package/dist/mcp/tools/parse-typescript-project.tool.js +15 -14
- package/dist/mcp/workers/chunk-worker-pool.js +196 -0
- package/dist/mcp/workers/chunk-worker.types.js +4 -0
- package/dist/mcp/workers/chunk.worker.js +89 -0
- package/dist/mcp/workers/parse-coordinator.js +183 -0
- package/dist/mcp/workers/worker.pool.js +54 -0
- package/dist/storage/neo4j/neo4j.service.js +190 -10
- package/package.json +1 -1
|
@@ -8,52 +8,67 @@ export class NaturalLanguageToCypherService {
|
|
|
8
8
|
schemaPath = null;
|
|
9
9
|
cachedSemanticTypes = null;
|
|
10
10
|
messageInstructions = `
|
|
11
|
-
===
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
=== THE SCHEMA FILE IS THE SOURCE OF TRUTH ===
|
|
12
|
+
ALWAYS read neo4j-apoc-schema.json FIRST before generating any query. It contains:
|
|
13
|
+
1. rawSchema: All node labels (keys), their properties, and relationships from Neo4j APOC
|
|
14
|
+
2. discoveredSchema (if available): Dynamically discovered nodeTypes, relationshipTypes, semanticTypes, commonPatterns
|
|
15
|
+
|
|
16
|
+
=== LABEL TYPES - TWO CATEGORIES ===
|
|
17
|
+
Check rawSchema keys for ALL valid labels. Labels fall into two categories:
|
|
18
|
+
|
|
19
|
+
1. CORE LABELS (base TypeScript AST):
|
|
20
|
+
SourceFile, Class, Function, Method, Interface, Property, Parameter, Constructor, Import, Export, Decorator, Enum, Variable, TypeAlias
|
|
21
|
+
|
|
22
|
+
2. FRAMEWORK LABELS (from framework enhancements - check rawSchema keys):
|
|
23
|
+
These REPLACE the core label for enhanced nodes. Examples: Service, Controller, Module, Entity, DTO, Repository, HttpEndpoint, MessageHandler
|
|
24
|
+
A node with label "Service" was originally a Class but got enhanced - use the framework label.
|
|
25
|
+
|
|
26
|
+
=== AST TYPE NAME MAPPING ===
|
|
27
|
+
AST type names are NOT valid labels. Always map them:
|
|
28
|
+
- ClassDeclaration → Class (or framework label: Service, Controller, etc.)
|
|
29
|
+
- FunctionDeclaration → Function
|
|
30
|
+
- MethodDeclaration → Method
|
|
31
|
+
- InterfaceDeclaration → Interface
|
|
32
|
+
- PropertyDeclaration → Property
|
|
33
|
+
- ParameterDeclaration → Parameter
|
|
34
|
+
|
|
35
|
+
=== FINDING SPECIFIC NODES ===
|
|
36
|
+
Class/service names are property values, NOT labels:
|
|
37
|
+
WRONG: (n:DbService), (n:UserController) - class names as labels
|
|
38
|
+
CORRECT: (n:Service {name: 'DbService'}), (n:Controller {name: 'UserController'})
|
|
39
|
+
CORRECT: (n:Class {name: 'SomeClass'}) - if no framework enhancement
|
|
19
40
|
|
|
20
41
|
Examples:
|
|
21
|
-
- "
|
|
22
|
-
- "
|
|
23
|
-
- "Methods in UserController" -> MATCH (c:
|
|
24
|
-
- "Classes with @Controller decorator" -> MATCH (c:Class) WHERE c.projectId = $projectId AND c.semanticType = 'NestController' RETURN c
|
|
25
|
-
===============================================
|
|
26
|
-
|
|
27
|
-
The schema file (neo4j-apoc-schema.json) contains two sections:
|
|
28
|
-
1. rawSchema: Complete Neo4j APOC schema with all node labels, properties, and relationships in the graph
|
|
29
|
-
2. discoveredSchema: Dynamically discovered graph structure including:
|
|
30
|
-
- nodeTypes: Array of {label, count, properties} for each node type in the graph
|
|
31
|
-
- relationshipTypes: Array of {type, count, connections} showing relationship types and what they connect
|
|
32
|
-
- semanticTypes: Array of {type, count} showing semantic node classifications (e.g., Service, Controller)
|
|
33
|
-
- commonPatterns: Array of {from, relationship, to, count} showing frequent relationship patterns
|
|
42
|
+
- "Count all classes" -> MATCH (n:Class) WHERE n.projectId = $projectId RETURN count(n)
|
|
43
|
+
- "Find DbService" -> MATCH (n:Service {name: 'DbService'}) WHERE n.projectId = $projectId RETURN n
|
|
44
|
+
- "Methods in UserController" -> MATCH (c:Controller {name: 'UserController'})-[:HAS_MEMBER]->(m:Method) WHERE c.projectId = $projectId RETURN m
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
The projectId will be provided as a parameter ($projectId).
|
|
46
|
+
=== PROJECT ISOLATION (REQUIRED) ===
|
|
47
|
+
ALL queries MUST filter by projectId on every node pattern:
|
|
48
|
+
WHERE n.projectId = $projectId
|
|
39
49
|
|
|
40
|
-
|
|
50
|
+
=== RESPONSE FORMAT ===
|
|
51
|
+
Return ONLY valid JSON:
|
|
41
52
|
{
|
|
42
|
-
"cypher": "MATCH (n:
|
|
53
|
+
"cypher": "MATCH (n:Label) WHERE n.projectId = $projectId RETURN n",
|
|
43
54
|
"parameters": { "param": "value" } | null,
|
|
44
|
-
"explanation": "
|
|
55
|
+
"explanation": "What this query does"
|
|
45
56
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
Do NOT include projectId in parameters - it's injected automatically.
|
|
58
|
+
|
|
59
|
+
Query Generation Process - FOLLOW THIS EXACTLY:
|
|
60
|
+
1. SEARCH THE SCHEMA FILE FIRST: Use file_search to read neo4j-apoc-schema.json BEFORE generating any query
|
|
61
|
+
2. EXTRACT VALID LABELS: The keys in rawSchema ARE the valid labels (e.g., "Service", "Controller", "Class", "Method")
|
|
62
|
+
- rawSchema is ALWAYS available and contains all labels currently in the graph
|
|
63
|
+
- discoveredSchema.nodeTypes (if available) provides counts and sample properties
|
|
64
|
+
3. CHECK RELATIONSHIPS: Look at rawSchema[label].relationships for each label to see available relationship types
|
|
65
|
+
4. CHECK SEMANTIC TYPES: Look at discoveredSchema.semanticTypes (if available) for framework-specific classifications
|
|
66
|
+
- semanticTypes are PROPERTY values (e.g., semanticType = 'NestController'), not labels
|
|
67
|
+
5. REVIEW PATTERNS: Check discoveredSchema.commonPatterns (if available) for frequent relationship patterns
|
|
68
|
+
6. EXAMINE PROPERTIES: Use rawSchema[label].properties for exact property names and types
|
|
69
|
+
7. GENERATE QUERY: Write the Cypher query using ONLY labels, relationships, and properties from the schema
|
|
70
|
+
8. VALIDATE LABELS: Double-check that every label in your query exists as a key in rawSchema
|
|
71
|
+
9. ADD PROJECT FILTER: Always include WHERE n.projectId = $projectId for every node pattern in the query
|
|
57
72
|
|
|
58
73
|
Critical Rules:
|
|
59
74
|
- ALWAYS filter by projectId on every node in the query (e.g., WHERE n.projectId = $projectId)
|
|
@@ -63,20 +78,22 @@ Critical Rules:
|
|
|
63
78
|
- Use parameterized queries with $ syntax for any dynamic values
|
|
64
79
|
- Return only the data relevant to the user's request
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
|
|
81
|
+
=== CORE RELATIONSHIPS ===
|
|
82
|
+
- CONTAINS: SourceFile contains declarations (use for "in file", "declared in", "defined in")
|
|
83
|
+
- HAS_MEMBER: Class/Interface has methods/properties (use for "has method", "contains property", "members")
|
|
84
|
+
- HAS_PARAMETER: Method/Function has parameters (use for "takes parameter", "accepts")
|
|
85
|
+
- EXTENDS: Class/Interface extends parent (use for "extends", "inherits from", "parent class", "subclass")
|
|
86
|
+
- IMPLEMENTS: Class implements Interface (use for "implements", "conforms to")
|
|
87
|
+
- IMPORTS: SourceFile imports another (use for "imports", "depends on", "requires")
|
|
88
|
+
- TYPED_AS: Parameter/Property has type annotation (use for "typed as", "has type", "returns")
|
|
89
|
+
- CALLS: Method/Function calls another (use for "calls", "invokes", "uses")
|
|
90
|
+
- DECORATED_WITH: Node has a Decorator (use for "decorated with", "has decorator", "@SomeDecorator")
|
|
91
|
+
|
|
92
|
+
=== FRAMEWORK RELATIONSHIPS ===
|
|
93
|
+
Framework-specific relationships are defined in rawSchema. Check rawSchema[label].relationships for each label to discover:
|
|
94
|
+
- What relationship types exist (e.g., INJECTS, EXPOSES, MODULE_IMPORTS, INTERNAL_API_CALL, etc.)
|
|
95
|
+
- Direction (in/out) and target labels for each relationship
|
|
96
|
+
- These vary by project - ALWAYS check the schema file for available relationships
|
|
80
97
|
|
|
81
98
|
CRITICAL: Do NOT confuse EXTENDS (inheritance) with HAS_MEMBER (composition). "extends" always means EXTENDS relationship.
|
|
82
99
|
|
|
@@ -97,67 +114,52 @@ Examples:
|
|
|
97
114
|
WHERE methodCount > 5
|
|
98
115
|
RETURN c, methodCount
|
|
99
116
|
|
|
100
|
-
SEMANTIC TYPES (Framework
|
|
101
|
-
|
|
117
|
+
=== SEMANTIC TYPES (Framework Classifications) ===
|
|
118
|
+
Nodes have a semanticType property set by framework detection. Check discoveredSchema.semanticTypes for actual values in this project.
|
|
102
119
|
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
The semanticType is a PROPERTY, not a label. Query it like:
|
|
121
|
+
- MATCH (c) WHERE c.projectId = $projectId AND c.semanticType = 'NestController' RETURN c
|
|
122
|
+
- MATCH (c) WHERE c.projectId = $projectId AND c.semanticType CONTAINS 'Service' RETURN c
|
|
105
123
|
|
|
106
|
-
Common semantic type patterns:
|
|
107
|
-
- Controllers:
|
|
108
|
-
- Services:
|
|
109
|
-
- Repositories:
|
|
110
|
-
- Modules:
|
|
124
|
+
Common semantic type patterns (but ALWAYS verify against discoveredSchema.semanticTypes):
|
|
125
|
+
- Controllers: types containing 'Controller'
|
|
126
|
+
- Services: types containing 'Service', 'Provider', or 'Injectable'
|
|
127
|
+
- Repositories: types containing 'Repository', 'DAL', or 'DAO'
|
|
128
|
+
- Modules: types containing 'Module'
|
|
111
129
|
|
|
112
|
-
If no semantic types are discovered, use name patterns
|
|
130
|
+
FALLBACK - If no semantic types are discovered, use name patterns:
|
|
113
131
|
- "Find all controllers" -> MATCH (c:Class) WHERE c.projectId = $projectId AND c.name CONTAINS 'Controller' RETURN c
|
|
114
132
|
- "Find all services" -> MATCH (c:Class) WHERE c.projectId = $projectId AND c.name CONTAINS 'Service' RETURN c
|
|
115
133
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
React/Frontend Projects:
|
|
121
|
-
- React functional components are stored as Function nodes, NOT Class nodes
|
|
122
|
-
- Example: "Find component UserProfile" -> MATCH (f:Function {name: 'UserProfile'}) WHERE f.projectId = $projectId RETURN f
|
|
123
|
-
- React hooks are also Function nodes (useAuth, useState, etc.)
|
|
124
|
-
- JSX files (.tsx) contain functions that return JSX elements
|
|
125
|
-
|
|
126
|
-
Decorator-Based Backend Projects (NestJS, custom frameworks, etc.):
|
|
127
|
-
- Uses Class nodes with semanticType property set based on decorators
|
|
128
|
-
- The actual semanticType values depend on the framework - check the discovered schema
|
|
129
|
-
- Controllers: MATCH (c:Class) WHERE c.projectId = $projectId AND c.semanticType IN [discovered controller types] RETURN c
|
|
130
|
-
- Services: MATCH (c:Class) WHERE c.projectId = $projectId AND c.semanticType IN [discovered service types] RETURN c
|
|
134
|
+
=== DECORATOR QUERIES ===
|
|
135
|
+
Use DECORATED_WITH relationship to find nodes with specific decorators:
|
|
136
|
+
- "Classes with @Controller" -> MATCH (c:Class)-[:DECORATED_WITH]->(d:Decorator {name: 'Controller'}) WHERE c.projectId = $projectId RETURN c
|
|
137
|
+
- "Methods with @Get" -> MATCH (m:Method)-[:DECORATED_WITH]->(d:Decorator {name: 'Get'}) WHERE m.projectId = $projectId RETURN m
|
|
131
138
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- If asking about decorator-based frameworks -> query Class nodes with semanticType property (using discovered types)
|
|
135
|
-
|
|
136
|
-
MODULE/DIRECTORY QUERIES:
|
|
137
|
-
To find things "in a module" or "in a directory", use filePath pattern matching:
|
|
139
|
+
=== MODULE/DIRECTORY QUERIES ===
|
|
140
|
+
Use filePath property for location-based queries:
|
|
138
141
|
- "in account module" -> WHERE n.filePath CONTAINS '/account/'
|
|
139
142
|
- "in auth folder" -> WHERE n.filePath CONTAINS '/auth/'
|
|
140
|
-
- "in src/services" -> WHERE n.filePath CONTAINS '/services/'
|
|
141
143
|
|
|
142
|
-
Examples
|
|
143
|
-
- "
|
|
144
|
-
MATCH (
|
|
145
|
-
|
|
146
|
-
RETURN c
|
|
144
|
+
Examples:
|
|
145
|
+
- "Services in account folder" ->
|
|
146
|
+
MATCH (s:Service) WHERE s.projectId = $projectId AND s.filePath CONTAINS '/account/' RETURN s
|
|
147
|
+
- FALLBACK (if no framework labels):
|
|
148
|
+
MATCH (c:Class) WHERE c.projectId = $projectId AND c.name CONTAINS 'Service' AND c.filePath CONTAINS '/account/' RETURN c
|
|
149
|
+
|
|
150
|
+
=== FRAMEWORK-SPECIFIC PATTERNS ===
|
|
147
151
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
Backend Projects (decorator-based like NestJS):
|
|
153
|
+
- Check rawSchema for framework labels (Service, Controller, Module, etc.) that REPLACE Class label
|
|
154
|
+
- Use framework relationships (INJECTS, EXPOSES, etc.) from rawSchema[label].relationships
|
|
155
|
+
- Check discoveredSchema.semanticTypes for framework classifications
|
|
152
156
|
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
RETURN c
|
|
157
|
+
Frontend Projects (React, functional):
|
|
158
|
+
- React components are typically Function nodes, NOT Class nodes
|
|
159
|
+
- Hooks are Function nodes (useAuth, useState, etc.)
|
|
160
|
+
- Example: "Find UserProfile component" -> MATCH (f:Function {name: 'UserProfile'}) WHERE f.projectId = $projectId RETURN f
|
|
158
161
|
|
|
159
|
-
|
|
160
|
-
NOTE: Do NOT use DECORATED_WITH - use semanticType property instead.
|
|
162
|
+
Tip: Check rawSchema keys to understand if project uses framework labels or just core TypeScript labels.
|
|
161
163
|
|
|
162
164
|
IMPORTANT - Cypher Syntax (NOT SQL):
|
|
163
165
|
- Cypher does NOT use GROUP BY. Aggregation happens automatically in RETURN.
|
|
@@ -504,12 +506,12 @@ Remember to include WHERE n.projectId = $projectId for all node patterns.
|
|
|
504
506
|
}
|
|
505
507
|
}
|
|
506
508
|
/**
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
+
* Load valid labels dynamically from the schema file.
|
|
510
|
+
* Returns all keys from rawSchema which represent actual Neo4j labels.
|
|
509
511
|
*/
|
|
510
|
-
|
|
511
|
-
//
|
|
512
|
-
const
|
|
512
|
+
loadValidLabelsFromSchema() {
|
|
513
|
+
// Fallback to core TypeScript labels if schema not available
|
|
514
|
+
const coreLabels = new Set([
|
|
513
515
|
'SourceFile',
|
|
514
516
|
'Class',
|
|
515
517
|
'Method',
|
|
@@ -523,7 +525,49 @@ Remember to include WHERE n.projectId = $projectId for all node patterns.
|
|
|
523
525
|
'Import',
|
|
524
526
|
'Export',
|
|
525
527
|
'Decorator',
|
|
528
|
+
'TypeAlias',
|
|
529
|
+
'TypeScript',
|
|
530
|
+
'Embedded',
|
|
526
531
|
]);
|
|
532
|
+
if (!this.schemaPath) {
|
|
533
|
+
return coreLabels;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const content = fs.readFileSync(this.schemaPath, 'utf-8');
|
|
537
|
+
const schema = JSON.parse(content);
|
|
538
|
+
if (!schema.rawSchema?.records?.[0]?._fields?.[0]) {
|
|
539
|
+
return coreLabels;
|
|
540
|
+
}
|
|
541
|
+
// Extract all keys from rawSchema - these are the valid labels
|
|
542
|
+
const schemaLabels = Object.keys(schema.rawSchema.records[0]._fields[0]);
|
|
543
|
+
return new Set([...coreLabels, ...schemaLabels]);
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
return coreLabels;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Validates that the generated Cypher query uses only valid node labels.
|
|
551
|
+
* AST type names (ClassDeclaration) must be mapped to Neo4j labels (Class).
|
|
552
|
+
* Class/service names should be matched via {name: 'ClassName'}, not as labels.
|
|
553
|
+
*/
|
|
554
|
+
validateLabelUsage(cypher) {
|
|
555
|
+
// Load valid labels dynamically from schema file
|
|
556
|
+
const validLabels = this.loadValidLabelsFromSchema();
|
|
557
|
+
// Mapping from AST type names to correct Neo4j labels
|
|
558
|
+
const astTypeToLabel = {
|
|
559
|
+
ClassDeclaration: 'Class',
|
|
560
|
+
FunctionDeclaration: 'Function',
|
|
561
|
+
MethodDeclaration: 'Method',
|
|
562
|
+
InterfaceDeclaration: 'Interface',
|
|
563
|
+
PropertyDeclaration: 'Property',
|
|
564
|
+
ParameterDeclaration: 'Parameter',
|
|
565
|
+
ConstructorDeclaration: 'Constructor',
|
|
566
|
+
ImportDeclaration: 'Import',
|
|
567
|
+
ExportDeclaration: 'Export',
|
|
568
|
+
EnumDeclaration: 'Enum',
|
|
569
|
+
VariableDeclaration: 'Variable',
|
|
570
|
+
};
|
|
527
571
|
// Extract all labels from query (matches :LabelName patterns in node definitions)
|
|
528
572
|
// This regex matches labels after : in patterns like (n:Label) or (:Label)
|
|
529
573
|
const labelPattern = /\(\s*\w*\s*:\s*([A-Z][a-zA-Z0-9]*)/g;
|
|
@@ -537,10 +581,22 @@ Remember to include WHERE n.projectId = $projectId for all node patterns.
|
|
|
537
581
|
}
|
|
538
582
|
if (invalidLabels.length > 0) {
|
|
539
583
|
const label = invalidLabels[0];
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
`
|
|
584
|
+
const correctLabel = astTypeToLabel[label];
|
|
585
|
+
if (correctLabel) {
|
|
586
|
+
// AST type name used instead of Neo4j label
|
|
587
|
+
throw new Error(`Invalid label ":${label}" in query. ` +
|
|
588
|
+
`Use the Neo4j label ":${correctLabel}" instead of the AST type name ":${label}".\n` +
|
|
589
|
+
`Example: (n:${correctLabel}) instead of (n:${label})\n` +
|
|
590
|
+
`Query: ${cypher}`);
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
// Unknown label - likely a class/service name used as label
|
|
594
|
+
throw new Error(`Invalid label ":${label}" in query. ` +
|
|
595
|
+
`Class/service names should be matched via {name: '${label}'}, not as labels.\n` +
|
|
596
|
+
`Example: (n:Class {name: '${label}'}) instead of (n:${label})\n` +
|
|
597
|
+
`Valid labels: ${Array.from(validLabels).join(', ')}\n` +
|
|
598
|
+
`Query: ${cypher}`);
|
|
599
|
+
}
|
|
544
600
|
}
|
|
545
601
|
}
|
|
546
602
|
}
|