@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
@@ -1 +1 @@
1
- {"version":3,"file":"formatNode.d.ts","sourceRoot":"","sources":["../../src/utils/formatNode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAmB3F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAE9D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,WAAW,EAAE,MAAM,GAClB,MAAM,CAIR"}
1
+ {"version":3,"file":"formatNode.d.ts","sourceRoot":"","sources":["../../src/utils/formatNode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAyBhE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAoB3F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAE9D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,WAAW,EAAE,MAAM,GAClB,MAAM,CAIR"}
@@ -5,6 +5,38 @@
5
5
  * Semantic IDs are shown as the PRIMARY identifier, with location as secondary.
6
6
  */
7
7
  import { relative } from 'path';
8
+ /**
9
+ * Get the display name for a node based on its type.
10
+ *
11
+ * HTTP nodes use method + path/url instead of name.
12
+ * Other nodes use their name field.
13
+ */
14
+ export function getNodeDisplayName(node) {
15
+ switch (node.type) {
16
+ case 'http:route':
17
+ // Express routes: "GET /users"
18
+ if (node.method && node.path) {
19
+ return `${node.method} ${node.path}`;
20
+ }
21
+ break;
22
+ case 'http:request':
23
+ // Fetch/axios requests: "POST /api/data"
24
+ if (node.method && node.url) {
25
+ return `${node.method} ${node.url}`;
26
+ }
27
+ break;
28
+ }
29
+ // Default: use name, but guard against JSON metadata corruption
30
+ if (node.name && !node.name.startsWith('{')) {
31
+ return node.name;
32
+ }
33
+ // Fallback: extract name from semantic ID if possible
34
+ const parts = node.id.split('#');
35
+ if (parts.length > 1) {
36
+ return parts[1]; // Usually contains the key identifier
37
+ }
38
+ return node.id;
39
+ }
8
40
  /**
9
41
  * Format a node for primary display (multi-line)
10
42
  *
@@ -16,8 +48,9 @@ import { relative } from 'path';
16
48
  export function formatNodeDisplay(node, options) {
17
49
  const { projectPath, showLocation = true, indent = '' } = options;
18
50
  const lines = [];
19
- // Line 1: [TYPE] name
20
- lines.push(`${indent}[${node.type}] ${node.name}`);
51
+ // Line 1: [TYPE] display name (type-specific)
52
+ const displayName = getNodeDisplayName(node);
53
+ lines.push(`${indent}[${node.type}] ${displayName}`);
21
54
  // Line 2: ID (semantic ID)
22
55
  lines.push(`${indent} ID: ${node.id}`);
23
56
  // Line 3: Location (optional)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafema/cli",
3
- "version": "0.1.1-alpha",
3
+ "version": "0.2.1-beta",
4
4
  "description": "CLI for Grafema code analysis toolkit",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -37,8 +37,8 @@
37
37
  "ink-text-input": "^6.0.0",
38
38
  "react": "^19.2.3",
39
39
  "yaml": "^2.8.2",
40
- "@grafema/core": "0.1.1-alpha",
41
- "@grafema/types": "0.1.1-alpha"
40
+ "@grafema/core": "0.2.0-beta",
41
+ "@grafema/types": "0.2.0-beta"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^25.0.8",
package/src/cli.ts CHANGED
@@ -8,6 +8,8 @@ import { initCommand } from './commands/init.js';
8
8
  import { analyzeCommand } from './commands/analyze.js';
9
9
  import { overviewCommand } from './commands/overview.js';
10
10
  import { queryCommand } from './commands/query.js';
11
+ import { typesCommand } from './commands/types.js';
12
+ import { lsCommand } from './commands/ls.js';
11
13
  import { getCommand } from './commands/get.js';
12
14
  import { traceCommand } from './commands/trace.js';
13
15
  import { impactCommand } from './commands/impact.js';
@@ -16,6 +18,9 @@ import { statsCommand } from './commands/stats.js';
16
18
  import { checkCommand } from './commands/check.js';
17
19
  import { serverCommand } from './commands/server.js';
18
20
  import { coverageCommand } from './commands/coverage.js';
21
+ import { doctorCommand } from './commands/doctor.js';
22
+ import { schemaCommand } from './commands/schema.js';
23
+ import { explainCommand } from './commands/explain.js';
19
24
 
20
25
  const program = new Command();
21
26
 
@@ -29,6 +34,8 @@ program.addCommand(initCommand);
29
34
  program.addCommand(analyzeCommand);
30
35
  program.addCommand(overviewCommand);
31
36
  program.addCommand(queryCommand);
37
+ program.addCommand(typesCommand);
38
+ program.addCommand(lsCommand);
32
39
  program.addCommand(getCommand);
33
40
  program.addCommand(traceCommand);
34
41
  program.addCommand(impactCommand);
@@ -37,5 +44,8 @@ program.addCommand(statsCommand); // Keep for backwards compat
37
44
  program.addCommand(coverageCommand);
38
45
  program.addCommand(checkCommand);
39
46
  program.addCommand(serverCommand);
47
+ program.addCommand(doctorCommand);
48
+ program.addCommand(schemaCommand);
49
+ program.addCommand(explainCommand);
40
50
 
41
51
  program.parse();
@@ -4,7 +4,8 @@
4
4
 
5
5
  import { Command } from 'commander';
6
6
  import { resolve, join } from 'path';
7
- import { existsSync, mkdirSync } from 'fs';
7
+ import { existsSync, mkdirSync, readdirSync } from 'fs';
8
+ import { pathToFileURL } from 'url';
8
9
  import {
9
10
  Orchestrator,
10
11
  RFDBServerBackend,
@@ -24,6 +25,7 @@ import {
24
25
  // Analysis
25
26
  JSASTAnalyzer,
26
27
  ExpressRouteAnalyzer,
28
+ ExpressResponseAnalyzer,
27
29
  SocketIOAnalyzer,
28
30
  DatabaseAnalyzer,
29
31
  FetchAnalyzer,
@@ -32,12 +34,14 @@ import {
32
34
  RustAnalyzer,
33
35
  // Enrichment
34
36
  MethodCallResolver,
37
+ ArgumentParameterLinker,
35
38
  AliasTracker,
36
39
  ValueDomainAnalyzer,
37
40
  MountPointResolver,
38
41
  PrefixEvaluator,
39
42
  InstanceOfResolver,
40
43
  ImportExportLinker,
44
+ FunctionCallResolver,
41
45
  HTTPConnectionEnricher,
42
46
  RustFFIEnricher,
43
47
  // Validation
@@ -48,6 +52,7 @@ import {
48
52
  GraphConnectivityValidator,
49
53
  DataFlowValidator,
50
54
  TypeScriptDeadCodeValidator,
55
+ BrokenImportValidator,
51
56
  } from '@grafema/core';
52
57
  import type { LogLevel } from '@grafema/types';
53
58
 
@@ -62,6 +67,7 @@ const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
62
67
  // Analysis
63
68
  JSASTAnalyzer: () => new JSASTAnalyzer() as Plugin,
64
69
  ExpressRouteAnalyzer: () => new ExpressRouteAnalyzer() as Plugin,
70
+ ExpressResponseAnalyzer: () => new ExpressResponseAnalyzer() as Plugin,
65
71
  SocketIOAnalyzer: () => new SocketIOAnalyzer() as Plugin,
66
72
  DatabaseAnalyzer: () => new DatabaseAnalyzer() as Plugin,
67
73
  FetchAnalyzer: () => new FetchAnalyzer() as Plugin,
@@ -70,12 +76,14 @@ const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
70
76
  RustAnalyzer: () => new RustAnalyzer() as Plugin,
71
77
  // Enrichment
72
78
  MethodCallResolver: () => new MethodCallResolver() as Plugin,
79
+ ArgumentParameterLinker: () => new ArgumentParameterLinker() as Plugin,
73
80
  AliasTracker: () => new AliasTracker() as Plugin,
74
81
  ValueDomainAnalyzer: () => new ValueDomainAnalyzer() as Plugin,
75
82
  MountPointResolver: () => new MountPointResolver() as Plugin,
76
83
  PrefixEvaluator: () => new PrefixEvaluator() as Plugin,
77
84
  InstanceOfResolver: () => new InstanceOfResolver() as Plugin,
78
85
  ImportExportLinker: () => new ImportExportLinker() as Plugin,
86
+ FunctionCallResolver: () => new FunctionCallResolver() as Plugin,
79
87
  HTTPConnectionEnricher: () => new HTTPConnectionEnricher() as Plugin,
80
88
  RustFFIEnricher: () => new RustFFIEnricher() as Plugin,
81
89
  // Validation
@@ -86,16 +94,63 @@ const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
86
94
  GraphConnectivityValidator: () => new GraphConnectivityValidator() as Plugin,
87
95
  DataFlowValidator: () => new DataFlowValidator() as Plugin,
88
96
  TypeScriptDeadCodeValidator: () => new TypeScriptDeadCodeValidator() as Plugin,
97
+ BrokenImportValidator: () => new BrokenImportValidator() as Plugin,
89
98
  };
90
99
 
91
- function createPlugins(config: GrafemaConfig['plugins']): Plugin[] {
100
+ /**
101
+ * Load custom plugins from .grafema/plugins/ directory
102
+ */
103
+ async function loadCustomPlugins(
104
+ projectPath: string,
105
+ log: (msg: string) => void
106
+ ): Promise<Record<string, () => Plugin>> {
107
+ const pluginsDir = join(projectPath, '.grafema', 'plugins');
108
+ if (!existsSync(pluginsDir)) {
109
+ return {};
110
+ }
111
+
112
+ const customPlugins: Record<string, () => Plugin> = {};
113
+
114
+ try {
115
+ const files = readdirSync(pluginsDir).filter(
116
+ (f) => f.endsWith('.js') || f.endsWith('.mjs')
117
+ );
118
+
119
+ for (const file of files) {
120
+ try {
121
+ const pluginPath = join(pluginsDir, file);
122
+ const pluginUrl = pathToFileURL(pluginPath).href;
123
+ const module = await import(pluginUrl);
124
+
125
+ const PluginClass = module.default || module[file.replace(/\.(m?js)$/, '')];
126
+ if (PluginClass && typeof PluginClass === 'function') {
127
+ const pluginName = PluginClass.name || file.replace(/\.(m?js)$/, '');
128
+ customPlugins[pluginName] = () => new PluginClass() as Plugin;
129
+ log(`Loaded custom plugin: ${pluginName}`);
130
+ }
131
+ } catch (err) {
132
+ console.warn(`Failed to load plugin ${file}: ${(err as Error).message}`);
133
+ }
134
+ }
135
+ } catch (err) {
136
+ console.warn(`Error loading custom plugins: ${(err as Error).message}`);
137
+ }
138
+
139
+ return customPlugins;
140
+ }
141
+
142
+ function createPlugins(
143
+ config: GrafemaConfig['plugins'],
144
+ customPlugins: Record<string, () => Plugin> = {}
145
+ ): Plugin[] {
92
146
  const plugins: Plugin[] = [];
93
147
  const phases: (keyof GrafemaConfig['plugins'])[] = ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'];
94
148
 
95
149
  for (const phase of phases) {
96
150
  const names = config[phase] || [];
97
151
  for (const name of names) {
98
- const factory = BUILTIN_PLUGINS[name];
152
+ // Check built-in first, then custom
153
+ const factory = BUILTIN_PLUGINS[name] || customPlugins[name];
99
154
  if (factory) {
100
155
  plugins.push(factory());
101
156
  } else {
@@ -133,7 +188,18 @@ export const analyzeCommand = new Command('analyze')
133
188
  .option('-v, --verbose', 'Show verbose logging')
134
189
  .option('--debug', 'Enable debug mode (writes diagnostics.log)')
135
190
  .option('--log-level <level>', 'Set log level (silent, errors, warnings, info, debug)')
136
- .action(async (path: string, options: { service?: string; entrypoint?: string; clear?: boolean; quiet?: boolean; verbose?: boolean; debug?: boolean; logLevel?: string }) => {
191
+ .option('--strict', 'Enable strict mode (fail on unresolved references)')
192
+ .addHelpText('after', `
193
+ Examples:
194
+ grafema analyze Analyze current project
195
+ grafema analyze ./my-project Analyze specific directory
196
+ grafema analyze --clear Clear database and rebuild from scratch
197
+ grafema analyze -s api Analyze only "api" service (monorepo)
198
+ grafema analyze -v Verbose output with progress details
199
+ grafema analyze --debug Write diagnostics.log for debugging
200
+ grafema analyze --strict Fail on unresolved references (debugging)
201
+ `)
202
+ .action(async (path: string, options: { service?: string; entrypoint?: string; clear?: boolean; quiet?: boolean; verbose?: boolean; debug?: boolean; logLevel?: string; strict?: boolean }) => {
137
203
  const projectPath = resolve(path);
138
204
  const grafemaDir = join(projectPath, '.grafema');
139
205
  const dbPath = join(grafemaDir, 'graph.rfdb');
@@ -169,10 +235,18 @@ export const analyzeCommand = new Command('analyze')
169
235
  }
170
236
  }
171
237
 
172
- const plugins = createPlugins(config.plugins);
238
+ // Load custom plugins from .grafema/plugins/
239
+ const customPlugins = await loadCustomPlugins(projectPath, log);
240
+ const plugins = createPlugins(config.plugins, customPlugins);
173
241
 
174
242
  log(`Loaded ${plugins.length} plugins`);
175
243
 
244
+ // Resolve strict mode: CLI flag overrides config
245
+ const strictMode = options.strict ?? config.strict ?? false;
246
+ if (strictMode) {
247
+ log('Strict mode enabled - analysis will fail on unresolved references');
248
+ }
249
+
176
250
  const startTime = Date.now();
177
251
 
178
252
  const orchestrator = new Orchestrator({
@@ -183,6 +257,7 @@ export const analyzeCommand = new Command('analyze')
183
257
  forceAnalysis: options.clear || false,
184
258
  logger,
185
259
  services: config.services.length > 0 ? config.services : undefined, // Pass config services (REG-174)
260
+ strictMode, // REG-330: Pass strict mode flag
186
261
  onProgress: (progress) => {
187
262
  if (options.verbose) {
188
263
  log(`[${progress.phase}] ${progress.message}`);
@@ -211,7 +286,7 @@ export const analyzeCommand = new Command('analyze')
211
286
  // Print summary if there are any issues
212
287
  if (diagnostics.count() > 0) {
213
288
  log('');
214
- log(reporter.summary());
289
+ log(reporter.categorizedSummary());
215
290
 
216
291
  // In verbose mode, print full report
217
292
  if (options.verbose) {
@@ -220,10 +295,10 @@ export const analyzeCommand = new Command('analyze')
220
295
  }
221
296
  }
222
297
 
223
- // Write diagnostics.log in debug mode
298
+ // Always write diagnostics.log (required for `grafema check` command)
299
+ const writer = new DiagnosticWriter();
300
+ await writer.write(diagnostics, grafemaDir);
224
301
  if (options.debug) {
225
- const writer = new DiagnosticWriter();
226
- await writer.write(diagnostics, grafemaDir);
227
302
  log(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
228
303
  }
229
304
 
@@ -38,6 +38,37 @@ const BUILT_IN_VALIDATORS: Record<string, { name: string; description: string }>
38
38
  }
39
39
  };
40
40
 
41
+ // Category definition for diagnostic filtering
42
+ export interface DiagnosticCheckCategory {
43
+ name: string;
44
+ description: string;
45
+ codes: string[];
46
+ }
47
+
48
+ // Available diagnostic categories
49
+ export const CHECK_CATEGORIES: Record<string, DiagnosticCheckCategory> = {
50
+ 'connectivity': {
51
+ name: 'Graph Connectivity',
52
+ description: 'Check for disconnected nodes in the graph',
53
+ codes: ['ERR_DISCONNECTED_NODES', 'ERR_DISCONNECTED_NODE'],
54
+ },
55
+ 'calls': {
56
+ name: 'Call Resolution',
57
+ description: 'Check for unresolved function calls',
58
+ codes: ['ERR_UNRESOLVED_CALL'],
59
+ },
60
+ 'dataflow': {
61
+ name: 'Data Flow',
62
+ description: 'Check for missing assignments and broken references',
63
+ codes: ['ERR_MISSING_ASSIGNMENT', 'ERR_BROKEN_REFERENCE', 'ERR_NO_LEAF_NODE'],
64
+ },
65
+ 'imports': {
66
+ name: 'Import Validation',
67
+ description: 'Check for broken imports and undefined symbols',
68
+ codes: ['ERR_BROKEN_IMPORT', 'ERR_UNDEFINED_SYMBOL'],
69
+ },
70
+ };
71
+
41
72
  export const checkCommand = new Command('check')
42
73
  .description('Check invariants/guarantees')
43
74
  .argument('[rule]', 'Specific rule ID to check (or "all" for all rules)')
@@ -47,8 +78,22 @@ export const checkCommand = new Command('check')
47
78
  .option('-j, --json', 'Output results as JSON')
48
79
  .option('-q, --quiet', 'Only output failures')
49
80
  .option('--list-guarantees', 'List available built-in guarantees')
81
+ .option('--list-categories', 'List available diagnostic categories')
50
82
  .option('--skip-reanalysis', 'Skip automatic reanalysis of stale modules')
51
83
  .option('--fail-on-stale', 'Exit with error if stale modules found (CI mode)')
84
+ .addHelpText('after', `
85
+ Examples:
86
+ grafema check Run all guarantee checks
87
+ grafema check connectivity Check graph connectivity
88
+ grafema check calls Check call resolution
89
+ grafema check dataflow Check data flow integrity
90
+ grafema check all Run all diagnostic categories
91
+ grafema check --guarantee node-creation Run built-in validator
92
+ grafema check --list-categories List available categories
93
+ grafema check --list-guarantees List built-in guarantees
94
+ grafema check --fail-on-stale CI mode: fail if graph is stale
95
+ grafema check -q Only show failures (quiet mode)
96
+ `)
52
97
  .action(
53
98
  async (
54
99
  rule: string | undefined,
@@ -59,10 +104,25 @@ export const checkCommand = new Command('check')
59
104
  json?: boolean;
60
105
  quiet?: boolean;
61
106
  listGuarantees?: boolean;
107
+ listCategories?: boolean;
62
108
  skipReanalysis?: boolean;
63
109
  failOnStale?: boolean;
64
110
  }
65
111
  ) => {
112
+ // List available categories
113
+ if (options.listCategories) {
114
+ console.log('Available diagnostic categories:');
115
+ console.log('');
116
+ for (const [key, category] of Object.entries(CHECK_CATEGORIES)) {
117
+ console.log(` ${key}`);
118
+ console.log(` ${category.name}`);
119
+ console.log(` ${category.description}`);
120
+ console.log(` Usage: grafema check ${key}`);
121
+ console.log('');
122
+ }
123
+ return;
124
+ }
125
+
66
126
  // List available guarantees
67
127
  if (options.listGuarantees) {
68
128
  console.log('Available built-in guarantees:');
@@ -75,6 +135,12 @@ export const checkCommand = new Command('check')
75
135
  return;
76
136
  }
77
137
 
138
+ // Check if rule argument is a category name
139
+ if (rule && (rule in CHECK_CATEGORIES || rule === 'all')) {
140
+ await runCategoryCheck(rule, options);
141
+ return;
142
+ }
143
+
78
144
  // Run built-in guarantee validator
79
145
  if (options.guarantee) {
80
146
  const validatorInfo = BUILT_IN_VALIDATORS[options.guarantee];
@@ -377,3 +443,138 @@ async function runBuiltInValidator(
377
443
  await backend.close();
378
444
  }
379
445
  }
446
+
447
+ /**
448
+ * Run category-based diagnostic check
449
+ */
450
+ async function runCategoryCheck(
451
+ category: string,
452
+ options: { project: string; json?: boolean; quiet?: boolean }
453
+ ): Promise<void> {
454
+ const resolvedPath = resolve(options.project);
455
+ const grafemaDir = join(resolvedPath, '.grafema');
456
+ const diagnosticsLogPath = join(grafemaDir, 'diagnostics.log');
457
+
458
+ if (!existsSync(diagnosticsLogPath)) {
459
+ exitWithError('No diagnostics found', [
460
+ 'Run: grafema analyze',
461
+ 'Diagnostics are collected during analysis'
462
+ ]);
463
+ }
464
+
465
+ // Read diagnostics from log file (JSON lines format)
466
+ const diagnosticsContent = readFileSync(diagnosticsLogPath, 'utf-8');
467
+ const allDiagnostics = diagnosticsContent
468
+ .split('\n')
469
+ .filter(line => line.trim())
470
+ .map(line => {
471
+ try {
472
+ return JSON.parse(line);
473
+ } catch (e) {
474
+ return null;
475
+ }
476
+ })
477
+ .filter(Boolean);
478
+
479
+ // Filter diagnostics by category codes
480
+ let filteredDiagnostics = allDiagnostics;
481
+ if (category !== 'all') {
482
+ const categoryInfo = CHECK_CATEGORIES[category];
483
+ if (!categoryInfo) {
484
+ exitWithError(`Unknown category: ${category}`, [
485
+ 'Use --list-categories to see available options'
486
+ ]);
487
+ }
488
+ filteredDiagnostics = allDiagnostics.filter((d: any) =>
489
+ categoryInfo.codes.includes(d.code)
490
+ );
491
+ }
492
+
493
+ if (options.json) {
494
+ console.log(JSON.stringify({
495
+ category: category,
496
+ total: filteredDiagnostics.length,
497
+ diagnostics: filteredDiagnostics
498
+ }, null, 2));
499
+ } else {
500
+ const categoryName = category === 'all'
501
+ ? 'All Categories'
502
+ : CHECK_CATEGORIES[category].name;
503
+
504
+ if (!options.quiet) {
505
+ console.log(`Checking ${categoryName}...`);
506
+ console.log('');
507
+ }
508
+
509
+ if (filteredDiagnostics.length === 0) {
510
+ console.log('\x1b[32m✓\x1b[0m No issues found');
511
+ } else {
512
+ console.log(`\x1b[33m⚠\x1b[0m Found ${filteredDiagnostics.length} diagnostic(s):`);
513
+ console.log('');
514
+
515
+ // Group by severity
516
+ const errors = filteredDiagnostics.filter((d: any) => d.severity === 'error' || d.severity === 'fatal');
517
+ const warnings = filteredDiagnostics.filter((d: any) => d.severity === 'warning');
518
+ const infos = filteredDiagnostics.filter((d: any) => d.severity === 'info');
519
+
520
+ // Display errors first
521
+ if (errors.length > 0) {
522
+ console.log(`\x1b[31mErrors (${errors.length}):\x1b[0m`);
523
+ for (const diag of errors.slice(0, 10)) {
524
+ const location = diag.file ? `${diag.file}${diag.line ? `:${diag.line}` : ''}` : '';
525
+ console.log(` \x1b[31m•\x1b[0m [${diag.code}] ${diag.message}`);
526
+ if (location) {
527
+ console.log(` ${location}`);
528
+ }
529
+ if (diag.suggestion && !options.quiet) {
530
+ console.log(` Suggestion: ${diag.suggestion}`);
531
+ }
532
+ }
533
+ if (errors.length > 10) {
534
+ console.log(` ... and ${errors.length - 10} more errors`);
535
+ }
536
+ console.log('');
537
+ }
538
+
539
+ // Display warnings
540
+ if (warnings.length > 0) {
541
+ console.log(`\x1b[33mWarnings (${warnings.length}):\x1b[0m`);
542
+ for (const diag of warnings.slice(0, 10)) {
543
+ const location = diag.file ? `${diag.file}${diag.line ? `:${diag.line}` : ''}` : '';
544
+ console.log(` \x1b[33m•\x1b[0m [${diag.code}] ${diag.message}`);
545
+ if (location) {
546
+ console.log(` ${location}`);
547
+ }
548
+ if (diag.suggestion && !options.quiet) {
549
+ console.log(` Suggestion: ${diag.suggestion}`);
550
+ }
551
+ }
552
+ if (warnings.length > 10) {
553
+ console.log(` ... and ${warnings.length - 10} more warnings`);
554
+ }
555
+ console.log('');
556
+ }
557
+
558
+ // Display infos
559
+ if (infos.length > 0 && !options.quiet) {
560
+ console.log(`\x1b[36mInfo (${infos.length}):\x1b[0m`);
561
+ for (const diag of infos.slice(0, 5)) {
562
+ const location = diag.file ? `${diag.file}${diag.line ? `:${diag.line}` : ''}` : '';
563
+ console.log(` \x1b[36m•\x1b[0m [${diag.code}] ${diag.message}`);
564
+ if (location) {
565
+ console.log(` ${location}`);
566
+ }
567
+ }
568
+ if (infos.length > 5) {
569
+ console.log(` ... and ${infos.length - 5} more info messages`);
570
+ }
571
+ console.log('');
572
+ }
573
+ }
574
+
575
+ console.log('');
576
+ if (filteredDiagnostics.some((d: any) => d.severity === 'error' || d.severity === 'fatal')) {
577
+ process.exit(1);
578
+ }
579
+ }
580
+ }
@@ -18,6 +18,13 @@ export const coverageCommand = new Command('coverage')
18
18
  .option('-p, --project <path>', 'Project path', '.')
19
19
  .option('-j, --json', 'Output as JSON')
20
20
  .option('-v, --verbose', 'Show detailed file lists')
21
+ .addHelpText('after', `
22
+ Examples:
23
+ grafema coverage Show coverage summary
24
+ grafema coverage --verbose Show detailed file lists
25
+ grafema coverage --json Output coverage as JSON
26
+ grafema coverage -p ./app Coverage for specific project
27
+ `)
21
28
  .action(async (options: { project: string; json?: boolean; verbose?: boolean }) => {
22
29
  const projectPath = resolve(options.project);
23
30
  const grafemaDir = join(projectPath, '.grafema');