@henryqw/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 +21 -0
- package/README.md +57 -0
- package/extensions/codegraph.ts +113 -0
- package/mcp.json +11 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henry Wang
|
|
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,57 @@
|
|
|
1
|
+
# pi-codegraph
|
|
2
|
+
|
|
3
|
+
Get a separate CodeGraph index when you start Pi in a new Git worktree. The package also supplies a lazy CodeGraph MCP server through **pi-mcp-adapter**, including the explore tool. It does not implement an MCP client or edit your MCP configuration.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Requirements: Pi 0.87.x, Node.js 22.19 or newer, Git, the `codegraph` CLI on `PATH`, and **pi-mcp-adapter 2.36.x or newer within 2.x installed and enabled in Pi**.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g @colbymchenry/codegraph
|
|
11
|
+
pi install npm:pi-mcp-adapter
|
|
12
|
+
pi install npm:@henryqw/pi-codegraph
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Installing an npm peer dependency alone does not enable the adapter's Pi extension. Install both packages with Pi, then restart Pi or run `/reload`.
|
|
16
|
+
|
|
17
|
+
Opt a repository in by building its first index in the **primary checkout**:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
cd /path/to/primary-checkout
|
|
21
|
+
codegraph init --yes
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
After that, launch `pi` in any linked worktree. The extension initializes when Pi starts, not when Git creates the worktree.
|
|
25
|
+
|
|
26
|
+
## What happens
|
|
27
|
+
|
|
28
|
+
- A missing worktree index is built only when the primary checkout already has `.codegraph/codegraph.db`.
|
|
29
|
+
- Launching from a subdirectory still initializes the worktree root. Each worktree keeps its own `.codegraph`; databases are never copied or shared between branches.
|
|
30
|
+
- Existing indexes are left alone. CodeGraph owns synchronization when its MCP server runs.
|
|
31
|
+
- Non-Git directories and repositories without a primary index are not initialized. A first index always requires your explicit `codegraph init`.
|
|
32
|
+
- Pi shows initialization or waiting status. Initialization has a ten-minute timeout; concurrent sessions wait up to five minutes for the same worktree's initializer.
|
|
33
|
+
- The adapter server is `henryqw_pi-codegraph__codegraph`. It discovers tools lazily and exposes `codegraph_explore` directly without an extra package prefix. Other CodeGraph tools remain available through `mcp`. On a cold connection, CodeGraph may require an explicit `projectPath`; pass the worktree root when its tool schema requests it.
|
|
34
|
+
|
|
35
|
+
The MCP definition uses the adapter's native `pi.mcp` package discovery. Install this **package**, not just its extension `.ts` file. Loading a single file with `pi -e` does not discover its MCP manifest. When using the whole local harness as a Pi package, its server is instead `henryqw_pi-harness__codegraph`; do not install both forms together.
|
|
36
|
+
|
|
37
|
+
## Failures and limits
|
|
38
|
+
|
|
39
|
+
At startup, the extension checks for a loaded adapter and runs `codegraph --version`, even when an index already exists. If either prerequisite is unavailable, Pi warns with the installation command for each missing prerequisite and skips setup without creating a lock. Headless sessions print the same warning to stderr. A broken or timed-out CLI check includes its failure details; if CodeGraph is already installed, check Pi's `PATH`. Restart Pi or run `/reload` after fixing the prerequisites.
|
|
40
|
+
|
|
41
|
+
The `codegraph` executable must be available to both Pi and its MCP child process. The package never installs either prerequisite automatically.
|
|
42
|
+
|
|
43
|
+
Initialization uses an exclusive `pi-codegraph-init.lock` directory inside the worktree's Git metadata. Failed or interrupted initialization keeps this lock so a partial database is not accepted on the next launch. Errors include the exact worktree and lock paths. To recover:
|
|
44
|
+
|
|
45
|
+
1. Confirm no initializer is still running.
|
|
46
|
+
2. Inspect the failed index and run `codegraph index` in that worktree.
|
|
47
|
+
3. Only after indexing succeeds, remove the reported empty lock directory with `rmdir`, then run `/reload`.
|
|
48
|
+
|
|
49
|
+
Do not remove an active lock. Existing indexes without an extension-owned lock are not health-checked. The lock coordinates this extension's sessions, not manual `codegraph init` commands or unrelated clients; avoid running those during initialization. A setup error does not remove files or change adapter configuration.
|
|
50
|
+
|
|
51
|
+
Only Git worktree-root indexes and the default `.codegraph` directory are supported. An alternate `CODEGRAPH_DIR` is rejected. Nested monorepo indexes are not initialized automatically. The primary checkout must remain present and indexed for automatic opt-in detection.
|
|
52
|
+
|
|
53
|
+
The extension does not add ignore rules, delete indexes, or prune worktrees. Add `.codegraph/` to your own ignore rules if needed. Pi Subagent and Pi Orchestrator conservatively treat ignored files as retained work: an indexed worker worktree may require manual cleanup. This package does not weaken those data-loss safeguards.
|
|
54
|
+
|
|
55
|
+
## Existing MCP configuration
|
|
56
|
+
|
|
57
|
+
If you already configured a `codegraph` server manually, remove that entry after confirming the package server works. Keeping both can expose duplicate servers/tools. The extension does not change your MCP configuration.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { mkdir, rmdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
const LOCK_WAIT_MS = 5 * 60_000;
|
|
7
|
+
const INIT_TIMEOUT_MS = 10 * 60_000;
|
|
8
|
+
|
|
9
|
+
async function hasIndex(root: string): Promise<boolean> {
|
|
10
|
+
try {
|
|
11
|
+
return (await stat(join(root, ".codegraph", "codegraph.db"))).isFile();
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function git(pi: ExtensionAPI, cwd: string, args: string[]): Promise<string> {
|
|
19
|
+
const result = await pi.exec("git", args, { cwd, timeout: 10_000 });
|
|
20
|
+
if (result.code !== 0 || result.killed) {
|
|
21
|
+
throw new Error(`git ${args.join(" ")} failed: ${result.stderr.trim() || `exit ${result.code}`}`);
|
|
22
|
+
}
|
|
23
|
+
return result.stdout.replace(/\r?\n$/, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function initialize(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
27
|
+
const prerequisites: string[] = [];
|
|
28
|
+
if (!pi.getAllTools().some((tool) => tool.name === "mcp")) {
|
|
29
|
+
prerequisites.push("Install and enable pi-mcp-adapter >=2.36.0: pi install npm:pi-mcp-adapter");
|
|
30
|
+
}
|
|
31
|
+
const version = await pi.exec("codegraph", ["--version"], { cwd: ctx.cwd, timeout: 10_000 });
|
|
32
|
+
if (version.code !== 0 || version.killed) {
|
|
33
|
+
const detail = version.killed ? "timed out or killed" : version.stderr.trim().slice(-1000) || `exit ${version.code}`;
|
|
34
|
+
prerequisites.push(`codegraph --version failed (${detail}). Install CodeGraph: npm install -g @colbymchenry/codegraph. If already installed, check that codegraph runs on Pi's PATH.`);
|
|
35
|
+
}
|
|
36
|
+
if (prerequisites.length) {
|
|
37
|
+
const message = `pi-codegraph: setup skipped.\n${prerequisites.join("\n")}\nThen restart Pi or /reload.`;
|
|
38
|
+
ctx.ui.setStatus("pi-codegraph", "CodeGraph: prerequisites missing");
|
|
39
|
+
if (ctx.hasUI) ctx.ui.notify(message, "warning");
|
|
40
|
+
else console.warn(message);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
ctx.ui.setStatus("pi-codegraph", undefined);
|
|
44
|
+
const rootResult = await pi.exec("git", ["rev-parse", "--show-toplevel"], { cwd: ctx.cwd, timeout: 10_000 });
|
|
45
|
+
if (rootResult.code !== 0 || rootResult.killed) {
|
|
46
|
+
if (!rootResult.killed && rootResult.stderr.includes("not a git repository")) return;
|
|
47
|
+
throw new Error(`Cannot locate Git worktree: ${rootResult.stderr.trim() || `exit ${rootResult.code}`}`);
|
|
48
|
+
}
|
|
49
|
+
const root = rootResult.stdout.replace(/\r?\n$/, "");
|
|
50
|
+
if (!root) throw new Error("Git returned an empty worktree root.");
|
|
51
|
+
if (process.env.CODEGRAPH_DIR && process.env.CODEGRAPH_DIR !== ".codegraph") {
|
|
52
|
+
throw new Error("pi-codegraph requires the default CODEGRAPH_DIR (.codegraph). Unset CODEGRAPH_DIR before launching Pi.");
|
|
53
|
+
}
|
|
54
|
+
const gitDir = await git(pi, root, ["rev-parse", "--absolute-git-dir"]);
|
|
55
|
+
if (!gitDir) throw new Error("Git returned an empty metadata directory.");
|
|
56
|
+
const lock = join(gitDir, "pi-codegraph-init.lock");
|
|
57
|
+
// Check the lock before the database: an in-progress/failed init can leave a partial DB.
|
|
58
|
+
const recovery = `Inspect CodeGraph in ${root}. If no initializer is running, run codegraph index in that directory, then remove ${lock} with rmdir and /reload.`;
|
|
59
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
60
|
+
ctx.ui.setStatus("pi-codegraph", "CodeGraph: checking index…");
|
|
61
|
+
try {
|
|
62
|
+
while (true) {
|
|
63
|
+
try {
|
|
64
|
+
await mkdir(lock);
|
|
65
|
+
break;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
68
|
+
if (Date.now() >= deadline) throw new Error(`Timed out waiting for CodeGraph initialization. ${recovery}`);
|
|
69
|
+
ctx.ui.setStatus("pi-codegraph", "CodeGraph: waiting for initialization…");
|
|
70
|
+
await delay(250);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let attempted = false;
|
|
74
|
+
let succeeded = false;
|
|
75
|
+
try {
|
|
76
|
+
if (await hasIndex(root)) return;
|
|
77
|
+
const worktrees = await git(pi, root, ["worktree", "list", "--porcelain", "-z"]);
|
|
78
|
+
const first = worktrees.split("\0", 1)[0];
|
|
79
|
+
if (!first?.startsWith("worktree ")) throw new Error("Git returned an invalid primary worktree.");
|
|
80
|
+
const primary = first.slice("worktree ".length);
|
|
81
|
+
if (!primary || primary === root || !(await hasIndex(primary))) return;
|
|
82
|
+
ctx.ui.setStatus("pi-codegraph", "CodeGraph: building worktree index…");
|
|
83
|
+
attempted = true;
|
|
84
|
+
const result = await pi.exec("codegraph", ["init", "--yes", root], { cwd: root, timeout: INIT_TIMEOUT_MS });
|
|
85
|
+
if (result.code !== 0 || result.killed || !(await hasIndex(root))) {
|
|
86
|
+
throw new Error(`CodeGraph init failed (${result.killed ? "timed out or killed" : `exit ${result.code}`}): ${(result.stderr || result.stdout).trim().slice(-2000)}`);
|
|
87
|
+
}
|
|
88
|
+
succeeded = true;
|
|
89
|
+
ctx.ui.notify("CodeGraph worktree index ready.", "info");
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (attempted) throw new Error(`${error instanceof Error ? error.message : String(error)}\n${recovery}`, { cause: error });
|
|
92
|
+
throw error;
|
|
93
|
+
} finally {
|
|
94
|
+
// A failed or interrupted init stays locked; never accept its partial DB on reload.
|
|
95
|
+
if (!attempted || succeeded) await rmdir(lock);
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
ctx.ui.setStatus("pi-codegraph", undefined);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default function codegraphExtension(pi: ExtensionAPI): void {
|
|
103
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
104
|
+
try {
|
|
105
|
+
await initialize(pi, ctx);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const message = `pi-codegraph: ${error instanceof Error ? error.message : String(error)}`;
|
|
108
|
+
ctx.ui.setStatus("pi-codegraph", "CodeGraph: setup failed");
|
|
109
|
+
if (ctx.hasUI) ctx.ui.notify(message, "error");
|
|
110
|
+
else throw new Error(message, { cause: error });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
package/mcp.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@henryqw/pi-codegraph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Initialize CodeGraph indexes in opted-in Git worktrees and expose CodeGraph through pi-mcp-adapter.",
|
|
5
|
+
"keywords": ["pi-package", "pi", "codegraph", "mcp", "worktree"],
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": { "node": ">=22.19.0" },
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"files": ["LICENSE", "extensions", "mcp.json", "README.md"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test test/*.test.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit --allowImportingTsExtensions --target ES2024 --module NodeNext --moduleResolution NodeNext --skipLibCheck extensions/codegraph.ts test/*.test.ts",
|
|
13
|
+
"pack:check": "npm pack --dry-run"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@earendil-works/pi-coding-agent": ">=0.87.0 <0.88.0",
|
|
17
|
+
"pi-mcp-adapter": ">=2.36.0 <3.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": { "@earendil-works/pi-coding-agent": "0.87.0" },
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/HenryQW/pi-harness.git",
|
|
23
|
+
"directory": "extensions/pi-codegraph"
|
|
24
|
+
},
|
|
25
|
+
"bugs": { "url": "https://github.com/HenryQW/pi-harness/issues" },
|
|
26
|
+
"publishConfig": { "access": "public" },
|
|
27
|
+
"pi": {
|
|
28
|
+
"extensions": ["./extensions/codegraph.ts"],
|
|
29
|
+
"mcp": "./mcp.json"
|
|
30
|
+
}
|
|
31
|
+
}
|