@vndv/pi-codegraph 0.1.3 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # pi-codegraph
2
2
  ### CodeGraph tools for pi
3
3
 
4
- Install · Usage · How it works
4
+ [Install](#install) · [Usage](#usage) · [How it works](#how-it-works)
5
5
 
6
6
  Ask pi structural questions about your codebase without falling back to slow grep/read loops.
7
7
 
@@ -15,7 +15,7 @@ An extension for [pi](https://pi.dev) that gives the agent access to [CodeGraph]
15
15
  npm install -g @colbymchenry/codegraph
16
16
  cd /path/to/project
17
17
  codegraph init -i
18
- pi install npm:@vndv/pi-codegraph@0.1.1
18
+ pi install npm:@vndv/pi-codegraph@0.1.5
19
19
  pi
20
20
  ```
21
21
 
@@ -51,7 +51,7 @@ Extension tools only. There is no MCP setup for pi users to maintain.
51
51
  From npm:
52
52
 
53
53
  ```bash
54
- pi install npm:@vndv/pi-codegraph@0.1.1
54
+ pi install npm:@vndv/pi-codegraph@0.1.5
55
55
  ```
56
56
 
57
57
  From GitHub:
@@ -160,7 +160,7 @@ That means another developer only needs the npm package, the `codegraph` CLI, an
160
160
  Remove the package using the same source shown by `pi list`:
161
161
 
162
162
  ```bash
163
- pi remove npm:@vndv/pi-codegraph@0.1.1
163
+ pi remove npm:@vndv/pi-codegraph@0.1.5
164
164
  ```
165
165
 
166
166
  If you installed from GitHub or a local path, remove that exact entry instead:
@@ -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
- let nextId = 1;
198
- let stdout = "";
199
- let stderr = "";
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 += chunk.toString("utf-8");
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
- child.on("error", (err) => {
245
- for (const entry of pending.values()) entry.reject(err);
246
- pending.clear();
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
- child.on("exit", (code) => {
250
- if (pending.size === 0) return;
251
- const diagnostic = sanitizeDiagnostic(stderr.trim());
252
- const msg = diagnostic || `CodeGraph MCP process exited with code ${code}`;
253
- for (const entry of pending.values()) entry.reject(new Error(msg));
254
- pending.clear();
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
- const sendRequest: JsonRpcRequest = (method, params) => {
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
- const sendNotification = (method: string, params: Record<string, unknown>) => {
268
- child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
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
- try {
272
- const rootUri = pathToFileURL(cwd).href;
273
- await sendRequest("initialize", {
274
- protocolVersion: "2024-11-05",
275
- rootUri,
276
- workspaceFolders: [{ uri: rootUri, name: cwd.split(/[\\/]/).pop() || cwd }],
277
- capabilities: {},
278
- clientInfo: { name: "pi-codegraph", version: "0.1.0" },
279
- });
280
- sendNotification("initialized", {});
281
- return await fn(sendRequest);
282
- } finally {
283
- signal?.removeEventListener("abort", onAbort);
284
- cleanup();
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",
3
+ "version": "0.1.5",
4
4
  "description": "CodeGraph tools for Pi Agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,15 +35,17 @@
35
35
  ]
36
36
  },
37
37
  "scripts": {
38
- "ci": "npm run typecheck && npm test && npm run compat:codegraph && npm pack --dry-run",
38
+ "check:readme-version": "node scripts/sync-readme-version.mjs --check",
39
+ "ci": "npm run typecheck && npm test && npm run compat:codegraph && npm run check:readme-version && npm pack --dry-run",
39
40
  "compat:codegraph": "codegraph --version",
40
41
  "local-release": "changeset version && changeset publish",
41
- "publish-packages": "npm publish --access public --provenance",
42
+ "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'}); }\"",
42
43
  "prepack": "npm run typecheck && npm test",
43
44
  "prepublishOnly": "npm run ci",
44
45
  "typecheck": "tsc --noEmit",
45
46
  "test": "vitest run",
46
- "version-packages": "changeset version && npm install --package-lock-only --ignore-scripts"
47
+ "sync:readme-version": "node scripts/sync-readme-version.mjs",
48
+ "version-packages": "changeset version && npm run sync:readme-version && npm install --package-lock-only --ignore-scripts"
47
49
  },
48
50
  "engines": {
49
51
  "node": ">=22.19.0 <25"
@@ -58,9 +60,9 @@
58
60
  "devDependencies": {
59
61
  "@changesets/cli": "^2.31.0",
60
62
  "@colbymchenry/codegraph": "^0.9.7",
61
- "@earendil-works/pi-coding-agent": "^0.76.0",
62
- "@types/node": "^20.19.30",
63
- "typescript": "^5.0.0",
64
- "vitest": "^2.1.9"
63
+ "@earendil-works/pi-coding-agent": "^0.78.0",
64
+ "@types/node": "^25.9.1",
65
+ "typescript": "^6.0.3",
66
+ "vitest": "^4.1.7"
65
67
  }
66
68
  }