@grafema/cli 0.2.11 → 0.3.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.
Files changed (133) hide show
  1. package/dist/cli.js +13 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/analyze.d.ts.map +1 -1
  4. package/dist/commands/analyze.js +2 -4
  5. package/dist/commands/analyze.js.map +1 -1
  6. package/dist/commands/analyzeAction.d.ts +5 -3
  7. package/dist/commands/analyzeAction.d.ts.map +1 -1
  8. package/dist/commands/analyzeAction.js +109 -151
  9. package/dist/commands/analyzeAction.js.map +1 -1
  10. package/dist/commands/check.d.ts +1 -1
  11. package/dist/commands/check.js +4 -4
  12. package/dist/commands/check.js.map +1 -1
  13. package/dist/commands/context.js +2 -2
  14. package/dist/commands/context.js.map +1 -1
  15. package/dist/commands/coverage.js +2 -2
  16. package/dist/commands/coverage.js.map +1 -1
  17. package/dist/commands/describe.d.ts +13 -0
  18. package/dist/commands/describe.d.ts.map +1 -0
  19. package/dist/commands/describe.js +131 -0
  20. package/dist/commands/describe.js.map +1 -0
  21. package/dist/commands/doctor/checks.d.ts +6 -1
  22. package/dist/commands/doctor/checks.d.ts.map +1 -1
  23. package/dist/commands/doctor/checks.js +128 -13
  24. package/dist/commands/doctor/checks.js.map +1 -1
  25. package/dist/commands/doctor.d.ts +10 -9
  26. package/dist/commands/doctor.d.ts.map +1 -1
  27. package/dist/commands/doctor.js +12 -10
  28. package/dist/commands/doctor.js.map +1 -1
  29. package/dist/commands/explain.js +2 -2
  30. package/dist/commands/explain.js.map +1 -1
  31. package/dist/commands/file.js +2 -2
  32. package/dist/commands/file.js.map +1 -1
  33. package/dist/commands/get.js +2 -2
  34. package/dist/commands/get.js.map +1 -1
  35. package/dist/commands/git-ingest.d.ts +6 -0
  36. package/dist/commands/git-ingest.d.ts.map +1 -0
  37. package/dist/commands/git-ingest.js +46 -0
  38. package/dist/commands/git-ingest.js.map +1 -0
  39. package/dist/commands/impact.d.ts.map +1 -1
  40. package/dist/commands/impact.js +276 -50
  41. package/dist/commands/impact.js.map +1 -1
  42. package/dist/commands/init.d.ts.map +1 -1
  43. package/dist/commands/init.js +20 -22
  44. package/dist/commands/init.js.map +1 -1
  45. package/dist/commands/ls.js +2 -2
  46. package/dist/commands/ls.js.map +1 -1
  47. package/dist/commands/overview.js +2 -2
  48. package/dist/commands/overview.js.map +1 -1
  49. package/dist/commands/query.d.ts +1 -1
  50. package/dist/commands/query.d.ts.map +1 -1
  51. package/dist/commands/query.js +169 -7
  52. package/dist/commands/query.js.map +1 -1
  53. package/dist/commands/schema.js +2 -2
  54. package/dist/commands/schema.js.map +1 -1
  55. package/dist/commands/server.d.ts.map +1 -1
  56. package/dist/commands/server.js +122 -76
  57. package/dist/commands/server.js.map +1 -1
  58. package/dist/commands/stats.js +2 -2
  59. package/dist/commands/stats.js.map +1 -1
  60. package/dist/commands/tldr.d.ts +12 -0
  61. package/dist/commands/tldr.d.ts.map +1 -0
  62. package/dist/commands/tldr.js +81 -0
  63. package/dist/commands/tldr.js.map +1 -0
  64. package/dist/commands/trace.d.ts +1 -1
  65. package/dist/commands/trace.d.ts.map +1 -1
  66. package/dist/commands/trace.js +17 -133
  67. package/dist/commands/trace.js.map +1 -1
  68. package/dist/commands/types.js +2 -2
  69. package/dist/commands/types.js.map +1 -1
  70. package/dist/commands/who.d.ts +12 -0
  71. package/dist/commands/who.d.ts.map +1 -0
  72. package/dist/commands/who.js +184 -0
  73. package/dist/commands/who.js.map +1 -0
  74. package/dist/commands/why.d.ts +12 -0
  75. package/dist/commands/why.d.ts.map +1 -0
  76. package/dist/commands/why.js +118 -0
  77. package/dist/commands/why.js.map +1 -0
  78. package/dist/commands/wtf.d.ts +12 -0
  79. package/dist/commands/wtf.d.ts.map +1 -0
  80. package/dist/commands/wtf.js +117 -0
  81. package/dist/commands/wtf.js.map +1 -0
  82. package/dist/plugins/builtinPlugins.d.ts +1 -9
  83. package/dist/plugins/builtinPlugins.d.ts.map +1 -1
  84. package/dist/plugins/builtinPlugins.js +2 -67
  85. package/dist/plugins/builtinPlugins.js.map +1 -1
  86. package/dist/plugins/pluginLoader.d.ts +1 -15
  87. package/dist/plugins/pluginLoader.d.ts.map +1 -1
  88. package/dist/plugins/pluginLoader.js +2 -100
  89. package/dist/plugins/pluginLoader.js.map +1 -1
  90. package/dist/plugins/pluginResolver.js +3 -3
  91. package/dist/utils/progressRenderer.d.ts +15 -1
  92. package/dist/utils/progressRenderer.d.ts.map +1 -1
  93. package/dist/utils/progressRenderer.js +19 -3
  94. package/dist/utils/progressRenderer.js.map +1 -1
  95. package/dist/utils/queryHints.d.ts +6 -0
  96. package/dist/utils/queryHints.d.ts.map +1 -0
  97. package/dist/utils/queryHints.js +36 -0
  98. package/dist/utils/queryHints.js.map +1 -0
  99. package/package.json +4 -4
  100. package/skills/grafema-codebase-analysis/SKILL.md +1 -1
  101. package/src/cli.ts +14 -0
  102. package/src/commands/analyze.ts +2 -4
  103. package/src/commands/analyzeAction.ts +122 -168
  104. package/src/commands/check.ts +5 -5
  105. package/src/commands/context.ts +3 -3
  106. package/src/commands/coverage.ts +2 -2
  107. package/src/commands/describe.ts +160 -0
  108. package/src/commands/doctor/checks.ts +153 -10
  109. package/src/commands/doctor.ts +13 -9
  110. package/src/commands/explain.ts +2 -2
  111. package/src/commands/explore.tsx +2 -2
  112. package/src/commands/file.ts +3 -3
  113. package/src/commands/get.ts +2 -2
  114. package/src/commands/git-ingest.ts +49 -0
  115. package/src/commands/impact.ts +318 -55
  116. package/src/commands/init.ts +20 -22
  117. package/src/commands/ls.ts +2 -2
  118. package/src/commands/overview.ts +2 -2
  119. package/src/commands/query.ts +197 -7
  120. package/src/commands/schema.ts +2 -2
  121. package/src/commands/server.ts +136 -84
  122. package/src/commands/stats.ts +2 -2
  123. package/src/commands/tldr.ts +103 -0
  124. package/src/commands/trace.ts +19 -161
  125. package/src/commands/types.ts +2 -2
  126. package/src/commands/who.ts +215 -0
  127. package/src/commands/why.ts +134 -0
  128. package/src/commands/wtf.ts +140 -0
  129. package/src/plugins/builtinPlugins.ts +1 -108
  130. package/src/plugins/pluginLoader.ts +1 -123
  131. package/src/plugins/pluginResolver.js +3 -3
  132. package/src/utils/progressRenderer.ts +34 -4
  133. package/src/utils/queryHints.ts +46 -0
@@ -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
+ });
@@ -2,14 +2,14 @@
2
2
  * Diagnostic check functions for `grafema doctor` command - REG-214
3
3
  *
4
4
  * Checks are organized in levels:
5
- * - Level 1: Prerequisites (fail-fast) - checkGrafemaInitialized, checkServerStatus
5
+ * - Level 1: Prerequisites (fail-fast) - checkBinaries, checkGrafemaInitialized, checkServerStatus
6
6
  * - Level 2: Configuration - checkConfigValidity, checkEntrypoints
7
7
  * - Level 3: Graph Health - checkDatabaseExists, checkGraphStats, checkConnectivity, checkFreshness
8
8
  * - Level 4: Informational - checkVersions
9
9
  */
10
10
 
11
11
  import { existsSync, readFileSync, statSync } from 'fs';
12
- import { join, dirname } from 'path';
12
+ import { join, dirname, delimiter } from 'path';
13
13
  import { fileURLToPath } from 'url';
14
14
  import { createRequire } from 'module';
15
15
  import {
@@ -17,7 +17,7 @@ import {
17
17
  RFDBClient,
18
18
  loadConfig,
19
19
  GraphFreshnessChecker,
20
- } from '@grafema/core';
20
+ } from '@grafema/util';
21
21
  import type { DoctorCheckResult } from './types.js';
22
22
 
23
23
  const __filename = fileURLToPath(import.meta.url);
@@ -38,13 +38,156 @@ const VALID_PLUGIN_NAMES = new Set([
38
38
  'RustFFIEnricher',
39
39
  // Validation
40
40
  'CallResolverValidator', 'EvalBanValidator', 'SQLInjectionValidator', 'ShadowingDetector',
41
- 'GraphConnectivityValidator', 'DataFlowValidator', 'TypeScriptDeadCodeValidator',
41
+ 'GraphConnectivityValidator', 'DataFlowValidator',
42
42
  ]);
43
43
 
44
44
  // =============================================================================
45
45
  // Level 1: Prerequisites (fail-fast)
46
46
  // =============================================================================
47
47
 
48
+ /**
49
+ * Binary lookup config — mirrors packages/grafema/src/binaries.ts but inlined
50
+ * to avoid cross-package dependency (CLI does not depend on the `grafema` pkg).
51
+ */
52
+ type BinaryName = 'rfdb-server' | 'grafema-orchestrator';
53
+
54
+ interface BinaryLookupResult {
55
+ name: BinaryName;
56
+ path: string | null;
57
+ source: string | null;
58
+ }
59
+
60
+ function findBinaryForDoctor(binaryName: BinaryName): BinaryLookupResult {
61
+ const envVars: Record<BinaryName, string> = {
62
+ 'rfdb-server': 'GRAFEMA_RFDB_SERVER',
63
+ 'grafema-orchestrator': 'GRAFEMA_ORCHESTRATOR',
64
+ };
65
+ const monorepoPackages: Record<BinaryName, string> = {
66
+ 'rfdb-server': 'rfdb-server',
67
+ 'grafema-orchestrator': 'grafema-orchestrator',
68
+ };
69
+
70
+ const envVar = envVars[binaryName];
71
+ const monorepoPkg = monorepoPackages[binaryName];
72
+
73
+ // 1. Environment variable
74
+ const envPath = process.env[envVar];
75
+ if (envPath && existsSync(envPath)) {
76
+ return { name: binaryName, path: envPath, source: `$${envVar}` };
77
+ }
78
+
79
+ // 2. Monorepo development builds (release, then debug)
80
+ // Walk up from this file to find monorepo root
81
+ let dir = __dirname;
82
+ let monorepoRoot: string | null = null;
83
+ for (let i = 0; i < 8; i++) {
84
+ if (existsSync(join(dir, 'packages', 'util')) && existsSync(join(dir, 'pnpm-workspace.yaml'))) {
85
+ monorepoRoot = dir;
86
+ break;
87
+ }
88
+ const parent = dirname(dir);
89
+ if (parent === dir) break;
90
+ dir = parent;
91
+ }
92
+ if (!monorepoRoot) {
93
+ const envRoot = process.env.GRAFEMA_ROOT;
94
+ if (envRoot && existsSync(join(envRoot, 'packages', 'util'))) {
95
+ monorepoRoot = envRoot;
96
+ }
97
+ }
98
+
99
+ if (monorepoRoot) {
100
+ for (const profile of ['release', 'debug'] as const) {
101
+ const p = join(monorepoRoot, 'packages', monorepoPkg, 'target', profile, binaryName);
102
+ if (existsSync(p)) {
103
+ return { name: binaryName, path: p, source: `monorepo (${profile})` };
104
+ }
105
+ }
106
+ }
107
+
108
+ // 3. System PATH
109
+ const pathDirs = (process.env.PATH || '').split(delimiter);
110
+ for (const pathDir of pathDirs) {
111
+ if (!pathDir) continue;
112
+ const p = join(pathDir, binaryName);
113
+ if (existsSync(p)) {
114
+ return { name: binaryName, path: p, source: 'PATH' };
115
+ }
116
+ }
117
+
118
+ // 4. ~/.local/bin
119
+ const home = process.env.HOME || process.env.USERPROFILE || '';
120
+ if (home) {
121
+ const p = join(home, '.local', 'bin', binaryName);
122
+ if (existsSync(p)) {
123
+ return { name: binaryName, path: p, source: '~/.local/bin' };
124
+ }
125
+ }
126
+
127
+ return { name: binaryName, path: null, source: null };
128
+ }
129
+
130
+ /**
131
+ * Check if native binaries (rfdb-server, grafema-orchestrator) are findable.
132
+ * FAIL if both missing, WARN if one missing, PASS if both found.
133
+ */
134
+ export async function checkBinaries(): Promise<DoctorCheckResult> {
135
+ const rfdb = findBinaryForDoctor('rfdb-server');
136
+ const orchestrator = findBinaryForDoctor('grafema-orchestrator');
137
+
138
+ const found: string[] = [];
139
+ const missing: string[] = [];
140
+
141
+ if (rfdb.path) {
142
+ found.push(`rfdb-server (${rfdb.source})`);
143
+ } else {
144
+ missing.push('rfdb-server');
145
+ }
146
+
147
+ if (orchestrator.path) {
148
+ found.push(`grafema-orchestrator (${orchestrator.source})`);
149
+ } else {
150
+ missing.push('grafema-orchestrator');
151
+ }
152
+
153
+ if (missing.length === 0) {
154
+ return {
155
+ name: 'binaries',
156
+ status: 'pass',
157
+ message: `Binaries: ${found.join(', ')}`,
158
+ details: {
159
+ rfdbServer: rfdb.path,
160
+ orchestrator: orchestrator.path,
161
+ },
162
+ };
163
+ }
164
+
165
+ if (missing.length === 2) {
166
+ return {
167
+ name: 'binaries',
168
+ status: 'fail',
169
+ message: 'Native binaries not found: rfdb-server, grafema-orchestrator',
170
+ recommendation:
171
+ 'Install: npm install grafema, or build from source: cd packages/<name> && cargo build --release, or set GRAFEMA_RFDB_SERVER / GRAFEMA_ORCHESTRATOR env vars',
172
+ };
173
+ }
174
+
175
+ // One found, one missing
176
+ return {
177
+ name: 'binaries',
178
+ status: 'warn',
179
+ message: `Missing binary: ${missing[0]} (found: ${found[0]})`,
180
+ recommendation:
181
+ missing[0] === 'rfdb-server'
182
+ ? 'Set GRAFEMA_RFDB_SERVER env var or build: cd packages/rfdb-server && cargo build --release'
183
+ : 'Set GRAFEMA_ORCHESTRATOR env var or build: cd packages/grafema-orchestrator && cargo build --release',
184
+ details: {
185
+ rfdbServer: rfdb.path,
186
+ orchestrator: orchestrator.path,
187
+ },
188
+ };
189
+ }
190
+
48
191
  /**
49
192
  * Check if .grafema directory exists with config file.
50
193
  * FAIL if not initialized.
@@ -103,7 +246,7 @@ export async function checkServerStatus(
103
246
  };
104
247
  }
105
248
 
106
- const client = new RFDBClient(socketPath);
249
+ const client = new RFDBClient(socketPath, 'cli');
107
250
  client.on('error', () => {}); // Suppress error events
108
251
 
109
252
  try {
@@ -330,7 +473,7 @@ export async function checkGraphStats(
330
473
  };
331
474
  }
332
475
 
333
- const backend = new RFDBServerBackend({ dbPath });
476
+ const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
334
477
  try {
335
478
  await backend.connect();
336
479
  const stats = await backend.getStats();
@@ -387,7 +530,7 @@ export async function checkConnectivity(
387
530
  };
388
531
  }
389
532
 
390
- const backend = new RFDBServerBackend({ dbPath });
533
+ const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
391
534
  try {
392
535
  await backend.connect();
393
536
 
@@ -523,7 +666,7 @@ export async function checkFreshness(
523
666
  };
524
667
  }
525
668
 
526
- const backend = new RFDBServerBackend({ dbPath });
669
+ const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
527
670
  try {
528
671
  await backend.connect();
529
672
  const freshnessChecker = new GraphFreshnessChecker();
@@ -584,7 +727,7 @@ export async function checkVersions(
584
727
  // Read core version
585
728
  try {
586
729
  const require = createRequire(import.meta.url);
587
- const corePkgPath = require.resolve('@grafema/core/package.json');
730
+ const corePkgPath = require.resolve('@grafema/util/package.json');
588
731
  const corePkg = JSON.parse(readFileSync(corePkgPath, 'utf-8'));
589
732
  coreVersion = corePkg.version;
590
733
  } catch {
@@ -594,7 +737,7 @@ export async function checkVersions(
594
737
  // Get RFDB version from server if running
595
738
  const socketPath = join(projectPath, '.grafema', 'rfdb.sock');
596
739
  if (existsSync(socketPath)) {
597
- const client = new RFDBClient(socketPath);
740
+ const client = new RFDBClient(socketPath, 'cli');
598
741
  client.on('error', () => {});
599
742
  try {
600
743
  await client.connect();
@@ -2,20 +2,22 @@
2
2
  * Doctor command - Diagnose Grafema setup issues
3
3
  *
4
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
5
+ * 1. Binaries (rfdb-server, grafema-orchestrator)
6
+ * 2. Initialization (.grafema directory, config file)
7
+ * 3. Config validity (syntax, plugin names)
8
+ * 4. Entrypoints (service paths exist)
9
+ * 5. Server status (RFDB server running)
10
+ * 6. Database exists and has data
11
+ * 7. Graph statistics
12
+ * 8. Graph connectivity
13
+ * 9. Graph freshness
14
+ * 10. Version information
14
15
  */
15
16
 
16
17
  import { Command } from 'commander';
17
18
  import { resolve } from 'path';
18
19
  import {
20
+ checkBinaries,
19
21
  checkGrafemaInitialized,
20
22
  checkServerStatus,
21
23
  checkConfigValidity,
@@ -47,6 +49,8 @@ Examples:
47
49
  const checks: DoctorCheckResult[] = [];
48
50
 
49
51
  // Level 1: Prerequisites (fail-fast)
52
+ checks.push(await checkBinaries());
53
+
50
54
  const initCheck = await checkGrafemaInitialized(projectPath);
51
55
  checks.push(initCheck);
52
56
 
@@ -16,7 +16,7 @@ import { Command } from 'commander';
16
16
  import { resolve, join, relative, normalize } from 'path';
17
17
  import { existsSync, realpathSync } from 'fs';
18
18
  import { toRelativeDisplay } from '../utils/pathUtils.js';
19
- import { RFDBServerBackend, FileExplainer, type EnhancedNode } from '@grafema/core';
19
+ import { RFDBServerBackend, FileExplainer, type EnhancedNode } from '@grafema/util';
20
20
  import { exitWithError } from '../utils/errorFormatter.js';
21
21
 
22
22
  interface ExplainOptions {
@@ -81,7 +81,7 @@ If a file shows NOT_ANALYZED:
81
81
  // Keep relative path for display
82
82
  const relativeFilePath = relative(projectPath, absoluteFilePath);
83
83
 
84
- const backend = new RFDBServerBackend({ dbPath });
84
+ const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
85
85
  await backend.connect();
86
86
 
87
87
  try {
@@ -15,7 +15,7 @@ import { existsSync } from 'fs';
15
15
  import { execSync } from 'child_process';
16
16
  import React, { useState, useEffect } from 'react';
17
17
  import { render, Box, Text, useInput, useApp } from 'ink';
18
- import { RFDBServerBackend, findContainingFunction as findContainingFunctionCore, findCallsInFunction as findCallsInFunctionCore } from '@grafema/core';
18
+ import { RFDBServerBackend, findContainingFunction as findContainingFunctionCore, findCallsInFunction as findCallsInFunctionCore } from '@grafema/util';
19
19
  import { getCodePreview, formatCodePreview } from '../utils/codePreview.js';
20
20
  import { exitWithError } from '../utils/errorFormatter.js';
21
21
 
@@ -816,7 +816,7 @@ async function getCallees(backend: RFDBServerBackend, nodeId: string, limit: num
816
816
  const seen = new Set<string>();
817
817
 
818
818
  try {
819
- // Use shared utility from @grafema/core
819
+ // Use shared utility from @grafema/util
820
820
  const calls = await findCallsInFunctionCore(backend, nodeId);
821
821
 
822
822
  for (const call of calls) {
@@ -14,8 +14,8 @@
14
14
  import { Command } from 'commander';
15
15
  import { resolve, join, relative, normalize } from 'path';
16
16
  import { existsSync, realpathSync } from 'fs';
17
- import { RFDBServerBackend, FileOverview } from '@grafema/core';
18
- import type { FileOverviewResult, FunctionOverview } from '@grafema/core';
17
+ import { RFDBServerBackend, FileOverview } from '@grafema/util';
18
+ import type { FileOverviewResult, FunctionOverview } from '@grafema/util';
19
19
  import { exitWithError } from '../utils/errorFormatter.js';
20
20
  import { Spinner } from '../utils/spinner.js';
21
21
 
@@ -79,7 +79,7 @@ Use 'grafema context <id>' to dive deeper into any specific entity.
79
79
  const absoluteFilePath = realpathSync(resolvedPath);
80
80
  const relativeFilePath = relative(projectPath, absoluteFilePath);
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 file overview...');
@@ -9,7 +9,7 @@
9
9
  import { Command } from 'commander';
10
10
  import { resolve, join } from 'path';
11
11
  import { existsSync } from 'fs';
12
- import { RFDBServerBackend } from '@grafema/core';
12
+ import { RFDBServerBackend } from '@grafema/util';
13
13
  import { formatNodeDisplay } from '../utils/formatNode.js';
14
14
  import { exitWithError } from '../utils/errorFormatter.js';
15
15
  import { Spinner } from '../utils/spinner.js';
@@ -64,7 +64,7 @@ Examples:
64
64
  exitWithError('No graph database found', ['Run: grafema analyze']);
65
65
  }
66
66
 
67
- const backend = new RFDBServerBackend({ dbPath });
67
+ const backend = new RFDBServerBackend({ dbPath, clientName: 'cli' });
68
68
  await backend.connect();
69
69
 
70
70
  const spinner = new Spinner('Querying graph...');
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Git Ingest command - ingest git history into knowledge layer
3
+ */
4
+
5
+ import { Command } from 'commander';
6
+ import { resolve, join } from 'path';
7
+ import { GitIngest } from '@grafema/util';
8
+
9
+ export const gitIngestCommand = new Command('git-ingest')
10
+ .description('Ingest git history into the knowledge layer')
11
+ .argument('[path]', 'Repository path', '.')
12
+ .option('--full', 'Full re-ingest (rebuilds derived/)')
13
+ .option('--since <date>', 'Ingest from date (ISO format)')
14
+ .option('--branch <branch>', 'Ingest specific branch')
15
+ .addHelpText('after', `
16
+ Examples:
17
+ grafema git-ingest Incremental ingest from last cursor
18
+ grafema git-ingest --full Full re-ingest of all history
19
+ grafema git-ingest --full ./my-repo Full ingest of specific repo
20
+ grafema git-ingest --branch develop Ingest specific branch
21
+ grafema git-ingest --since 2025-01-01 Ingest from specific date
22
+ `)
23
+ .action(async (path: string, options: { full?: boolean; since?: string; branch?: string }) => {
24
+ const repoPath = resolve(path);
25
+ const knowledgeDir = join(repoPath, '.grafema', 'knowledge');
26
+
27
+ const ingest = new GitIngest(knowledgeDir);
28
+
29
+ console.log(`Ingesting git history from ${repoPath}...`);
30
+
31
+ try {
32
+ let result;
33
+ if (options.full || options.since) {
34
+ result = await ingest.ingestFull(repoPath, options.branch, options.since);
35
+ } else {
36
+ result = await ingest.ingestIncremental(repoPath, options.branch);
37
+ }
38
+
39
+ console.log('Git ingest complete:');
40
+ console.log(` Commits: ${result.commits}`);
41
+ console.log(` Authors: ${result.authors}`);
42
+ console.log(` Files changed: ${result.filesChanged}`);
43
+ console.log(`\nData written to ${knowledgeDir}/derived/`);
44
+ } catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ console.error(`Error: ${message}`);
47
+ process.exit(1);
48
+ }
49
+ });