@grafema/cli 0.1.1-alpha → 0.2.0-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 +28 -7
  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 +31 -5
  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,534 @@
1
+ /**
2
+ * Diagnostic check functions for `grafema doctor` command - REG-214
3
+ *
4
+ * Checks are organized in levels:
5
+ * - Level 1: Prerequisites (fail-fast) - checkGrafemaInitialized, checkServerStatus
6
+ * - Level 2: Configuration - checkConfigValidity, checkEntrypoints
7
+ * - Level 3: Graph Health - checkDatabaseExists, checkGraphStats, checkConnectivity, checkFreshness
8
+ * - Level 4: Informational - checkVersions
9
+ */
10
+ import { existsSync, readFileSync, statSync } from 'fs';
11
+ import { join, dirname } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import { createRequire } from 'module';
14
+ import { RFDBServerBackend, RFDBClient, loadConfig, GraphFreshnessChecker, } from '@grafema/core';
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ // Valid built-in plugin names (for config validation)
18
+ const VALID_PLUGIN_NAMES = new Set([
19
+ // Discovery
20
+ 'SimpleProjectDiscovery', 'MonorepoServiceDiscovery', 'WorkspaceDiscovery',
21
+ // Indexing
22
+ 'JSModuleIndexer', 'RustModuleIndexer',
23
+ // Analysis
24
+ 'JSASTAnalyzer', 'ExpressRouteAnalyzer', 'SocketIOAnalyzer', 'DatabaseAnalyzer',
25
+ 'FetchAnalyzer', 'ServiceLayerAnalyzer', 'ReactAnalyzer', 'RustAnalyzer',
26
+ // Enrichment
27
+ 'MethodCallResolver', 'AliasTracker', 'ValueDomainAnalyzer', 'MountPointResolver',
28
+ 'PrefixEvaluator', 'InstanceOfResolver', 'ImportExportLinker', 'HTTPConnectionEnricher',
29
+ 'RustFFIEnricher',
30
+ // Validation
31
+ 'CallResolverValidator', 'EvalBanValidator', 'SQLInjectionValidator', 'ShadowingDetector',
32
+ 'GraphConnectivityValidator', 'DataFlowValidator', 'TypeScriptDeadCodeValidator',
33
+ ]);
34
+ // =============================================================================
35
+ // Level 1: Prerequisites (fail-fast)
36
+ // =============================================================================
37
+ /**
38
+ * Check if .grafema directory exists with config file.
39
+ * FAIL if not initialized.
40
+ */
41
+ export async function checkGrafemaInitialized(projectPath) {
42
+ const grafemaDir = join(projectPath, '.grafema');
43
+ const configYaml = join(grafemaDir, 'config.yaml');
44
+ const configJson = join(grafemaDir, 'config.json');
45
+ if (!existsSync(grafemaDir)) {
46
+ return {
47
+ name: 'initialization',
48
+ status: 'fail',
49
+ message: '.grafema directory not found',
50
+ recommendation: 'Run: grafema init',
51
+ };
52
+ }
53
+ if (!existsSync(configYaml) && !existsSync(configJson)) {
54
+ return {
55
+ name: 'initialization',
56
+ status: 'fail',
57
+ message: 'Config file not found',
58
+ recommendation: 'Run: grafema init',
59
+ };
60
+ }
61
+ const configFile = existsSync(configYaml) ? 'config.yaml' : 'config.json';
62
+ const deprecated = configFile === 'config.json';
63
+ return {
64
+ name: 'initialization',
65
+ status: deprecated ? 'warn' : 'pass',
66
+ message: `Config file: .grafema/${configFile}`,
67
+ recommendation: deprecated ? 'Run: grafema init --force (migrate to YAML)' : undefined,
68
+ };
69
+ }
70
+ /**
71
+ * Check if RFDB server is running and responsive.
72
+ * WARN if not running (server starts on-demand during analyze).
73
+ */
74
+ export async function checkServerStatus(projectPath) {
75
+ const socketPath = join(projectPath, '.grafema', 'rfdb.sock');
76
+ if (!existsSync(socketPath)) {
77
+ return {
78
+ name: 'server',
79
+ status: 'warn',
80
+ message: 'RFDB server not running',
81
+ recommendation: 'Run: grafema analyze (starts server automatically)',
82
+ };
83
+ }
84
+ const client = new RFDBClient(socketPath);
85
+ client.on('error', () => { }); // Suppress error events
86
+ try {
87
+ await client.connect();
88
+ const version = await client.ping();
89
+ await client.close();
90
+ return {
91
+ name: 'server',
92
+ status: 'pass',
93
+ message: `Server: connected (RFDB ${version || 'unknown'})`,
94
+ details: { version, socketPath },
95
+ };
96
+ }
97
+ catch {
98
+ return {
99
+ name: 'server',
100
+ status: 'warn',
101
+ message: 'Server socket exists but not responding (stale)',
102
+ recommendation: 'Run: grafema analyze (will restart server)',
103
+ };
104
+ }
105
+ }
106
+ // =============================================================================
107
+ // Level 2: Configuration Validity
108
+ // =============================================================================
109
+ /**
110
+ * Validate config file syntax and structure.
111
+ * Uses existing loadConfig() which throws on errors.
112
+ */
113
+ export async function checkConfigValidity(projectPath) {
114
+ try {
115
+ // Silent logger to suppress warnings during validation
116
+ const config = loadConfig(projectPath, { warn: () => { } });
117
+ // Check for unknown plugins
118
+ const unknownPlugins = [];
119
+ const phases = ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'];
120
+ for (const phase of phases) {
121
+ const plugins = config.plugins[phase] || [];
122
+ for (const name of plugins) {
123
+ if (!VALID_PLUGIN_NAMES.has(name)) {
124
+ unknownPlugins.push(name);
125
+ }
126
+ }
127
+ }
128
+ if (unknownPlugins.length > 0) {
129
+ return {
130
+ name: 'config',
131
+ status: 'warn',
132
+ message: `Unknown plugin(s): ${unknownPlugins.join(', ')}`,
133
+ recommendation: 'Check plugin names for typos. Run: grafema doctor --verbose for available plugins',
134
+ details: { unknownPlugins },
135
+ };
136
+ }
137
+ const totalPlugins = phases.reduce((sum, phase) => sum + (config.plugins[phase]?.length || 0), 0);
138
+ return {
139
+ name: 'config',
140
+ status: 'pass',
141
+ message: `Config valid: ${totalPlugins} plugins configured`,
142
+ details: { pluginCount: totalPlugins, services: config.services.length },
143
+ };
144
+ }
145
+ catch (err) {
146
+ const error = err instanceof Error ? err : new Error(String(err));
147
+ return {
148
+ name: 'config',
149
+ status: 'fail',
150
+ message: `Config error: ${error.message}`,
151
+ recommendation: 'Fix config.yaml syntax or run: grafema init --force',
152
+ };
153
+ }
154
+ }
155
+ /**
156
+ * Check that entrypoints can be resolved.
157
+ * For config-defined services, validates that entrypoint files exist.
158
+ */
159
+ export async function checkEntrypoints(projectPath) {
160
+ let config;
161
+ try {
162
+ config = loadConfig(projectPath, { warn: () => { } });
163
+ }
164
+ catch {
165
+ // Config loading failed - already reported by checkConfigValidity
166
+ return {
167
+ name: 'entrypoints',
168
+ status: 'skip',
169
+ message: 'Skipped (config error)',
170
+ };
171
+ }
172
+ if (config.services.length === 0) {
173
+ // Auto-discovery mode - check package.json exists
174
+ const pkgJson = join(projectPath, 'package.json');
175
+ if (!existsSync(pkgJson)) {
176
+ return {
177
+ name: 'entrypoints',
178
+ status: 'warn',
179
+ message: 'No package.json found for auto-discovery',
180
+ recommendation: 'Add package.json or configure services in config.yaml',
181
+ };
182
+ }
183
+ return {
184
+ name: 'entrypoints',
185
+ status: 'pass',
186
+ message: 'Using auto-discovery mode',
187
+ };
188
+ }
189
+ // Config-defined services - validate each
190
+ const issues = [];
191
+ const valid = [];
192
+ for (const svc of config.services) {
193
+ const svcPath = join(projectPath, svc.path);
194
+ let entrypoint;
195
+ if (svc.entryPoint) {
196
+ entrypoint = join(svcPath, svc.entryPoint);
197
+ }
198
+ else {
199
+ // Auto-detect from package.json
200
+ const pkgPath = join(svcPath, 'package.json');
201
+ if (existsSync(pkgPath)) {
202
+ try {
203
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
204
+ entrypoint = join(svcPath, pkg.main || 'index.js');
205
+ }
206
+ catch {
207
+ entrypoint = join(svcPath, 'index.js');
208
+ }
209
+ }
210
+ else {
211
+ entrypoint = join(svcPath, 'index.js');
212
+ }
213
+ }
214
+ if (existsSync(entrypoint)) {
215
+ valid.push(svc.name);
216
+ }
217
+ else {
218
+ issues.push(`${svc.name}: ${entrypoint} not found`);
219
+ }
220
+ }
221
+ if (issues.length > 0) {
222
+ return {
223
+ name: 'entrypoints',
224
+ status: 'warn',
225
+ message: `${issues.length} service(s) with missing entrypoints`,
226
+ recommendation: 'Check service paths in config.yaml',
227
+ details: { issues, valid },
228
+ };
229
+ }
230
+ return {
231
+ name: 'entrypoints',
232
+ status: 'pass',
233
+ message: `Entrypoints: ${valid.length} service(s) found`,
234
+ details: { services: valid },
235
+ };
236
+ }
237
+ // =============================================================================
238
+ // Level 3: Graph Health
239
+ // =============================================================================
240
+ /**
241
+ * Check if database file exists and has data.
242
+ */
243
+ export async function checkDatabaseExists(projectPath) {
244
+ const dbPath = join(projectPath, '.grafema', 'graph.rfdb');
245
+ if (!existsSync(dbPath)) {
246
+ return {
247
+ name: 'database',
248
+ status: 'fail',
249
+ message: 'Database not found',
250
+ recommendation: 'Run: grafema analyze',
251
+ };
252
+ }
253
+ // Check file size (empty DB is typically < 100 bytes)
254
+ const stats = statSync(dbPath);
255
+ if (stats.size < 100) {
256
+ return {
257
+ name: 'database',
258
+ status: 'warn',
259
+ message: 'Database appears empty',
260
+ recommendation: 'Run: grafema analyze',
261
+ };
262
+ }
263
+ return {
264
+ name: 'database',
265
+ status: 'pass',
266
+ message: `Database: ${dbPath}`,
267
+ details: { size: stats.size },
268
+ };
269
+ }
270
+ /**
271
+ * Get graph statistics (requires server running).
272
+ */
273
+ export async function checkGraphStats(projectPath) {
274
+ const socketPath = join(projectPath, '.grafema', 'rfdb.sock');
275
+ const dbPath = join(projectPath, '.grafema', 'graph.rfdb');
276
+ if (!existsSync(socketPath)) {
277
+ return {
278
+ name: 'graph_stats',
279
+ status: 'skip',
280
+ message: 'Server not running (skipped stats check)',
281
+ };
282
+ }
283
+ const backend = new RFDBServerBackend({ dbPath });
284
+ try {
285
+ await backend.connect();
286
+ const stats = await backend.getStats();
287
+ await backend.close();
288
+ if (stats.nodeCount === 0) {
289
+ return {
290
+ name: 'graph_stats',
291
+ status: 'fail',
292
+ message: 'Database is empty (0 nodes)',
293
+ recommendation: 'Run: grafema analyze',
294
+ };
295
+ }
296
+ return {
297
+ name: 'graph_stats',
298
+ status: 'pass',
299
+ message: `Graph: ${stats.nodeCount.toLocaleString()} nodes, ${stats.edgeCount.toLocaleString()} edges`,
300
+ details: {
301
+ nodeCount: stats.nodeCount,
302
+ edgeCount: stats.edgeCount,
303
+ nodesByType: stats.nodesByType,
304
+ edgesByType: stats.edgesByType,
305
+ },
306
+ };
307
+ }
308
+ catch (err) {
309
+ return {
310
+ name: 'graph_stats',
311
+ status: 'warn',
312
+ message: `Could not read graph stats: ${err.message}`,
313
+ };
314
+ }
315
+ }
316
+ /**
317
+ * Check graph connectivity - find disconnected nodes.
318
+ * Thresholds:
319
+ * 0-5%: pass (normal for external modules)
320
+ * 5-20%: warn
321
+ * >20%: fail (critical issue)
322
+ */
323
+ export async function checkConnectivity(projectPath) {
324
+ const socketPath = join(projectPath, '.grafema', 'rfdb.sock');
325
+ const dbPath = join(projectPath, '.grafema', 'graph.rfdb');
326
+ if (!existsSync(socketPath)) {
327
+ return {
328
+ name: 'connectivity',
329
+ status: 'skip',
330
+ message: 'Server not running (skipped connectivity check)',
331
+ };
332
+ }
333
+ const backend = new RFDBServerBackend({ dbPath });
334
+ try {
335
+ await backend.connect();
336
+ // Get all nodes
337
+ const allNodes = [];
338
+ for await (const node of backend.queryNodes({})) {
339
+ allNodes.push({ id: node.id, type: node.type });
340
+ }
341
+ const totalCount = allNodes.length;
342
+ if (totalCount === 0) {
343
+ await backend.close();
344
+ return {
345
+ name: 'connectivity',
346
+ status: 'skip',
347
+ message: 'No nodes to check',
348
+ };
349
+ }
350
+ // Find root nodes (SERVICE, MODULE, PROJECT)
351
+ const rootTypes = ['SERVICE', 'MODULE', 'PROJECT'];
352
+ const rootNodes = allNodes.filter(n => rootTypes.includes(n.type));
353
+ if (rootNodes.length === 0) {
354
+ await backend.close();
355
+ return {
356
+ name: 'connectivity',
357
+ status: 'warn',
358
+ message: 'No root nodes found (SERVICE/MODULE/PROJECT)',
359
+ recommendation: 'Run: grafema analyze',
360
+ };
361
+ }
362
+ // Get all edges and build adjacency
363
+ const allEdges = await backend.getAllEdges();
364
+ const adjacencyOut = new Map();
365
+ const adjacencyIn = new Map();
366
+ for (const edge of allEdges) {
367
+ if (!adjacencyOut.has(edge.src))
368
+ adjacencyOut.set(edge.src, []);
369
+ adjacencyOut.get(edge.src).push(edge.dst);
370
+ if (!adjacencyIn.has(edge.dst))
371
+ adjacencyIn.set(edge.dst, []);
372
+ adjacencyIn.get(edge.dst).push(edge.src);
373
+ }
374
+ // BFS from roots
375
+ const reachable = new Set();
376
+ const queue = [...rootNodes.map(n => n.id)];
377
+ while (queue.length > 0) {
378
+ const nodeId = queue.shift();
379
+ if (reachable.has(nodeId))
380
+ continue;
381
+ reachable.add(nodeId);
382
+ const outgoing = adjacencyOut.get(nodeId) || [];
383
+ const incoming = adjacencyIn.get(nodeId) || [];
384
+ for (const targetId of [...outgoing, ...incoming]) {
385
+ if (!reachable.has(targetId))
386
+ queue.push(targetId);
387
+ }
388
+ }
389
+ await backend.close();
390
+ const unreachableCount = totalCount - reachable.size;
391
+ const percentage = (unreachableCount / totalCount) * 100;
392
+ if (unreachableCount === 0) {
393
+ return {
394
+ name: 'connectivity',
395
+ status: 'pass',
396
+ message: 'All nodes connected',
397
+ details: { totalNodes: totalCount },
398
+ };
399
+ }
400
+ // Group unreachable by type
401
+ const unreachableNodes = allNodes.filter(n => !reachable.has(n.id));
402
+ const byType = {};
403
+ for (const node of unreachableNodes) {
404
+ byType[node.type] = (byType[node.type] || 0) + 1;
405
+ }
406
+ if (percentage > 20) {
407
+ return {
408
+ name: 'connectivity',
409
+ status: 'fail',
410
+ message: `Critical: ${unreachableCount} disconnected nodes (${percentage.toFixed(1)}%)`,
411
+ recommendation: 'Run: grafema analyze --clear (rebuild graph)',
412
+ details: { unreachableCount, percentage, byType },
413
+ };
414
+ }
415
+ if (percentage > 5) {
416
+ return {
417
+ name: 'connectivity',
418
+ status: 'warn',
419
+ message: `${unreachableCount} disconnected nodes (${percentage.toFixed(1)}%)`,
420
+ recommendation: 'Run: grafema analyze --clear (may fix)',
421
+ details: { unreachableCount, percentage, byType },
422
+ };
423
+ }
424
+ return {
425
+ name: 'connectivity',
426
+ status: 'pass',
427
+ message: `${unreachableCount} disconnected nodes (${percentage.toFixed(1)}% - normal)`,
428
+ details: { unreachableCount, percentage, byType },
429
+ };
430
+ }
431
+ catch (err) {
432
+ return {
433
+ name: 'connectivity',
434
+ status: 'warn',
435
+ message: `Could not check connectivity: ${err.message}`,
436
+ };
437
+ }
438
+ }
439
+ /**
440
+ * Check if graph is fresh (no stale modules).
441
+ */
442
+ export async function checkFreshness(projectPath) {
443
+ const socketPath = join(projectPath, '.grafema', 'rfdb.sock');
444
+ const dbPath = join(projectPath, '.grafema', 'graph.rfdb');
445
+ if (!existsSync(socketPath)) {
446
+ return {
447
+ name: 'freshness',
448
+ status: 'skip',
449
+ message: 'Server not running (skipped freshness check)',
450
+ };
451
+ }
452
+ const backend = new RFDBServerBackend({ dbPath });
453
+ try {
454
+ await backend.connect();
455
+ const freshnessChecker = new GraphFreshnessChecker();
456
+ const result = await freshnessChecker.checkFreshness(backend);
457
+ await backend.close();
458
+ if (result.isFresh) {
459
+ return {
460
+ name: 'freshness',
461
+ status: 'pass',
462
+ message: 'Graph is up to date',
463
+ };
464
+ }
465
+ return {
466
+ name: 'freshness',
467
+ status: 'warn',
468
+ message: `${result.staleCount} stale module(s) detected`,
469
+ recommendation: 'Run: grafema analyze (or grafema check for auto-reanalysis)',
470
+ details: {
471
+ staleCount: result.staleCount,
472
+ staleModules: result.staleModules.slice(0, 5).map(m => m.file),
473
+ },
474
+ };
475
+ }
476
+ catch (err) {
477
+ return {
478
+ name: 'freshness',
479
+ status: 'warn',
480
+ message: `Could not check freshness: ${err.message}`,
481
+ };
482
+ }
483
+ }
484
+ // =============================================================================
485
+ // Level 4: Informational
486
+ // =============================================================================
487
+ /**
488
+ * Collect version information (always passes).
489
+ */
490
+ export async function checkVersions(projectPath) {
491
+ let cliVersion = 'unknown';
492
+ let coreVersion = 'unknown';
493
+ let rfdbVersion;
494
+ // Read CLI version - from dist/commands/doctor/ go up 3 levels to cli/
495
+ try {
496
+ const cliPkgPath = join(__dirname, '../../../package.json');
497
+ const cliPkg = JSON.parse(readFileSync(cliPkgPath, 'utf-8'));
498
+ cliVersion = cliPkg.version;
499
+ }
500
+ catch {
501
+ // Ignore errors
502
+ }
503
+ // Read core version
504
+ try {
505
+ const require = createRequire(import.meta.url);
506
+ const corePkgPath = require.resolve('@grafema/core/package.json');
507
+ const corePkg = JSON.parse(readFileSync(corePkgPath, 'utf-8'));
508
+ coreVersion = corePkg.version;
509
+ }
510
+ catch {
511
+ // Ignore errors
512
+ }
513
+ // Get RFDB version from server if running
514
+ const socketPath = join(projectPath, '.grafema', 'rfdb.sock');
515
+ if (existsSync(socketPath)) {
516
+ const client = new RFDBClient(socketPath);
517
+ client.on('error', () => { });
518
+ try {
519
+ await client.connect();
520
+ const version = await client.ping();
521
+ rfdbVersion = version || undefined;
522
+ await client.close();
523
+ }
524
+ catch {
525
+ // Ignore errors
526
+ }
527
+ }
528
+ return {
529
+ name: 'versions',
530
+ status: 'pass',
531
+ message: `CLI ${cliVersion}, Core ${coreVersion}${rfdbVersion ? `, RFDB ${rfdbVersion}` : ''}`,
532
+ details: { cli: cliVersion, core: coreVersion, rfdb: rfdbVersion },
533
+ };
534
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Output formatting utilities for `grafema doctor` command - REG-214
3
+ */
4
+ import type { DoctorCheckResult, DoctorReport } from './types.js';
5
+ /**
6
+ * Format a single check result for console output.
7
+ */
8
+ export declare function formatCheck(result: DoctorCheckResult, verbose: boolean): string;
9
+ /**
10
+ * Format full report for console.
11
+ */
12
+ export declare function formatReport(checks: DoctorCheckResult[], options: {
13
+ quiet?: boolean;
14
+ verbose?: boolean;
15
+ }): string;
16
+ /**
17
+ * Build JSON report structure.
18
+ */
19
+ export declare function buildJsonReport(checks: DoctorCheckResult[], projectPath: string): DoctorReport;
20
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/output.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmBlE;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAiB/E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,iBAAiB,EAAE,EAC3B,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9C,MAAM,CA2BR;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,iBAAiB,EAAE,EAC3B,WAAW,EAAE,MAAM,GAClB,YAAY,CA4Bd"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Output formatting utilities for `grafema doctor` command - REG-214
3
+ */
4
+ // ANSI colors (matching existing CLI style)
5
+ const COLORS = {
6
+ green: '\x1b[32m',
7
+ red: '\x1b[31m',
8
+ yellow: '\x1b[33m',
9
+ cyan: '\x1b[36m',
10
+ dim: '\x1b[2m',
11
+ reset: '\x1b[0m',
12
+ };
13
+ const STATUS_ICONS = {
14
+ pass: `${COLORS.green}✓${COLORS.reset}`,
15
+ warn: `${COLORS.yellow}⚠${COLORS.reset}`,
16
+ fail: `${COLORS.red}✗${COLORS.reset}`,
17
+ skip: `${COLORS.dim}○${COLORS.reset}`,
18
+ };
19
+ /**
20
+ * Format a single check result for console output.
21
+ */
22
+ export function formatCheck(result, verbose) {
23
+ const icon = STATUS_ICONS[result.status];
24
+ let output = `${icon} ${result.message}`;
25
+ if (result.recommendation) {
26
+ output += `\n ${COLORS.dim}→${COLORS.reset} ${result.recommendation}`;
27
+ }
28
+ if (verbose && result.details) {
29
+ const detailStr = JSON.stringify(result.details, null, 2)
30
+ .split('\n')
31
+ .map(line => ` ${COLORS.dim}${line}${COLORS.reset}`)
32
+ .join('\n');
33
+ output += `\n${detailStr}`;
34
+ }
35
+ return output;
36
+ }
37
+ /**
38
+ * Format full report for console.
39
+ */
40
+ export function formatReport(checks, options) {
41
+ const lines = [];
42
+ if (!options.quiet) {
43
+ lines.push('Checking Grafema setup...');
44
+ lines.push('');
45
+ }
46
+ for (const check of checks) {
47
+ if (options.quiet && check.status === 'pass')
48
+ continue;
49
+ lines.push(formatCheck(check, options.verbose || false));
50
+ }
51
+ // Summary
52
+ const failCount = checks.filter(c => c.status === 'fail').length;
53
+ const warnCount = checks.filter(c => c.status === 'warn').length;
54
+ lines.push('');
55
+ if (failCount > 0) {
56
+ lines.push(`${COLORS.red}Status: ${failCount} error(s), ${warnCount} warning(s)${COLORS.reset}`);
57
+ }
58
+ else if (warnCount > 0) {
59
+ lines.push(`${COLORS.yellow}Status: ${warnCount} warning(s)${COLORS.reset}`);
60
+ }
61
+ else {
62
+ lines.push(`${COLORS.green}Status: All checks passed${COLORS.reset}`);
63
+ }
64
+ return lines.join('\n');
65
+ }
66
+ /**
67
+ * Build JSON report structure.
68
+ */
69
+ export function buildJsonReport(checks, projectPath) {
70
+ const failCount = checks.filter(c => c.status === 'fail').length;
71
+ const warnCount = checks.filter(c => c.status === 'warn').length;
72
+ const status = failCount > 0 ? 'error' : warnCount > 0 ? 'warning' : 'healthy';
73
+ const recommendations = checks
74
+ .filter(c => c.recommendation)
75
+ .map(c => c.recommendation);
76
+ // Extract versions from versions check
77
+ const versionsCheck = checks.find(c => c.name === 'versions');
78
+ const versions = versionsCheck?.details || {
79
+ cli: 'unknown',
80
+ core: 'unknown',
81
+ };
82
+ return {
83
+ status,
84
+ timestamp: new Date().toISOString(),
85
+ project: projectPath,
86
+ checks,
87
+ recommendations,
88
+ versions: {
89
+ cli: versions.cli || 'unknown',
90
+ core: versions.core || 'unknown',
91
+ rfdb: versions.rfdb,
92
+ },
93
+ };
94
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Type definitions for `grafema doctor` command - REG-214
3
+ */
4
+ /**
5
+ * Status of a single diagnostic check
6
+ */
7
+ export type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';
8
+ /**
9
+ * Result of a single diagnostic check
10
+ */
11
+ export interface DoctorCheckResult {
12
+ name: string;
13
+ status: CheckStatus;
14
+ message: string;
15
+ recommendation?: string;
16
+ details?: Record<string, unknown>;
17
+ }
18
+ /**
19
+ * Options for the doctor command
20
+ */
21
+ export interface DoctorOptions {
22
+ project: string;
23
+ json?: boolean;
24
+ quiet?: boolean;
25
+ verbose?: boolean;
26
+ }
27
+ /**
28
+ * Overall doctor report (for JSON output)
29
+ */
30
+ export interface DoctorReport {
31
+ status: 'healthy' | 'warning' | 'error';
32
+ timestamp: string;
33
+ project: string;
34
+ checks: DoctorCheckResult[];
35
+ recommendations: string[];
36
+ versions: {
37
+ cli: string;
38
+ core: string;
39
+ rfdb?: string;
40
+ };
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,EAAE;QACR,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH"}