@trim21/personal-pi-extensions 0.1.448 → 0.1.451
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/README.md +13 -2
- package/package.json +1 -1
- package/src/aft/bridge.ts +53 -12
- package/src/aft/callgraph.md +3 -1
- package/src/aft/config.ts +99 -26
- package/src/aft/index.ts +16 -5
- package/src/aft/outline.md +1 -1
- package/src/aft/refactor.md +46 -2
- package/src/aft/refactor.ts +3 -4
- package/src/aft/search.md +1 -1
- package/src/aft/tools.ts +1 -3
- package/src/claude-code/files.ts +3 -3
- package/src/lib/lsp/client.ts +125 -22
- package/src/lib/lsp/lsp.ts +165 -2
- package/src/lib/lsp/server-config.ts +3 -5
- package/src/lib/lsp/watcher.ts +174 -0
- package/src/opencode/files.ts +3 -3
- package/src/aft/ast-edit.ts +0 -310
package/README.md
CHANGED
|
@@ -372,6 +372,13 @@ read/edit/write 工具内置 LSP 诊断(写文件后等待并报告 ERROR 级
|
|
|
372
372
|
"settings": {}, // → didChangeConfiguration / workspace/configuration 请求
|
|
373
373
|
},
|
|
374
374
|
},
|
|
375
|
+
"maxOpenDocuments": 32, // 驻留文档上限(LRU 容量),缺省 32
|
|
376
|
+
"watch": {
|
|
377
|
+
"enabled": true, // 工作区文件监听,缺省 true
|
|
378
|
+
"debounceMs": "300ms", // 事件去抖,缺省 300ms;也支持 "5s" / "1m"
|
|
379
|
+
"maxBatch": 500, // 单批事件上限,缺省 500,超出截断并提示一次
|
|
380
|
+
"ignore": [], // 追加忽略 glob(相对工作区根)
|
|
381
|
+
},
|
|
375
382
|
}
|
|
376
383
|
```
|
|
377
384
|
|
|
@@ -384,9 +391,13 @@ read/edit/write 工具内置 LSP 诊断(写文件后等待并报告 ERROR 级
|
|
|
384
391
|
- `startupTimeoutMs` / `diagnosticsWaitMs`:per-server 超时,覆盖全局配置与默认值
|
|
385
392
|
- `initializationOptions` 与 `settings` 按 LSP 语义分离:前者进 initialize 请求,后者进 didChangeConfiguration / workspace/configuration 请求
|
|
386
393
|
|
|
387
|
-
|
|
394
|
+
没有内置默认服务器:`servers` 的 key 就是服务器 id,全部来自你的配置,未定义 `servers` 时不启动任何语言服务器。executable 的发现逻辑(如 tsserver 路径、venv 里的 python)不内置,需要时用 `bin` / `args` / `settings` 自行表达。
|
|
395
|
+
|
|
396
|
+
启用控制只有顶层两处:`enabled`(白名单)与 `disabled`(排除),按服务器 id 生效。`enabled` 里的 id 必须是已配置服务器,否则视为配置错误;`disabled` 里未注册的 id 直接忽略。全局超时字段(`initializeTimeoutMs` 等)同样配在顶层。
|
|
397
|
+
|
|
398
|
+
顶层 `watch` 段控制工作区文件监听(事件源是会话 cwd 的递归 fs.watch,非本 agent 写入的改动——如 `git checkout`、外部格式化——也会以 `workspace/didChangeWatchedFiles` 批量通知服务器);内置忽略 `node_modules`、`.git`、`dist`、`build`、`.venv`、`venv`、`target`、`coverage`,`ignore` 可追加。`maxOpenDocuments` 是保持 open 的文档上限(LRU):超过时最久未使用的文档会被 `didClose`,服务器回落到读磁盘。`watch.enabled: false` 可整体关闭监听,回到仅工具触发同步的现状。
|
|
388
399
|
|
|
389
|
-
|
|
400
|
+
服务器记录里不认识的键会被忽略:历史配置中残留的 `"clangd": { "enabled": false }` 已无任何效果,要禁用某个已配置的服务器请改用顶层 `disabled`。
|
|
390
401
|
|
|
391
402
|
---
|
|
392
403
|
|
package/package.json
CHANGED
package/src/aft/bridge.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type BridgeRequestOptions,
|
|
14
14
|
createAftTransportPool,
|
|
15
15
|
findBinary,
|
|
16
|
+
inlineUserConfigTier,
|
|
16
17
|
readConfigTiers,
|
|
17
18
|
resolveCortexKitConfigPaths,
|
|
18
19
|
resolveCortexKitStorageRoot,
|
|
@@ -20,6 +21,8 @@ import {
|
|
|
20
21
|
} from "@cortexkit/aft-bridge";
|
|
21
22
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
22
23
|
|
|
24
|
+
import type { SemanticRemote } from "./config.js";
|
|
25
|
+
|
|
23
26
|
/** Pi 会话 ID:Rust 侧用它做 session 作用域(undo/checkpoint),感知工具可留空。 */
|
|
24
27
|
export function resolveSessionId(extCtx: ExtensionContext): string | undefined {
|
|
25
28
|
const manager = (extCtx as unknown as { sessionManager?: { getSessionId?: () => string } })
|
|
@@ -53,26 +56,64 @@ export interface AftPool {
|
|
|
53
56
|
/** semantic_search 等待索引构建完成的上限(与 Rust 侧 AFT_WAIT_FOR_SEMANTIC_READY_MS 一致)。 */
|
|
54
57
|
export const SEMANTIC_INDEX_WAIT_TIMEOUT_MS = 600_000;
|
|
55
58
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
59
|
+
/**
|
|
60
|
+
* 调用图存储未就绪时查询侧的内联等待窗口。Rust 侧默认 0(纯异步:立即返回
|
|
61
|
+
* callgraph_building),非零时查询会在窗口内加入同一个 single-flight 构建,
|
|
62
|
+
* 就绪后直接返回真实结果。窗口必须明显小于 aft-bridge 给 callgraph 的 60s
|
|
63
|
+
* transport 预算,否则客户端先超时并触发 bridge hang 升级。
|
|
64
|
+
*/
|
|
65
|
+
export const CALLGRAPH_BUILD_WAIT_MS = 30_000;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 本扩展替用户注入 embedding 密钥时使用的固定变量名。仅当用户只配了
|
|
69
|
+
* `semantic.api_key`(值)而没配 `semantic.api_key_env`(变量名)时生效。
|
|
70
|
+
*/
|
|
71
|
+
export const SEMANTIC_API_KEY_ENV = "AFT_SEMANTIC_API_KEY";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 创建 transport pool。每个项目根一个常驻 aft 进程,跨 session 共享。
|
|
75
|
+
*
|
|
76
|
+
* `semantic` 决定 embedding 密钥如何送达子进程:aft 只从配置里读 `api_key_env`
|
|
77
|
+
* 这个「变量名」,再自己 `env::var` 取值,所以提供值时必须连带把名字告诉它。
|
|
78
|
+
* 两者都缺省时不注入任何凭据(无鉴权端点直接可用)。
|
|
79
|
+
*/
|
|
80
|
+
export async function createAftPool(cwd: string, semantic?: SemanticRemote): Promise<AftPool> {
|
|
58
81
|
const binaryPath = await resolveAftBinary();
|
|
59
82
|
const paths = resolveCortexKitConfigPaths(cwd);
|
|
83
|
+
const childEnv: Record<string, string> = {
|
|
84
|
+
// Rust 侧 semantic_search 在索引 Building 时阻塞等待构建完成
|
|
85
|
+
// (main.rs wait_for_semantic_index_before_search),避免首次搜索拿到
|
|
86
|
+
// 词法 fallback 的部分结果。语义搜索未启用时该逻辑直接跳过。
|
|
87
|
+
AFT_WAIT_FOR_SEMANTIC_READY: "1",
|
|
88
|
+
AFT_WAIT_FOR_SEMANTIC_READY_MS: String(SEMANTIC_INDEX_WAIT_TIMEOUT_MS),
|
|
89
|
+
// 调用图冷构建与 watcher 重建时让查询内联等待就绪,而不是把
|
|
90
|
+
// callgraph_building 直接吐给模型(见 CALLGRAPH_BUILD_WAIT_MS 注释)。
|
|
91
|
+
AFT_CALLGRAPH_BUILD_WAIT_MS: String(CALLGRAPH_BUILD_WAIT_MS),
|
|
92
|
+
};
|
|
93
|
+
const config = [...readConfigTiers(paths)];
|
|
94
|
+
if (semantic?.apiKey !== undefined) {
|
|
95
|
+
// childEnv 叠加在继承的 process.env 之上,所以配置里的值优先于 shell 同名变量。
|
|
96
|
+
// 密钥只进子进程环境,不进日志与工具输出。
|
|
97
|
+
childEnv[semantic.apiKeyEnv ?? SEMANTIC_API_KEY_ENV] = semantic.apiKey;
|
|
98
|
+
if (semantic.apiKeyEnv === undefined) {
|
|
99
|
+
// 用户没指定变量名:追加一份 user tier(后应用的 doc 覆盖先应用的)让 aft
|
|
100
|
+
// 知道该读 SEMANTIC_API_KEY_ENV,否则它不知道去哪个变量取值。
|
|
101
|
+
config.push(
|
|
102
|
+
...inlineUserConfigTier(
|
|
103
|
+
{ semantic: { api_key_env: SEMANTIC_API_KEY_ENV } },
|
|
104
|
+
"pi:semantic-api-key",
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
60
109
|
const pool = await createAftTransportPool({
|
|
61
110
|
harness: "pi",
|
|
62
111
|
binaryPath,
|
|
63
|
-
poolOptions: {
|
|
64
|
-
childEnv: {
|
|
65
|
-
// Rust 侧 semantic_search 在索引 Building 时阻塞等待构建完成
|
|
66
|
-
// (main.rs wait_for_semantic_index_before_search),避免首次搜索拿到
|
|
67
|
-
// 词法 fallback 的部分结果。语义搜索未启用时该逻辑直接跳过。
|
|
68
|
-
AFT_WAIT_FOR_SEMANTIC_READY: "1",
|
|
69
|
-
AFT_WAIT_FOR_SEMANTIC_READY_MS: String(SEMANTIC_INDEX_WAIT_TIMEOUT_MS),
|
|
70
|
-
},
|
|
71
|
-
},
|
|
112
|
+
poolOptions: { childEnv },
|
|
72
113
|
configOverrides: {
|
|
73
114
|
storage_dir: resolveCortexKitStorageRoot(),
|
|
74
115
|
cortexkit_user_config_path: paths.userConfigPath,
|
|
75
|
-
config
|
|
116
|
+
config,
|
|
76
117
|
},
|
|
77
118
|
});
|
|
78
119
|
pool.setConfigureOverride("harness", "pi");
|
package/src/aft/callgraph.md
CHANGED
package/src/aft/config.ts
CHANGED
|
@@ -1,46 +1,119 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* aft.jsonc 最小读取:只关心 JS
|
|
3
|
-
*
|
|
2
|
+
* aft.jsonc 最小读取:只关心 JS 侧注册决策需要的字段(enabled / semantic_search /
|
|
3
|
+
* semantic.backend / semantic.base_url),其余字段由 Rust 侧从配置 tier 自行读取。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 只读用户级配置:语义搜索涉及外部 embedding 端点与密钥,按 AFT 的信任边界只允许
|
|
6
|
+
* 用户级控制(项目级配置不应替用户开启语义搜索或注入 base_url)。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 本仓库只接受外部 embedding 后端(openai_compatible / ollama):aft 默认后端
|
|
9
|
+
* fastembed 走本地 ONNX Runtime,内网镜像不提供该运行时,所以即便用户开了
|
|
10
|
+
* semantic_search,外部后端未就绪时也不注册 aft_search。
|
|
11
|
+
*
|
|
12
|
+
* JSONC 解析用微软 jsonc-parser(vscode 同源,处理注释与尾逗号),不手写解析器;
|
|
13
|
+
* 分两层:rawConfigSchema 只描述依赖的外部形状并做基础类型校验,toReadConfig 负责
|
|
14
|
+
* 补默认值与后端语义判定。
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
18
|
|
|
14
19
|
import { parse } from "jsonc-parser";
|
|
20
|
+
import { type Static, Type } from "typebox";
|
|
21
|
+
|
|
22
|
+
import { parseWithSchema } from "../lib/parse-with-schema.js";
|
|
23
|
+
|
|
24
|
+
/** aft 侧走 HTTP embeddings 端点的后端;fastembed(本地 ONNX)有意排除在外。 */
|
|
25
|
+
const REMOTE_SEMANTIC_BACKENDS = ["openai_compatible", "ollama"] as const;
|
|
26
|
+
|
|
27
|
+
export type RemoteSemanticBackend = (typeof REMOTE_SEMANTIC_BACKENDS)[number];
|
|
28
|
+
|
|
29
|
+
/** 就绪的外部 embedding 后端:类型受支持且配了 base_url。 */
|
|
30
|
+
export interface SemanticRemote {
|
|
31
|
+
backend: RemoteSemanticBackend;
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
/**
|
|
34
|
+
* 用户自己指定的密钥环境变量名(可选覆盖)。配了就直接用该名,值由用户的
|
|
35
|
+
* shell 提供,或由 `apiKey` 注入到同名变量。
|
|
36
|
+
*/
|
|
37
|
+
apiKeyEnv: string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* 密钥值(可选)。aft 只认环境变量名,所以未配 `apiKeyEnv` 时由本扩展注入成
|
|
40
|
+
* 固定的内部变量名并告诉 aft 去读它。undefined = 无鉴权端点(自建网关、ollama
|
|
41
|
+
* 都可以不带 key)。
|
|
42
|
+
*/
|
|
43
|
+
apiKey: string | undefined;
|
|
44
|
+
}
|
|
15
45
|
|
|
16
46
|
export interface AftReadConfig {
|
|
17
47
|
enabled: boolean;
|
|
18
48
|
semanticSearch: boolean;
|
|
49
|
+
/** undefined = 外部后端未就绪(未配 backend、非远程后端、或缺 base_url)。 */
|
|
50
|
+
semanticRemote: SemanticRemote | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const DEFAULTS: AftReadConfig = {
|
|
54
|
+
enabled: true,
|
|
55
|
+
semanticSearch: false,
|
|
56
|
+
semanticRemote: undefined,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** 上游(aft)控制的形状:只声明本仓库真正读取的字段,未声明的键由解析层忽略。 */
|
|
60
|
+
const rawConfigSchema = Type.Object({
|
|
61
|
+
enabled: Type.Optional(Type.Boolean()),
|
|
62
|
+
semantic_search: Type.Optional(Type.Boolean()),
|
|
63
|
+
semantic: Type.Optional(
|
|
64
|
+
Type.Object({
|
|
65
|
+
backend: Type.Optional(Type.String()),
|
|
66
|
+
base_url: Type.Optional(Type.String()),
|
|
67
|
+
api_key_env: Type.Optional(Type.String()),
|
|
68
|
+
// 本扩展独有:aft 只读环境变量,密钥值由我们注入成 api_key_env 命名的变量。
|
|
69
|
+
api_key: Type.Optional(Type.String()),
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
type RawConfig = Static<typeof rawConfigSchema>;
|
|
75
|
+
|
|
76
|
+
function isRemoteBackend(value: string): value is RemoteSemanticBackend {
|
|
77
|
+
return (REMOTE_SEMANTIC_BACKENDS as readonly string[]).includes(value);
|
|
19
78
|
}
|
|
20
79
|
|
|
21
|
-
|
|
80
|
+
function trimmedOrUndefined(value: string | undefined): string | undefined {
|
|
81
|
+
const trimmed = value?.trim();
|
|
82
|
+
return trimmed === undefined || trimmed === "" ? undefined : trimmed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function remoteOf(semantic: RawConfig["semantic"]): SemanticRemote | undefined {
|
|
86
|
+
const backend = trimmedOrUndefined(semantic?.backend);
|
|
87
|
+
// 去掉尾斜杠:aft 侧按 base_url + /embeddings 拼请求。
|
|
88
|
+
const baseUrl = trimmedOrUndefined(semantic?.base_url)?.replace(/\/+$/, "");
|
|
89
|
+
if (backend === undefined || baseUrl === undefined) return undefined;
|
|
90
|
+
if (!isRemoteBackend(backend)) return undefined;
|
|
91
|
+
return {
|
|
92
|
+
backend,
|
|
93
|
+
baseUrl,
|
|
94
|
+
apiKeyEnv: trimmedOrUndefined(semantic?.api_key_env),
|
|
95
|
+
apiKey: trimmedOrUndefined(semantic?.api_key),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
22
98
|
|
|
23
|
-
function
|
|
99
|
+
function toReadConfig(raw: RawConfig): AftReadConfig {
|
|
100
|
+
return {
|
|
101
|
+
enabled: raw.enabled ?? DEFAULTS.enabled,
|
|
102
|
+
semanticSearch: raw.semantic_search ?? DEFAULTS.semanticSearch,
|
|
103
|
+
semanticRemote: remoteOf(raw.semantic),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 读用户级 aft.jsonc。文件缺失、非法 JSONC 或形状不符(含字段类型错误)时整体回退
|
|
109
|
+
* 默认值:注册决策宁可保守,不该因配置写错而让扩展加载失败。
|
|
110
|
+
*/
|
|
111
|
+
export function loadAftConfig(userConfigPath: string): AftReadConfig {
|
|
24
112
|
try {
|
|
25
|
-
if (!existsSync(
|
|
26
|
-
const parsed: unknown = parse(readFileSync(
|
|
27
|
-
|
|
28
|
-
return DEFAULTS;
|
|
29
|
-
}
|
|
30
|
-
const value = parsed as Record<string, unknown>;
|
|
31
|
-
return {
|
|
32
|
-
enabled: typeof value.enabled === "boolean" ? value.enabled : DEFAULTS.enabled,
|
|
33
|
-
semanticSearch:
|
|
34
|
-
typeof value.semantic_search === "boolean"
|
|
35
|
-
? value.semantic_search
|
|
36
|
-
: DEFAULTS.semanticSearch,
|
|
37
|
-
};
|
|
113
|
+
if (!existsSync(userConfigPath)) return DEFAULTS;
|
|
114
|
+
const parsed: unknown = parse(readFileSync(userConfigPath, "utf8"));
|
|
115
|
+
return toReadConfig(parseWithSchema(rawConfigSchema, parsed ?? {}));
|
|
38
116
|
} catch {
|
|
39
117
|
return DEFAULTS;
|
|
40
118
|
}
|
|
41
119
|
}
|
|
42
|
-
|
|
43
|
-
/** 读用户级 aft.jsonc。解析失败或文件缺失时回退默认值(不阻塞扩展加载)。 */
|
|
44
|
-
export function loadAftConfig(userConfigPath: string): AftReadConfig {
|
|
45
|
-
return readOne(userConfigPath);
|
|
46
|
-
}
|
package/src/aft/index.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* + aft_refactor / aft_import(workspace-wide 重构与 import 管理,路径级写保护)。
|
|
4
4
|
*
|
|
5
5
|
* 感知工具只读,不触碰本仓库自己的 read/write/edit/bash 工具及其安全机制
|
|
6
|
-
* (bwrap 沙箱、write-guard、reads 记账)。aft_search 仅当用户级
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* (bwrap 沙箱、write-guard、reads 记账)。aft_search 仅当用户级 aft.jsonc 开启
|
|
7
|
+
* semantic_search 且配好外部 embedding 后端(semantic.backend 为
|
|
8
|
+
* openai_compatible / ollama 且有 base_url)时注册;aft 默认的本地 ONNX
|
|
9
|
+
* fastembed 后端不使用。aft_refactor / aft_import 的 Rust 命令不支持 preview,
|
|
9
10
|
* 写保护退化为路径级审批。
|
|
10
11
|
*
|
|
11
12
|
* 二进制缺失或 pool 创建失败时降级:不注册任何工具并在 session 开始时报
|
|
@@ -36,7 +37,7 @@ export default async function aftReadTools(pi: ExtensionAPI): Promise<void> {
|
|
|
36
37
|
|
|
37
38
|
let pool: AftPool;
|
|
38
39
|
try {
|
|
39
|
-
pool = await createAftPool(cwd);
|
|
40
|
+
pool = await createAftPool(cwd, cfg.semanticRemote);
|
|
40
41
|
} catch (error) {
|
|
41
42
|
const message = error instanceof Error ? error.message : String(error);
|
|
42
43
|
pi.on("session_start", (_event, ctx) => {
|
|
@@ -55,7 +56,17 @@ export default async function aftReadTools(pi: ExtensionAPI): Promise<void> {
|
|
|
55
56
|
registerRefactorTool(pi, toolCtx);
|
|
56
57
|
registerImportTool(pi, toolCtx);
|
|
57
58
|
if (cfg.semanticSearch) {
|
|
58
|
-
|
|
59
|
+
if (cfg.semanticRemote) {
|
|
60
|
+
registerSearchTool(pi, toolCtx);
|
|
61
|
+
} else {
|
|
62
|
+
// 只开了开关、没配外部 embedding 后端:与其静默不注册,不如说明缺什么。
|
|
63
|
+
pi.on("session_start", (_event, ctx) => {
|
|
64
|
+
ctx.ui.notify(
|
|
65
|
+
"aft_search is not registered: semantic_search needs an external embedding backend (aft.jsonc semantic.backend = openai_compatible | ollama, plus base_url). The local ONNX fastembed default is not used here.",
|
|
66
|
+
"warning",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
// 关闭 bridge pool。session_shutdown 是 pi 的正常生命周期;beforeExit 兜底
|
package/src/aft/outline.md
CHANGED
package/src/aft/refactor.md
CHANGED
|
@@ -2,8 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
workspace-wide 重构:跨文件移动符号、抽函数、内联,自动更新引用与 import。
|
|
4
4
|
|
|
5
|
-
- `move`:把顶层符号(非嵌套函数/类方法)移到另一个文件,全 workspace 重写 import
|
|
5
|
+
- `move`:把顶层符号(非嵌套函数/类方法)移到另一个文件,全 workspace 重写 import 与引用。需要 `symbol` + `destination`;同名符号歧义时用 `scope` 消歧。
|
|
6
6
|
- `extract`:把行区间抽成新函数(TS/JS/TSX、Python)。需要 `name` + `start_line` + `end_line`(1 起,含端点)。
|
|
7
7
|
- `inline`:把调用点替换为函数体。需要 `symbol` + `call_site_line`(1 起)。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
重构前先看影响面:对目标符号跑 aft_callgraph `impact`,确认波及范围符合预期再动手。
|
|
10
|
+
|
|
11
|
+
#### 示例
|
|
12
|
+
|
|
13
|
+
`path` 相对项目根,行号 1 起且含端点。
|
|
14
|
+
|
|
15
|
+
跨文件移动顶层符号,自动重写全部 import 与引用:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"op": "move",
|
|
20
|
+
"path": "src/aft/tools.ts",
|
|
21
|
+
"symbol": "buildPendantMarkdown",
|
|
22
|
+
"destination": "src/lib/pendant.ts"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
把行区间抽成新函数(先用 aft_outline 或 Read 定位区间):
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"op": "extract",
|
|
31
|
+
"path": "src/aft/refactor.ts",
|
|
32
|
+
"name": "buildRefactorArgs",
|
|
33
|
+
"start_line": 121,
|
|
34
|
+
"end_line": 134
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`start_line` / `end_line` 指 `path` 文件里被抽取的代码,不是新函数所在的行。
|
|
39
|
+
|
|
40
|
+
内联单个调用点:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"op": "inline",
|
|
45
|
+
"path": "src/aft/refactor.ts",
|
|
46
|
+
"symbol": "requireField",
|
|
47
|
+
"call_site_line": 98
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`call_site_line` 是调用发生的那一行,不是函数定义行;只替换指定那一处,内联掉全部调用点后原定义不会被自动删除,需要自己清理。
|
|
52
|
+
|
|
53
|
+
只支持顶层符号;嵌套函数与类方法不适用。
|
package/src/aft/refactor.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 参数经 bridge.toolCall 以 agent 工具名 "refactor" 分派,Rust 侧 subc 翻译层
|
|
5
5
|
* 按 op 转成内部命令(move_symbol / extract_function / inline_symbol)。
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 这些命令不支持 preview,写保护退化为路径级审批(workspace 内自动放行,
|
|
7
|
+
* 外部路径经 write-guard 确认,无 diff 预览)。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
@@ -71,10 +71,9 @@ export function registerRefactorTool(pi: ExtensionAPI, ctx: AftToolContext): voi
|
|
|
71
71
|
label: "aft_refactor",
|
|
72
72
|
description: [
|
|
73
73
|
"workspace-wide 重构:更新跨文件的 import 与引用。",
|
|
74
|
-
"move:把顶层符号(非嵌套函数/类方法)移到另一文件,全 workspace 重写 import
|
|
74
|
+
"move:把顶层符号(非嵌套函数/类方法)移到另一文件,全 workspace 重写 import。",
|
|
75
75
|
"extract:把行区间抽成新函数(TS/JS/TSX、Python)。",
|
|
76
76
|
"inline:把调用点替换为函数体。",
|
|
77
|
-
"move / rename 整个文件用 aft_move(OS 层操作,不更新引用);移动代码符号用本工具 op=move。",
|
|
78
77
|
].join("\n"),
|
|
79
78
|
promptSnippet: "Workspace-wide symbol move / function extraction / inlining",
|
|
80
79
|
promptGuidelines: [REFACTOR_PROMPT],
|
package/src/aft/search.md
CHANGED
package/src/aft/tools.ts
CHANGED
|
@@ -389,9 +389,7 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
|
|
|
389
389
|
"一个工具完成代码搜索:概念、标识符、错误串、正则、字面量、文件名自动路由到合适的引擎并按相关度排序。",
|
|
390
390
|
"概念类查询('ORM 如何构建并执行查询')用自然语言整句——语义通道理解意图并匹配 docstring 和注释;",
|
|
391
391
|
"精确名字、字符串、正则保持简短('^export'、'Cargo.lock')。",
|
|
392
|
-
"
|
|
393
|
-
"否则退化为词法/正则匹配通道。",
|
|
394
|
-
"启用语义索引时,首次调用会阻塞到索引构建完成,避免返回部分结果。",
|
|
392
|
+
"索引首次构建时本调用会阻塞到构建完成,避免返回部分结果。",
|
|
395
393
|
].join("\n"),
|
|
396
394
|
promptSnippet: "Search code by meaning or exact text",
|
|
397
395
|
promptGuidelines: [SEARCH_PROMPT],
|
package/src/claude-code/files.ts
CHANGED
|
@@ -349,9 +349,9 @@ export function registerFileTools(
|
|
|
349
349
|
}
|
|
350
350
|
const snapshot = { ...snapshotOf(buffer), offset, limit };
|
|
351
351
|
state.reads.set(key, snapshot);
|
|
352
|
-
// LSP
|
|
353
|
-
void service.
|
|
354
|
-
//
|
|
352
|
+
// LSP 文件事件通知是后台任务,失败不影响读取(read 不驻留文档)
|
|
353
|
+
void service.notifyFile(filePath, ctx.cwd).catch(() => {
|
|
354
|
+
// 后台通知失败不影响读取
|
|
355
355
|
});
|
|
356
356
|
return {
|
|
357
357
|
content: [{ type: "text", text: formatted.text }],
|