@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,295 @@
1
+ /**
2
+ * Check command - Check invariants/guarantees
3
+ *
4
+ * Supports two modes:
5
+ * 1. Rule-based: Check YAML-defined guarantees (default)
6
+ * 2. Built-in validators: --guarantee=<name> (e.g., --guarantee=node-creation)
7
+ */
8
+ import { Command } from 'commander';
9
+ import { resolve, join } from 'path';
10
+ import { existsSync } from 'fs';
11
+ import { RFDBServerBackend, GuaranteeManager, NodeCreationValidator, GraphFreshnessChecker, IncrementalReanalyzer } from '@grafema/core';
12
+ import { exitWithError } from '../utils/errorFormatter.js';
13
+ // Available built-in validators
14
+ const BUILT_IN_VALIDATORS = {
15
+ 'node-creation': {
16
+ name: 'NodeCreationValidator',
17
+ description: 'Validates that all nodes are created through NodeFactory'
18
+ }
19
+ };
20
+ export const checkCommand = new Command('check')
21
+ .description('Check invariants/guarantees')
22
+ .argument('[rule]', 'Specific rule ID to check (or "all" for all rules)')
23
+ .option('-p, --project <path>', 'Project path', '.')
24
+ .option('-f, --file <path>', 'Path to guarantees YAML file')
25
+ .option('-g, --guarantee <name>', 'Run a built-in guarantee validator (e.g., node-creation)')
26
+ .option('-j, --json', 'Output results as JSON')
27
+ .option('-q, --quiet', 'Only output failures')
28
+ .option('--list-guarantees', 'List available built-in guarantees')
29
+ .option('--skip-reanalysis', 'Skip automatic reanalysis of stale modules')
30
+ .option('--fail-on-stale', 'Exit with error if stale modules found (CI mode)')
31
+ .action(async (rule, options) => {
32
+ // List available guarantees
33
+ if (options.listGuarantees) {
34
+ console.log('Available built-in guarantees:');
35
+ console.log('');
36
+ for (const [key, info] of Object.entries(BUILT_IN_VALIDATORS)) {
37
+ console.log(` ${key}`);
38
+ console.log(` ${info.description}`);
39
+ console.log('');
40
+ }
41
+ return;
42
+ }
43
+ // Run built-in guarantee validator
44
+ if (options.guarantee) {
45
+ const validatorInfo = BUILT_IN_VALIDATORS[options.guarantee];
46
+ if (!validatorInfo) {
47
+ const available = Object.keys(BUILT_IN_VALIDATORS).join(', ');
48
+ exitWithError(`Unknown guarantee: ${options.guarantee}`, [
49
+ `Available: ${available}`
50
+ ]);
51
+ }
52
+ await runBuiltInValidator(options.guarantee, options.project, {
53
+ json: options.json,
54
+ quiet: options.quiet,
55
+ skipReanalysis: options.skipReanalysis,
56
+ failOnStale: options.failOnStale
57
+ });
58
+ return;
59
+ }
60
+ const projectPath = resolve(options.project);
61
+ const grafemaDir = join(projectPath, '.grafema');
62
+ const dbPath = join(grafemaDir, 'graph.rfdb');
63
+ if (!existsSync(dbPath)) {
64
+ exitWithError('No graph database found', ['Run: grafema analyze']);
65
+ }
66
+ const backend = new RFDBServerBackend({ dbPath });
67
+ await backend.connect();
68
+ // Check graph freshness
69
+ const freshnessChecker = new GraphFreshnessChecker();
70
+ const freshness = await freshnessChecker.checkFreshness(backend);
71
+ if (!freshness.isFresh) {
72
+ if (options.failOnStale) {
73
+ console.error(`✗ Graph is stale: ${freshness.staleCount} module(s) changed`);
74
+ for (const stale of freshness.staleModules.slice(0, 5)) {
75
+ console.error(` ${stale.file} (${stale.reason})`);
76
+ }
77
+ if (freshness.staleModules.length > 5) {
78
+ console.error(` ... and ${freshness.staleModules.length - 5} more`);
79
+ }
80
+ console.error('');
81
+ console.error('→ Run: grafema analyze');
82
+ await backend.close();
83
+ process.exit(1);
84
+ }
85
+ if (!options.skipReanalysis) {
86
+ console.log(`Reanalyzing ${freshness.staleCount} stale module(s)...`);
87
+ const reanalyzer = new IncrementalReanalyzer(backend, projectPath);
88
+ const result = await reanalyzer.reanalyze(freshness.staleModules);
89
+ console.log(`Reanalyzed ${result.modulesReanalyzed} module(s) in ${result.durationMs}ms`);
90
+ console.log('');
91
+ }
92
+ else {
93
+ console.warn(`Warning: ${freshness.staleCount} stale module(s) detected. Use --skip-reanalysis to suppress.`);
94
+ for (const stale of freshness.staleModules.slice(0, 5)) {
95
+ console.warn(` - ${stale.file} (${stale.reason})`);
96
+ }
97
+ if (freshness.staleModules.length > 5) {
98
+ console.warn(` ... and ${freshness.staleModules.length - 5} more`);
99
+ }
100
+ console.log('');
101
+ }
102
+ }
103
+ else if (!options.quiet) {
104
+ console.log('Graph is fresh');
105
+ console.log('');
106
+ }
107
+ try {
108
+ const guaranteeGraph = backend;
109
+ const manager = new GuaranteeManager(guaranteeGraph, projectPath);
110
+ // Load guarantees from file if specified
111
+ const guaranteesFile = options.file || join(grafemaDir, 'guarantees.yaml');
112
+ if (existsSync(guaranteesFile)) {
113
+ await manager.import(guaranteesFile);
114
+ }
115
+ // Get all guarantees
116
+ const guarantees = await manager.list();
117
+ if (guarantees.length === 0) {
118
+ console.log('No guarantees found.');
119
+ console.log('');
120
+ console.log('Create guarantees in .grafema/guarantees.yaml or use --file option.');
121
+ return;
122
+ }
123
+ // Filter to specific rule if requested
124
+ const toCheck = rule && rule !== 'all'
125
+ ? guarantees.filter((g) => g.id === rule || g.name === rule)
126
+ : guarantees;
127
+ if (toCheck.length === 0 && rule) {
128
+ const available = guarantees.map((g) => g.id).join(', ');
129
+ exitWithError(`Guarantee not found: ${rule}`, [
130
+ `Available: ${available}`
131
+ ]);
132
+ }
133
+ // Check all matching guarantees
134
+ const results = await manager.checkAll();
135
+ // Filter results to only requested rules
136
+ const filteredResults = rule && rule !== 'all'
137
+ ? {
138
+ ...results,
139
+ results: results.results.filter((r) => toCheck.some((g) => g.id === r.guaranteeId)),
140
+ }
141
+ : results;
142
+ if (options.json) {
143
+ console.log(JSON.stringify(filteredResults, null, 2));
144
+ }
145
+ else {
146
+ if (!options.quiet) {
147
+ console.log(`Checking ${filteredResults.results.length} guarantee(s)...`);
148
+ console.log('');
149
+ }
150
+ for (const result of filteredResults.results) {
151
+ if (options.quiet && result.passed)
152
+ continue;
153
+ const status = result.passed ? '✓' : '✗';
154
+ const color = result.passed ? '\x1b[32m' : '\x1b[31m';
155
+ const reset = '\x1b[0m';
156
+ console.log(`${color}${status}${reset} ${result.guaranteeId}: ${result.name}`);
157
+ if (!result.passed && result.violations.length > 0) {
158
+ console.log(` Violations (${result.violationCount}):`);
159
+ for (const v of result.violations.slice(0, 10)) {
160
+ // Prefer nodeId (semantic ID) for queryability
161
+ const identifier = v.nodeId || (v.file ? `${v.file}${v.line ? `:${v.line}` : ''}` : '(unknown)');
162
+ console.log(` - ${identifier}`);
163
+ if (v.name || v.type) {
164
+ console.log(` ${v.name || ''} (${v.type || 'unknown'})`);
165
+ }
166
+ }
167
+ if (result.violations.length > 10) {
168
+ console.log(` ... and ${result.violations.length - 10} more`);
169
+ }
170
+ }
171
+ if (result.error) {
172
+ console.log(` Error: ${result.error}`);
173
+ }
174
+ }
175
+ console.log('');
176
+ console.log(`Summary: ${filteredResults.passed}/${filteredResults.total} passed`);
177
+ if (filteredResults.failed > 0) {
178
+ process.exit(1);
179
+ }
180
+ }
181
+ }
182
+ finally {
183
+ await backend.close();
184
+ }
185
+ });
186
+ /**
187
+ * Run a built-in validator
188
+ */
189
+ async function runBuiltInValidator(guaranteeName, projectPath, options) {
190
+ const resolvedPath = resolve(projectPath);
191
+ const grafemaDir = join(resolvedPath, '.grafema');
192
+ const dbPath = join(grafemaDir, 'graph.rfdb');
193
+ if (!existsSync(dbPath)) {
194
+ exitWithError('No graph database found', ['Run: grafema analyze']);
195
+ }
196
+ const backend = new RFDBServerBackend({ dbPath });
197
+ await backend.connect();
198
+ // Check graph freshness
199
+ const freshnessChecker = new GraphFreshnessChecker();
200
+ const freshness = await freshnessChecker.checkFreshness(backend);
201
+ if (!freshness.isFresh) {
202
+ if (options.failOnStale) {
203
+ console.error(`✗ Graph is stale: ${freshness.staleCount} module(s) changed`);
204
+ for (const stale of freshness.staleModules.slice(0, 5)) {
205
+ console.error(` ${stale.file} (${stale.reason})`);
206
+ }
207
+ if (freshness.staleModules.length > 5) {
208
+ console.error(` ... and ${freshness.staleModules.length - 5} more`);
209
+ }
210
+ console.error('');
211
+ console.error('→ Run: grafema analyze');
212
+ await backend.close();
213
+ process.exit(1);
214
+ }
215
+ if (!options.skipReanalysis) {
216
+ console.log(`Reanalyzing ${freshness.staleCount} stale module(s)...`);
217
+ const reanalyzer = new IncrementalReanalyzer(backend, resolvedPath);
218
+ const result = await reanalyzer.reanalyze(freshness.staleModules);
219
+ console.log(`Reanalyzed ${result.modulesReanalyzed} module(s) in ${result.durationMs}ms`);
220
+ console.log('');
221
+ }
222
+ else {
223
+ console.warn(`Warning: ${freshness.staleCount} stale module(s) detected. Use --skip-reanalysis to suppress.`);
224
+ for (const stale of freshness.staleModules.slice(0, 5)) {
225
+ console.warn(` - ${stale.file} (${stale.reason})`);
226
+ }
227
+ if (freshness.staleModules.length > 5) {
228
+ console.warn(` ... and ${freshness.staleModules.length - 5} more`);
229
+ }
230
+ console.log('');
231
+ }
232
+ }
233
+ else if (!options.quiet) {
234
+ console.log('Graph is fresh');
235
+ console.log('');
236
+ }
237
+ try {
238
+ let validator;
239
+ let validatorName;
240
+ switch (guaranteeName) {
241
+ case 'node-creation':
242
+ validator = new NodeCreationValidator();
243
+ validatorName = 'NodeCreationValidator';
244
+ break;
245
+ default:
246
+ exitWithError(`Unknown guarantee: ${guaranteeName}`, [
247
+ 'Use --list-guarantees to see available options'
248
+ ]);
249
+ }
250
+ if (!options.quiet) {
251
+ console.log(`Running ${validatorName}...`);
252
+ console.log('');
253
+ }
254
+ const result = await validator.execute({
255
+ graph: backend,
256
+ projectPath: resolvedPath
257
+ });
258
+ const metadata = result.metadata;
259
+ if (options.json) {
260
+ console.log(JSON.stringify({
261
+ guarantee: guaranteeName,
262
+ passed: (metadata.summary?.totalViolations ?? 0) === 0,
263
+ ...metadata
264
+ }, null, 2));
265
+ }
266
+ else {
267
+ const violations = metadata.summary?.totalViolations ?? 0;
268
+ const issues = metadata.issues ?? [];
269
+ if (violations === 0) {
270
+ console.log('\x1b[32m✓\x1b[0m All checks passed');
271
+ }
272
+ else {
273
+ console.log(`\x1b[31m✗\x1b[0m Found ${violations} violation(s):`);
274
+ console.log('');
275
+ for (const issue of issues.slice(0, 10)) {
276
+ const location = issue.file ? `${issue.file}${issue.line ? `:${issue.line}` : ''}` : '';
277
+ console.log(` \x1b[31m•\x1b[0m [${issue.type}] ${issue.message}`);
278
+ if (issue.suggestion && !options.quiet) {
279
+ console.log(` Suggestion: ${issue.suggestion}`);
280
+ }
281
+ }
282
+ if (issues.length > 10) {
283
+ console.log(` ... and ${issues.length - 10} more violations`);
284
+ }
285
+ }
286
+ console.log('');
287
+ if (violations > 0) {
288
+ process.exit(1);
289
+ }
290
+ }
291
+ }
292
+ finally {
293
+ await backend.close();
294
+ }
295
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Coverage command - Show analysis coverage statistics
3
+ *
4
+ * Shows what percentage of the codebase has been analyzed:
5
+ * - Analyzed: Files in the graph as MODULE nodes
6
+ * - Unsupported: Files with extensions that no indexer handles
7
+ * - Unreachable: Files with supported extensions but not imported from entrypoints
8
+ */
9
+ import { Command } from 'commander';
10
+ export declare const coverageCommand: Command;
11
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/commands/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,eAAe,SA6BxB,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Coverage command - Show analysis coverage statistics
3
+ *
4
+ * Shows what percentage of the codebase has been analyzed:
5
+ * - Analyzed: Files in the graph as MODULE nodes
6
+ * - Unsupported: Files with extensions that no indexer handles
7
+ * - Unreachable: Files with supported extensions but not imported from entrypoints
8
+ */
9
+ import { Command } from 'commander';
10
+ import { resolve, join } from 'path';
11
+ import { existsSync } from 'fs';
12
+ import { RFDBServerBackend, CoverageAnalyzer } from '@grafema/core';
13
+ import { exitWithError } from '../utils/errorFormatter.js';
14
+ export const coverageCommand = new Command('coverage')
15
+ .description('Show analysis coverage statistics')
16
+ .option('-p, --project <path>', 'Project path', '.')
17
+ .option('-j, --json', 'Output as JSON')
18
+ .option('-v, --verbose', 'Show detailed file lists')
19
+ .action(async (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
+ const analyzer = new CoverageAnalyzer(backend, projectPath);
30
+ const result = await analyzer.analyze();
31
+ if (options.json) {
32
+ console.log(JSON.stringify(result, null, 2));
33
+ }
34
+ else {
35
+ printCoverageReport(result, options.verbose ?? false);
36
+ }
37
+ }
38
+ finally {
39
+ await backend.close();
40
+ }
41
+ });
42
+ /**
43
+ * Print human-readable coverage report
44
+ */
45
+ function printCoverageReport(result, verbose) {
46
+ console.log('');
47
+ console.log('Analysis Coverage');
48
+ console.log('=================');
49
+ console.log('');
50
+ // Summary
51
+ console.log(`Project: ${result.projectPath}`);
52
+ console.log('');
53
+ // Main statistics
54
+ console.log('File breakdown:');
55
+ console.log(` Total files: ${result.total}`);
56
+ console.log(` Analyzed: ${result.analyzed.count} (${result.percentages.analyzed}%) - in graph`);
57
+ console.log(` Unsupported: ${result.unsupported.count} (${result.percentages.unsupported}%) - no indexer available`);
58
+ console.log(` Unreachable: ${result.unreachable.count} (${result.percentages.unreachable}%) - not imported from entrypoints`);
59
+ // Unsupported files breakdown
60
+ if (result.unsupported.count > 0) {
61
+ console.log('');
62
+ console.log('Unsupported files by extension:');
63
+ const sortedExtensions = Object.entries(result.unsupported.byExtension)
64
+ .sort((a, b) => b[1].length - a[1].length);
65
+ for (const [ext, files] of sortedExtensions) {
66
+ console.log(` ${ext}: ${files.length} files`);
67
+ if (verbose) {
68
+ for (const file of files.slice(0, 10)) {
69
+ console.log(` - ${file}`);
70
+ }
71
+ if (files.length > 10) {
72
+ console.log(` ... and ${files.length - 10} more`);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ // Unreachable files breakdown
78
+ if (result.unreachable.count > 0) {
79
+ console.log('');
80
+ console.log('Unreachable source files:');
81
+ const sortedExtensions = Object.entries(result.unreachable.byExtension)
82
+ .sort((a, b) => b[1].length - a[1].length);
83
+ for (const [ext, files] of sortedExtensions) {
84
+ console.log(` ${ext}: ${files.length} files - not imported from entrypoints`);
85
+ if (verbose) {
86
+ for (const file of files.slice(0, 10)) {
87
+ console.log(` - ${file}`);
88
+ }
89
+ if (files.length > 10) {
90
+ console.log(` ... and ${files.length - 10} more`);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ console.log('');
96
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Explore command - Interactive TUI for graph navigation
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare const exploreCommand: Command;
6
+ //# sourceMappingURL=explore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explore.d.ts","sourceRoot":"","sources":["../../src/commands/explore.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2/BpC,eAAO,MAAM,cAAc,SAgCvB,CAAC"}