@grafema/cli 0.2.5-beta → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts +3 -10
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +5 -347
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/analyzeAction.d.ts +28 -0
- package/dist/commands/analyzeAction.d.ts.map +1 -0
- package/dist/commands/analyzeAction.js +243 -0
- package/dist/commands/analyzeAction.js.map +1 -0
- package/dist/commands/check.js +2 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/context.d.ts +16 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +238 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor/checks.js +1 -1
- package/dist/commands/doctor/checks.js.map +1 -1
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +4 -3
- package/dist/commands/explain.js.map +1 -1
- package/dist/commands/file.d.ts +15 -0
- package/dist/commands/file.d.ts.map +1 -0
- package/dist/commands/file.js +144 -0
- package/dist/commands/file.js.map +1 -0
- package/dist/commands/impact.d.ts.map +1 -1
- package/dist/commands/impact.js +2 -3
- package/dist/commands/impact.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +3 -2
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/query.d.ts +8 -0
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +158 -51
- package/dist/commands/query.js.map +1 -1
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +3 -2
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +8 -59
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/setup-skill.d.ts +17 -0
- package/dist/commands/setup-skill.d.ts.map +1 -0
- package/dist/commands/setup-skill.js +131 -0
- package/dist/commands/setup-skill.js.map +1 -0
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +20 -10
- package/dist/commands/trace.js.map +1 -1
- package/dist/plugins/builtinPlugins.d.ts +10 -0
- package/dist/plugins/builtinPlugins.d.ts.map +1 -0
- package/dist/plugins/builtinPlugins.js +68 -0
- package/dist/plugins/builtinPlugins.js.map +1 -0
- package/dist/plugins/pluginLoader.d.ts +16 -0
- package/dist/plugins/pluginLoader.d.ts.map +1 -0
- package/dist/plugins/pluginLoader.js +101 -0
- package/dist/plugins/pluginLoader.js.map +1 -0
- package/dist/plugins/pluginResolver.js +38 -0
- package/dist/utils/codePreview.d.ts +1 -0
- package/dist/utils/codePreview.d.ts.map +1 -1
- package/dist/utils/codePreview.js +5 -3
- package/dist/utils/codePreview.js.map +1 -1
- package/dist/utils/formatNode.d.ts +1 -1
- package/dist/utils/formatNode.d.ts.map +1 -1
- package/dist/utils/formatNode.js +2 -2
- package/dist/utils/formatNode.js.map +1 -1
- package/dist/utils/pathUtils.d.ts +2 -0
- package/dist/utils/pathUtils.d.ts.map +1 -0
- package/dist/utils/pathUtils.js +9 -0
- package/dist/utils/pathUtils.js.map +1 -0
- package/dist/utils/progressRenderer.d.ts +4 -0
- package/dist/utils/progressRenderer.d.ts.map +1 -1
- package/dist/utils/progressRenderer.js +23 -4
- package/dist/utils/progressRenderer.js.map +1 -1
- package/package.json +7 -9
- package/skills/grafema-codebase-analysis/SKILL.md +295 -0
- package/skills/grafema-codebase-analysis/references/node-edge-types.md +123 -0
- package/skills/grafema-codebase-analysis/references/query-patterns.md +205 -0
- package/src/cli.ts +8 -2
- package/src/commands/analyze.ts +5 -435
- package/src/commands/analyzeAction.ts +284 -0
- package/src/commands/check.ts +2 -2
- package/src/commands/context.ts +309 -0
- package/src/commands/doctor/checks.ts +1 -1
- package/src/commands/explain.ts +4 -3
- package/src/commands/explore.tsx +7 -5
- package/src/commands/file.ts +179 -0
- package/src/commands/impact.ts +2 -3
- package/src/commands/init.ts +13 -1
- package/src/commands/ls.ts +3 -2
- package/src/commands/query.ts +167 -52
- package/src/commands/schema.ts +3 -2
- package/src/commands/server.ts +8 -64
- package/src/commands/setup-skill.ts +162 -0
- package/src/commands/trace.ts +18 -9
- package/src/plugins/builtinPlugins.ts +108 -0
- package/src/plugins/pluginLoader.ts +123 -0
- package/src/plugins/pluginResolver.js +38 -0
- package/src/utils/codePreview.ts +7 -3
- package/src/utils/formatNode.ts +3 -3
- package/src/utils/pathUtils.ts +9 -0
- package/src/utils/progressRenderer.ts +25 -4
package/src/commands/query.ts
CHANGED
|
@@ -10,13 +10,23 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Command } from 'commander';
|
|
13
|
-
import { resolve, join,
|
|
13
|
+
import { resolve, join, basename } from 'path';
|
|
14
|
+
import { toRelativeDisplay } from '../utils/pathUtils.js';
|
|
14
15
|
import { existsSync } from 'fs';
|
|
15
|
-
import { RFDBServerBackend, parseSemanticId, findCallsInFunction as findCallsInFunctionCore, findContainingFunction as findContainingFunctionCore } from '@grafema/core';
|
|
16
|
+
import { RFDBServerBackend, parseSemanticId, parseSemanticIdV2, findCallsInFunction as findCallsInFunctionCore, findContainingFunction as findContainingFunctionCore } from '@grafema/core';
|
|
16
17
|
import { formatNodeDisplay, formatNodeInline, formatLocation } from '../utils/formatNode.js';
|
|
17
18
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
18
19
|
import { Spinner } from '../utils/spinner.js';
|
|
19
20
|
|
|
21
|
+
// Node type constants to avoid magic string duplication
|
|
22
|
+
const HTTP_ROUTE_TYPE = 'http:route';
|
|
23
|
+
const HTTP_REQUEST_TYPE = 'http:request';
|
|
24
|
+
const SOCKETIO_EVENT_TYPE = 'socketio:event';
|
|
25
|
+
const SOCKETIO_EMIT_TYPE = 'socketio:emit';
|
|
26
|
+
const SOCKETIO_ON_TYPE = 'socketio:on';
|
|
27
|
+
const GRAFEMA_PLUGIN_TYPE = 'grafema:plugin';
|
|
28
|
+
const PROPERTY_ACCESS_TYPE = 'PROPERTY_ACCESS';
|
|
29
|
+
|
|
20
30
|
interface QueryOptions {
|
|
21
31
|
project: string;
|
|
22
32
|
json?: boolean;
|
|
@@ -76,6 +86,9 @@ export const queryCommand = new Command('query')
|
|
|
76
86
|
'--raw',
|
|
77
87
|
`Execute raw Datalog query
|
|
78
88
|
|
|
89
|
+
Supports both direct queries and Datalog rules.
|
|
90
|
+
Rules (containing ":-") define a violation predicate and return matching nodes.
|
|
91
|
+
|
|
79
92
|
Predicates:
|
|
80
93
|
type(Id, Type) Find nodes by type or get type of node
|
|
81
94
|
node(Id, Type) Alias for type
|
|
@@ -84,10 +97,14 @@ Predicates:
|
|
|
84
97
|
path(Src, Dst) Check reachability between nodes
|
|
85
98
|
incoming(Dst, Src, T) Find incoming edges
|
|
86
99
|
|
|
87
|
-
|
|
100
|
+
Direct queries:
|
|
88
101
|
grafema query --raw 'type(X, "FUNCTION")'
|
|
89
102
|
grafema query --raw 'type(X, "FUNCTION"), attr(X, "name", "main")'
|
|
90
|
-
grafema query --raw 'edge(X, Y, "CALLS")'
|
|
103
|
+
grafema query --raw 'edge(X, Y, "CALLS")'
|
|
104
|
+
|
|
105
|
+
Rules (must define violation/1):
|
|
106
|
+
grafema query --raw 'violation(X) :- node(X, "FUNCTION").'
|
|
107
|
+
grafema query --raw 'violation(X) :- node(X, "http:route"), attr(X, "method", "POST").'`
|
|
91
108
|
)
|
|
92
109
|
.option(
|
|
93
110
|
'-t, --type <nodeType>',
|
|
@@ -135,15 +152,19 @@ Examples:
|
|
|
135
152
|
spinner.start();
|
|
136
153
|
|
|
137
154
|
try {
|
|
155
|
+
const limit = parseInt(options.limit, 10);
|
|
156
|
+
if (isNaN(limit) || limit < 1) {
|
|
157
|
+
spinner.stop();
|
|
158
|
+
exitWithError('Invalid limit', ['Use a positive number, e.g.: --limit 10']);
|
|
159
|
+
}
|
|
160
|
+
|
|
138
161
|
// Raw Datalog mode
|
|
139
162
|
if (options.raw) {
|
|
140
163
|
spinner.stop();
|
|
141
|
-
await executeRawQuery(backend, pattern, options);
|
|
164
|
+
await executeRawQuery(backend, pattern, limit, options.json);
|
|
142
165
|
return;
|
|
143
166
|
}
|
|
144
167
|
|
|
145
|
-
const limit = parseInt(options.limit, 10);
|
|
146
|
-
|
|
147
168
|
// Parse query with scope support
|
|
148
169
|
let query: ParsedQuery;
|
|
149
170
|
|
|
@@ -245,28 +266,31 @@ function parsePattern(pattern: string): { type: string | null; name: string } {
|
|
|
245
266
|
fn: 'FUNCTION',
|
|
246
267
|
func: 'FUNCTION',
|
|
247
268
|
class: 'CLASS',
|
|
269
|
+
interface: 'INTERFACE',
|
|
270
|
+
type: 'TYPE',
|
|
271
|
+
enum: 'ENUM',
|
|
248
272
|
module: 'MODULE',
|
|
249
273
|
variable: 'VARIABLE',
|
|
250
274
|
var: 'VARIABLE',
|
|
251
275
|
const: 'CONSTANT',
|
|
252
276
|
constant: 'CONSTANT',
|
|
253
277
|
// HTTP route aliases
|
|
254
|
-
route:
|
|
255
|
-
endpoint:
|
|
278
|
+
route: HTTP_ROUTE_TYPE,
|
|
279
|
+
endpoint: HTTP_ROUTE_TYPE,
|
|
256
280
|
// HTTP request aliases
|
|
257
|
-
request:
|
|
258
|
-
fetch:
|
|
259
|
-
api:
|
|
281
|
+
request: HTTP_REQUEST_TYPE,
|
|
282
|
+
fetch: HTTP_REQUEST_TYPE,
|
|
283
|
+
api: HTTP_REQUEST_TYPE,
|
|
260
284
|
// Socket.IO aliases
|
|
261
|
-
event:
|
|
262
|
-
emit:
|
|
263
|
-
on:
|
|
264
|
-
listener:
|
|
285
|
+
event: SOCKETIO_EVENT_TYPE,
|
|
286
|
+
emit: SOCKETIO_EMIT_TYPE,
|
|
287
|
+
on: SOCKETIO_ON_TYPE,
|
|
288
|
+
listener: SOCKETIO_ON_TYPE,
|
|
265
289
|
// Grafema internal
|
|
266
|
-
plugin:
|
|
290
|
+
plugin: GRAFEMA_PLUGIN_TYPE,
|
|
267
291
|
// Property access aliases (REG-395)
|
|
268
|
-
property:
|
|
269
|
-
prop:
|
|
292
|
+
property: PROPERTY_ACCESS_TYPE,
|
|
293
|
+
prop: PROPERTY_ACCESS_TYPE,
|
|
270
294
|
};
|
|
271
295
|
|
|
272
296
|
if (typeMap[typeWord]) {
|
|
@@ -370,6 +394,36 @@ export function isFileScope(scope: string): boolean {
|
|
|
370
394
|
* @returns true if ID matches all constraints
|
|
371
395
|
*/
|
|
372
396
|
export function matchesScope(semanticId: string, file: string | null, scopes: string[]): boolean {
|
|
397
|
+
// No constraints = everything matches (regardless of ID format)
|
|
398
|
+
if (file === null && scopes.length === 0) return true;
|
|
399
|
+
|
|
400
|
+
// Try v2 parsing first
|
|
401
|
+
const parsedV2 = parseSemanticIdV2(semanticId);
|
|
402
|
+
if (parsedV2) {
|
|
403
|
+
// File scope check (v2)
|
|
404
|
+
if (file !== null) {
|
|
405
|
+
if (parsedV2.file === file) {
|
|
406
|
+
// Exact match - OK
|
|
407
|
+
} else if (parsedV2.file.endsWith('/' + file)) {
|
|
408
|
+
// Partial path match - OK
|
|
409
|
+
} else if (basename(parsedV2.file) === file) {
|
|
410
|
+
// Basename exact match - OK
|
|
411
|
+
} else {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Function/class scope check (v2): check namedParent
|
|
417
|
+
for (const scope of scopes) {
|
|
418
|
+
if (!parsedV2.namedParent || parsedV2.namedParent.toLowerCase() !== scope.toLowerCase()) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Fallback to v1 parsing
|
|
373
427
|
const parsed = parseSemanticId(semanticId);
|
|
374
428
|
if (!parsed) return false;
|
|
375
429
|
|
|
@@ -424,6 +478,17 @@ export function matchesScope(semanticId: string, file: string | null, scopes: st
|
|
|
424
478
|
* @returns Human-readable scope context or null
|
|
425
479
|
*/
|
|
426
480
|
export function extractScopeContext(semanticId: string): string | null {
|
|
481
|
+
// Try v2 parsing first
|
|
482
|
+
const parsedV2 = parseSemanticIdV2(semanticId);
|
|
483
|
+
if (parsedV2) {
|
|
484
|
+
if (parsedV2.namedParent) {
|
|
485
|
+
return `inside ${parsedV2.namedParent}`;
|
|
486
|
+
}
|
|
487
|
+
// v2 with no parent = top-level
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Fallback to v1 parsing
|
|
427
492
|
const parsed = parseSemanticId(semanticId);
|
|
428
493
|
if (!parsed) return null;
|
|
429
494
|
|
|
@@ -475,7 +540,7 @@ function matchesSearchPattern(
|
|
|
475
540
|
const lowerPattern = pattern.toLowerCase();
|
|
476
541
|
|
|
477
542
|
// HTTP routes: search method and path
|
|
478
|
-
if (nodeType ===
|
|
543
|
+
if (nodeType === HTTP_ROUTE_TYPE) {
|
|
479
544
|
const method = (node.method || '').toLowerCase();
|
|
480
545
|
const path = (node.path || '').toLowerCase();
|
|
481
546
|
|
|
@@ -501,7 +566,7 @@ function matchesSearchPattern(
|
|
|
501
566
|
}
|
|
502
567
|
|
|
503
568
|
// HTTP requests: search method and url
|
|
504
|
-
if (nodeType ===
|
|
569
|
+
if (nodeType === HTTP_REQUEST_TYPE) {
|
|
505
570
|
const method = (node.method || '').toLowerCase();
|
|
506
571
|
const url = (node.url || '').toLowerCase();
|
|
507
572
|
|
|
@@ -527,13 +592,13 @@ function matchesSearchPattern(
|
|
|
527
592
|
}
|
|
528
593
|
|
|
529
594
|
// Socket.IO event channels: search name field (standard)
|
|
530
|
-
if (nodeType ===
|
|
595
|
+
if (nodeType === SOCKETIO_EVENT_TYPE) {
|
|
531
596
|
const nodeName = (node.name || '').toLowerCase();
|
|
532
597
|
return nodeName.includes(lowerPattern);
|
|
533
598
|
}
|
|
534
599
|
|
|
535
600
|
// Socket.IO emit/on: search event field
|
|
536
|
-
if (nodeType ===
|
|
601
|
+
if (nodeType === SOCKETIO_EMIT_TYPE || nodeType === SOCKETIO_ON_TYPE) {
|
|
537
602
|
const eventName = (node.event || '').toLowerCase();
|
|
538
603
|
return eventName.includes(lowerPattern);
|
|
539
604
|
}
|
|
@@ -557,19 +622,22 @@ async function findNodes(
|
|
|
557
622
|
: [
|
|
558
623
|
'FUNCTION',
|
|
559
624
|
'CLASS',
|
|
625
|
+
'INTERFACE',
|
|
626
|
+
'TYPE',
|
|
627
|
+
'ENUM',
|
|
560
628
|
'MODULE',
|
|
561
629
|
'VARIABLE',
|
|
562
630
|
'CONSTANT',
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
631
|
+
HTTP_ROUTE_TYPE,
|
|
632
|
+
HTTP_REQUEST_TYPE,
|
|
633
|
+
SOCKETIO_EVENT_TYPE,
|
|
634
|
+
SOCKETIO_EMIT_TYPE,
|
|
635
|
+
SOCKETIO_ON_TYPE,
|
|
636
|
+
PROPERTY_ACCESS_TYPE,
|
|
569
637
|
];
|
|
570
638
|
|
|
571
639
|
for (const nodeType of searchTypes) {
|
|
572
|
-
for await (const node of backend.queryNodes({ nodeType
|
|
640
|
+
for await (const node of backend.queryNodes({ nodeType })) {
|
|
573
641
|
// Type-aware field matching (name)
|
|
574
642
|
const nameMatches = matchesSearchPattern(node, nodeType, query.name);
|
|
575
643
|
if (!nameMatches) continue;
|
|
@@ -590,24 +658,24 @@ async function findNodes(
|
|
|
590
658
|
nodeInfo.scopeContext = extractScopeContext(node.id);
|
|
591
659
|
|
|
592
660
|
// Include method and path for http:route nodes
|
|
593
|
-
if (nodeType ===
|
|
661
|
+
if (nodeType === HTTP_ROUTE_TYPE) {
|
|
594
662
|
nodeInfo.method = node.method as string | undefined;
|
|
595
663
|
nodeInfo.path = node.path as string | undefined;
|
|
596
664
|
}
|
|
597
665
|
|
|
598
666
|
// Include method and url for http:request nodes
|
|
599
|
-
if (nodeType ===
|
|
667
|
+
if (nodeType === HTTP_REQUEST_TYPE) {
|
|
600
668
|
nodeInfo.method = node.method as string | undefined;
|
|
601
669
|
nodeInfo.url = node.url as string | undefined;
|
|
602
670
|
}
|
|
603
671
|
|
|
604
672
|
// Include event field for Socket.IO nodes
|
|
605
|
-
if (nodeType ===
|
|
673
|
+
if (nodeType === SOCKETIO_EVENT_TYPE || nodeType === SOCKETIO_EMIT_TYPE || nodeType === SOCKETIO_ON_TYPE) {
|
|
606
674
|
nodeInfo.event = node.event as string | undefined;
|
|
607
675
|
}
|
|
608
676
|
|
|
609
677
|
// Include emit-specific fields
|
|
610
|
-
if (nodeType ===
|
|
678
|
+
if (nodeType === SOCKETIO_EMIT_TYPE) {
|
|
611
679
|
nodeInfo.room = node.room as string | undefined;
|
|
612
680
|
nodeInfo.namespace = node.namespace as string | undefined;
|
|
613
681
|
nodeInfo.broadcast = node.broadcast as boolean | undefined;
|
|
@@ -615,13 +683,13 @@ async function findNodes(
|
|
|
615
683
|
}
|
|
616
684
|
|
|
617
685
|
// Include listener-specific fields
|
|
618
|
-
if (nodeType ===
|
|
686
|
+
if (nodeType === SOCKETIO_ON_TYPE) {
|
|
619
687
|
nodeInfo.objectName = node.objectName as string | undefined;
|
|
620
688
|
nodeInfo.handlerName = node.handlerName as string | undefined;
|
|
621
689
|
}
|
|
622
690
|
|
|
623
691
|
// Include plugin-specific fields
|
|
624
|
-
if (nodeType ===
|
|
692
|
+
if (nodeType === GRAFEMA_PLUGIN_TYPE) {
|
|
625
693
|
nodeInfo.phase = node.phase as string | undefined;
|
|
626
694
|
nodeInfo.priority = node.priority as number | undefined;
|
|
627
695
|
nodeInfo.builtin = node.builtin as boolean | undefined;
|
|
@@ -631,7 +699,7 @@ async function findNodes(
|
|
|
631
699
|
}
|
|
632
700
|
|
|
633
701
|
// Include objectName for PROPERTY_ACCESS nodes (REG-395)
|
|
634
|
-
if (nodeType ===
|
|
702
|
+
if (nodeType === PROPERTY_ACCESS_TYPE) {
|
|
635
703
|
nodeInfo.objectName = node.objectName as string | undefined;
|
|
636
704
|
}
|
|
637
705
|
|
|
@@ -741,31 +809,31 @@ async function getCallees(
|
|
|
741
809
|
*/
|
|
742
810
|
async function displayNode(node: NodeInfo, projectPath: string, backend: RFDBServerBackend): Promise<void> {
|
|
743
811
|
// Special formatting for HTTP routes
|
|
744
|
-
if (node.type ===
|
|
812
|
+
if (node.type === HTTP_ROUTE_TYPE && node.method && node.path) {
|
|
745
813
|
console.log(formatHttpRouteDisplay(node, projectPath));
|
|
746
814
|
return;
|
|
747
815
|
}
|
|
748
816
|
|
|
749
817
|
// Special formatting for HTTP requests
|
|
750
|
-
if (node.type ===
|
|
818
|
+
if (node.type === HTTP_REQUEST_TYPE) {
|
|
751
819
|
console.log(formatHttpRequestDisplay(node, projectPath));
|
|
752
820
|
return;
|
|
753
821
|
}
|
|
754
822
|
|
|
755
823
|
// Special formatting for Socket.IO event channels
|
|
756
|
-
if (node.type ===
|
|
824
|
+
if (node.type === SOCKETIO_EVENT_TYPE) {
|
|
757
825
|
console.log(await formatSocketEventDisplay(node, projectPath, backend));
|
|
758
826
|
return;
|
|
759
827
|
}
|
|
760
828
|
|
|
761
829
|
// Special formatting for Socket.IO emit/on
|
|
762
|
-
if (node.type ===
|
|
830
|
+
if (node.type === SOCKETIO_EMIT_TYPE || node.type === SOCKETIO_ON_TYPE) {
|
|
763
831
|
console.log(formatSocketIONodeDisplay(node, projectPath));
|
|
764
832
|
return;
|
|
765
833
|
}
|
|
766
834
|
|
|
767
835
|
// Special formatting for Grafema plugin nodes
|
|
768
|
-
if (node.type ===
|
|
836
|
+
if (node.type === GRAFEMA_PLUGIN_TYPE) {
|
|
769
837
|
console.log(formatPluginDisplay(node, projectPath));
|
|
770
838
|
return;
|
|
771
839
|
}
|
|
@@ -793,7 +861,7 @@ function formatHttpRouteDisplay(node: NodeInfo, projectPath: string): string {
|
|
|
793
861
|
|
|
794
862
|
// Line 2: Location
|
|
795
863
|
if (node.file) {
|
|
796
|
-
const relPath =
|
|
864
|
+
const relPath = toRelativeDisplay(node.file, projectPath);
|
|
797
865
|
const loc = node.line ? `${relPath}:${node.line}` : relPath;
|
|
798
866
|
lines.push(` Location: ${loc}`);
|
|
799
867
|
}
|
|
@@ -818,7 +886,7 @@ function formatHttpRequestDisplay(node: NodeInfo, projectPath: string): string {
|
|
|
818
886
|
|
|
819
887
|
// Line 2: Location
|
|
820
888
|
if (node.file) {
|
|
821
|
-
const relPath =
|
|
889
|
+
const relPath = toRelativeDisplay(node.file, projectPath);
|
|
822
890
|
const loc = node.line ? `${relPath}:${node.line}` : relPath;
|
|
823
891
|
lines.push(` Location: ${loc}`);
|
|
824
892
|
}
|
|
@@ -905,7 +973,7 @@ function formatSocketIONodeDisplay(node: NodeInfo, projectPath: string): string
|
|
|
905
973
|
}
|
|
906
974
|
|
|
907
975
|
// Emit-specific fields
|
|
908
|
-
if (node.type ===
|
|
976
|
+
if (node.type === SOCKETIO_EMIT_TYPE) {
|
|
909
977
|
if (node.room) {
|
|
910
978
|
lines.push(` Room: ${node.room}`);
|
|
911
979
|
}
|
|
@@ -918,7 +986,7 @@ function formatSocketIONodeDisplay(node: NodeInfo, projectPath: string): string
|
|
|
918
986
|
}
|
|
919
987
|
|
|
920
988
|
// Listener-specific fields
|
|
921
|
-
if (node.type ===
|
|
989
|
+
if (node.type === SOCKETIO_ON_TYPE && node.handlerName) {
|
|
922
990
|
lines.push(` Handler: ${node.handlerName}`);
|
|
923
991
|
}
|
|
924
992
|
|
|
@@ -959,26 +1027,63 @@ function formatPluginDisplay(node: NodeInfo, projectPath: string): string {
|
|
|
959
1027
|
}
|
|
960
1028
|
|
|
961
1029
|
if (node.file) {
|
|
962
|
-
const relPath =
|
|
1030
|
+
const relPath = toRelativeDisplay(node.file, projectPath);
|
|
963
1031
|
lines.push(` Source: ${relPath}`);
|
|
964
1032
|
}
|
|
965
1033
|
|
|
966
1034
|
return lines.join('\n');
|
|
967
1035
|
}
|
|
968
1036
|
|
|
1037
|
+
/** Built-in Datalog predicates supported by RFDB server */
|
|
1038
|
+
export const BUILTIN_PREDICATES = new Set([
|
|
1039
|
+
'node', 'type', 'edge', 'incoming', 'path',
|
|
1040
|
+
'attr', 'attr_edge',
|
|
1041
|
+
'neq', 'starts_with', 'not_starts_with',
|
|
1042
|
+
]);
|
|
1043
|
+
|
|
1044
|
+
/** Extract predicate names from a Datalog query string */
|
|
1045
|
+
export function extractPredicates(query: string): string[] {
|
|
1046
|
+
const regex = /\b([a-z_][a-z0-9_]*)\s*\(/g;
|
|
1047
|
+
const predicates = new Set<string>();
|
|
1048
|
+
let match;
|
|
1049
|
+
while ((match = regex.exec(query)) !== null) {
|
|
1050
|
+
predicates.add(match[1]);
|
|
1051
|
+
}
|
|
1052
|
+
return [...predicates];
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/** Extract predicate names defined as rule heads (word(...) :-) */
|
|
1056
|
+
export function extractRuleHeads(query: string): Set<string> {
|
|
1057
|
+
const regex = /\b([a-z_][a-z0-9_]*)\s*\([^)]*\)\s*:-/g;
|
|
1058
|
+
const heads = new Set<string>();
|
|
1059
|
+
let match;
|
|
1060
|
+
while ((match = regex.exec(query)) !== null) {
|
|
1061
|
+
heads.add(match[1]);
|
|
1062
|
+
}
|
|
1063
|
+
return heads;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/** Find predicates in a query that are not built-in and not user-defined rule heads */
|
|
1067
|
+
export function getUnknownPredicates(query: string): string[] {
|
|
1068
|
+
const predicates = extractPredicates(query);
|
|
1069
|
+
const ruleHeads = extractRuleHeads(query);
|
|
1070
|
+
return predicates.filter(p => !BUILTIN_PREDICATES.has(p) && !ruleHeads.has(p));
|
|
1071
|
+
}
|
|
1072
|
+
|
|
969
1073
|
/**
|
|
970
|
-
* Execute raw Datalog query
|
|
1074
|
+
* Execute raw Datalog query.
|
|
1075
|
+
* Uses unified executeDatalog endpoint which auto-detects rules vs direct queries.
|
|
971
1076
|
*/
|
|
972
1077
|
async function executeRawQuery(
|
|
973
1078
|
backend: RFDBServerBackend,
|
|
974
1079
|
query: string,
|
|
975
|
-
|
|
1080
|
+
limit: number,
|
|
1081
|
+
json?: boolean
|
|
976
1082
|
): Promise<void> {
|
|
977
|
-
const results = await backend.
|
|
978
|
-
const limit = parseInt(options.limit, 10);
|
|
1083
|
+
const results = await backend.executeDatalog(query);
|
|
979
1084
|
const limited = results.slice(0, limit);
|
|
980
1085
|
|
|
981
|
-
if (
|
|
1086
|
+
if (json) {
|
|
982
1087
|
console.log(JSON.stringify(limited, null, 2));
|
|
983
1088
|
} else {
|
|
984
1089
|
if (limited.length === 0) {
|
|
@@ -992,4 +1097,14 @@ async function executeRawQuery(
|
|
|
992
1097
|
}
|
|
993
1098
|
}
|
|
994
1099
|
}
|
|
1100
|
+
|
|
1101
|
+
// Show warning for unknown predicates (on stderr, works in both text and JSON mode)
|
|
1102
|
+
if (limited.length === 0) {
|
|
1103
|
+
const unknown = getUnknownPredicates(query);
|
|
1104
|
+
if (unknown.length > 0) {
|
|
1105
|
+
const unknownList = unknown.map(p => `'${p}'`).join(', ');
|
|
1106
|
+
const builtinList = [...BUILTIN_PREDICATES].join(', ');
|
|
1107
|
+
console.error(`Note: unknown predicate ${unknownList}. Built-in predicates: ${builtinList}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
995
1110
|
}
|
package/src/commands/schema.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Command } from 'commander';
|
|
13
|
-
import { resolve, join
|
|
13
|
+
import { resolve, join } from 'path';
|
|
14
|
+
import { toRelativeDisplay } from '../utils/pathUtils.js';
|
|
14
15
|
import { existsSync, writeFileSync } from 'fs';
|
|
15
16
|
import {
|
|
16
17
|
RFDBServerBackend,
|
|
@@ -82,7 +83,7 @@ function formatInterfaceYaml(schema: InterfaceSchema): string {
|
|
|
82
83
|
|
|
83
84
|
function formatInterfaceMarkdown(schema: InterfaceSchema, projectPath: string): string {
|
|
84
85
|
const lines: string[] = [];
|
|
85
|
-
const relPath =
|
|
86
|
+
const relPath = toRelativeDisplay(schema.source.file, projectPath);
|
|
86
87
|
|
|
87
88
|
lines.push(`# Interface: ${schema.name}`);
|
|
88
89
|
lines.push('');
|
package/src/commands/server.ts
CHANGED
|
@@ -9,84 +9,28 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { Command } from 'commander';
|
|
12
|
-
import { resolve, join
|
|
12
|
+
import { resolve, join } from 'path';
|
|
13
13
|
import { existsSync, unlinkSync, writeFileSync, readFileSync } from 'fs';
|
|
14
14
|
import { spawn } from 'child_process';
|
|
15
|
-
import { fileURLToPath } from 'url';
|
|
16
15
|
import { setTimeout as sleep } from 'timers/promises';
|
|
17
|
-
import { RFDBClient, loadConfig, RFDBServerBackend } from '@grafema/core';
|
|
16
|
+
import { RFDBClient, loadConfig, RFDBServerBackend, findRfdbBinary } from '@grafema/core';
|
|
18
17
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
19
18
|
|
|
20
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
-
const __dirname = dirname(__filename);
|
|
22
|
-
|
|
23
19
|
// Extend config type for server settings
|
|
24
20
|
interface ServerConfig {
|
|
25
21
|
binaryPath?: string;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
/**
|
|
29
|
-
* Find RFDB server binary
|
|
30
|
-
*
|
|
31
|
-
* 2. Monorepo development (target/release, target/debug)
|
|
32
|
-
* 3. @grafema/rfdb npm package (prebuilt binaries)
|
|
33
|
-
* 4. ~/.local/bin/rfdb-server (user-installed)
|
|
25
|
+
* Find RFDB server binary using shared utility.
|
|
26
|
+
* Wraps findRfdbBinary() with CLI-specific error logging.
|
|
34
27
|
*/
|
|
35
28
|
function findServerBinary(explicitPath?: string): string | null {
|
|
36
|
-
|
|
37
|
-
if (explicitPath) {
|
|
38
|
-
|
|
39
|
-
if (existsSync(resolved)) {
|
|
40
|
-
return resolved;
|
|
41
|
-
}
|
|
42
|
-
// Explicit path specified but not found - this is an error
|
|
43
|
-
console.error(`Specified binary not found: ${resolved}`);
|
|
44
|
-
return null;
|
|
29
|
+
const binaryPath = findRfdbBinary({ explicitPath });
|
|
30
|
+
if (!binaryPath && explicitPath) {
|
|
31
|
+
console.error(`Specified binary not found: ${explicitPath}`);
|
|
45
32
|
}
|
|
46
|
-
|
|
47
|
-
// 2. Check packages/rfdb-server in monorepo (for development)
|
|
48
|
-
const projectRoot = join(__dirname, '../../../..');
|
|
49
|
-
const releaseBinary = join(projectRoot, 'packages/rfdb-server/target/release/rfdb-server');
|
|
50
|
-
if (existsSync(releaseBinary)) {
|
|
51
|
-
return releaseBinary;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const debugBinary = join(projectRoot, 'packages/rfdb-server/target/debug/rfdb-server');
|
|
55
|
-
if (existsSync(debugBinary)) {
|
|
56
|
-
return debugBinary;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 3. Check @grafema/rfdb npm package
|
|
60
|
-
try {
|
|
61
|
-
const rfdbPkg = require.resolve('@grafema/rfdb');
|
|
62
|
-
const rfdbDir = dirname(rfdbPkg);
|
|
63
|
-
const platform = process.platform;
|
|
64
|
-
const arch = process.arch;
|
|
65
|
-
|
|
66
|
-
let platformDir: string;
|
|
67
|
-
if (platform === 'darwin') {
|
|
68
|
-
platformDir = arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
|
|
69
|
-
} else if (platform === 'linux') {
|
|
70
|
-
platformDir = arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
|
|
71
|
-
} else {
|
|
72
|
-
platformDir = `${platform}-${arch}`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const npmBinary = join(rfdbDir, 'prebuilt', platformDir, 'rfdb-server');
|
|
76
|
-
if (existsSync(npmBinary)) {
|
|
77
|
-
return npmBinary;
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
// @grafema/rfdb not installed
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 4. Check ~/.local/bin (user-installed binary for unsupported platforms)
|
|
84
|
-
const homeBinary = join(process.env.HOME || '', '.local', 'bin', 'rfdb-server');
|
|
85
|
-
if (existsSync(homeBinary)) {
|
|
86
|
-
return homeBinary;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return null;
|
|
33
|
+
return binaryPath;
|
|
90
34
|
}
|
|
91
35
|
|
|
92
36
|
/**
|