@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.
- package/dist/cli.js +10 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +28 -7
- package/dist/commands/check.d.ts +6 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +177 -1
- package/dist/commands/coverage.d.ts.map +1 -1
- package/dist/commands/coverage.js +7 -0
- package/dist/commands/doctor/checks.d.ts +55 -0
- package/dist/commands/doctor/checks.d.ts.map +1 -0
- package/dist/commands/doctor/checks.js +534 -0
- package/dist/commands/doctor/output.d.ts +20 -0
- package/dist/commands/doctor/output.d.ts.map +1 -0
- package/dist/commands/doctor/output.js +94 -0
- package/dist/commands/doctor/types.d.ts +42 -0
- package/dist/commands/doctor/types.d.ts.map +1 -0
- package/dist/commands/doctor/types.js +4 -0
- package/dist/commands/doctor.d.ts +17 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +80 -0
- package/dist/commands/explain.d.ts +16 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +145 -0
- package/dist/commands/explore.d.ts +7 -1
- package/dist/commands/explore.d.ts.map +1 -1
- package/dist/commands/explore.js +204 -85
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +16 -4
- package/dist/commands/impact.d.ts.map +1 -1
- package/dist/commands/impact.js +48 -50
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +93 -15
- package/dist/commands/ls.d.ts +14 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +132 -0
- package/dist/commands/overview.d.ts.map +1 -1
- package/dist/commands/overview.js +15 -2
- package/dist/commands/query.d.ts +98 -0
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +549 -136
- package/dist/commands/schema.d.ts +13 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +279 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +13 -6
- package/dist/commands/stats.d.ts.map +1 -1
- package/dist/commands/stats.js +7 -0
- package/dist/commands/trace.d.ts +73 -0
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +500 -5
- package/dist/commands/types.d.ts +12 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +79 -0
- package/dist/utils/formatNode.d.ts +13 -0
- package/dist/utils/formatNode.d.ts.map +1 -1
- package/dist/utils/formatNode.js +35 -2
- package/package.json +3 -3
- package/src/cli.ts +10 -0
- package/src/commands/analyze.ts +31 -5
- package/src/commands/check.ts +201 -0
- package/src/commands/coverage.ts +7 -0
- package/src/commands/doctor/checks.ts +612 -0
- package/src/commands/doctor/output.ts +115 -0
- package/src/commands/doctor/types.ts +45 -0
- package/src/commands/doctor.ts +106 -0
- package/src/commands/explain.ts +173 -0
- package/src/commands/explore.tsx +247 -97
- package/src/commands/get.ts +20 -6
- package/src/commands/impact.ts +55 -61
- package/src/commands/init.ts +101 -14
- package/src/commands/ls.ts +166 -0
- package/src/commands/overview.ts +15 -2
- package/src/commands/query.ts +643 -149
- package/src/commands/schema.ts +345 -0
- package/src/commands/server.ts +13 -6
- package/src/commands/stats.ts +7 -0
- package/src/commands/trace.ts +647 -6
- package/src/commands/types.ts +94 -0
- 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;
|
|
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"}
|
package/dist/utils/formatNode.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
41
|
-
"@grafema/types": "0.
|
|
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();
|
package/src/commands/analyze.ts
CHANGED
|
@@ -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
|
-
.
|
|
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.
|
|
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
|
-
//
|
|
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
|
|
package/src/commands/check.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/commands/coverage.ts
CHANGED
|
@@ -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');
|