@grafema/cli 0.2.4-beta → 0.2.6-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 (139) hide show
  1. package/README.md +85 -0
  2. package/dist/cli.js +7 -2
  3. package/dist/cli.js.map +1 -0
  4. package/dist/commands/analyze.d.ts +3 -1
  5. package/dist/commands/analyze.d.ts.map +1 -1
  6. package/dist/commands/analyze.js +8 -266
  7. package/dist/commands/analyze.js.map +1 -0
  8. package/dist/commands/analyzeAction.d.ts +28 -0
  9. package/dist/commands/analyzeAction.d.ts.map +1 -0
  10. package/dist/commands/analyzeAction.js +243 -0
  11. package/dist/commands/analyzeAction.js.map +1 -0
  12. package/dist/commands/check.d.ts +2 -6
  13. package/dist/commands/check.d.ts.map +1 -1
  14. package/dist/commands/check.js +34 -48
  15. package/dist/commands/check.js.map +1 -0
  16. package/dist/commands/context.d.ts +16 -0
  17. package/dist/commands/context.d.ts.map +1 -0
  18. package/dist/commands/context.js +238 -0
  19. package/dist/commands/context.js.map +1 -0
  20. package/dist/commands/coverage.js +1 -0
  21. package/dist/commands/coverage.js.map +1 -0
  22. package/dist/commands/doctor/checks.d.ts.map +1 -1
  23. package/dist/commands/doctor/checks.js +10 -6
  24. package/dist/commands/doctor/checks.js.map +1 -0
  25. package/dist/commands/doctor/output.js +1 -0
  26. package/dist/commands/doctor/output.js.map +1 -0
  27. package/dist/commands/doctor/types.js +1 -0
  28. package/dist/commands/doctor/types.js.map +1 -0
  29. package/dist/commands/doctor.js +1 -0
  30. package/dist/commands/doctor.js.map +1 -0
  31. package/dist/commands/explain.d.ts.map +1 -1
  32. package/dist/commands/explain.js +5 -3
  33. package/dist/commands/explain.js.map +1 -0
  34. package/dist/commands/explore.d.ts.map +1 -1
  35. package/dist/commands/explore.js +9 -4
  36. package/dist/commands/explore.js.map +1 -0
  37. package/dist/commands/file.d.ts +15 -0
  38. package/dist/commands/file.d.ts.map +1 -0
  39. package/dist/commands/file.js +144 -0
  40. package/dist/commands/file.js.map +1 -0
  41. package/dist/commands/get.d.ts.map +1 -1
  42. package/dist/commands/get.js +7 -0
  43. package/dist/commands/get.js.map +1 -0
  44. package/dist/commands/impact.d.ts.map +1 -1
  45. package/dist/commands/impact.js +3 -3
  46. package/dist/commands/impact.js.map +1 -0
  47. package/dist/commands/init.d.ts.map +1 -1
  48. package/dist/commands/init.js +20 -2
  49. package/dist/commands/init.js.map +1 -0
  50. package/dist/commands/ls.d.ts.map +1 -1
  51. package/dist/commands/ls.js +10 -2
  52. package/dist/commands/ls.js.map +1 -0
  53. package/dist/commands/overview.d.ts.map +1 -1
  54. package/dist/commands/overview.js +1 -0
  55. package/dist/commands/overview.js.map +1 -0
  56. package/dist/commands/query.d.ts +8 -0
  57. package/dist/commands/query.d.ts.map +1 -1
  58. package/dist/commands/query.js +217 -43
  59. package/dist/commands/query.js.map +1 -0
  60. package/dist/commands/schema.d.ts.map +1 -1
  61. package/dist/commands/schema.js +4 -2
  62. package/dist/commands/schema.js.map +1 -0
  63. package/dist/commands/server.d.ts +2 -1
  64. package/dist/commands/server.d.ts.map +1 -1
  65. package/dist/commands/server.js +76 -14
  66. package/dist/commands/server.js.map +1 -0
  67. package/dist/commands/setup-skill.d.ts +17 -0
  68. package/dist/commands/setup-skill.d.ts.map +1 -0
  69. package/dist/commands/setup-skill.js +131 -0
  70. package/dist/commands/setup-skill.js.map +1 -0
  71. package/dist/commands/stats.js +1 -0
  72. package/dist/commands/stats.js.map +1 -0
  73. package/dist/commands/trace.d.ts.map +1 -1
  74. package/dist/commands/trace.js +21 -10
  75. package/dist/commands/trace.js.map +1 -0
  76. package/dist/commands/types.js +1 -0
  77. package/dist/commands/types.js.map +1 -0
  78. package/dist/plugins/builtinPlugins.d.ts +10 -0
  79. package/dist/plugins/builtinPlugins.d.ts.map +1 -0
  80. package/dist/plugins/builtinPlugins.js +68 -0
  81. package/dist/plugins/builtinPlugins.js.map +1 -0
  82. package/dist/plugins/pluginLoader.d.ts +16 -0
  83. package/dist/plugins/pluginLoader.d.ts.map +1 -0
  84. package/dist/plugins/pluginLoader.js +101 -0
  85. package/dist/plugins/pluginLoader.js.map +1 -0
  86. package/dist/plugins/pluginResolver.js +38 -0
  87. package/dist/utils/codePreview.d.ts +1 -0
  88. package/dist/utils/codePreview.d.ts.map +1 -1
  89. package/dist/utils/codePreview.js +6 -3
  90. package/dist/utils/codePreview.js.map +1 -0
  91. package/dist/utils/errorFormatter.js +1 -0
  92. package/dist/utils/errorFormatter.js.map +1 -0
  93. package/dist/utils/formatNode.d.ts +1 -1
  94. package/dist/utils/formatNode.d.ts.map +1 -1
  95. package/dist/utils/formatNode.js +3 -2
  96. package/dist/utils/formatNode.js.map +1 -0
  97. package/dist/utils/pathUtils.d.ts +2 -0
  98. package/dist/utils/pathUtils.d.ts.map +1 -0
  99. package/dist/utils/pathUtils.js +9 -0
  100. package/dist/utils/pathUtils.js.map +1 -0
  101. package/dist/utils/progressRenderer.d.ts +119 -0
  102. package/dist/utils/progressRenderer.d.ts.map +1 -0
  103. package/dist/utils/progressRenderer.js +245 -0
  104. package/dist/utils/progressRenderer.js.map +1 -0
  105. package/dist/utils/spinner.d.ts +39 -0
  106. package/dist/utils/spinner.d.ts.map +1 -0
  107. package/dist/utils/spinner.js +84 -0
  108. package/dist/utils/spinner.js.map +1 -0
  109. package/package.json +8 -9
  110. package/skills/grafema-codebase-analysis/SKILL.md +295 -0
  111. package/skills/grafema-codebase-analysis/references/node-edge-types.md +123 -0
  112. package/skills/grafema-codebase-analysis/references/query-patterns.md +205 -0
  113. package/src/cli.ts +8 -2
  114. package/src/commands/analyze.ts +7 -342
  115. package/src/commands/analyzeAction.ts +284 -0
  116. package/src/commands/check.ts +38 -70
  117. package/src/commands/context.ts +309 -0
  118. package/src/commands/doctor/checks.ts +9 -6
  119. package/src/commands/explain.ts +4 -3
  120. package/src/commands/explore.tsx +15 -9
  121. package/src/commands/file.ts +179 -0
  122. package/src/commands/get.ts +8 -0
  123. package/src/commands/impact.ts +3 -4
  124. package/src/commands/init.ts +19 -3
  125. package/src/commands/ls.ts +11 -2
  126. package/src/commands/overview.ts +0 -4
  127. package/src/commands/query.ts +235 -44
  128. package/src/commands/schema.ts +3 -2
  129. package/src/commands/server.ts +85 -15
  130. package/src/commands/setup-skill.ts +162 -0
  131. package/src/commands/trace.ts +18 -9
  132. package/src/plugins/builtinPlugins.ts +108 -0
  133. package/src/plugins/pluginLoader.ts +123 -0
  134. package/src/plugins/pluginResolver.js +38 -0
  135. package/src/utils/codePreview.ts +7 -3
  136. package/src/utils/formatNode.ts +3 -3
  137. package/src/utils/pathUtils.ts +9 -0
  138. package/src/utils/progressRenderer.ts +288 -0
  139. package/src/utils/spinner.ts +94 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Analyze command action — connects to RFDB, loads plugins, runs Orchestrator.
3
+ *
4
+ * Extracted from analyze.ts (REG-435) to keep command definition separate
5
+ * from execution logic.
6
+ */
7
+
8
+ import { resolve, join } from 'path';
9
+ import { existsSync, mkdirSync } from 'fs';
10
+ import {
11
+ Orchestrator,
12
+ RFDBServerBackend,
13
+ DiagnosticReporter,
14
+ DiagnosticWriter,
15
+ createLogger,
16
+ loadConfig,
17
+ StrictModeFailure,
18
+ } from '@grafema/core';
19
+ import type { LogLevel, GraphBackend } from '@grafema/types';
20
+ import { ProgressRenderer } from '../utils/progressRenderer.js';
21
+ import { loadCustomPlugins, createPlugins } from '../plugins/pluginLoader.js';
22
+
23
+ export interface NodeEdgeCountBackend {
24
+ nodeCount: () => Promise<number>;
25
+ edgeCount: () => Promise<number>;
26
+ }
27
+
28
+ export async function fetchNodeEdgeCounts(backend: NodeEdgeCountBackend): Promise<{ nodeCount: number; edgeCount: number }> {
29
+ const [nodeCount, edgeCount] = await Promise.all([backend.nodeCount(), backend.edgeCount()]);
30
+ return { nodeCount, edgeCount };
31
+ }
32
+
33
+ export function exitWithCode(code: number, exitFn: (code: number) => void = process.exit): void {
34
+ exitFn(code);
35
+ }
36
+
37
+ /**
38
+ * Determine log level from CLI options.
39
+ * Priority: --log-level > --quiet > --verbose > default ('silent')
40
+ *
41
+ * By default, logs are silent to allow clean progress UI.
42
+ * Use --verbose to see detailed logs (disables interactive progress).
43
+ */
44
+ function getLogLevel(options: { quiet?: boolean; verbose?: boolean; logLevel?: string }): LogLevel {
45
+ if (options.logLevel) {
46
+ const validLevels: LogLevel[] = ['silent', 'errors', 'warnings', 'info', 'debug'];
47
+ if (validLevels.includes(options.logLevel as LogLevel)) {
48
+ return options.logLevel as LogLevel;
49
+ }
50
+ }
51
+ if (options.quiet) return 'silent';
52
+ if (options.verbose) return 'info'; // --verbose shows logs instead of progress UI
53
+ return 'silent'; // Default: silent logs, clean progress UI
54
+ }
55
+
56
+ export async function analyzeAction(path: string, options: { service?: string; entrypoint?: string; clear?: boolean; quiet?: boolean; verbose?: boolean; debug?: boolean; logLevel?: string; logFile?: string; strict?: boolean; autoStart?: boolean }): Promise<void> {
57
+ const projectPath = resolve(path);
58
+ const grafemaDir = join(projectPath, '.grafema');
59
+ const dbPath = join(grafemaDir, 'graph.rfdb');
60
+
61
+ if (!existsSync(grafemaDir)) {
62
+ mkdirSync(grafemaDir, { recursive: true });
63
+ }
64
+
65
+ // Two log levels for CLI output:
66
+ // - info: important results (shows unless --quiet)
67
+ // - debug: verbose details (shows only with --verbose)
68
+ const info = options.quiet ? () => {} : console.log;
69
+ const debug = options.verbose ? console.log : () => {};
70
+
71
+ // Create logger based on CLI flags
72
+ const logLevel = getLogLevel(options);
73
+ const logFile = options.logFile ? resolve(options.logFile) : undefined;
74
+ const logger = createLogger(logLevel, logFile ? { logFile } : undefined);
75
+
76
+ if (logFile) {
77
+ debug(`Log file: ${logFile}`);
78
+ }
79
+ debug(`Analyzing project: ${projectPath}`);
80
+
81
+ // Connect to RFDB server
82
+ // Default: require explicit `grafema server start`
83
+ // Use --auto-start for CI or backwards compatibility
84
+ // In normal mode (not verbose), suppress backend logs for clean progress UI
85
+ const backend = new RFDBServerBackend({
86
+ dbPath,
87
+ autoStart: options.autoStart ?? false,
88
+ silent: !options.verbose // Silent in normal mode (show progress), verbose shows logs
89
+ });
90
+
91
+ try {
92
+ await backend.connect();
93
+ } catch (err) {
94
+ if (!options.autoStart && err instanceof Error && err.message.includes('not running')) {
95
+ console.error('');
96
+ console.error('RFDB server is not running.');
97
+ console.error('');
98
+ console.error('Start the server first:');
99
+ console.error(' grafema server start');
100
+ console.error('');
101
+ console.error('Or use --auto-start flag:');
102
+ console.error(' grafema analyze --auto-start');
103
+ console.error('');
104
+ process.exit(1);
105
+ }
106
+ throw err;
107
+ }
108
+
109
+ if (options.clear) {
110
+ debug('Clearing existing database...');
111
+ await backend.clear();
112
+ }
113
+
114
+ const config = loadConfig(projectPath, logger);
115
+
116
+ // Extract services from config (REG-174)
117
+ if (config.services.length > 0) {
118
+ debug(`Loaded ${config.services.length} service(s) from config`);
119
+ for (const svc of config.services) {
120
+ const entry = svc.entryPoint ? ` (entry: ${svc.entryPoint})` : '';
121
+ debug(` - ${svc.name}: ${svc.path}${entry}`);
122
+ }
123
+ }
124
+
125
+ // Load custom plugins from .grafema/plugins/
126
+ const customPlugins = await loadCustomPlugins(projectPath, debug);
127
+ const plugins = createPlugins(config.plugins, customPlugins, options.verbose);
128
+
129
+ debug(`Loaded ${plugins.length} plugins`);
130
+
131
+ // Resolve strict mode: CLI flag overrides config
132
+ const strictMode = options.strict ?? config.strict ?? false;
133
+ if (strictMode) {
134
+ debug('Strict mode enabled - analysis will fail on unresolved references');
135
+ }
136
+
137
+ const startTime = Date.now();
138
+
139
+ // Create progress renderer for CLI output
140
+ // In quiet mode, use a no-op renderer (skip rendering)
141
+ // In verbose mode, use non-interactive (newlines per update)
142
+ // In normal mode, use interactive (spinner with line overwrite)
143
+ const renderer = options.quiet
144
+ ? null
145
+ : new ProgressRenderer({
146
+ isInteractive: !options.verbose && process.stdout.isTTY,
147
+ });
148
+
149
+ // Poll graph stats periodically to show node/edge counts in progress
150
+ let statsInterval: NodeJS.Timeout | null = null;
151
+ if (renderer && !options.quiet) {
152
+ statsInterval = setInterval(async () => {
153
+ try {
154
+ const stats = await fetchNodeEdgeCounts(backend);
155
+ renderer.setStats(stats.nodeCount, stats.edgeCount);
156
+ } catch {
157
+ // Ignore stats errors during analysis
158
+ }
159
+ }, 500); // Poll every 500ms
160
+ statsInterval.unref?.();
161
+ }
162
+
163
+ const orchestrator = new Orchestrator({
164
+ graph: backend as unknown as GraphBackend,
165
+ plugins,
166
+ serviceFilter: options.service || null,
167
+ entrypoint: options.entrypoint,
168
+ forceAnalysis: options.clear || false,
169
+ logger,
170
+ services: config.services.length > 0 ? config.services : undefined, // Pass config services (REG-174)
171
+ strictMode, // REG-330: Pass strict mode flag
172
+ onProgress: (progress) => {
173
+ renderer?.update(progress);
174
+ },
175
+ });
176
+
177
+ let exitCode = 0;
178
+
179
+ try {
180
+ await orchestrator.run(projectPath);
181
+ await backend.flush();
182
+
183
+ const elapsedSeconds = (Date.now() - startTime) / 1000;
184
+ const stats = await fetchNodeEdgeCounts(backend);
185
+
186
+ // Clear progress line in interactive mode, then show results
187
+ if (renderer && process.stdout.isTTY) {
188
+ process.stdout.write('\r\x1b[K'); // Clear line
189
+ }
190
+ info('');
191
+ info(renderer ? renderer.finish(elapsedSeconds) : `Analysis complete in ${elapsedSeconds.toFixed(2)}s`);
192
+ info(` Nodes: ${stats.nodeCount}`);
193
+ info(` Edges: ${stats.edgeCount}`);
194
+
195
+ // Get diagnostics and report summary
196
+ const diagnostics = orchestrator.getDiagnostics();
197
+ const reporter = new DiagnosticReporter(diagnostics);
198
+
199
+ // Print summary if there are any issues
200
+ if (diagnostics.count() > 0) {
201
+ info('');
202
+ info(reporter.categorizedSummary());
203
+
204
+ // In verbose mode, print full report
205
+ if (options.verbose) {
206
+ debug('');
207
+ debug(reporter.report({ format: 'text', includeSummary: false }));
208
+ }
209
+ }
210
+
211
+ // Always write diagnostics.log (required for `grafema check` command)
212
+ const writer = new DiagnosticWriter();
213
+ await writer.write(diagnostics, grafemaDir);
214
+ if (options.debug) {
215
+ debug(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
216
+ }
217
+
218
+ // Determine exit code based on severity
219
+ if (diagnostics.hasFatal()) {
220
+ exitCode = 1;
221
+ } else if (diagnostics.hasErrors()) {
222
+ exitCode = 2; // Completed with errors
223
+ } else {
224
+ exitCode = 0; // Success (maybe warnings)
225
+ }
226
+ } catch (e) {
227
+ const diagnostics = orchestrator.getDiagnostics();
228
+ const reporter = new DiagnosticReporter(diagnostics);
229
+
230
+ // Clear progress line in interactive mode
231
+ if (renderer && process.stdout.isTTY) {
232
+ process.stdout.write('\r\x1b[K');
233
+ }
234
+
235
+ // Check if this is a strict mode failure (REG-332: structured output)
236
+ if (e instanceof StrictModeFailure) {
237
+ // Format ONLY from diagnostics, not from error.message
238
+ console.error('');
239
+ console.error(`✗ Strict mode: ${e.count} unresolved reference(s) found during ENRICHMENT.`);
240
+ console.error('');
241
+ console.error(reporter.formatStrict(e.diagnostics, {
242
+ verbose: options.verbose,
243
+ suppressedCount: e.suppressedCount, // REG-332
244
+ }));
245
+ console.error('');
246
+ console.error('Run without --strict for graceful degradation, or fix the underlying issues.');
247
+ } else {
248
+ // Generic error handling (non-strict)
249
+ const error = e instanceof Error ? e : new Error(String(e));
250
+ console.error('');
251
+ console.error(`✗ Analysis failed: ${error.message}`);
252
+ console.error('');
253
+ console.error('→ Run with --debug for detailed diagnostics');
254
+
255
+ if (diagnostics.count() > 0) {
256
+ console.error('');
257
+ console.error(reporter.report({ format: 'text', includeSummary: true }));
258
+ }
259
+ }
260
+
261
+ // Write diagnostics.log in debug mode even on failure
262
+ if (options.debug) {
263
+ const writer = new DiagnosticWriter();
264
+ await writer.write(diagnostics, grafemaDir);
265
+ console.error(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
266
+ }
267
+
268
+ exitCode = 1;
269
+ } finally {
270
+ // Stop stats polling
271
+ if (statsInterval) {
272
+ clearInterval(statsInterval);
273
+ statsInterval = null;
274
+ }
275
+
276
+ if (backend.connected) {
277
+ await backend.close();
278
+ }
279
+
280
+ // Exit with appropriate code
281
+ // 0 = success, 1 = fatal, 2 = errors
282
+ exitWithCode(exitCode);
283
+ }
284
+ }
@@ -12,69 +12,34 @@ import { existsSync, readFileSync } from 'fs';
12
12
  import {
13
13
  RFDBServerBackend,
14
14
  GuaranteeManager,
15
- NodeCreationValidator,
16
15
  GraphFreshnessChecker,
17
- IncrementalReanalyzer
16
+ IncrementalReanalyzer,
17
+ DIAGNOSTIC_CATEGORIES,
18
18
  } from '@grafema/core';
19
- import type { GuaranteeGraph } from '@grafema/core';
20
- import type { GraphBackend } from '@grafema/types';
19
+ import type { GuaranteeGraph, DiagnosticCategoryKey } from '@grafema/core';
21
20
  import { exitWithError } from '../utils/errorFormatter.js';
22
21
 
23
- interface GuaranteeFile {
24
- guarantees: Array<{
25
- id: string;
26
- name: string;
27
- rule: string;
28
- severity?: 'error' | 'warning' | 'info';
29
- governs?: string[];
30
- }>;
31
- }
32
22
 
33
23
  // Available built-in validators
34
- const BUILT_IN_VALIDATORS: Record<string, { name: string; description: string }> = {
35
- 'node-creation': {
36
- name: 'NodeCreationValidator',
37
- description: 'Validates that all nodes are created through NodeFactory'
38
- }
24
+ // Add new validators here as they are implemented
25
+ const BUILT_IN_VALIDATORS: Record<string, { name: string; description: string; create: () => unknown }> = {
26
+ // Example:
27
+ // 'my-validator': {
28
+ // name: 'MyValidator',
29
+ // description: 'Validates something important',
30
+ // create: () => new MyValidator()
31
+ // }
39
32
  };
40
33
 
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
- };
34
+ // Re-export for backward compatibility (deprecated - import from @grafema/core instead)
35
+ export { DIAGNOSTIC_CATEGORIES as CHECK_CATEGORIES };
71
36
 
72
37
  export const checkCommand = new Command('check')
73
38
  .description('Check invariants/guarantees')
74
39
  .argument('[rule]', 'Specific rule ID to check (or "all" for all rules)')
75
40
  .option('-p, --project <path>', 'Project path', '.')
76
41
  .option('-f, --file <path>', 'Path to guarantees YAML file')
77
- .option('-g, --guarantee <name>', 'Run a built-in guarantee validator (e.g., node-creation)')
42
+ .option('-g, --guarantee <name>', 'Run a built-in guarantee validator')
78
43
  .option('-j, --json', 'Output results as JSON')
79
44
  .option('-q, --quiet', 'Only output failures')
80
45
  .option('--list-guarantees', 'List available built-in guarantees')
@@ -88,7 +53,7 @@ Examples:
88
53
  grafema check calls Check call resolution
89
54
  grafema check dataflow Check data flow integrity
90
55
  grafema check all Run all diagnostic categories
91
- grafema check --guarantee node-creation Run built-in validator
56
+ grafema check --guarantee <name> Run built-in validator
92
57
  grafema check --list-categories List available categories
93
58
  grafema check --list-guarantees List built-in guarantees
94
59
  grafema check --fail-on-stale CI mode: fail if graph is stale
@@ -113,7 +78,7 @@ Examples:
113
78
  if (options.listCategories) {
114
79
  console.log('Available diagnostic categories:');
115
80
  console.log('');
116
- for (const [key, category] of Object.entries(CHECK_CATEGORIES)) {
81
+ for (const [key, category] of Object.entries(DIAGNOSTIC_CATEGORIES)) {
117
82
  console.log(` ${key}`);
118
83
  console.log(` ${category.name}`);
119
84
  console.log(` ${category.description}`);
@@ -136,7 +101,7 @@ Examples:
136
101
  }
137
102
 
138
103
  // Check if rule argument is a category name
139
- if (rule && (rule in CHECK_CATEGORIES || rule === 'all')) {
104
+ if (rule && (rule in DIAGNOSTIC_CATEGORIES || rule === 'all')) {
140
105
  await runCategoryCheck(rule, options);
141
106
  return;
142
107
  }
@@ -172,7 +137,7 @@ Examples:
172
137
 
173
138
  // Check graph freshness
174
139
  const freshnessChecker = new GraphFreshnessChecker();
175
- const freshness = await freshnessChecker.checkFreshness(backend);
140
+ const freshness = await freshnessChecker.checkFreshness(backend, projectPath);
176
141
 
177
142
  if (!freshness.isFresh) {
178
143
  if (options.failOnStale) {
@@ -327,7 +292,7 @@ async function runBuiltInValidator(
327
292
 
328
293
  // Check graph freshness
329
294
  const freshnessChecker = new GraphFreshnessChecker();
330
- const freshness = await freshnessChecker.checkFreshness(backend);
295
+ const freshness = await freshnessChecker.checkFreshness(backend, resolvedPath);
331
296
 
332
297
  if (!freshness.isFresh) {
333
298
  if (options.failOnStale) {
@@ -366,27 +331,29 @@ async function runBuiltInValidator(
366
331
  }
367
332
 
368
333
  try {
369
- let validator;
370
- let validatorName: string;
371
-
372
- switch (guaranteeName) {
373
- case 'node-creation':
374
- validator = new NodeCreationValidator();
375
- validatorName = 'NodeCreationValidator';
376
- break;
377
- default:
334
+ const validatorInfo = BUILT_IN_VALIDATORS[guaranteeName];
335
+ if (!validatorInfo) {
336
+ const available = Object.keys(BUILT_IN_VALIDATORS);
337
+ if (available.length === 0) {
378
338
  exitWithError(`Unknown guarantee: ${guaranteeName}`, [
379
- 'Use --list-guarantees to see available options'
339
+ 'No built-in guarantees are currently available'
380
340
  ]);
341
+ }
342
+ exitWithError(`Unknown guarantee: ${guaranteeName}`, [
343
+ `Available: ${available.join(', ')}`
344
+ ]);
381
345
  }
382
346
 
347
+ const validator = validatorInfo.create() as { execute: (ctx: { graph: unknown; projectPath: string }) => Promise<{ metadata?: unknown }> };
348
+ const validatorName = validatorInfo.name;
349
+
383
350
  if (!options.quiet) {
384
351
  console.log(`Running ${validatorName}...`);
385
352
  console.log('');
386
353
  }
387
354
 
388
355
  const result = await validator.execute({
389
- graph: backend as unknown as GraphBackend,
356
+ graph: backend,
390
357
  projectPath: resolvedPath
391
358
  });
392
359
 
@@ -422,7 +389,7 @@ async function runBuiltInValidator(
422
389
  console.log('');
423
390
 
424
391
  for (const issue of issues.slice(0, 10)) {
425
- const location = issue.file ? `${issue.file}${issue.line ? `:${issue.line}` : ''}` : '';
392
+ const _location = issue.file ? `${issue.file}${issue.line ? `:${issue.line}` : ''}` : '';
426
393
  console.log(` \x1b[31m•\x1b[0m [${issue.type}] ${issue.message}`);
427
394
  if (issue.suggestion && !options.quiet) {
428
395
  console.log(` Suggestion: ${issue.suggestion}`);
@@ -470,7 +437,7 @@ async function runCategoryCheck(
470
437
  .map(line => {
471
438
  try {
472
439
  return JSON.parse(line);
473
- } catch (e) {
440
+ } catch {
474
441
  return null;
475
442
  }
476
443
  })
@@ -479,12 +446,13 @@ async function runCategoryCheck(
479
446
  // Filter diagnostics by category codes
480
447
  let filteredDiagnostics = allDiagnostics;
481
448
  if (category !== 'all') {
482
- const categoryInfo = CHECK_CATEGORIES[category];
483
- if (!categoryInfo) {
449
+ if (!(category in DIAGNOSTIC_CATEGORIES)) {
484
450
  exitWithError(`Unknown category: ${category}`, [
485
451
  'Use --list-categories to see available options'
486
452
  ]);
487
453
  }
454
+ const categoryKey = category as DiagnosticCategoryKey;
455
+ const categoryInfo = DIAGNOSTIC_CATEGORIES[categoryKey];
488
456
  filteredDiagnostics = allDiagnostics.filter((d: any) =>
489
457
  categoryInfo.codes.includes(d.code)
490
458
  );
@@ -499,7 +467,7 @@ async function runCategoryCheck(
499
467
  } else {
500
468
  const categoryName = category === 'all'
501
469
  ? 'All Categories'
502
- : CHECK_CATEGORIES[category].name;
470
+ : DIAGNOSTIC_CATEGORIES[category as DiagnosticCategoryKey].name;
503
471
 
504
472
  if (!options.quiet) {
505
473
  console.log(`Checking ${categoryName}...`);