@liuxincuit/pi-codegraph 0.1.2 → 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 +14 -6
- package/extensions/codegraph.ts +596 -488
- package/package.json +43 -43
- package/skills/codegraph/SKILL.md +31 -18
package/extensions/codegraph.ts
CHANGED
|
@@ -1,488 +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
|
-
name
|
|
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
|
-
snippet: "
|
|
282
|
-
guidelines: [
|
|
283
|
-
"To
|
|
284
|
-
],
|
|
285
|
-
subcommand: "
|
|
286
|
-
parameters: Type.Object({
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
),
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
+
}
|