@vndv/pi-codegraph 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/extensions/codegraph.ts +116 -69
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -88,6 +88,8 @@ CodeGraph must be installed and available on `PATH`:
|
|
|
88
88
|
npm install -g @colbymchenry/codegraph
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
This extension is tested against the `@colbymchenry/codegraph` npm package declared in `devDependencies`. Dependabot watches that package and opens update PRs when CodeGraph releases a new version.
|
|
92
|
+
|
|
91
93
|
Each project must be indexed before pi can query it:
|
|
92
94
|
|
|
93
95
|
```bash
|
|
@@ -211,6 +213,8 @@ npm ci
|
|
|
211
213
|
npm run ci
|
|
212
214
|
```
|
|
213
215
|
|
|
216
|
+
`npm run ci` type-checks the extension, runs tests, verifies the pinned CodeGraph CLI can start, and dry-runs the npm package.
|
|
217
|
+
|
|
214
218
|
Install the local checkout into pi:
|
|
215
219
|
|
|
216
220
|
```bash
|
package/extensions/codegraph.ts
CHANGED
|
@@ -135,6 +135,10 @@ const ToolDefinitions = [
|
|
|
135
135
|
type ToolName = (typeof ToolDefinitions)[number]["name"];
|
|
136
136
|
type ToolParams = Record<string, unknown> & { projectPath?: string };
|
|
137
137
|
type JsonRpcRequest = (method: string, params: Record<string, unknown>) => Promise<any>;
|
|
138
|
+
type PendingJsonRpcRequests = Map<number, {
|
|
139
|
+
resolve: (value: any) => void;
|
|
140
|
+
reject: (error: Error) => void;
|
|
141
|
+
}>;
|
|
138
142
|
|
|
139
143
|
const MaxDiagnosticLength = 1000;
|
|
140
144
|
|
|
@@ -194,67 +198,106 @@ async function runJsonRpcSession<T>(
|
|
|
194
198
|
signal: AbortSignal | undefined,
|
|
195
199
|
fn: (request: JsonRpcRequest) => Promise<T>,
|
|
196
200
|
): Promise<T> {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const pending = new Map<number, {
|
|
201
|
-
resolve: (value: any) => void;
|
|
202
|
-
reject: (error: Error) => void;
|
|
203
|
-
}>();
|
|
204
|
-
|
|
205
|
-
const cleanup = () => {
|
|
206
|
-
for (const entry of pending.values()) {
|
|
207
|
-
entry.reject(new Error("CodeGraph MCP process closed before responding."));
|
|
208
|
-
}
|
|
209
|
-
pending.clear();
|
|
210
|
-
if (!child.killed) child.kill();
|
|
211
|
-
};
|
|
212
|
-
|
|
201
|
+
const pending: PendingJsonRpcRequests = new Map();
|
|
202
|
+
const stderr = { value: "" };
|
|
203
|
+
const cleanup = () => cleanupJsonRpcChild(child, pending);
|
|
213
204
|
const onAbort = () => cleanup();
|
|
205
|
+
|
|
214
206
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
207
|
+
attachJsonRpcHandlers(child, pending, stderr);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const sendRequest = createJsonRpcRequestSender(child, pending);
|
|
211
|
+
await initializeJsonRpcSession(cwd, sendRequest, sendJsonRpcNotification.bind(undefined, child));
|
|
212
|
+
return await fn(sendRequest);
|
|
213
|
+
} finally {
|
|
214
|
+
signal?.removeEventListener("abort", onAbort);
|
|
215
|
+
cleanup();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function cleanupJsonRpcChild(
|
|
220
|
+
child: ChildProcessWithoutNullStreams,
|
|
221
|
+
pending: PendingJsonRpcRequests,
|
|
222
|
+
): void {
|
|
223
|
+
rejectPendingJsonRpcRequests(
|
|
224
|
+
pending,
|
|
225
|
+
new Error("CodeGraph MCP process closed before responding."),
|
|
226
|
+
);
|
|
227
|
+
if (!child.killed) child.kill();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function rejectPendingJsonRpcRequests(
|
|
231
|
+
pending: PendingJsonRpcRequests,
|
|
232
|
+
error: Error,
|
|
233
|
+
): void {
|
|
234
|
+
for (const entry of pending.values()) entry.reject(error);
|
|
235
|
+
pending.clear();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function attachJsonRpcHandlers(
|
|
239
|
+
child: ChildProcessWithoutNullStreams,
|
|
240
|
+
pending: PendingJsonRpcRequests,
|
|
241
|
+
stderr: { value: string },
|
|
242
|
+
): void {
|
|
243
|
+
const stdout = { value: "" };
|
|
215
244
|
|
|
216
245
|
child.stdout.on("data", (chunk) => {
|
|
217
|
-
stdout
|
|
218
|
-
let newline;
|
|
219
|
-
while ((newline = stdout.indexOf("\n")) !== -1) {
|
|
220
|
-
const line = stdout.slice(0, newline).trim();
|
|
221
|
-
stdout = stdout.slice(newline + 1);
|
|
222
|
-
if (!line) continue;
|
|
223
|
-
|
|
224
|
-
let msg: any;
|
|
225
|
-
try {
|
|
226
|
-
msg = JSON.parse(line);
|
|
227
|
-
} catch {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (msg.id !== undefined && pending.has(msg.id)) {
|
|
232
|
-
const { resolve, reject } = pending.get(msg.id)!;
|
|
233
|
-
pending.delete(msg.id);
|
|
234
|
-
if (msg.error) reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
235
|
-
else resolve(msg.result);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
246
|
+
handleJsonRpcStdout(chunk, stdout, pending);
|
|
238
247
|
});
|
|
239
|
-
|
|
240
248
|
child.stderr.on("data", (chunk) => {
|
|
241
|
-
stderr += chunk.toString("utf-8");
|
|
249
|
+
stderr.value += chunk.toString("utf-8");
|
|
242
250
|
});
|
|
251
|
+
child.on("error", (err) => rejectPendingJsonRpcRequests(pending, err));
|
|
252
|
+
child.on("exit", (code) => rejectPendingJsonRpcOnExit(pending, stderr.value, code));
|
|
253
|
+
}
|
|
243
254
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
255
|
+
function handleJsonRpcStdout(
|
|
256
|
+
chunk: Buffer,
|
|
257
|
+
stdout: { value: string },
|
|
258
|
+
pending: PendingJsonRpcRequests,
|
|
259
|
+
): void {
|
|
260
|
+
stdout.value += chunk.toString("utf-8");
|
|
261
|
+
let newline;
|
|
262
|
+
while ((newline = stdout.value.indexOf("\n")) !== -1) {
|
|
263
|
+
const line = stdout.value.slice(0, newline).trim();
|
|
264
|
+
stdout.value = stdout.value.slice(newline + 1);
|
|
265
|
+
if (line) resolveJsonRpcLine(line, pending);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
248
268
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
269
|
+
function resolveJsonRpcLine(line: string, pending: PendingJsonRpcRequests): void {
|
|
270
|
+
let msg: any;
|
|
271
|
+
try {
|
|
272
|
+
msg = JSON.parse(line);
|
|
273
|
+
} catch {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (msg.id === undefined || !pending.has(msg.id)) return;
|
|
278
|
+
const { resolve, reject } = pending.get(msg.id)!;
|
|
279
|
+
pending.delete(msg.id);
|
|
280
|
+
if (msg.error) reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
281
|
+
else resolve(msg.result);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function rejectPendingJsonRpcOnExit(
|
|
285
|
+
pending: PendingJsonRpcRequests,
|
|
286
|
+
stderr: string,
|
|
287
|
+
code: number | null,
|
|
288
|
+
): void {
|
|
289
|
+
if (pending.size === 0) return;
|
|
290
|
+
const diagnostic = sanitizeDiagnostic(stderr.trim());
|
|
291
|
+
const msg = diagnostic || `CodeGraph MCP process exited with code ${code}`;
|
|
292
|
+
rejectPendingJsonRpcRequests(pending, new Error(msg));
|
|
293
|
+
}
|
|
256
294
|
|
|
257
|
-
|
|
295
|
+
function createJsonRpcRequestSender(
|
|
296
|
+
child: ChildProcessWithoutNullStreams,
|
|
297
|
+
pending: PendingJsonRpcRequests,
|
|
298
|
+
): JsonRpcRequest {
|
|
299
|
+
let nextId = 1;
|
|
300
|
+
return (method, params) => {
|
|
258
301
|
const id = nextId++;
|
|
259
302
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
260
303
|
const promise = new Promise<any>((resolve, reject) => {
|
|
@@ -263,26 +306,30 @@ async function runJsonRpcSession<T>(
|
|
|
263
306
|
child.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
264
307
|
return promise;
|
|
265
308
|
};
|
|
309
|
+
}
|
|
266
310
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
311
|
+
function sendJsonRpcNotification(
|
|
312
|
+
child: ChildProcessWithoutNullStreams,
|
|
313
|
+
method: string,
|
|
314
|
+
params: Record<string, unknown>,
|
|
315
|
+
): void {
|
|
316
|
+
child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
317
|
+
}
|
|
270
318
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
319
|
+
async function initializeJsonRpcSession(
|
|
320
|
+
cwd: string,
|
|
321
|
+
sendRequest: JsonRpcRequest,
|
|
322
|
+
sendNotification: (method: string, params: Record<string, unknown>) => void,
|
|
323
|
+
): Promise<void> {
|
|
324
|
+
const rootUri = pathToFileURL(cwd).href;
|
|
325
|
+
await sendRequest("initialize", {
|
|
326
|
+
protocolVersion: "2024-11-05",
|
|
327
|
+
rootUri,
|
|
328
|
+
workspaceFolders: [{ uri: rootUri, name: cwd.split(/[\\/]/).pop() || cwd }],
|
|
329
|
+
capabilities: {},
|
|
330
|
+
clientInfo: { name: "pi-codegraph", version: "0.1.0" },
|
|
331
|
+
});
|
|
332
|
+
sendNotification("initialized", {});
|
|
286
333
|
}
|
|
287
334
|
|
|
288
335
|
export async function callCodeGraphTool(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vndv/pi-codegraph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "CodeGraph tools for Pi Agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,9 +35,10 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
|
-
"ci": "npm run typecheck && npm test && npm pack --dry-run",
|
|
38
|
+
"ci": "npm run typecheck && npm test && npm run compat:codegraph && npm pack --dry-run",
|
|
39
|
+
"compat:codegraph": "codegraph --version",
|
|
39
40
|
"local-release": "changeset version && changeset publish",
|
|
40
|
-
"publish-packages": "npm publish --access public --provenance",
|
|
41
|
+
"publish-packages": "node -e \"const {execSync}=require('node:child_process'); const p=require('./package.json'); const spec=p.name+'@'+p.version; try { execSync('npm view '+spec+' version', {stdio:'ignore'}); console.log(spec+' already published; skipping.'); } catch { execSync('npm publish --access public --provenance --ignore-scripts', {stdio:'inherit'}); }\"",
|
|
41
42
|
"prepack": "npm run typecheck && npm test",
|
|
42
43
|
"prepublishOnly": "npm run ci",
|
|
43
44
|
"typecheck": "tsc --noEmit",
|
|
@@ -56,9 +57,10 @@
|
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@changesets/cli": "^2.31.0",
|
|
59
|
-
"@
|
|
60
|
-
"@
|
|
61
|
-
"
|
|
62
|
-
"
|
|
60
|
+
"@colbymchenry/codegraph": "^0.9.7",
|
|
61
|
+
"@earendil-works/pi-coding-agent": "^0.78.0",
|
|
62
|
+
"@types/node": "^25.9.1",
|
|
63
|
+
"typescript": "^6.0.3",
|
|
64
|
+
"vitest": "^4.1.7"
|
|
63
65
|
}
|
|
64
66
|
}
|