@liuxincuit/pi-codegraph 0.1.1 → 0.1.3
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/CONTEXT.md +21 -21
- package/LICENSE +22 -22
- package/README.md +15 -1
- package/extensions/codegraph.ts +596 -298
- package/package.json +43 -43
- package/skills/codegraph/SKILL.md +43 -27
package/CONTEXT.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
# pi-codegraph
|
|
2
|
-
|
|
3
|
-
A pi extension that gives the agent access to a CodeGraph index of the current project.
|
|
4
|
-
|
|
5
|
-
## Language
|
|
6
|
-
|
|
7
|
-
**Project**:
|
|
8
|
-
The directory the pi session runs in (`ctx.cwd`). The unit that CodeGraph indexes and the default scope of every query.
|
|
9
|
-
_Avoid_: workspace, repository, repo
|
|
10
|
-
|
|
11
|
-
**Index**:
|
|
12
|
-
The `.codegraph/` directory at the project root, holding the SQLite knowledge graph of the project's symbols, edges, and files. Built by `codegraph init`, updated by `codegraph sync`.
|
|
13
|
-
_Avoid_: database, cache, graph (ambiguous with the data structure)
|
|
14
|
-
|
|
15
|
-
**Explore**:
|
|
16
|
-
The single agent-facing query operation: a natural-language or symbol question answered with the relevant symbols' verbatim source plus the call paths between them.
|
|
17
|
-
_Avoid_: search, query, lookup
|
|
18
|
-
|
|
19
|
-
**Sync**:
|
|
20
|
-
An incremental update of the Index to match the files currently on disk. Cheap when nothing changed.
|
|
21
|
-
_Avoid_: refresh, rebuild (that's a full re-index)
|
|
1
|
+
# pi-codegraph
|
|
2
|
+
|
|
3
|
+
A pi extension that gives the agent access to a CodeGraph index of the current project.
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**Project**:
|
|
8
|
+
The directory the pi session runs in (`ctx.cwd`). The unit that CodeGraph indexes and the default scope of every query.
|
|
9
|
+
_Avoid_: workspace, repository, repo
|
|
10
|
+
|
|
11
|
+
**Index**:
|
|
12
|
+
The `.codegraph/` directory at the project root, holding the SQLite knowledge graph of the project's symbols, edges, and files. Built by `codegraph init`, updated by `codegraph sync`.
|
|
13
|
+
_Avoid_: database, cache, graph (ambiguous with the data structure)
|
|
14
|
+
|
|
15
|
+
**Explore**:
|
|
16
|
+
The single agent-facing query operation: a natural-language or symbol question answered with the relevant symbols' verbatim source plus the call paths between them.
|
|
17
|
+
_Avoid_: search, query, lookup
|
|
18
|
+
|
|
19
|
+
**Sync**:
|
|
20
|
+
An incremental update of the Index to match the files currently on disk. Cheap when nothing changed.
|
|
21
|
+
_Avoid_: refresh, rebuild (that's a full re-index)
|
package/LICENSE
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 izhimu
|
|
4
|
-
Copyright (c) 2026 liuxincuit
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 izhimu
|
|
4
|
+
Copyright (c) 2026 liuxincuit
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -49,7 +49,9 @@ pi -e ./extensions/codegraph.ts
|
|
|
49
49
|
|
|
50
50
|
## 功能一览
|
|
51
51
|
|
|
52
|
-
- **`
|
|
52
|
+
- **`codegraph_*` 工具集** — 面向智能体的代码智能工具,均支持 `path` 参数查询其他已建索引的项目:
|
|
53
|
+
- `codegraph_explore`(**默认注册**)— 一揽子探索:相关符号逐字源码 + 调用路径 + 影响范围(`maxFiles` 限制返回行数)
|
|
54
|
+
- `codegraph_query` / `codegraph_node` / `codegraph_callers` / `codegraph_callees` / `codegraph_impact` / `codegraph_files`(**默认隐藏**,见下方配置)
|
|
53
55
|
- **`/codegraph-init [path]`** — 为项目建立索引(`codegraph init`)。
|
|
54
56
|
- **`/codegraph-sync [path]`** — 手动同步自上次索引以来的改动(`codegraph sync`)。
|
|
55
57
|
- **`/codegraph-status [path]`** — 查看索引状态与统计信息(`codegraph status`)。
|
|
@@ -71,6 +73,18 @@ pi -e ./extensions/codegraph.ts
|
|
|
71
73
|
|
|
72
74
|
索引构建始终由你显式触发——智能体自身不会运行 `codegraph init`(见 `docs/adr/0002`)。
|
|
73
75
|
|
|
76
|
+
## 细粒度工具(可选开启)
|
|
77
|
+
|
|
78
|
+
`codegraph_explore` 能覆盖绝大多数结构化查询,因此另外 6 个细粒度工具默认**不注册**,避免过多工具增加智能体的决策负担(见 `docs/adr/0003`)。需要时在全局 `~/.pi/agent/extensions/pi-codegraph/config.json` 或项目 `.pi/extensions/pi-codegraph/config.json` 中配置 `extraTools` 开启(项目配置覆盖全局):
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"extraTools": ["query", "node", "impact"]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
短名(`node`、`impact`)与完整工具名(`codegraph_node`)均可用,`"all"` 开启全部。配置在下一个会话生效(`/reload` 或重启 pi)。
|
|
87
|
+
|
|
74
88
|
## 工作原理
|
|
75
89
|
|
|
76
90
|
pi 原生不支持 MCP,因此本扩展通过执行 CLI 来桥接 CodeGraph——`codegraph explore` 产生与 `codegraph_explore` MCP 工具相同的输出(见 `docs/adr/0001`)。每次工具调用仅需一次 `pi.exec`:没有守护进程、没有 JSON-RPC,没有可泄漏或需要恢复的状态。
|
package/extensions/codegraph.ts
CHANGED
|
@@ -1,298 +1,596 @@
|
|
|
1
|
-
// pi-codegraph — pi extension
|
|
2
|
-
//
|
|
3
|
-
// CodeGraph support for pi: codegraph_explore tool, /codegraph-init,
|
|
4
|
-
// /codegraph-sync, /codegraph-status, and /codegraph-unlock commands.
|
|
5
|
-
//
|
|
6
|
-
// Bridges to CodeGraph by executing the CLI (see docs/adr/0001). Requires the
|
|
7
|
-
// codegraph CLI on PATH: npm i -g @colbymchenry/codegraph
|
|
8
|
-
// Upstream: https://github.com/colbymchenry/codegraph
|
|
9
|
-
|
|
10
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import * as
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
{
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
1
|
+
// pi-codegraph — pi extension
|
|
2
|
+
//
|
|
3
|
+
// CodeGraph support for pi: codegraph_explore tool, /codegraph-init,
|
|
4
|
+
// /codegraph-sync, /codegraph-status, and /codegraph-unlock commands.
|
|
5
|
+
//
|
|
6
|
+
// Bridges to CodeGraph by executing the CLI (see docs/adr/0001). Requires the
|
|
7
|
+
// codegraph CLI on PATH: npm i -g @colbymchenry/codegraph
|
|
8
|
+
// Upstream: https://github.com/colbymchenry/codegraph
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { TSchema, Type } from "typebox";
|
|
13
|
+
import * as fs from "node:fs/promises";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const INSTALL_HINT =
|
|
18
|
+
"未检测到 codegraph CLI(PATH 中无 codegraph 命令),本插件未注入任何工具、命令或技能。" +
|
|
19
|
+
"安装:npm i -g @colbymchenry/codegraph,然后 /reload 或重启 pi 生效。";
|
|
20
|
+
|
|
21
|
+
// Whole-process dedup for the missing-CLI hint: extensions re-run per session and
|
|
22
|
+
// per subagent (separate jiti module copies), so gate on globalThis.
|
|
23
|
+
const MISSING_CLI_HINTED = Symbol.for("pi-codegraph.missing-cli-hinted");
|
|
24
|
+
|
|
25
|
+
// Result of `codegraph version` this session — null until first check.
|
|
26
|
+
let cliAvailable: boolean | null = null;
|
|
27
|
+
|
|
28
|
+
async function execCg(
|
|
29
|
+
pi: ExtensionAPI,
|
|
30
|
+
args: string[],
|
|
31
|
+
options: { signal?: AbortSignal; timeout?: number; cwd?: string } = {},
|
|
32
|
+
) {
|
|
33
|
+
if (process.platform === "win32") {
|
|
34
|
+
return await pi.exec("cmd.exe", ["/d", "/s", "/c", "codegraph", ...args], options);
|
|
35
|
+
}
|
|
36
|
+
return await pi.exec("codegraph", args, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function ensureCli(pi: ExtensionAPI): Promise<boolean> {
|
|
40
|
+
if (cliAvailable !== null) return cliAvailable;
|
|
41
|
+
try {
|
|
42
|
+
const result = await execCg(pi, ["version"], { timeout: 10_000 });
|
|
43
|
+
cliAvailable = result.code === 0;
|
|
44
|
+
} catch {
|
|
45
|
+
cliAvailable = false;
|
|
46
|
+
}
|
|
47
|
+
return cliAvailable;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function textResult(text: string) {
|
|
51
|
+
return { content: [{ type: "text" as const, text }], details: undefined };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function outputOf(result: { stdout: string; stderr: string; code: number }): string {
|
|
55
|
+
return (result.stdout + result.stderr).trim() || `exit ${result.code}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function isIndexed(cwd: string): Promise<boolean> {
|
|
59
|
+
try {
|
|
60
|
+
await fs.access(path.join(cwd, ".codegraph", "codegraph.db"));
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type StatusState = "index" | "sync" | "init" | boolean | undefined;
|
|
68
|
+
|
|
69
|
+
function updateStatusBar(ctx: ExtensionContext, state: StatusState) {
|
|
70
|
+
if (!ctx.hasUI) return;
|
|
71
|
+
const label = state === true ? "index" : state === false ? undefined : state;
|
|
72
|
+
if (label) {
|
|
73
|
+
const text = ctx.ui.theme?.fg
|
|
74
|
+
? `${ctx.ui.theme.fg("accent", "⬡")} ${label}`
|
|
75
|
+
: `⬡ ${label}`;
|
|
76
|
+
ctx.ui.setStatus("codegraph", text);
|
|
77
|
+
} else {
|
|
78
|
+
ctx.ui.setStatus("codegraph", undefined);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 仿照上游 MCP SERVER_INSTRUCTIONS(src/mcp/server-instructions.ts)编写:引导智能体
|
|
83
|
+
// 在 grep/read 之前优先使用 codegraph 工具,并给出反模式与过期处理。提示内容按实际
|
|
84
|
+
// 启用的工具集生成:未开启的工具绝不被宣传(见 docs/adr/0003),避免智能体调用
|
|
85
|
+
// 不存在的工具。
|
|
86
|
+
const EXPLORE_HINT_LINE =
|
|
87
|
+
"`codegraph_explore` — 一次调用完成广泛探索:相关符号的源码 + 调用路径。" +
|
|
88
|
+
"可以点名端点符号(如 `mutateElement renderScene`)以跨越动态分派跳转揭示调用路径。";
|
|
89
|
+
|
|
90
|
+
const EXTRA_TOOL_HINT_LINES: Record<string, string> = {
|
|
91
|
+
codegraph_query: "`codegraph_query` — 定位符号:位置 + 签名,不含源码。",
|
|
92
|
+
codegraph_node:
|
|
93
|
+
"`codegraph_node` — 单个符号的源码 + 调用/被调轨迹(可链式追踪调用图)。",
|
|
94
|
+
codegraph_callers: "`codegraph_callers` — 谁调用了某符号。",
|
|
95
|
+
codegraph_callees: "`codegraph_callees` — 某符号调用了什么。",
|
|
96
|
+
codegraph_impact: "`codegraph_impact` — 修改某符号的影响范围(编辑前调用)。",
|
|
97
|
+
codegraph_files: "`codegraph_files` — 已建索引的文件树(tree/flat/grouped 按语言分组)。",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const HIDDEN_TOOLS_NOTE =
|
|
101
|
+
"细粒度工具(`codegraph_query` `codegraph_node` `codegraph_callers` `codegraph_callees` " +
|
|
102
|
+
"`codegraph_impact` `codegraph_files`)默认隐藏以保持工具列表精简,只有用户通过配置开启后才会注册" +
|
|
103
|
+
"(全局 `~/.pi/agent/extensions/pi-codegraph/config.json` 或项目 `.pi/extensions/pi-codegraph/config.json` 的 " +
|
|
104
|
+
"`extraTools`)——当前会话请勿调用它们。";
|
|
105
|
+
|
|
106
|
+
function buildIndexHint(enabled: Set<string>): string {
|
|
107
|
+
const lines = [
|
|
108
|
+
"# CodeGraph — 本项目已建立索引",
|
|
109
|
+
"",
|
|
110
|
+
"这里存在 `.codegraph/` 索引:项目每个符号、边、文件构成的 SQLite 知识图谱(支持 30+ 语言)。" +
|
|
111
|
+
"它可以回答结构性问题,并给出逐字、带行号的源码(把 codegraph 输出视为已经 Read 过的内容,可直接据此编辑)。",
|
|
112
|
+
"",
|
|
113
|
+
"- 对于结构性问题(X 如何工作 / X 在哪里 / 谁调用 Y / 修改 Z 会破坏什么),应使用 codegraph 工具" +
|
|
114
|
+
"而不是 grep + read——通常一次调用即可完整回答。",
|
|
115
|
+
`- ${EXPLORE_HINT_LINE}`,
|
|
116
|
+
];
|
|
117
|
+
for (const name of Object.keys(EXTRA_TOOL_HINT_LINES)) {
|
|
118
|
+
if (enabled.has(name)) lines.push(`- ${EXTRA_TOOL_HINT_LINES[name]}`);
|
|
119
|
+
}
|
|
120
|
+
lines.push(
|
|
121
|
+
enabled.size === 0
|
|
122
|
+
? `- ${HIDDEN_TOOLS_NOTE}`
|
|
123
|
+
: "- 以上细粒度工具已通过 `extraTools` 配置开启。",
|
|
124
|
+
);
|
|
125
|
+
lines.push(
|
|
126
|
+
"- 反模式:不要先用 grep 或 Read;不要用 grep 重复验证 codegraph 输出(基于 AST,比 grep 更准确);不要手工重建调用流程。",
|
|
127
|
+
'- "Already sent earlier in this conversation":该提示表示内容已在会话上下文中——不要重新获取或 Read。',
|
|
128
|
+
'- 过期提示:如果工具输出包含 "⚠️ Some files referenced below were edited since the last index sync",只直接读取其中被标记的文件。',
|
|
129
|
+
"- 多项目 / Monorepo:传入 `path` 查询任意已建索引的子项目目录。",
|
|
130
|
+
"- 项目没有 `.codegraph/` 时,在该项目使用内置工具;是否建索引由用户决定——必要时建议 /codegraph-init。",
|
|
131
|
+
);
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── 配置:细粒度工具 opt-in(docs/adr/0003)───────────────────────────
|
|
136
|
+
// 全局配置在 ~/.pi/agent/extensions/pi-codegraph/config.json,项目配置在
|
|
137
|
+
// .pi/extensions/pi-codegraph/config.json(覆盖全局,仅对受信任项目生效)。
|
|
138
|
+
// 结构为扁平 JSON:{ "extraTools": ["node", "impact"] }。pi 没有第三方
|
|
139
|
+
// 扩展配置 API,故由扩展自行读取这两个文件。
|
|
140
|
+
type CgConfig = { extraTools?: string[] | "all" };
|
|
141
|
+
|
|
142
|
+
const CONFIG_REL_PATH = path.join("extensions", "pi-codegraph", "config.json");
|
|
143
|
+
|
|
144
|
+
async function readConfigFile<T>(file: string): Promise<T | undefined> {
|
|
145
|
+
try {
|
|
146
|
+
const raw = await fs.readFile(file, "utf8");
|
|
147
|
+
return JSON.parse(raw) as T;
|
|
148
|
+
} catch {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function loadCgConfig(ctx: ExtensionContext): Promise<CgConfig> {
|
|
154
|
+
const global = await readConfigFile<CgConfig>(path.join(getAgentDir(), CONFIG_REL_PATH));
|
|
155
|
+
let project: CgConfig | undefined;
|
|
156
|
+
if (ctx.isProjectTrusted()) {
|
|
157
|
+
project = await readConfigFile<CgConfig>(path.join(ctx.cwd, CONFIG_DIR_NAME, CONFIG_REL_PATH));
|
|
158
|
+
}
|
|
159
|
+
return { ...(global ?? {}), ...(project ?? {}) };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// When the CLI is absent, register nothing but a one-shot hint handler: no
|
|
163
|
+
// tools, no commands, no resources_discover (so no skill injection), no sync.
|
|
164
|
+
export function registerMissingCliHint(pi: ExtensionAPI) {
|
|
165
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
166
|
+
if ((globalThis as Record<symbol, boolean>)[MISSING_CLI_HINTED]) return;
|
|
167
|
+
(globalThis as Record<symbol, boolean>)[MISSING_CLI_HINTED] = true;
|
|
168
|
+
if (ctx.hasUI) ctx.ui.notify(INSTALL_HINT, "warning");
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export default async function codegraphExtension(pi: ExtensionAPI) {
|
|
173
|
+
if (!(await ensureCli(pi))) {
|
|
174
|
+
registerMissingCliHint(pi);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// ── codegraph_explore tool ─────────────────────────────────────────────
|
|
178
|
+
pi.registerTool({
|
|
179
|
+
name: "codegraph_explore",
|
|
180
|
+
label: "CodeGraph Explore",
|
|
181
|
+
description:
|
|
182
|
+
"Broad code exploration in one shot: relevant symbols' verbatim line-numbered source, call paths between them, and a blast-radius summary.",
|
|
183
|
+
promptSnippet:
|
|
184
|
+
"codegraph_explore: symbol source + call paths in one shot from the project's CodeGraph index",
|
|
185
|
+
promptGuidelines: [
|
|
186
|
+
"For structural code questions (how does X work, where is X, what breaks if I change X), prefer codegraph_explore over grep when the project has a .codegraph/ index.",
|
|
187
|
+
"Indexing is the user's decision — never run codegraph init yourself; suggest /codegraph-init instead.",
|
|
188
|
+
],
|
|
189
|
+
parameters: Type.Object({
|
|
190
|
+
query: Type.String({
|
|
191
|
+
description: "Symbol names or a natural-language question about the code",
|
|
192
|
+
}),
|
|
193
|
+
path: Type.Optional(
|
|
194
|
+
Type.String({
|
|
195
|
+
description: "Project path (default: cwd)",
|
|
196
|
+
}),
|
|
197
|
+
),
|
|
198
|
+
maxFiles: Type.Optional(
|
|
199
|
+
Type.Integer({
|
|
200
|
+
description: "Maximum number of files to include source from",
|
|
201
|
+
}),
|
|
202
|
+
),
|
|
203
|
+
}),
|
|
204
|
+
execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
|
|
205
|
+
if (!(await ensureCli(pi))) return textResult(INSTALL_HINT);
|
|
206
|
+
const cwd = params.path ?? ctx.cwd;
|
|
207
|
+
const args = ["explore", params.query, "-p", cwd];
|
|
208
|
+
if (typeof params.maxFiles === "number" && params.maxFiles > 0) {
|
|
209
|
+
args.push("--max-files", String(params.maxFiles));
|
|
210
|
+
}
|
|
211
|
+
const result = await execCg(pi, args, {
|
|
212
|
+
signal,
|
|
213
|
+
timeout: 120_000,
|
|
214
|
+
});
|
|
215
|
+
if (result.killed) return textResult("codegraph explore timed out (120s)");
|
|
216
|
+
// Non-zero exits carry upstream's agent-friendly guidance (e.g. the
|
|
217
|
+
// "not initialized" message) — pass it through verbatim.
|
|
218
|
+
if (result.code !== 0) return textResult(outputOf(result));
|
|
219
|
+
return textResult(result.stdout.trim());
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// ── codegraph_* fine-grained tools (CLI parity with the MCP tools) ────
|
|
224
|
+
// Each maps 1:1 to a codegraph CLI subcommand; `path` selects the project,
|
|
225
|
+
// defaults to the session cwd (same convention as codegraph_explore).
|
|
226
|
+
const projectPath = () =>
|
|
227
|
+
Type.Optional(
|
|
228
|
+
Type.String({
|
|
229
|
+
description: "Project path (default: cwd)",
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
type CliToolDef = {
|
|
234
|
+
name: string;
|
|
235
|
+
label: string;
|
|
236
|
+
description: string;
|
|
237
|
+
snippet: string;
|
|
238
|
+
guidelines: string[];
|
|
239
|
+
subcommand: string;
|
|
240
|
+
parameters: TSchema;
|
|
241
|
+
positional?: (p: Record<string, unknown>) => string[];
|
|
242
|
+
flags?: (p: Record<string, unknown>) => string[];
|
|
243
|
+
timeout?: number;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
function registerCliTool(pi: ExtensionAPI, def: CliToolDef) {
|
|
247
|
+
pi.registerTool({
|
|
248
|
+
name: def.name,
|
|
249
|
+
label: def.label,
|
|
250
|
+
description: def.description,
|
|
251
|
+
promptSnippet: def.snippet,
|
|
252
|
+
promptGuidelines: def.guidelines,
|
|
253
|
+
parameters: def.parameters,
|
|
254
|
+
execute: async (_toolCallId, params: Record<string, unknown>, signal, _onUpdate, ctx) => {
|
|
255
|
+
const cwd = (params.path as string | undefined) ?? ctx.cwd;
|
|
256
|
+
const args = [
|
|
257
|
+
def.subcommand,
|
|
258
|
+
...(def.positional?.(params) ?? []),
|
|
259
|
+
"-p",
|
|
260
|
+
cwd,
|
|
261
|
+
...(def.flags?.(params) ?? []),
|
|
262
|
+
];
|
|
263
|
+
const result = await execCg(pi, args, {
|
|
264
|
+
signal,
|
|
265
|
+
timeout: def.timeout ?? 60_000,
|
|
266
|
+
});
|
|
267
|
+
if (result.killed) return textResult(`codegraph ${def.subcommand} timed out`);
|
|
268
|
+
// Non-zero exits carry upstream's agent-friendly guidance — pass it through.
|
|
269
|
+
if (result.code !== 0) return textResult(outputOf(result));
|
|
270
|
+
return textResult(result.stdout.trim());
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const cliTools: CliToolDef[] = [
|
|
276
|
+
{
|
|
277
|
+
name: "codegraph_query",
|
|
278
|
+
label: "CodeGraph Query",
|
|
279
|
+
description:
|
|
280
|
+
"Search symbols by name. Returns locations and signatures only (no source). Use to locate where a symbol is declared.",
|
|
281
|
+
snippet: "codegraph_query: symbol locations + signatures by name",
|
|
282
|
+
guidelines: [
|
|
283
|
+
"To locate where a symbol is declared (kind, file, line), use codegraph_query before grep.",
|
|
284
|
+
],
|
|
285
|
+
subcommand: "query",
|
|
286
|
+
parameters: Type.Object({
|
|
287
|
+
search: Type.String({ description: "Symbol name or partial name to search" }),
|
|
288
|
+
kind: Type.Optional(
|
|
289
|
+
Type.String({
|
|
290
|
+
description:
|
|
291
|
+
"Filter by node kind: function, method, class, interface, type, variable, route, component",
|
|
292
|
+
}),
|
|
293
|
+
),
|
|
294
|
+
limit: Type.Optional(Type.Integer({ description: "Maximum results (default 10)" })),
|
|
295
|
+
path: projectPath(),
|
|
296
|
+
}),
|
|
297
|
+
positional: (p) => [p.search as string],
|
|
298
|
+
flags: (p) => [
|
|
299
|
+
...(p.kind ? ["--kind", p.kind as string] : []),
|
|
300
|
+
...(typeof p.limit === "number" && p.limit > 0 ? ["--limit", String(p.limit)] : []),
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "codegraph_node",
|
|
305
|
+
label: "CodeGraph Node",
|
|
306
|
+
description:
|
|
307
|
+
"One symbol's source plus its caller/callee trail. Chain it to follow a call graph across files.",
|
|
308
|
+
snippet: "codegraph_node: a symbol's source + caller/callee trail",
|
|
309
|
+
guidelines: [
|
|
310
|
+
"To deep-dive one known symbol (verbatim source + who it calls / is called by), use codegraph_node.",
|
|
311
|
+
],
|
|
312
|
+
subcommand: "node",
|
|
313
|
+
parameters: Type.Object({
|
|
314
|
+
name: Type.String({ description: "Symbol name to inspect" }),
|
|
315
|
+
path: projectPath(),
|
|
316
|
+
}),
|
|
317
|
+
positional: (p) => [p.name as string],
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: "codegraph_callers",
|
|
321
|
+
label: "CodeGraph Callers",
|
|
322
|
+
description: "Find all functions or methods that call a specific symbol.",
|
|
323
|
+
snippet: "codegraph_callers: who calls a symbol",
|
|
324
|
+
guidelines: [
|
|
325
|
+
"To find what calls a symbol (reverse dependencies), use codegraph_callers.",
|
|
326
|
+
],
|
|
327
|
+
subcommand: "callers",
|
|
328
|
+
parameters: Type.Object({
|
|
329
|
+
symbol: Type.String({ description: "Symbol name whose callers to find" }),
|
|
330
|
+
limit: Type.Optional(Type.Integer({ description: "Maximum results (default 20)" })),
|
|
331
|
+
path: projectPath(),
|
|
332
|
+
}),
|
|
333
|
+
positional: (p) => [p.symbol as string],
|
|
334
|
+
flags: (p) => [
|
|
335
|
+
...(typeof p.limit === "number" && p.limit > 0 ? ["--limit", String(p.limit)] : []),
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: "codegraph_callees",
|
|
340
|
+
label: "CodeGraph Callees",
|
|
341
|
+
description: "Find all functions or methods that a specific symbol calls.",
|
|
342
|
+
snippet: "codegraph_callees: what a symbol calls",
|
|
343
|
+
guidelines: [
|
|
344
|
+
"To find what a symbol calls (its outgoing edges), use codegraph_callees.",
|
|
345
|
+
],
|
|
346
|
+
subcommand: "callees",
|
|
347
|
+
parameters: Type.Object({
|
|
348
|
+
symbol: Type.String({ description: "Symbol name whose callees to find" }),
|
|
349
|
+
limit: Type.Optional(Type.Integer({ description: "Maximum results (default 20)" })),
|
|
350
|
+
path: projectPath(),
|
|
351
|
+
}),
|
|
352
|
+
positional: (p) => [p.symbol as string],
|
|
353
|
+
flags: (p) => [
|
|
354
|
+
...(typeof p.limit === "number" && p.limit > 0 ? ["--limit", String(p.limit)] : []),
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "codegraph_impact",
|
|
359
|
+
label: "CodeGraph Impact",
|
|
360
|
+
description: "Analyze what code is affected by changing a symbol (blast radius).",
|
|
361
|
+
snippet: "codegraph_impact: blast radius of changing a symbol",
|
|
362
|
+
guidelines: [
|
|
363
|
+
"Before editing a symbol, use codegraph_impact to see what depends on it.",
|
|
364
|
+
],
|
|
365
|
+
subcommand: "impact",
|
|
366
|
+
parameters: Type.Object({
|
|
367
|
+
symbol: Type.String({ description: "Symbol name to analyze impact of changing" }),
|
|
368
|
+
depth: Type.Optional(Type.Integer({ description: "Traversal depth (default 2)" })),
|
|
369
|
+
path: projectPath(),
|
|
370
|
+
}),
|
|
371
|
+
positional: (p) => [p.symbol as string],
|
|
372
|
+
flags: (p) => [
|
|
373
|
+
...(typeof p.depth === "number" && p.depth > 0 ? ["--depth", String(p.depth)] : []),
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "codegraph_files",
|
|
378
|
+
label: "CodeGraph Files",
|
|
379
|
+
description:
|
|
380
|
+
"Show the indexed project's file structure (tree, flat, or grouped by language), with per-file symbol counts.",
|
|
381
|
+
snippet: "codegraph_files: indexed file tree with symbol counts",
|
|
382
|
+
guidelines: [
|
|
383
|
+
"To get a structured view of the project files, use codegraph_files.",
|
|
384
|
+
],
|
|
385
|
+
subcommand: "files",
|
|
386
|
+
parameters: Type.Object({
|
|
387
|
+
dir: Type.Optional(Type.String({ description: "Subdirectory within the project to show" })),
|
|
388
|
+
pattern: Type.Optional(Type.String({ description: "Glob pattern to filter files" })),
|
|
389
|
+
format: Type.Optional(
|
|
390
|
+
Type.Union([Type.Literal("tree"), Type.Literal("flat"), Type.Literal("grouped")]),
|
|
391
|
+
),
|
|
392
|
+
maxDepth: Type.Optional(Type.Integer({ description: "Maximum directory depth for tree format" })),
|
|
393
|
+
path: projectPath(),
|
|
394
|
+
}),
|
|
395
|
+
positional: (p) => (p.dir ? [p.dir as string] : []),
|
|
396
|
+
flags: (p) => [
|
|
397
|
+
...(p.pattern ? ["--pattern", p.pattern as string] : []),
|
|
398
|
+
...(p.format ? ["--format", p.format as string] : []),
|
|
399
|
+
...(typeof p.maxDepth === "number" && p.maxDepth > 0
|
|
400
|
+
? ["--max-depth", String(p.maxDepth)]
|
|
401
|
+
: []),
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
// 细粒度工具为 opt-in:仅当配置开启时才注册,保证默认工具列表精简(见
|
|
407
|
+
// docs/adr/0003)。`registeredExtraTools` 防止同一进程内重复注册;
|
|
408
|
+
// `enabledExtraTools` 同时驱动活动工具集与注入的提示内容。
|
|
409
|
+
const registeredExtraTools = new Set<string>();
|
|
410
|
+
let enabledExtraTools = new Set<string>();
|
|
411
|
+
|
|
412
|
+
const extraToolKey = (def: CliToolDef) => def.name.replace(/^codegraph_/, "");
|
|
413
|
+
|
|
414
|
+
function resolveExtraTools(cfg: CgConfig, ctx: ExtensionContext): Set<string> {
|
|
415
|
+
const enabled = new Set<string>();
|
|
416
|
+
const wanted = cfg.extraTools ?? [];
|
|
417
|
+
const names = wanted === "all" ? cliTools.map((d) => d.name) : wanted;
|
|
418
|
+
for (const item of names) {
|
|
419
|
+
const key = typeof item === "string" ? item.trim() : "";
|
|
420
|
+
const def = cliTools.find((d) => d.name === key || extraToolKey(d) === key);
|
|
421
|
+
if (def) {
|
|
422
|
+
enabled.add(def.name);
|
|
423
|
+
} else if (key && ctx.hasUI) {
|
|
424
|
+
ctx.ui.notify(`codegraph: unknown extra tool "${item}" (ignored)`, "warning");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return enabled;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function applyExtraTools(pi: ExtensionAPI, cfg: CgConfig, ctx: ExtensionContext) {
|
|
431
|
+
enabledExtraTools = resolveExtraTools(cfg, ctx);
|
|
432
|
+
for (const def of cliTools) {
|
|
433
|
+
if (enabledExtraTools.has(def.name) && !registeredExtraTools.has(def.name)) {
|
|
434
|
+
registerCliTool(pi, def);
|
|
435
|
+
registeredExtraTools.add(def.name);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// 将活动工具集与配置对齐:此前已注册但当前未启用的工具(例如切换项目后)
|
|
439
|
+
// 不得再出现在系统提示中。
|
|
440
|
+
const active = new Set(pi.getActiveTools());
|
|
441
|
+
const extraNames = new Set(cliTools.map((d) => d.name));
|
|
442
|
+
let changed = false;
|
|
443
|
+
for (const name of extraNames) {
|
|
444
|
+
if (enabledExtraTools.has(name) && !active.has(name)) {
|
|
445
|
+
active.add(name);
|
|
446
|
+
changed = true;
|
|
447
|
+
} else if (!enabledExtraTools.has(name) && active.has(name)) {
|
|
448
|
+
active.delete(name);
|
|
449
|
+
changed = true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (changed) pi.setActiveTools([...active]);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ── /codegraph-init ────────────────────────────────────────────────────
|
|
456
|
+
pi.registerCommand("codegraph-init", {
|
|
457
|
+
description: "Build the CodeGraph index for the current project (codegraph init)",
|
|
458
|
+
handler: async (args, ctx) => {
|
|
459
|
+
if (!(await ensureCli(pi))) {
|
|
460
|
+
if (ctx.hasUI) ctx.ui.notify(INSTALL_HINT, "error");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const target = args?.trim() || ctx.cwd;
|
|
464
|
+
if (ctx.hasUI) {
|
|
465
|
+
ctx.ui.notify(`Indexing ${target} — can take minutes on a large repo…`, "info");
|
|
466
|
+
}
|
|
467
|
+
updateStatusBar(ctx, "init");
|
|
468
|
+
try {
|
|
469
|
+
const result = await execCg(pi, ["init", target], { timeout: 1_800_000 });
|
|
470
|
+
const out = outputOf(result);
|
|
471
|
+
const success = result.code === 0;
|
|
472
|
+
if (ctx.hasUI) {
|
|
473
|
+
ctx.ui.notify(
|
|
474
|
+
success ? "CodeGraph index built." : `codegraph init failed (exit ${result.code})`,
|
|
475
|
+
success ? "info" : "error",
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
pi.sendMessage(
|
|
479
|
+
{ customType: "codegraph-init", content: out, display: true },
|
|
480
|
+
{ triggerTurn: false },
|
|
481
|
+
);
|
|
482
|
+
} finally {
|
|
483
|
+
updateStatusBar(ctx, await isIndexed(ctx.cwd));
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// ── /codegraph-sync ────────────────────────────────────────────────────
|
|
489
|
+
pi.registerCommand("codegraph-sync", {
|
|
490
|
+
description: "Sync CodeGraph changes since last index (codegraph sync)",
|
|
491
|
+
handler: async (args, ctx) => {
|
|
492
|
+
if (!(await ensureCli(pi))) {
|
|
493
|
+
if (ctx.hasUI) ctx.ui.notify(INSTALL_HINT, "error");
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const target = args?.trim() || ctx.cwd;
|
|
497
|
+
if (ctx.hasUI) {
|
|
498
|
+
ctx.ui.notify(`Syncing CodeGraph for ${target}…`, "info");
|
|
499
|
+
}
|
|
500
|
+
updateStatusBar(ctx, "sync");
|
|
501
|
+
try {
|
|
502
|
+
const result = await execCg(pi, ["sync", target], { timeout: 300_000 });
|
|
503
|
+
const out = outputOf(result);
|
|
504
|
+
if (ctx.hasUI) {
|
|
505
|
+
ctx.ui.notify(out, result.code === 0 ? "info" : "warning");
|
|
506
|
+
}
|
|
507
|
+
pi.sendMessage(
|
|
508
|
+
{ customType: "codegraph-sync", content: out, display: true },
|
|
509
|
+
{ triggerTurn: false },
|
|
510
|
+
);
|
|
511
|
+
} finally {
|
|
512
|
+
updateStatusBar(ctx, await isIndexed(ctx.cwd));
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// ── /codegraph-status ──────────────────────────────────────────────────
|
|
518
|
+
pi.registerCommand("codegraph-status", {
|
|
519
|
+
description: "Show CodeGraph index status and statistics",
|
|
520
|
+
handler: async (args, ctx) => {
|
|
521
|
+
if (!(await ensureCli(pi))) {
|
|
522
|
+
if (ctx.hasUI) ctx.ui.notify(INSTALL_HINT, "error");
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const target = args?.trim() || ctx.cwd;
|
|
526
|
+
const result = await execCg(pi, ["status", target], { timeout: 30_000 });
|
|
527
|
+
const out = outputOf(result);
|
|
528
|
+
if (ctx.hasUI) ctx.ui.notify(out, result.code === 0 ? "info" : "warning");
|
|
529
|
+
updateStatusBar(ctx, await isIndexed(ctx.cwd));
|
|
530
|
+
pi.sendMessage(
|
|
531
|
+
{ customType: "codegraph-status", content: out, display: true },
|
|
532
|
+
{ triggerTurn: false },
|
|
533
|
+
);
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// ── /codegraph-unlock ──────────────────────────────────────────────────
|
|
538
|
+
pi.registerCommand("codegraph-unlock", {
|
|
539
|
+
description: "Release stale CodeGraph database lock (codegraph unlock)",
|
|
540
|
+
handler: async (args, ctx) => {
|
|
541
|
+
if (!(await ensureCli(pi))) {
|
|
542
|
+
if (ctx.hasUI) ctx.ui.notify(INSTALL_HINT, "error");
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const target = args?.trim() || ctx.cwd;
|
|
546
|
+
const result = await execCg(pi, ["unlock", target], { timeout: 10_000 });
|
|
547
|
+
const out = outputOf(result);
|
|
548
|
+
if (ctx.hasUI) ctx.ui.notify(out, result.code === 0 ? "info" : "warning");
|
|
549
|
+
pi.sendMessage(
|
|
550
|
+
{ customType: "codegraph-unlock", content: out, display: true },
|
|
551
|
+
{ triggerTurn: false },
|
|
552
|
+
);
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// ── resources_discover: contribute the skill only when the CLI exists ──
|
|
557
|
+
pi.on("resources_discover", async () => {
|
|
558
|
+
return {
|
|
559
|
+
skillPaths: [fileURLToPath(new URL("../skills/codegraph/SKILL.md", import.meta.url))],
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ── session_start:opt-in 工具 + 增量同步 + 上下文提示 ───────────────
|
|
564
|
+
pi.on("session_start", async (event, ctx) => {
|
|
565
|
+
const cfg = await loadCgConfig(ctx);
|
|
566
|
+
applyExtraTools(pi, cfg, ctx);
|
|
567
|
+
const indexed = await isIndexed(ctx.cwd);
|
|
568
|
+
if (indexed) {
|
|
569
|
+
updateStatusBar(ctx, "sync");
|
|
570
|
+
// Incremental sync; near-zero cost when nothing changed.
|
|
571
|
+
void execCg(pi, ["sync", "-q", ctx.cwd], { timeout: 300_000 })
|
|
572
|
+
.catch(() => {})
|
|
573
|
+
.finally(async () => {
|
|
574
|
+
updateStatusBar(ctx, await isIndexed(ctx.cwd));
|
|
575
|
+
});
|
|
576
|
+
} else {
|
|
577
|
+
updateStatusBar(ctx, false);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Inject the agent playbook once per process when the project IS
|
|
581
|
+
// indexed (upstream does this via MCP initialize instructions). Skip
|
|
582
|
+
// "reload": extensions rebind in place and the message would duplicate.
|
|
583
|
+
if (event.reason !== "reload" && indexed) {
|
|
584
|
+
pi.sendMessage(
|
|
585
|
+
{ customType: "codegraph-context", content: buildIndexHint(enabledExtraTools), display: false },
|
|
586
|
+
{ triggerTurn: false },
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
592
|
+
if (ctx.hasUI) {
|
|
593
|
+
ctx.ui.setStatus("codegraph", undefined);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
package/package.json
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@liuxincuit/pi-codegraph",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CodeGraph support for pi — symbol source + call paths via the codegraph CLI",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"pi-package",
|
|
8
|
-
"codegraph",
|
|
9
|
-
"code-intelligence",
|
|
10
|
-
"knowledge-graph"
|
|
11
|
-
],
|
|
12
|
-
"repository": {
|
|
13
|
-
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/liuxincuit/pi-codegraph.git"
|
|
15
|
-
},
|
|
16
|
-
"author": "liuxincuit",
|
|
17
|
-
"license": "MIT",
|
|
18
|
-
"peerDependencies": {
|
|
19
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
20
|
-
},
|
|
21
|
-
"devDependencies": {
|
|
22
|
-
"@earendil-works/pi-coding-agent": "*",
|
|
23
|
-
"@types/node": "^22.0.0",
|
|
24
|
-
"typebox": "^1.3.7",
|
|
25
|
-
"typescript": "^5.5.0"
|
|
26
|
-
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"typecheck": "tsc --noEmit",
|
|
29
|
-
"smoke": "tsc extensions/codegraph.ts --outDir .smoke-build --module esnext --target es2022 --moduleResolution bundler --skipLibCheck --noEmit false && node smoke.mjs"
|
|
30
|
-
},
|
|
31
|
-
"pi": {
|
|
32
|
-
"extensions": [
|
|
33
|
-
"./extensions"
|
|
34
|
-
]
|
|
35
|
-
},
|
|
36
|
-
"files": [
|
|
37
|
-
"extensions/",
|
|
38
|
-
"skills/",
|
|
39
|
-
"CONTEXT.md",
|
|
40
|
-
"README.md",
|
|
41
|
-
"LICENSE"
|
|
42
|
-
]
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@liuxincuit/pi-codegraph",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "CodeGraph support for pi — symbol source + call paths via the codegraph CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"codegraph",
|
|
9
|
+
"code-intelligence",
|
|
10
|
+
"knowledge-graph"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/liuxincuit/pi-codegraph.git"
|
|
15
|
+
},
|
|
16
|
+
"author": "liuxincuit",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"typebox": "^1.3.7",
|
|
25
|
+
"typescript": "^5.5.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"smoke": "tsc extensions/codegraph.ts --outDir .smoke-build --module esnext --target es2022 --moduleResolution bundler --skipLibCheck --noEmit false && node smoke.mjs"
|
|
30
|
+
},
|
|
31
|
+
"pi": {
|
|
32
|
+
"extensions": [
|
|
33
|
+
"./extensions"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"extensions/",
|
|
38
|
+
"skills/",
|
|
39
|
+
"CONTEXT.md",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -1,27 +1,43 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: codegraph
|
|
3
|
-
description:
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# CodeGraph
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- `
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
1
|
+
---
|
|
2
|
+
name: codegraph
|
|
3
|
+
description: 通过 codegraph_explore 工具查询项目的 CodeGraph 索引(符号源码、调用路径、影响范围)。当回答结构化代码问题——X 如何工作、X 在哪里、谁调用 Y、修改某处会影响什么——且项目存在 .codegraph/ 索引时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CodeGraph
|
|
7
|
+
|
|
8
|
+
`codegraph_explore` 工具从项目的 CodeGraph 索引(`.codegraph/`,由 `codegraph` CLI 建立与维护)回答结构化代码问题。**当项目存在 `.codegraph/` 索引时**,应优先使用它而不是 grep/read。
|
|
9
|
+
|
|
10
|
+
## 默认工具
|
|
11
|
+
|
|
12
|
+
- `codegraph_explore` — 一次调用完成广泛探索:相关符号的源码 + 调用路径。可以点名端点符号(`mutateElement renderScene`)以跨越动态分派跳转揭示调用路径。参数:`query`(符号名或自然语言问题)、可选 `maxFiles`(限制返回源码行数)。
|
|
13
|
+
|
|
14
|
+
绝大多数结构化问题只需 `codegraph_explore` 即可解决。该工具始终注册。
|
|
15
|
+
|
|
16
|
+
## 细粒度工具(可选开启)
|
|
17
|
+
|
|
18
|
+
以下细粒度工具**默认隐藏**,以保持工具列表精简。它们只有在用户通过配置 `extraTools` 开启后才会注册;开启之前调用会报 "tool not found"。除非能在 `Available tools` 中看到它们,否则不要调用。
|
|
19
|
+
|
|
20
|
+
- `codegraph_query` — 定位符号:位置 + 签名,不含源码。
|
|
21
|
+
- `codegraph_node` — 单个已知符号的逐字源码 + 调用/被调轨迹,可链式追踪调用图。
|
|
22
|
+
- `codegraph_callers` / `codegraph_callees` — 谁调用了某符号 / 某符号调用了什么。
|
|
23
|
+
- `codegraph_impact` — 修改某符号的影响范围;编辑前调用。
|
|
24
|
+
- `codegraph_files` — 已建索引的文件树(tree/flat/grouped 按语言分组)与符号数量。
|
|
25
|
+
|
|
26
|
+
开启方式:在 `~/.pi/agent/extensions/pi-codegraph/config.json`(全局)或 `.pi/extensions/pi-codegraph/config.json`(项目,覆盖全局)中配置:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{ "extraTools": ["query", "node", "impact"] }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
短名(`node`、`impact`)与完整工具名(`codegraph_node`)均可,`"extraTools": "all"` 开启全部。配置在下一个会话生效(`/reload` 或重启 pi)。如果反复需要某个隐藏工具,可以向用户建议此配置。
|
|
33
|
+
|
|
34
|
+
## 如何查询
|
|
35
|
+
|
|
36
|
+
- `codegraph_explore` 接受 `path` 参数:要查询的项目目录,默认为当前工作目录。在 monorepo 中,传入包含 `.codegraph/` 的子项目目录。
|
|
37
|
+
|
|
38
|
+
## 反模式与指导
|
|
39
|
+
|
|
40
|
+
- **信任 AST 结果。** 不要用 grep 重复验证 codegraph 输出。
|
|
41
|
+
- **Already sent earlier in this conversation。** 当出现该提示时,内容已在会话上下文中——回看上下文,不要重新获取或 Read。
|
|
42
|
+
- **过期提示。** 如果输出警告 `⚠️ Some files referenced below were edited since the last index sync`,只直接读取其中被标记的文件;其余内容仍然新鲜。
|
|
43
|
+
- **无索引不可用。** 如果输出提示项目未建索引,本会话内停止对该项目调用 `codegraph_explore`,改用内置工具。是否建索引由用户决定——适当时建议用户运行 `/codegraph-init`。
|