@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.
- package/dist/analysis-worker.d.ts +4 -3
- package/dist/analysis-worker.d.ts.map +1 -1
- package/dist/analysis-worker.js +8 -203
- package/dist/analysis-worker.js.map +1 -1
- package/dist/analysis.d.ts +10 -3
- package/dist/analysis.d.ts.map +1 -1
- package/dist/analysis.js +130 -62
- package/dist/analysis.js.map +1 -1
- package/dist/config.d.ts +5 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -128
- package/dist/config.js.map +1 -1
- package/dist/definitions/analysis-tools.d.ts +6 -0
- package/dist/definitions/analysis-tools.d.ts.map +1 -0
- package/dist/definitions/analysis-tools.js +125 -0
- package/dist/definitions/analysis-tools.js.map +1 -0
- package/dist/definitions/context-tools.d.ts +6 -0
- package/dist/definitions/context-tools.d.ts.map +1 -0
- package/dist/definitions/context-tools.js +144 -0
- package/dist/definitions/context-tools.js.map +1 -0
- package/dist/definitions/graph-tools.d.ts +7 -0
- package/dist/definitions/graph-tools.d.ts.map +1 -0
- package/dist/definitions/graph-tools.js +124 -0
- package/dist/definitions/graph-tools.js.map +1 -0
- package/dist/definitions/graphql-tools.d.ts +6 -0
- package/dist/definitions/graphql-tools.d.ts.map +1 -0
- package/dist/definitions/graphql-tools.js +62 -0
- package/dist/definitions/graphql-tools.js.map +1 -0
- package/dist/definitions/guarantee-tools.d.ts +6 -0
- package/dist/definitions/guarantee-tools.d.ts.map +1 -0
- package/dist/definitions/guarantee-tools.js +136 -0
- package/dist/definitions/guarantee-tools.js.map +1 -0
- package/dist/definitions/index.d.ts +7 -0
- package/dist/definitions/index.d.ts.map +1 -0
- package/dist/definitions/index.js +24 -0
- package/dist/definitions/index.js.map +1 -0
- package/dist/definitions/knowledge-tools.d.ts +10 -0
- package/dist/definitions/knowledge-tools.d.ts.map +1 -0
- package/dist/definitions/knowledge-tools.js +300 -0
- package/dist/definitions/knowledge-tools.js.map +1 -0
- package/dist/definitions/notation-tools.d.ts +9 -0
- package/dist/definitions/notation-tools.d.ts.map +1 -0
- package/dist/definitions/notation-tools.js +62 -0
- package/dist/definitions/notation-tools.js.map +1 -0
- package/dist/definitions/project-tools.d.ts +6 -0
- package/dist/definitions/project-tools.d.ts.map +1 -0
- package/dist/definitions/project-tools.js +181 -0
- package/dist/definitions/project-tools.js.map +1 -0
- package/dist/definitions/query-tools.d.ts +6 -0
- package/dist/definitions/query-tools.d.ts.map +1 -0
- package/dist/definitions/query-tools.js +245 -0
- package/dist/definitions/query-tools.js.map +1 -0
- package/dist/definitions/types.d.ts +21 -0
- package/dist/definitions/types.d.ts.map +1 -0
- package/dist/definitions/types.js +5 -0
- package/dist/definitions/types.js.map +1 -0
- package/dist/dev-proxy.d.ts +29 -0
- package/dist/dev-proxy.d.ts.map +1 -0
- package/dist/dev-proxy.js +267 -0
- package/dist/dev-proxy.js.map +1 -0
- package/dist/handlers/analysis-handlers.d.ts.map +1 -1
- package/dist/handlers/analysis-handlers.js +34 -4
- package/dist/handlers/analysis-handlers.js.map +1 -1
- package/dist/handlers/context-handlers.d.ts +5 -6
- package/dist/handlers/context-handlers.d.ts.map +1 -1
- package/dist/handlers/context-handlers.js +19 -16
- package/dist/handlers/context-handlers.js.map +1 -1
- package/dist/handlers/coverage-handlers.js +1 -1
- package/dist/handlers/dataflow-handlers.d.ts +2 -0
- package/dist/handlers/dataflow-handlers.d.ts.map +1 -1
- package/dist/handlers/dataflow-handlers.js +68 -46
- package/dist/handlers/dataflow-handlers.js.map +1 -1
- package/dist/handlers/documentation-handlers.d.ts.map +1 -1
- package/dist/handlers/documentation-handlers.js +56 -2
- package/dist/handlers/documentation-handlers.js.map +1 -1
- package/dist/handlers/graph-handlers.d.ts +23 -0
- package/dist/handlers/graph-handlers.d.ts.map +1 -0
- package/dist/handlers/graph-handlers.js +155 -0
- package/dist/handlers/graph-handlers.js.map +1 -0
- package/dist/handlers/graphql-handlers.d.ts +9 -0
- package/dist/handlers/graphql-handlers.d.ts.map +1 -0
- package/dist/handlers/graphql-handlers.js +57 -0
- package/dist/handlers/graphql-handlers.js.map +1 -0
- package/dist/handlers/guarantee-handlers.js +1 -1
- package/dist/handlers/guard-handlers.d.ts.map +1 -1
- package/dist/handlers/guard-handlers.js +6 -3
- package/dist/handlers/guard-handlers.js.map +1 -1
- package/dist/handlers/index.d.ts +4 -0
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/issue-handlers.d.ts.map +1 -1
- package/dist/handlers/issue-handlers.js +10 -15
- package/dist/handlers/issue-handlers.js.map +1 -1
- package/dist/handlers/knowledge-handlers.d.ts +25 -0
- package/dist/handlers/knowledge-handlers.d.ts.map +1 -0
- package/dist/handlers/knowledge-handlers.js +208 -0
- package/dist/handlers/knowledge-handlers.js.map +1 -0
- package/dist/handlers/notation-handlers.d.ts +6 -0
- package/dist/handlers/notation-handlers.d.ts.map +1 -0
- package/dist/handlers/notation-handlers.js +53 -0
- package/dist/handlers/notation-handlers.js.map +1 -0
- package/dist/handlers/project-handlers.js +1 -1
- package/dist/handlers/query-handlers.d.ts.map +1 -1
- package/dist/handlers/query-handlers.js +166 -20
- package/dist/handlers/query-handlers.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/server.d.ts +19 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +93 -3
- package/dist/server.js.map +1 -1
- package/dist/state.d.ts +10 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +61 -8
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +75 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +18 -1
- package/dist/utils.js.map +1 -1
- package/package.json +4 -3
- package/src/analysis-worker.ts +9 -301
- package/src/analysis.ts +151 -77
- package/src/config.ts +6 -193
- package/src/definitions/analysis-tools.ts +127 -0
- package/src/definitions/context-tools.ts +147 -0
- package/src/definitions/graph-tools.ts +126 -0
- package/src/definitions/graphql-tools.ts +64 -0
- package/src/definitions/guarantee-tools.ts +138 -0
- package/src/definitions/index.ts +28 -0
- package/src/definitions/knowledge-tools.ts +302 -0
- package/src/definitions/notation-tools.ts +64 -0
- package/src/definitions/project-tools.ts +183 -0
- package/src/definitions/query-tools.ts +247 -0
- package/src/definitions/types.ts +22 -0
- package/src/dev-proxy.ts +336 -0
- package/src/handlers/analysis-handlers.ts +35 -4
- package/src/handlers/context-handlers.ts +19 -15
- package/src/handlers/coverage-handlers.ts +1 -1
- package/src/handlers/dataflow-handlers.ts +74 -56
- package/src/handlers/documentation-handlers.ts +56 -2
- package/src/handlers/graph-handlers.ts +212 -0
- package/src/handlers/graphql-handlers.ts +70 -0
- package/src/handlers/guarantee-handlers.ts +1 -1
- package/src/handlers/guard-handlers.ts +7 -3
- package/src/handlers/index.ts +6 -0
- package/src/handlers/issue-handlers.ts +10 -15
- package/src/handlers/knowledge-handlers.ts +242 -0
- package/src/handlers/notation-handlers.ts +71 -0
- package/src/handlers/project-handlers.ts +1 -1
- package/src/handlers/query-handlers.ts +186 -22
- package/src/prompts.ts +1 -1
- package/src/server.ts +126 -2
- package/src/state.ts +68 -8
- package/src/types.ts +98 -3
- package/src/utils.ts +22 -1
- package/src/definitions.ts +0 -665
package/src/dev-proxy.ts
ADDED
|
@@ -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,
|
|
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
|
|
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
|
|
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/
|
|
8
|
-
import type { CallInfo, CallerInfo, NodeContext } from '@grafema/
|
|
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
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* FUNCTION
|
|
32
|
-
*
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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: "${
|
|
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(
|
|
331
|
+
const result = await overview.getOverview(relativePath, {
|
|
328
332
|
includeEdges,
|
|
329
333
|
});
|
|
330
334
|
|