@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
@@ -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.0-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();
@@ -24,6 +24,7 @@ import {
24
24
  // Analysis
25
25
  JSASTAnalyzer,
26
26
  ExpressRouteAnalyzer,
27
+ ExpressResponseAnalyzer,
27
28
  SocketIOAnalyzer,
28
29
  DatabaseAnalyzer,
29
30
  FetchAnalyzer,
@@ -32,12 +33,14 @@ import {
32
33
  RustAnalyzer,
33
34
  // Enrichment
34
35
  MethodCallResolver,
36
+ ArgumentParameterLinker,
35
37
  AliasTracker,
36
38
  ValueDomainAnalyzer,
37
39
  MountPointResolver,
38
40
  PrefixEvaluator,
39
41
  InstanceOfResolver,
40
42
  ImportExportLinker,
43
+ FunctionCallResolver,
41
44
  HTTPConnectionEnricher,
42
45
  RustFFIEnricher,
43
46
  // Validation
@@ -48,6 +51,7 @@ import {
48
51
  GraphConnectivityValidator,
49
52
  DataFlowValidator,
50
53
  TypeScriptDeadCodeValidator,
54
+ BrokenImportValidator,
51
55
  } from '@grafema/core';
52
56
  import type { LogLevel } from '@grafema/types';
53
57
 
@@ -62,6 +66,7 @@ const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
62
66
  // Analysis
63
67
  JSASTAnalyzer: () => new JSASTAnalyzer() as Plugin,
64
68
  ExpressRouteAnalyzer: () => new ExpressRouteAnalyzer() as Plugin,
69
+ ExpressResponseAnalyzer: () => new ExpressResponseAnalyzer() as Plugin,
65
70
  SocketIOAnalyzer: () => new SocketIOAnalyzer() as Plugin,
66
71
  DatabaseAnalyzer: () => new DatabaseAnalyzer() as Plugin,
67
72
  FetchAnalyzer: () => new FetchAnalyzer() as Plugin,
@@ -70,12 +75,14 @@ const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
70
75
  RustAnalyzer: () => new RustAnalyzer() as Plugin,
71
76
  // Enrichment
72
77
  MethodCallResolver: () => new MethodCallResolver() as Plugin,
78
+ ArgumentParameterLinker: () => new ArgumentParameterLinker() as Plugin,
73
79
  AliasTracker: () => new AliasTracker() as Plugin,
74
80
  ValueDomainAnalyzer: () => new ValueDomainAnalyzer() as Plugin,
75
81
  MountPointResolver: () => new MountPointResolver() as Plugin,
76
82
  PrefixEvaluator: () => new PrefixEvaluator() as Plugin,
77
83
  InstanceOfResolver: () => new InstanceOfResolver() as Plugin,
78
84
  ImportExportLinker: () => new ImportExportLinker() as Plugin,
85
+ FunctionCallResolver: () => new FunctionCallResolver() as Plugin,
79
86
  HTTPConnectionEnricher: () => new HTTPConnectionEnricher() as Plugin,
80
87
  RustFFIEnricher: () => new RustFFIEnricher() as Plugin,
81
88
  // Validation
@@ -86,6 +93,7 @@ const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
86
93
  GraphConnectivityValidator: () => new GraphConnectivityValidator() as Plugin,
87
94
  DataFlowValidator: () => new DataFlowValidator() as Plugin,
88
95
  TypeScriptDeadCodeValidator: () => new TypeScriptDeadCodeValidator() as Plugin,
96
+ BrokenImportValidator: () => new BrokenImportValidator() as Plugin,
89
97
  };
90
98
 
91
99
  function createPlugins(config: GrafemaConfig['plugins']): Plugin[] {
@@ -133,7 +141,18 @@ export const analyzeCommand = new Command('analyze')
133
141
  .option('-v, --verbose', 'Show verbose logging')
134
142
  .option('--debug', 'Enable debug mode (writes diagnostics.log)')
135
143
  .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 }) => {
144
+ .option('--strict', 'Enable strict mode (fail on unresolved references)')
145
+ .addHelpText('after', `
146
+ Examples:
147
+ grafema analyze Analyze current project
148
+ grafema analyze ./my-project Analyze specific directory
149
+ grafema analyze --clear Clear database and rebuild from scratch
150
+ grafema analyze -s api Analyze only "api" service (monorepo)
151
+ grafema analyze -v Verbose output with progress details
152
+ grafema analyze --debug Write diagnostics.log for debugging
153
+ grafema analyze --strict Fail on unresolved references (debugging)
154
+ `)
155
+ .action(async (path: string, options: { service?: string; entrypoint?: string; clear?: boolean; quiet?: boolean; verbose?: boolean; debug?: boolean; logLevel?: string; strict?: boolean }) => {
137
156
  const projectPath = resolve(path);
138
157
  const grafemaDir = join(projectPath, '.grafema');
139
158
  const dbPath = join(grafemaDir, 'graph.rfdb');
@@ -173,6 +192,12 @@ export const analyzeCommand = new Command('analyze')
173
192
 
174
193
  log(`Loaded ${plugins.length} plugins`);
175
194
 
195
+ // Resolve strict mode: CLI flag overrides config
196
+ const strictMode = options.strict ?? config.strict ?? false;
197
+ if (strictMode) {
198
+ log('Strict mode enabled - analysis will fail on unresolved references');
199
+ }
200
+
176
201
  const startTime = Date.now();
177
202
 
178
203
  const orchestrator = new Orchestrator({
@@ -183,6 +208,7 @@ export const analyzeCommand = new Command('analyze')
183
208
  forceAnalysis: options.clear || false,
184
209
  logger,
185
210
  services: config.services.length > 0 ? config.services : undefined, // Pass config services (REG-174)
211
+ strictMode, // REG-330: Pass strict mode flag
186
212
  onProgress: (progress) => {
187
213
  if (options.verbose) {
188
214
  log(`[${progress.phase}] ${progress.message}`);
@@ -211,7 +237,7 @@ export const analyzeCommand = new Command('analyze')
211
237
  // Print summary if there are any issues
212
238
  if (diagnostics.count() > 0) {
213
239
  log('');
214
- log(reporter.summary());
240
+ log(reporter.categorizedSummary());
215
241
 
216
242
  // In verbose mode, print full report
217
243
  if (options.verbose) {
@@ -220,10 +246,10 @@ export const analyzeCommand = new Command('analyze')
220
246
  }
221
247
  }
222
248
 
223
- // Write diagnostics.log in debug mode
249
+ // Always write diagnostics.log (required for `grafema check` command)
250
+ const writer = new DiagnosticWriter();
251
+ await writer.write(diagnostics, grafemaDir);
224
252
  if (options.debug) {
225
- const writer = new DiagnosticWriter();
226
- await writer.write(diagnostics, grafemaDir);
227
253
  log(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
228
254
  }
229
255
 
@@ -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');