@grafema/cli 0.1.1-alpha → 0.2.1-beta

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 (79) hide show
  1. package/dist/cli.js +10 -0
  2. package/dist/commands/analyze.d.ts.map +1 -1
  3. package/dist/commands/analyze.js +69 -11
  4. package/dist/commands/check.d.ts +6 -0
  5. package/dist/commands/check.d.ts.map +1 -1
  6. package/dist/commands/check.js +177 -1
  7. package/dist/commands/coverage.d.ts.map +1 -1
  8. package/dist/commands/coverage.js +7 -0
  9. package/dist/commands/doctor/checks.d.ts +55 -0
  10. package/dist/commands/doctor/checks.d.ts.map +1 -0
  11. package/dist/commands/doctor/checks.js +534 -0
  12. package/dist/commands/doctor/output.d.ts +20 -0
  13. package/dist/commands/doctor/output.d.ts.map +1 -0
  14. package/dist/commands/doctor/output.js +94 -0
  15. package/dist/commands/doctor/types.d.ts +42 -0
  16. package/dist/commands/doctor/types.d.ts.map +1 -0
  17. package/dist/commands/doctor/types.js +4 -0
  18. package/dist/commands/doctor.d.ts +17 -0
  19. package/dist/commands/doctor.d.ts.map +1 -0
  20. package/dist/commands/doctor.js +80 -0
  21. package/dist/commands/explain.d.ts +16 -0
  22. package/dist/commands/explain.d.ts.map +1 -0
  23. package/dist/commands/explain.js +145 -0
  24. package/dist/commands/explore.d.ts +7 -1
  25. package/dist/commands/explore.d.ts.map +1 -1
  26. package/dist/commands/explore.js +204 -85
  27. package/dist/commands/get.d.ts.map +1 -1
  28. package/dist/commands/get.js +16 -4
  29. package/dist/commands/impact.d.ts.map +1 -1
  30. package/dist/commands/impact.js +48 -50
  31. package/dist/commands/init.d.ts.map +1 -1
  32. package/dist/commands/init.js +93 -15
  33. package/dist/commands/ls.d.ts +14 -0
  34. package/dist/commands/ls.d.ts.map +1 -0
  35. package/dist/commands/ls.js +132 -0
  36. package/dist/commands/overview.d.ts.map +1 -1
  37. package/dist/commands/overview.js +15 -2
  38. package/dist/commands/query.d.ts +98 -0
  39. package/dist/commands/query.d.ts.map +1 -1
  40. package/dist/commands/query.js +549 -136
  41. package/dist/commands/schema.d.ts +13 -0
  42. package/dist/commands/schema.d.ts.map +1 -0
  43. package/dist/commands/schema.js +279 -0
  44. package/dist/commands/server.d.ts.map +1 -1
  45. package/dist/commands/server.js +13 -6
  46. package/dist/commands/stats.d.ts.map +1 -1
  47. package/dist/commands/stats.js +7 -0
  48. package/dist/commands/trace.d.ts +73 -0
  49. package/dist/commands/trace.d.ts.map +1 -1
  50. package/dist/commands/trace.js +500 -5
  51. package/dist/commands/types.d.ts +12 -0
  52. package/dist/commands/types.d.ts.map +1 -0
  53. package/dist/commands/types.js +79 -0
  54. package/dist/utils/formatNode.d.ts +13 -0
  55. package/dist/utils/formatNode.d.ts.map +1 -1
  56. package/dist/utils/formatNode.js +35 -2
  57. package/package.json +3 -3
  58. package/src/cli.ts +10 -0
  59. package/src/commands/analyze.ts +84 -9
  60. package/src/commands/check.ts +201 -0
  61. package/src/commands/coverage.ts +7 -0
  62. package/src/commands/doctor/checks.ts +612 -0
  63. package/src/commands/doctor/output.ts +115 -0
  64. package/src/commands/doctor/types.ts +45 -0
  65. package/src/commands/doctor.ts +106 -0
  66. package/src/commands/explain.ts +173 -0
  67. package/src/commands/explore.tsx +247 -97
  68. package/src/commands/get.ts +20 -6
  69. package/src/commands/impact.ts +55 -61
  70. package/src/commands/init.ts +101 -14
  71. package/src/commands/ls.ts +166 -0
  72. package/src/commands/overview.ts +15 -2
  73. package/src/commands/query.ts +643 -149
  74. package/src/commands/schema.ts +345 -0
  75. package/src/commands/server.ts +13 -6
  76. package/src/commands/stats.ts +7 -0
  77. package/src/commands/trace.ts +647 -6
  78. package/src/commands/types.ts +94 -0
  79. package/src/utils/formatNode.ts +42 -2
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Output formatting utilities for `grafema doctor` command - REG-214
3
+ */
4
+
5
+ import type { DoctorCheckResult, DoctorReport } from './types.js';
6
+
7
+ // ANSI colors (matching existing CLI style)
8
+ const COLORS = {
9
+ green: '\x1b[32m',
10
+ red: '\x1b[31m',
11
+ yellow: '\x1b[33m',
12
+ cyan: '\x1b[36m',
13
+ dim: '\x1b[2m',
14
+ reset: '\x1b[0m',
15
+ };
16
+
17
+ const STATUS_ICONS: Record<string, string> = {
18
+ pass: `${COLORS.green}✓${COLORS.reset}`,
19
+ warn: `${COLORS.yellow}⚠${COLORS.reset}`,
20
+ fail: `${COLORS.red}✗${COLORS.reset}`,
21
+ skip: `${COLORS.dim}○${COLORS.reset}`,
22
+ };
23
+
24
+ /**
25
+ * Format a single check result for console output.
26
+ */
27
+ export function formatCheck(result: DoctorCheckResult, verbose: boolean): string {
28
+ const icon = STATUS_ICONS[result.status];
29
+ let output = `${icon} ${result.message}`;
30
+
31
+ if (result.recommendation) {
32
+ output += `\n ${COLORS.dim}→${COLORS.reset} ${result.recommendation}`;
33
+ }
34
+
35
+ if (verbose && result.details) {
36
+ const detailStr = JSON.stringify(result.details, null, 2)
37
+ .split('\n')
38
+ .map(line => ` ${COLORS.dim}${line}${COLORS.reset}`)
39
+ .join('\n');
40
+ output += `\n${detailStr}`;
41
+ }
42
+
43
+ return output;
44
+ }
45
+
46
+ /**
47
+ * Format full report for console.
48
+ */
49
+ export function formatReport(
50
+ checks: DoctorCheckResult[],
51
+ options: { quiet?: boolean; verbose?: boolean }
52
+ ): string {
53
+ const lines: string[] = [];
54
+
55
+ if (!options.quiet) {
56
+ lines.push('Checking Grafema setup...');
57
+ lines.push('');
58
+ }
59
+
60
+ for (const check of checks) {
61
+ if (options.quiet && check.status === 'pass') continue;
62
+ lines.push(formatCheck(check, options.verbose || false));
63
+ }
64
+
65
+ // Summary
66
+ const failCount = checks.filter(c => c.status === 'fail').length;
67
+ const warnCount = checks.filter(c => c.status === 'warn').length;
68
+
69
+ lines.push('');
70
+ if (failCount > 0) {
71
+ lines.push(`${COLORS.red}Status: ${failCount} error(s), ${warnCount} warning(s)${COLORS.reset}`);
72
+ } else if (warnCount > 0) {
73
+ lines.push(`${COLORS.yellow}Status: ${warnCount} warning(s)${COLORS.reset}`);
74
+ } else {
75
+ lines.push(`${COLORS.green}Status: All checks passed${COLORS.reset}`);
76
+ }
77
+
78
+ return lines.join('\n');
79
+ }
80
+
81
+ /**
82
+ * Build JSON report structure.
83
+ */
84
+ export function buildJsonReport(
85
+ checks: DoctorCheckResult[],
86
+ projectPath: string
87
+ ): DoctorReport {
88
+ const failCount = checks.filter(c => c.status === 'fail').length;
89
+ const warnCount = checks.filter(c => c.status === 'warn').length;
90
+
91
+ const status = failCount > 0 ? 'error' : warnCount > 0 ? 'warning' : 'healthy';
92
+ const recommendations = checks
93
+ .filter(c => c.recommendation)
94
+ .map(c => c.recommendation as string);
95
+
96
+ // Extract versions from versions check
97
+ const versionsCheck = checks.find(c => c.name === 'versions');
98
+ const versions = (versionsCheck?.details as { cli?: string; core?: string; rfdb?: string }) || {
99
+ cli: 'unknown',
100
+ core: 'unknown',
101
+ };
102
+
103
+ return {
104
+ status,
105
+ timestamp: new Date().toISOString(),
106
+ project: projectPath,
107
+ checks,
108
+ recommendations,
109
+ versions: {
110
+ cli: versions.cli || 'unknown',
111
+ core: versions.core || 'unknown',
112
+ rfdb: versions.rfdb,
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Type definitions for `grafema doctor` command - REG-214
3
+ */
4
+
5
+ /**
6
+ * Status of a single diagnostic check
7
+ */
8
+ export type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';
9
+
10
+ /**
11
+ * Result of a single diagnostic check
12
+ */
13
+ export interface DoctorCheckResult {
14
+ name: string; // e.g., 'config', 'server', 'database'
15
+ status: CheckStatus;
16
+ message: string; // Human-readable message
17
+ recommendation?: string; // Actionable next step if not pass
18
+ details?: Record<string, unknown>; // Additional data (counts, versions, etc.)
19
+ }
20
+
21
+ /**
22
+ * Options for the doctor command
23
+ */
24
+ export interface DoctorOptions {
25
+ project: string; // Project path (default: ".")
26
+ json?: boolean; // Output as JSON
27
+ quiet?: boolean; // Only show failures
28
+ verbose?: boolean; // Show detailed diagnostics
29
+ }
30
+
31
+ /**
32
+ * Overall doctor report (for JSON output)
33
+ */
34
+ export interface DoctorReport {
35
+ status: 'healthy' | 'warning' | 'error';
36
+ timestamp: string; // ISO timestamp
37
+ project: string; // Absolute project path
38
+ checks: DoctorCheckResult[];
39
+ recommendations: string[];
40
+ versions: {
41
+ cli: string;
42
+ core: string;
43
+ rfdb?: string;
44
+ };
45
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Doctor command - Diagnose Grafema setup issues
3
+ *
4
+ * Checks (in order):
5
+ * 1. Initialization (.grafema directory, config file)
6
+ * 2. Config validity (syntax, plugin names)
7
+ * 3. Entrypoints (service paths exist)
8
+ * 4. Server status (RFDB server running)
9
+ * 5. Database exists and has data
10
+ * 6. Graph statistics
11
+ * 7. Graph connectivity
12
+ * 8. Graph freshness
13
+ * 9. Version information
14
+ */
15
+
16
+ import { Command } from 'commander';
17
+ import { resolve } from 'path';
18
+ import {
19
+ checkGrafemaInitialized,
20
+ checkServerStatus,
21
+ checkConfigValidity,
22
+ checkEntrypoints,
23
+ checkDatabaseExists,
24
+ checkGraphStats,
25
+ checkConnectivity,
26
+ checkFreshness,
27
+ checkVersions,
28
+ } from './doctor/checks.js';
29
+ import { formatReport, buildJsonReport } from './doctor/output.js';
30
+ import type { DoctorOptions, DoctorCheckResult } from './doctor/types.js';
31
+
32
+ export const doctorCommand = new Command('doctor')
33
+ .description('Diagnose Grafema setup issues')
34
+ .option('-p, --project <path>', 'Project path', '.')
35
+ .option('-j, --json', 'Output as JSON')
36
+ .option('-q, --quiet', 'Only show failures')
37
+ .option('-v, --verbose', 'Show detailed diagnostics')
38
+ .addHelpText('after', `
39
+ Examples:
40
+ grafema doctor Run all diagnostic checks
41
+ grafema doctor --verbose Show detailed diagnostics
42
+ grafema doctor --quiet Only show failures
43
+ grafema doctor --json Output diagnostics as JSON
44
+ `)
45
+ .action(async (options: DoctorOptions) => {
46
+ const projectPath = resolve(options.project);
47
+ const checks: DoctorCheckResult[] = [];
48
+
49
+ // Level 1: Prerequisites (fail-fast)
50
+ const initCheck = await checkGrafemaInitialized(projectPath);
51
+ checks.push(initCheck);
52
+
53
+ if (initCheck.status === 'fail') {
54
+ // Can't continue without initialization
55
+ outputResults(checks, projectPath, options);
56
+ process.exit(1);
57
+ }
58
+
59
+ // Level 2: Configuration
60
+ checks.push(await checkConfigValidity(projectPath));
61
+ checks.push(await checkEntrypoints(projectPath));
62
+
63
+ // Server status (needed for Level 3 checks)
64
+ const serverCheck = await checkServerStatus(projectPath);
65
+ checks.push(serverCheck);
66
+
67
+ // Level 3: Graph Health (requires database and optionally server)
68
+ checks.push(await checkDatabaseExists(projectPath));
69
+
70
+ if (serverCheck.status === 'pass') {
71
+ // Server is running - can do full health checks
72
+ checks.push(await checkGraphStats(projectPath));
73
+ checks.push(await checkConnectivity(projectPath));
74
+ checks.push(await checkFreshness(projectPath));
75
+ }
76
+
77
+ // Level 4: Informational
78
+ checks.push(await checkVersions(projectPath));
79
+
80
+ // Output results
81
+ outputResults(checks, projectPath, options);
82
+
83
+ // Exit code
84
+ const failCount = checks.filter(c => c.status === 'fail').length;
85
+ const warnCount = checks.filter(c => c.status === 'warn').length;
86
+
87
+ if (failCount > 0) {
88
+ process.exit(1); // Critical issues
89
+ } else if (warnCount > 0) {
90
+ process.exit(2); // Warnings only
91
+ }
92
+ // Exit 0 for all pass
93
+ });
94
+
95
+ function outputResults(
96
+ checks: DoctorCheckResult[],
97
+ projectPath: string,
98
+ options: DoctorOptions
99
+ ): void {
100
+ if (options.json) {
101
+ const report = buildJsonReport(checks, projectPath);
102
+ console.log(JSON.stringify(report, null, 2));
103
+ } else {
104
+ console.log(formatReport(checks, options));
105
+ }
106
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Explain command - Show what nodes exist in a file
3
+ *
4
+ * Purpose: Help users discover what nodes exist in the graph for a file,
5
+ * displaying semantic IDs so users can query them.
6
+ *
7
+ * Use cases:
8
+ * - User can't find a variable they expect to be in the graph
9
+ * - User wants to understand what's been analyzed for a file
10
+ * - User needs semantic IDs to construct queries
11
+ *
12
+ * @see _tasks/REG-177/006-don-revised-plan.md
13
+ */
14
+
15
+ import { Command } from 'commander';
16
+ import { resolve, join, relative, normalize } from 'path';
17
+ import { existsSync, realpathSync } from 'fs';
18
+ import { RFDBServerBackend, FileExplainer, type EnhancedNode } from '@grafema/core';
19
+ import { exitWithError } from '../utils/errorFormatter.js';
20
+
21
+ interface ExplainOptions {
22
+ project: string;
23
+ json?: boolean;
24
+ }
25
+
26
+ export const explainCommand = new Command('explain')
27
+ .description('Show what nodes exist in a file')
28
+ .argument('<file>', 'File path to explain')
29
+ .option('-p, --project <path>', 'Project path', '.')
30
+ .option('-j, --json', 'Output as JSON')
31
+ .addHelpText('after', `
32
+ Examples:
33
+ grafema explain src/app.ts Show all nodes in src/app.ts
34
+ grafema explain src/app.ts --json Output as JSON for scripting
35
+ grafema explain ./src/utils.js Works with relative paths
36
+
37
+ This command helps you:
38
+ 1. Discover what nodes exist in the graph for a file
39
+ 2. Find semantic IDs to use in queries
40
+ 3. Understand scope context (try/catch, conditionals, etc.)
41
+
42
+ If a file shows NOT_ANALYZED:
43
+ - Run: grafema analyze
44
+ - Check if file is excluded in config
45
+ `)
46
+ .action(async (file: string, options: ExplainOptions) => {
47
+ const projectPath = resolve(options.project);
48
+ const grafemaDir = join(projectPath, '.grafema');
49
+ const dbPath = join(grafemaDir, 'graph.rfdb');
50
+
51
+ // Check database exists
52
+ if (!existsSync(dbPath)) {
53
+ exitWithError('No graph database found', [
54
+ 'Run: grafema init && grafema analyze',
55
+ ]);
56
+ }
57
+
58
+ // Normalize and resolve file path
59
+ let filePath = file;
60
+
61
+ // Handle relative paths - convert to relative from project root
62
+ if (file.startsWith('./') || file.startsWith('../')) {
63
+ filePath = normalize(file).replace(/^\.\//, '');
64
+ } else if (resolve(file) === file) {
65
+ // Absolute path - convert to relative
66
+ filePath = relative(projectPath, file);
67
+ }
68
+
69
+ // Resolve to absolute path for graph lookup
70
+ const resolvedPath = resolve(projectPath, filePath);
71
+ if (!existsSync(resolvedPath)) {
72
+ exitWithError(`File not found: ${file}`, [
73
+ 'Check the file path and try again',
74
+ ]);
75
+ }
76
+
77
+ // Use realpath to match how graph stores paths (handles symlinks like /tmp -> /private/tmp on macOS)
78
+ const absoluteFilePath = realpathSync(resolvedPath);
79
+
80
+ // Keep relative path for display
81
+ const relativeFilePath = relative(projectPath, absoluteFilePath);
82
+
83
+ const backend = new RFDBServerBackend({ dbPath });
84
+ await backend.connect();
85
+
86
+ try {
87
+ const explainer = new FileExplainer(backend);
88
+ // Query with absolute path since graph stores absolute paths
89
+ const result = await explainer.explain(absoluteFilePath);
90
+
91
+ // Override file in result for display purposes (show relative path)
92
+ result.file = relativeFilePath;
93
+
94
+ if (options.json) {
95
+ console.log(JSON.stringify(result, null, 2));
96
+ return;
97
+ }
98
+
99
+ // Human-readable output
100
+ console.log(`File: ${result.file}`);
101
+ console.log(`Status: ${result.status}`);
102
+ console.log('');
103
+
104
+ if (result.status === 'NOT_ANALYZED') {
105
+ console.log('This file has not been analyzed yet.');
106
+ console.log('');
107
+ console.log('To analyze:');
108
+ console.log(' grafema analyze');
109
+ return;
110
+ }
111
+
112
+ console.log(`Nodes in graph: ${result.totalCount}`);
113
+ console.log('');
114
+
115
+ // Group nodes by type for display
116
+ const nodesByType = groupNodesByType(result.nodes);
117
+
118
+ for (const [type, nodes] of Object.entries(nodesByType)) {
119
+ for (const node of nodes) {
120
+ displayNode(node, type, projectPath);
121
+ console.log('');
122
+ }
123
+ }
124
+
125
+ // Show summary by type
126
+ console.log('Summary:');
127
+ for (const [type, count] of Object.entries(result.byType).sort()) {
128
+ console.log(` ${type}: ${count}`);
129
+ }
130
+
131
+ console.log('');
132
+ console.log('To query a specific node by ID:');
133
+ console.log(' grafema query --raw \'attr(X, "id", "<semantic-id>")\'');
134
+ } finally {
135
+ await backend.close();
136
+ }
137
+ });
138
+
139
+ /**
140
+ * Group nodes by type for organized display
141
+ */
142
+ function groupNodesByType(nodes: EnhancedNode[]): Record<string, EnhancedNode[]> {
143
+ const grouped: Record<string, EnhancedNode[]> = {};
144
+
145
+ for (const node of nodes) {
146
+ const type = node.type || 'UNKNOWN';
147
+ if (!grouped[type]) {
148
+ grouped[type] = [];
149
+ }
150
+ grouped[type].push(node);
151
+ }
152
+
153
+ return grouped;
154
+ }
155
+
156
+ /**
157
+ * Display a single node in human-readable format
158
+ */
159
+ function displayNode(node: EnhancedNode, type: string, projectPath: string): void {
160
+ // Line 1: [TYPE] name (context)
161
+ const contextSuffix = node.context ? ` (${node.context})` : '';
162
+ console.log(`[${type}] ${node.name || '<anonymous>'}${contextSuffix}`);
163
+
164
+ // Line 2: ID (semantic ID for querying)
165
+ console.log(` ID: ${node.id}`);
166
+
167
+ // Line 3: Location
168
+ if (node.file) {
169
+ const relPath = relative(projectPath, node.file);
170
+ const loc = node.line ? `${relPath}:${node.line}` : relPath;
171
+ console.log(` Location: ${loc}`);
172
+ }
173
+ }