@lowgular/code-graph 0.1.1 → 0.1.2

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 (44) hide show
  1. package/bin/cli.js +2458 -5
  2. package/lib.js +2430 -0
  3. package/package.json +3 -2
  4. package/code-graph-v2/code.graph.js +0 -37
  5. package/code-graph-v2/config-from-query.js +0 -131
  6. package/code-graph-v2/extractors/extractor.js +0 -27
  7. package/code-graph-v2/extractors/index.js +0 -1
  8. package/code-graph-v2/graph-builder/code-graph.builder.js +0 -49
  9. package/code-graph-v2/graph-builder/index.js +0 -1
  10. package/code-graph-v2/graph-builder/node.processor.js +0 -22
  11. package/code-graph-v2/graph-builder/relationship.processor.js +0 -55
  12. package/code-graph-v2/graph-builder/type.processor.js +0 -21
  13. package/code-graph-v2/index.js +0 -4
  14. package/code-graph-v2/tools/build-code-graph.tool.js +0 -19
  15. package/code-graph-v2/utils.js +0 -34
  16. package/codegular/index.js +0 -5
  17. package/codegular/node.js +0 -71
  18. package/codegular/program.js +0 -100
  19. package/codegular/string.js +0 -121
  20. package/codegular/type-checker.js +0 -133
  21. package/codegular/type.js +0 -356
  22. package/codegular/utils.js +0 -335
  23. package/cypher/index.js +0 -1
  24. package/cypher/lib/executor/condition-evaluator.js +0 -135
  25. package/cypher/lib/executor/executor.js +0 -60
  26. package/cypher/lib/executor/graph.js +0 -0
  27. package/cypher/lib/executor/match-engine.js +0 -130
  28. package/cypher/lib/executor/pattern-matcher.js +0 -86
  29. package/cypher/lib/executor/relationship-navigator.js +0 -41
  30. package/cypher/lib/executor/result-formatter.js +0 -149
  31. package/cypher/lib/executor/traverse-engine.js +0 -141
  32. package/cypher/lib/executor/utils.js +0 -14
  33. package/cypher/lib/graph.stub.js +0 -38
  34. package/cypher/lib/index.js +0 -32
  35. package/cypher/lib/lexer.js +0 -376
  36. package/cypher/lib/parser.js +0 -586
  37. package/cypher/lib/validator/query-validator.js +0 -75
  38. package/cypher/lib/validator/supported-features.config.js +0 -83
  39. package/cypher/lib/validator/unsupported-features.config.js +0 -124
  40. package/cypher-cli.js +0 -41
  41. package/infra/code-graph.js +0 -147
  42. package/main.js +0 -0
  43. package/resources-cli.js +0 -75
  44. package/run-cli.js +0 -43
package/bin/cli.js CHANGED
@@ -1,10 +1,2463 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ // apps/code-graph/src/bin/cli.ts
2
9
  import yargs from "yargs";
3
10
  import { hideBin } from "yargs/helpers";
4
- import * as QueryModule from "../run-cli.js";
5
- import * as ResourcesModule from "../resources-cli.js";
6
- import * as CypherModule from "../cypher-cli.js";
7
- const VERSION = process.env.npm_package_version ?? "0.1.0";
8
- yargs(hideBin(process.argv)).scriptName("code-graph").demandCommand().recommendCommands().command(QueryModule.command, QueryModule.describe, QueryModule).command(ResourcesModule.command, ResourcesModule.describe, ResourcesModule).command(CypherModule.command, CypherModule.describe, CypherModule).wrap(120).strict().help().version(VERSION).epilog(
11
+
12
+ // apps/code-graph/src/run-cli.ts
13
+ var run_cli_exports = {};
14
+ __export(run_cli_exports, {
15
+ builder: () => builder,
16
+ command: () => command,
17
+ describe: () => describe,
18
+ handler: () => handler
19
+ });
20
+ import * as fs from "fs";
21
+ import * as path2 from "path";
22
+
23
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/config-from-query.ts
24
+ var STANDARD_NODE_PROPERTIES = {
25
+ firstIdentifier: `helpers.getDescendantsBy(args.current, (node) => node.kind === args.ts.SyntaxKind.Identifier)[0]?.getText() ?? null`,
26
+ initializer: `args.current.initializer ? args.current.initializer?.getText() : null`,
27
+ name: `args.current.name?.getText() ?? null`,
28
+ filePath: `args.sourceFile.fileName`,
29
+ text: `args.current.getText()`,
30
+ type: `(() => { try { const t = args.typeChecker.getTypeAtLocation(args.current); return t ? args.typeChecker.typeToString(t) : null; } catch { return null; } })()`
31
+ };
32
+ var SUPPORTED_RELATIONSHIPS = {
33
+ // --- Traversal relationships ---
34
+ HAS_DESCENDANTS: (labels) => labels.length > 0 ? `helpers.getDescendantsBy(args.current, (node) => ${buildKindFilter(labels)})` : `helpers.getDescendantsBy(args.current, () => true)`,
35
+ HAS_ANCESTORS: (labels) => labels.length > 0 ? `helpers.getAncestorsBy(args.current, (node) => ${buildKindFilter(labels)})` : `helpers.getAncestorsBy(args.current, () => true)`,
36
+ HAS_TYPE: () => `helpers.getType(args.current)`,
37
+ HAS_NAME: () => `args.current.name ? [args.current.name] : []`,
38
+ // --- Direct AST property relationships ---
39
+ HAS_INITIALIZER: () => `args.current.initializer ? [args.current.initializer] : []`,
40
+ HAS_PARAMETERS: () => `args.current.parameters ? [...args.current.parameters] : []`,
41
+ HAS_EXPRESSION: () => `args.current.expression ? [args.current.expression] : []`,
42
+ HAS_TYPE_ANNOTATION: () => `args.current.type ? [args.current.type] : []`,
43
+ HAS_BODY: () => `args.current.body ? [args.current.body] : []`,
44
+ HAS_MEMBERS: () => `args.current.members ? [...args.current.members] : []`,
45
+ HAS_PROPERTIES: () => `args.current.properties ? [...args.current.properties] : []`,
46
+ HAS_ARGUMENTS: () => `args.current.arguments ? [...args.current.arguments] : []`,
47
+ HAS_DECORATORS: () => `args.ts.getDecorators(args.current) || []`,
48
+ // --- Heritage relationships ---
49
+ EXTENDS: () => `(() => { const c = (args.current.heritageClauses || []).find(c => c.token === args.ts.SyntaxKind.ExtendsKeyword); return c ? [...c.types] : []; })()`,
50
+ IMPLEMENTS: () => `(() => { const c = (args.current.heritageClauses || []).find(c => c.token === args.ts.SyntaxKind.ImplementsKeyword); return c ? [...c.types] : []; })()`
51
+ };
52
+ var buildConfigFromQuery = (query2) => {
53
+ const nodeExtractors = {};
54
+ const matchStatements = query2.statements.filter(
55
+ (s) => s.type === "MatchStatement"
56
+ );
57
+ const variableToLabels = {};
58
+ for (const match of matchStatements) {
59
+ for (const pattern of match.patterns) {
60
+ if (pattern.variable && pattern.labels.length > 0) {
61
+ variableToLabels[pattern.variable] = pattern.labels;
62
+ }
63
+ }
64
+ }
65
+ for (const match of matchStatements) {
66
+ buildExtractorsFromMatch(match, nodeExtractors, variableToLabels);
67
+ }
68
+ return { nodeExtractors };
69
+ };
70
+ var resolveLabels = (pattern, variableToLabels) => {
71
+ if (pattern.labels.length > 0)
72
+ return pattern.labels;
73
+ if (pattern.variable && variableToLabels[pattern.variable]) {
74
+ return variableToLabels[pattern.variable];
75
+ }
76
+ return [];
77
+ };
78
+ var buildExtractorsFromMatch = (match, rootNodeExtractors, variableToLabels) => {
79
+ const { patterns, relationships } = match;
80
+ if (patterns.length === 0)
81
+ return;
82
+ const rootPattern = patterns[0];
83
+ const rootLabels = resolveLabels(rootPattern, variableToLabels);
84
+ for (const label of rootLabels) {
85
+ rootNodeExtractors[label] ??= {
86
+ properties: { ...STANDARD_NODE_PROPERTIES }
87
+ };
88
+ }
89
+ if (!relationships || relationships.length === 0)
90
+ return;
91
+ let currentExtractors = rootNodeExtractors;
92
+ for (let i = 0; i < relationships.length; i++) {
93
+ const rel = relationships[i];
94
+ const sourcePattern = patterns[i];
95
+ const targetPattern = patterns[i + 1];
96
+ if (!targetPattern)
97
+ break;
98
+ const edgeType = resolveEdgeType(rel.edgeType);
99
+ const codeGenerator = getCodeGenerator(edgeType);
100
+ const sourceLabels = resolveLabels(sourcePattern, variableToLabels);
101
+ const targetLabels = resolveLabels(targetPattern, variableToLabels);
102
+ const targetNodeExtractors = {};
103
+ if (targetLabels.length === 0) {
104
+ targetNodeExtractors["*"] = {
105
+ properties: { ...STANDARD_NODE_PROPERTIES }
106
+ };
107
+ } else {
108
+ for (const label of targetLabels) {
109
+ targetNodeExtractors[label] = {
110
+ properties: { ...STANDARD_NODE_PROPERTIES }
111
+ };
112
+ }
113
+ }
114
+ for (const sourceLabel of sourceLabels) {
115
+ currentExtractors[sourceLabel] ??= {
116
+ properties: { ...STANDARD_NODE_PROPERTIES }
117
+ };
118
+ currentExtractors[sourceLabel].relationships ??= {};
119
+ currentExtractors[sourceLabel].relationships[edgeType] = {
120
+ extractorCode: codeGenerator(targetLabels),
121
+ nodeExtractors: targetNodeExtractors
122
+ };
123
+ }
124
+ currentExtractors = targetNodeExtractors;
125
+ }
126
+ };
127
+ var resolveEdgeType = (edgeType) => {
128
+ if (!edgeType) {
129
+ throw new Error(
130
+ `Relationship type is required in auto-config mode. Supported: ${Object.keys(SUPPORTED_RELATIONSHIPS).join(", ")}`
131
+ );
132
+ }
133
+ if (Array.isArray(edgeType)) {
134
+ throw new Error(
135
+ `Union relationship types are not supported in auto-config. Got: ${edgeType.join("|")}`
136
+ );
137
+ }
138
+ return edgeType;
139
+ };
140
+ var getCodeGenerator = (edgeType) => {
141
+ const generator = SUPPORTED_RELATIONSHIPS[edgeType];
142
+ if (!generator) {
143
+ throw new Error(
144
+ `Unsupported relationship type: "${edgeType}". Auto-config supports: ${Object.keys(SUPPORTED_RELATIONSHIPS).join(", ")}`
145
+ );
146
+ }
147
+ return generator;
148
+ };
149
+ var buildKindFilter = (labels) => {
150
+ return labels.map((l) => `node.kind === args.ts.SyntaxKind.${l}`).join(" || ");
151
+ };
152
+
153
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/extractors/extractor.ts
154
+ function executeExtractor(code, context) {
155
+ try {
156
+ const fn = new Function(
157
+ ...Object.keys(context),
158
+ `
159
+ return ${code}
160
+ `
161
+ );
162
+ return fn(...Object.values(context));
163
+ } catch (error) {
164
+ throw new Error(`Extractor execution failed: ${error.message}`);
165
+ }
166
+ }
167
+ function extractNodeProperties(context, propertyExectorMap = {}) {
168
+ const properties = {};
169
+ for (const [propertyName, extractorCode] of Object.entries(
170
+ propertyExectorMap
171
+ )) {
172
+ const value = executeExtractor(extractorCode, context);
173
+ properties[propertyName] = value;
174
+ }
175
+ return properties;
176
+ }
177
+
178
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/code-graph.builder.ts
179
+ import * as ts7 from "typescript";
180
+
181
+ // libs/cli/code-graph-core/src/lib/codegular/node.ts
182
+ import * as ts3 from "typescript";
183
+
184
+ // libs/cli/code-graph-core/src/lib/codegular/string.ts
185
+ import * as ts2 from "typescript";
186
+
187
+ // libs/cli/code-graph-core/src/lib/codegular/utils.ts
188
+ import * as ts from "typescript";
189
+ var { forEachChild: forEachChild2, SyntaxKind } = ts;
190
+ function getAncestorsBy(node, predicate) {
191
+ let currentNode = node;
192
+ const nodes = [];
193
+ while (currentNode) {
194
+ if (predicate(currentNode)) {
195
+ nodes.push(currentNode);
196
+ }
197
+ const parent = currentNode?.parent;
198
+ if (!parent) {
199
+ break;
200
+ }
201
+ currentNode = parent;
202
+ }
203
+ return nodes;
204
+ }
205
+ function getDescendantsBy(node, predicate) {
206
+ let foundElements = [];
207
+ let queue = [node];
208
+ while (queue.length > 0) {
209
+ const currentNode = queue.shift();
210
+ const children = Array.from(
211
+ currentNode?.getChildren(node.getSourceFile()) || []
212
+ );
213
+ foundElements = [
214
+ ...foundElements,
215
+ ...children.filter((node2) => predicate(node2))
216
+ ];
217
+ queue = [...queue, ...children];
218
+ }
219
+ return foundElements;
220
+ }
221
+ function getDescendantsByKind(node, kind) {
222
+ return getDescendantsBy(node, (node2) => node2.kind === kind);
223
+ }
224
+ var getSolutionSourceFiles = (program) => {
225
+ const allSourceFiles = program.getSourceFiles();
226
+ const sourceFiles = allSourceFiles.filter((sourceFile) => {
227
+ const fileName = sourceFile.fileName;
228
+ if (fileName.includes("node_modules")) {
229
+ return false;
230
+ }
231
+ if (fileName.endsWith(".d.ts")) {
232
+ return false;
233
+ }
234
+ return true;
235
+ });
236
+ return sourceFiles;
237
+ };
238
+
239
+ // libs/cli/code-graph-core/src/lib/codegular/string.ts
240
+ var { SyntaxKind: SyntaxKind3 } = ts2;
241
+
242
+ // libs/cli/code-graph-core/src/lib/codegular/node.ts
243
+ var { forEachChild: forEachChild3, SyntaxKind: SyntaxKind4 } = ts3;
244
+
245
+ // libs/cli/code-graph-core/src/lib/codegular/program.ts
246
+ import * as path from "path";
247
+ import * as ts4 from "typescript";
248
+ var createProgramFromTsConfig = (configPath) => {
249
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile);
250
+ if (configFile.error) {
251
+ const errorMessage = ts4.formatDiagnostic(configFile.error, {
252
+ getCurrentDirectory: () => process.cwd(),
253
+ getNewLine: () => "\n",
254
+ getCanonicalFileName: (fileName) => fileName
255
+ });
256
+ throw new Error(`Error reading tsconfig at ${configPath}: ${errorMessage}`);
257
+ }
258
+ const parsedConfig = ts4.parseJsonConfigFileContent(
259
+ configFile.config,
260
+ ts4.sys,
261
+ path.dirname(configPath)
262
+ );
263
+ return ts4.createProgram(
264
+ parsedConfig.fileNames,
265
+ parsedConfig.options,
266
+ ts4.createCompilerHost(parsedConfig.options)
267
+ );
268
+ };
269
+
270
+ // libs/cli/code-graph-core/src/lib/codegular/type-checker.ts
271
+ import * as ts5 from "typescript";
272
+
273
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/code.graph.ts
274
+ var addNode = (graph, node) => {
275
+ graph.nodesById[node.id] = node;
276
+ return graph;
277
+ };
278
+ var getEdgeKey = (edge) => {
279
+ return `${edge.source}|${edge.target}|${edge.type}`;
280
+ };
281
+ var addEdge = (graph, edge) => {
282
+ const edgeKey = getEdgeKey(edge);
283
+ const sourceEdges = graph.edgesBySource[edge.source];
284
+ if (sourceEdges) {
285
+ const sourceEdgeKeys = new Set(sourceEdges.map(getEdgeKey));
286
+ if (sourceEdgeKeys.has(edgeKey)) {
287
+ return graph;
288
+ }
289
+ }
290
+ const targetEdges = graph.edgesByTarget[edge.target];
291
+ if (targetEdges) {
292
+ const targetEdgeKeys = new Set(targetEdges.map(getEdgeKey));
293
+ if (targetEdgeKeys.has(edgeKey)) {
294
+ return graph;
295
+ }
296
+ }
297
+ if (!graph.edgesBySource[edge.source]) {
298
+ graph.edgesBySource[edge.source] = [];
299
+ }
300
+ graph.edgesBySource[edge.source].push(edge);
301
+ if (!graph.edgesByTarget[edge.target]) {
302
+ graph.edgesByTarget[edge.target] = [];
303
+ }
304
+ graph.edgesByTarget[edge.target].push(edge);
305
+ return graph;
306
+ };
307
+
308
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/utils.ts
309
+ import * as ts6 from "typescript";
310
+ var getSyntaxKindName = (kind) => {
311
+ return ts6.SyntaxKind[kind];
312
+ };
313
+ var generateNodeId = (n) => {
314
+ const s = n.getSourceFile();
315
+ const start = s.getLineAndCharacterOfPosition(n.getStart(s));
316
+ const end = s.getLineAndCharacterOfPosition(n.getEnd());
317
+ return `${s.fileName}.${start.line}.${start.character}.${end.line}.${end.character}`;
318
+ };
319
+
320
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/type.processor.ts
321
+ var processType = (context, nodeExtractors, codeGraph) => {
322
+ const nodeId = context.args.typeChecker.typeToString(
323
+ context.args.current
324
+ );
325
+ const kindName = "Type";
326
+ const nodeConfig = nodeExtractors[kindName];
327
+ const graphNode = {
328
+ ...nodeConfig && nodeConfig.properties ? extractNodeProperties(context, nodeConfig.properties) : {},
329
+ id: nodeId,
330
+ labels: [kindName]
331
+ };
332
+ addNode(codeGraph, graphNode);
333
+ processRelationships(nodeId, context, nodeConfig, codeGraph);
334
+ return graphNode;
335
+ };
336
+
337
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/relationship.processor.ts
338
+ function isTsNode(obj) {
339
+ return obj && "kind" in obj && typeof obj.kind === "number";
340
+ }
341
+ function isTsType(obj) {
342
+ return obj && "flags" in obj && typeof obj.flags === "number" && !("kind" in obj);
343
+ }
344
+ var processRelationships = (parentId, context, nodeConfig, codeGraph) => {
345
+ if (nodeConfig?.relationships) {
346
+ for (const relationshipName of Object.keys(nodeConfig.relationships)) {
347
+ const relationshipExtractorFnCode = nodeConfig.relationships[relationshipName].extractorCode;
348
+ const relationshipNodeExtractors = nodeConfig.relationships[relationshipName].nodeExtractors;
349
+ Object.keys(relationshipNodeExtractors).forEach((label) => {
350
+ const children = executeExtractor(relationshipExtractorFnCode, context);
351
+ children.forEach(
352
+ (child) => {
353
+ if (isTsType(child)) {
354
+ const childNode = processType(
355
+ { ...context, args: { ...context.args, current: child } },
356
+ relationshipNodeExtractors,
357
+ codeGraph
358
+ );
359
+ addEdge(codeGraph, {
360
+ source: parentId,
361
+ target: childNode.id,
362
+ type: relationshipName
363
+ });
364
+ } else if (isTsNode(child)) {
365
+ const childNode = processNode(
366
+ { ...context, args: { ...context.args, current: child } },
367
+ relationshipNodeExtractors,
368
+ codeGraph
369
+ );
370
+ addEdge(codeGraph, {
371
+ source: parentId,
372
+ target: childNode.id,
373
+ type: relationshipName
374
+ });
375
+ } else {
376
+ throw new Error(
377
+ `The code provided: ${relationshipExtractorFnCode} returned an invalid child: ${child}. We support only ts.Node and ts.Type array returns from executed code`
378
+ );
379
+ }
380
+ }
381
+ );
382
+ });
383
+ }
384
+ }
385
+ };
386
+
387
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/node.processor.ts
388
+ var processNode = (context, nodeExtractors, codeGraph) => {
389
+ const nodeId = generateNodeId(context.args.current);
390
+ const kindName = getSyntaxKindName(context.args.current.kind);
391
+ const nodeConfig = nodeExtractors[kindName] ?? nodeExtractors["*"];
392
+ const graphNode = {
393
+ ...nodeConfig && nodeConfig.properties ? extractNodeProperties(context, nodeConfig.properties) : {},
394
+ id: nodeId,
395
+ labels: [kindName]
396
+ };
397
+ addNode(codeGraph, graphNode);
398
+ processRelationships(nodeId, context, nodeConfig, codeGraph);
399
+ return graphNode;
400
+ };
401
+
402
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/code-graph.builder.ts
403
+ var buildCodeGraph = (program, config) => {
404
+ const codeGraph = {
405
+ nodesById: {},
406
+ edgesBySource: {},
407
+ edgesByTarget: {}
408
+ };
409
+ const sourceFiles = getSolutionSourceFiles(program);
410
+ sourceFiles.forEach((sourceFile) => {
411
+ Object.keys(config.nodeExtractors).forEach((kindName) => {
412
+ const nodes = getDescendantsByKind(sourceFile, ts7.SyntaxKind[kindName]);
413
+ nodes.forEach((node) => {
414
+ const extractorContext = {
415
+ args: {
416
+ current: node,
417
+ sourceFile,
418
+ typeChecker: program.getTypeChecker(),
419
+ ts: ts7
420
+ },
421
+ helpers: {
422
+ getDescendantsBy,
423
+ getAncestorsBy,
424
+ getType: (node2) => []
425
+ // TODO: add type extraction
426
+ // getType(
427
+ // node,
428
+ // program.getTypeChecker(),
429
+ // new TypeResolutionTracker(),
430
+ // ),
431
+ // getDeclarations: (node: ts.Type | ts.Node) =>
432
+ // getDeclarations(node, context.typeChecker),
433
+ }
434
+ };
435
+ processNode(extractorContext, config.nodeExtractors, codeGraph);
436
+ });
437
+ });
438
+ });
439
+ return codeGraph;
440
+ };
441
+
442
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/condition-evaluator.ts
443
+ var ConditionEvaluator = class {
444
+ // @TODO: apply where at match level
445
+ /**
446
+ * Apply WHERE clause filtering to matches
447
+ */
448
+ applyWhereClauseIfNeeded(matches, where) {
449
+ return matches.filter((match) => {
450
+ const conditionResults = where.conditions.map(
451
+ (condition) => this.evaluateCondition(match, condition)
452
+ );
453
+ const logic = where.logic || "AND";
454
+ if (logic === "AND") {
455
+ return conditionResults.every((result) => result === true);
456
+ } else {
457
+ return conditionResults.some((result) => result === true);
458
+ }
459
+ });
460
+ }
461
+ // @fixme move to utils
462
+ /**
463
+ * Escape special regex characters in a string
464
+ */
465
+ escapeRegex(str) {
466
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
467
+ }
468
+ /**
469
+ * Evaluate a single WHERE condition
470
+ * Supports flat properties only (no nesting, like Neo4j Cypher)
471
+ * Handles null values from optional matches (null values fail the condition)
472
+ */
473
+ evaluateCondition(match, condition) {
474
+ const value = match[condition.variable];
475
+ if ((condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") && !condition.property) {
476
+ const isNull = value === null || value === void 0;
477
+ return condition.operator === "IS NULL" ? isNull : !isNull;
478
+ }
479
+ if (!value || value === null) {
480
+ return false;
481
+ }
482
+ let baseObject;
483
+ if ("id" in value) {
484
+ baseObject = value;
485
+ } else if ("type" in value && "source" in value) {
486
+ baseObject = value;
487
+ } else {
488
+ return false;
489
+ }
490
+ let actualValue;
491
+ if (!condition.property) {
492
+ actualValue = baseObject;
493
+ } else {
494
+ actualValue = baseObject[condition.property];
495
+ if (actualValue === void 0) {
496
+ if (condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") {
497
+ actualValue = void 0;
498
+ } else {
499
+ return false;
500
+ }
501
+ }
502
+ }
503
+ const conditionValue = condition.value;
504
+ switch (condition.operator) {
505
+ case "=":
506
+ return actualValue === conditionValue;
507
+ case "!=":
508
+ return actualValue !== conditionValue;
509
+ case ">":
510
+ return actualValue > conditionValue;
511
+ case "<":
512
+ return actualValue < conditionValue;
513
+ case ">=":
514
+ return actualValue >= conditionValue;
515
+ case "<=":
516
+ return actualValue <= conditionValue;
517
+ case "IS NULL":
518
+ return actualValue === null || actualValue === void 0;
519
+ case "IS NOT NULL":
520
+ return actualValue !== null && actualValue !== void 0;
521
+ case "IN":
522
+ if (Array.isArray(conditionValue)) {
523
+ if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
524
+ const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
525
+ return extractedValues.includes(actualValue);
526
+ }
527
+ return conditionValue.includes(actualValue);
528
+ }
529
+ return false;
530
+ case "NOT IN":
531
+ if (Array.isArray(conditionValue)) {
532
+ if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
533
+ const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
534
+ return !extractedValues.includes(actualValue);
535
+ }
536
+ return !conditionValue.includes(actualValue);
537
+ }
538
+ return true;
539
+ case "STARTS WITH":
540
+ return this.testRegex(
541
+ new RegExp(`^${this.escapeRegex(conditionValue)}`),
542
+ actualValue
543
+ );
544
+ case "ENDS WITH":
545
+ return this.testRegex(
546
+ new RegExp(`${this.escapeRegex(conditionValue)}$`),
547
+ actualValue
548
+ );
549
+ case "CONTAINS":
550
+ return this.testRegex(
551
+ new RegExp(this.escapeRegex(conditionValue)),
552
+ actualValue
553
+ );
554
+ case "=~":
555
+ return this.testRegex(new RegExp(conditionValue), actualValue);
556
+ default:
557
+ throw new Error(`Unsupported operator: ${condition.operator}`);
558
+ }
559
+ }
560
+ testRegex(regexp, actualValue) {
561
+ if (actualValue === null || actualValue === void 0) {
562
+ return false;
563
+ }
564
+ if (typeof actualValue !== "string" && typeof actualValue !== "number" && typeof actualValue !== "boolean") {
565
+ return false;
566
+ }
567
+ const regexStr = typeof actualValue === "string" ? actualValue : String(actualValue);
568
+ try {
569
+ return regexp.test(regexStr);
570
+ } catch {
571
+ return false;
572
+ }
573
+ }
574
+ };
575
+
576
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/utils.ts
577
+ var Annonymizer = class {
578
+ constructor() {
579
+ this.anonymousVariablePrefix = "annonymous_";
580
+ }
581
+ generate(node) {
582
+ return this.anonymousVariablePrefix + node.id;
583
+ }
584
+ isAnonymous(variable) {
585
+ return variable.startsWith(this.anonymousVariablePrefix);
586
+ }
587
+ };
588
+
589
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/match-engine.ts
590
+ var MatchEngine = class {
591
+ constructor(patternMatcher, traverseEngine) {
592
+ this.patternMatcher = patternMatcher;
593
+ this.traverseEngine = traverseEngine;
594
+ this.annonymizer = new Annonymizer();
595
+ }
596
+ processMatchStatement(graph, match, existingResults = []) {
597
+ if (match.patterns.length === 0)
598
+ return existingResults;
599
+ const starting = match.patterns[0];
600
+ const allResults = [];
601
+ this.patternMatcher.filterMatchingNodes(graph, starting).forEach((node) => {
602
+ const matches = this.traverseEngine.traverseRelationships(
603
+ graph,
604
+ node,
605
+ starting.variable || this.annonymizer.generate(node),
606
+ match.patterns.slice(1),
607
+ match.relationships
608
+ );
609
+ allResults.push(...matches);
610
+ });
611
+ if (existingResults.length > 0) {
612
+ if (match.optional) {
613
+ return this.mergeOptionalResults(existingResults, allResults, match);
614
+ } else {
615
+ return this.mergeResults(existingResults, allResults);
616
+ }
617
+ }
618
+ return allResults;
619
+ }
620
+ mergeResults(existingResults, newResults) {
621
+ if (existingResults.length === 0) {
622
+ return newResults;
623
+ }
624
+ const mergedResults = [];
625
+ for (const existingResult of existingResults) {
626
+ for (const newResult of newResults) {
627
+ if (this.canMergeResult(existingResult, newResult)) {
628
+ mergedResults.push({ ...existingResult, ...newResult });
629
+ }
630
+ }
631
+ }
632
+ return mergedResults;
633
+ }
634
+ /**
635
+ * Merge optional match results with existing results (left outer join)
636
+ * - Keeps all existing results
637
+ * - Merges optional results where variables overlap
638
+ * - Adds nulls for optional variables when no match is found
639
+ */
640
+ mergeOptionalResults(existingResults, optionalResults, match) {
641
+ if (existingResults.length === 0) {
642
+ return optionalResults;
643
+ }
644
+ const optionalVarNames = /* @__PURE__ */ new Set();
645
+ for (const pattern of match.patterns) {
646
+ if (pattern.variable) {
647
+ optionalVarNames.add(pattern.variable);
648
+ }
649
+ }
650
+ if (match.relationships) {
651
+ for (const rel of match.relationships) {
652
+ if (rel.variable) {
653
+ optionalVarNames.add(rel.variable);
654
+ }
655
+ }
656
+ }
657
+ if (optionalResults.length === 0) {
658
+ return existingResults.map((result) => {
659
+ const resultWithNulls = { ...result };
660
+ for (const varName of optionalVarNames) {
661
+ if (!(varName in resultWithNulls)) {
662
+ resultWithNulls[varName] = null;
663
+ }
664
+ }
665
+ return resultWithNulls;
666
+ });
667
+ }
668
+ for (const optionalResult of optionalResults) {
669
+ for (const key in optionalResult) {
670
+ optionalVarNames.add(key);
671
+ }
672
+ }
673
+ const mergedResults = [];
674
+ for (const existingResult of existingResults) {
675
+ let hasMatch = false;
676
+ for (const optionalResult of optionalResults) {
677
+ if (this.canMergeResult(existingResult, optionalResult)) {
678
+ mergedResults.push({ ...existingResult, ...optionalResult });
679
+ hasMatch = true;
680
+ }
681
+ }
682
+ if (!hasMatch) {
683
+ const resultWithNulls = { ...existingResult };
684
+ for (const varName of optionalVarNames) {
685
+ if (!(varName in resultWithNulls)) {
686
+ resultWithNulls[varName] = null;
687
+ }
688
+ }
689
+ mergedResults.push(resultWithNulls);
690
+ }
691
+ }
692
+ return mergedResults;
693
+ }
694
+ /**
695
+ * Check if additionalResult can be merged with existingResult
696
+ * Returns true if shared variables match (same node/edge) or if there are no conflicts
697
+ * Does NOT mutate any parameters
698
+ */
699
+ canMergeResult(additionalResult, existingResult) {
700
+ for (const key in additionalResult) {
701
+ if (key in existingResult) {
702
+ const existing = existingResult[key];
703
+ const additional = additionalResult[key];
704
+ if (existing !== null && additional !== null) {
705
+ if ("id" in existing && "id" in additional && existing.id !== additional.id) {
706
+ return false;
707
+ }
708
+ } else if (existing !== additional) {
709
+ return false;
710
+ }
711
+ }
712
+ }
713
+ return true;
714
+ }
715
+ };
716
+
717
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/pattern-matcher.ts
718
+ var PatternMatcher = class {
719
+ /**
720
+ * Check if an edge type matches the relationship pattern's edge type(s)
721
+ * Supports both single edge type (string) and multiple edge types (array)
722
+ */
723
+ matchesEdgeType(edgeType, patternEdgeType) {
724
+ if (!patternEdgeType) {
725
+ return true;
726
+ }
727
+ if (typeof patternEdgeType === "string") {
728
+ return edgeType === patternEdgeType;
729
+ }
730
+ return patternEdgeType.includes(edgeType);
731
+ }
732
+ /**
733
+ * Deep equality check for arrays
734
+ */
735
+ arraysEqual(a, b) {
736
+ if (a.length !== b.length) {
737
+ return false;
738
+ }
739
+ for (let i = 0; i < a.length; i++) {
740
+ if (Array.isArray(a[i]) && Array.isArray(b[i])) {
741
+ if (!this.arraysEqual(a[i], b[i])) {
742
+ return false;
743
+ }
744
+ } else if (a[i] !== b[i]) {
745
+ return false;
746
+ }
747
+ }
748
+ return true;
749
+ }
750
+ /**
751
+ * Check if two values are equal (handles arrays with deep equality)
752
+ */
753
+ valuesEqual(expected, actual) {
754
+ if (Array.isArray(expected) && Array.isArray(actual)) {
755
+ return this.arraysEqual(expected, actual);
756
+ }
757
+ return expected === actual;
758
+ }
759
+ /**
760
+ * Check if a node matches a pattern (labels and properties)
761
+ */
762
+ doesNodeMatchPattern(node, pattern) {
763
+ if (pattern.labels.length > 0) {
764
+ const labelOperator = pattern.labelOperator || "AND";
765
+ if (labelOperator === "AND") {
766
+ if (!pattern.labels.every((label) => node.labels.includes(label))) {
767
+ return false;
768
+ }
769
+ } else {
770
+ if (!pattern.labels.some((label) => node.labels.includes(label))) {
771
+ return false;
772
+ }
773
+ }
774
+ }
775
+ if (pattern.properties) {
776
+ const notFound = Object.entries(pattern.properties).find(
777
+ ([key, expectedValue]) => !this.valuesEqual(expectedValue, node[key])
778
+ ) !== void 0;
779
+ if (notFound) {
780
+ return false;
781
+ }
782
+ }
783
+ return true;
784
+ }
785
+ findMatchingNodes(graph, pattern) {
786
+ return Object.values(graph.nodesById).find(
787
+ (node) => this.doesNodeMatchPattern(node, pattern)
788
+ );
789
+ }
790
+ /**
791
+ * Match a single node pattern
792
+ * Finds all nodes in the graph that match the given pattern
793
+ * Reuses matchesPattern to avoid code duplication
794
+ */
795
+ filterMatchingNodes(graph, pattern) {
796
+ return Object.values(graph.nodesById).filter(
797
+ (node) => this.doesNodeMatchPattern(node, pattern)
798
+ );
799
+ }
800
+ };
801
+
802
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/relationship-navigator.ts
803
+ var RelationshipNavigator = class {
804
+ constructor(patternMatcher) {
805
+ this.patternMatcher = patternMatcher;
806
+ }
807
+ /**
808
+ * Find nodes and edges connected via a relationship from the given node
809
+ * Returns both the connected node and the edge that connects them
810
+ */
811
+ findConnectedNodesAndEdges(graph, node, relationship) {
812
+ const connections = [];
813
+ const seenNodeIds = /* @__PURE__ */ new Set();
814
+ if (relationship.direction === "outgoing" || relationship.direction === "both") {
815
+ const outgoingEdges = graph.edgesBySource[node.id] || [];
816
+ for (const edge of outgoingEdges) {
817
+ if (this.patternMatcher.matchesEdgeType(edge.type, relationship.edgeType)) {
818
+ const targetNode = graph.nodesById[edge.target];
819
+ if (targetNode && !seenNodeIds.has(targetNode.id)) {
820
+ seenNodeIds.add(targetNode.id);
821
+ connections.push({ node: targetNode, edge });
822
+ }
823
+ }
824
+ }
825
+ }
826
+ if (relationship.direction === "incoming" || relationship.direction === "both") {
827
+ const incomingEdges = graph.edgesByTarget[node.id] || [];
828
+ for (const edge of incomingEdges) {
829
+ if (this.patternMatcher.matchesEdgeType(edge.type, relationship.edgeType)) {
830
+ const sourceNode = graph.nodesById[edge.source];
831
+ if (sourceNode && !seenNodeIds.has(sourceNode.id)) {
832
+ seenNodeIds.add(sourceNode.id);
833
+ connections.push({ node: sourceNode, edge });
834
+ }
835
+ }
836
+ }
837
+ }
838
+ return connections;
839
+ }
840
+ };
841
+
842
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/result-formatter.ts
843
+ var ResultFormatter = class {
844
+ constructor() {
845
+ this.annonymizer = new Annonymizer();
846
+ }
847
+ /**
848
+ * Format a single match value (node or edge) based on extractData flag
849
+ */
850
+ formatValue(value) {
851
+ if (value === null || value === void 0) {
852
+ return null;
853
+ }
854
+ if ("id" in value && "labels" in value) {
855
+ const node = value;
856
+ return node;
857
+ } else {
858
+ const edge = value;
859
+ return edge;
860
+ }
861
+ }
862
+ /**
863
+ * Sort results based on ORDER BY clause
864
+ */
865
+ sortResults(results, orderBy) {
866
+ return [...results].sort((a, b) => {
867
+ for (const item of orderBy.items) {
868
+ const valA = this.getPropertyValue(a[item.variable], item.property);
869
+ const valB = this.getPropertyValue(b[item.variable], item.property);
870
+ const comparison = this.compareValues(valA, valB, item.direction);
871
+ if (comparison !== 0)
872
+ return comparison;
873
+ }
874
+ return 0;
875
+ });
876
+ }
877
+ /**
878
+ * Get property value from an object, handling nested property access
879
+ */
880
+ getPropertyValue(obj, property) {
881
+ if (obj === null || obj === void 0)
882
+ return null;
883
+ return property ? obj[property] : obj;
884
+ }
885
+ /**
886
+ * Compare two values with Neo4j-style NULL handling
887
+ * ASC: NULLs first, then ascending values
888
+ * DESC: Values descending, then NULLs last
889
+ */
890
+ compareValues(a, b, direction) {
891
+ const aIsNull = a === null || a === void 0;
892
+ const bIsNull = b === null || b === void 0;
893
+ if (aIsNull && bIsNull)
894
+ return 0;
895
+ if (aIsNull)
896
+ return direction === "ASC" ? -1 : 1;
897
+ if (bIsNull)
898
+ return direction === "ASC" ? 1 : -1;
899
+ let result;
900
+ if (typeof a === "string" && typeof b === "string") {
901
+ result = a.localeCompare(b);
902
+ } else {
903
+ result = a < b ? -1 : a > b ? 1 : 0;
904
+ }
905
+ return direction === "DESC" ? -result : result;
906
+ }
907
+ /**
908
+ * Format results with RETURN clause
909
+ * Only includes variables specified in RETURN clause, with optional aliases
910
+ * If returnAll is true (RETURN *), returns all variables
911
+ * If distinct is true, deduplicate results based on returned variables' ids
912
+ */
913
+ formatResults(results, returnClause) {
914
+ let processedResults = returnClause.limit ? results.slice(0, returnClause.limit) : results;
915
+ if (returnClause.distinct) {
916
+ processedResults = this.deduplicateResults(processedResults, returnClause);
917
+ }
918
+ if (returnClause.returnAll) {
919
+ return this.formatDefault(processedResults);
920
+ }
921
+ return processedResults.map((match) => {
922
+ const result = {};
923
+ for (const item of returnClause.items) {
924
+ const variable = item.expression;
925
+ if (this.annonymizer.isAnonymous(variable)) {
926
+ continue;
927
+ }
928
+ const alias = item.alias || variable;
929
+ if (variable in match) {
930
+ const value = match[variable];
931
+ result[alias] = this.formatValue(value);
932
+ } else {
933
+ throw new Error(`Variable ${variable} not found in match results`);
934
+ }
935
+ }
936
+ return result;
937
+ });
938
+ }
939
+ /**
940
+ * Deduplicate results based on returned variables' ids
941
+ */
942
+ deduplicateResults(results, returnClause) {
943
+ const seen = /* @__PURE__ */ new Map();
944
+ for (const match of results) {
945
+ const key = returnClause.items.map((item) => this.getValueId(match[item.expression])).join("|");
946
+ if (!seen.has(key)) {
947
+ seen.set(key, match);
948
+ }
949
+ }
950
+ return Array.from(seen.values());
951
+ }
952
+ /**
953
+ * Get a unique identifier for a match result value
954
+ */
955
+ getValueId(value) {
956
+ if (value === null || value === void 0) {
957
+ return "null";
958
+ }
959
+ if ("id" in value && "labels" in value) {
960
+ return value.id;
961
+ }
962
+ const edge = value;
963
+ return `edge:${edge.source}|${edge.target}|${edge.type}`;
964
+ }
965
+ /**
966
+ * Format results without RETURN clause (default)
967
+ * Returns all matched variables keyed by their variable names
968
+ */
969
+ formatDefault(matches) {
970
+ return matches.map((match) => {
971
+ const result = {};
972
+ for (const [varName, value] of Object.entries(match)) {
973
+ if (this.annonymizer.isAnonymous(varName)) {
974
+ continue;
975
+ }
976
+ result[varName] = this.formatValue(value);
977
+ }
978
+ return result;
979
+ });
980
+ }
981
+ };
982
+
983
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/traverse-engine.ts
984
+ var TraverseEngine = class {
985
+ constructor(patternMatcher, relationshipNavigator) {
986
+ this.patternMatcher = patternMatcher;
987
+ this.relationshipNavigator = relationshipNavigator;
988
+ this.annonymizer = new Annonymizer();
989
+ }
990
+ /**
991
+ * Recursively traverse relationships to match subsequent patterns
992
+ * Supports returning edges as variables when relationship variable is specified
993
+ */
994
+ traverseRelationships(graph, currentNode, currentVarName, remainingPatterns, relationships) {
995
+ if (remainingPatterns.length === 0) {
996
+ return [{ [currentVarName]: currentNode }];
997
+ }
998
+ const results = [];
999
+ const nextPattern = remainingPatterns[0];
1000
+ const nextRelationship = relationships[0];
1001
+ const nextVarName = nextPattern.variable || this.annonymizer.generate(currentNode);
1002
+ const remainingPatterns_ = remainingPatterns.slice(1);
1003
+ const remainingRelationships = relationships.slice(1);
1004
+ if (nextRelationship.variableLength) {
1005
+ return this.traverseVariableLengthPath(
1006
+ graph,
1007
+ currentNode,
1008
+ currentVarName,
1009
+ nextRelationship,
1010
+ nextPattern,
1011
+ nextVarName,
1012
+ remainingPatterns_,
1013
+ remainingRelationships
1014
+ );
1015
+ }
1016
+ const connections = this.relationshipNavigator.findConnectedNodesAndEdges(
1017
+ graph,
1018
+ currentNode,
1019
+ nextRelationship
1020
+ );
1021
+ for (const { node: connectedNode, edge } of connections) {
1022
+ if (this.patternMatcher.doesNodeMatchPattern(connectedNode, nextPattern)) {
1023
+ const subMatches = this.traverseRelationships(
1024
+ graph,
1025
+ connectedNode,
1026
+ nextVarName,
1027
+ remainingPatterns_,
1028
+ remainingRelationships
1029
+ );
1030
+ for (const subMatch of subMatches) {
1031
+ const result = {
1032
+ [currentVarName]: currentNode,
1033
+ [nextVarName]: connectedNode,
1034
+ // Always include the connected node
1035
+ ...subMatch
1036
+ };
1037
+ if (nextRelationship.variable && edge) {
1038
+ result[nextRelationship.variable] = edge;
1039
+ }
1040
+ results.push(result);
1041
+ }
1042
+ }
1043
+ }
1044
+ return results;
1045
+ }
1046
+ /**
1047
+ * Traverse variable-length paths (zero or more edges)
1048
+ * Handles cycles by tracking visited nodes
1049
+ */
1050
+ traverseVariableLengthPath(graph, startNode, startVarName, relationship, targetPattern, targetVarName, remainingPatterns, remainingRelationships) {
1051
+ const results = [];
1052
+ const minLength = relationship.minLength ?? 0;
1053
+ const maxLength = relationship.maxLength ?? Infinity;
1054
+ const traverse = (currentNode, pathLength, visited, pathEdges) => {
1055
+ if (pathLength >= minLength && this.patternMatcher.doesNodeMatchPattern(currentNode, targetPattern)) {
1056
+ const subMatches = this.traverseRelationships(
1057
+ graph,
1058
+ currentNode,
1059
+ targetVarName,
1060
+ remainingPatterns,
1061
+ remainingRelationships
1062
+ );
1063
+ for (const subMatch of subMatches) {
1064
+ const result = {
1065
+ [startVarName]: startNode,
1066
+ [targetVarName]: currentNode,
1067
+ ...subMatch
1068
+ };
1069
+ if (relationship.variable && pathEdges.length > 0) {
1070
+ result[relationship.variable] = pathEdges[pathEdges.length - 1];
1071
+ }
1072
+ results.push(result);
1073
+ }
1074
+ }
1075
+ if (pathLength < maxLength) {
1076
+ const connections2 = this.relationshipNavigator.findConnectedNodesAndEdges(
1077
+ graph,
1078
+ currentNode,
1079
+ relationship
1080
+ );
1081
+ for (const { node: nextNode, edge } of connections2) {
1082
+ if (!visited.has(nextNode.id)) {
1083
+ const newVisited = new Set(visited);
1084
+ newVisited.add(nextNode.id);
1085
+ traverse(nextNode, pathLength + 1, newVisited, [
1086
+ ...pathEdges,
1087
+ edge
1088
+ ]);
1089
+ }
1090
+ }
1091
+ }
1092
+ };
1093
+ if (minLength === 0 && this.patternMatcher.doesNodeMatchPattern(startNode, targetPattern)) {
1094
+ const subMatches = this.traverseRelationships(
1095
+ graph,
1096
+ startNode,
1097
+ targetVarName,
1098
+ remainingPatterns,
1099
+ remainingRelationships
1100
+ );
1101
+ for (const subMatch of subMatches) {
1102
+ const result = {
1103
+ [startVarName]: startNode,
1104
+ [targetVarName]: startNode,
1105
+ ...subMatch
1106
+ };
1107
+ results.push(result);
1108
+ }
1109
+ }
1110
+ const connections = this.relationshipNavigator.findConnectedNodesAndEdges(
1111
+ graph,
1112
+ startNode,
1113
+ relationship
1114
+ );
1115
+ for (const { node: nextNode, edge } of connections) {
1116
+ traverse(nextNode, 1, /* @__PURE__ */ new Set([startNode.id, nextNode.id]), [edge]);
1117
+ }
1118
+ return results;
1119
+ }
1120
+ };
1121
+
1122
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/executor.ts
1123
+ var CypherExecutor = class {
1124
+ constructor(graph) {
1125
+ this.graph = graph;
1126
+ this.conditionEvaluator = new ConditionEvaluator();
1127
+ const patternMatcher = new PatternMatcher();
1128
+ const relationshipNavigator = new RelationshipNavigator(patternMatcher);
1129
+ const traverseEngine = new TraverseEngine(
1130
+ patternMatcher,
1131
+ relationshipNavigator
1132
+ );
1133
+ this.matchEngine = new MatchEngine(patternMatcher, traverseEngine);
1134
+ this.resultFormatter = new ResultFormatter();
1135
+ }
1136
+ /**
1137
+ * Execute a parsed Cypher query
1138
+ * @param extractData - If true (default), returns node.data. If false, returns full GraphNode objects.
1139
+ * For edges, always returns the full GraphEdge object (edges have no data property).
1140
+ * Returns results where keys are variable names and values are either node data or edge objects
1141
+ */
1142
+ execute(query2) {
1143
+ if (query2.statements.length === 0) {
1144
+ throw new Error("Query must have at least one statement");
1145
+ }
1146
+ if (query2.statements[query2.statements.length - 1].type !== "ReturnStatement") {
1147
+ throw new Error("MATCH Query must have a RETURN statement");
1148
+ }
1149
+ let results = [];
1150
+ for (const statement of query2.statements) {
1151
+ if (statement.type === "MatchStatement") {
1152
+ results = this.matchEngine.processMatchStatement(
1153
+ this.graph,
1154
+ statement,
1155
+ results
1156
+ );
1157
+ } else if (statement.type === "WhereStatement") {
1158
+ results = this.conditionEvaluator.applyWhereClauseIfNeeded(
1159
+ results,
1160
+ statement
1161
+ );
1162
+ } else if (statement.type === "OrderByStatement") {
1163
+ results = this.resultFormatter.sortResults(results, statement);
1164
+ }
1165
+ }
1166
+ const returnStatement = query2.statements[query2.statements.length - 1];
1167
+ let formattedResults = this.resultFormatter.formatResults(
1168
+ results,
1169
+ returnStatement
1170
+ );
1171
+ return formattedResults;
1172
+ }
1173
+ };
1174
+
1175
+ // libs/cli/code-graph-core/src/lib/cypher/lib/lexer.ts
1176
+ var Lexer = class {
1177
+ constructor(input) {
1178
+ this.input = input;
1179
+ this.position = 0;
1180
+ this.currentChar = input.length > 0 ? input[0] : null;
1181
+ }
1182
+ advance() {
1183
+ this.position++;
1184
+ this.currentChar = this.position >= this.input.length ? null : this.input[this.position];
1185
+ }
1186
+ skipWhitespace() {
1187
+ while (this.currentChar && /\s/.test(this.currentChar)) {
1188
+ this.advance();
1189
+ }
1190
+ }
1191
+ readIdentifier() {
1192
+ let result = "";
1193
+ while (this.currentChar && /[a-zA-Z0-9_]/.test(this.currentChar)) {
1194
+ result += this.currentChar;
1195
+ this.advance();
1196
+ }
1197
+ return result;
1198
+ }
1199
+ readString() {
1200
+ const quote = this.currentChar;
1201
+ this.advance();
1202
+ let result = "";
1203
+ while (this.currentChar && this.currentChar !== quote) {
1204
+ if (this.currentChar === "\\") {
1205
+ this.advance();
1206
+ if (this.currentChar === "n") {
1207
+ result += "\n";
1208
+ } else if (this.currentChar === "t") {
1209
+ result += " ";
1210
+ } else {
1211
+ result += this.currentChar;
1212
+ }
1213
+ this.advance();
1214
+ } else {
1215
+ result += this.currentChar;
1216
+ this.advance();
1217
+ }
1218
+ }
1219
+ if (this.currentChar === quote) {
1220
+ this.advance();
1221
+ }
1222
+ return result;
1223
+ }
1224
+ readNumber() {
1225
+ let result = "";
1226
+ while (this.currentChar && /[0-9.]/.test(this.currentChar)) {
1227
+ result += this.currentChar;
1228
+ this.advance();
1229
+ }
1230
+ return result;
1231
+ }
1232
+ peekKeyword() {
1233
+ const savedPos = this.position;
1234
+ const savedChar = this.currentChar;
1235
+ const keyword = this.readIdentifier();
1236
+ this.position = savedPos;
1237
+ this.currentChar = savedChar;
1238
+ return keyword.length > 0 ? keyword.toUpperCase() : null;
1239
+ }
1240
+ tokenize() {
1241
+ const tokens = [];
1242
+ this.position = 0;
1243
+ this.currentChar = this.input.length > 0 ? this.input[0] : null;
1244
+ while (this.currentChar !== null) {
1245
+ this.skipWhitespace();
1246
+ if (!this.currentChar)
1247
+ break;
1248
+ const startPos = this.position;
1249
+ const keyword = this.peekKeyword();
1250
+ const keywordMap = {
1251
+ MATCH: "MATCH" /* MATCH */,
1252
+ OPTIONAL: "OPTIONAL" /* OPTIONAL */,
1253
+ RETURN: "RETURN" /* RETURN */,
1254
+ DISTINCT: "DISTINCT" /* DISTINCT */,
1255
+ WHERE: "WHERE" /* WHERE */,
1256
+ AND: "AND" /* AND */,
1257
+ OR: "OR" /* OR */,
1258
+ AS: "AS" /* AS */,
1259
+ IN: "IN" /* IN */,
1260
+ NOT: "NOT" /* NOT */,
1261
+ IS: "IS" /* IS */,
1262
+ NULL: "NULL" /* NULL */,
1263
+ TRUE: "TRUE" /* TRUE */,
1264
+ FALSE: "FALSE" /* FALSE */,
1265
+ STARTS: "STARTS" /* STARTS */,
1266
+ ENDS: "ENDS" /* ENDS */,
1267
+ CONTAINS: "CONTAINS" /* CONTAINS */,
1268
+ WITH: "WITH" /* WITH */,
1269
+ LIMIT: "LIMIT" /* LIMIT */,
1270
+ ORDER: "ORDER" /* ORDER */,
1271
+ BY: "BY" /* BY */,
1272
+ ASC: "ASC" /* ASC */,
1273
+ DESC: "DESC" /* DESC */
1274
+ };
1275
+ if (keyword && keywordMap[keyword]) {
1276
+ tokens.push({
1277
+ type: keywordMap[keyword],
1278
+ value: keyword,
1279
+ position: startPos
1280
+ });
1281
+ this.readIdentifier();
1282
+ continue;
1283
+ }
1284
+ switch (this.currentChar) {
1285
+ case "(":
1286
+ tokens.push({
1287
+ type: "LPAREN" /* LPAREN */,
1288
+ value: "(",
1289
+ position: startPos
1290
+ });
1291
+ this.advance();
1292
+ break;
1293
+ case ")":
1294
+ tokens.push({
1295
+ type: "RPAREN" /* RPAREN */,
1296
+ value: ")",
1297
+ position: startPos
1298
+ });
1299
+ this.advance();
1300
+ break;
1301
+ case "[":
1302
+ tokens.push({
1303
+ type: "LBRACKET" /* LBRACKET */,
1304
+ value: "[",
1305
+ position: startPos
1306
+ });
1307
+ this.advance();
1308
+ break;
1309
+ case "]":
1310
+ tokens.push({
1311
+ type: "RBRACKET" /* RBRACKET */,
1312
+ value: "]",
1313
+ position: startPos
1314
+ });
1315
+ this.advance();
1316
+ break;
1317
+ case "{":
1318
+ tokens.push({
1319
+ type: "LBRACE" /* LBRACE */,
1320
+ value: "{",
1321
+ position: startPos
1322
+ });
1323
+ this.advance();
1324
+ break;
1325
+ case "}":
1326
+ tokens.push({
1327
+ type: "RBRACE" /* RBRACE */,
1328
+ value: "}",
1329
+ position: startPos
1330
+ });
1331
+ this.advance();
1332
+ break;
1333
+ case "<":
1334
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "-") {
1335
+ tokens.push({
1336
+ type: "ARROW" /* ARROW */,
1337
+ value: "<-",
1338
+ position: startPos
1339
+ });
1340
+ this.advance();
1341
+ this.advance();
1342
+ } else if (this.position + 1 < this.input.length && this.input[this.position + 1] === "=") {
1343
+ tokens.push({
1344
+ type: "LTE" /* LTE */,
1345
+ value: "<=",
1346
+ position: startPos
1347
+ });
1348
+ this.advance();
1349
+ this.advance();
1350
+ } else {
1351
+ tokens.push({
1352
+ type: "LT" /* LT */,
1353
+ value: "<",
1354
+ position: startPos
1355
+ });
1356
+ this.advance();
1357
+ }
1358
+ break;
1359
+ case "-":
1360
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === ">") {
1361
+ tokens.push({
1362
+ type: "ARROW" /* ARROW */,
1363
+ value: "->",
1364
+ position: startPos
1365
+ });
1366
+ this.advance();
1367
+ this.advance();
1368
+ } else {
1369
+ tokens.push({
1370
+ type: "DASH" /* DASH */,
1371
+ value: "-",
1372
+ position: startPos
1373
+ });
1374
+ this.advance();
1375
+ }
1376
+ break;
1377
+ case "=":
1378
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "~") {
1379
+ tokens.push({
1380
+ type: "REGEX_MATCH" /* REGEX_MATCH */,
1381
+ value: "=~",
1382
+ position: startPos
1383
+ });
1384
+ this.advance();
1385
+ this.advance();
1386
+ } else {
1387
+ tokens.push({
1388
+ type: "EQUALS" /* EQUALS */,
1389
+ value: "=",
1390
+ position: startPos
1391
+ });
1392
+ this.advance();
1393
+ }
1394
+ break;
1395
+ case "!":
1396
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "=") {
1397
+ tokens.push({
1398
+ type: "NOT_EQUALS" /* NOT_EQUALS */,
1399
+ value: "!=",
1400
+ position: startPos
1401
+ });
1402
+ this.advance();
1403
+ this.advance();
1404
+ } else {
1405
+ throw new Error(`Unexpected character: ! at position ${startPos}`);
1406
+ }
1407
+ break;
1408
+ case ">":
1409
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "=") {
1410
+ tokens.push({
1411
+ type: "GTE" /* GTE */,
1412
+ value: ">=",
1413
+ position: startPos
1414
+ });
1415
+ this.advance();
1416
+ this.advance();
1417
+ } else {
1418
+ tokens.push({
1419
+ type: "GT" /* GT */,
1420
+ value: ">",
1421
+ position: startPos
1422
+ });
1423
+ this.advance();
1424
+ }
1425
+ break;
1426
+ case ",":
1427
+ tokens.push({
1428
+ type: "COMMA" /* COMMA */,
1429
+ value: ",",
1430
+ position: startPos
1431
+ });
1432
+ this.advance();
1433
+ break;
1434
+ case ".":
1435
+ tokens.push({
1436
+ type: "DOT" /* DOT */,
1437
+ value: ".",
1438
+ position: startPos
1439
+ });
1440
+ this.advance();
1441
+ break;
1442
+ case "|":
1443
+ tokens.push({
1444
+ type: "PIPE" /* PIPE */,
1445
+ value: "|",
1446
+ position: startPos
1447
+ });
1448
+ this.advance();
1449
+ break;
1450
+ case "*":
1451
+ tokens.push({
1452
+ type: "ASTERISK" /* ASTERISK */,
1453
+ value: "*",
1454
+ position: startPos
1455
+ });
1456
+ this.advance();
1457
+ break;
1458
+ case ":":
1459
+ tokens.push({
1460
+ type: "LABEL" /* LABEL */,
1461
+ value: ":",
1462
+ position: startPos
1463
+ });
1464
+ this.advance();
1465
+ break;
1466
+ case '"':
1467
+ case "'":
1468
+ tokens.push({
1469
+ type: "STRING" /* STRING */,
1470
+ value: this.readString(),
1471
+ position: startPos
1472
+ });
1473
+ break;
1474
+ default:
1475
+ if (/[0-9]/.test(this.currentChar)) {
1476
+ tokens.push({
1477
+ type: "NUMBER" /* NUMBER */,
1478
+ value: this.readNumber(),
1479
+ position: startPos
1480
+ });
1481
+ } else if (/[a-zA-Z_]/.test(this.currentChar)) {
1482
+ tokens.push({
1483
+ type: "IDENTIFIER" /* IDENTIFIER */,
1484
+ value: this.readIdentifier(),
1485
+ position: startPos
1486
+ });
1487
+ } else {
1488
+ throw new Error(
1489
+ `Unexpected character: ${this.currentChar} at position ${startPos}`
1490
+ );
1491
+ }
1492
+ }
1493
+ }
1494
+ tokens.push({ type: "EOF" /* EOF */, value: "", position: this.position });
1495
+ return tokens;
1496
+ }
1497
+ };
1498
+
1499
+ // libs/cli/code-graph-core/src/lib/cypher/lib/parser.ts
1500
+ var Parser = class {
1501
+ constructor(tokens) {
1502
+ this.tokens = [];
1503
+ this.position = 0;
1504
+ this.tokens = tokens;
1505
+ }
1506
+ current() {
1507
+ return this.tokens[this.position];
1508
+ }
1509
+ advance() {
1510
+ const token = this.current();
1511
+ if (this.position < this.tokens.length - 1) {
1512
+ this.position++;
1513
+ }
1514
+ return token;
1515
+ }
1516
+ peek() {
1517
+ return this.tokens[this.position + 1] || this.tokens[this.tokens.length - 1];
1518
+ }
1519
+ expect(type) {
1520
+ const token = this.current();
1521
+ if (token.type !== type) {
1522
+ throw new Error(
1523
+ `Expected ${type}, got ${token.type} at position ${token.position}`
1524
+ );
1525
+ }
1526
+ return this.advance();
1527
+ }
1528
+ isKeyword(keyword) {
1529
+ return this.current().type === keyword;
1530
+ }
1531
+ /**
1532
+ * Parse a property object: { key: value, ... }
1533
+ * Note: Cypher uses colon (:) for properties in node patterns, not equals (=)
1534
+ */
1535
+ parseProperties() {
1536
+ this.expect("LBRACE" /* LBRACE */);
1537
+ const properties = {};
1538
+ if (this.current().type === "RBRACE" /* RBRACE */) {
1539
+ this.advance();
1540
+ return properties;
1541
+ }
1542
+ while (true) {
1543
+ const key = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1544
+ this.expect("LABEL" /* LABEL */);
1545
+ let value;
1546
+ if (this.current().type === "LBRACKET" /* LBRACKET */) {
1547
+ value = this.parseArrayLiteral();
1548
+ } else if (this.current().type === "STRING" /* STRING */) {
1549
+ value = this.advance().value;
1550
+ } else if (this.current().type === "NUMBER" /* NUMBER */) {
1551
+ value = parseFloat(this.advance().value);
1552
+ } else if (this.current().type === "TRUE" /* TRUE */) {
1553
+ value = true;
1554
+ this.advance();
1555
+ } else if (this.current().type === "FALSE" /* FALSE */) {
1556
+ value = false;
1557
+ this.advance();
1558
+ } else if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1559
+ value = this.advance().value;
1560
+ } else {
1561
+ throw new Error(
1562
+ `Unexpected token type for property value: ${this.current().type}`
1563
+ );
1564
+ }
1565
+ properties[key] = value;
1566
+ if (this.current().type === "RBRACE" /* RBRACE */) {
1567
+ this.advance();
1568
+ break;
1569
+ }
1570
+ this.expect("COMMA" /* COMMA */);
1571
+ }
1572
+ return properties;
1573
+ }
1574
+ /**
1575
+ * Parse a node pattern: (var:Label {prop: value}) or (:Label {prop: value})
1576
+ */
1577
+ parseNodePattern() {
1578
+ this.expect("LPAREN" /* LPAREN */);
1579
+ const pattern = {
1580
+ type: "NodePattern",
1581
+ labels: []
1582
+ };
1583
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1584
+ const nextToken = this.peek();
1585
+ if (nextToken.type === "LABEL" /* LABEL */ || nextToken.type === "LBRACE" /* LBRACE */ || nextToken.type === "RPAREN" /* RPAREN */) {
1586
+ pattern.variable = this.advance().value;
1587
+ }
1588
+ }
1589
+ if (this.current().type === "LABEL" /* LABEL */) {
1590
+ this.advance();
1591
+ if (this.current().type !== "IDENTIFIER" /* IDENTIFIER */) {
1592
+ throw new Error(
1593
+ `Expected identifier after :, got ${this.current().type}`
1594
+ );
1595
+ }
1596
+ pattern.labels.push(this.advance().value);
1597
+ if (this.current().type === "LABEL" /* LABEL */) {
1598
+ pattern.labelOperator = "AND";
1599
+ while (this.current().type === "LABEL" /* LABEL */) {
1600
+ this.advance();
1601
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1602
+ pattern.labels.push(this.advance().value);
1603
+ } else {
1604
+ throw new Error(
1605
+ `Expected identifier after :, got ${this.current().type}`
1606
+ );
1607
+ }
1608
+ }
1609
+ } else if (this.current().type === "PIPE" /* PIPE */) {
1610
+ pattern.labelOperator = "OR";
1611
+ while (this.current().type === "PIPE" /* PIPE */) {
1612
+ this.advance();
1613
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1614
+ pattern.labels.push(this.advance().value);
1615
+ } else {
1616
+ throw new Error(
1617
+ `Expected identifier after |, got ${this.current().type}`
1618
+ );
1619
+ }
1620
+ }
1621
+ } else {
1622
+ pattern.labelOperator = "AND";
1623
+ }
1624
+ }
1625
+ if (this.current().type === "LBRACE" /* LBRACE */) {
1626
+ pattern.properties = this.parseProperties();
1627
+ }
1628
+ this.expect("RPAREN" /* RPAREN */);
1629
+ return pattern;
1630
+ }
1631
+ /**
1632
+ * Parse relationship pattern: -[:RELATIONSHIP_TYPE]-> or <-[:RELATIONSHIP_TYPE]- or -[r:RELATIONSHIP_TYPE]->
1633
+ */
1634
+ parseRelationship() {
1635
+ const relationship = {
1636
+ type: "RelationshipPattern",
1637
+ direction: "outgoing"
1638
+ };
1639
+ if (this.current().type === "ARROW" /* ARROW */ && this.current().value === "<-") {
1640
+ relationship.direction = "incoming";
1641
+ this.advance();
1642
+ } else {
1643
+ this.expect("DASH" /* DASH */);
1644
+ }
1645
+ if (this.current().type === "LBRACKET" /* LBRACKET */) {
1646
+ this.advance();
1647
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1648
+ const nextToken = this.peek();
1649
+ if (nextToken.type === "LABEL" /* LABEL */ || nextToken.type === "ASTERISK" /* ASTERISK */) {
1650
+ relationship.variable = this.advance().value;
1651
+ }
1652
+ }
1653
+ if (this.current().type === "ASTERISK" /* ASTERISK */) {
1654
+ this.advance();
1655
+ relationship.variableLength = true;
1656
+ relationship.minLength = 0;
1657
+ relationship.maxLength = void 0;
1658
+ } else if (this.current().type === "LABEL" /* LABEL */) {
1659
+ this.advance();
1660
+ const edgeTypes = [];
1661
+ edgeTypes.push(this.expect("IDENTIFIER" /* IDENTIFIER */).value);
1662
+ while (this.current().type === "PIPE" /* PIPE */) {
1663
+ this.advance();
1664
+ edgeTypes.push(this.expect("IDENTIFIER" /* IDENTIFIER */).value);
1665
+ }
1666
+ relationship.edgeType = edgeTypes.length === 1 ? edgeTypes[0] : edgeTypes;
1667
+ if (this.current().type === "ASTERISK" /* ASTERISK */) {
1668
+ this.advance();
1669
+ relationship.variableLength = true;
1670
+ relationship.minLength = 0;
1671
+ relationship.maxLength = void 0;
1672
+ }
1673
+ }
1674
+ this.expect("RBRACKET" /* RBRACKET */);
1675
+ }
1676
+ if (relationship.direction === "incoming") {
1677
+ this.expect("DASH" /* DASH */);
1678
+ } else {
1679
+ if (this.current().type === "ARROW" /* ARROW */ && this.current().value === "->") {
1680
+ this.advance();
1681
+ } else if (this.current().type === "DASH" /* DASH */) {
1682
+ relationship.direction = "both";
1683
+ this.advance();
1684
+ }
1685
+ }
1686
+ return relationship;
1687
+ }
1688
+ /**
1689
+ * Parse MATCH clause (supports OPTIONAL MATCH)
1690
+ */
1691
+ parseMatch() {
1692
+ const isOptional = this.isKeyword("OPTIONAL" /* OPTIONAL */);
1693
+ if (isOptional) {
1694
+ this.advance();
1695
+ }
1696
+ this.expect("MATCH" /* MATCH */);
1697
+ const firstPattern = this.parsePatternChain();
1698
+ const matchClause = {
1699
+ type: "MatchClause",
1700
+ patterns: firstPattern.patterns,
1701
+ relationships: firstPattern.relationships,
1702
+ additionalMatches: []
1703
+ };
1704
+ while (this.current().type === "COMMA" /* COMMA */) {
1705
+ this.advance();
1706
+ const additionalPattern = this.parsePatternChain();
1707
+ matchClause.additionalMatches.push(additionalPattern);
1708
+ }
1709
+ if (isOptional) {
1710
+ matchClause.optionalMatches = [
1711
+ {
1712
+ type: "MatchStatement",
1713
+ patterns: firstPattern.patterns,
1714
+ relationships: firstPattern.relationships
1715
+ },
1716
+ ...matchClause.additionalMatches || []
1717
+ ];
1718
+ matchClause.patterns = [];
1719
+ matchClause.relationships = void 0;
1720
+ matchClause.additionalMatches = void 0;
1721
+ }
1722
+ return matchClause;
1723
+ }
1724
+ /**
1725
+ * Parse a pattern chain: (node)-[:rel]->(node)-[:rel]->(node)...
1726
+ * Returns patterns and relationships for a single connected pattern
1727
+ */
1728
+ parsePatternChain() {
1729
+ const patterns = [];
1730
+ const relationships = [];
1731
+ patterns.push(this.parseNodePattern());
1732
+ while (this.current().type === "DASH" /* DASH */ || this.current().type === "ARROW" /* ARROW */) {
1733
+ relationships.push(this.parseRelationship());
1734
+ patterns.push(this.parseNodePattern());
1735
+ }
1736
+ return {
1737
+ type: "MatchStatement",
1738
+ patterns,
1739
+ relationships: relationships.length > 0 ? relationships : void 0
1740
+ };
1741
+ }
1742
+ /**
1743
+ * Parse WHERE clause
1744
+ * Example: WHERE m.type = "Command" AND "param" IN m.parameters
1745
+ * Note: Flat properties only (no nesting, like Neo4j Cypher)
1746
+ */
1747
+ parseWhere() {
1748
+ this.expect("WHERE" /* WHERE */);
1749
+ const conditions = [];
1750
+ let logic;
1751
+ while (true) {
1752
+ const variable = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1753
+ let property;
1754
+ if (this.current().type === "DOT" /* DOT */) {
1755
+ this.advance();
1756
+ property = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1757
+ }
1758
+ let operator = "=";
1759
+ if (this.current().type === "EQUALS" /* EQUALS */) {
1760
+ operator = "=";
1761
+ this.advance();
1762
+ } else if (this.current().type === "NOT_EQUALS" /* NOT_EQUALS */) {
1763
+ operator = "!=";
1764
+ this.advance();
1765
+ } else if (this.current().type === "GT" /* GT */) {
1766
+ operator = ">";
1767
+ this.advance();
1768
+ } else if (this.current().type === "LT" /* LT */) {
1769
+ operator = "<";
1770
+ this.advance();
1771
+ } else if (this.current().type === "GTE" /* GTE */) {
1772
+ operator = ">=";
1773
+ this.advance();
1774
+ } else if (this.current().type === "LTE" /* LTE */) {
1775
+ operator = "<=";
1776
+ this.advance();
1777
+ } else if (this.current().type === "NOT" /* NOT */) {
1778
+ this.advance();
1779
+ this.expect("IN" /* IN */);
1780
+ operator = "NOT IN";
1781
+ } else if (this.current().type === "IN" /* IN */) {
1782
+ this.advance();
1783
+ operator = "IN";
1784
+ } else if (this.current().type === "IS" /* IS */) {
1785
+ this.advance();
1786
+ if (this.isKeyword("NULL" /* NULL */)) {
1787
+ this.advance();
1788
+ operator = "IS NULL";
1789
+ } else if (this.isKeyword("NOT" /* NOT */)) {
1790
+ this.advance();
1791
+ this.expect("NULL" /* NULL */);
1792
+ operator = "IS NOT NULL";
1793
+ } else {
1794
+ throw new Error(
1795
+ `Expected NULL or NOT NULL after IS at position ${this.current().position}`
1796
+ );
1797
+ }
1798
+ } else if (this.current().type === "STARTS" /* STARTS */) {
1799
+ this.advance();
1800
+ this.expect("WITH" /* WITH */);
1801
+ operator = "STARTS WITH";
1802
+ } else if (this.current().type === "ENDS" /* ENDS */) {
1803
+ this.advance();
1804
+ this.expect("WITH" /* WITH */);
1805
+ operator = "ENDS WITH";
1806
+ } else if (this.current().type === "CONTAINS" /* CONTAINS */) {
1807
+ operator = "CONTAINS";
1808
+ this.advance();
1809
+ } else if (this.current().type === "REGEX_MATCH" /* REGEX_MATCH */) {
1810
+ operator = "=~";
1811
+ this.advance();
1812
+ } else {
1813
+ throw new Error(
1814
+ `Unsupported operator at position ${this.current().position}.`
1815
+ );
1816
+ }
1817
+ let value = null;
1818
+ if (operator === "IS NULL" || operator === "IS NOT NULL") {
1819
+ } else if (operator === "STARTS WITH" || operator === "ENDS WITH" || operator === "CONTAINS" || operator === "=~") {
1820
+ if (this.current().type === "STRING" /* STRING */) {
1821
+ value = this.advance().value;
1822
+ } else {
1823
+ throw new Error(
1824
+ `String matching operators (${operator}) require a string value at position ${this.current().position}`
1825
+ );
1826
+ }
1827
+ } else if (this.current().type === "LBRACKET" /* LBRACKET */) {
1828
+ value = this.parseArrayLiteral();
1829
+ } else if (this.current().type === "STRING" /* STRING */) {
1830
+ value = this.advance().value;
1831
+ } else if (this.current().type === "NUMBER" /* NUMBER */) {
1832
+ value = parseFloat(this.advance().value);
1833
+ } else if (this.current().type === "TRUE" /* TRUE */) {
1834
+ value = true;
1835
+ this.advance();
1836
+ } else if (this.current().type === "FALSE" /* FALSE */) {
1837
+ value = false;
1838
+ this.advance();
1839
+ } else if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1840
+ value = this.advance().value;
1841
+ } else {
1842
+ throw new Error(
1843
+ `Unexpected token type for WHERE value: ${this.current().type}`
1844
+ );
1845
+ }
1846
+ conditions.push({ variable, property, operator, value });
1847
+ if (this.isKeyword("AND" /* AND */)) {
1848
+ if (logic === void 0) {
1849
+ logic = "AND";
1850
+ } else if (logic !== "AND") {
1851
+ throw new Error("Cannot mix AND and OR in WHERE clause");
1852
+ }
1853
+ this.advance();
1854
+ continue;
1855
+ } else if (this.isKeyword("OR" /* OR */)) {
1856
+ if (logic === void 0) {
1857
+ logic = "OR";
1858
+ } else if (logic !== "OR") {
1859
+ throw new Error("Cannot mix AND and OR in WHERE clause");
1860
+ }
1861
+ this.advance();
1862
+ continue;
1863
+ }
1864
+ break;
1865
+ }
1866
+ return {
1867
+ type: "WhereClause",
1868
+ conditions,
1869
+ logic
1870
+ };
1871
+ }
1872
+ /**
1873
+ * Parse array literal: [value1, value2, ...]
1874
+ */
1875
+ parseArrayLiteral() {
1876
+ this.expect("LBRACKET" /* LBRACKET */);
1877
+ const array = [];
1878
+ if (this.current().type === "RBRACKET" /* RBRACKET */) {
1879
+ this.advance();
1880
+ return array;
1881
+ }
1882
+ while (true) {
1883
+ if (this.current().type === "STRING" /* STRING */) {
1884
+ array.push(this.advance().value);
1885
+ } else if (this.current().type === "NUMBER" /* NUMBER */) {
1886
+ array.push(parseFloat(this.advance().value));
1887
+ } else if (this.current().type === "TRUE" /* TRUE */) {
1888
+ array.push(true);
1889
+ this.advance();
1890
+ } else if (this.current().type === "FALSE" /* FALSE */) {
1891
+ array.push(false);
1892
+ this.advance();
1893
+ } else if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1894
+ array.push(this.advance().value);
1895
+ } else {
1896
+ throw new Error(
1897
+ `Unexpected token type in array literal: ${this.current().type}`
1898
+ );
1899
+ }
1900
+ if (this.current().type === "COMMA" /* COMMA */) {
1901
+ this.advance();
1902
+ } else if (this.current().type === "RBRACKET" /* RBRACKET */) {
1903
+ this.advance();
1904
+ break;
1905
+ } else {
1906
+ throw new Error(
1907
+ `Expected comma or closing bracket in array literal at position ${this.current().position}`
1908
+ );
1909
+ }
1910
+ }
1911
+ return array;
1912
+ }
1913
+ /**
1914
+ * Parse RETURN clause
1915
+ * Supports: RETURN *, RETURN DISTINCT, RETURN var1, var2, RETURN var AS alias, LIMIT n
1916
+ */
1917
+ parseReturn() {
1918
+ this.expect("RETURN" /* RETURN */);
1919
+ const distinct = this.isKeyword("DISTINCT" /* DISTINCT */);
1920
+ if (distinct) {
1921
+ this.advance();
1922
+ }
1923
+ if (this.current().type === "ASTERISK" /* ASTERISK */) {
1924
+ this.advance();
1925
+ const limit2 = this.parseOptionalLimit();
1926
+ return {
1927
+ type: "ReturnClause",
1928
+ distinct,
1929
+ returnAll: true,
1930
+ items: [],
1931
+ limit: limit2
1932
+ };
1933
+ }
1934
+ const items = [];
1935
+ while (true) {
1936
+ const expression = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1937
+ let alias;
1938
+ if (this.isKeyword("AS" /* AS */)) {
1939
+ this.advance();
1940
+ alias = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1941
+ }
1942
+ items.push({ expression, alias });
1943
+ if (this.current().type === "COMMA" /* COMMA */) {
1944
+ this.advance();
1945
+ continue;
1946
+ }
1947
+ break;
1948
+ }
1949
+ const limit = this.parseOptionalLimit();
1950
+ return {
1951
+ type: "ReturnClause",
1952
+ distinct,
1953
+ returnAll: false,
1954
+ items,
1955
+ limit
1956
+ };
1957
+ }
1958
+ /**
1959
+ * Parse optional LIMIT clause
1960
+ * Returns the limit number or undefined if not present
1961
+ */
1962
+ parseOptionalLimit() {
1963
+ if (this.isKeyword("LIMIT" /* LIMIT */)) {
1964
+ this.advance();
1965
+ const limitToken = this.expect("NUMBER" /* NUMBER */);
1966
+ return parseInt(limitToken.value, 10);
1967
+ }
1968
+ return void 0;
1969
+ }
1970
+ /**
1971
+ * Parse ORDER BY clause
1972
+ * Supports: ORDER BY n.name, ORDER BY n.name ASC, ORDER BY n.name DESC
1973
+ * Also supports multiple fields: ORDER BY n.type DESC, n.name ASC
1974
+ */
1975
+ parseOrderBy() {
1976
+ this.expect("ORDER" /* ORDER */);
1977
+ this.expect("BY" /* BY */);
1978
+ const items = [];
1979
+ while (true) {
1980
+ const variable = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1981
+ let property;
1982
+ if (this.current().type === "DOT" /* DOT */) {
1983
+ this.advance();
1984
+ property = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1985
+ }
1986
+ let direction = "ASC";
1987
+ if (this.isKeyword("ASC" /* ASC */)) {
1988
+ this.advance();
1989
+ direction = "ASC";
1990
+ } else if (this.isKeyword("DESC" /* DESC */)) {
1991
+ this.advance();
1992
+ direction = "DESC";
1993
+ }
1994
+ items.push({ variable, property, direction });
1995
+ if (this.current().type === "COMMA" /* COMMA */) {
1996
+ this.advance();
1997
+ continue;
1998
+ }
1999
+ break;
2000
+ }
2001
+ return { type: "OrderByStatement", items };
2002
+ }
2003
+ /**
2004
+ * Parse entire Cypher query
2005
+ * Returns statements in execution order
2006
+ */
2007
+ parse() {
2008
+ const statements = [];
2009
+ while (this.current().type !== "EOF" /* EOF */) {
2010
+ if (this.isKeyword("MATCH" /* MATCH */) || this.isKeyword("OPTIONAL" /* OPTIONAL */)) {
2011
+ const isOptional = this.isKeyword("OPTIONAL" /* OPTIONAL */);
2012
+ const matchClause = this.parseMatch();
2013
+ if (matchClause.optionalMatches) {
2014
+ for (const optionalMatch of matchClause.optionalMatches) {
2015
+ statements.push({
2016
+ type: "MatchStatement",
2017
+ patterns: optionalMatch.patterns,
2018
+ relationships: optionalMatch.relationships,
2019
+ optional: true
2020
+ });
2021
+ }
2022
+ } else {
2023
+ if (matchClause.patterns.length > 0) {
2024
+ statements.push({
2025
+ type: "MatchStatement",
2026
+ patterns: matchClause.patterns,
2027
+ relationships: matchClause.relationships,
2028
+ optional: false
2029
+ });
2030
+ }
2031
+ if (matchClause.additionalMatches) {
2032
+ for (const additionalMatch of matchClause.additionalMatches) {
2033
+ statements.push({
2034
+ type: "MatchStatement",
2035
+ patterns: additionalMatch.patterns,
2036
+ relationships: additionalMatch.relationships,
2037
+ optional: false
2038
+ });
2039
+ }
2040
+ }
2041
+ }
2042
+ continue;
2043
+ }
2044
+ if (this.isKeyword("WHERE" /* WHERE */)) {
2045
+ const whereClause = this.parseWhere();
2046
+ statements.push({
2047
+ type: "WhereStatement",
2048
+ conditions: whereClause.conditions,
2049
+ logic: whereClause.logic
2050
+ });
2051
+ continue;
2052
+ }
2053
+ if (this.isKeyword("ORDER" /* ORDER */)) {
2054
+ const orderByStatement = this.parseOrderBy();
2055
+ statements.push(orderByStatement);
2056
+ continue;
2057
+ }
2058
+ if (this.isKeyword("RETURN" /* RETURN */)) {
2059
+ const returnClause = this.parseReturn();
2060
+ statements.push({
2061
+ type: "ReturnStatement",
2062
+ distinct: returnClause.distinct,
2063
+ returnAll: returnClause.returnAll,
2064
+ items: returnClause.items,
2065
+ limit: returnClause.limit
2066
+ });
2067
+ continue;
2068
+ }
2069
+ break;
2070
+ }
2071
+ if (this.current().type !== "EOF" /* EOF */) {
2072
+ throw new Error(
2073
+ `Unexpected token: ${this.current().type} at position ${this.current().position}`
2074
+ );
2075
+ }
2076
+ return {
2077
+ type: "CypherQuery",
2078
+ statements
2079
+ };
2080
+ }
2081
+ };
2082
+
2083
+ // libs/cli/code-graph-core/src/lib/cypher/lib/validator/unsupported-features.config.ts
2084
+ var FORBIDDEN_KEYWORDS = [
2085
+ {
2086
+ pattern: /\bWITH\b/i,
2087
+ name: "WITH clause",
2088
+ errorMessage: "WITH clause is not supported. Cannot use WITH for intermediate results or variable passing.",
2089
+ alternative: "Use multiple MATCH statements or inline variables directly in WHERE/RETURN clauses."
2090
+ },
2091
+ {
2092
+ pattern: /\bUNWIND\b/i,
2093
+ name: "UNWIND clause",
2094
+ errorMessage: "UNWIND clause is not supported. Cannot use UNWIND to expand arrays."
2095
+ },
2096
+ {
2097
+ pattern: /\bUNION\b/i,
2098
+ name: "UNION clause",
2099
+ errorMessage: "UNION clause is not supported. Cannot combine result sets with UNION."
2100
+ },
2101
+ {
2102
+ pattern: /\bCREATE\b/i,
2103
+ name: "CREATE clause",
2104
+ errorMessage: "CREATE clause is not supported. This is a read-only query engine (no mutations)."
2105
+ },
2106
+ {
2107
+ pattern: /\bMERGE\b/i,
2108
+ name: "MERGE clause",
2109
+ errorMessage: "MERGE clause is not supported. This is a read-only query engine (no mutations)."
2110
+ },
2111
+ {
2112
+ pattern: /\bDELETE\b/i,
2113
+ name: "DELETE clause",
2114
+ errorMessage: "DELETE clause is not supported. This is a read-only query engine (no mutations)."
2115
+ }
2116
+ // {
2117
+ // pattern: /\bSET\b/i,
2118
+ // name: 'SET clause',
2119
+ // errorMessage:
2120
+ // 'SET clause is not supported. This is a read-only query engine (no mutations).',
2121
+ // },
2122
+ ];
2123
+ var FORBIDDEN_AGGREGATIONS = [
2124
+ {
2125
+ pattern: /\bCOUNT\s*\(/i,
2126
+ name: "COUNT() function",
2127
+ errorMessage: "COUNT() aggregation function is not supported. Aggregation functions are not available.",
2128
+ alternative: "Use manual counting patterns or return all results and count in application code."
2129
+ },
2130
+ {
2131
+ pattern: /\bSUM\s*\(/i,
2132
+ name: "SUM() function",
2133
+ errorMessage: "SUM() aggregation function is not supported. Aggregation functions are not available."
2134
+ },
2135
+ {
2136
+ pattern: /\bcollect\s*\(/i,
2137
+ name: "collect() function",
2138
+ errorMessage: "collect() aggregation function is not supported. Aggregation functions are not available."
2139
+ },
2140
+ {
2141
+ pattern: /\bMIN\s*\(/i,
2142
+ name: "MIN() function",
2143
+ errorMessage: "MIN() aggregation function is not supported. Aggregation functions are not available."
2144
+ },
2145
+ {
2146
+ pattern: /\bMAX\s*\(/i,
2147
+ name: "MAX() function",
2148
+ errorMessage: "MAX() aggregation function is not supported. Aggregation functions are not available."
2149
+ },
2150
+ {
2151
+ pattern: /\bAVG\s*\(/i,
2152
+ name: "AVG() function",
2153
+ errorMessage: "AVG() aggregation function is not supported. Aggregation functions are not available."
2154
+ }
2155
+ ];
2156
+ var FORBIDDEN_MODIFIERS = [
2157
+ {
2158
+ pattern: /\bSKIP\b/i,
2159
+ name: "SKIP clause",
2160
+ errorMessage: "SKIP clause is not supported. Pagination is not available."
2161
+ }
2162
+ ];
2163
+ var FORBIDDEN_PATTERNS = [
2164
+ {
2165
+ pattern: /\[.*:\w+\*\d+\.\.\d+.*\]/,
2166
+ name: "Path range syntax",
2167
+ errorMessage: "Path range syntax is not supported. Patterns like `*1..3`, `*2..`, `*..3` are invalid.",
2168
+ alternative: "Use `*` (unlimited, zero or more) for variable-length paths."
2169
+ },
2170
+ {
2171
+ pattern: /\[.*:\w+\*\d+\.\..*\]/,
2172
+ name: "Path range syntax (lower bound)",
2173
+ errorMessage: "Path range syntax is not supported. Patterns like `*2..` are invalid.",
2174
+ alternative: "Use `*` (unlimited, zero or more) for variable-length paths."
2175
+ },
2176
+ {
2177
+ pattern: /\[.*:\w+\*\.\.\d+.*\]/,
2178
+ name: "Path range syntax (upper bound)",
2179
+ errorMessage: "Path range syntax is not supported. Patterns like `*..3` are invalid.",
2180
+ alternative: "Use `*` (unlimited, zero or more) for variable-length paths."
2181
+ },
2182
+ {
2183
+ pattern: /WHERE\s+NOT\s*\(/i,
2184
+ name: "WHERE NOT (pattern)",
2185
+ errorMessage: "WHERE NOT (pattern) is not supported. Cannot use pattern negation in WHERE clause.",
2186
+ alternative: "Use `OPTIONAL MATCH` + `WHERE IS NULL` instead. Example: `OPTIONAL MATCH (m)-[:HAS_PARAMETER]->(:ParameterDeclaration) WHERE ... IS NULL`."
2187
+ },
2188
+ {
2189
+ pattern: /MATCH\s*\(:\w+[^)]*\)(?:\s*,\s*\(:\w+[^)]*\))*(?:\s*WHERE[^R]*)?/i,
2190
+ name: "Anonymous-only queries",
2191
+ errorMessage: "Queries with only anonymous nodes (no variable bindings) are not useful.",
2192
+ alternative: "Use bound variables in the pattern. Example: `MATCH (a:A)` instead of `MATCH (:A)`."
2193
+ }
2194
+ ];
2195
+
2196
+ // libs/cli/code-graph-core/src/lib/cypher/lib/validator/query-validator.ts
2197
+ var UNSUPPORTED_FEATURES = [
2198
+ ...FORBIDDEN_KEYWORDS,
2199
+ ...FORBIDDEN_AGGREGATIONS,
2200
+ ...FORBIDDEN_MODIFIERS,
2201
+ ...FORBIDDEN_PATTERNS
2202
+ ];
2203
+ var UnsupportedFeatureError = class extends Error {
2204
+ constructor(feature, query2) {
2205
+ const message = `Unsupported Cypher feature detected: ${feature.name}
2206
+
2207
+ ${feature.errorMessage}${feature.alternative ? `
2208
+
2209
+ Alternative: ${feature.alternative}` : ""}
2210
+
2211
+ Query: ${query2}`;
2212
+ super(message);
2213
+ this.feature = feature;
2214
+ this.query = query2;
2215
+ this.name = "UnsupportedFeatureError";
2216
+ }
2217
+ };
2218
+ function validateQuery(query2) {
2219
+ if (!query2 || typeof query2 !== "string") {
2220
+ throw new Error("Query must be a non-empty string");
2221
+ }
2222
+ const normalizedQuery = query2.trim();
2223
+ if (normalizedQuery.length === 0) {
2224
+ throw new Error("Query cannot be empty");
2225
+ }
2226
+ for (const feature of UNSUPPORTED_FEATURES) {
2227
+ const pattern = typeof feature.pattern === "string" ? new RegExp(feature.pattern, "i") : feature.pattern;
2228
+ if (pattern.test(normalizedQuery)) {
2229
+ if (feature.name === "WITH clause") {
2230
+ const withMatches = normalizedQuery.matchAll(/\bWITH\b/gi);
2231
+ let isAllowed = false;
2232
+ for (const match of withMatches) {
2233
+ const beforeMatch = normalizedQuery.substring(
2234
+ Math.max(0, match.index - 10),
2235
+ match.index
2236
+ );
2237
+ if (/\bSTARTS\s+$/i.test(beforeMatch) || /\bENDS\s+$/i.test(beforeMatch)) {
2238
+ isAllowed = true;
2239
+ break;
2240
+ }
2241
+ }
2242
+ if (isAllowed) {
2243
+ continue;
2244
+ }
2245
+ }
2246
+ throw new UnsupportedFeatureError(feature, normalizedQuery);
2247
+ }
2248
+ }
2249
+ }
2250
+
2251
+ // libs/cli/code-graph-core/src/lib/cypher/lib/index.ts
2252
+ var buildQueryObject = (query2) => {
2253
+ const joinedQuery = Array.isArray(query2) ? query2.join(" ") : query2;
2254
+ validateQuery(joinedQuery);
2255
+ const lexer = new Lexer(joinedQuery);
2256
+ const tokens = lexer.tokenize();
2257
+ const parser = new Parser(tokens);
2258
+ return parser.parse();
2259
+ };
2260
+ var query = (queryStatements, graph) => {
2261
+ const queryObject = buildQueryObject(queryStatements);
2262
+ const executor = new CypherExecutor(graph);
2263
+ return executor.execute(queryObject);
2264
+ };
2265
+
2266
+ // libs/cli/code-graph-core/src/lib/code-graph-core.ts
2267
+ var autoMatch = (cypherQuery, program, options) => {
2268
+ const queryObject = buildQueryObject(cypherQuery);
2269
+ const config = buildConfigFromQuery(queryObject);
2270
+ const codeGraph = buildCodeGraph(program, config);
2271
+ const results = query(cypherQuery, codeGraph);
2272
+ return options?.skipRewrite ? results : rewriteOccurenceOf(results);
2273
+ };
2274
+ var PATH_ANCHOR_SRC_APP = "src/app";
2275
+ var rewriteUntilPath = (pathOrNodeId, pathAnchor) => {
2276
+ const searchSegment = pathAnchor.startsWith("/") && pathAnchor.endsWith("/") ? pathAnchor : `/${pathAnchor.replace(/^\/|\/$/g, "")}/`;
2277
+ const idx = pathOrNodeId.lastIndexOf(searchSegment);
2278
+ if (idx < 0)
2279
+ return pathOrNodeId;
2280
+ const sliced = pathOrNodeId.slice(idx);
2281
+ return sliced.startsWith("/") ? sliced.slice(1) : sliced;
2282
+ };
2283
+ var rewriteOccurenceOf = (result) => {
2284
+ return result.map((result2) => {
2285
+ return Object.keys(result2).reduce((acc, key) => {
2286
+ if (!result2[key]) {
2287
+ return acc;
2288
+ }
2289
+ const id = result2[key]["id"];
2290
+ if (!id) {
2291
+ return acc;
2292
+ }
2293
+ return {
2294
+ ...acc,
2295
+ [key]: {
2296
+ ...result2[key],
2297
+ id: rewriteUntilPath(id, PATH_ANCHOR_SRC_APP)
2298
+ }
2299
+ };
2300
+ }, {});
2301
+ });
2302
+ };
2303
+
2304
+ // apps/code-graph/src/run-cli.ts
2305
+ var command = "query <cypher>";
2306
+ var describe = "Run Cypher and print match results as JSON. Builds the code graph from the project tsconfig; node/relationship config is inferred from the query.";
2307
+ var builder = (yargs2) => yargs2.option("tsconfig", {
2308
+ type: "string",
2309
+ default: "tsconfig.json",
2310
+ description: "Path to tsconfig.json (default: tsconfig.json in cwd)"
2311
+ }).positional("cypher", {
2312
+ type: "string",
2313
+ demandOption: true,
2314
+ description: 'Cypher query (quote in shell: "MATCH (n:ClassDeclaration) RETURN n")'
2315
+ });
2316
+ async function handler(argv) {
2317
+ const cypherQuery = argv.cypher.trim();
2318
+ const cwd = process.cwd();
2319
+ const tsconfigPath = path2.isAbsolute(argv.tsconfig) ? argv.tsconfig : path2.join(cwd, argv.tsconfig);
2320
+ if (!fs.existsSync(tsconfigPath)) {
2321
+ console.error(`Error: tsconfig not found at ${tsconfigPath}`);
2322
+ process.exit(1);
2323
+ }
2324
+ try {
2325
+ const program = createProgramFromTsConfig(tsconfigPath);
2326
+ const results = autoMatch(cypherQuery, program);
2327
+ console.log(JSON.stringify(results, null, 2));
2328
+ } catch (err) {
2329
+ const message = err instanceof Error ? err.message : String(err);
2330
+ const stack = err instanceof Error ? err.stack : void 0;
2331
+ console.error(`Error: ${message}`);
2332
+ if (stack && process.env.DEBUG) {
2333
+ console.error(stack);
2334
+ }
2335
+ process.exit(1);
2336
+ }
2337
+ }
2338
+
2339
+ // apps/code-graph/src/resources-cli.ts
2340
+ var resources_cli_exports = {};
2341
+ __export(resources_cli_exports, {
2342
+ builder: () => builder2,
2343
+ command: () => command2,
2344
+ describe: () => describe2,
2345
+ handler: () => handler2
2346
+ });
2347
+ var command2 = "resources";
2348
+ var describe2 = "Print code graph schema and Cypher reference (for agents and tooling).";
2349
+ var builder2 = (yargs2) => yargs2;
2350
+ function handler2() {
2351
+ console.log(RESOURCES_TEXT);
2352
+ }
2353
+ var RESOURCES_TEXT = `# Code Graph \u2014 Cypher over TypeScript
2354
+
2355
+ The graph database is your **TypeScript code**. Nodes are AST nodes; relationships follow the structure of the program. You run Cypher queries against a project built from a tsconfig; the engine builds the graph on the fly from the query (no separate schema).
2356
+
2357
+ ## Node labels = TypeScript SyntaxKind
2358
+
2359
+ Labels are **TypeScript AST node kinds** (SyntaxKind names). Examples:
2360
+
2361
+ ClassDeclaration, MethodDeclaration, PropertyDeclaration, Constructor,
2362
+ FunctionDeclaration, VariableStatement, CallExpression, Identifier,
2363
+ ObjectLiteralExpression, Decorator, SourceFile, ImportDeclaration, ...
2364
+
2365
+ Use them in patterns: \`(n:ClassDeclaration)\`, \`(d:Decorator)\`, \`(p:PropertyDeclaration)\`.
2366
+
2367
+ ## Node shape (every node)
2368
+
2369
+ Each node in the result has:
2370
+
2371
+ - **id** \u2014 unique node id (file path + position e.g. src/app/app.component.ts:1:2.3.4 means first line second character to third line fourth character)
2372
+ - **labels** \u2014 array of one label (the SyntaxKind name)
2373
+ - **name** \u2014 \`node.name?.getText()\` (e.g. class/method/prop name)
2374
+ - **filePath** \u2014 source file path
2375
+ - **text** \u2014 full text of the AST node
2376
+ - **type** \u2014 resolved type from TypeChecker when available
2377
+ - **firstIdentifier** \u2014 first descendant Identifier text
2378
+ - **initializer** \u2014 initializer text when present (e.g. variable, property)
2379
+
2380
+ ## Relationships (most common)
2381
+
2382
+ **Traversal (AST structure)**
2383
+ - **HAS_DESCENDANTS** \u2014 recursive descent into child nodes (optionally by kind)
2384
+ - **HAS_ANCESTORS** \u2014 recursive ascent to parent nodes
2385
+
2386
+ **Direct AST properties**
2387
+ - **HAS_INITIALIZER** \u2014 \`node.initializer\` (VariableDeclaration, PropertyDeclaration, \u2026)
2388
+ - **HAS_PARAMETERS** \u2014 \`node.parameters\` (functions, constructors, \u2026)
2389
+ - **HAS_EXPRESSION** \u2014 \`node.expression\` (CallExpression, Decorator, \u2026)
2390
+ - **HAS_TYPE_ANNOTATION** \u2014 \`node.type\` (explicit type on params, properties, return)
2391
+ - **HAS_BODY** \u2014 \`node.body\` (functions, methods, arrow functions, \u2026)
2392
+ - **HAS_MEMBERS** \u2014 \`node.members\` (class/interface/enum members)
2393
+ - **HAS_PROPERTIES** \u2014 \`node.properties\` (ObjectLiteralExpression)
2394
+ - **HAS_ARGUMENTS** \u2014 \`node.arguments\` (CallExpression, NewExpression)
2395
+ - **HAS_DECORATORS** \u2014 decorators on the node
2396
+ - **HAS_TYPE** \u2014 resolved type (TypeChecker)
2397
+ - **HAS_NAME** \u2014 \`node.name\` (Identifier)
2398
+
2399
+ **Heritage**
2400
+ - **EXTENDS** \u2014 \`extends\` clause (classes, interfaces)
2401
+ - **IMPLEMENTS** \u2014 \`implements\` clause (classes)
2402
+
2403
+ Example: find classes with a \`Component\` decorator:
2404
+
2405
+ MATCH (c:ClassDeclaration)-[:HAS_DECORATORS]->(d:Decorator)
2406
+ WHERE d.name = 'Component'
2407
+ RETURN c
2408
+
2409
+ Example: find method and its parameters:
2410
+
2411
+ MATCH (m:MethodDeclaration)-[:HAS_PARAMETERS]->(p:ParameterDeclaration)
2412
+ RETURN m, p
2413
+ `;
2414
+
2415
+ // apps/code-graph/src/cypher-cli.ts
2416
+ var cypher_cli_exports = {};
2417
+ __export(cypher_cli_exports, {
2418
+ builder: () => builder3,
2419
+ command: () => command3,
2420
+ describe: () => describe3,
2421
+ handler: () => handler3
2422
+ });
2423
+ var command3 = "cypher";
2424
+ var describe3 = "Print supported Cypher syntax and constraints (MATCH, WHERE, RETURN, etc.).";
2425
+ var builder3 = (yargs2) => yargs2;
2426
+ function handler3() {
2427
+ console.log(CYPHER_REFERENCE);
2428
+ }
2429
+ var CYPHER_REFERENCE = `# Cypher reference (code-graph dialect)
2430
+
2431
+ Unsupported features (e.g. CREATE, MERGE, path ranges like *1..3) will error \u2014 see validator if needed.
2432
+
2433
+ MATCH, WHERE, RETURN, ORDER BY, LIMIT, OPTIONAL MATCH, and variable-length paths are supported.
2434
+
2435
+ ## MATCH
2436
+ (n:Label), (n:Label { prop: 'value' }), (a)-[:REL]->(b), (a)<-[:REL]-(b)
2437
+ Variable-length: (a)-[*]->(b)
2438
+ Multiple labels: (n:A:B) AND, (n:A|B) OR. Multiple rel types: [:R1|R2]
2439
+ OPTIONAL MATCH for left outer joins.
2440
+
2441
+ ## WHERE
2442
+ =, !=, >, <, >=, <=
2443
+ IN [...], NOT IN [...]
2444
+ IS NULL, IS NOT NULL
2445
+ STARTS WITH, ENDS WITH, CONTAINS, =~ 'regex'
2446
+ Same logic for whole clause: all AND or all OR (no mixing).
2447
+
2448
+ ## RETURN
2449
+ RETURN n, m | RETURN * | RETURN n AS alias
2450
+ RETURN DISTINCT n | RETURN n LIMIT 10
2451
+
2452
+ ## ORDER BY
2453
+ ORDER BY n.name ASC | DESC, multiple fields supported.
2454
+
2455
+ ## Constraints
2456
+ RETURN required. Node properties flat, primitives only. Edges: source, target, type (no edge properties). Variable-length only * (no *1..3 etc).
2457
+ `;
2458
+
2459
+ // apps/code-graph/src/bin/cli.ts
2460
+ var VERSION = process.env.npm_package_version ?? "0.1.0";
2461
+ yargs(hideBin(process.argv)).scriptName("code-graph").demandCommand().recommendCommands().command(command, describe, run_cli_exports).command(command2, describe2, resources_cli_exports).command(command3, describe3, cypher_cli_exports).wrap(120).strict().help().version(VERSION).epilog(
9
2462
  'Exits 0 and prints JSON array of matches to stdout on success; exits 1 and prints error to stderr on failure.\n\nExamples:\n code-graph query "MATCH (n:ClassDeclaration) RETURN n"\n code-graph query --tsconfig ./lib/tsconfig.json "MATCH (n:ClassDeclaration) RETURN n"\n code-graph resources # schema and concepts\n code-graph cypher # full Cypher dialect reference'
10
2463
  ).parse();