@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
package/src/commands/schema.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Command } from 'commander';
|
|
13
|
-
import { resolve, join
|
|
13
|
+
import { resolve, join } from 'path';
|
|
14
|
+
import { toRelativeDisplay } from '../utils/pathUtils.js';
|
|
14
15
|
import { existsSync, writeFileSync } from 'fs';
|
|
15
16
|
import {
|
|
16
17
|
RFDBServerBackend,
|
|
@@ -82,7 +83,7 @@ function formatInterfaceYaml(schema: InterfaceSchema): string {
|
|
|
82
83
|
|
|
83
84
|
function formatInterfaceMarkdown(schema: InterfaceSchema, projectPath: string): string {
|
|
84
85
|
const lines: string[] = [];
|
|
85
|
-
const relPath =
|
|
86
|
+
const relPath = toRelativeDisplay(schema.source.file, projectPath);
|
|
86
87
|
|
|
87
88
|
lines.push(`# Interface: ${schema.name}`);
|
|
88
89
|
lines.push('');
|
package/src/commands/server.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server command - Manage RFDB server lifecycle
|
|
2
|
+
* Server command - Manage RFDB (Rega Flow Database) server lifecycle
|
|
3
3
|
*
|
|
4
4
|
* Provides explicit control over the RFDB server process:
|
|
5
5
|
* grafema server start - Start detached server
|
|
6
6
|
* grafema server stop - Stop server gracefully
|
|
7
7
|
* grafema server status - Check if server is running
|
|
8
|
+
* grafema server graphql - Start GraphQL API server
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import { Command } from 'commander';
|
|
@@ -12,7 +13,7 @@ import { resolve, join } from 'path';
|
|
|
12
13
|
import { existsSync, unlinkSync, writeFileSync, readFileSync } from 'fs';
|
|
13
14
|
import { spawn } from 'child_process';
|
|
14
15
|
import { setTimeout as sleep } from 'timers/promises';
|
|
15
|
-
import { RFDBClient, loadConfig,
|
|
16
|
+
import { RFDBClient, loadConfig, RFDBServerBackend, findRfdbBinary } from '@grafema/core';
|
|
16
17
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
17
18
|
|
|
18
19
|
// Extend config type for server settings
|
|
@@ -20,6 +21,18 @@ interface ServerConfig {
|
|
|
20
21
|
binaryPath?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Find RFDB server binary using shared utility.
|
|
26
|
+
* Wraps findRfdbBinary() with CLI-specific error logging.
|
|
27
|
+
*/
|
|
28
|
+
function findServerBinary(explicitPath?: string): string | null {
|
|
29
|
+
const binaryPath = findRfdbBinary({ explicitPath });
|
|
30
|
+
if (!binaryPath && explicitPath) {
|
|
31
|
+
console.error(`Specified binary not found: ${explicitPath}`);
|
|
32
|
+
}
|
|
33
|
+
return binaryPath;
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* Check if server is running by attempting to ping it
|
|
25
38
|
*/
|
|
@@ -56,7 +69,7 @@ function getProjectPaths(projectPath: string) {
|
|
|
56
69
|
|
|
57
70
|
// Create main server command with subcommands
|
|
58
71
|
export const serverCommand = new Command('server')
|
|
59
|
-
.description('Manage RFDB server lifecycle')
|
|
72
|
+
.description('Manage RFDB (Rega Flow Database) server lifecycle')
|
|
60
73
|
.addHelpText('after', `
|
|
61
74
|
Examples:
|
|
62
75
|
grafema server start Start the RFDB server
|
|
@@ -104,34 +117,38 @@ serverCommand
|
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
// Determine binary path: CLI flag > config > auto-detect
|
|
107
|
-
let
|
|
120
|
+
let binaryPath: string | null = null;
|
|
108
121
|
|
|
109
122
|
if (options.binary) {
|
|
110
123
|
// Explicit --binary flag
|
|
111
|
-
|
|
124
|
+
binaryPath = findServerBinary(options.binary);
|
|
112
125
|
} else {
|
|
113
126
|
// Try to read from config
|
|
114
127
|
try {
|
|
115
128
|
const config = loadConfig(projectPath);
|
|
116
129
|
const serverConfig = (config as unknown as { server?: ServerConfig }).server;
|
|
117
130
|
if (serverConfig?.binaryPath) {
|
|
118
|
-
|
|
131
|
+
binaryPath = findServerBinary(serverConfig.binaryPath);
|
|
119
132
|
}
|
|
120
133
|
} catch {
|
|
121
134
|
// Config not found or invalid - continue with auto-detect
|
|
122
135
|
}
|
|
123
|
-
}
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
// Auto-detect if not specified
|
|
138
|
+
if (!binaryPath) {
|
|
139
|
+
binaryPath = findServerBinary();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
126
142
|
|
|
127
143
|
if (!binaryPath) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
exitWithError('RFDB server binary not found', [
|
|
145
|
+
'Specify path: grafema server start --binary /path/to/rfdb-server',
|
|
146
|
+
'Or add to config.yaml:',
|
|
147
|
+
' server:',
|
|
148
|
+
' binaryPath: /path/to/rfdb-server',
|
|
149
|
+
'Or install: npm install @grafema/rfdb',
|
|
150
|
+
'Or build: cargo build --release && cp target/release/rfdb-server ~/.local/bin/'
|
|
151
|
+
]);
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
console.log(`Starting RFDB server...`);
|
|
@@ -323,3 +340,56 @@ serverCommand
|
|
|
323
340
|
}
|
|
324
341
|
}
|
|
325
342
|
});
|
|
343
|
+
|
|
344
|
+
// grafema server graphql
|
|
345
|
+
serverCommand
|
|
346
|
+
.command('graphql')
|
|
347
|
+
.description('Start GraphQL API server (requires RFDB server running)')
|
|
348
|
+
.option('-p, --project <path>', 'Project path', '.')
|
|
349
|
+
.option('--port <number>', 'Port to listen on', '4000')
|
|
350
|
+
.option('--host <string>', 'Hostname to bind to', 'localhost')
|
|
351
|
+
.action(async (options: { project: string; port: string; host: string }) => {
|
|
352
|
+
const projectPath = resolve(options.project);
|
|
353
|
+
const { socketPath } = getProjectPaths(projectPath);
|
|
354
|
+
|
|
355
|
+
// Check if RFDB server is running
|
|
356
|
+
const status = await isServerRunning(socketPath);
|
|
357
|
+
if (!status.running) {
|
|
358
|
+
exitWithError('RFDB server not running', [
|
|
359
|
+
'Start the server first: grafema server start',
|
|
360
|
+
'Or run: grafema analyze (starts server automatically)'
|
|
361
|
+
]);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Create backend connection
|
|
365
|
+
const backend = new RFDBServerBackend({ socketPath });
|
|
366
|
+
await backend.connect();
|
|
367
|
+
|
|
368
|
+
// Import and start GraphQL server
|
|
369
|
+
const { startServer } = await import('@grafema/api');
|
|
370
|
+
const port = parseInt(options.port, 10);
|
|
371
|
+
|
|
372
|
+
console.log('Starting Grafema GraphQL API...');
|
|
373
|
+
console.log(` RFDB Socket: ${socketPath}`);
|
|
374
|
+
if (status.version) {
|
|
375
|
+
console.log(` RFDB Version: ${status.version}`);
|
|
376
|
+
}
|
|
377
|
+
console.log('');
|
|
378
|
+
|
|
379
|
+
const server = startServer({
|
|
380
|
+
backend,
|
|
381
|
+
port,
|
|
382
|
+
hostname: options.host,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Handle shutdown
|
|
386
|
+
const shutdown = async () => {
|
|
387
|
+
console.log('\nShutting down GraphQL server...');
|
|
388
|
+
server.close();
|
|
389
|
+
await backend.close();
|
|
390
|
+
process.exit(0);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
process.on('SIGINT', shutdown);
|
|
394
|
+
process.on('SIGTERM', shutdown);
|
|
395
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup-skill command - Install Grafema Agent Skill into a project
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { resolve, join } from 'path';
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, cpSync } from 'fs';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
11
|
+
|
|
12
|
+
/** Default install paths per platform */
|
|
13
|
+
const PLATFORM_PATHS: Record<string, string> = {
|
|
14
|
+
claude: '.claude/skills',
|
|
15
|
+
gemini: '.gemini/skills',
|
|
16
|
+
cursor: '.cursor/skills',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const SKILL_DIR_NAME = 'grafema-codebase-analysis';
|
|
20
|
+
|
|
21
|
+
interface SetupSkillOptions {
|
|
22
|
+
outputDir?: string;
|
|
23
|
+
platform?: string;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the bundled skill source directory.
|
|
29
|
+
* In the published package, skills/ is at the package root alongside dist/.
|
|
30
|
+
*/
|
|
31
|
+
function getSkillSourceDir(): string {
|
|
32
|
+
// __dirname is dist/commands/ -> go up to package root, then into skills/
|
|
33
|
+
return join(__dirname, '..', '..', 'skills', SKILL_DIR_NAME);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read version from skill metadata.
|
|
38
|
+
*/
|
|
39
|
+
function getSkillVersion(skillDir: string): string | null {
|
|
40
|
+
const skillMd = join(skillDir, 'SKILL.md');
|
|
41
|
+
if (!existsSync(skillMd)) return null;
|
|
42
|
+
|
|
43
|
+
const content = readFileSync(skillMd, 'utf-8');
|
|
44
|
+
const versionMatch = content.match(/version:\s*"?([^"\n]+)"?/);
|
|
45
|
+
return versionMatch ? versionMatch[1].trim() : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the target directory for skill installation.
|
|
50
|
+
*/
|
|
51
|
+
function resolveTargetDir(projectPath: string, options: SetupSkillOptions): string {
|
|
52
|
+
if (options.outputDir) {
|
|
53
|
+
return resolve(options.outputDir, SKILL_DIR_NAME);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const platform = options.platform || 'claude';
|
|
57
|
+
const basePath = PLATFORM_PATHS[platform];
|
|
58
|
+
if (!basePath) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Unknown platform: ${platform}. Supported: ${Object.keys(PLATFORM_PATHS).join(', ')}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return join(projectPath, basePath, SKILL_DIR_NAME);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Copy skill directory recursively.
|
|
69
|
+
*/
|
|
70
|
+
function copySkill(sourceDir: string, targetDir: string): void {
|
|
71
|
+
mkdirSync(targetDir, { recursive: true });
|
|
72
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Install the Grafema Agent Skill into a project directory.
|
|
77
|
+
* Returns true if skill was installed, false if skipped.
|
|
78
|
+
*/
|
|
79
|
+
export function installSkill(projectPath: string, options: SetupSkillOptions = {}): boolean {
|
|
80
|
+
const sourceDir = getSkillSourceDir();
|
|
81
|
+
|
|
82
|
+
if (!existsSync(sourceDir)) {
|
|
83
|
+
throw new Error(`Skill source not found at ${sourceDir}. Package may be corrupted.`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const targetDir = resolveTargetDir(projectPath, options);
|
|
87
|
+
|
|
88
|
+
// Check if already installed
|
|
89
|
+
if (existsSync(targetDir) && !options.force) {
|
|
90
|
+
const installedVersion = getSkillVersion(targetDir);
|
|
91
|
+
const sourceVersion = getSkillVersion(sourceDir);
|
|
92
|
+
|
|
93
|
+
if (installedVersion === sourceVersion) {
|
|
94
|
+
return false; // Same version, skip
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Different version — warn but don't overwrite without --force
|
|
98
|
+
console.log(` Skill exists (v${installedVersion}), latest is v${sourceVersion}`);
|
|
99
|
+
console.log(' Use --force to update, or run: grafema setup-skill --force');
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
copySkill(sourceDir, targetDir);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const setupSkillCommand = new Command('setup-skill')
|
|
108
|
+
.description('Install Grafema Agent Skill into your project')
|
|
109
|
+
.argument('[path]', 'Project path', '.')
|
|
110
|
+
.option('--output-dir <path>', 'Custom output directory (overrides --platform)')
|
|
111
|
+
.option('--platform <name>', 'Target platform: claude, gemini, cursor', 'claude')
|
|
112
|
+
.option('-f, --force', 'Overwrite existing skill')
|
|
113
|
+
.addHelpText('after', `
|
|
114
|
+
Examples:
|
|
115
|
+
grafema setup-skill Install for Claude Code (.claude/skills/)
|
|
116
|
+
grafema setup-skill --platform gemini Install for Gemini CLI (.gemini/skills/)
|
|
117
|
+
grafema setup-skill --force Update existing skill
|
|
118
|
+
grafema setup-skill --output-dir ./my-skills/
|
|
119
|
+
`)
|
|
120
|
+
.action(async (path: string, options: SetupSkillOptions) => {
|
|
121
|
+
const projectPath = resolve(path);
|
|
122
|
+
const sourceDir = getSkillSourceDir();
|
|
123
|
+
|
|
124
|
+
if (!existsSync(sourceDir)) {
|
|
125
|
+
console.error('✗ Skill source not found. Package may be corrupted.');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const targetDir = resolveTargetDir(projectPath, options);
|
|
130
|
+
|
|
131
|
+
// Check if already installed
|
|
132
|
+
if (existsSync(targetDir) && !options.force) {
|
|
133
|
+
const installedVersion = getSkillVersion(targetDir);
|
|
134
|
+
const sourceVersion = getSkillVersion(sourceDir);
|
|
135
|
+
|
|
136
|
+
if (installedVersion === sourceVersion) {
|
|
137
|
+
console.log(`✓ Grafema skill already installed (v${installedVersion})`);
|
|
138
|
+
console.log(` Location: ${targetDir}`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(` Skill exists (v${installedVersion}), latest is v${sourceVersion}`);
|
|
143
|
+
console.log(' Use --force to update');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
copySkill(sourceDir, targetDir);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error('✗ Failed to install skill:', (err as Error).message);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const sourceVersion = getSkillVersion(sourceDir);
|
|
155
|
+
console.log(`✓ Grafema skill installed (v${sourceVersion})`);
|
|
156
|
+
console.log(` Location: ${targetDir}`);
|
|
157
|
+
console.log('');
|
|
158
|
+
console.log('Next steps:');
|
|
159
|
+
console.log(' 1. Ensure Grafema MCP server is configured in your AI agent');
|
|
160
|
+
console.log(' 2. Run "grafema analyze" to build the code graph');
|
|
161
|
+
console.log(' 3. Your AI agent will now prefer graph queries over reading files');
|
|
162
|
+
});
|
package/src/commands/trace.ts
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Command } from 'commander';
|
|
11
|
-
import { resolve, join } from 'path';
|
|
11
|
+
import { isAbsolute, resolve, join } from 'path';
|
|
12
12
|
import { existsSync } from 'fs';
|
|
13
|
-
import { RFDBServerBackend, parseSemanticId, traceValues, type ValueSource } from '@grafema/core';
|
|
13
|
+
import { RFDBServerBackend, parseSemanticId, parseSemanticIdV2, traceValues, type ValueSource } from '@grafema/core';
|
|
14
14
|
import { formatNodeDisplay, formatNodeInline } from '../utils/formatNode.js';
|
|
15
15
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
16
16
|
|
|
@@ -227,12 +227,21 @@ async function findVariables(
|
|
|
227
227
|
if (name.toLowerCase() === varName.toLowerCase()) {
|
|
228
228
|
// If scope specified, check if variable is in that scope
|
|
229
229
|
if (scopeName) {
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
// Try v2 parsing first
|
|
231
|
+
const parsedV2 = parseSemanticIdV2(node.id);
|
|
232
|
+
if (parsedV2) {
|
|
233
|
+
if (!parsedV2.namedParent || parsedV2.namedParent.toLowerCase() !== lowerScopeName) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// Fallback to v1 parsing
|
|
238
|
+
const parsed = parseSemanticId(node.id);
|
|
239
|
+
if (!parsed) continue; // Skip nodes with invalid IDs
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
241
|
+
// Check if scopeName appears anywhere in the scope chain
|
|
242
|
+
if (!parsed.scopePath.some(s => s.toLowerCase() === lowerScopeName)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
236
245
|
}
|
|
237
246
|
}
|
|
238
247
|
|
|
@@ -738,7 +747,7 @@ async function handleSinkTrace(
|
|
|
738
747
|
const sourcesCount = pv.sources.length;
|
|
739
748
|
console.log(` - ${JSON.stringify(pv.value)} (${sourcesCount} source${sourcesCount === 1 ? '' : 's'})`);
|
|
740
749
|
for (const src of pv.sources.slice(0, 3)) {
|
|
741
|
-
const relativePath = src.file
|
|
750
|
+
const relativePath = isAbsolute(src.file)
|
|
742
751
|
? src.file.substring(projectPath.length + 1)
|
|
743
752
|
: src.file;
|
|
744
753
|
console.log(` <- ${relativePath}:${src.line}`);
|
|
@@ -900,7 +909,7 @@ async function handleRouteTrace(
|
|
|
900
909
|
// Format traced values
|
|
901
910
|
const sources = await Promise.all(
|
|
902
911
|
traced.map(async (t) => {
|
|
903
|
-
const relativePath = t.source.file
|
|
912
|
+
const relativePath = isAbsolute(t.source.file)
|
|
904
913
|
? t.source.file.substring(projectPath.length + 1)
|
|
905
914
|
: t.source.file;
|
|
906
915
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in plugin registry — maps plugin names to factory functions.
|
|
3
|
+
*
|
|
4
|
+
* Each entry creates a fresh plugin instance. Plugin names match the class names
|
|
5
|
+
* and are referenced by name in .grafema/config.yaml under phases:
|
|
6
|
+
* discovery, indexing, analysis, enrichment, validation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Plugin } from '@grafema/core';
|
|
10
|
+
import {
|
|
11
|
+
// Discovery
|
|
12
|
+
SimpleProjectDiscovery,
|
|
13
|
+
MonorepoServiceDiscovery,
|
|
14
|
+
WorkspaceDiscovery,
|
|
15
|
+
// Indexing
|
|
16
|
+
JSModuleIndexer,
|
|
17
|
+
RustModuleIndexer,
|
|
18
|
+
// Analysis
|
|
19
|
+
JSASTAnalyzer,
|
|
20
|
+
ExpressRouteAnalyzer,
|
|
21
|
+
ExpressResponseAnalyzer,
|
|
22
|
+
NestJSRouteAnalyzer,
|
|
23
|
+
SocketIOAnalyzer,
|
|
24
|
+
DatabaseAnalyzer,
|
|
25
|
+
FetchAnalyzer,
|
|
26
|
+
ServiceLayerAnalyzer,
|
|
27
|
+
ReactAnalyzer,
|
|
28
|
+
RustAnalyzer,
|
|
29
|
+
// Enrichment
|
|
30
|
+
MethodCallResolver,
|
|
31
|
+
ArgumentParameterLinker,
|
|
32
|
+
AliasTracker,
|
|
33
|
+
ValueDomainAnalyzer,
|
|
34
|
+
MountPointResolver,
|
|
35
|
+
ExpressHandlerLinker,
|
|
36
|
+
PrefixEvaluator,
|
|
37
|
+
InstanceOfResolver,
|
|
38
|
+
ImportExportLinker,
|
|
39
|
+
FunctionCallResolver,
|
|
40
|
+
HTTPConnectionEnricher,
|
|
41
|
+
ConfigRoutingMapBuilder,
|
|
42
|
+
ServiceConnectionEnricher,
|
|
43
|
+
RustFFIEnricher,
|
|
44
|
+
RejectionPropagationEnricher,
|
|
45
|
+
CallbackCallResolver,
|
|
46
|
+
// Validation
|
|
47
|
+
CallResolverValidator,
|
|
48
|
+
EvalBanValidator,
|
|
49
|
+
SQLInjectionValidator,
|
|
50
|
+
AwaitInLoopValidator,
|
|
51
|
+
ShadowingDetector,
|
|
52
|
+
GraphConnectivityValidator,
|
|
53
|
+
DataFlowValidator,
|
|
54
|
+
TypeScriptDeadCodeValidator,
|
|
55
|
+
BrokenImportValidator,
|
|
56
|
+
UnconnectedRouteValidator,
|
|
57
|
+
PackageCoverageValidator,
|
|
58
|
+
} from '@grafema/core';
|
|
59
|
+
|
|
60
|
+
export const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
|
|
61
|
+
// Discovery
|
|
62
|
+
SimpleProjectDiscovery: () => new SimpleProjectDiscovery() as Plugin,
|
|
63
|
+
MonorepoServiceDiscovery: () => new MonorepoServiceDiscovery() as Plugin,
|
|
64
|
+
WorkspaceDiscovery: () => new WorkspaceDiscovery() as Plugin,
|
|
65
|
+
// Indexing
|
|
66
|
+
JSModuleIndexer: () => new JSModuleIndexer() as Plugin,
|
|
67
|
+
RustModuleIndexer: () => new RustModuleIndexer() as Plugin,
|
|
68
|
+
// Analysis
|
|
69
|
+
JSASTAnalyzer: () => new JSASTAnalyzer() as Plugin,
|
|
70
|
+
ExpressRouteAnalyzer: () => new ExpressRouteAnalyzer() as Plugin,
|
|
71
|
+
ExpressResponseAnalyzer: () => new ExpressResponseAnalyzer() as Plugin,
|
|
72
|
+
NestJSRouteAnalyzer: () => new NestJSRouteAnalyzer() as Plugin,
|
|
73
|
+
SocketIOAnalyzer: () => new SocketIOAnalyzer() as Plugin,
|
|
74
|
+
DatabaseAnalyzer: () => new DatabaseAnalyzer() as Plugin,
|
|
75
|
+
FetchAnalyzer: () => new FetchAnalyzer() as Plugin,
|
|
76
|
+
ServiceLayerAnalyzer: () => new ServiceLayerAnalyzer() as Plugin,
|
|
77
|
+
ReactAnalyzer: () => new ReactAnalyzer() as Plugin,
|
|
78
|
+
RustAnalyzer: () => new RustAnalyzer() as Plugin,
|
|
79
|
+
// Enrichment
|
|
80
|
+
MethodCallResolver: () => new MethodCallResolver() as Plugin,
|
|
81
|
+
ArgumentParameterLinker: () => new ArgumentParameterLinker() as Plugin,
|
|
82
|
+
AliasTracker: () => new AliasTracker() as Plugin,
|
|
83
|
+
ValueDomainAnalyzer: () => new ValueDomainAnalyzer() as Plugin,
|
|
84
|
+
MountPointResolver: () => new MountPointResolver() as Plugin,
|
|
85
|
+
ExpressHandlerLinker: () => new ExpressHandlerLinker() as Plugin,
|
|
86
|
+
PrefixEvaluator: () => new PrefixEvaluator() as Plugin,
|
|
87
|
+
InstanceOfResolver: () => new InstanceOfResolver() as Plugin,
|
|
88
|
+
ImportExportLinker: () => new ImportExportLinker() as Plugin,
|
|
89
|
+
FunctionCallResolver: () => new FunctionCallResolver() as Plugin,
|
|
90
|
+
HTTPConnectionEnricher: () => new HTTPConnectionEnricher() as Plugin,
|
|
91
|
+
ConfigRoutingMapBuilder: () => new ConfigRoutingMapBuilder() as Plugin,
|
|
92
|
+
ServiceConnectionEnricher: () => new ServiceConnectionEnricher() as Plugin,
|
|
93
|
+
RustFFIEnricher: () => new RustFFIEnricher() as Plugin,
|
|
94
|
+
RejectionPropagationEnricher: () => new RejectionPropagationEnricher() as Plugin,
|
|
95
|
+
CallbackCallResolver: () => new CallbackCallResolver() as Plugin,
|
|
96
|
+
// Validation
|
|
97
|
+
CallResolverValidator: () => new CallResolverValidator() as Plugin,
|
|
98
|
+
EvalBanValidator: () => new EvalBanValidator() as Plugin,
|
|
99
|
+
SQLInjectionValidator: () => new SQLInjectionValidator() as Plugin,
|
|
100
|
+
AwaitInLoopValidator: () => new AwaitInLoopValidator() as Plugin,
|
|
101
|
+
ShadowingDetector: () => new ShadowingDetector() as Plugin,
|
|
102
|
+
GraphConnectivityValidator: () => new GraphConnectivityValidator() as Plugin,
|
|
103
|
+
DataFlowValidator: () => new DataFlowValidator() as Plugin,
|
|
104
|
+
TypeScriptDeadCodeValidator: () => new TypeScriptDeadCodeValidator() as Plugin,
|
|
105
|
+
BrokenImportValidator: () => new BrokenImportValidator() as Plugin,
|
|
106
|
+
UnconnectedRouteValidator: () => new UnconnectedRouteValidator() as Plugin,
|
|
107
|
+
PackageCoverageValidator: () => new PackageCoverageValidator() as Plugin,
|
|
108
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin loading — resolves built-in and custom plugins from config.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - ESM resolve hook for custom plugin @grafema/* imports
|
|
6
|
+
* - Loading custom plugins from .grafema/plugins/
|
|
7
|
+
* - Creating plugin instances from config phases
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { existsSync, readdirSync } from 'fs';
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
13
|
+
import { register } from 'node:module';
|
|
14
|
+
import type { Plugin, GrafemaConfig } from '@grafema/core';
|
|
15
|
+
import { BUILTIN_PLUGINS } from './builtinPlugins.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register ESM resolve hook so custom plugins can import @grafema/* packages.
|
|
19
|
+
*
|
|
20
|
+
* Plugins in .grafema/plugins/ do `import { Plugin } from '@grafema/core'`,
|
|
21
|
+
* but @grafema/core isn't in the target project's node_modules/.
|
|
22
|
+
* This hook redirects those imports to the CLI's bundled packages.
|
|
23
|
+
*
|
|
24
|
+
* Uses module.register() (stable Node.js 20.6+ API).
|
|
25
|
+
* Safe to call multiple times — subsequent calls add redundant hooks
|
|
26
|
+
* that short-circuit on the same specifiers.
|
|
27
|
+
*/
|
|
28
|
+
let pluginResolverRegistered = false;
|
|
29
|
+
|
|
30
|
+
export function registerPluginResolver(): void {
|
|
31
|
+
if (pluginResolverRegistered) return;
|
|
32
|
+
pluginResolverRegistered = true;
|
|
33
|
+
|
|
34
|
+
const grafemaPackages: Record<string, string> = {};
|
|
35
|
+
for (const pkg of ['@grafema/core', '@grafema/types']) {
|
|
36
|
+
try {
|
|
37
|
+
grafemaPackages[pkg] = import.meta.resolve(pkg);
|
|
38
|
+
} catch {
|
|
39
|
+
// Package not available from CLI context — skip
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
register(
|
|
44
|
+
new URL('./pluginResolver.js', import.meta.url),
|
|
45
|
+
{ data: { grafemaPackages } },
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load custom plugins from .grafema/plugins/ directory
|
|
51
|
+
*/
|
|
52
|
+
export async function loadCustomPlugins(
|
|
53
|
+
projectPath: string,
|
|
54
|
+
log: (msg: string) => void
|
|
55
|
+
): Promise<Record<string, () => Plugin>> {
|
|
56
|
+
const pluginsDir = join(projectPath, '.grafema', 'plugins');
|
|
57
|
+
if (!existsSync(pluginsDir)) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Ensure @grafema/* imports resolve for custom plugins (REG-380)
|
|
62
|
+
registerPluginResolver();
|
|
63
|
+
|
|
64
|
+
const customPlugins: Record<string, () => Plugin> = {};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const files = readdirSync(pluginsDir).filter(
|
|
68
|
+
(f) => f.endsWith('.js') || f.endsWith('.mjs') || f.endsWith('.cjs')
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
try {
|
|
73
|
+
const pluginPath = join(pluginsDir, file);
|
|
74
|
+
const pluginUrl = pathToFileURL(pluginPath).href;
|
|
75
|
+
const module = await import(pluginUrl);
|
|
76
|
+
|
|
77
|
+
const PluginClass = module.default || module[file.replace(/\.[cm]?js$/, '')];
|
|
78
|
+
if (PluginClass && typeof PluginClass === 'function') {
|
|
79
|
+
const pluginName = PluginClass.name || file.replace(/\.[cm]?js$/, '');
|
|
80
|
+
customPlugins[pluginName] = () => {
|
|
81
|
+
const instance = new PluginClass() as Plugin;
|
|
82
|
+
instance.config.sourceFile = pluginPath;
|
|
83
|
+
return instance;
|
|
84
|
+
};
|
|
85
|
+
log(`Loaded custom plugin: ${pluginName}`);
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
89
|
+
console.warn(`Failed to load plugin ${file}: ${message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
+
console.warn(`Error loading custom plugins: ${message}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return customPlugins;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createPlugins(
|
|
101
|
+
config: GrafemaConfig['plugins'],
|
|
102
|
+
customPlugins: Record<string, () => Plugin> = {},
|
|
103
|
+
verbose: boolean = false
|
|
104
|
+
): Plugin[] {
|
|
105
|
+
const plugins: Plugin[] = [];
|
|
106
|
+
const phases: (keyof GrafemaConfig['plugins'])[] = ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'];
|
|
107
|
+
|
|
108
|
+
for (const phase of phases) {
|
|
109
|
+
const names = config[phase] || [];
|
|
110
|
+
for (const name of names) {
|
|
111
|
+
// Check built-in first, then custom
|
|
112
|
+
const factory = BUILTIN_PLUGINS[name] || customPlugins[name];
|
|
113
|
+
if (factory) {
|
|
114
|
+
plugins.push(factory());
|
|
115
|
+
} else if (verbose) {
|
|
116
|
+
// Only show plugin warning in verbose mode
|
|
117
|
+
console.warn(`Plugin not found: ${name} (skipping). Check .grafema/config.yaml or add to .grafema/plugins/`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return plugins;
|
|
123
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESM resolve hook for custom Grafema plugins.
|
|
3
|
+
*
|
|
4
|
+
* Allows plugins in .grafema/plugins/ to `import { Plugin } from '@grafema/core'`
|
|
5
|
+
* without requiring @grafema/core in the target project's node_modules/.
|
|
6
|
+
*
|
|
7
|
+
* The hook maps @grafema/* bare specifiers to the actual package URLs
|
|
8
|
+
* within the CLI's dependency tree.
|
|
9
|
+
*
|
|
10
|
+
* Registered via module.register() before loading custom plugins.
|
|
11
|
+
* Must be plain JS — loader hooks run in a separate thread.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** @type {Record<string, string>} package name → resolved file URL */
|
|
15
|
+
let grafemaPackages = {};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Called once when the hook is registered via module.register().
|
|
19
|
+
* @param {{ grafemaPackages: Record<string, string> }} data
|
|
20
|
+
*/
|
|
21
|
+
export function initialize(data) {
|
|
22
|
+
grafemaPackages = data.grafemaPackages;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve hook — intercepts bare specifier imports for @grafema/* packages
|
|
27
|
+
* and redirects them to the CLI's bundled versions.
|
|
28
|
+
*
|
|
29
|
+
* Only exact package name matches are handled (e.g. '@grafema/core').
|
|
30
|
+
* All other specifiers pass through to the default resolver.
|
|
31
|
+
*/
|
|
32
|
+
export function resolve(specifier, context, next) {
|
|
33
|
+
if (grafemaPackages[specifier]) {
|
|
34
|
+
return { url: grafemaPackages[specifier], shortCircuit: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return next(specifier, context);
|
|
38
|
+
}
|