@vndv/pi-codegraph 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Matveev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # pi-codegraph
2
+
3
+ Pi package that adds CodeGraph tools to Pi Agent.
4
+
5
+ ## Requirements
6
+
7
+ Node.js 22 LTS is recommended. CodeGraph blocks Node.js 25 because of a V8 WASM JIT bug in tree-sitter grammar compilation.
8
+
9
+ Pi provides the package extension runtime and core libraries. This package declares `@earendil-works/pi-coding-agent` and `typebox` as peer dependencies, as required by Pi package loading.
10
+
11
+ CodeGraph must already be installed and available on `PATH`:
12
+
13
+ ```bash
14
+ npm install -g @colbymchenry/codegraph
15
+ ```
16
+
17
+ Projects must be indexed before Pi can query them:
18
+
19
+ ```bash
20
+ cd /path/to/project
21
+ codegraph init -i
22
+ ```
23
+
24
+ ## Install
25
+
26
+ From GitHub:
27
+
28
+ ```bash
29
+ pi install https://github.com/vndv/pi-codegraph
30
+ ```
31
+
32
+ From npm:
33
+
34
+ ```bash
35
+ pi install npm:@vndv/pi-codegraph@0.1.0
36
+ ```
37
+
38
+ This works only after `@vndv/pi-codegraph@0.1.0` has been published to npm. If npm returns `404 Not Found`, use the GitHub or local development install until the first npm publish is complete.
39
+
40
+ Local development install:
41
+
42
+ ```bash
43
+ git clone https://github.com/vndv/pi-codegraph.git
44
+ cd pi-codegraph
45
+ pi install "$(pwd)"
46
+ ```
47
+
48
+ Verify Pi sees the package:
49
+
50
+ ```bash
51
+ pi list
52
+ ```
53
+
54
+ ## Uninstall
55
+
56
+ Remove the package using the same source shown by `pi list`:
57
+
58
+ ```bash
59
+ pi remove https://github.com/vndv/pi-codegraph
60
+ ```
61
+
62
+ If you installed from npm or a local path, remove that exact entry instead:
63
+
64
+ ```bash
65
+ pi remove npm:@vndv/pi-codegraph@0.1.0
66
+ pi remove /path/to/pi-codegraph
67
+ ```
68
+
69
+ Then start Pi inside an indexed project:
70
+
71
+ ```bash
72
+ cd /path/to/project
73
+ pi
74
+ ```
75
+
76
+ Example prompt:
77
+
78
+ ```text
79
+ Use CodeGraph. Show project structure and main entry points.
80
+ ```
81
+
82
+ ## Tools
83
+
84
+ This package registers:
85
+
86
+ - `codegraph_search`
87
+ - `codegraph_context`
88
+ - `codegraph_callers`
89
+ - `codegraph_callees`
90
+ - `codegraph_impact`
91
+ - `codegraph_explore`
92
+ - `codegraph_node`
93
+ - `codegraph_status`
94
+ - `codegraph_files`
95
+ - `codegraph_trace`
96
+
97
+ Each tool proxies to:
98
+
99
+ ```bash
100
+ codegraph serve --mcp --path <project>
101
+ ```
102
+
103
+ For broad code questions, Pi should prefer `codegraph_context`. For known symbols, use `codegraph_node`. Use `codegraph_search` for declaration/symbol lookup, not literal constants or arbitrary text.
104
+
105
+ ## Development
106
+
107
+ ```bash
108
+ npm ci
109
+ npm run ci
110
+ ```
111
+
112
+ ## Release
113
+
114
+ Run the full local package check before publishing:
115
+
116
+ ```bash
117
+ npm run ci
118
+ npm pack --dry-run
119
+ ```
120
+
121
+ First npm publish:
122
+
123
+ ```bash
124
+ npm login
125
+ npm publish --access public
126
+ ```
127
+
128
+ Future releases use Changesets:
129
+
130
+ ```bash
131
+ npx changeset
132
+ npm run local-release
133
+ ```
134
+
135
+ GitHub publishing is also supported:
136
+
137
+ 1. Add `NPM_TOKEN` repository secret in GitHub.
138
+ 2. Update the package version with Changesets or by editing `package.json`.
139
+ 3. Commit and push to `main`.
140
+ 4. Create a GitHub release for the version tag.
141
+
142
+ The publish workflow runs `npm publish --provenance`.
@@ -0,0 +1,306 @@
1
+ import { spawn } from "node:child_process";
2
+ import { pathToFileURL } from "node:url";
3
+ import type { ChildProcessWithoutNullStreams } from "node:child_process";
4
+ import type { Static } from "typebox";
5
+ import { Type } from "typebox";
6
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
+
8
+ const OptionalProjectPath = Type.Optional(Type.String({
9
+ description: "Path to a different project with .codegraph/ initialized. Defaults to current project.",
10
+ }));
11
+
12
+ const ToolKind = Type.Optional(Type.Union([
13
+ Type.Literal("function"),
14
+ Type.Literal("method"),
15
+ Type.Literal("class"),
16
+ Type.Literal("interface"),
17
+ Type.Literal("type"),
18
+ Type.Literal("variable"),
19
+ Type.Literal("route"),
20
+ Type.Literal("component"),
21
+ ]));
22
+
23
+ const ToolDefinitions = [
24
+ {
25
+ name: "codegraph_search",
26
+ label: "CodeGraph Search",
27
+ description: "Quick symbol search by name. Returns locations only.",
28
+ parameters: Type.Object({
29
+ query: Type.String({ description: "Symbol name or partial name." }),
30
+ kind: ToolKind,
31
+ limit: Type.Optional(Type.Number({ default: 10 })),
32
+ projectPath: OptionalProjectPath,
33
+ }),
34
+ },
35
+ {
36
+ name: "codegraph_context",
37
+ label: "CodeGraph Context",
38
+ description: "Primary tool for architecture, feature, bug-context, or how-does-X-work questions.",
39
+ parameters: Type.Object({
40
+ task: Type.String({ description: "Task, question, or code area to understand." }),
41
+ maxNodes: Type.Optional(Type.Number({ default: 20 })),
42
+ includeCode: Type.Optional(Type.Boolean({ default: true })),
43
+ projectPath: OptionalProjectPath,
44
+ }),
45
+ },
46
+ {
47
+ name: "codegraph_callers",
48
+ label: "CodeGraph Callers",
49
+ description: "Find all functions or methods that call a specific symbol.",
50
+ parameters: Type.Object({
51
+ symbol: Type.String(),
52
+ limit: Type.Optional(Type.Number({ default: 20 })),
53
+ projectPath: OptionalProjectPath,
54
+ }),
55
+ },
56
+ {
57
+ name: "codegraph_callees",
58
+ label: "CodeGraph Callees",
59
+ description: "Find all functions or methods that a specific symbol calls.",
60
+ parameters: Type.Object({
61
+ symbol: Type.String(),
62
+ limit: Type.Optional(Type.Number({ default: 20 })),
63
+ projectPath: OptionalProjectPath,
64
+ }),
65
+ },
66
+ {
67
+ name: "codegraph_impact",
68
+ label: "CodeGraph Impact",
69
+ description: "Analyze the impact radius of changing a symbol.",
70
+ parameters: Type.Object({
71
+ symbol: Type.String(),
72
+ depth: Type.Optional(Type.Number({ default: 2 })),
73
+ projectPath: OptionalProjectPath,
74
+ }),
75
+ },
76
+ {
77
+ name: "codegraph_explore",
78
+ label: "CodeGraph Explore",
79
+ description: "Return source for several related symbols grouped by file.",
80
+ parameters: Type.Object({
81
+ query: Type.String({ description: "Specific symbols, files, or code terms to explore." }),
82
+ maxFiles: Type.Optional(Type.Number({ default: 12 })),
83
+ projectPath: OptionalProjectPath,
84
+ }),
85
+ },
86
+ {
87
+ name: "codegraph_node",
88
+ label: "CodeGraph Node",
89
+ description: "Get one symbol's details plus callers and callees trail.",
90
+ parameters: Type.Object({
91
+ symbol: Type.String(),
92
+ includeCode: Type.Optional(Type.Boolean({ default: false })),
93
+ projectPath: OptionalProjectPath,
94
+ }),
95
+ },
96
+ {
97
+ name: "codegraph_status",
98
+ label: "CodeGraph Status",
99
+ description: "Get CodeGraph index status.",
100
+ parameters: Type.Object({
101
+ projectPath: OptionalProjectPath,
102
+ }),
103
+ },
104
+ {
105
+ name: "codegraph_files",
106
+ label: "CodeGraph Files",
107
+ description: "Get project file structure from the CodeGraph index.",
108
+ parameters: Type.Object({
109
+ path: Type.Optional(Type.String()),
110
+ pattern: Type.Optional(Type.String()),
111
+ format: Type.Optional(Type.Union([
112
+ Type.Literal("tree"),
113
+ Type.Literal("flat"),
114
+ Type.Literal("grouped"),
115
+ ], { default: "tree" })),
116
+ includeMetadata: Type.Optional(Type.Boolean({ default: true })),
117
+ maxDepth: Type.Optional(Type.Number()),
118
+ projectPath: OptionalProjectPath,
119
+ }),
120
+ },
121
+ {
122
+ name: "codegraph_trace",
123
+ label: "CodeGraph Trace",
124
+ description: "Trace the call path between two symbols.",
125
+ parameters: Type.Object({
126
+ from: Type.String(),
127
+ to: Type.String(),
128
+ projectPath: OptionalProjectPath,
129
+ }),
130
+ },
131
+ ] as const;
132
+
133
+ type ToolName = (typeof ToolDefinitions)[number]["name"];
134
+ type ToolParams = Record<string, unknown> & { projectPath?: string };
135
+ type JsonRpcRequest = (method: string, params: Record<string, unknown>) => Promise<any>;
136
+
137
+ export const codegraphToolNames = ToolDefinitions.map((tool) => tool.name);
138
+
139
+ export async function withCodeGraphMcp<T>(
140
+ projectPath: string | undefined,
141
+ signal: AbortSignal | undefined,
142
+ fn: (request: JsonRpcRequest) => Promise<T>,
143
+ ): Promise<T> {
144
+ const cwd = projectPath || process.cwd();
145
+ const child = spawn("codegraph", ["serve", "--mcp", "--path", cwd], {
146
+ cwd,
147
+ env: process.env,
148
+ stdio: ["pipe", "pipe", "pipe"],
149
+ });
150
+
151
+ return runJsonRpcSession(child, cwd, signal, fn);
152
+ }
153
+
154
+ async function runJsonRpcSession<T>(
155
+ child: ChildProcessWithoutNullStreams,
156
+ cwd: string,
157
+ signal: AbortSignal | undefined,
158
+ fn: (request: JsonRpcRequest) => Promise<T>,
159
+ ): Promise<T> {
160
+ let nextId = 1;
161
+ let stdout = "";
162
+ let stderr = "";
163
+ const pending = new Map<number, {
164
+ resolve: (value: any) => void;
165
+ reject: (error: Error) => void;
166
+ }>();
167
+
168
+ const cleanup = () => {
169
+ for (const entry of pending.values()) {
170
+ entry.reject(new Error("CodeGraph MCP process closed before responding."));
171
+ }
172
+ pending.clear();
173
+ if (!child.killed) child.kill();
174
+ };
175
+
176
+ const onAbort = () => cleanup();
177
+ signal?.addEventListener("abort", onAbort, { once: true });
178
+
179
+ child.stdout.on("data", (chunk) => {
180
+ stdout += chunk.toString("utf-8");
181
+ let newline;
182
+ while ((newline = stdout.indexOf("\n")) !== -1) {
183
+ const line = stdout.slice(0, newline).trim();
184
+ stdout = stdout.slice(newline + 1);
185
+ if (!line) continue;
186
+
187
+ let msg: any;
188
+ try {
189
+ msg = JSON.parse(line);
190
+ } catch {
191
+ continue;
192
+ }
193
+
194
+ if (msg.id !== undefined && pending.has(msg.id)) {
195
+ const { resolve, reject } = pending.get(msg.id)!;
196
+ pending.delete(msg.id);
197
+ if (msg.error) reject(new Error(msg.error.message || JSON.stringify(msg.error)));
198
+ else resolve(msg.result);
199
+ }
200
+ }
201
+ });
202
+
203
+ child.stderr.on("data", (chunk) => {
204
+ stderr += chunk.toString("utf-8");
205
+ });
206
+
207
+ child.on("error", (err) => {
208
+ for (const entry of pending.values()) entry.reject(err);
209
+ pending.clear();
210
+ });
211
+
212
+ child.on("exit", (code) => {
213
+ if (pending.size === 0) return;
214
+ const msg = stderr.trim() || `CodeGraph MCP process exited with code ${code}`;
215
+ for (const entry of pending.values()) entry.reject(new Error(msg));
216
+ pending.clear();
217
+ });
218
+
219
+ const sendRequest: JsonRpcRequest = (method, params) => {
220
+ const id = nextId++;
221
+ const payload = { jsonrpc: "2.0", id, method, params };
222
+ const promise = new Promise<any>((resolve, reject) => {
223
+ pending.set(id, { resolve, reject });
224
+ });
225
+ child.stdin.write(`${JSON.stringify(payload)}\n`);
226
+ return promise;
227
+ };
228
+
229
+ const sendNotification = (method: string, params: Record<string, unknown>) => {
230
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
231
+ };
232
+
233
+ try {
234
+ const rootUri = pathToFileURL(cwd).href;
235
+ await sendRequest("initialize", {
236
+ protocolVersion: "2024-11-05",
237
+ rootUri,
238
+ workspaceFolders: [{ uri: rootUri, name: cwd.split(/[\\/]/).pop() || cwd }],
239
+ capabilities: {},
240
+ clientInfo: { name: "pi-codegraph", version: "0.1.0" },
241
+ });
242
+ sendNotification("initialized", {});
243
+ return await fn(sendRequest);
244
+ } finally {
245
+ signal?.removeEventListener("abort", onAbort);
246
+ cleanup();
247
+ }
248
+ }
249
+
250
+ export async function callCodeGraphTool(
251
+ name: ToolName,
252
+ params: ToolParams,
253
+ signal?: AbortSignal,
254
+ ): Promise<string> {
255
+ const projectPath = typeof params.projectPath === "string" ? params.projectPath : undefined;
256
+ const result = await withCodeGraphMcp(projectPath, signal, (request) =>
257
+ request("tools/call", {
258
+ name,
259
+ arguments: params || {},
260
+ })
261
+ );
262
+
263
+ const text = (result?.content || [])
264
+ .filter((part: any) => part?.type === "text")
265
+ .map((part: any) => part.text)
266
+ .join("\n");
267
+
268
+ if (result?.isError) throw new Error(text || "CodeGraph tool failed.");
269
+ return text || JSON.stringify(result);
270
+ }
271
+
272
+ export default function codegraphExtension(pi: ExtensionAPI): void {
273
+ pi.on("before_agent_start", async (event) => {
274
+ const guidance = [
275
+ "CodeGraph tools are available as codegraph_* Pi tools.",
276
+ "For architecture, flow, where-is-symbol, impact, and codebase navigation questions, use CodeGraph tools directly before grep/read.",
277
+ "Use codegraph_context first for broad questions, codegraph_search for symbol-name lookup, codegraph_files for project structure, codegraph_node for a known symbol, and codegraph_trace for call paths.",
278
+ "If codegraph_search returns no exact result, try codegraph_context or codegraph_files/codegraph_explore before falling back to grep/read; CodeGraph symbol search may miss literal constants or generated names that still exist in source text.",
279
+ "Only use grep/read after CodeGraph is insufficient or when the user asks for literal text matching.",
280
+ ].join("\n");
281
+
282
+ return {
283
+ systemPrompt: event.systemPrompt ? `${event.systemPrompt}\n\n${guidance}` : guidance,
284
+ };
285
+ });
286
+
287
+ for (const tool of ToolDefinitions) {
288
+ pi.registerTool({
289
+ name: tool.name,
290
+ label: tool.label,
291
+ description: tool.description,
292
+ promptSnippet: tool.description,
293
+ promptGuidelines: [
294
+ `${tool.name} is available for structural code questions backed by the local CodeGraph index.`,
295
+ ],
296
+ parameters: tool.parameters,
297
+ async execute(_toolCallId, params: Static<typeof tool.parameters>, signal) {
298
+ const text = await callCodeGraphTool(tool.name, (params || {}) as ToolParams, signal);
299
+ return {
300
+ content: [{ type: "text" as const, text }],
301
+ details: {},
302
+ };
303
+ },
304
+ });
305
+ }
306
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@vndv/pi-codegraph",
3
+ "version": "0.1.0",
4
+ "description": "CodeGraph tools for Pi Agent.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "author": "CodeGraph contributors",
8
+ "homepage": "https://github.com/vndv/pi-codegraph#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/vndv/pi-codegraph.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/vndv/pi-codegraph/issues"
15
+ },
16
+ "keywords": [
17
+ "pi-package",
18
+ "pi",
19
+ "codegraph",
20
+ "coding-agent",
21
+ "code-intelligence"
22
+ ],
23
+ "files": [
24
+ "extensions",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "exports": {
29
+ "./extensions/codegraph": "./extensions/codegraph.ts",
30
+ "./extensions/codegraph.ts": "./extensions/codegraph.ts"
31
+ },
32
+ "pi": {
33
+ "extensions": [
34
+ "./extensions"
35
+ ]
36
+ },
37
+ "scripts": {
38
+ "ci": "npm run typecheck && npm test && npm pack --dry-run",
39
+ "local-release": "changeset version && changeset publish",
40
+ "prepack": "npm run typecheck && npm test",
41
+ "prepublishOnly": "npm run ci",
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "vitest run"
44
+ },
45
+ "engines": {
46
+ "node": ">=22.19.0 <25"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "peerDependencies": {
52
+ "@earendil-works/pi-coding-agent": "*",
53
+ "typebox": "*"
54
+ },
55
+ "devDependencies": {
56
+ "@changesets/cli": "^2.31.0",
57
+ "@earendil-works/pi-coding-agent": "^0.76.0",
58
+ "@types/node": "^20.19.30",
59
+ "typescript": "^5.0.0",
60
+ "vitest": "^2.1.9"
61
+ }
62
+ }