@grafema/cli 0.1.1-alpha → 0.2.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 +10 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +69 -11
- 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 +84 -9
- 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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities for `grafema doctor` command - REG-214
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DoctorCheckResult, DoctorReport } from './types.js';
|
|
6
|
+
|
|
7
|
+
// ANSI colors (matching existing CLI style)
|
|
8
|
+
const COLORS = {
|
|
9
|
+
green: '\x1b[32m',
|
|
10
|
+
red: '\x1b[31m',
|
|
11
|
+
yellow: '\x1b[33m',
|
|
12
|
+
cyan: '\x1b[36m',
|
|
13
|
+
dim: '\x1b[2m',
|
|
14
|
+
reset: '\x1b[0m',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const STATUS_ICONS: Record<string, string> = {
|
|
18
|
+
pass: `${COLORS.green}✓${COLORS.reset}`,
|
|
19
|
+
warn: `${COLORS.yellow}⚠${COLORS.reset}`,
|
|
20
|
+
fail: `${COLORS.red}✗${COLORS.reset}`,
|
|
21
|
+
skip: `${COLORS.dim}○${COLORS.reset}`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format a single check result for console output.
|
|
26
|
+
*/
|
|
27
|
+
export function formatCheck(result: DoctorCheckResult, verbose: boolean): string {
|
|
28
|
+
const icon = STATUS_ICONS[result.status];
|
|
29
|
+
let output = `${icon} ${result.message}`;
|
|
30
|
+
|
|
31
|
+
if (result.recommendation) {
|
|
32
|
+
output += `\n ${COLORS.dim}→${COLORS.reset} ${result.recommendation}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (verbose && result.details) {
|
|
36
|
+
const detailStr = JSON.stringify(result.details, null, 2)
|
|
37
|
+
.split('\n')
|
|
38
|
+
.map(line => ` ${COLORS.dim}${line}${COLORS.reset}`)
|
|
39
|
+
.join('\n');
|
|
40
|
+
output += `\n${detailStr}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return output;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format full report for console.
|
|
48
|
+
*/
|
|
49
|
+
export function formatReport(
|
|
50
|
+
checks: DoctorCheckResult[],
|
|
51
|
+
options: { quiet?: boolean; verbose?: boolean }
|
|
52
|
+
): string {
|
|
53
|
+
const lines: string[] = [];
|
|
54
|
+
|
|
55
|
+
if (!options.quiet) {
|
|
56
|
+
lines.push('Checking Grafema setup...');
|
|
57
|
+
lines.push('');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const check of checks) {
|
|
61
|
+
if (options.quiet && check.status === 'pass') continue;
|
|
62
|
+
lines.push(formatCheck(check, options.verbose || false));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Summary
|
|
66
|
+
const failCount = checks.filter(c => c.status === 'fail').length;
|
|
67
|
+
const warnCount = checks.filter(c => c.status === 'warn').length;
|
|
68
|
+
|
|
69
|
+
lines.push('');
|
|
70
|
+
if (failCount > 0) {
|
|
71
|
+
lines.push(`${COLORS.red}Status: ${failCount} error(s), ${warnCount} warning(s)${COLORS.reset}`);
|
|
72
|
+
} else if (warnCount > 0) {
|
|
73
|
+
lines.push(`${COLORS.yellow}Status: ${warnCount} warning(s)${COLORS.reset}`);
|
|
74
|
+
} else {
|
|
75
|
+
lines.push(`${COLORS.green}Status: All checks passed${COLORS.reset}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build JSON report structure.
|
|
83
|
+
*/
|
|
84
|
+
export function buildJsonReport(
|
|
85
|
+
checks: DoctorCheckResult[],
|
|
86
|
+
projectPath: string
|
|
87
|
+
): DoctorReport {
|
|
88
|
+
const failCount = checks.filter(c => c.status === 'fail').length;
|
|
89
|
+
const warnCount = checks.filter(c => c.status === 'warn').length;
|
|
90
|
+
|
|
91
|
+
const status = failCount > 0 ? 'error' : warnCount > 0 ? 'warning' : 'healthy';
|
|
92
|
+
const recommendations = checks
|
|
93
|
+
.filter(c => c.recommendation)
|
|
94
|
+
.map(c => c.recommendation as string);
|
|
95
|
+
|
|
96
|
+
// Extract versions from versions check
|
|
97
|
+
const versionsCheck = checks.find(c => c.name === 'versions');
|
|
98
|
+
const versions = (versionsCheck?.details as { cli?: string; core?: string; rfdb?: string }) || {
|
|
99
|
+
cli: 'unknown',
|
|
100
|
+
core: 'unknown',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
status,
|
|
105
|
+
timestamp: new Date().toISOString(),
|
|
106
|
+
project: projectPath,
|
|
107
|
+
checks,
|
|
108
|
+
recommendations,
|
|
109
|
+
versions: {
|
|
110
|
+
cli: versions.cli || 'unknown',
|
|
111
|
+
core: versions.core || 'unknown',
|
|
112
|
+
rfdb: versions.rfdb,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for `grafema doctor` command - REG-214
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Status of a single diagnostic check
|
|
7
|
+
*/
|
|
8
|
+
export type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Result of a single diagnostic check
|
|
12
|
+
*/
|
|
13
|
+
export interface DoctorCheckResult {
|
|
14
|
+
name: string; // e.g., 'config', 'server', 'database'
|
|
15
|
+
status: CheckStatus;
|
|
16
|
+
message: string; // Human-readable message
|
|
17
|
+
recommendation?: string; // Actionable next step if not pass
|
|
18
|
+
details?: Record<string, unknown>; // Additional data (counts, versions, etc.)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for the doctor command
|
|
23
|
+
*/
|
|
24
|
+
export interface DoctorOptions {
|
|
25
|
+
project: string; // Project path (default: ".")
|
|
26
|
+
json?: boolean; // Output as JSON
|
|
27
|
+
quiet?: boolean; // Only show failures
|
|
28
|
+
verbose?: boolean; // Show detailed diagnostics
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Overall doctor report (for JSON output)
|
|
33
|
+
*/
|
|
34
|
+
export interface DoctorReport {
|
|
35
|
+
status: 'healthy' | 'warning' | 'error';
|
|
36
|
+
timestamp: string; // ISO timestamp
|
|
37
|
+
project: string; // Absolute project path
|
|
38
|
+
checks: DoctorCheckResult[];
|
|
39
|
+
recommendations: string[];
|
|
40
|
+
versions: {
|
|
41
|
+
cli: string;
|
|
42
|
+
core: string;
|
|
43
|
+
rfdb?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor command - Diagnose Grafema setup issues
|
|
3
|
+
*
|
|
4
|
+
* Checks (in order):
|
|
5
|
+
* 1. Initialization (.grafema directory, config file)
|
|
6
|
+
* 2. Config validity (syntax, plugin names)
|
|
7
|
+
* 3. Entrypoints (service paths exist)
|
|
8
|
+
* 4. Server status (RFDB server running)
|
|
9
|
+
* 5. Database exists and has data
|
|
10
|
+
* 6. Graph statistics
|
|
11
|
+
* 7. Graph connectivity
|
|
12
|
+
* 8. Graph freshness
|
|
13
|
+
* 9. Version information
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Command } from 'commander';
|
|
17
|
+
import { resolve } from 'path';
|
|
18
|
+
import {
|
|
19
|
+
checkGrafemaInitialized,
|
|
20
|
+
checkServerStatus,
|
|
21
|
+
checkConfigValidity,
|
|
22
|
+
checkEntrypoints,
|
|
23
|
+
checkDatabaseExists,
|
|
24
|
+
checkGraphStats,
|
|
25
|
+
checkConnectivity,
|
|
26
|
+
checkFreshness,
|
|
27
|
+
checkVersions,
|
|
28
|
+
} from './doctor/checks.js';
|
|
29
|
+
import { formatReport, buildJsonReport } from './doctor/output.js';
|
|
30
|
+
import type { DoctorOptions, DoctorCheckResult } from './doctor/types.js';
|
|
31
|
+
|
|
32
|
+
export const doctorCommand = new Command('doctor')
|
|
33
|
+
.description('Diagnose Grafema setup issues')
|
|
34
|
+
.option('-p, --project <path>', 'Project path', '.')
|
|
35
|
+
.option('-j, --json', 'Output as JSON')
|
|
36
|
+
.option('-q, --quiet', 'Only show failures')
|
|
37
|
+
.option('-v, --verbose', 'Show detailed diagnostics')
|
|
38
|
+
.addHelpText('after', `
|
|
39
|
+
Examples:
|
|
40
|
+
grafema doctor Run all diagnostic checks
|
|
41
|
+
grafema doctor --verbose Show detailed diagnostics
|
|
42
|
+
grafema doctor --quiet Only show failures
|
|
43
|
+
grafema doctor --json Output diagnostics as JSON
|
|
44
|
+
`)
|
|
45
|
+
.action(async (options: DoctorOptions) => {
|
|
46
|
+
const projectPath = resolve(options.project);
|
|
47
|
+
const checks: DoctorCheckResult[] = [];
|
|
48
|
+
|
|
49
|
+
// Level 1: Prerequisites (fail-fast)
|
|
50
|
+
const initCheck = await checkGrafemaInitialized(projectPath);
|
|
51
|
+
checks.push(initCheck);
|
|
52
|
+
|
|
53
|
+
if (initCheck.status === 'fail') {
|
|
54
|
+
// Can't continue without initialization
|
|
55
|
+
outputResults(checks, projectPath, options);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Level 2: Configuration
|
|
60
|
+
checks.push(await checkConfigValidity(projectPath));
|
|
61
|
+
checks.push(await checkEntrypoints(projectPath));
|
|
62
|
+
|
|
63
|
+
// Server status (needed for Level 3 checks)
|
|
64
|
+
const serverCheck = await checkServerStatus(projectPath);
|
|
65
|
+
checks.push(serverCheck);
|
|
66
|
+
|
|
67
|
+
// Level 3: Graph Health (requires database and optionally server)
|
|
68
|
+
checks.push(await checkDatabaseExists(projectPath));
|
|
69
|
+
|
|
70
|
+
if (serverCheck.status === 'pass') {
|
|
71
|
+
// Server is running - can do full health checks
|
|
72
|
+
checks.push(await checkGraphStats(projectPath));
|
|
73
|
+
checks.push(await checkConnectivity(projectPath));
|
|
74
|
+
checks.push(await checkFreshness(projectPath));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Level 4: Informational
|
|
78
|
+
checks.push(await checkVersions(projectPath));
|
|
79
|
+
|
|
80
|
+
// Output results
|
|
81
|
+
outputResults(checks, projectPath, options);
|
|
82
|
+
|
|
83
|
+
// Exit code
|
|
84
|
+
const failCount = checks.filter(c => c.status === 'fail').length;
|
|
85
|
+
const warnCount = checks.filter(c => c.status === 'warn').length;
|
|
86
|
+
|
|
87
|
+
if (failCount > 0) {
|
|
88
|
+
process.exit(1); // Critical issues
|
|
89
|
+
} else if (warnCount > 0) {
|
|
90
|
+
process.exit(2); // Warnings only
|
|
91
|
+
}
|
|
92
|
+
// Exit 0 for all pass
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function outputResults(
|
|
96
|
+
checks: DoctorCheckResult[],
|
|
97
|
+
projectPath: string,
|
|
98
|
+
options: DoctorOptions
|
|
99
|
+
): void {
|
|
100
|
+
if (options.json) {
|
|
101
|
+
const report = buildJsonReport(checks, projectPath);
|
|
102
|
+
console.log(JSON.stringify(report, null, 2));
|
|
103
|
+
} else {
|
|
104
|
+
console.log(formatReport(checks, options));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explain command - Show what nodes exist in a file
|
|
3
|
+
*
|
|
4
|
+
* Purpose: Help users discover what nodes exist in the graph for a file,
|
|
5
|
+
* displaying semantic IDs so users can query them.
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - User can't find a variable they expect to be in the graph
|
|
9
|
+
* - User wants to understand what's been analyzed for a file
|
|
10
|
+
* - User needs semantic IDs to construct queries
|
|
11
|
+
*
|
|
12
|
+
* @see _tasks/REG-177/006-don-revised-plan.md
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Command } from 'commander';
|
|
16
|
+
import { resolve, join, relative, normalize } from 'path';
|
|
17
|
+
import { existsSync, realpathSync } from 'fs';
|
|
18
|
+
import { RFDBServerBackend, FileExplainer, type EnhancedNode } from '@grafema/core';
|
|
19
|
+
import { exitWithError } from '../utils/errorFormatter.js';
|
|
20
|
+
|
|
21
|
+
interface ExplainOptions {
|
|
22
|
+
project: string;
|
|
23
|
+
json?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const explainCommand = new Command('explain')
|
|
27
|
+
.description('Show what nodes exist in a file')
|
|
28
|
+
.argument('<file>', 'File path to explain')
|
|
29
|
+
.option('-p, --project <path>', 'Project path', '.')
|
|
30
|
+
.option('-j, --json', 'Output as JSON')
|
|
31
|
+
.addHelpText('after', `
|
|
32
|
+
Examples:
|
|
33
|
+
grafema explain src/app.ts Show all nodes in src/app.ts
|
|
34
|
+
grafema explain src/app.ts --json Output as JSON for scripting
|
|
35
|
+
grafema explain ./src/utils.js Works with relative paths
|
|
36
|
+
|
|
37
|
+
This command helps you:
|
|
38
|
+
1. Discover what nodes exist in the graph for a file
|
|
39
|
+
2. Find semantic IDs to use in queries
|
|
40
|
+
3. Understand scope context (try/catch, conditionals, etc.)
|
|
41
|
+
|
|
42
|
+
If a file shows NOT_ANALYZED:
|
|
43
|
+
- Run: grafema analyze
|
|
44
|
+
- Check if file is excluded in config
|
|
45
|
+
`)
|
|
46
|
+
.action(async (file: string, options: ExplainOptions) => {
|
|
47
|
+
const projectPath = resolve(options.project);
|
|
48
|
+
const grafemaDir = join(projectPath, '.grafema');
|
|
49
|
+
const dbPath = join(grafemaDir, 'graph.rfdb');
|
|
50
|
+
|
|
51
|
+
// Check database exists
|
|
52
|
+
if (!existsSync(dbPath)) {
|
|
53
|
+
exitWithError('No graph database found', [
|
|
54
|
+
'Run: grafema init && grafema analyze',
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Normalize and resolve file path
|
|
59
|
+
let filePath = file;
|
|
60
|
+
|
|
61
|
+
// Handle relative paths - convert to relative from project root
|
|
62
|
+
if (file.startsWith('./') || file.startsWith('../')) {
|
|
63
|
+
filePath = normalize(file).replace(/^\.\//, '');
|
|
64
|
+
} else if (resolve(file) === file) {
|
|
65
|
+
// Absolute path - convert to relative
|
|
66
|
+
filePath = relative(projectPath, file);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Resolve to absolute path for graph lookup
|
|
70
|
+
const resolvedPath = resolve(projectPath, filePath);
|
|
71
|
+
if (!existsSync(resolvedPath)) {
|
|
72
|
+
exitWithError(`File not found: ${file}`, [
|
|
73
|
+
'Check the file path and try again',
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Use realpath to match how graph stores paths (handles symlinks like /tmp -> /private/tmp on macOS)
|
|
78
|
+
const absoluteFilePath = realpathSync(resolvedPath);
|
|
79
|
+
|
|
80
|
+
// Keep relative path for display
|
|
81
|
+
const relativeFilePath = relative(projectPath, absoluteFilePath);
|
|
82
|
+
|
|
83
|
+
const backend = new RFDBServerBackend({ dbPath });
|
|
84
|
+
await backend.connect();
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const explainer = new FileExplainer(backend);
|
|
88
|
+
// Query with absolute path since graph stores absolute paths
|
|
89
|
+
const result = await explainer.explain(absoluteFilePath);
|
|
90
|
+
|
|
91
|
+
// Override file in result for display purposes (show relative path)
|
|
92
|
+
result.file = relativeFilePath;
|
|
93
|
+
|
|
94
|
+
if (options.json) {
|
|
95
|
+
console.log(JSON.stringify(result, null, 2));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Human-readable output
|
|
100
|
+
console.log(`File: ${result.file}`);
|
|
101
|
+
console.log(`Status: ${result.status}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
|
|
104
|
+
if (result.status === 'NOT_ANALYZED') {
|
|
105
|
+
console.log('This file has not been analyzed yet.');
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log('To analyze:');
|
|
108
|
+
console.log(' grafema analyze');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`Nodes in graph: ${result.totalCount}`);
|
|
113
|
+
console.log('');
|
|
114
|
+
|
|
115
|
+
// Group nodes by type for display
|
|
116
|
+
const nodesByType = groupNodesByType(result.nodes);
|
|
117
|
+
|
|
118
|
+
for (const [type, nodes] of Object.entries(nodesByType)) {
|
|
119
|
+
for (const node of nodes) {
|
|
120
|
+
displayNode(node, type, projectPath);
|
|
121
|
+
console.log('');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Show summary by type
|
|
126
|
+
console.log('Summary:');
|
|
127
|
+
for (const [type, count] of Object.entries(result.byType).sort()) {
|
|
128
|
+
console.log(` ${type}: ${count}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log('To query a specific node by ID:');
|
|
133
|
+
console.log(' grafema query --raw \'attr(X, "id", "<semantic-id>")\'');
|
|
134
|
+
} finally {
|
|
135
|
+
await backend.close();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Group nodes by type for organized display
|
|
141
|
+
*/
|
|
142
|
+
function groupNodesByType(nodes: EnhancedNode[]): Record<string, EnhancedNode[]> {
|
|
143
|
+
const grouped: Record<string, EnhancedNode[]> = {};
|
|
144
|
+
|
|
145
|
+
for (const node of nodes) {
|
|
146
|
+
const type = node.type || 'UNKNOWN';
|
|
147
|
+
if (!grouped[type]) {
|
|
148
|
+
grouped[type] = [];
|
|
149
|
+
}
|
|
150
|
+
grouped[type].push(node);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return grouped;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Display a single node in human-readable format
|
|
158
|
+
*/
|
|
159
|
+
function displayNode(node: EnhancedNode, type: string, projectPath: string): void {
|
|
160
|
+
// Line 1: [TYPE] name (context)
|
|
161
|
+
const contextSuffix = node.context ? ` (${node.context})` : '';
|
|
162
|
+
console.log(`[${type}] ${node.name || '<anonymous>'}${contextSuffix}`);
|
|
163
|
+
|
|
164
|
+
// Line 2: ID (semantic ID for querying)
|
|
165
|
+
console.log(` ID: ${node.id}`);
|
|
166
|
+
|
|
167
|
+
// Line 3: Location
|
|
168
|
+
if (node.file) {
|
|
169
|
+
const relPath = relative(projectPath, node.file);
|
|
170
|
+
const loc = node.line ? `${relPath}:${node.line}` : relPath;
|
|
171
|
+
console.log(` Location: ${loc}`);
|
|
172
|
+
}
|
|
173
|
+
}
|