code-graph-context 2.0.0 → 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.
Files changed (34) hide show
  1. package/README.md +156 -2
  2. package/dist/constants.js +167 -0
  3. package/dist/core/config/fairsquare-framework-schema.js +9 -7
  4. package/dist/core/config/nestjs-framework-schema.js +60 -43
  5. package/dist/core/config/schema.js +41 -2
  6. package/dist/core/embeddings/natural-language-to-cypher.service.js +166 -110
  7. package/dist/core/parsers/typescript-parser.js +1043 -747
  8. package/dist/core/parsers/workspace-parser.js +177 -194
  9. package/dist/core/utils/code-normalizer.js +299 -0
  10. package/dist/core/utils/file-change-detection.js +17 -2
  11. package/dist/core/utils/file-utils.js +40 -5
  12. package/dist/core/utils/graph-factory.js +161 -0
  13. package/dist/core/utils/shared-utils.js +79 -0
  14. package/dist/core/workspace/workspace-detector.js +59 -5
  15. package/dist/mcp/constants.js +141 -8
  16. package/dist/mcp/handlers/graph-generator.handler.js +1 -0
  17. package/dist/mcp/handlers/incremental-parse.handler.js +3 -6
  18. package/dist/mcp/handlers/parallel-import.handler.js +136 -0
  19. package/dist/mcp/handlers/streaming-import.handler.js +14 -59
  20. package/dist/mcp/mcp.server.js +1 -1
  21. package/dist/mcp/services/job-manager.js +5 -8
  22. package/dist/mcp/services/watch-manager.js +7 -18
  23. package/dist/mcp/tools/detect-dead-code.tool.js +413 -0
  24. package/dist/mcp/tools/detect-duplicate-code.tool.js +450 -0
  25. package/dist/mcp/tools/impact-analysis.tool.js +20 -4
  26. package/dist/mcp/tools/index.js +4 -0
  27. package/dist/mcp/tools/parse-typescript-project.tool.js +15 -14
  28. package/dist/mcp/workers/chunk-worker-pool.js +196 -0
  29. package/dist/mcp/workers/chunk-worker.types.js +4 -0
  30. package/dist/mcp/workers/chunk.worker.js +89 -0
  31. package/dist/mcp/workers/parse-coordinator.js +183 -0
  32. package/dist/mcp/workers/worker.pool.js +54 -0
  33. package/dist/storage/neo4j/neo4j.service.js +190 -10
  34. package/package.json +1 -1
@@ -8,52 +8,67 @@ export class NaturalLanguageToCypherService {
8
8
  schemaPath = null;
9
9
  cachedSemanticTypes = null;
10
10
  messageInstructions = `
11
- === CRITICAL - CLASS/SERVICE NAME HANDLING ===
12
- WRONG: (n:DbService), (n:UserService), (n:AuthController) - DO NOT USE CLASS NAMES AS LABELS
13
- CORRECT: (n:Class {name: 'DbService'}) - Match on the "name" property instead
14
-
15
- Class/service names are NOT Neo4j labels. They are values of the "name" property on Class nodes.
16
-
17
- The ONLY valid node labels are: SourceFile, Class, Method, Function, Property, Interface,
18
- Constructor, Parameter, Enum, Variable, Import, Export, Decorator
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
- - "Find DbService" -> MATCH (n:Class {name: 'DbService'}) WHERE n.projectId = $projectId RETURN n
22
- - "Classes extending BaseService" -> MATCH (c:Class)-[:EXTENDS]->(p:Class {name: 'BaseService'}) WHERE c.projectId = $projectId RETURN c
23
- - "Methods in UserController" -> MATCH (c:Class {name: 'UserController'})-[:HAS_MEMBER]->(m:Method) WHERE c.projectId = $projectId RETURN m
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
- IMPORTANT - Multi-Project Isolation:
36
- All nodes have a "projectId" property that isolates data between different projects.
37
- You MUST include a projectId filter in EVERY query to ensure project isolation.
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
- Your response must be a valid JSON object with this exact structure:
50
+ === RESPONSE FORMAT ===
51
+ Return ONLY valid JSON:
41
52
  {
42
- "cypher": "MATCH (n:NodeType) WHERE n.projectId = $projectId AND n.property = $param RETURN n",
53
+ "cypher": "MATCH (n:Label) WHERE n.projectId = $projectId RETURN n",
43
54
  "parameters": { "param": "value" } | null,
44
- "explanation": "Concise explanation of what the query does and why it matches the user's request"
55
+ "explanation": "What this query does"
45
56
  }
46
-
47
- Note: Do NOT include projectId in the parameters object - it will be injected automatically by the system.
48
-
49
- Query Generation Process:
50
- 1. CHECK NODE TYPES: Look at discoveredSchema.nodeTypes to see available node labels and their properties
51
- 2. CHECK RELATIONSHIPS: Look at discoveredSchema.relationshipTypes to understand how nodes connect
52
- 3. CHECK SEMANTIC TYPES: Look at discoveredSchema.semanticTypes for higher-level node classifications
53
- 4. REVIEW PATTERNS: Check discoveredSchema.commonPatterns for frequent relationship patterns in the graph
54
- 5. EXAMINE PROPERTIES: Use rawSchema for exact property names and types
55
- 6. GENERATE QUERY: Write the Cypher query using only node labels, relationships, and properties that exist in the schema
56
- 7. ADD PROJECT FILTER: Always include WHERE n.projectId = $projectId for every node pattern in the query
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
- RELATIONSHIP TYPE DEFINITIONS (use these exact types):
67
- - EXTENDS: Inheritance - one class/interface IS_A parent (use for "extends", "inherits from", "parent class", "subclass")
68
- - IMPLEMENTS: Contract - a class implements an interface (use for "implements", "conforms to")
69
- - HAS_MEMBER: Composition - a class/interface contains methods/properties (use for "has method", "contains property", "members")
70
- - CONTAINS: Structure - file contains declarations (use for "in file", "declared in", "defined in")
71
- - IMPORTS: Dependencies - file imports another (use for "imports", "depends on", "requires")
72
- - TYPED_AS: Type annotation - parameter/property has a type (use for "typed as", "has type", "returns")
73
- - HAS_PARAMETER: Function signature - method/function has parameters (use for "takes parameter", "accepts")
74
-
75
- WARNING - NOT IMPLEMENTED (will return 0 results):
76
- - CALLS: Function call tracking is NOT YET IMPLEMENTED. Do not use this relationship type.
77
- Instead, for "calls" or "uses" queries, suggest using IMPORTS to find file dependencies.
78
- - DECORATED_WITH: Decorator relationships are NOT IMPLEMENTED. Do not use this relationship type.
79
- Instead, use the semanticType property (e.g., WHERE c.semanticType = 'NestController').
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-Specific Classifications):
101
- The parser assigns semanticType based on decorators or naming patterns. The actual semantic types vary by framework.
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
- IMPORTANT: Do NOT assume NestJS semantic type names like 'NestController' or 'NestService'.
104
- Instead, refer to the SEMANTIC TYPES IN THIS PROJECT section below for the actual types discovered in this codebase.
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: Look for types containing 'Controller' (e.g., 'Controller', 'NestController')
108
- - Services: Look for types containing 'Service', 'Provider', or 'Injectable'
109
- - Repositories: Look for types containing 'Repository', 'DAL', or 'DAO'
110
- - Modules: Look for types containing 'Module'
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 as fallback:
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
- NOTE: Do NOT use DECORATED_WITH relationships - they don't exist in the graph. Use semanticType property instead.
117
-
118
- FRAMEWORK-SPECIFIC PATTERNS:
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
- IMPORTANT: When user asks about "components" or "hooks":
133
- - If asking about React -> query Function nodes
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 (use discovered semantic types from this project):
143
- - "Controllers in account module" ->
144
- MATCH (c:Class)
145
- WHERE c.projectId = $projectId AND c.semanticType IN [discovered controller types] AND c.filePath CONTAINS '/account/'
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
- - "All services in the auth folder" ->
149
- MATCH (c:Class)
150
- WHERE c.projectId = $projectId AND c.semanticType IN [discovered service types] AND c.filePath CONTAINS '/auth/'
151
- RETURN c
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
- FALLBACK (when semantic types not available):
154
- - "Controllers in account module" ->
155
- MATCH (c:Class)
156
- WHERE c.projectId = $projectId AND c.name CONTAINS 'Controller' AND c.filePath CONTAINS '/account/'
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
- NOTE: Do NOT assume packageName exists - use filePath for directory-based queries.
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
- * Validates that the generated Cypher query uses only valid node labels.
508
- * Class/service names should be matched via {name: 'ClassName'}, not as labels.
509
+ * Load valid labels dynamically from the schema file.
510
+ * Returns all keys from rawSchema which represent actual Neo4j labels.
509
511
  */
510
- validateLabelUsage(cypher) {
511
- // Valid labels from the schema (actual Neo4j labels, not AST type names)
512
- const validLabels = new Set([
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
- throw new Error(`Invalid label ":${label}" in query. ` +
541
- `Class/service names should be matched via {name: '${label}'}, not as labels.\n` +
542
- `Example: (n:Class {name: '${label}'}) instead of (n:${label})\n` +
543
- `Query: ${cypher}`);
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
  }