@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.
- package/README.md +85 -0
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +3 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +8 -266
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyzeAction.d.ts +28 -0
- package/dist/commands/analyzeAction.d.ts.map +1 -0
- package/dist/commands/analyzeAction.js +243 -0
- package/dist/commands/analyzeAction.js.map +1 -0
- package/dist/commands/check.d.ts +2 -6
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +34 -48
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/context.d.ts +16 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +238 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/coverage.js +1 -0
- package/dist/commands/coverage.js.map +1 -0
- package/dist/commands/doctor/checks.d.ts.map +1 -1
- package/dist/commands/doctor/checks.js +10 -6
- package/dist/commands/doctor/checks.js.map +1 -0
- package/dist/commands/doctor/output.js +1 -0
- package/dist/commands/doctor/output.js.map +1 -0
- package/dist/commands/doctor/types.js +1 -0
- package/dist/commands/doctor/types.js.map +1 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +5 -3
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/explore.d.ts.map +1 -1
- package/dist/commands/explore.js +9 -4
- package/dist/commands/explore.js.map +1 -0
- package/dist/commands/file.d.ts +15 -0
- package/dist/commands/file.d.ts.map +1 -0
- package/dist/commands/file.js +144 -0
- package/dist/commands/file.js.map +1 -0
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +7 -0
- package/dist/commands/get.js.map +1 -0
- package/dist/commands/impact.d.ts.map +1 -1
- package/dist/commands/impact.js +3 -3
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +20 -2
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +10 -2
- package/dist/commands/ls.js.map +1 -0
- package/dist/commands/overview.d.ts.map +1 -1
- package/dist/commands/overview.js +1 -0
- package/dist/commands/overview.js.map +1 -0
- package/dist/commands/query.d.ts +8 -0
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +217 -43
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +4 -2
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/server.d.ts +2 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +76 -14
- package/dist/commands/server.js.map +1 -0
- package/dist/commands/setup-skill.d.ts +17 -0
- package/dist/commands/setup-skill.d.ts.map +1 -0
- package/dist/commands/setup-skill.js +131 -0
- package/dist/commands/setup-skill.js.map +1 -0
- package/dist/commands/stats.js +1 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +21 -10
- package/dist/commands/trace.js.map +1 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/plugins/builtinPlugins.d.ts +10 -0
- package/dist/plugins/builtinPlugins.d.ts.map +1 -0
- package/dist/plugins/builtinPlugins.js +68 -0
- package/dist/plugins/builtinPlugins.js.map +1 -0
- package/dist/plugins/pluginLoader.d.ts +16 -0
- package/dist/plugins/pluginLoader.d.ts.map +1 -0
- package/dist/plugins/pluginLoader.js +101 -0
- package/dist/plugins/pluginLoader.js.map +1 -0
- package/dist/plugins/pluginResolver.js +38 -0
- package/dist/utils/codePreview.d.ts +1 -0
- package/dist/utils/codePreview.d.ts.map +1 -1
- package/dist/utils/codePreview.js +6 -3
- package/dist/utils/codePreview.js.map +1 -0
- package/dist/utils/errorFormatter.js +1 -0
- package/dist/utils/errorFormatter.js.map +1 -0
- package/dist/utils/formatNode.d.ts +1 -1
- package/dist/utils/formatNode.d.ts.map +1 -1
- package/dist/utils/formatNode.js +3 -2
- package/dist/utils/formatNode.js.map +1 -0
- package/dist/utils/pathUtils.d.ts +2 -0
- package/dist/utils/pathUtils.d.ts.map +1 -0
- package/dist/utils/pathUtils.js +9 -0
- package/dist/utils/pathUtils.js.map +1 -0
- package/dist/utils/progressRenderer.d.ts +119 -0
- package/dist/utils/progressRenderer.d.ts.map +1 -0
- package/dist/utils/progressRenderer.js +245 -0
- package/dist/utils/progressRenderer.js.map +1 -0
- package/dist/utils/spinner.d.ts +39 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +84 -0
- package/dist/utils/spinner.js.map +1 -0
- package/package.json +8 -9
- package/skills/grafema-codebase-analysis/SKILL.md +295 -0
- package/skills/grafema-codebase-analysis/references/node-edge-types.md +123 -0
- package/skills/grafema-codebase-analysis/references/query-patterns.md +205 -0
- package/src/cli.ts +8 -2
- package/src/commands/analyze.ts +7 -342
- package/src/commands/analyzeAction.ts +284 -0
- package/src/commands/check.ts +38 -70
- package/src/commands/context.ts +309 -0
- package/src/commands/doctor/checks.ts +9 -6
- package/src/commands/explain.ts +4 -3
- package/src/commands/explore.tsx +15 -9
- package/src/commands/file.ts +179 -0
- package/src/commands/get.ts +8 -0
- package/src/commands/impact.ts +3 -4
- package/src/commands/init.ts +19 -3
- package/src/commands/ls.ts +11 -2
- package/src/commands/overview.ts +0 -4
- package/src/commands/query.ts +235 -44
- package/src/commands/schema.ts +3 -2
- package/src/commands/server.ts +85 -15
- package/src/commands/setup-skill.ts +162 -0
- package/src/commands/trace.ts +18 -9
- package/src/plugins/builtinPlugins.ts +108 -0
- package/src/plugins/pluginLoader.ts +123 -0
- package/src/plugins/pluginResolver.js +38 -0
- package/src/utils/codePreview.ts +7 -3
- package/src/utils/formatNode.ts +3 -3
- package/src/utils/pathUtils.ts +9 -0
- package/src/utils/progressRenderer.ts +288 -0
- 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
|
+
}
|
package/src/commands/check.ts
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
//
|
|
42
|
-
export
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
'
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
:
|
|
470
|
+
: DIAGNOSTIC_CATEGORIES[category as DiagnosticCategoryKey].name;
|
|
503
471
|
|
|
504
472
|
if (!options.quiet) {
|
|
505
473
|
console.log(`Checking ${categoryName}...`);
|