@trim21/personal-pi-extensions 0.0.332 → 0.0.334
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/package.json +1 -1
- package/src/aft/callgraph.md +18 -0
- package/src/aft/import.md +12 -0
- package/src/aft/imports.ts +10 -0
- package/src/aft/outline.md +11 -0
- package/src/aft/refactor.md +9 -0
- package/src/aft/refactor.ts +10 -0
- package/src/aft/search.md +11 -0
- package/src/aft/tools.ts +51 -92
- package/src/aft/zoom.md +11 -0
- package/src/claude-code/bash.md +1 -1
- package/src/claude-code/edit.md +1 -1
- package/src/claude-code/files.ts +3 -3
- package/src/claude-code/glob.md +1 -1
- package/src/claude-code/glob.ts +1 -1
- package/src/claude-code/grep.md +1 -1
- package/src/claude-code/grep.ts +1 -1
- package/src/claude-code/read.md +1 -1
- package/src/claude-code/session-tools.ts +1 -1
- package/src/claude-code/shell.ts +1 -1
- package/src/claude-code/write.md +1 -1
- package/src/spawn-agent.ts +5 -13
- package/src/system-prompt/index.ts +4 -4
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
### aft_callgraph tool
|
|
2
|
+
|
|
3
|
+
基于真实调用图回答代码关系问题(谁调用我、影响面、调用链),替代 grep + read 的链条式排查。
|
|
4
|
+
|
|
5
|
+
op 选择:
|
|
6
|
+
|
|
7
|
+
- `callers`:谁调用了目标符号。改名/改签名前先查调用点。含测试文件用 `includeTests: true`。
|
|
8
|
+
- `impact`:改动一个符号会波及谁(影响面分析)。与 callers 配合评估重构风险。
|
|
9
|
+
- `call_tree`:目标符号调用了什么(展开调用链)。
|
|
10
|
+
- `trace_to`:从某个入口函数如何执行到目标符号(调用链路径)。
|
|
11
|
+
- `trace_to_symbol`:两个符号之间的最短路径。需要 `toSymbol`;同名符号歧义时用 `toPath` 指定目标文件。
|
|
12
|
+
- `trace_data`:追踪某个值在参数/赋值间的流转。需要 `expression`(如参数名、变量名)。
|
|
13
|
+
|
|
14
|
+
参数:`path`(包含目标符号的文件)+ `symbol` 必填;`depth` 限制遍历深度。
|
|
15
|
+
|
|
16
|
+
标记含义:`~` = 仅按名字解析的边(可能指向同名符号);`[unresolved]` = 未解析到定义的调用点(外部库/stdlib 默认折叠为每父节点一条摘要,`includeUnresolved: true` 逐个列出)。
|
|
17
|
+
|
|
18
|
+
符号未定义或调用图索引仍在构建时返回文本说明,不报错。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
### aft_import tool
|
|
2
|
+
|
|
3
|
+
语言感知的 import 管理:add / remove。
|
|
4
|
+
|
|
5
|
+
支持 TS, JS, TSX, Python, Rust, Go, Solidity, Java, C#, PHP, Kotlin, Scala, Swift, Ruby, Lua, C, C++, Perl, Vue。
|
|
6
|
+
|
|
7
|
+
- `add`:添加默认导入(`default_import`)、具名导入(`names`,支持按名 `as` 别名,如 `['useState']`、`['ERC20', 'IERC20 as IToken']`)、命名空间导入(`namespace`,如 `import * as ns from 'mod'`)。
|
|
8
|
+
- `remove`:移除整个 import(只给 `module`),或只移除其中某个具名导入(`remove_name`)。
|
|
9
|
+
- 语言相关修饰符用 `modifiers`(如 Java/C# `static`、Swift `@testable`);符号类导入用 `import_kind`(如 PHP `function`/`const`、Swift `struct`/`enum`);仅类型导入用 `type_only`(仅 TS)。
|
|
10
|
+
- `validate`:编辑后校验级别,默认 `syntax`,`full` 做完整校验。
|
|
11
|
+
|
|
12
|
+
import 排序整理交给 lint(如 import/order + --fix),本工具不负责排序。写操作走路径级审批。
|
package/src/aft/imports.ts
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* 与 aft_refactor 相同:不支持 preview,写保护为路径级审批。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
9
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
13
|
import { Type } from "typebox";
|
|
11
14
|
|
|
@@ -14,6 +17,12 @@ import { guardWriteAccess } from "../lib/write-guard.js";
|
|
|
14
17
|
import { callAftTool } from "./bridge.js";
|
|
15
18
|
import { type AftToolContext, bridgeFor, buildPendantMarkdown, resolvePathArg } from "./tools.js";
|
|
16
19
|
|
|
20
|
+
/** 工具使用指南,以 markdown 形式维护,读起来像文档。 */
|
|
21
|
+
const IMPORT_PROMPT = readFileSync(
|
|
22
|
+
fileURLToPath(new URL("import.md", import.meta.url)),
|
|
23
|
+
"utf8",
|
|
24
|
+
).trim();
|
|
25
|
+
|
|
17
26
|
const IMPORT_OPS = ["add", "remove"] as const;
|
|
18
27
|
const VALIDATE_LEVELS = ["syntax", "full"] as const;
|
|
19
28
|
|
|
@@ -82,6 +91,7 @@ export function registerImportTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
82
91
|
"import 排序整理交给 lint(如 import/order + --fix),本工具不负责。",
|
|
83
92
|
].join("\n"),
|
|
84
93
|
promptSnippet: "Language-aware import add / remove",
|
|
94
|
+
promptGuidelines: [IMPORT_PROMPT],
|
|
85
95
|
parameters: ImportParams,
|
|
86
96
|
async execute(_id, params, _signal, _onUpdate, extCtx) {
|
|
87
97
|
if (params.module === undefined || params.module.trim() === "") {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### aft_outline tool
|
|
2
|
+
|
|
3
|
+
在读取具体内容之前先了解文件或目录的结构(比整文件 read 省 token)。
|
|
4
|
+
|
|
5
|
+
- target 为文件路径时:返回该文件的符号大纲,每个符号带签名与行号(如 `function greet(name: string): void 5:12`);Markdown/HTML 返回标题层级。适合先定位符号名和位置,再决定是否用 aft_zoom 看实现。
|
|
6
|
+
- target 为目录路径时:默认返回扁平文件树(语言、顶层符号数、字节大小),一眼看清目录里有什么、规模多大;传 `files: false` 可切换为符号大纲(目录下每个文件的符号树,无签名,输出上限 30KB,超限截断)。
|
|
7
|
+
- `includeTests` 只在符号大纲模式(`files: false`)生效:为 true 时包含测试文件,默认排除。
|
|
8
|
+
- 目录递归上限 200 个文件;超出部分在结果中标记截断。
|
|
9
|
+
- 只接受单个 target,不支持数组;跨文件批量用多次调用。
|
|
10
|
+
|
|
11
|
+
分工:看结构用本工具;看某个符号的完整源码用 aft_zoom;看符号间调用关系用 aft_callgraph;按语义/文本搜代码用 aft_search。
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### aft_refactor tool
|
|
2
|
+
|
|
3
|
+
workspace-wide 重构:跨文件移动符号、抽函数、内联,自动更新引用与 import。
|
|
4
|
+
|
|
5
|
+
- `move`:把顶层符号(非嵌套函数/类方法)移到另一个文件,全 workspace 重写 import 与引用;执行前自动创建 checkpoint。需要 `symbol` + `destination`;同名符号歧义时用 `scope` 消歧。注意:move / rename 整个文件请用 aft_move(OS 层操作,不更新引用)。
|
|
6
|
+
- `extract`:把行区间抽成新函数(TS/JS/TSX、Python)。需要 `name` + `start_line` + `end_line`(1 起,含端点)。
|
|
7
|
+
- `inline`:把调用点替换为函数体。需要 `symbol` + `call_site_line`(1 起)。
|
|
8
|
+
|
|
9
|
+
重构是写操作:不支持 preview(无 diff 预览),写保护退化为路径级审批——workspace 内目标自动放行,外部路径需要确认。重构前先看影响面:对目标符号跑 aft_callgraph `impact`,确认波及范围符合预期再动手。
|
package/src/aft/refactor.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* (workspace 内自动放行,外部路径经 write-guard 确认,无 diff 预览)。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
10
13
|
import { coerceOptionalInt } from "@cortexkit/aft-bridge";
|
|
11
14
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
15
|
import { Type } from "typebox";
|
|
@@ -16,6 +19,12 @@ import { guardWriteAccess } from "../lib/write-guard.js";
|
|
|
16
19
|
import { callAftTool } from "./bridge.js";
|
|
17
20
|
import { type AftToolContext, bridgeFor, buildPendantMarkdown, resolvePathArg } from "./tools.js";
|
|
18
21
|
|
|
22
|
+
/** 工具使用指南,以 markdown 形式维护,读起来像文档。 */
|
|
23
|
+
const REFACTOR_PROMPT = readFileSync(
|
|
24
|
+
fileURLToPath(new URL("refactor.md", import.meta.url)),
|
|
25
|
+
"utf8",
|
|
26
|
+
).trim();
|
|
27
|
+
|
|
19
28
|
const REFACTOR_OPS = ["move", "extract", "inline"] as const;
|
|
20
29
|
|
|
21
30
|
function requireField(value: unknown, name: string, op: string): void {
|
|
@@ -68,6 +77,7 @@ export function registerRefactorTool(pi: ExtensionAPI, ctx: AftToolContext): voi
|
|
|
68
77
|
"move / rename 整个文件用 aft_move(OS 层操作,不更新引用);移动代码符号用本工具 op=move。",
|
|
69
78
|
].join("\n"),
|
|
70
79
|
promptSnippet: "Workspace-wide symbol move / function extraction / inlining",
|
|
80
|
+
promptGuidelines: [REFACTOR_PROMPT],
|
|
71
81
|
parameters: RefactorParams,
|
|
72
82
|
async execute(_id, params, _signal, _onUpdate, extCtx) {
|
|
73
83
|
const startLine = coerceOptionalInt(
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### aft_search tool
|
|
2
|
+
|
|
3
|
+
一个工具完成代码搜索,按意图自动路由到合适的引擎并按相关度排序。
|
|
4
|
+
|
|
5
|
+
- 概念类查询("ORM 如何构建并执行查询")用自然语言整句:语义通道理解意图,匹配 docstring 与注释。
|
|
6
|
+
- 精确的名字、字符串、正则保持简短(`^export`、`Cargo.lock`):走词法/正则匹配通道。
|
|
7
|
+
- `topK`:最大结果数,默认 10,最大 100。
|
|
8
|
+
- `includeTests: true`:包含测试文件,默认排除。
|
|
9
|
+
- `path`:仅当要搜索另一个 Git 项目时设置(绝对或 ~ 路径);默认搜索当前项目。
|
|
10
|
+
|
|
11
|
+
需要语义索引可用(aft.jsonc 中 `semantic_search: true` 且 embedding 后端就绪);否则退化为词法/正则通道。启用语义索引时,首次调用会阻塞到索引构建完成,避免拿到部分结果。
|
package/src/aft/tools.ts
CHANGED
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* 由 Rust 侧(tree-sitter 符号表 / trigram 索引 / 调用图)计算。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { stat } from "node:fs/promises";
|
|
8
10
|
import { homedir } from "node:os";
|
|
9
11
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
10
13
|
|
|
11
14
|
import {
|
|
12
15
|
type AftProjectTransport,
|
|
@@ -23,6 +26,21 @@ import { Type } from "typebox";
|
|
|
23
26
|
import { type ToolPendant } from "../lib/pendant.js";
|
|
24
27
|
import { callAftTool, SEMANTIC_INDEX_WAIT_TIMEOUT_MS } from "./bridge.js";
|
|
25
28
|
|
|
29
|
+
/** 工具使用指南,以 markdown 形式维护,读起来像文档。 */
|
|
30
|
+
const OUTLINE_PROMPT = readFileSync(
|
|
31
|
+
fileURLToPath(new URL("outline.md", import.meta.url)),
|
|
32
|
+
"utf8",
|
|
33
|
+
).trim();
|
|
34
|
+
const ZOOM_PROMPT = readFileSync(fileURLToPath(new URL("zoom.md", import.meta.url)), "utf8").trim();
|
|
35
|
+
const CALLGRAPH_PROMPT = readFileSync(
|
|
36
|
+
fileURLToPath(new URL("callgraph.md", import.meta.url)),
|
|
37
|
+
"utf8",
|
|
38
|
+
).trim();
|
|
39
|
+
const SEARCH_PROMPT = readFileSync(
|
|
40
|
+
fileURLToPath(new URL("search.md", import.meta.url)),
|
|
41
|
+
"utf8",
|
|
42
|
+
).trim();
|
|
43
|
+
|
|
26
44
|
/** 解析 `~` 前缀与相对路径(相对 session cwd)。URL 与绝对路径原样返回。 */
|
|
27
45
|
export function resolvePathArg(cwd: string, input: string): string {
|
|
28
46
|
if (input === "~" || input.startsWith("~/")) {
|
|
@@ -84,16 +102,18 @@ const OutlineParams = Type.Object(
|
|
|
84
102
|
{
|
|
85
103
|
target: Type.String({
|
|
86
104
|
description:
|
|
87
|
-
"要 outline
|
|
105
|
+
"要 outline 的对象:文件路径或目录路径。只接受单个 target;目录递归上限 200 个文件。",
|
|
88
106
|
}),
|
|
89
107
|
files: Type.Optional(
|
|
90
108
|
Type.Boolean({
|
|
91
109
|
description:
|
|
92
|
-
"
|
|
110
|
+
"为 true 时 target 必须是目录,返回带语言/符号数/字节大小的扁平文件树,而非符号大纲。默认:target 为目录时 true,为文件时 false。",
|
|
93
111
|
}),
|
|
94
112
|
),
|
|
95
113
|
includeTests: Type.Optional(
|
|
96
|
-
Type.Boolean({
|
|
114
|
+
Type.Boolean({
|
|
115
|
+
description: "目录符号大纲(files: false)模式:包含测试文件。默认 false。",
|
|
116
|
+
}),
|
|
97
117
|
),
|
|
98
118
|
},
|
|
99
119
|
{ additionalProperties: false },
|
|
@@ -104,25 +124,28 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
104
124
|
name: "aft_outline",
|
|
105
125
|
label: "aft_outline",
|
|
106
126
|
description: [
|
|
107
|
-
"
|
|
127
|
+
"输出代码文件、目录的结构化大纲:函数/类/类型等符号及其行号范围;Markdown/HTML 返回标题层级。",
|
|
108
128
|
"用它在读取具体内容之前先了解文件结构(比整文件 read 省 token)。",
|
|
109
129
|
"深入了解某个符号用 aft_zoom;看跨文件调用关系用 aft_callgraph。",
|
|
110
|
-
"target 支持:文件路径(带签名的符号大纲)、目录路径(递归最多 200
|
|
111
|
-
"files:
|
|
130
|
+
"target 支持:文件路径(带签名的符号大纲)、目录路径(递归最多 200 文件)。只接受单个 target。",
|
|
131
|
+
"target 为目录时默认返回扁平文件树(语言、顶层符号数、字节大小);传 files: false 可改回符号大纲。",
|
|
112
132
|
].join("\n"),
|
|
113
|
-
promptSnippet: "Output structural outline of a file/directory
|
|
133
|
+
promptSnippet: "Output structural outline of a file/directory",
|
|
134
|
+
promptGuidelines: [OUTLINE_PROMPT],
|
|
114
135
|
parameters: OutlineParams,
|
|
115
136
|
async execute(_id, params, _signal, _onUpdate, extCtx) {
|
|
116
137
|
const target = coerceTargetParam(params.target);
|
|
117
138
|
if (typeof target !== "string" || target.length === 0) {
|
|
118
|
-
throw new Error("'target' must be a single path
|
|
139
|
+
throw new Error("'target' must be a single path (array targets are not supported)");
|
|
140
|
+
}
|
|
141
|
+
const resolved = resolvePathArg(extCtx.cwd, target);
|
|
142
|
+
let filesMode = coerceBoolean(params.files);
|
|
143
|
+
if (params.files === undefined) {
|
|
144
|
+
const stats = await stat(resolved).catch(() => null);
|
|
145
|
+
filesMode = stats?.isDirectory() ?? false;
|
|
119
146
|
}
|
|
120
|
-
const filesMode = coerceBoolean(params.files);
|
|
121
147
|
const rawArgs: Record<string, unknown> = {
|
|
122
|
-
target:
|
|
123
|
-
filesMode || target.startsWith("http://") || target.startsWith("https://")
|
|
124
|
-
? target
|
|
125
|
-
: resolvePathArg(extCtx.cwd, target),
|
|
148
|
+
target: filesMode ? target : resolved,
|
|
126
149
|
};
|
|
127
150
|
if (filesMode) rawArgs.files = true;
|
|
128
151
|
if (params.includeTests !== undefined) rawArgs.includeTests = params.includeTests;
|
|
@@ -139,23 +162,12 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
139
162
|
});
|
|
140
163
|
}
|
|
141
164
|
|
|
142
|
-
const ZoomTarget = Type.Object({
|
|
143
|
-
path: Type.String({ description: "文件路径(绝对或相对项目根)" }),
|
|
144
|
-
symbol: Type.String({ description: "该文件中的符号名" }),
|
|
145
|
-
});
|
|
146
|
-
|
|
147
165
|
const ZoomParams = Type.Object(
|
|
148
166
|
{
|
|
149
|
-
path: Type.
|
|
150
|
-
url: Type.Optional(Type.String({ description: "要 zoom 的 HTML/Markdown 文档 URL" })),
|
|
167
|
+
path: Type.String({ description: "文件路径(绝对或相对项目根)" }),
|
|
151
168
|
symbols: Type.Optional(
|
|
152
169
|
Type.Union([Type.String(), Type.Array(Type.String())], {
|
|
153
|
-
description: "
|
|
154
|
-
}),
|
|
155
|
-
),
|
|
156
|
-
targets: Type.Optional(
|
|
157
|
-
Type.Union([ZoomTarget, Type.Array(ZoomTarget)], {
|
|
158
|
-
description: "跨文件批量:`{ path, symbol }` 或数组。与 path/url/symbols 互斥。",
|
|
170
|
+
description: "符号名(代码)或标题文本;字符串或数组(同文件批量查询)。",
|
|
159
171
|
}),
|
|
160
172
|
),
|
|
161
173
|
contextLines: Type.Optional(
|
|
@@ -180,51 +192,16 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
|
|
|
180
192
|
"查看命名符号(函数/类/类型)的完整源码,或 Markdown/HTML 的标题段落内容。",
|
|
181
193
|
"需要理解某个具体符号时用它(读整个文件用 read)。",
|
|
182
194
|
"callgraph: true 时附带同文件内的调用关系标注。",
|
|
183
|
-
"
|
|
195
|
+
"同文件多符号用 `symbols` 数组。",
|
|
184
196
|
].join("\n"),
|
|
185
197
|
promptSnippet: "Inspect the full source of a named symbol",
|
|
198
|
+
promptGuidelines: [ZOOM_PROMPT],
|
|
186
199
|
parameters: ZoomParams,
|
|
187
200
|
async execute(_id, params, _signal, _onUpdate, extCtx) {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const hasUrl = !isEmpty(params.url);
|
|
193
|
-
const hasSymbols = !isEmpty(params.symbols);
|
|
194
|
-
const hasTargets = !isEmpty(params.targets);
|
|
195
|
-
|
|
196
|
-
if (hasTargets && (hasPath || hasUrl || hasSymbols)) {
|
|
197
|
-
throw new Error("'targets' 与 'path'/'url'/'symbols' 互斥,只能提供一种模式");
|
|
198
|
-
}
|
|
199
|
-
if (hasPath && hasUrl) {
|
|
200
|
-
throw new Error("'path' 与 'url' 互斥,只能提供一种");
|
|
201
|
-
}
|
|
202
|
-
if (!hasTargets && !hasPath && !hasUrl) {
|
|
203
|
-
throw new Error("Provide exactly one of 'path', 'url', or 'targets'");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const rawArgs: Record<string, unknown> = {};
|
|
207
|
-
if (hasTargets) {
|
|
208
|
-
const targetList = params.targets;
|
|
209
|
-
if (!targetList) {
|
|
210
|
-
throw new Error("'targets' must be a non-empty object or array");
|
|
211
|
-
}
|
|
212
|
-
const list = Array.isArray(targetList) ? targetList : [targetList];
|
|
213
|
-
rawArgs.targets = list.map((t) => ({
|
|
214
|
-
filePath: resolvePathArg(extCtx.cwd, t.path),
|
|
215
|
-
symbol: t.symbol,
|
|
216
|
-
}));
|
|
217
|
-
} else if (hasUrl) {
|
|
218
|
-
rawArgs.url = params.url;
|
|
219
|
-
if (hasSymbols) rawArgs.symbols = params.symbols;
|
|
220
|
-
} else {
|
|
221
|
-
const filePath = params.path;
|
|
222
|
-
if (!filePath) {
|
|
223
|
-
throw new Error("'path' must be a non-empty string");
|
|
224
|
-
}
|
|
225
|
-
rawArgs.filePath = resolvePathArg(extCtx.cwd, filePath);
|
|
226
|
-
if (hasSymbols) rawArgs.symbols = params.symbols;
|
|
227
|
-
}
|
|
201
|
+
const rawArgs: Record<string, unknown> = {
|
|
202
|
+
filePath: resolvePathArg(extCtx.cwd, params.path),
|
|
203
|
+
...(params.symbols && { symbols: params.symbols }),
|
|
204
|
+
};
|
|
228
205
|
|
|
229
206
|
const contextLines = coerceOptionalInt(
|
|
230
207
|
params.contextLines,
|
|
@@ -260,35 +237,15 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
|
|
|
260
237
|
});
|
|
261
238
|
}
|
|
262
239
|
|
|
263
|
-
/** 构建 aft_zoom pendant 的 subtitle:`path="…" symbol="…"
|
|
240
|
+
/** 构建 aft_zoom pendant 的 subtitle:`path="…" symbol="…"`。 */
|
|
264
241
|
export function buildZoomSubtitle(
|
|
265
242
|
cwd: string,
|
|
266
243
|
params: Type.Static<typeof ZoomParams>,
|
|
267
244
|
): string | undefined {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const targets = params.targets;
|
|
273
|
-
if (Array.isArray(targets)) {
|
|
274
|
-
for (const t of targets) {
|
|
275
|
-
parts.push(
|
|
276
|
-
`path="${formatDisplayPath(cwd, resolvePathArg(cwd, t.path))}" symbol="${t.symbol}"`,
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
} else if (targets) {
|
|
280
|
-
parts.push(
|
|
281
|
-
`path="${formatDisplayPath(cwd, resolvePathArg(cwd, targets.path))}" symbol="${targets.symbol}"`,
|
|
282
|
-
);
|
|
283
|
-
} else if (!isEmpty(params.url)) {
|
|
284
|
-
parts.push(`url="${params.url}"`);
|
|
285
|
-
} else if (params.path) {
|
|
286
|
-
const symbols = params.symbols;
|
|
287
|
-
const symbolStr = Array.isArray(symbols) ? symbols.join(", ") : symbols;
|
|
288
|
-
const pathPart = `path="${formatDisplayPath(cwd, resolvePathArg(cwd, params.path))}"`;
|
|
289
|
-
parts.push(symbolStr ? `${pathPart} symbol="${symbolStr}"` : pathPart);
|
|
290
|
-
}
|
|
291
|
-
return parts.length > 0 ? parts.join(" ") : undefined;
|
|
245
|
+
const symbols = params.symbols;
|
|
246
|
+
const symbolStr = Array.isArray(symbols) ? symbols.join(", ") : symbols;
|
|
247
|
+
const pathPart = `path="${formatDisplayPath(cwd, resolvePathArg(cwd, params.path))}"`;
|
|
248
|
+
return symbolStr ? `${pathPart} symbol="${symbolStr}"` : pathPart;
|
|
292
249
|
}
|
|
293
250
|
|
|
294
251
|
const CALLGRAPH_OPS = [
|
|
@@ -345,6 +302,7 @@ export function registerCallgraphTool(pi: ExtensionAPI, ctx: AftToolContext): vo
|
|
|
345
302
|
"标记:~ = 仅按名字解析的边(可能指向同名符号);[unresolved] = 未解析到定义的调用点。",
|
|
346
303
|
].join("\n"),
|
|
347
304
|
promptSnippet: "Call graph and data-flow navigation",
|
|
305
|
+
promptGuidelines: [CALLGRAPH_PROMPT],
|
|
348
306
|
parameters: CallgraphParams,
|
|
349
307
|
async execute(_id, params, _signal, _onUpdate, extCtx) {
|
|
350
308
|
const rawArgs: Record<string, unknown> = {
|
|
@@ -437,6 +395,7 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
437
395
|
"启用语义索引时,首次调用会阻塞到索引构建完成,避免返回部分结果。",
|
|
438
396
|
].join("\n"),
|
|
439
397
|
promptSnippet: "Search code by meaning or exact text",
|
|
398
|
+
promptGuidelines: [SEARCH_PROMPT],
|
|
440
399
|
parameters: SearchParams,
|
|
441
400
|
async execute(_id, params, _signal, _onUpdate, extCtx) {
|
|
442
401
|
if (typeof params.query !== "string" || params.query.trim().length === 0) {
|
package/src/aft/zoom.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### aft_zoom tool
|
|
2
|
+
|
|
3
|
+
查看命名符号(函数/类/类型)的完整源码,或 Markdown/HTML 文档的标题段落内容。
|
|
4
|
+
|
|
5
|
+
- `path` 必填:文件路径(绝对或相对项目根)。
|
|
6
|
+
- `symbols`:要查看的符号名;字符串或数组(同文件多个符号一次查询)。代码文件按符号名精确解析;Markdown/HTML 按标题文本匹配(可含空格,用完整标题字符串)。
|
|
7
|
+
- `contextLines`:符号前后附加的上下文行数,默认 3。
|
|
8
|
+
- `callgraph: true`:附带同文件内 calls-out / called-by 调用关系标注,帮助理解符号如何被使用、依赖谁。
|
|
9
|
+
- 适合先 aft_outline 拿到符号名与行号,再 zoom 具体实现;不要用它读整个文件(用 read)。
|
|
10
|
+
|
|
11
|
+
只支持本地文件路径,不支持 URL。
|
package/src/claude-code/bash.md
CHANGED
package/src/claude-code/edit.md
CHANGED
package/src/claude-code/files.ts
CHANGED
|
@@ -228,7 +228,7 @@ export function registerFileTools(
|
|
|
228
228
|
"This tool reads files, not directories.",
|
|
229
229
|
].join("\n"),
|
|
230
230
|
promptSnippet: "Read files from the local filesystem with line numbers",
|
|
231
|
-
promptGuidelines: [
|
|
231
|
+
promptGuidelines: [READ_PROMPT],
|
|
232
232
|
parameters: Type.Object(
|
|
233
233
|
{
|
|
234
234
|
file_path: Type.String({ description: "The absolute path to the file to read" }),
|
|
@@ -331,7 +331,7 @@ export function registerFileTools(
|
|
|
331
331
|
"This tool does not use regular expressions or fuzzy matching.",
|
|
332
332
|
].join("\n"),
|
|
333
333
|
promptSnippet: "Make exact string replacements in files",
|
|
334
|
-
promptGuidelines: [
|
|
334
|
+
promptGuidelines: [EDIT_PROMPT],
|
|
335
335
|
parameters: Type.Object(
|
|
336
336
|
{
|
|
337
337
|
file_path: Type.String({ description: "The absolute path to the file to modify" }),
|
|
@@ -502,7 +502,7 @@ export function registerFileTools(
|
|
|
502
502
|
"If the file exists, you must use Read first. Prefer Edit for partial changes.",
|
|
503
503
|
].join("\n"),
|
|
504
504
|
promptSnippet: "Create or overwrite files",
|
|
505
|
-
promptGuidelines: [
|
|
505
|
+
promptGuidelines: [WRITE_PROMPT],
|
|
506
506
|
parameters: Type.Object(
|
|
507
507
|
{
|
|
508
508
|
file_path: Type.String({
|
package/src/claude-code/glob.md
CHANGED
package/src/claude-code/glob.ts
CHANGED
|
@@ -111,7 +111,7 @@ export function registerGlobTool(pi: ExtensionAPI): void {
|
|
|
111
111
|
"Returns matching file paths sorted by modification time (oldest first).",
|
|
112
112
|
].join("\n"),
|
|
113
113
|
promptSnippet: "Find files by name patterns",
|
|
114
|
-
promptGuidelines: [
|
|
114
|
+
promptGuidelines: [GLOB_PROMPT],
|
|
115
115
|
parameters: Type.Object(
|
|
116
116
|
{
|
|
117
117
|
pattern: Type.String({ description: "The glob pattern to match files against" }),
|
package/src/claude-code/grep.md
CHANGED
package/src/claude-code/grep.ts
CHANGED
|
@@ -241,7 +241,7 @@ export function registerGrepTool(pi: ExtensionAPI): void {
|
|
|
241
241
|
'output_mode defaults to "files_with_matches"; use "content" for matching lines or "count" for match counts.',
|
|
242
242
|
].join("\n"),
|
|
243
243
|
promptSnippet: "Search file contents with regular expressions",
|
|
244
|
-
promptGuidelines: [
|
|
244
|
+
promptGuidelines: [GREP_PROMPT],
|
|
245
245
|
parameters: grepParametersSchema,
|
|
246
246
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
247
247
|
if (
|
package/src/claude-code/read.md
CHANGED
|
@@ -177,7 +177,7 @@ export function registerSessionTools(pi: ExtensionAPI): void {
|
|
|
177
177
|
'If you recommend an option, put it first and append "(Recommended)" to its label.',
|
|
178
178
|
].join("\n"),
|
|
179
179
|
promptSnippet: "Ask the user questions during execution",
|
|
180
|
-
promptGuidelines: [
|
|
180
|
+
promptGuidelines: [ASK_PROMPT],
|
|
181
181
|
parameters: Type.Object(
|
|
182
182
|
{
|
|
183
183
|
questions: Type.Array(questionSchema, {
|
package/src/claude-code/shell.ts
CHANGED
|
@@ -79,7 +79,7 @@ export function registerShellTools(
|
|
|
79
79
|
pi.registerTool({
|
|
80
80
|
name: "Bash",
|
|
81
81
|
promptSnippet: "execute command",
|
|
82
|
-
promptGuidelines: [
|
|
82
|
+
promptGuidelines: [BASH_PROMPT],
|
|
83
83
|
label: "Bash",
|
|
84
84
|
description: [
|
|
85
85
|
"Executes a given bash command synchronously and returns its output.",
|
package/src/claude-code/write.md
CHANGED
package/src/spawn-agent.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The subagent definition comes from `~/.pi/agent/agents/*.md` (markdown with
|
|
6
6
|
* YAML frontmatter, see spawn-agent-agents.ts). The extension discovers the
|
|
7
|
-
* available subagent types once at startup and
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* available subagent types once at startup and injects the list via the tool's
|
|
8
|
+
* `promptGuidelines`, so the model always knows which `agent` names it can
|
|
9
|
+
* pass to the tool. Execution
|
|
10
10
|
* is blocking: the tool awaits the subagent process until it exits and
|
|
11
11
|
* returns its final output to the parent model. Progress is streamed through
|
|
12
12
|
* `onUpdate`, the same channel the built-in bash tool uses for live output.
|
|
@@ -622,7 +622,7 @@ function parseJsonRecord(line: string): Record<string, unknown> | null {
|
|
|
622
622
|
export function formatAgentListSection(agents: AgentConfig[]): string {
|
|
623
623
|
const lines = agents.map((a) => `- \`${a.name}\`: ${a.description}`);
|
|
624
624
|
return [
|
|
625
|
-
"
|
|
625
|
+
"### Available subagents",
|
|
626
626
|
"",
|
|
627
627
|
"You can delegate tasks to the following subagent types by calling the `spawn-agent` tool with their name in the `agent` parameter:",
|
|
628
628
|
"",
|
|
@@ -653,15 +653,6 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
653
653
|
);
|
|
654
654
|
const agentListSection = agents.length > 0 ? formatAgentListSection(agents) : null;
|
|
655
655
|
|
|
656
|
-
if (agentListSection) {
|
|
657
|
-
// Same pattern as the bwrap extension: append the list to the system
|
|
658
|
-
// prompt on every agent start. The system prompt is rebuilt each turn
|
|
659
|
-
// anyway, so a persistent per-session injection would add no value.
|
|
660
|
-
pi.on("before_agent_start", (event) => {
|
|
661
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${agentListSection}` };
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
|
|
665
656
|
pi.registerTool<typeof spawnAgentSchema, SubagentDetails>({
|
|
666
657
|
name: "spawn-agent",
|
|
667
658
|
label: "spawn-agent",
|
|
@@ -670,6 +661,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
670
661
|
"The `agent` parameter must be one of the available subagent types listed in the system prompt.",
|
|
671
662
|
`Subagents run read-only (${DEFAULT_TOOLS.join(", ")}) unless the agent declares an explicit toolset.`,
|
|
672
663
|
].join(" "),
|
|
664
|
+
promptGuidelines: agentListSection ? [agentListSection] : undefined,
|
|
673
665
|
parameters: spawnAgentSchema,
|
|
674
666
|
|
|
675
667
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -71,11 +71,11 @@ export function formatTools(
|
|
|
71
71
|
return lines.length > 0 ? lines.join("\n") : "(none)";
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/** 渲染工具特定 guideline
|
|
74
|
+
/** 渲染工具特定 guideline 块;为空时整个块(含标题)省略。guide 本身是 md 文档,直接拼接。 */
|
|
75
75
|
export function formatGuidelines(promptGuidelines: string[] | undefined): string {
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
return `## Guidelines\n\n${
|
|
76
|
+
const items = (promptGuidelines ?? []).map((g) => g.trim()).filter((g) => g.length > 0);
|
|
77
|
+
if (items.length === 0) return "";
|
|
78
|
+
return `## Guidelines\n\n${items.join("\n\n")}`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/** 渲染 AGENTS.md 等上下文文件;为空时省略 */
|