@grafema/mcp 0.2.12-beta → 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 (158) hide show
  1. package/dist/analysis-worker.d.ts +4 -3
  2. package/dist/analysis-worker.d.ts.map +1 -1
  3. package/dist/analysis-worker.js +8 -203
  4. package/dist/analysis-worker.js.map +1 -1
  5. package/dist/analysis.d.ts +10 -3
  6. package/dist/analysis.d.ts.map +1 -1
  7. package/dist/analysis.js +130 -62
  8. package/dist/analysis.js.map +1 -1
  9. package/dist/config.d.ts +5 -11
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +6 -128
  12. package/dist/config.js.map +1 -1
  13. package/dist/definitions/analysis-tools.d.ts +6 -0
  14. package/dist/definitions/analysis-tools.d.ts.map +1 -0
  15. package/dist/definitions/analysis-tools.js +125 -0
  16. package/dist/definitions/analysis-tools.js.map +1 -0
  17. package/dist/definitions/context-tools.d.ts +6 -0
  18. package/dist/definitions/context-tools.d.ts.map +1 -0
  19. package/dist/definitions/context-tools.js +144 -0
  20. package/dist/definitions/context-tools.js.map +1 -0
  21. package/dist/definitions/graph-tools.d.ts +7 -0
  22. package/dist/definitions/graph-tools.d.ts.map +1 -0
  23. package/dist/definitions/graph-tools.js +124 -0
  24. package/dist/definitions/graph-tools.js.map +1 -0
  25. package/dist/definitions/graphql-tools.d.ts +6 -0
  26. package/dist/definitions/graphql-tools.d.ts.map +1 -0
  27. package/dist/definitions/graphql-tools.js +62 -0
  28. package/dist/definitions/graphql-tools.js.map +1 -0
  29. package/dist/definitions/guarantee-tools.d.ts +6 -0
  30. package/dist/definitions/guarantee-tools.d.ts.map +1 -0
  31. package/dist/definitions/guarantee-tools.js +136 -0
  32. package/dist/definitions/guarantee-tools.js.map +1 -0
  33. package/dist/definitions/index.d.ts +7 -0
  34. package/dist/definitions/index.d.ts.map +1 -0
  35. package/dist/definitions/index.js +24 -0
  36. package/dist/definitions/index.js.map +1 -0
  37. package/dist/definitions/knowledge-tools.d.ts +10 -0
  38. package/dist/definitions/knowledge-tools.d.ts.map +1 -0
  39. package/dist/definitions/knowledge-tools.js +300 -0
  40. package/dist/definitions/knowledge-tools.js.map +1 -0
  41. package/dist/definitions/notation-tools.d.ts +9 -0
  42. package/dist/definitions/notation-tools.d.ts.map +1 -0
  43. package/dist/definitions/notation-tools.js +62 -0
  44. package/dist/definitions/notation-tools.js.map +1 -0
  45. package/dist/definitions/project-tools.d.ts +6 -0
  46. package/dist/definitions/project-tools.d.ts.map +1 -0
  47. package/dist/definitions/project-tools.js +181 -0
  48. package/dist/definitions/project-tools.js.map +1 -0
  49. package/dist/definitions/query-tools.d.ts +6 -0
  50. package/dist/definitions/query-tools.d.ts.map +1 -0
  51. package/dist/definitions/query-tools.js +245 -0
  52. package/dist/definitions/query-tools.js.map +1 -0
  53. package/dist/definitions/types.d.ts +21 -0
  54. package/dist/definitions/types.d.ts.map +1 -0
  55. package/dist/definitions/types.js +5 -0
  56. package/dist/definitions/types.js.map +1 -0
  57. package/dist/dev-proxy.d.ts +29 -0
  58. package/dist/dev-proxy.d.ts.map +1 -0
  59. package/dist/dev-proxy.js +267 -0
  60. package/dist/dev-proxy.js.map +1 -0
  61. package/dist/handlers/analysis-handlers.d.ts.map +1 -1
  62. package/dist/handlers/analysis-handlers.js +34 -4
  63. package/dist/handlers/analysis-handlers.js.map +1 -1
  64. package/dist/handlers/context-handlers.d.ts +5 -6
  65. package/dist/handlers/context-handlers.d.ts.map +1 -1
  66. package/dist/handlers/context-handlers.js +19 -16
  67. package/dist/handlers/context-handlers.js.map +1 -1
  68. package/dist/handlers/coverage-handlers.js +1 -1
  69. package/dist/handlers/dataflow-handlers.d.ts +2 -0
  70. package/dist/handlers/dataflow-handlers.d.ts.map +1 -1
  71. package/dist/handlers/dataflow-handlers.js +68 -46
  72. package/dist/handlers/dataflow-handlers.js.map +1 -1
  73. package/dist/handlers/documentation-handlers.d.ts.map +1 -1
  74. package/dist/handlers/documentation-handlers.js +56 -2
  75. package/dist/handlers/documentation-handlers.js.map +1 -1
  76. package/dist/handlers/graph-handlers.d.ts +23 -0
  77. package/dist/handlers/graph-handlers.d.ts.map +1 -0
  78. package/dist/handlers/graph-handlers.js +155 -0
  79. package/dist/handlers/graph-handlers.js.map +1 -0
  80. package/dist/handlers/graphql-handlers.d.ts +9 -0
  81. package/dist/handlers/graphql-handlers.d.ts.map +1 -0
  82. package/dist/handlers/graphql-handlers.js +57 -0
  83. package/dist/handlers/graphql-handlers.js.map +1 -0
  84. package/dist/handlers/guarantee-handlers.js +1 -1
  85. package/dist/handlers/guard-handlers.d.ts.map +1 -1
  86. package/dist/handlers/guard-handlers.js +6 -3
  87. package/dist/handlers/guard-handlers.js.map +1 -1
  88. package/dist/handlers/index.d.ts +4 -0
  89. package/dist/handlers/index.d.ts.map +1 -1
  90. package/dist/handlers/index.js +6 -0
  91. package/dist/handlers/index.js.map +1 -1
  92. package/dist/handlers/issue-handlers.d.ts.map +1 -1
  93. package/dist/handlers/issue-handlers.js +10 -15
  94. package/dist/handlers/issue-handlers.js.map +1 -1
  95. package/dist/handlers/knowledge-handlers.d.ts +25 -0
  96. package/dist/handlers/knowledge-handlers.d.ts.map +1 -0
  97. package/dist/handlers/knowledge-handlers.js +208 -0
  98. package/dist/handlers/knowledge-handlers.js.map +1 -0
  99. package/dist/handlers/notation-handlers.d.ts +6 -0
  100. package/dist/handlers/notation-handlers.d.ts.map +1 -0
  101. package/dist/handlers/notation-handlers.js +53 -0
  102. package/dist/handlers/notation-handlers.js.map +1 -0
  103. package/dist/handlers/project-handlers.js +1 -1
  104. package/dist/handlers/query-handlers.d.ts.map +1 -1
  105. package/dist/handlers/query-handlers.js +166 -20
  106. package/dist/handlers/query-handlers.js.map +1 -1
  107. package/dist/prompts.js +1 -1
  108. package/dist/server.d.ts +19 -1
  109. package/dist/server.d.ts.map +1 -1
  110. package/dist/server.js +93 -3
  111. package/dist/server.js.map +1 -1
  112. package/dist/state.d.ts +10 -1
  113. package/dist/state.d.ts.map +1 -1
  114. package/dist/state.js +61 -8
  115. package/dist/state.js.map +1 -1
  116. package/dist/types.d.ts +75 -3
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/utils.d.ts +4 -0
  119. package/dist/utils.d.ts.map +1 -1
  120. package/dist/utils.js +18 -1
  121. package/dist/utils.js.map +1 -1
  122. package/package.json +4 -3
  123. package/src/analysis-worker.ts +9 -301
  124. package/src/analysis.ts +151 -77
  125. package/src/config.ts +6 -193
  126. package/src/definitions/analysis-tools.ts +127 -0
  127. package/src/definitions/context-tools.ts +147 -0
  128. package/src/definitions/graph-tools.ts +126 -0
  129. package/src/definitions/graphql-tools.ts +64 -0
  130. package/src/definitions/guarantee-tools.ts +138 -0
  131. package/src/definitions/index.ts +28 -0
  132. package/src/definitions/knowledge-tools.ts +302 -0
  133. package/src/definitions/notation-tools.ts +64 -0
  134. package/src/definitions/project-tools.ts +183 -0
  135. package/src/definitions/query-tools.ts +247 -0
  136. package/src/definitions/types.ts +22 -0
  137. package/src/dev-proxy.ts +336 -0
  138. package/src/handlers/analysis-handlers.ts +35 -4
  139. package/src/handlers/context-handlers.ts +19 -15
  140. package/src/handlers/coverage-handlers.ts +1 -1
  141. package/src/handlers/dataflow-handlers.ts +74 -56
  142. package/src/handlers/documentation-handlers.ts +56 -2
  143. package/src/handlers/graph-handlers.ts +212 -0
  144. package/src/handlers/graphql-handlers.ts +70 -0
  145. package/src/handlers/guarantee-handlers.ts +1 -1
  146. package/src/handlers/guard-handlers.ts +7 -3
  147. package/src/handlers/index.ts +6 -0
  148. package/src/handlers/issue-handlers.ts +10 -15
  149. package/src/handlers/knowledge-handlers.ts +242 -0
  150. package/src/handlers/notation-handlers.ts +71 -0
  151. package/src/handlers/project-handlers.ts +1 -1
  152. package/src/handlers/query-handlers.ts +186 -22
  153. package/src/prompts.ts +1 -1
  154. package/src/server.ts +126 -2
  155. package/src/state.ts +68 -8
  156. package/src/types.ts +98 -3
  157. package/src/utils.ts +22 -1
  158. package/src/definitions.ts +0 -665
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Grafema MCP Dev Proxy
4
+ *
5
+ * A thin stdio proxy that sits between Claude Code and the real MCP server.
6
+ * It transparently forwards all JSON-RPC messages, with two exceptions:
7
+ *
8
+ * 1. Intercepts `tools/call` with `name: "reload"` — kills and respawns the
9
+ * child server process so it picks up new code from dist/ after `pnpm build`.
10
+ *
11
+ * 2. Injects a `reload` tool definition into `tools/list` responses from the
12
+ * real server.
13
+ *
14
+ * Architecture:
15
+ * Claude Code <--stdio--> dev-proxy <--stdio--> real server.js
16
+ *
17
+ * Usage in .claude/mcp.json:
18
+ * {
19
+ * "grafema-dev": {
20
+ * "command": "node",
21
+ * "args": ["packages/mcp/dist/dev-proxy.js", "--project", "/path/to/project"]
22
+ * }
23
+ * }
24
+ *
25
+ * The MCP SDK uses newline-delimited JSON framing (each message is a single
26
+ * JSON object followed by '\n'). No Content-Length headers.
27
+ */
28
+
29
+ import { spawn, type ChildProcess } from 'node:child_process';
30
+ import { dirname, join } from 'node:path';
31
+ import { fileURLToPath } from 'node:url';
32
+
33
+ const __filename = fileURLToPath(import.meta.url);
34
+ const __dirname = dirname(__filename);
35
+
36
+ // ── Constants ──────────────────────────────────────────────────────────
37
+
38
+ const RELOAD_TOOL_DEFINITION = {
39
+ name: 'reload',
40
+ description:
41
+ 'Hot-reload the MCP server (restarts the process to pick up new dist/ code after pnpm build)',
42
+ inputSchema: { type: 'object' as const, properties: {} },
43
+ };
44
+
45
+ /** Path to the real MCP server entry point (co-located in dist/) */
46
+ const SERVER_SCRIPT = join(__dirname, 'server.js');
47
+
48
+ /** All CLI args passed to this proxy are forwarded to the child server */
49
+ const CHILD_ARGS = process.argv.slice(2);
50
+
51
+ // ── Logging (stderr only — stdout is the JSON-RPC channel) ────────────
52
+
53
+ function log(msg: string): void {
54
+ process.stderr.write(`[dev-proxy] ${msg}\n`);
55
+ }
56
+
57
+ // ── Child Process Management ───────────────────────────────────────────
58
+
59
+ let child: ChildProcess | null = null;
60
+ let childReady = false;
61
+ /** Messages buffered while child is (re)starting */
62
+ let pendingMessages: string[] = [];
63
+ /** Track in-flight requests so we can send error responses on crash */
64
+ const inflightRequests = new Map<string | number, boolean>();
65
+
66
+ /**
67
+ * Spawn the real MCP server as a child process.
68
+ * stdin/stdout are piped for JSON-RPC; stderr is inherited for debug logs.
69
+ */
70
+ function spawnChild(): void {
71
+ log(`Spawning child: node ${SERVER_SCRIPT} ${CHILD_ARGS.join(' ')}`);
72
+
73
+ child = spawn('node', [SERVER_SCRIPT, ...CHILD_ARGS], {
74
+ stdio: ['pipe', 'pipe', 'inherit'],
75
+ });
76
+
77
+ childReady = true;
78
+
79
+ // ── Child stdout → proxy stdout (with tools/list injection) ──
80
+ let childBuffer = '';
81
+
82
+ child.stdout!.on('data', (chunk: Buffer) => {
83
+ childBuffer += chunk.toString('utf8');
84
+
85
+ // Process complete newline-delimited messages
86
+ let newlineIdx: number;
87
+ while ((newlineIdx = childBuffer.indexOf('\n')) !== -1) {
88
+ const line = childBuffer.slice(0, newlineIdx);
89
+ childBuffer = childBuffer.slice(newlineIdx + 1);
90
+
91
+ if (line.trim().length === 0) continue;
92
+
93
+ try {
94
+ const msg = JSON.parse(line);
95
+
96
+ // Remove from in-flight tracking if this is a response
97
+ if ('id' in msg && msg.id !== undefined) {
98
+ inflightRequests.delete(msg.id);
99
+ }
100
+
101
+ // Inject reload tool into tools/list responses
102
+ if (isToolsListResponse(msg)) {
103
+ msg.result.tools.push(RELOAD_TOOL_DEFINITION);
104
+ }
105
+
106
+ process.stdout.write(JSON.stringify(msg) + '\n');
107
+ } catch {
108
+ // If we can't parse it, forward raw (shouldn't happen with well-formed server)
109
+ process.stdout.write(line + '\n');
110
+ }
111
+ }
112
+ });
113
+
114
+ // ── Child exit handling ──
115
+ child.on('exit', (code, signal) => {
116
+ log(`Child exited: code=${code}, signal=${signal}`);
117
+ childReady = false;
118
+
119
+ // Send error responses for any in-flight requests
120
+ for (const [id] of inflightRequests) {
121
+ sendErrorResponse(id, -32000, 'MCP server process crashed');
122
+ }
123
+ inflightRequests.clear();
124
+
125
+ child = null;
126
+
127
+ // Auto-restart unless we're shutting down
128
+ if (!shuttingDown) {
129
+ log('Auto-restarting child in 500ms...');
130
+ setTimeout(() => {
131
+ spawnChild();
132
+ flushPendingMessages();
133
+ }, 500);
134
+ }
135
+ });
136
+
137
+ child.on('error', (err) => {
138
+ log(`Child spawn error: ${err.message}`);
139
+ childReady = false;
140
+ child = null;
141
+ });
142
+
143
+ // Flush any messages that arrived while child was restarting
144
+ flushPendingMessages();
145
+ }
146
+
147
+ function flushPendingMessages(): void {
148
+ if (!child || !childReady || pendingMessages.length === 0) return;
149
+ log(`Flushing ${pendingMessages.length} pending message(s)`);
150
+ for (const msg of pendingMessages) {
151
+ child.stdin!.write(msg + '\n');
152
+ }
153
+ pendingMessages = [];
154
+ }
155
+
156
+ function killChild(): Promise<void> {
157
+ return new Promise((resolve) => {
158
+ if (!child) {
159
+ resolve();
160
+ return;
161
+ }
162
+
163
+ const c = child;
164
+ // Prevent auto-restart during intentional reload
165
+ const onExit = () => {
166
+ child = null;
167
+ childReady = false;
168
+ resolve();
169
+ };
170
+
171
+ c.once('exit', onExit);
172
+ c.kill('SIGTERM');
173
+
174
+ // Force kill after 3 seconds
175
+ setTimeout(() => {
176
+ if (c.exitCode === null && c.signalCode === null) {
177
+ log('Child did not exit after SIGTERM, sending SIGKILL');
178
+ c.kill('SIGKILL');
179
+ }
180
+ }, 3000);
181
+ });
182
+ }
183
+
184
+ // ── JSON-RPC Helpers ───────────────────────────────────────────────────
185
+
186
+ interface JsonRpcRequest {
187
+ jsonrpc: '2.0';
188
+ id?: string | number;
189
+ method: string;
190
+ params?: Record<string, unknown>;
191
+ }
192
+
193
+ interface JsonRpcResponse {
194
+ jsonrpc: '2.0';
195
+ id: string | number;
196
+ result?: unknown;
197
+ error?: { code: number; message: string; data?: unknown };
198
+ }
199
+
200
+ function isToolsCallReload(msg: JsonRpcRequest): boolean {
201
+ return (
202
+ msg.method === 'tools/call' &&
203
+ msg.params != null &&
204
+ (msg.params as Record<string, unknown>).name === 'reload'
205
+ );
206
+ }
207
+
208
+ function isToolsListResponse(msg: unknown): msg is JsonRpcResponse & {
209
+ result: { tools: Array<{ name: string }> };
210
+ } {
211
+ const m = msg as Record<string, unknown>;
212
+ if (!m || typeof m !== 'object') return false;
213
+ if (!('result' in m) || !m.result) return false;
214
+ const result = m.result as Record<string, unknown>;
215
+ return Array.isArray(result.tools);
216
+ }
217
+
218
+ function sendSuccessResponse(id: string | number, result: unknown): void {
219
+ const response: JsonRpcResponse = {
220
+ jsonrpc: '2.0',
221
+ id,
222
+ result,
223
+ };
224
+ process.stdout.write(JSON.stringify(response) + '\n');
225
+ }
226
+
227
+ function sendErrorResponse(
228
+ id: string | number,
229
+ code: number,
230
+ message: string
231
+ ): void {
232
+ const response: JsonRpcResponse = {
233
+ jsonrpc: '2.0',
234
+ id,
235
+ error: { code, message },
236
+ };
237
+ process.stdout.write(JSON.stringify(response) + '\n');
238
+ }
239
+
240
+ // ── Reload Handler ─────────────────────────────────────────────────────
241
+
242
+ async function handleReload(requestId: string | number): Promise<void> {
243
+ log('Reload requested — restarting child process...');
244
+
245
+ try {
246
+ // Suppress auto-restart during intentional kill
247
+ shuttingDown = true;
248
+ await killChild();
249
+ shuttingDown = false;
250
+
251
+ spawnChild();
252
+
253
+ sendSuccessResponse(requestId, {
254
+ content: [
255
+ {
256
+ type: 'text',
257
+ text: 'MCP server reloaded successfully. New dist/ code is now active.',
258
+ },
259
+ ],
260
+ });
261
+ } catch (err) {
262
+ shuttingDown = false;
263
+ const message = err instanceof Error ? err.message : String(err);
264
+ log(`Reload failed: ${message}`);
265
+ sendErrorResponse(requestId, -32000, `Reload failed: ${message}`);
266
+ }
267
+ }
268
+
269
+ // ── Main: stdin → child (with reload interception) ─────────────────────
270
+
271
+ let shuttingDown = false;
272
+ let stdinBuffer = '';
273
+
274
+ process.stdin.setEncoding('utf8');
275
+ process.stdin.on('data', (chunk: string) => {
276
+ stdinBuffer += chunk;
277
+
278
+ let newlineIdx: number;
279
+ while ((newlineIdx = stdinBuffer.indexOf('\n')) !== -1) {
280
+ const line = stdinBuffer.slice(0, newlineIdx);
281
+ stdinBuffer = stdinBuffer.slice(newlineIdx + 1);
282
+
283
+ if (line.trim().length === 0) continue;
284
+
285
+ try {
286
+ const msg = JSON.parse(line) as JsonRpcRequest;
287
+
288
+ // Intercept reload tool call
289
+ if (msg.method === 'tools/call' && isToolsCallReload(msg)) {
290
+ if (msg.id !== undefined) {
291
+ void handleReload(msg.id);
292
+ }
293
+ continue;
294
+ }
295
+
296
+ // Track request IDs for crash error responses
297
+ if ('id' in msg && msg.id !== undefined && 'method' in msg) {
298
+ inflightRequests.set(msg.id, true);
299
+ }
300
+
301
+ // Forward to child
302
+ if (child && childReady) {
303
+ child.stdin!.write(line + '\n');
304
+ } else {
305
+ pendingMessages.push(line);
306
+ }
307
+ } catch {
308
+ // Not valid JSON — forward raw (shouldn't happen with well-formed client)
309
+ if (child && childReady) {
310
+ child.stdin!.write(line + '\n');
311
+ } else {
312
+ pendingMessages.push(line);
313
+ }
314
+ }
315
+ }
316
+ });
317
+
318
+ // ── Graceful Shutdown ──────────────────────────────────────────────────
319
+
320
+ async function shutdown(): Promise<void> {
321
+ if (shuttingDown) return;
322
+ shuttingDown = true;
323
+ log('Shutting down...');
324
+
325
+ await killChild();
326
+ process.exit(0);
327
+ }
328
+
329
+ process.on('SIGINT', () => void shutdown());
330
+ process.on('SIGTERM', () => void shutdown());
331
+ process.stdin.on('end', () => void shutdown());
332
+
333
+ // ── Start ──────────────────────────────────────────────────────────────
334
+
335
+ log('Dev proxy starting');
336
+ spawnChild();
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { ensureAnalyzed } from '../analysis.js';
6
- import { getAnalysisStatus, getOrCreateBackend, isAnalysisRunning } from '../state.js';
6
+ import { getAnalysisStatus, isAnalysisRunning } from '../state.js';
7
7
  import {
8
8
  textResult,
9
9
  errorResult,
@@ -13,6 +13,7 @@ import type {
13
13
  AnalyzeProjectArgs,
14
14
  GetSchemaArgs,
15
15
  } from '../types.js';
16
+ import type { ServerStats } from '@grafema/types';
16
17
 
17
18
  // === ANALYSIS HANDLERS ===
18
19
 
@@ -62,24 +63,54 @@ export async function handleGetAnalysisStatus(): Promise<ToolResult> {
62
63
  }
63
64
 
64
65
  export async function handleGetStats(): Promise<ToolResult> {
65
- const db = await getOrCreateBackend();
66
+ const db = await ensureAnalyzed();
66
67
 
67
68
  const nodeCount = await db.nodeCount();
68
69
  const edgeCount = await db.edgeCount();
69
70
  const nodesByType = await db.countNodesByType();
70
71
  const edgesByType = await db.countEdgesByType();
71
72
 
73
+ let shardSection = '';
74
+ if ('getServerStats' in db && typeof (db as Record<string, unknown>).getServerStats === 'function') {
75
+ try {
76
+ const stats = await (db as { getServerStats(): Promise<ServerStats> }).getServerStats();
77
+ if (stats.shardDiagnostics?.length > 0) {
78
+ shardSection = `\nShard Diagnostics (${stats.shardDiagnostics.length} shards):\n`;
79
+ for (const s of stats.shardDiagnostics) {
80
+ const parts = [
81
+ `nodes=${s.nodeCount}`,
82
+ `edges=${s.edgeCount}`,
83
+ `wb=${s.writeBufferNodes}/${s.writeBufferEdges}`,
84
+ s.compacted ? `compacted (L1: ${s.l1NodeRecords}n/${s.l1EdgeRecords}e)` : `L0: ${s.l0NodeSegmentCount}n/${s.l0EdgeSegmentCount}e segs`,
85
+ ];
86
+ if (s.tombstoneNodeCount > 0 || s.tombstoneEdgeCount > 0) {
87
+ parts.push(`tombstones=${s.tombstoneNodeCount}n/${s.tombstoneEdgeCount}e`);
88
+ }
89
+ const indexes = [s.hasL1ByType && 'type', s.hasL1ByFile && 'file', s.hasL1ByName && 'name'].filter(Boolean);
90
+ if (indexes.length > 0) {
91
+ parts.push(`indexes=[${indexes.join(',')}]`);
92
+ }
93
+ shardSection += ` shard ${s.shardId}: ${parts.join(', ')}\n`;
94
+ }
95
+ shardSection += `\nServer: uptime=${stats.uptimeSecs}s, queries=${stats.queryCount}, memory=${stats.memoryPercent.toFixed(1)}%`;
96
+ }
97
+ } catch {
98
+ // Server may not support getStats yet — skip diagnostics
99
+ }
100
+ }
101
+
72
102
  return textResult(
73
103
  `Graph Statistics:\n\n` +
74
104
  `Total nodes: ${nodeCount.toLocaleString()}\n` +
75
105
  `Total edges: ${edgeCount.toLocaleString()}\n\n` +
76
106
  `Nodes by type:\n${JSON.stringify(nodesByType, null, 2)}\n\n` +
77
- `Edges by type:\n${JSON.stringify(edgesByType, null, 2)}`
107
+ `Edges by type:\n${JSON.stringify(edgesByType, null, 2)}` +
108
+ shardSection
78
109
  );
79
110
  }
80
111
 
81
112
  export async function handleGetSchema(args: GetSchemaArgs): Promise<ToolResult> {
82
- const db = await getOrCreateBackend();
113
+ const db = await ensureAnalyzed();
83
114
  const { type = 'all' } = args;
84
115
 
85
116
  const nodesByType = await db.countNodesByType();
@@ -4,8 +4,8 @@
4
4
 
5
5
  import { ensureAnalyzed } from '../analysis.js';
6
6
  import { getProjectPath } from '../state.js';
7
- import { findCallsInFunction, findContainingFunction, FileOverview, buildNodeContext, getNodeDisplayName, formatEdgeMetadata, STRUCTURAL_EDGE_TYPES } from '@grafema/core';
8
- import type { CallInfo, CallerInfo, NodeContext } from '@grafema/core';
7
+ import { findCallsInFunction, findContainingFunction, FileOverview, buildNodeContext, getNodeDisplayName, formatEdgeMetadata, STRUCTURAL_EDGE_TYPES, isGrafemaUri, toCompactSemanticId } from '@grafema/util';
8
+ import type { CallInfo, CallerInfo, NodeContext } from '@grafema/util';
9
9
  import { existsSync, readFileSync, realpathSync } from 'fs';
10
10
  import { isAbsolute, join, relative } from 'path';
11
11
  import {
@@ -26,12 +26,11 @@ import type {
26
26
  /**
27
27
  * Get comprehensive function details including calls made and callers.
28
28
  *
29
- * Graph structure:
30
- * ```
31
- * FUNCTION -[HAS_SCOPE]-> SCOPE -[CONTAINS]-> CALL/METHOD_CALL
32
- * SCOPE -[CONTAINS]-> SCOPE (nested blocks)
33
- * CALL -[CALLS]-> FUNCTION (target)
34
- * ```
29
+ * Supports two graph layouts (see findCallsInFunction for details):
30
+ * - Layout A: FUNCTION -> HAS_SCOPE -> SCOPE -> CONTAINS -> CALL (legacy)
31
+ * - Layout B: FUNCTION -> AWAITS|RETURNS|THROWS -> CALL (Rust orchestrator)
32
+ *
33
+ * In both layouts: CALL -[CALLS]-> FUNCTION (target)
35
34
  *
36
35
  * This is the core tool for understanding function behavior.
37
36
  * Use transitive=true to follow call chains (A -> B -> C).
@@ -42,12 +41,14 @@ export async function handleGetFunctionDetails(
42
41
  const db = await ensureAnalyzed();
43
42
  const { name, file, transitive = false } = args;
44
43
 
45
- // Step 1: Find the function
44
+ // Step 1: Find the function (search both FUNCTION and METHOD nodes)
46
45
  const candidates: GraphNode[] = [];
47
- for await (const node of db.queryNodes({ type: 'FUNCTION' })) {
48
- if (node.name !== name) continue;
49
- if (file && !node.file?.includes(file)) continue;
50
- candidates.push(node);
46
+ for (const nodeType of ['FUNCTION', 'METHOD']) {
47
+ for await (const node of db.queryNodes({ type: nodeType })) {
48
+ if (node.name !== name) continue;
49
+ if (file && !node.file?.includes(file)) continue;
50
+ candidates.push(node);
51
+ }
51
52
  }
52
53
 
53
54
  if (candidates.length === 0) {
@@ -168,11 +169,14 @@ export async function handleGetContext(
168
169
  const db = await ensureAnalyzed();
169
170
  const { semanticId, contextLines: ctxLines = 3, edgeType } = args;
170
171
 
172
+ // Accept both grafema:// URI and compact format
173
+ const displayId = isGrafemaUri(semanticId) ? toCompactSemanticId(semanticId) : semanticId;
174
+
171
175
  // 1. Look up node
172
176
  const node = await db.getNode(semanticId);
173
177
  if (!node) {
174
178
  return errorResult(
175
- `Node not found: "${semanticId}"\n` +
179
+ `Node not found: "${displayId}"\n` +
176
180
  `Use find_nodes or query_graph to find the correct semantic ID.`
177
181
  );
178
182
  }
@@ -324,7 +328,7 @@ export async function handleGetFileOverview(
324
328
 
325
329
  try {
326
330
  const overview = new FileOverview(db);
327
- const result = await overview.getOverview(absolutePath, {
331
+ const result = await overview.getOverview(relativePath, {
328
332
  includeEdges,
329
333
  });
330
334
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { getOrCreateBackend, getProjectPath } from '../state.js';
6
- import { CoverageAnalyzer } from '@grafema/core';
6
+ import { CoverageAnalyzer } from '@grafema/util';
7
7
  import {
8
8
  textResult,
9
9
  errorResult,