@grafema/cli 0.2.12-beta → 0.3.1-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +2 -4
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/analyzeAction.d.ts +5 -3
- package/dist/commands/analyzeAction.d.ts.map +1 -1
- package/dist/commands/analyzeAction.js +109 -151
- package/dist/commands/analyzeAction.js.map +1 -1
- package/dist/commands/check.d.ts +1 -1
- package/dist/commands/check.js +4 -4
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/context.js +2 -2
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/coverage.js +2 -2
- package/dist/commands/coverage.js.map +1 -1
- package/dist/commands/describe.d.ts +13 -0
- package/dist/commands/describe.d.ts.map +1 -0
- package/dist/commands/describe.js +131 -0
- package/dist/commands/describe.js.map +1 -0
- package/dist/commands/doctor/checks.d.ts +6 -1
- package/dist/commands/doctor/checks.d.ts.map +1 -1
- package/dist/commands/doctor/checks.js +128 -13
- package/dist/commands/doctor/checks.js.map +1 -1
- package/dist/commands/doctor.d.ts +10 -9
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +12 -10
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain.js +2 -2
- package/dist/commands/explain.js.map +1 -1
- package/dist/commands/file.js +2 -2
- package/dist/commands/file.js.map +1 -1
- package/dist/commands/get.js +2 -2
- package/dist/commands/get.js.map +1 -1
- package/dist/commands/git-ingest.d.ts +6 -0
- package/dist/commands/git-ingest.d.ts.map +1 -0
- package/dist/commands/git-ingest.js +46 -0
- package/dist/commands/git-ingest.js.map +1 -0
- package/dist/commands/impact.d.ts.map +1 -1
- package/dist/commands/impact.js +276 -50
- package/dist/commands/impact.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +20 -22
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ls.js +2 -2
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/overview.js +2 -2
- package/dist/commands/overview.js.map +1 -1
- package/dist/commands/query.d.ts +1 -1
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +169 -7
- package/dist/commands/query.js.map +1 -1
- package/dist/commands/schema.js +2 -2
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/server.js +11 -6
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/stats.js +2 -2
- package/dist/commands/stats.js.map +1 -1
- package/dist/commands/tldr.d.ts +12 -0
- package/dist/commands/tldr.d.ts.map +1 -0
- package/dist/commands/tldr.js +81 -0
- package/dist/commands/tldr.js.map +1 -0
- package/dist/commands/trace.d.ts +1 -1
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +17 -133
- package/dist/commands/trace.js.map +1 -1
- package/dist/commands/types.js +2 -2
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/who.d.ts +12 -0
- package/dist/commands/who.d.ts.map +1 -0
- package/dist/commands/who.js +184 -0
- package/dist/commands/who.js.map +1 -0
- package/dist/commands/why.d.ts +12 -0
- package/dist/commands/why.d.ts.map +1 -0
- package/dist/commands/why.js +118 -0
- package/dist/commands/why.js.map +1 -0
- package/dist/commands/wtf.d.ts +12 -0
- package/dist/commands/wtf.d.ts.map +1 -0
- package/dist/commands/wtf.js +117 -0
- package/dist/commands/wtf.js.map +1 -0
- package/dist/plugins/builtinPlugins.d.ts +1 -9
- package/dist/plugins/builtinPlugins.d.ts.map +1 -1
- package/dist/plugins/builtinPlugins.js +2 -67
- package/dist/plugins/builtinPlugins.js.map +1 -1
- package/dist/plugins/pluginLoader.d.ts +1 -15
- package/dist/plugins/pluginLoader.d.ts.map +1 -1
- package/dist/plugins/pluginLoader.js +2 -100
- package/dist/plugins/pluginLoader.js.map +1 -1
- package/dist/plugins/pluginResolver.js +3 -3
- package/dist/utils/progressRenderer.d.ts +15 -1
- package/dist/utils/progressRenderer.d.ts.map +1 -1
- package/dist/utils/progressRenderer.js.map +1 -1
- package/dist/utils/queryHints.d.ts +6 -0
- package/dist/utils/queryHints.d.ts.map +1 -0
- package/dist/utils/queryHints.js +36 -0
- package/dist/utils/queryHints.js.map +1 -0
- package/package.json +4 -4
- package/skills/grafema-codebase-analysis/SKILL.md +1 -1
- package/src/cli.ts +14 -0
- package/src/commands/analyze.ts +2 -4
- package/src/commands/analyzeAction.ts +122 -168
- package/src/commands/check.ts +5 -5
- package/src/commands/context.ts +3 -3
- package/src/commands/coverage.ts +2 -2
- package/src/commands/describe.ts +160 -0
- package/src/commands/doctor/checks.ts +153 -10
- package/src/commands/doctor.ts +13 -9
- package/src/commands/explain.ts +2 -2
- package/src/commands/explore.tsx +2 -2
- package/src/commands/file.ts +3 -3
- package/src/commands/get.ts +2 -2
- package/src/commands/git-ingest.ts +49 -0
- package/src/commands/impact.ts +318 -55
- package/src/commands/init.ts +20 -22
- package/src/commands/ls.ts +2 -2
- package/src/commands/overview.ts +2 -2
- package/src/commands/query.ts +197 -7
- package/src/commands/schema.ts +2 -2
- package/src/commands/server.ts +12 -6
- package/src/commands/stats.ts +2 -2
- package/src/commands/tldr.ts +103 -0
- package/src/commands/trace.ts +19 -161
- package/src/commands/types.ts +2 -2
- package/src/commands/who.ts +215 -0
- package/src/commands/why.ts +134 -0
- package/src/commands/wtf.ts +140 -0
- package/src/plugins/builtinPlugins.ts +1 -108
- package/src/plugins/pluginLoader.ts +1 -123
- package/src/plugins/pluginResolver.js +3 -3
- package/src/utils/progressRenderer.ts +15 -1
- package/src/utils/queryHints.ts +46 -0
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Analyze command action —
|
|
2
|
+
* Analyze command action — spawns grafema-orchestrator for project analysis.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The Rust grafema-orchestrator binary handles the full analysis pipeline:
|
|
5
|
+
* discovery, parsing (OXC), analysis (grafema-analyzer), resolution,
|
|
6
|
+
* and RFDB ingestion. This action finds the binary, spawns it with
|
|
7
|
+
* the correct args, streams output, and prints a summary.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { resolve, join } from 'path';
|
|
9
11
|
import { existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { spawn } from 'child_process';
|
|
10
13
|
import {
|
|
11
|
-
Orchestrator,
|
|
12
14
|
RFDBServerBackend,
|
|
13
|
-
DiagnosticReporter,
|
|
14
|
-
DiagnosticWriter,
|
|
15
15
|
createLogger,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import {
|
|
16
|
+
findOrchestratorBinary,
|
|
17
|
+
getBinaryNotFoundMessage,
|
|
18
|
+
findAnalyzerBinary,
|
|
19
|
+
ensureBinary,
|
|
20
|
+
} from '@grafema/util';
|
|
21
|
+
import type { LogLevel } from '@grafema/util';
|
|
22
22
|
|
|
23
23
|
export interface NodeEdgeCountBackend {
|
|
24
24
|
nodeCount: () => Promise<number>;
|
|
@@ -53,6 +53,28 @@ function getLogLevel(options: { quiet?: boolean; verbose?: boolean; logLevel?: s
|
|
|
53
53
|
return 'silent'; // Default: silent logs, clean progress UI
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Find the grafema.config.yaml config file for the orchestrator.
|
|
58
|
+
*
|
|
59
|
+
* Search order:
|
|
60
|
+
* 1. <projectPath>/grafema.config.yaml
|
|
61
|
+
* 2. <projectPath>/.grafema/config.yaml (legacy location)
|
|
62
|
+
*/
|
|
63
|
+
function findConfigFile(projectPath: string): string | null {
|
|
64
|
+
const candidates = [
|
|
65
|
+
join(projectPath, 'grafema.config.yaml'),
|
|
66
|
+
join(projectPath, '.grafema', 'config.yaml'),
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const candidate of candidates) {
|
|
70
|
+
if (existsSync(candidate)) {
|
|
71
|
+
return candidate;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
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
79
|
const projectPath = resolve(path);
|
|
58
80
|
const grafemaDir = join(projectPath, '.grafema');
|
|
@@ -71,35 +93,74 @@ export async function analyzeAction(path: string, options: { service?: string; e
|
|
|
71
93
|
// Create logger based on CLI flags
|
|
72
94
|
const logLevel = getLogLevel(options);
|
|
73
95
|
const logFile = options.logFile ? resolve(options.logFile) : undefined;
|
|
74
|
-
const
|
|
96
|
+
const _logger = createLogger(logLevel, logFile ? { logFile } : undefined);
|
|
75
97
|
|
|
76
98
|
if (logFile) {
|
|
77
99
|
debug(`Log file: ${logFile}`);
|
|
78
100
|
}
|
|
79
101
|
debug(`Analyzing project: ${projectPath}`);
|
|
80
102
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
// Find grafema-orchestrator binary
|
|
104
|
+
const orchestratorBinary = findOrchestratorBinary();
|
|
105
|
+
if (!orchestratorBinary) {
|
|
106
|
+
console.error('');
|
|
107
|
+
console.error(getBinaryNotFoundMessage('grafema-orchestrator'));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
debug(`Using orchestrator: ${orchestratorBinary}`);
|
|
112
|
+
|
|
113
|
+
// Ensure JS/TS analyzer binaries exist (lazy download if missing)
|
|
114
|
+
for (const binName of ['grafema-analyzer', 'grafema-resolve']) {
|
|
115
|
+
const existing = findAnalyzerBinary(binName);
|
|
116
|
+
if (!existing) {
|
|
117
|
+
const downloaded = await ensureBinary(binName, null, info);
|
|
118
|
+
if (downloaded) {
|
|
119
|
+
debug(`Downloaded ${binName} → ${downloaded}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Find config file for the orchestrator
|
|
125
|
+
const configPath = findConfigFile(projectPath);
|
|
126
|
+
if (!configPath) {
|
|
127
|
+
console.error('');
|
|
128
|
+
console.error('No grafema config file found.');
|
|
129
|
+
console.error('');
|
|
130
|
+
console.error('Expected one of:');
|
|
131
|
+
console.error(` ${join(projectPath, 'grafema.config.yaml')}`);
|
|
132
|
+
console.error(` ${join(projectPath, '.grafema', 'config.yaml')}`);
|
|
133
|
+
console.error('');
|
|
134
|
+
console.error('Create a config file with at least:');
|
|
135
|
+
console.error(' root: "."');
|
|
136
|
+
console.error(' include:');
|
|
137
|
+
console.error(' - "src/**/*.js"');
|
|
138
|
+
console.error('');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
debug(`Using config: ${configPath}`);
|
|
143
|
+
|
|
144
|
+
// Connect to RFDB server — auto-start by default (zero-config UX)
|
|
85
145
|
const backend = new RFDBServerBackend({
|
|
86
146
|
dbPath,
|
|
87
|
-
autoStart: options.autoStart ??
|
|
88
|
-
silent: !options.verbose
|
|
147
|
+
autoStart: options.autoStart ?? true,
|
|
148
|
+
silent: !options.verbose,
|
|
149
|
+
clientName: 'cli'
|
|
89
150
|
});
|
|
90
151
|
|
|
91
152
|
try {
|
|
92
153
|
await backend.connect();
|
|
93
154
|
} catch (err) {
|
|
94
|
-
if (
|
|
155
|
+
if (err instanceof Error && err.message.includes('not running')) {
|
|
95
156
|
console.error('');
|
|
96
|
-
console.error('RFDB server
|
|
157
|
+
console.error('RFDB server failed to start.');
|
|
97
158
|
console.error('');
|
|
98
|
-
console.error('
|
|
159
|
+
console.error('Try starting manually:');
|
|
99
160
|
console.error(' grafema server start');
|
|
100
161
|
console.error('');
|
|
101
|
-
console.error('Or
|
|
102
|
-
console.error(' grafema
|
|
162
|
+
console.error('Or run diagnostics:');
|
|
163
|
+
console.error(' grafema doctor');
|
|
103
164
|
console.error('');
|
|
104
165
|
process.exit(1);
|
|
105
166
|
}
|
|
@@ -111,174 +172,67 @@ export async function analyzeAction(path: string, options: { service?: string; e
|
|
|
111
172
|
await backend.clear();
|
|
112
173
|
}
|
|
113
174
|
|
|
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
175
|
const startTime = Date.now();
|
|
138
176
|
|
|
139
|
-
//
|
|
140
|
-
|
|
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
|
-
});
|
|
177
|
+
// Build orchestrator args
|
|
178
|
+
const args: string[] = ['analyze', '--config', configPath, '--socket', backend.socketPath];
|
|
148
179
|
|
|
149
|
-
|
|
150
|
-
|
|
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?.();
|
|
180
|
+
if (options.clear) {
|
|
181
|
+
args.push('--force');
|
|
161
182
|
}
|
|
162
183
|
|
|
163
|
-
|
|
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
|
-
});
|
|
184
|
+
debug(`Spawning: ${orchestratorBinary} ${args.join(' ')}`);
|
|
176
185
|
|
|
177
186
|
let exitCode = 0;
|
|
178
187
|
|
|
179
188
|
try {
|
|
180
|
-
|
|
181
|
-
await
|
|
189
|
+
// Spawn grafema-orchestrator
|
|
190
|
+
exitCode = await new Promise<number>((resolvePromise, reject) => {
|
|
191
|
+
const child = spawn(orchestratorBinary, args, {
|
|
192
|
+
stdio: [
|
|
193
|
+
'ignore',
|
|
194
|
+
options.quiet ? 'ignore' : 'inherit',
|
|
195
|
+
options.quiet ? 'ignore' : 'inherit',
|
|
196
|
+
],
|
|
197
|
+
env: {
|
|
198
|
+
...process.env,
|
|
199
|
+
// Pass RUST_LOG for tracing verbosity
|
|
200
|
+
RUST_LOG: options.verbose ? 'info' : (options.debug ? 'debug' : 'warn'),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
182
203
|
|
|
183
|
-
|
|
184
|
-
|
|
204
|
+
child.on('error', (err) => {
|
|
205
|
+
reject(new Error(`Failed to spawn grafema-orchestrator: ${err.message}`));
|
|
206
|
+
});
|
|
185
207
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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}`);
|
|
208
|
+
child.on('close', (code) => {
|
|
209
|
+
resolvePromise(code ?? 1);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
194
212
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
213
|
+
if (exitCode === 0) {
|
|
214
|
+
const elapsedSeconds = (Date.now() - startTime) / 1000;
|
|
215
|
+
const stats = await fetchNodeEdgeCounts(backend);
|
|
198
216
|
|
|
199
|
-
// Print summary if there are any issues
|
|
200
|
-
if (diagnostics.count() > 0) {
|
|
201
217
|
info('');
|
|
202
|
-
info(
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
218
|
+
info(`Analysis complete in ${elapsedSeconds.toFixed(2)}s`);
|
|
219
|
+
info(` Nodes: ${stats.nodeCount}`);
|
|
220
|
+
info(` Edges: ${stats.edgeCount}`);
|
|
223
221
|
} 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
222
|
console.error('');
|
|
241
|
-
console.error(
|
|
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)}`);
|
|
223
|
+
console.error(`Analysis failed with exit code ${exitCode}`);
|
|
266
224
|
}
|
|
267
|
-
|
|
225
|
+
} catch (e) {
|
|
226
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
227
|
+
console.error('');
|
|
228
|
+
console.error(`Analysis failed: ${error.message}`);
|
|
268
229
|
exitCode = 1;
|
|
269
230
|
} finally {
|
|
270
|
-
// Stop stats polling
|
|
271
|
-
if (statsInterval) {
|
|
272
|
-
clearInterval(statsInterval);
|
|
273
|
-
statsInterval = null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
231
|
if (backend.connected) {
|
|
277
232
|
await backend.close();
|
|
278
233
|
}
|
|
279
234
|
|
|
280
235
|
// Exit with appropriate code
|
|
281
|
-
// 0 = success, 1 = fatal, 2 = errors
|
|
282
236
|
exitWithCode(exitCode);
|
|
283
237
|
}
|
|
284
238
|
}
|
package/src/commands/check.ts
CHANGED
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
GraphFreshnessChecker,
|
|
16
16
|
IncrementalReanalyzer,
|
|
17
17
|
DIAGNOSTIC_CATEGORIES,
|
|
18
|
-
} from '@grafema/
|
|
19
|
-
import type { GuaranteeGraph, DiagnosticCategoryKey } from '@grafema/
|
|
18
|
+
} from '@grafema/util';
|
|
19
|
+
import type { GuaranteeGraph, DiagnosticCategoryKey } from '@grafema/util';
|
|
20
20
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
21
21
|
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ const BUILT_IN_VALIDATORS: Record<string, { name: string; description: string; c
|
|
|
31
31
|
// }
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
// Re-export for backward compatibility (deprecated - import from @grafema/
|
|
34
|
+
// Re-export for backward compatibility (deprecated - import from @grafema/util instead)
|
|
35
35
|
export { DIAGNOSTIC_CATEGORIES as CHECK_CATEGORIES };
|
|
36
36
|
|
|
37
37
|
export const checkCommand = new Command('check')
|
|
@@ -132,7 +132,7 @@ Examples:
|
|
|
132
132
|
exitWithError('No graph database found', ['Run: grafema analyze']);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
const backend = new RFDBServerBackend({ dbPath });
|
|
135
|
+
const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
|
|
136
136
|
await backend.connect();
|
|
137
137
|
|
|
138
138
|
// Check graph freshness
|
|
@@ -287,7 +287,7 @@ async function runBuiltInValidator(
|
|
|
287
287
|
exitWithError('No graph database found', ['Run: grafema analyze']);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
const backend = new RFDBServerBackend({ dbPath });
|
|
290
|
+
const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
|
|
291
291
|
await backend.connect();
|
|
292
292
|
|
|
293
293
|
// Check graph freshness
|
package/src/commands/context.ts
CHANGED
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
getNodeDisplayName,
|
|
22
22
|
formatEdgeMetadata,
|
|
23
23
|
STRUCTURAL_EDGE_TYPES,
|
|
24
|
-
} from '@grafema/
|
|
25
|
-
import type { NodeContext, EdgeGroup } from '@grafema/
|
|
24
|
+
} from '@grafema/util';
|
|
25
|
+
import type { NodeContext, EdgeGroup } from '@grafema/util';
|
|
26
26
|
import type { BaseNodeRecord } from '@grafema/types';
|
|
27
27
|
import { getCodePreview, formatCodePreview } from '../utils/codePreview.js';
|
|
28
28
|
import { formatLocation } from '../utils/formatNode.js';
|
|
@@ -79,7 +79,7 @@ Examples:
|
|
|
79
79
|
exitWithError('No graph database found', ['Run: grafema analyze']);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const backend = new RFDBServerBackend({ dbPath });
|
|
82
|
+
const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
|
|
83
83
|
await backend.connect();
|
|
84
84
|
|
|
85
85
|
const spinner = new Spinner('Loading context...');
|
package/src/commands/coverage.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import { resolve, join } from 'path';
|
|
12
12
|
import { existsSync } from 'fs';
|
|
13
|
-
import { RFDBServerBackend, CoverageAnalyzer, type CoverageResult } from '@grafema/
|
|
13
|
+
import { RFDBServerBackend, CoverageAnalyzer, type CoverageResult } from '@grafema/util';
|
|
14
14
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
15
15
|
|
|
16
16
|
export const coverageCommand = new Command('coverage')
|
|
@@ -34,7 +34,7 @@ Examples:
|
|
|
34
34
|
exitWithError('No graph database found', ['Run: grafema analyze']);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const backend = new RFDBServerBackend({ dbPath });
|
|
37
|
+
const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
|
|
38
38
|
await backend.connect();
|
|
39
39
|
|
|
40
40
|
try {
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describe command — Render compact DSL notation for a graph node
|
|
3
|
+
*
|
|
4
|
+
* Resolves target by: semantic ID → file path (MODULE) → node name.
|
|
5
|
+
* Calls extractSubgraph() + renderNotation() from @grafema/util.
|
|
6
|
+
*
|
|
7
|
+
* Output is DSL notation using archetype-grouped operators:
|
|
8
|
+
* o- dependency, > call/flow, < read/input, => write,
|
|
9
|
+
* >x exception, ~>> event, ?| guard, |= governance
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Command } from 'commander';
|
|
13
|
+
import { resolve, join } from 'path';
|
|
14
|
+
import { existsSync } from 'fs';
|
|
15
|
+
import {
|
|
16
|
+
RFDBServerBackend,
|
|
17
|
+
renderNotation,
|
|
18
|
+
extractSubgraph,
|
|
19
|
+
PERSPECTIVES,
|
|
20
|
+
} from '@grafema/util';
|
|
21
|
+
import type { DescribeOptions } from '@grafema/util';
|
|
22
|
+
import { exitWithError } from '../utils/errorFormatter.js';
|
|
23
|
+
import { Spinner } from '../utils/spinner.js';
|
|
24
|
+
|
|
25
|
+
interface DescribeCommandOptions {
|
|
26
|
+
project: string;
|
|
27
|
+
depth: string;
|
|
28
|
+
perspective?: string;
|
|
29
|
+
budget?: string;
|
|
30
|
+
json?: boolean;
|
|
31
|
+
locations?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const describeCommand = new Command('describe')
|
|
35
|
+
.description('Render compact DSL notation for a graph node')
|
|
36
|
+
.argument('<target>', 'Semantic ID, file path, or node name')
|
|
37
|
+
.option('-p, --project <path>', 'Project path', '.')
|
|
38
|
+
.option('-d, --depth <level>', 'LOD: 0=names, 1=edges, 2=nested+fold, 3=nested (exact)', '1')
|
|
39
|
+
.option(
|
|
40
|
+
'--perspective <name>',
|
|
41
|
+
`Perspective preset: ${Object.keys(PERSPECTIVES).join(', ')}`,
|
|
42
|
+
)
|
|
43
|
+
.option('-b, --budget <n>', 'Max items before summarization (default 7)')
|
|
44
|
+
.option('-j, --json', 'Output as JSON for scripting')
|
|
45
|
+
.option('-l, --locations', 'Include file:line locations')
|
|
46
|
+
.addHelpText('after', `
|
|
47
|
+
Perspectives:
|
|
48
|
+
security writes + exceptions
|
|
49
|
+
data flow out/in + writes
|
|
50
|
+
errors exceptions only
|
|
51
|
+
api flow out + publishes + depends
|
|
52
|
+
events publishes only
|
|
53
|
+
|
|
54
|
+
LOD levels:
|
|
55
|
+
0 Node names only (minimal)
|
|
56
|
+
1 Node + edges (default)
|
|
57
|
+
2 Node + edges + nested children (folded)
|
|
58
|
+
3 Node + edges + nested children (exact, no folding)
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
grafema describe "src/app.ts->FUNCTION->main"
|
|
62
|
+
grafema describe src/app.ts
|
|
63
|
+
grafema describe handleRequest
|
|
64
|
+
grafema describe handleRequest --perspective security
|
|
65
|
+
grafema describe MyClass -d 2 --locations
|
|
66
|
+
grafema describe handleRequest --json
|
|
67
|
+
`)
|
|
68
|
+
.action(async (target: string, options: DescribeCommandOptions) => {
|
|
69
|
+
const projectPath = resolve(options.project);
|
|
70
|
+
const grafemaDir = join(projectPath, '.grafema');
|
|
71
|
+
const dbPath = join(grafemaDir, 'graph.rfdb');
|
|
72
|
+
|
|
73
|
+
if (!existsSync(dbPath)) {
|
|
74
|
+
exitWithError('No graph database found', ['Run: grafema analyze']);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const depth = parseInt(options.depth, 10);
|
|
78
|
+
if (isNaN(depth) || depth < 0 || depth > 3) {
|
|
79
|
+
exitWithError('Invalid depth', ['Use 0, 1, 2, or 3']);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (options.perspective && !PERSPECTIVES[options.perspective]) {
|
|
83
|
+
exitWithError(`Unknown perspective: "${options.perspective}"`, [
|
|
84
|
+
`Available: ${Object.keys(PERSPECTIVES).join(', ')}`,
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
|
|
89
|
+
await backend.connect();
|
|
90
|
+
|
|
91
|
+
const spinner = new Spinner('Resolving target...');
|
|
92
|
+
spinner.start();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Step 1: Resolve target → node
|
|
96
|
+
let node = await backend.getNode(target);
|
|
97
|
+
|
|
98
|
+
if (!node) {
|
|
99
|
+
for await (const n of backend.queryNodes({ file: target, type: 'MODULE' })) {
|
|
100
|
+
node = n;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!node) {
|
|
105
|
+
for await (const n of backend.queryNodes({ name: target })) {
|
|
106
|
+
node = n;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!node) {
|
|
112
|
+
spinner.stop();
|
|
113
|
+
exitWithError(`Target not found: "${target}"`, [
|
|
114
|
+
'Use: grafema query "<name>" to find available nodes',
|
|
115
|
+
'Try: semantic ID, file path, or node name',
|
|
116
|
+
]);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Step 2: Extract subgraph
|
|
121
|
+
const subgraph = await extractSubgraph(backend, node.id, depth);
|
|
122
|
+
|
|
123
|
+
// Step 3: Build options
|
|
124
|
+
const describeOptions: DescribeOptions = {
|
|
125
|
+
depth,
|
|
126
|
+
includeLocations: options.locations ?? depth >= 2,
|
|
127
|
+
};
|
|
128
|
+
if (options.perspective && PERSPECTIVES[options.perspective]) {
|
|
129
|
+
describeOptions.archetypeFilter = PERSPECTIVES[options.perspective];
|
|
130
|
+
}
|
|
131
|
+
if (options.budget) {
|
|
132
|
+
describeOptions.budget = parseInt(options.budget, 10);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Step 4: Render
|
|
136
|
+
const notation = renderNotation(subgraph, describeOptions);
|
|
137
|
+
|
|
138
|
+
spinner.stop();
|
|
139
|
+
|
|
140
|
+
if (options.json) {
|
|
141
|
+
console.log(JSON.stringify({
|
|
142
|
+
target: node.id,
|
|
143
|
+
dsl: notation || `[${node.type}] ${node.name ?? node.id}\nNo relationships found at depth=${depth}.`,
|
|
144
|
+
subgraph: {
|
|
145
|
+
rootNodes: subgraph.rootNodes.length,
|
|
146
|
+
edges: subgraph.edges.length,
|
|
147
|
+
nodes: subgraph.nodeMap.size,
|
|
148
|
+
},
|
|
149
|
+
}, null, 2));
|
|
150
|
+
} else if (notation.trim()) {
|
|
151
|
+
console.log(notation);
|
|
152
|
+
} else {
|
|
153
|
+
console.log(`[${node.type}] ${node.name ?? node.id}`);
|
|
154
|
+
console.log(`No relationships found at depth=${depth}.`);
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
spinner.stop();
|
|
158
|
+
await backend.close();
|
|
159
|
+
}
|
|
160
|
+
});
|