@grafema/cli 0.1.1-alpha

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 (66) hide show
  1. package/LICENSE +190 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +36 -0
  5. package/dist/commands/analyze.d.ts +6 -0
  6. package/dist/commands/analyze.d.ts.map +1 -0
  7. package/dist/commands/analyze.js +209 -0
  8. package/dist/commands/check.d.ts +10 -0
  9. package/dist/commands/check.d.ts.map +1 -0
  10. package/dist/commands/check.js +295 -0
  11. package/dist/commands/coverage.d.ts +11 -0
  12. package/dist/commands/coverage.d.ts.map +1 -0
  13. package/dist/commands/coverage.js +96 -0
  14. package/dist/commands/explore.d.ts +6 -0
  15. package/dist/commands/explore.d.ts.map +1 -0
  16. package/dist/commands/explore.js +633 -0
  17. package/dist/commands/get.d.ts +10 -0
  18. package/dist/commands/get.d.ts.map +1 -0
  19. package/dist/commands/get.js +189 -0
  20. package/dist/commands/impact.d.ts +10 -0
  21. package/dist/commands/impact.d.ts.map +1 -0
  22. package/dist/commands/impact.js +313 -0
  23. package/dist/commands/init.d.ts +6 -0
  24. package/dist/commands/init.d.ts.map +1 -0
  25. package/dist/commands/init.js +94 -0
  26. package/dist/commands/overview.d.ts +6 -0
  27. package/dist/commands/overview.d.ts.map +1 -0
  28. package/dist/commands/overview.js +91 -0
  29. package/dist/commands/query.d.ts +13 -0
  30. package/dist/commands/query.d.ts.map +1 -0
  31. package/dist/commands/query.js +340 -0
  32. package/dist/commands/server.d.ts +11 -0
  33. package/dist/commands/server.d.ts.map +1 -0
  34. package/dist/commands/server.js +300 -0
  35. package/dist/commands/stats.d.ts +6 -0
  36. package/dist/commands/stats.d.ts.map +1 -0
  37. package/dist/commands/stats.js +52 -0
  38. package/dist/commands/trace.d.ts +10 -0
  39. package/dist/commands/trace.d.ts.map +1 -0
  40. package/dist/commands/trace.js +270 -0
  41. package/dist/utils/codePreview.d.ts +28 -0
  42. package/dist/utils/codePreview.d.ts.map +1 -0
  43. package/dist/utils/codePreview.js +51 -0
  44. package/dist/utils/errorFormatter.d.ts +24 -0
  45. package/dist/utils/errorFormatter.d.ts.map +1 -0
  46. package/dist/utils/errorFormatter.js +32 -0
  47. package/dist/utils/formatNode.d.ts +53 -0
  48. package/dist/utils/formatNode.d.ts.map +1 -0
  49. package/dist/utils/formatNode.js +49 -0
  50. package/package.json +54 -0
  51. package/src/cli.ts +41 -0
  52. package/src/commands/analyze.ts +271 -0
  53. package/src/commands/check.ts +379 -0
  54. package/src/commands/coverage.ts +108 -0
  55. package/src/commands/explore.tsx +1056 -0
  56. package/src/commands/get.ts +265 -0
  57. package/src/commands/impact.ts +400 -0
  58. package/src/commands/init.ts +112 -0
  59. package/src/commands/overview.ts +108 -0
  60. package/src/commands/query.ts +425 -0
  61. package/src/commands/server.ts +335 -0
  62. package/src/commands/stats.ts +58 -0
  63. package/src/commands/trace.ts +341 -0
  64. package/src/utils/codePreview.ts +77 -0
  65. package/src/utils/errorFormatter.ts +35 -0
  66. package/src/utils/formatNode.ts +88 -0
@@ -0,0 +1,633 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Explore command - Interactive TUI for graph navigation
4
+ */
5
+ import { Command } from 'commander';
6
+ import { resolve, join, relative } from 'path';
7
+ import { existsSync } from 'fs';
8
+ import { execSync } from 'child_process';
9
+ import { useState, useEffect } from 'react';
10
+ import { render, Box, Text, useInput, useApp } from 'ink';
11
+ import { RFDBServerBackend } from '@grafema/core';
12
+ import { getCodePreview, formatCodePreview } from '../utils/codePreview.js';
13
+ import { exitWithError } from '../utils/errorFormatter.js';
14
+ // Main Explorer Component
15
+ function Explorer({ backend, startNode, projectPath }) {
16
+ const { exit } = useApp();
17
+ const [state, setState] = useState({
18
+ currentNode: startNode,
19
+ callers: [],
20
+ callees: [],
21
+ fields: [],
22
+ methods: [],
23
+ dataFlowSources: [],
24
+ dataFlowTargets: [],
25
+ breadcrumbs: startNode ? [startNode] : [],
26
+ selectedIndex: 0,
27
+ selectedPanel: 'callers',
28
+ searchMode: false,
29
+ searchQuery: '',
30
+ searchResults: [],
31
+ modules: [],
32
+ loading: true,
33
+ error: null,
34
+ visibleCallers: 10,
35
+ visibleCallees: 10,
36
+ viewMode: 'function',
37
+ showCodePreview: false,
38
+ codePreviewLines: [],
39
+ });
40
+ // Load data when currentNode changes
41
+ useEffect(() => {
42
+ if (!state.currentNode)
43
+ return;
44
+ const load = async () => {
45
+ setState(s => ({ ...s, loading: true }));
46
+ try {
47
+ const nodeType = state.currentNode.type;
48
+ if (nodeType === 'CLASS') {
49
+ // Load fields and methods for class
50
+ const { fields, methods } = await getClassMembers(backend, state.currentNode.id);
51
+ setState(s => ({
52
+ ...s,
53
+ fields,
54
+ methods,
55
+ viewMode: 'class',
56
+ selectedPanel: 'fields',
57
+ loading: false,
58
+ selectedIndex: 0,
59
+ }));
60
+ }
61
+ else if (nodeType === 'VARIABLE' || nodeType === 'PARAMETER') {
62
+ // Load data flow for variable
63
+ const { sources, targets } = await getDataFlow(backend, state.currentNode.id);
64
+ setState(s => ({
65
+ ...s,
66
+ dataFlowSources: sources,
67
+ dataFlowTargets: targets,
68
+ viewMode: 'dataflow',
69
+ selectedPanel: 'sources',
70
+ loading: false,
71
+ selectedIndex: 0,
72
+ }));
73
+ }
74
+ else {
75
+ // Load callers/callees for function
76
+ const callers = await getCallers(backend, state.currentNode.id, 50);
77
+ const callees = await getCallees(backend, state.currentNode.id, 50);
78
+ setState(s => ({
79
+ ...s,
80
+ callers,
81
+ callees,
82
+ viewMode: 'function',
83
+ selectedPanel: 'callers',
84
+ loading: false,
85
+ selectedIndex: 0,
86
+ visibleCallers: 10,
87
+ visibleCallees: 10,
88
+ }));
89
+ }
90
+ }
91
+ catch (err) {
92
+ setState(s => ({
93
+ ...s,
94
+ error: err.message,
95
+ loading: false,
96
+ }));
97
+ }
98
+ };
99
+ load();
100
+ }, [state.currentNode?.id]);
101
+ // Get current list based on view mode and panel
102
+ const getCurrentList = () => {
103
+ if (state.viewMode === 'search')
104
+ return state.searchResults;
105
+ if (state.viewMode === 'modules')
106
+ return state.modules;
107
+ if (state.viewMode === 'class') {
108
+ return state.selectedPanel === 'fields' ? state.fields : state.methods;
109
+ }
110
+ if (state.viewMode === 'dataflow') {
111
+ return state.selectedPanel === 'sources' ? state.dataFlowSources : state.dataFlowTargets;
112
+ }
113
+ return state.selectedPanel === 'callers' ? state.callers : state.callees;
114
+ };
115
+ // Keyboard input
116
+ useInput((input, key) => {
117
+ if (state.searchMode) {
118
+ if (key.escape) {
119
+ setState(s => ({ ...s, searchMode: false, searchQuery: '' }));
120
+ }
121
+ else if (key.return) {
122
+ performSearch(state.searchQuery);
123
+ setState(s => ({ ...s, searchMode: false }));
124
+ }
125
+ else if (key.backspace || key.delete) {
126
+ setState(s => ({ ...s, searchQuery: s.searchQuery.slice(0, -1) }));
127
+ }
128
+ else if (input && !key.ctrl && !key.meta) {
129
+ setState(s => ({ ...s, searchQuery: s.searchQuery + input }));
130
+ }
131
+ return;
132
+ }
133
+ // Normal mode
134
+ if (input === 'q') {
135
+ exit();
136
+ return;
137
+ }
138
+ if (input === '/') {
139
+ setState(s => ({ ...s, searchMode: true, searchQuery: '' }));
140
+ return;
141
+ }
142
+ if (input === '?') {
143
+ // TODO: show help
144
+ return;
145
+ }
146
+ // 'm' - show modules view
147
+ if (input === 'm') {
148
+ loadModules();
149
+ return;
150
+ }
151
+ // Space - toggle code preview
152
+ if (input === ' ') {
153
+ if (state.currentNode && state.currentNode.file && state.currentNode.line) {
154
+ if (state.showCodePreview) {
155
+ // Hide code preview
156
+ setState(s => ({ ...s, showCodePreview: false, codePreviewLines: [] }));
157
+ }
158
+ else {
159
+ // Show code preview
160
+ const preview = getCodePreview({
161
+ file: state.currentNode.file,
162
+ line: state.currentNode.line,
163
+ });
164
+ if (preview) {
165
+ const formatted = formatCodePreview(preview, state.currentNode.line);
166
+ setState(s => ({ ...s, showCodePreview: true, codePreviewLines: formatted }));
167
+ }
168
+ }
169
+ }
170
+ return;
171
+ }
172
+ // 'o' - open in editor
173
+ if (input === 'o') {
174
+ if (state.currentNode && state.currentNode.file) {
175
+ const editor = process.env.EDITOR || 'code';
176
+ const file = state.currentNode.file;
177
+ const line = state.currentNode.line;
178
+ try {
179
+ if (editor.includes('code')) {
180
+ // VS Code supports --goto
181
+ execSync(`${editor} --goto "${file}:${line || 1}"`, { stdio: 'ignore' });
182
+ }
183
+ else {
184
+ // Generic editor
185
+ execSync(`${editor} +${line || 1} "${file}"`, { stdio: 'ignore' });
186
+ }
187
+ }
188
+ catch {
189
+ // Ignore editor errors
190
+ }
191
+ }
192
+ return;
193
+ }
194
+ // Arrow keys for panel switching
195
+ if (key.leftArrow || input === 'h') {
196
+ if (state.viewMode === 'function') {
197
+ setState(s => ({ ...s, selectedPanel: 'callers', selectedIndex: 0 }));
198
+ }
199
+ else if (state.viewMode === 'class') {
200
+ setState(s => ({ ...s, selectedPanel: 'fields', selectedIndex: 0 }));
201
+ }
202
+ else if (state.viewMode === 'dataflow') {
203
+ setState(s => ({ ...s, selectedPanel: 'sources', selectedIndex: 0 }));
204
+ }
205
+ return;
206
+ }
207
+ if (key.rightArrow || input === 'l') {
208
+ if (state.viewMode === 'function') {
209
+ setState(s => ({ ...s, selectedPanel: 'callees', selectedIndex: 0 }));
210
+ }
211
+ else if (state.viewMode === 'class') {
212
+ setState(s => ({ ...s, selectedPanel: 'methods', selectedIndex: 0 }));
213
+ }
214
+ else if (state.viewMode === 'dataflow') {
215
+ setState(s => ({ ...s, selectedPanel: 'targets', selectedIndex: 0 }));
216
+ }
217
+ return;
218
+ }
219
+ // Up arrow - works in all modes
220
+ if (key.upArrow || input === 'k') {
221
+ setState(s => ({
222
+ ...s,
223
+ selectedIndex: Math.max(0, s.selectedIndex - 1),
224
+ }));
225
+ return;
226
+ }
227
+ // Down arrow - works in all modes
228
+ if (key.downArrow || input === 'j') {
229
+ const list = getCurrentList();
230
+ setState(s => {
231
+ const newIndex = Math.min(list.length - 1, s.selectedIndex + 1);
232
+ return { ...s, selectedIndex: newIndex };
233
+ });
234
+ return;
235
+ }
236
+ // Enter - select item
237
+ if (key.return) {
238
+ const list = getCurrentList();
239
+ const selected = list[state.selectedIndex];
240
+ if (selected) {
241
+ // Don't navigate into recursive calls (same function)
242
+ if (selected.id !== state.currentNode?.id) {
243
+ navigateTo(selected);
244
+ setState(s => ({ ...s, viewMode: 'function', selectedPanel: 'callers' }));
245
+ }
246
+ }
247
+ return;
248
+ }
249
+ if (key.backspace || key.delete) {
250
+ goBack();
251
+ return;
252
+ }
253
+ if (input === 'tab') {
254
+ setState(s => ({
255
+ ...s,
256
+ selectedPanel: s.selectedPanel === 'callers' ? 'callees' : 'callers',
257
+ selectedIndex: 0,
258
+ }));
259
+ return;
260
+ }
261
+ });
262
+ const navigateTo = (node) => {
263
+ setState(s => ({
264
+ ...s,
265
+ currentNode: node,
266
+ breadcrumbs: [...s.breadcrumbs, node],
267
+ selectedIndex: 0,
268
+ }));
269
+ };
270
+ const goBack = () => {
271
+ // From search/modules view, go back to function view
272
+ if (state.viewMode !== 'function') {
273
+ setState(s => ({
274
+ ...s,
275
+ viewMode: 'function',
276
+ selectedPanel: 'callers',
277
+ selectedIndex: 0,
278
+ }));
279
+ return;
280
+ }
281
+ // From function view with breadcrumbs, go back
282
+ if (state.breadcrumbs.length > 1) {
283
+ const newBreadcrumbs = state.breadcrumbs.slice(0, -1);
284
+ const previousNode = newBreadcrumbs[newBreadcrumbs.length - 1];
285
+ setState(s => ({
286
+ ...s,
287
+ currentNode: previousNode,
288
+ breadcrumbs: newBreadcrumbs,
289
+ selectedIndex: 0,
290
+ }));
291
+ }
292
+ };
293
+ const performSearch = async (query) => {
294
+ if (!query.trim())
295
+ return;
296
+ setState(s => ({ ...s, loading: true }));
297
+ try {
298
+ const results = await searchNodes(backend, query, 20);
299
+ setState(s => ({
300
+ ...s,
301
+ searchResults: results,
302
+ viewMode: 'search',
303
+ selectedPanel: 'search',
304
+ selectedIndex: 0,
305
+ loading: false,
306
+ error: results.length === 0 ? `No results for "${query}"` : null,
307
+ }));
308
+ }
309
+ catch (err) {
310
+ setState(s => ({
311
+ ...s,
312
+ error: err.message,
313
+ loading: false,
314
+ }));
315
+ }
316
+ };
317
+ const loadModules = async () => {
318
+ setState(s => ({ ...s, loading: true }));
319
+ try {
320
+ const modules = await getModules(backend, 50);
321
+ setState(s => ({
322
+ ...s,
323
+ modules,
324
+ viewMode: 'modules',
325
+ selectedPanel: 'modules',
326
+ selectedIndex: 0,
327
+ loading: false,
328
+ }));
329
+ }
330
+ catch (err) {
331
+ setState(s => ({
332
+ ...s,
333
+ error: err.message,
334
+ loading: false,
335
+ }));
336
+ }
337
+ };
338
+ const formatLoc = (node) => {
339
+ if (!node.file)
340
+ return '';
341
+ const rel = relative(projectPath, node.file);
342
+ return node.line ? `${rel}:${node.line}` : rel;
343
+ };
344
+ // Render
345
+ if (!state.currentNode) {
346
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: "yellow", children: "No function selected." }), _jsx(Text, { children: "Press / to search, q to quit." }), state.searchMode && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Search: " }), _jsx(Text, { color: "cyan", children: state.searchQuery }), _jsx(Text, { color: "gray", children: "_" })] }))] }));
347
+ }
348
+ // Build badges for current node
349
+ const badges = [];
350
+ if (state.currentNode.async)
351
+ badges.push('async');
352
+ if (state.currentNode.exported)
353
+ badges.push('exp');
354
+ if (state.currentNode.generator)
355
+ badges.push('gen');
356
+ if (state.currentNode.arrowFunction)
357
+ badges.push('arrow');
358
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Grafema Explorer" }), state.loading && _jsx(Text, { color: "yellow", children: " (loading...)" }), badges.length > 0 && (_jsxs(Text, { children: [' ', badges.map((badge, i) => (_jsxs(Text, { children: [_jsxs(Text, { color: "magenta", children: ["[", badge, "]"] }), i < badges.length - 1 ? ' ' : ''] }, `badge-${i}`)))] }))] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "gray", children: state.breadcrumbs.map((b, i) => (_jsxs(Text, { children: [i > 0 ? ' → ' : '', _jsx(Text, { color: i === state.breadcrumbs.length - 1 ? 'white' : 'gray', children: b.name })] }, `bc-${i}-${b.id}`))) }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: state.currentNode.type }), _jsx(Text, { children: ": " }), _jsx(Text, { bold: true, children: state.currentNode.name })] }), state.currentNode.signature && (_jsx(Text, { color: "yellow", children: state.currentNode.signature })), state.currentNode.jsdocSummary && (_jsxs(Text, { color: "gray", italic: true, children: [" ", state.currentNode.jsdocSummary] })), _jsx(Text, { color: "gray", children: formatLoc(state.currentNode) })] }), state.viewMode === 'search' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Search Results (", state.searchResults.length, "):"] }), state.searchResults.length === 0 ? (_jsx(Text, { color: "gray", children: " No results" })) : (state.searchResults.map((item, i) => (_jsxs(Text, { children: [i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsxs(Text, { color: i === state.selectedIndex ? 'white' : 'gray', children: [_jsx(Text, { color: "green", children: item.type }), " ", item.name] }), _jsxs(Text, { color: "gray", dimColor: true, children: [" ", formatLoc(item)] })] }, `search-${i}-${item.id}`))))] })), state.viewMode === 'modules' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Modules (", state.modules.length, "):"] }), state.modules.length === 0 ? (_jsx(Text, { color: "gray", children: " No modules" })) : (state.modules.slice(0, 20).map((mod, i) => (_jsxs(Text, { children: [i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsx(Text, { color: i === state.selectedIndex ? 'white' : 'gray', children: formatLoc(mod) })] }, `mod-${i}-${mod.id}`)))), state.modules.length > 20 && (_jsxs(Text, { color: "gray", children: [" \u2193 ", state.modules.length - 20, " more"] }))] })), state.viewMode === 'function' && (_jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: "50%", paddingRight: 1, children: [_jsxs(Text, { bold: true, color: state.selectedPanel === 'callers' ? 'cyan' : 'gray', children: ["Called by (", state.callers.length, "):"] }), state.callers.length === 0 ? (_jsx(Text, { color: "gray", children: " (none)" })) : (state.callers.slice(0, state.visibleCallers).map((caller, i) => {
359
+ const isRecursive = caller.id === state.currentNode?.id;
360
+ return (_jsxs(Text, { children: [state.selectedPanel === 'callers' && i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsxs(Text, { color: isRecursive ? 'yellow' : (state.selectedPanel === 'callers' && i === state.selectedIndex ? 'white' : 'gray'), children: [caller.name, isRecursive ? ' ↻' : ''] })] }, `caller-${i}-${caller.id}`));
361
+ })), state.callers.length > state.visibleCallers && (_jsxs(Text, { color: "gray", children: [" \u2193 ", state.callers.length - state.visibleCallers, " more"] }))] }), _jsxs(Box, { flexDirection: "column", width: "50%", paddingLeft: 1, children: [_jsxs(Text, { bold: true, color: state.selectedPanel === 'callees' ? 'cyan' : 'gray', children: ["Calls (", state.callees.length, "):"] }), state.callees.length === 0 ? (_jsx(Text, { color: "gray", children: " (none)" })) : (state.callees.slice(0, state.visibleCallees).map((callee, i) => {
362
+ const isRecursive = callee.id === state.currentNode?.id;
363
+ return (_jsxs(Text, { children: [state.selectedPanel === 'callees' && i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsxs(Text, { color: isRecursive ? 'yellow' : (state.selectedPanel === 'callees' && i === state.selectedIndex ? 'white' : 'gray'), children: [callee.name, isRecursive ? ' ↻' : ''] })] }, `callee-${i}-${callee.id}`));
364
+ })), state.callees.length > state.visibleCallees && (_jsxs(Text, { color: "gray", children: [" \u2193 ", state.callees.length - state.visibleCallees, " more"] }))] })] })), state.viewMode === 'class' && (_jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: "50%", paddingRight: 1, children: [_jsxs(Text, { bold: true, color: state.selectedPanel === 'fields' ? 'cyan' : 'gray', children: ["Fields (", state.fields.length, "):"] }), state.fields.length === 0 ? (_jsx(Text, { color: "gray", children: " (none)" })) : (state.fields.slice(0, 15).map((field, i) => (_jsxs(Text, { children: [state.selectedPanel === 'fields' && i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsx(Text, { color: state.selectedPanel === 'fields' && i === state.selectedIndex ? 'white' : 'gray', children: field.name })] }, `field-${i}-${field.id}`)))), state.fields.length > 15 && (_jsxs(Text, { color: "gray", children: [" \u2193 ", state.fields.length - 15, " more"] }))] }), _jsxs(Box, { flexDirection: "column", width: "50%", paddingLeft: 1, children: [_jsxs(Text, { bold: true, color: state.selectedPanel === 'methods' ? 'cyan' : 'gray', children: ["Methods (", state.methods.length, "):"] }), state.methods.length === 0 ? (_jsx(Text, { color: "gray", children: " (none)" })) : (state.methods.slice(0, 15).map((method, i) => (_jsxs(Text, { children: [state.selectedPanel === 'methods' && i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsxs(Text, { color: state.selectedPanel === 'methods' && i === state.selectedIndex ? 'white' : 'gray', children: [method.name, "()"] })] }, `method-${i}-${method.id}`)))), state.methods.length > 15 && (_jsxs(Text, { color: "gray", children: [" \u2193 ", state.methods.length - 15, " more"] }))] })] })), state.viewMode === 'dataflow' && (_jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: "50%", paddingRight: 1, children: [_jsxs(Text, { bold: true, color: state.selectedPanel === 'sources' ? 'cyan' : 'gray', children: ["Data from (", state.dataFlowSources.length, "):"] }), state.dataFlowSources.length === 0 ? (_jsx(Text, { color: "gray", children: " (none)" })) : (state.dataFlowSources.slice(0, 15).map((src, i) => (_jsxs(Text, { children: [state.selectedPanel === 'sources' && i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsxs(Text, { color: state.selectedPanel === 'sources' && i === state.selectedIndex ? 'white' : 'gray', children: ["\u2190 ", src.name, " ", _jsxs(Text, { dimColor: true, children: ["(", src.type, ")"] })] })] }, `src-${i}-${src.id}`))))] }), _jsxs(Box, { flexDirection: "column", width: "50%", paddingLeft: 1, children: [_jsxs(Text, { bold: true, color: state.selectedPanel === 'targets' ? 'cyan' : 'gray', children: ["Flows to (", state.dataFlowTargets.length, "):"] }), state.dataFlowTargets.length === 0 ? (_jsx(Text, { color: "gray", children: " (none)" })) : (state.dataFlowTargets.slice(0, 15).map((tgt, i) => (_jsxs(Text, { children: [state.selectedPanel === 'targets' && i === state.selectedIndex ? (_jsx(Text, { color: "cyan", bold: true, children: '> ' })) : (_jsx(Text, { children: ' ' })), _jsxs(Text, { color: state.selectedPanel === 'targets' && i === state.selectedIndex ? 'white' : 'gray', children: ["\u2192 ", tgt.name, " ", _jsxs(Text, { dimColor: true, children: ["(", tgt.type, ")"] })] })] }, `tgt-${i}-${tgt.id}`))))] })] })), state.showCodePreview && state.codePreviewLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Code Preview:" }), state.codePreviewLines.map((line, i) => (_jsx(Text, { color: line.startsWith('>') ? 'white' : 'gray', children: line }, `code-${i}`)))] })), state.error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", state.error] }) })), state.searchMode && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "Search: " }), _jsx(Text, { color: "cyan", children: state.searchQuery }), _jsx(Text, { color: "gray", children: "_" })] })), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "gray", children: "\u2191\u2193: Select | \u2190\u2192: Panel | Enter: Open | Backspace: Back | /: Search | m: Modules | Space: Code | o: Editor | q: Quit" }) })] }));
365
+ }
366
+ // Helper function to extract NodeInfo with extended fields from a raw node
367
+ function extractNodeInfo(node) {
368
+ const nodeType = node.type || node.nodeType || 'UNKNOWN';
369
+ return {
370
+ id: node.id,
371
+ type: nodeType,
372
+ name: node.name || '<anonymous>',
373
+ file: node.file || '',
374
+ line: node.line,
375
+ // Function-specific fields
376
+ async: node.async,
377
+ exported: node.exported,
378
+ generator: node.generator,
379
+ arrowFunction: node.arrowFunction,
380
+ params: node.params,
381
+ paramTypes: node.paramTypes,
382
+ returnType: node.returnType,
383
+ signature: node.signature,
384
+ jsdocSummary: node.jsdocSummary,
385
+ };
386
+ }
387
+ // Helper functions
388
+ async function getCallers(backend, nodeId, limit) {
389
+ const callers = [];
390
+ const seen = new Set();
391
+ try {
392
+ const callEdges = await backend.getIncomingEdges(nodeId, ['CALLS']);
393
+ for (const edge of callEdges) {
394
+ if (callers.length >= limit)
395
+ break;
396
+ const callNode = await backend.getNode(edge.src);
397
+ if (!callNode)
398
+ continue;
399
+ const containingFunc = await findContainingFunction(backend, callNode.id);
400
+ if (containingFunc && !seen.has(containingFunc.id)) {
401
+ seen.add(containingFunc.id);
402
+ callers.push(containingFunc);
403
+ }
404
+ }
405
+ }
406
+ catch {
407
+ // Ignore
408
+ }
409
+ return callers;
410
+ }
411
+ async function getCallees(backend, nodeId, limit) {
412
+ const callees = [];
413
+ const seen = new Set();
414
+ try {
415
+ const callNodes = await findCallsInFunction(backend, nodeId);
416
+ for (const callNode of callNodes) {
417
+ if (callees.length >= limit)
418
+ break;
419
+ const callEdges = await backend.getOutgoingEdges(callNode.id, ['CALLS']);
420
+ for (const edge of callEdges) {
421
+ if (callees.length >= limit)
422
+ break;
423
+ const targetNode = await backend.getNode(edge.dst);
424
+ if (!targetNode || seen.has(targetNode.id))
425
+ continue;
426
+ seen.add(targetNode.id);
427
+ callees.push(extractNodeInfo(targetNode));
428
+ }
429
+ }
430
+ }
431
+ catch {
432
+ // Ignore
433
+ }
434
+ return callees;
435
+ }
436
+ async function findContainingFunction(backend, nodeId) {
437
+ const visited = new Set();
438
+ const queue = [{ id: nodeId, depth: 0 }];
439
+ while (queue.length > 0) {
440
+ const { id, depth } = queue.shift();
441
+ if (visited.has(id) || depth > 15)
442
+ continue;
443
+ visited.add(id);
444
+ try {
445
+ const edges = await backend.getIncomingEdges(id, null);
446
+ for (const edge of edges) {
447
+ const edgeType = edge.edgeType || edge.type;
448
+ if (!['CONTAINS', 'HAS_SCOPE', 'DECLARES'].includes(edgeType))
449
+ continue;
450
+ const parent = await backend.getNode(edge.src);
451
+ if (!parent || visited.has(parent.id))
452
+ continue;
453
+ const parentType = parent.type || parent.nodeType;
454
+ if (parentType === 'FUNCTION' || parentType === 'CLASS' || parentType === 'MODULE') {
455
+ return extractNodeInfo(parent);
456
+ }
457
+ queue.push({ id: parent.id, depth: depth + 1 });
458
+ }
459
+ }
460
+ catch {
461
+ // Ignore
462
+ }
463
+ }
464
+ return null;
465
+ }
466
+ async function findCallsInFunction(backend, nodeId) {
467
+ const calls = [];
468
+ const visited = new Set();
469
+ const queue = [{ id: nodeId, depth: 0 }];
470
+ while (queue.length > 0) {
471
+ const { id, depth } = queue.shift();
472
+ if (visited.has(id) || depth > 10)
473
+ continue;
474
+ visited.add(id);
475
+ try {
476
+ const edges = await backend.getOutgoingEdges(id, ['CONTAINS']);
477
+ for (const edge of edges) {
478
+ const child = await backend.getNode(edge.dst);
479
+ if (!child)
480
+ continue;
481
+ const childType = child.type || child.nodeType;
482
+ if (childType === 'CALL') {
483
+ calls.push({
484
+ id: child.id,
485
+ type: 'CALL',
486
+ name: child.name || '',
487
+ file: child.file || '',
488
+ line: child.line,
489
+ });
490
+ }
491
+ if (childType !== 'FUNCTION' && childType !== 'CLASS') {
492
+ queue.push({ id: child.id, depth: depth + 1 });
493
+ }
494
+ }
495
+ }
496
+ catch {
497
+ // Ignore
498
+ }
499
+ }
500
+ return calls;
501
+ }
502
+ async function searchNode(backend, query) {
503
+ const results = await searchNodes(backend, query, 1);
504
+ return results[0] || null;
505
+ }
506
+ async function searchNodes(backend, query, limit) {
507
+ const results = [];
508
+ const lowerQuery = query.toLowerCase();
509
+ for (const nodeType of ['FUNCTION', 'CLASS', 'MODULE']) {
510
+ for await (const node of backend.queryNodes({ nodeType: nodeType })) {
511
+ const name = (node.name || '').toLowerCase();
512
+ if (name === lowerQuery || name.includes(lowerQuery)) {
513
+ results.push(extractNodeInfo(node));
514
+ if (results.length >= limit)
515
+ return results;
516
+ }
517
+ }
518
+ }
519
+ return results;
520
+ }
521
+ async function getModules(backend, limit) {
522
+ const modules = [];
523
+ for await (const node of backend.queryNodes({ nodeType: 'MODULE' })) {
524
+ modules.push(extractNodeInfo(node));
525
+ if (modules.length >= limit)
526
+ break;
527
+ }
528
+ // Sort by file path for better navigation
529
+ modules.sort((a, b) => a.file.localeCompare(b.file));
530
+ return modules;
531
+ }
532
+ async function getClassMembers(backend, classId) {
533
+ const fields = [];
534
+ const methods = [];
535
+ try {
536
+ // Get children via CONTAINS edge
537
+ const edges = await backend.getOutgoingEdges(classId, ['CONTAINS']);
538
+ for (const edge of edges) {
539
+ const child = await backend.getNode(edge.dst);
540
+ if (!child)
541
+ continue;
542
+ const childType = child.type || child.nodeType;
543
+ const nodeInfo = extractNodeInfo(child);
544
+ if (childType === 'FUNCTION') {
545
+ methods.push(nodeInfo);
546
+ }
547
+ else if (childType === 'VARIABLE' || childType === 'PARAMETER') {
548
+ fields.push(nodeInfo);
549
+ }
550
+ }
551
+ }
552
+ catch {
553
+ // Ignore
554
+ }
555
+ return { fields, methods };
556
+ }
557
+ async function getDataFlow(backend, varId) {
558
+ const sources = [];
559
+ const targets = [];
560
+ try {
561
+ // Get incoming ASSIGNED_FROM edges (where data comes from)
562
+ const inEdges = await backend.getIncomingEdges(varId, ['ASSIGNED_FROM']);
563
+ for (const edge of inEdges) {
564
+ const src = await backend.getNode(edge.src);
565
+ if (src) {
566
+ sources.push(extractNodeInfo(src));
567
+ }
568
+ }
569
+ // Get outgoing ASSIGNED_FROM edges (where data flows to)
570
+ const outEdges = await backend.getOutgoingEdges(varId, ['ASSIGNED_FROM']);
571
+ for (const edge of outEdges) {
572
+ const tgt = await backend.getNode(edge.dst);
573
+ if (tgt) {
574
+ targets.push(extractNodeInfo(tgt));
575
+ }
576
+ }
577
+ // Also check DERIVES_FROM for additional flow info
578
+ const derivesIn = await backend.getIncomingEdges(varId, ['DERIVES_FROM']);
579
+ for (const edge of derivesIn) {
580
+ const src = await backend.getNode(edge.src);
581
+ if (src && !sources.find(s => s.id === src.id)) {
582
+ sources.push(extractNodeInfo(src));
583
+ }
584
+ }
585
+ }
586
+ catch {
587
+ // Ignore
588
+ }
589
+ return { sources, targets };
590
+ }
591
+ async function findStartNode(backend, startName) {
592
+ if (startName) {
593
+ return searchNode(backend, startName);
594
+ }
595
+ // Find first function with most callers
596
+ let bestNode = null;
597
+ let bestCallerCount = 0;
598
+ let checked = 0;
599
+ for await (const node of backend.queryNodes({ nodeType: 'FUNCTION' })) {
600
+ const incoming = await backend.getIncomingEdges(node.id, ['CALLS']);
601
+ if (incoming.length > bestCallerCount) {
602
+ bestCallerCount = incoming.length;
603
+ bestNode = extractNodeInfo(node);
604
+ }
605
+ checked++;
606
+ if (checked >= 100)
607
+ break; // Limit search
608
+ }
609
+ return bestNode;
610
+ }
611
+ // Command
612
+ export const exploreCommand = new Command('explore')
613
+ .description('Interactive graph navigation')
614
+ .argument('[start]', 'Starting function name')
615
+ .option('-p, --project <path>', 'Project path', '.')
616
+ .action(async (start, options) => {
617
+ const projectPath = resolve(options.project);
618
+ const grafemaDir = join(projectPath, '.grafema');
619
+ const dbPath = join(grafemaDir, 'graph.rfdb');
620
+ if (!existsSync(dbPath)) {
621
+ exitWithError('No graph database found', ['Run: grafema analyze']);
622
+ }
623
+ const backend = new RFDBServerBackend({ dbPath });
624
+ try {
625
+ await backend.connect();
626
+ const startNode = await findStartNode(backend, start || null);
627
+ const { waitUntilExit } = render(_jsx(Explorer, { backend: backend, startNode: startNode, projectPath: projectPath }));
628
+ await waitUntilExit();
629
+ }
630
+ finally {
631
+ await backend.close();
632
+ }
633
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Get command - Retrieve node by semantic ID
3
+ *
4
+ * Usage:
5
+ * grafema get "file.js->scope->TYPE->name"
6
+ * grafema get "file.js->scope->TYPE->name" --json
7
+ */
8
+ import { Command } from 'commander';
9
+ export declare const getCommand: Command;
10
+ //# sourceMappingURL=get.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../src/commands/get.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkCpC,eAAO,MAAM,UAAU,SAyCnB,CAAC"}