@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,189 @@
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
+ import { resolve, join } from 'path';
10
+ import { existsSync } from 'fs';
11
+ import { RFDBServerBackend } from '@grafema/core';
12
+ import { formatNodeDisplay } from '../utils/formatNode.js';
13
+ import { exitWithError } from '../utils/errorFormatter.js';
14
+ export const getCommand = new Command('get')
15
+ .description('Retrieve a node by its semantic ID')
16
+ .argument('<semantic-id>', 'Semantic ID of the node (e.g., "file.js->scope->TYPE->name")')
17
+ .option('-p, --project <path>', 'Project path', '.')
18
+ .option('-j, --json', 'Output as JSON')
19
+ .action(async (semanticId, options) => {
20
+ const projectPath = resolve(options.project);
21
+ const grafemaDir = join(projectPath, '.grafema');
22
+ const dbPath = join(grafemaDir, 'graph.rfdb');
23
+ if (!existsSync(dbPath)) {
24
+ exitWithError('No graph database found', ['Run: grafema analyze']);
25
+ }
26
+ const backend = new RFDBServerBackend({ dbPath });
27
+ await backend.connect();
28
+ try {
29
+ // Retrieve node by semantic ID
30
+ const node = await backend.getNode(semanticId);
31
+ if (!node) {
32
+ exitWithError('Node not found', [
33
+ `ID: ${semanticId}`,
34
+ 'Try: grafema query "<name>" to search for nodes',
35
+ ]);
36
+ }
37
+ // Get incoming and outgoing edges
38
+ const incomingEdges = await backend.getIncomingEdges(semanticId, null);
39
+ const outgoingEdges = await backend.getOutgoingEdges(semanticId, null);
40
+ if (options.json) {
41
+ await outputJSON(backend, node, incomingEdges, outgoingEdges);
42
+ }
43
+ else {
44
+ await outputText(backend, node, incomingEdges, outgoingEdges, projectPath);
45
+ }
46
+ }
47
+ finally {
48
+ await backend.close();
49
+ }
50
+ });
51
+ /**
52
+ * Output node and edges as JSON
53
+ */
54
+ async function outputJSON(backend, node, incomingEdges, outgoingEdges) {
55
+ // Fetch target node names for all edges
56
+ const incomingWithNames = await Promise.all(incomingEdges.map(async (edge) => ({
57
+ edgeType: edge.edgeType || edge.type || 'UNKNOWN',
58
+ targetId: edge.src,
59
+ targetName: await getNodeName(backend, edge.src),
60
+ })));
61
+ const outgoingWithNames = await Promise.all(outgoingEdges.map(async (edge) => ({
62
+ edgeType: edge.edgeType || edge.type || 'UNKNOWN',
63
+ targetId: edge.dst,
64
+ targetName: await getNodeName(backend, edge.dst),
65
+ })));
66
+ const result = {
67
+ node: {
68
+ id: node.id,
69
+ type: node.type || 'UNKNOWN',
70
+ name: node.name || '',
71
+ file: node.file || '',
72
+ line: node.line,
73
+ ...getMetadataFields(node),
74
+ },
75
+ edges: {
76
+ incoming: incomingWithNames,
77
+ outgoing: outgoingWithNames,
78
+ },
79
+ stats: {
80
+ incomingCount: incomingEdges.length,
81
+ outgoingCount: outgoingEdges.length,
82
+ },
83
+ };
84
+ console.log(JSON.stringify(result, null, 2));
85
+ }
86
+ /**
87
+ * Output node and edges as formatted text
88
+ */
89
+ async function outputText(backend, node, incomingEdges, outgoingEdges, projectPath) {
90
+ const nodeInfo = {
91
+ id: node.id,
92
+ type: node.type || 'UNKNOWN',
93
+ name: node.name || '',
94
+ file: node.file || '',
95
+ line: node.line,
96
+ };
97
+ // Display node details
98
+ console.log(formatNodeDisplay(nodeInfo, { projectPath }));
99
+ // Display metadata if present
100
+ const metadata = getMetadataFields(node);
101
+ if (Object.keys(metadata).length > 0) {
102
+ console.log('');
103
+ console.log('Metadata:');
104
+ for (const [key, value] of Object.entries(metadata)) {
105
+ console.log(` ${key}: ${JSON.stringify(value)}`);
106
+ }
107
+ }
108
+ // Display edges
109
+ console.log('');
110
+ await displayEdges(backend, 'Incoming', incomingEdges, (edge) => edge.src);
111
+ console.log('');
112
+ await displayEdges(backend, 'Outgoing', outgoingEdges, (edge) => edge.dst);
113
+ }
114
+ /**
115
+ * Display edges grouped by type, limited to 20 in text mode
116
+ */
117
+ async function displayEdges(backend, direction, edges, getTargetId) {
118
+ const totalCount = edges.length;
119
+ if (totalCount === 0) {
120
+ console.log(`${direction} edges (0):`);
121
+ console.log(' (none)');
122
+ return;
123
+ }
124
+ // Group edges by type
125
+ const byType = new Map();
126
+ for (const edge of edges) {
127
+ const edgeType = edge.edgeType || edge.type || 'UNKNOWN';
128
+ const targetId = getTargetId(edge);
129
+ const targetName = await getNodeName(backend, targetId);
130
+ if (!byType.has(edgeType)) {
131
+ byType.set(edgeType, []);
132
+ }
133
+ byType.get(edgeType).push({ edgeType, targetId, targetName });
134
+ }
135
+ // Display header with count
136
+ const limitApplied = totalCount > 20;
137
+ console.log(`${direction} edges (${totalCount}):`);
138
+ // Display edges, limited to 20 total
139
+ let displayed = 0;
140
+ const limit = 20;
141
+ for (const [edgeType, edgesOfType] of Array.from(byType.entries())) {
142
+ console.log(` ${edgeType}:`);
143
+ for (const edge of edgesOfType) {
144
+ if (displayed >= limit)
145
+ break;
146
+ // Format: TYPE#name
147
+ const label = edge.targetName ? `${edge.edgeType}#${edge.targetName}` : edge.targetId;
148
+ console.log(` ${label}`);
149
+ displayed++;
150
+ }
151
+ if (displayed >= limit)
152
+ break;
153
+ }
154
+ // Show "and X more" if we hit the limit
155
+ if (limitApplied) {
156
+ const remaining = totalCount - displayed;
157
+ console.log(` ... and ${remaining} more (use --json to see all)`);
158
+ }
159
+ }
160
+ /**
161
+ * Get node name for display
162
+ */
163
+ async function getNodeName(backend, nodeId) {
164
+ try {
165
+ const node = await backend.getNode(nodeId);
166
+ if (node) {
167
+ return node.name || '';
168
+ }
169
+ }
170
+ catch {
171
+ // Ignore errors
172
+ }
173
+ return '';
174
+ }
175
+ /**
176
+ * Extract metadata fields (exclude standard fields)
177
+ */
178
+ function getMetadataFields(node) {
179
+ const standardFields = new Set([
180
+ 'id', 'type', 'nodeType', 'name', 'file', 'line',
181
+ ]);
182
+ const metadata = {};
183
+ for (const [key, value] of Object.entries(node)) {
184
+ if (!standardFields.has(key) && value !== undefined && value !== null) {
185
+ metadata[key] = value;
186
+ }
187
+ }
188
+ return metadata;
189
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Impact command - Change impact analysis
3
+ *
4
+ * Usage:
5
+ * grafema impact "function authenticate"
6
+ * grafema impact "class UserService"
7
+ */
8
+ import { Command } from 'commander';
9
+ export declare const impactCommand: Command;
10
+ //# sourceMappingURL=impact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impact.d.ts","sourceRoot":"","sources":["../../src/commands/impact.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,eAAO,MAAM,aAAa,SAqDtB,CAAC"}
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Impact command - Change impact analysis
3
+ *
4
+ * Usage:
5
+ * grafema impact "function authenticate"
6
+ * grafema impact "class UserService"
7
+ */
8
+ import { Command } from 'commander';
9
+ import { resolve, join, dirname } from 'path';
10
+ import { relative } from 'path';
11
+ import { existsSync } from 'fs';
12
+ import { RFDBServerBackend } from '@grafema/core';
13
+ import { formatNodeDisplay, formatNodeInline } from '../utils/formatNode.js';
14
+ import { exitWithError } from '../utils/errorFormatter.js';
15
+ export const impactCommand = new Command('impact')
16
+ .description('Analyze change impact for a function or class')
17
+ .argument('<pattern>', 'Target: "function X" or "class Y" or just "X"')
18
+ .option('-p, --project <path>', 'Project path', '.')
19
+ .option('-j, --json', 'Output as JSON')
20
+ .option('-d, --depth <n>', 'Max traversal depth', '10')
21
+ .action(async (pattern, options) => {
22
+ const projectPath = resolve(options.project);
23
+ const grafemaDir = join(projectPath, '.grafema');
24
+ const dbPath = join(grafemaDir, 'graph.rfdb');
25
+ if (!existsSync(dbPath)) {
26
+ exitWithError('No graph database found', ['Run: grafema analyze']);
27
+ }
28
+ const backend = new RFDBServerBackend({ dbPath });
29
+ await backend.connect();
30
+ try {
31
+ const { type, name } = parsePattern(pattern);
32
+ const maxDepth = parseInt(options.depth, 10);
33
+ console.log(`Analyzing impact of changing ${name}...`);
34
+ console.log('');
35
+ // Find target node
36
+ const target = await findTarget(backend, type, name);
37
+ if (!target) {
38
+ console.log(`No ${type || 'node'} "${name}" found`);
39
+ return;
40
+ }
41
+ // Analyze impact
42
+ const impact = await analyzeImpact(backend, target, maxDepth, projectPath);
43
+ if (options.json) {
44
+ console.log(JSON.stringify({
45
+ target: impact.target,
46
+ directCallers: impact.directCallers.length,
47
+ transitiveCallers: impact.transitiveCallers.length,
48
+ affectedModules: Object.fromEntries(impact.affectedModules),
49
+ callChains: impact.callChains.slice(0, 5),
50
+ }, null, 2));
51
+ return;
52
+ }
53
+ // Display results
54
+ displayImpact(impact, projectPath);
55
+ }
56
+ finally {
57
+ await backend.close();
58
+ }
59
+ });
60
+ /**
61
+ * Parse pattern like "function authenticate"
62
+ */
63
+ function parsePattern(pattern) {
64
+ const words = pattern.trim().split(/\s+/);
65
+ if (words.length >= 2) {
66
+ const typeWord = words[0].toLowerCase();
67
+ const name = words.slice(1).join(' ');
68
+ const typeMap = {
69
+ function: 'FUNCTION',
70
+ fn: 'FUNCTION',
71
+ class: 'CLASS',
72
+ module: 'MODULE',
73
+ };
74
+ if (typeMap[typeWord]) {
75
+ return { type: typeMap[typeWord], name };
76
+ }
77
+ }
78
+ return { type: null, name: pattern.trim() };
79
+ }
80
+ /**
81
+ * Find target node
82
+ */
83
+ async function findTarget(backend, type, name) {
84
+ const searchTypes = type ? [type] : ['FUNCTION', 'CLASS'];
85
+ for (const nodeType of searchTypes) {
86
+ for await (const node of backend.queryNodes({ nodeType: nodeType })) {
87
+ const nodeName = node.name || '';
88
+ if (nodeName.toLowerCase() === name.toLowerCase()) {
89
+ return {
90
+ id: node.id,
91
+ type: node.type || nodeType,
92
+ name: nodeName,
93
+ file: node.file || '',
94
+ line: node.line,
95
+ };
96
+ }
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Analyze impact of changing a node
103
+ */
104
+ async function analyzeImpact(backend, target, maxDepth, projectPath) {
105
+ const directCallers = [];
106
+ const transitiveCallers = [];
107
+ const affectedModules = new Map();
108
+ const callChains = [];
109
+ const visited = new Set();
110
+ // BFS to find all callers
111
+ const queue = [
112
+ { id: target.id, depth: 0, chain: [target.name] }
113
+ ];
114
+ while (queue.length > 0) {
115
+ const { id, depth, chain } = queue.shift();
116
+ if (visited.has(id))
117
+ continue;
118
+ visited.add(id);
119
+ if (depth > maxDepth)
120
+ continue;
121
+ try {
122
+ // Find what calls this node
123
+ // First, find CALL nodes that have this as target
124
+ const containingCalls = await findCallsToNode(backend, id);
125
+ for (const callNode of containingCalls) {
126
+ // Find the function containing this call
127
+ const container = await findContainingFunction(backend, callNode.id);
128
+ if (container && !visited.has(container.id)) {
129
+ const caller = {
130
+ id: container.id,
131
+ type: container.type,
132
+ name: container.name,
133
+ file: container.file,
134
+ line: container.line,
135
+ };
136
+ if (depth === 0) {
137
+ directCallers.push(caller);
138
+ }
139
+ else {
140
+ transitiveCallers.push(caller);
141
+ }
142
+ // Track affected modules
143
+ const modulePath = getModulePath(caller.file, projectPath);
144
+ affectedModules.set(modulePath, (affectedModules.get(modulePath) || 0) + 1);
145
+ // Track call chain
146
+ const newChain = [...chain, caller.name];
147
+ if (newChain.length <= 4) {
148
+ callChains.push(newChain);
149
+ }
150
+ // Continue BFS
151
+ queue.push({ id: container.id, depth: depth + 1, chain: newChain });
152
+ }
153
+ }
154
+ }
155
+ catch {
156
+ // Ignore errors
157
+ }
158
+ }
159
+ // Sort call chains by length
160
+ callChains.sort((a, b) => b.length - a.length);
161
+ return {
162
+ target,
163
+ directCallers,
164
+ transitiveCallers,
165
+ affectedModules,
166
+ callChains,
167
+ };
168
+ }
169
+ /**
170
+ * Find CALL nodes that reference a target
171
+ */
172
+ async function findCallsToNode(backend, targetId) {
173
+ const calls = [];
174
+ try {
175
+ // Get incoming CALLS edges
176
+ const edges = await backend.getIncomingEdges(targetId, ['CALLS']);
177
+ for (const edge of edges) {
178
+ const callNode = await backend.getNode(edge.src);
179
+ if (callNode) {
180
+ calls.push({
181
+ id: callNode.id,
182
+ type: callNode.type || 'CALL',
183
+ name: callNode.name || '',
184
+ file: callNode.file || '',
185
+ line: callNode.line,
186
+ });
187
+ }
188
+ }
189
+ }
190
+ catch {
191
+ // Ignore
192
+ }
193
+ return calls;
194
+ }
195
+ /**
196
+ * Find the function that contains a call node
197
+ *
198
+ * Path: CALL → CONTAINS → SCOPE → CONTAINS → SCOPE → HAS_SCOPE → FUNCTION
199
+ */
200
+ async function findContainingFunction(backend, nodeId, maxDepth = 15) {
201
+ const visited = new Set();
202
+ const queue = [{ id: nodeId, depth: 0 }];
203
+ while (queue.length > 0) {
204
+ const { id, depth } = queue.shift();
205
+ if (visited.has(id) || depth > maxDepth)
206
+ continue;
207
+ visited.add(id);
208
+ try {
209
+ // Get incoming edges: CONTAINS, HAS_SCOPE
210
+ const edges = await backend.getIncomingEdges(id, null);
211
+ for (const edge of edges) {
212
+ const edgeType = edge.edgeType || edge.type;
213
+ // Only follow structural edges
214
+ if (!['CONTAINS', 'HAS_SCOPE', 'DECLARES'].includes(edgeType))
215
+ continue;
216
+ const parent = await backend.getNode(edge.src);
217
+ if (!parent || visited.has(parent.id))
218
+ continue;
219
+ const parentType = parent.type;
220
+ // FUNCTION, CLASS, or MODULE (for top-level calls)
221
+ if (parentType === 'FUNCTION' || parentType === 'CLASS' || parentType === 'MODULE') {
222
+ return {
223
+ id: parent.id,
224
+ type: parentType,
225
+ name: parent.name || '',
226
+ file: parent.file || '',
227
+ line: parent.line,
228
+ };
229
+ }
230
+ queue.push({ id: parent.id, depth: depth + 1 });
231
+ }
232
+ }
233
+ catch {
234
+ // Ignore
235
+ }
236
+ }
237
+ return null;
238
+ }
239
+ /**
240
+ * Get module path relative to project
241
+ */
242
+ function getModulePath(file, projectPath) {
243
+ if (!file)
244
+ return '<unknown>';
245
+ const relPath = relative(projectPath, file);
246
+ const dir = dirname(relPath);
247
+ return dir === '.' ? relPath : `${dir}/*`;
248
+ }
249
+ /**
250
+ * Display impact analysis results with semantic IDs
251
+ */
252
+ function displayImpact(impact, projectPath) {
253
+ console.log(formatNodeDisplay(impact.target, { projectPath }));
254
+ console.log('');
255
+ // Direct impact
256
+ console.log('Direct impact:');
257
+ console.log(` ${impact.directCallers.length} direct callers`);
258
+ console.log(` ${impact.transitiveCallers.length} transitive callers`);
259
+ console.log(` ${impact.directCallers.length + impact.transitiveCallers.length} total affected`);
260
+ console.log('');
261
+ // Show direct callers
262
+ if (impact.directCallers.length > 0) {
263
+ console.log('Direct callers:');
264
+ for (const caller of impact.directCallers.slice(0, 10)) {
265
+ console.log(` <- ${formatNodeInline(caller)}`);
266
+ }
267
+ if (impact.directCallers.length > 10) {
268
+ console.log(` ... and ${impact.directCallers.length - 10} more`);
269
+ }
270
+ console.log('');
271
+ }
272
+ // Affected modules
273
+ if (impact.affectedModules.size > 0) {
274
+ console.log('Affected modules:');
275
+ const sorted = [...impact.affectedModules.entries()].sort((a, b) => b[1] - a[1]);
276
+ for (const [module, count] of sorted.slice(0, 5)) {
277
+ console.log(` ├─ ${module} (${count} calls)`);
278
+ }
279
+ if (sorted.length > 5) {
280
+ console.log(` └─ ... and ${sorted.length - 5} more modules`);
281
+ }
282
+ console.log('');
283
+ }
284
+ // Call chains
285
+ if (impact.callChains.length > 0) {
286
+ console.log('Call chains (sample):');
287
+ for (const chain of impact.callChains.slice(0, 3)) {
288
+ console.log(` ${chain.join(' → ')}`);
289
+ }
290
+ console.log('');
291
+ }
292
+ // Risk assessment
293
+ const totalAffected = impact.directCallers.length + impact.transitiveCallers.length;
294
+ const moduleCount = impact.affectedModules.size;
295
+ let risk = 'LOW';
296
+ let color = '\x1b[32m'; // green
297
+ if (totalAffected > 20 || moduleCount > 5) {
298
+ risk = 'HIGH';
299
+ color = '\x1b[31m'; // red
300
+ }
301
+ else if (totalAffected > 5 || moduleCount > 2) {
302
+ risk = 'MEDIUM';
303
+ color = '\x1b[33m'; // yellow
304
+ }
305
+ console.log(`Risk level: ${color}${risk}\x1b[0m`);
306
+ if (risk === 'HIGH') {
307
+ console.log('');
308
+ console.log('Recommendation:');
309
+ console.log(' • Consider adding backward-compatible wrapper');
310
+ console.log(' • Update tests in affected modules');
311
+ console.log(' • Notify team about breaking change');
312
+ }
313
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Init command - Initialize Grafema in a project
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare const initCommand: Command;
6
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4CpC,eAAO,MAAM,WAAW,SA+DpB,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Init command - Initialize Grafema in a project
3
+ */
4
+ import { Command } from 'commander';
5
+ import { resolve, join } from 'path';
6
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
7
+ import { exitWithError } from '../utils/errorFormatter.js';
8
+ import { stringify as stringifyYAML } from 'yaml';
9
+ import { DEFAULT_CONFIG } from '@grafema/core';
10
+ /**
11
+ * Generate config.yaml content with commented future features.
12
+ * Only includes implemented features (plugins).
13
+ */
14
+ function generateConfigYAML() {
15
+ // Start with working default config
16
+ const config = {
17
+ // Plugin list (fully implemented)
18
+ plugins: DEFAULT_CONFIG.plugins,
19
+ };
20
+ // Convert to YAML
21
+ const yaml = stringifyYAML(config, {
22
+ lineWidth: 0, // Don't wrap long lines
23
+ });
24
+ // Add header comment
25
+ return `# Grafema Configuration
26
+ # Documentation: https://github.com/grafema/grafema#configuration
27
+
28
+ ${yaml}
29
+ # Future: File discovery patterns (not yet implemented)
30
+ # Grafema currently uses entrypoint-based discovery (follows imports from package.json main field)
31
+ # Glob-based include/exclude patterns will be added in a future release
32
+ #
33
+ # include:
34
+ # - "src/**/*.{ts,js,tsx,jsx}"
35
+ # exclude:
36
+ # - "**/*.test.ts"
37
+ # - "node_modules/**"
38
+ `;
39
+ }
40
+ export const initCommand = new Command('init')
41
+ .description('Initialize Grafema in current project')
42
+ .argument('[path]', 'Project path', '.')
43
+ .option('-f, --force', 'Overwrite existing config')
44
+ .action(async (path, options) => {
45
+ const projectPath = resolve(path);
46
+ const grafemaDir = join(projectPath, '.grafema');
47
+ const configPath = join(grafemaDir, 'config.yaml');
48
+ const packageJsonPath = join(projectPath, 'package.json');
49
+ const tsconfigPath = join(projectPath, 'tsconfig.json');
50
+ // Check package.json
51
+ if (!existsSync(packageJsonPath)) {
52
+ exitWithError('No package.json found', [
53
+ 'Initialize a project: npm init',
54
+ 'Or check you are in the right directory'
55
+ ]);
56
+ }
57
+ console.log('✓ Found package.json');
58
+ // Detect TypeScript
59
+ const isTypeScript = existsSync(tsconfigPath);
60
+ if (isTypeScript) {
61
+ console.log('✓ Detected TypeScript project');
62
+ }
63
+ else {
64
+ console.log('✓ Detected JavaScript project');
65
+ }
66
+ // Check existing config
67
+ if (existsSync(configPath) && !options.force) {
68
+ console.log('');
69
+ console.log('✓ Grafema already initialized');
70
+ console.log(' → Use --force to overwrite config');
71
+ console.log('');
72
+ console.log('Next: Run "grafema analyze" to build the code graph');
73
+ return;
74
+ }
75
+ // Create .grafema directory
76
+ if (!existsSync(grafemaDir)) {
77
+ mkdirSync(grafemaDir, { recursive: true });
78
+ }
79
+ // Write config
80
+ const configContent = generateConfigYAML();
81
+ writeFileSync(configPath, configContent);
82
+ console.log('✓ Created .grafema/config.yaml');
83
+ // Add to .gitignore if exists
84
+ const gitignorePath = join(projectPath, '.gitignore');
85
+ if (existsSync(gitignorePath)) {
86
+ const gitignore = readFileSync(gitignorePath, 'utf-8');
87
+ if (!gitignore.includes('.grafema/graph.rfdb')) {
88
+ writeFileSync(gitignorePath, gitignore + '\n# Grafema\n.grafema/graph.rfdb\n.grafema/rfdb.sock\n');
89
+ console.log('✓ Updated .gitignore');
90
+ }
91
+ }
92
+ console.log('');
93
+ console.log('Next: Run "grafema analyze" to build the code graph');
94
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Overview command - Project dashboard
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare const overviewCommand: Command;
6
+ //# sourceMappingURL=overview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../src/commands/overview.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,eAAO,MAAM,eAAe,SA4FxB,CAAC"}