@vndv/pi-codegraph 0.1.7 → 0.1.9
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 +5 -9
- package/extensions/codegraph.ts +106 -33
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Ask pi structural questions about your codebase without falling back to slow grep/read loops.
|
|
10
10
|
|
|
11
|
-
An extension for [pi](https://pi.dev) that gives the agent access to [CodeGraph](https://github.com/colbymchenry/codegraph) tools. CodeGraph indexes your project with tree-sitter, then pi can query symbols, callers, callees, dependency impact, files, and
|
|
11
|
+
An extension for [pi](https://pi.dev) that gives the agent access to [CodeGraph](https://github.com/colbymchenry/codegraph) tools. CodeGraph indexes your project with tree-sitter, then pi can query symbols, callers, callees, dependency impact, files, and relationships through native extension tools.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ An extension for [pi](https://pi.dev) that gives the agent access to [CodeGraph]
|
|
|
18
18
|
npm install -g @colbymchenry/codegraph
|
|
19
19
|
cd /path/to/project
|
|
20
20
|
codegraph init -i
|
|
21
|
-
pi install npm:@vndv/pi-codegraph@0.1.
|
|
21
|
+
pi install npm:@vndv/pi-codegraph@0.1.9
|
|
22
22
|
pi
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -36,13 +36,11 @@ Extension tools only. There is no MCP setup for pi users to maintain.
|
|
|
36
36
|
|
|
37
37
|
| Tool | Description |
|
|
38
38
|
| --- | --- |
|
|
39
|
-
| `codegraph_context` | Broad task context: entry points, related symbols, callers, callees, and key code |
|
|
40
39
|
| `codegraph_search` | Symbol search by name |
|
|
41
40
|
| `codegraph_node` | One symbol's signature, location, source, callers, and callees |
|
|
42
41
|
| `codegraph_files` | Indexed file tree |
|
|
43
42
|
| `codegraph_callers` | Functions or methods that call a symbol |
|
|
44
43
|
| `codegraph_callees` | Functions or methods called by a symbol |
|
|
45
|
-
| `codegraph_trace` | Static call path from one symbol to another |
|
|
46
44
|
| `codegraph_impact` | Impact radius for changing a symbol |
|
|
47
45
|
| `codegraph_explore` | Source for several related symbols grouped by file |
|
|
48
46
|
| `codegraph_status` | Index health and pending sync status |
|
|
@@ -54,7 +52,7 @@ Extension tools only. There is no MCP setup for pi users to maintain.
|
|
|
54
52
|
From npm:
|
|
55
53
|
|
|
56
54
|
```bash
|
|
57
|
-
pi install npm:@vndv/pi-codegraph@0.1.
|
|
55
|
+
pi install npm:@vndv/pi-codegraph@0.1.9
|
|
58
56
|
```
|
|
59
57
|
|
|
60
58
|
From GitHub:
|
|
@@ -126,12 +124,10 @@ Use CodeGraph. Show files under internal/services and important symbols.
|
|
|
126
124
|
|
|
127
125
|
### 3. Prefer the right tool
|
|
128
126
|
|
|
129
|
-
Use `
|
|
127
|
+
Use `codegraph_explore` for broad "how does this work?" or "how does X reach Y?" questions.
|
|
130
128
|
|
|
131
129
|
Use `codegraph_node` when you already know the symbol name.
|
|
132
130
|
|
|
133
|
-
Use `codegraph_trace` for "how does X reach Y?" flow questions.
|
|
134
|
-
|
|
135
131
|
Use `codegraph_search` for declarations and symbols, not arbitrary text or constant values.
|
|
136
132
|
|
|
137
133
|
---
|
|
@@ -163,7 +159,7 @@ That means another developer only needs the npm package, the `codegraph` CLI, an
|
|
|
163
159
|
Remove the package using the same source shown by `pi list`:
|
|
164
160
|
|
|
165
161
|
```bash
|
|
166
|
-
pi remove npm:@vndv/pi-codegraph@0.1.
|
|
162
|
+
pi remove npm:@vndv/pi-codegraph@0.1.9
|
|
167
163
|
```
|
|
168
164
|
|
|
169
165
|
If you installed from GitHub or a local path, remove that exact entry instead:
|
package/extensions/codegraph.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { stat } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
@@ -34,17 +35,6 @@ const ToolDefinitions = [
|
|
|
34
35
|
projectPath: OptionalProjectPath,
|
|
35
36
|
}),
|
|
36
37
|
},
|
|
37
|
-
{
|
|
38
|
-
name: "codegraph_context",
|
|
39
|
-
label: "CodeGraph Context",
|
|
40
|
-
description: "Primary tool for architecture, feature, bug-context, or how-does-X-work questions.",
|
|
41
|
-
parameters: Type.Object({
|
|
42
|
-
task: Type.String({ description: "Task, question, or code area to understand." }),
|
|
43
|
-
maxNodes: Type.Optional(Type.Number({ default: 20 })),
|
|
44
|
-
includeCode: Type.Optional(Type.Boolean({ default: true })),
|
|
45
|
-
projectPath: OptionalProjectPath,
|
|
46
|
-
}),
|
|
47
|
-
},
|
|
48
38
|
{
|
|
49
39
|
name: "codegraph_callers",
|
|
50
40
|
label: "CodeGraph Callers",
|
|
@@ -120,16 +110,6 @@ const ToolDefinitions = [
|
|
|
120
110
|
projectPath: OptionalProjectPath,
|
|
121
111
|
}),
|
|
122
112
|
},
|
|
123
|
-
{
|
|
124
|
-
name: "codegraph_trace",
|
|
125
|
-
label: "CodeGraph Trace",
|
|
126
|
-
description: "Trace the call path between two symbols.",
|
|
127
|
-
parameters: Type.Object({
|
|
128
|
-
from: Type.String(),
|
|
129
|
-
to: Type.String(),
|
|
130
|
-
projectPath: OptionalProjectPath,
|
|
131
|
-
}),
|
|
132
|
-
},
|
|
133
113
|
] as const;
|
|
134
114
|
|
|
135
115
|
type ToolName = (typeof ToolDefinitions)[number]["name"];
|
|
@@ -140,7 +120,8 @@ type PendingJsonRpcRequests = Map<number, {
|
|
|
140
120
|
reject: (error: Error) => void;
|
|
141
121
|
}>;
|
|
142
122
|
|
|
143
|
-
const MaxDiagnosticLength = 1000;
|
|
123
|
+
export const MaxDiagnosticLength = 1000;
|
|
124
|
+
export const SessionTimeoutMs = 20_000;
|
|
144
125
|
|
|
145
126
|
export const codegraphToolNames = ToolDefinitions.map((tool) => tool.name);
|
|
146
127
|
|
|
@@ -156,11 +137,49 @@ export async function withCodeGraphMcp<T>(
|
|
|
156
137
|
stdio: ["pipe", "pipe", "pipe"],
|
|
157
138
|
});
|
|
158
139
|
|
|
159
|
-
|
|
140
|
+
const session = runJsonRpcSession(child, cwd, signal, fn);
|
|
141
|
+
|
|
142
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
143
|
+
const onAbortClearTimer = () => clearTimeout(timer);
|
|
144
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
145
|
+
timer = setTimeout(() => {
|
|
146
|
+
if (!child.killed) child.kill();
|
|
147
|
+
reject(new Error(
|
|
148
|
+
"CodeGraph MCP session timed out after " + SessionTimeoutMs + "ms. " +
|
|
149
|
+
'Try running "codegraph unlock" in the project directory, then restart pi.'
|
|
150
|
+
));
|
|
151
|
+
}, SessionTimeoutMs);
|
|
152
|
+
signal?.addEventListener("abort", onAbortClearTimer, { once: true });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
session.catch(() => {});
|
|
156
|
+
timeout.catch(() => {});
|
|
157
|
+
return Promise.race([session, timeout]).finally(() => {
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
signal?.removeEventListener("abort", onAbortClearTimer);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function normalizeWindowsPath(inputPath: string): string {
|
|
164
|
+
let normalized = inputPath.trim();
|
|
165
|
+
|
|
166
|
+
if (process.platform !== "win32") return normalized;
|
|
167
|
+
|
|
168
|
+
const wslMatch = normalized.match(/^\/mnt\/([a-zA-Z])\/(.*)$/);
|
|
169
|
+
if (wslMatch) {
|
|
170
|
+
normalized = wslMatch[1].toUpperCase() + ":\\" + wslMatch[2].replace(/\//g, "\\");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const gitBashMatch = normalized.match(/^\/([a-zA-Z])\/(.*)$/);
|
|
174
|
+
if (gitBashMatch) {
|
|
175
|
+
normalized = gitBashMatch[1].toUpperCase() + ":\\" + gitBashMatch[2].replace(/\//g, "\\");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return normalized;
|
|
160
179
|
}
|
|
161
180
|
|
|
162
181
|
export async function resolveProjectCwd(projectPath: string | undefined): Promise<string> {
|
|
163
|
-
const cwd = projectPath || process.cwd();
|
|
182
|
+
const cwd = normalizeWindowsPath(projectPath || process.cwd());
|
|
164
183
|
|
|
165
184
|
if (!path.isAbsolute(cwd)) {
|
|
166
185
|
throw new Error("CodeGraph projectPath must be an absolute path.");
|
|
@@ -180,6 +199,34 @@ export async function resolveProjectCwd(projectPath: string | undefined): Promis
|
|
|
180
199
|
return cwd;
|
|
181
200
|
}
|
|
182
201
|
|
|
202
|
+
export function normalizeFilesPath(inputPath?: string, projectCwd?: string): string | undefined {
|
|
203
|
+
if (typeof inputPath !== "string" || inputPath.trim() === "") return undefined;
|
|
204
|
+
|
|
205
|
+
const trimmed = inputPath.trim();
|
|
206
|
+
let expanded = trimmed;
|
|
207
|
+
if (expanded === "~" || expanded.startsWith("~/") || expanded.startsWith("~\\")) {
|
|
208
|
+
expanded = path.join(os.homedir(), expanded.slice(1));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (projectCwd && path.isAbsolute(expanded)) {
|
|
212
|
+
const relative = path.relative(projectCwd, expanded);
|
|
213
|
+
if (relative === "") return undefined;
|
|
214
|
+
if (!relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
215
|
+
return relative.split(path.sep).join("/");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return trimmed.split(path.sep).join("/");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const EmptyFilesMarker = "No files found matching the criteria.";
|
|
223
|
+
|
|
224
|
+
export function annotateFilesResult(resultText: string, originalPath?: string): string {
|
|
225
|
+
if (!originalPath || !resultText.includes(EmptyFilesMarker)) return resultText;
|
|
226
|
+
|
|
227
|
+
return `${resultText}\n\nHint: codegraph_files interprets "path" as a root-relative POSIX prefix (e.g. "src/components"). The filter "${originalPath}" did not match any indexed path.`;
|
|
228
|
+
}
|
|
229
|
+
|
|
183
230
|
export function sanitizeDiagnostic(value: string): string {
|
|
184
231
|
const withoutAnsi = value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
185
232
|
const redacted = withoutAnsi
|
|
@@ -332,17 +379,42 @@ async function initializeJsonRpcSession(
|
|
|
332
379
|
sendNotification("initialized", {});
|
|
333
380
|
}
|
|
334
381
|
|
|
382
|
+
async function prepareToolArguments(
|
|
383
|
+
name: ToolName,
|
|
384
|
+
params: ToolParams,
|
|
385
|
+
): Promise<{ args: ToolParams; originalFilesPath?: string }> {
|
|
386
|
+
if (name !== "codegraph_files") return { args: params };
|
|
387
|
+
|
|
388
|
+
const projectPath = typeof params.projectPath === "string" ? params.projectPath : undefined;
|
|
389
|
+
const projectCwd = await resolveProjectCwd(projectPath);
|
|
390
|
+
const originalFilesPath = typeof params.path === "string" ? params.path : undefined;
|
|
391
|
+
const normalizedPath = normalizeFilesPath(originalFilesPath, projectCwd);
|
|
392
|
+
|
|
393
|
+
const args: ToolParams = { ...params };
|
|
394
|
+
if (normalizedPath === undefined) {
|
|
395
|
+
delete args.path;
|
|
396
|
+
} else {
|
|
397
|
+
args.path = normalizedPath;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return { args, originalFilesPath };
|
|
401
|
+
}
|
|
402
|
+
|
|
335
403
|
export async function callCodeGraphTool(
|
|
336
404
|
name: ToolName,
|
|
337
405
|
params: ToolParams,
|
|
338
406
|
signal?: AbortSignal,
|
|
339
407
|
): Promise<string> {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
408
|
+
const { args, originalFilesPath } = await prepareToolArguments(name, params);
|
|
409
|
+
|
|
410
|
+
const result = await withCodeGraphMcp(
|
|
411
|
+
typeof args.projectPath === "string" ? args.projectPath : undefined,
|
|
412
|
+
signal,
|
|
413
|
+
(request) =>
|
|
414
|
+
request("tools/call", {
|
|
415
|
+
name,
|
|
416
|
+
arguments: args,
|
|
417
|
+
}),
|
|
346
418
|
);
|
|
347
419
|
|
|
348
420
|
const text = (result?.content || [])
|
|
@@ -351,7 +423,8 @@ export async function callCodeGraphTool(
|
|
|
351
423
|
.join("\n");
|
|
352
424
|
|
|
353
425
|
if (result?.isError) throw new Error(text || "CodeGraph tool failed.");
|
|
354
|
-
|
|
426
|
+
const finalText = text || JSON.stringify(result);
|
|
427
|
+
return name === "codegraph_files" ? annotateFilesResult(finalText, originalFilesPath) : finalText;
|
|
355
428
|
}
|
|
356
429
|
|
|
357
430
|
export default function codegraphExtension(pi: ExtensionAPI): void {
|
|
@@ -359,8 +432,8 @@ export default function codegraphExtension(pi: ExtensionAPI): void {
|
|
|
359
432
|
const guidance = [
|
|
360
433
|
"CodeGraph tools are available as codegraph_* Pi tools.",
|
|
361
434
|
"For architecture, flow, where-is-symbol, impact, and codebase navigation questions, use CodeGraph tools directly before grep/read.",
|
|
362
|
-
"Use
|
|
363
|
-
"If codegraph_search returns no exact result, try
|
|
435
|
+
"Use codegraph_explore first for broad questions, codegraph_search for symbol-name lookup, codegraph_files for project structure, codegraph_node for a known symbol, and codegraph_callers for impact/flow analysis.",
|
|
436
|
+
"If codegraph_search returns no exact result, try codegraph_explore or codegraph_files/codegraph_node before falling back to grep/read; CodeGraph symbol search may miss literal constants or generated names that still exist in source text.",
|
|
364
437
|
"Only use grep/read after CodeGraph is insufficient or when the user asks for literal text matching.",
|
|
365
438
|
].join("\n");
|
|
366
439
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vndv/pi-codegraph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "CodeGraph tools for Pi Agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@changesets/cli": "^2.31.0",
|
|
62
|
-
"@colbymchenry/codegraph": "^0.
|
|
63
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
64
|
-
"@types/node": "^
|
|
62
|
+
"@colbymchenry/codegraph": "^1.0.1",
|
|
63
|
+
"@earendil-works/pi-coding-agent": "^0.79.6",
|
|
64
|
+
"@types/node": "^26.0.0",
|
|
65
65
|
"typescript": "^6.0.3",
|
|
66
66
|
"vitest": "^4.1.7"
|
|
67
67
|
}
|