第十节 智能编程工具

下列工具默认在工作区目录范围内可用,互相协同支撑助手在你的代码仓库里"干活"。

10.1 读文件

支持按行或按字节读取,自动处理大文件分段。读取时会附带内容指纹,确保后续编辑基于"刚刚读过"的版本,防止并发覆写。

非 UTF-8 文本也能读对:读取时会先按 BOM 判断编码,没有 BOM 时对 GBK / GB18030 / UTF-16 等常见中文与宽字符编码做识别,解码成正常文本后再交给 AI —— 以前这类文件要么被当成二进制拒读、要么读出一堆乱码。搜索(fs.grep)走同一套解码,中文注释、Windows 下导出的日志都能正常命中。

二进制文件有守卫:确属二进制的文件不会被当文本硬读;确实需要按文本看待时,可以显式要求"按文本读"。

10.2 写文件 / 编辑

  • 覆盖写:完整重写整个文件,会校验"读过"前提。
  • 片段编辑:基于内容指纹做精确替换,AI 必须先读、再改,杜绝幻觉式覆盖。

10.3 目录列表 / 文件查找

  • 列目录支持过滤、按时间排序。
  • 支持通配符匹配文件名,自动跳过 .git / node_modules 等无关目录。

10.4 内容搜索

  • 支持正则表达式、文件名通配过滤。
  • 自动跳过二进制文件,启发式判断编码。

10.5 终端执行

  • 同步执行:单次命令运行后等待完成,输出回到对话流。
  • 后台执行:长任务(构建、压测)转后台,期间可拉取实时输出、随时停止。

每一次终端调用默认都受工具权限管控:可放行、可询问、可禁用。

10.5.1 常规模式下也能用 shell 语法

以前只有无限制模式才能写管道、串联,常规模式下 a && b、a | b 一律被拒,AI 只好拆成好几次调用。现在常规模式下同样支持:

语法 说明
串联 && / || / ; 每一段各自过白名单,任何一段不在白名单都整条拒绝
管道 | 同上,逐段校验
安全重定向 > / >> 目标必须落在工作区(或已附加的可写路径)内
cd 允许切目录,但仍圈禁在工作区内
  • samples/(样本沙箱)下的文件只拦"执行",读取、被当作参数传给别的命令不再一律拒绝。
  • test.run / lint.run / sast.run 里给出的命令走同一套护栏(此前这三个入口的覆盖有缺口),命令分词也统一到同一个解析器,不同入口不会再有不同判定。
  • 命令回显无歧义:结果里回显的命令按原样保留引号,不再"用空格重新拼一遍"导致含空格的参数看起来像两个。
  • Windows 上的批处理(.bat / .cmd)参数有专门的注入护栏。

10.5.2 编译器与路径

  • cc 家族编译器(cc / gcc / clang 等)已纳入白名单,写出通道(-o / -MF 等)圈禁在工作区内,危险旗标前缀一律拦截。
  • 工作区内的绝对路径不再一刀切拒绝:只要解析后仍落在工作区(或已附加的路径)内,绝对路径写法与相对写法走同一套判定,AI 不必为了过检而反复改写路径。
  • 命令解析不会被同名文件劫持:工作区下如果恰好有个叫 git 的文件,也不会被当成要执行的程序。
  • Windows 输出编码统一:Windows 上命令输出的 GBK / UTF-16 会在汇聚点统一转成 UTF-8,前台与后台(fs.exec.start 的输出拉取)都不再出现乱码。
  • 白名单命令名归一化:Windows 上命令名大小写、带不带 .exe 不再影响白名单判定(此前会出现"时好时坏");被策略拒绝时错误信息里会带上具体是哪条命令、哪条规则。

10.6 版本控制

提供基础 Git 操作:仓库状态查询、新建 / 切换分支、新建仓库,以及暂存 / 提交 / 合并 / 变基等分支写操作;推送仍由助手通过终端执行类工具完成。这些操作默认与其他工具一致为「启用」(不额外弹询问);对合并、推送等高风险操作,可在智能体管理器 →「配置工具」里按工作模式把对应工具设为「询问」或「禁用」(见 §13.1)。

10.7 网络检索(可选)

  • web.bing:Bing RSS 端点搜索,返回标题 / 摘要 / URL。
  • web.fetch:抓取指定 URL 并自动清洗为 Markdown / 文本 / HTML。

检索来源与内容均会作为引用卡片展示在消息流。

10.8 时间与上下文

  • time.now:获取当前时间(含时区),方便助手为日志、TODO、文件名打标。
  • TodoWrite:写入 / 更新待办列表,单次最多 50 条;详见第十六节。

10.9 上下文截断

任何读取或检索类工具的单次返回都有"软上限 + 硬上限",超出会自动截断并标注,方便助手分批续读,避免一次性灌爆模型上下文。

工具结果瘦身,更省额度:工作区工具的返回结果做了精简 —— 文件路径改为相对工作区根显示、错误信息剥除冗长的根路径前缀、去除重复的行哈希、分页记账仅在结果被截断时才输出。整体减少无谓的 token 占用,长任务更省额度(也不再把绝对根路径泄露到结果里)。

10.10 代码智能工具组(code.*,基于语言服务器)

新增一组让 AI 像 IDE 一样理解代码的工具:基于业界标准的 LSP(Language Server Protocol),把语言服务器的"找定义 / 找引用 / 重命名 / 编译诊断"等能力直接交给 AI 使用 — 做事更准、更省来回、不再靠正则猜。

工具 用途
code.definition 跳转到定义:找到某符号在哪个文件第几行真正定义
code.references 查找引用:列出某函数 / 类型 / 变量被哪里调用
code.hover 看签名 / 类型 / 文档:等同 IDE 鼠标悬停
code.symbols 文件大纲:列出文件里的函数 / 类型 / 方法
code.workspace_symbols 全仓按名搜索符号
code.diagnostics 编译错误 / 警告:拿到编译器级别的报错,不是靠日志猜
code.call_hierarchy 调用图:某函数被谁调用 / 调用了谁
code.repo_map 项目骨架:文件树 + 各文件顶层符号,token 友好的全仓概览
code.rename 跨文件安全重命名 — 真正改名而不是文本替换
code.code_action 快速修复:列出 / 应用语言服务器给出的 quick fix;配合 code.diagnostics 形成"看错 → 修错"自愈闭环
code.lsp_status 看本工作区 LSP server 池的实时状态
code.implementation 找实现:从接口 / 抽象方法跳到它的各个实现
code.type_definition 跳类型定义:从变量或表达式跳到它的类型声明处
code.document_highlight 本文件内高亮:某符号在当前文件里的全部读写与引用点,比全仓找引用轻量
code.completion 补全候选:给出某个光标位置的代码补全建议
code.signature_help 函数签名提示:调用函数时给出形参列表并标出当前参数
code.formatting 格式化整篇:按语言服务器规则格式化并写回文件(无改动则不写)

位置参数对外 1-based(与读文件一致 / 同 cat -n),不必心算偏移。

按语言服务器实际支持情况自动降级:并非每门语言的服务器都实现了全部能力。是否支持以实际调用结果为准,而不是听服务器自报,避免它其实能做却被误判拒绝;判定出"不支持"后会记住,同一台服务器在后续会话里不再重复试探。

10.10.1 内置 24 门语言 + 按需自动下载

内置覆盖主流语言:Go、TypeScript / JavaScript、Python、C / C++、Rust、Java、Lua、Bash、YAML、PHP、Ruby、Vue、Zig、Dart、Kotlin、Clojure、Elixir、Haskell、F# / C#、Gleam、Astro 等 24 门。

  • 缺哪门语言的服务器会按需自动下载(安全托管、带紧急开关),下载进度有 toast 提示。
  • 设置 → 代码智能 / LSP 面板能看到正在运行的服务器与安装状态。

10.10.2 用 lsp.json / lsp.yaml 覆盖默认

工作区根目录或 ~/.avlcode/ 放一个 lsp.yaml(推荐,兼容旧 lsp.json)就能覆盖默认设置 — 改启动命令、改 root marker、禁用某门语言、增加自定义 server 都行。改完热重载、不用重启工作区;服务器进程崩了 / SSH 掉线自动重启。

10.10.3 远程工作区同样可用

LSP 在 SSH 远端启动:远端缺什么语言的服务器按需自动下载到远端,跨系统的路径与读写都已正确处理,远程工作区的代码智能与本地等同。

10.10.4 Python 虚拟环境(venv)自动遵循

AI 在工作区里跑 Python 相关命令时自动探测并使用工作区的虚拟环境,不必每次手动 activate:

  • 覆盖:venv / .venv、conda(含 Windows 上 Scripts/python.exe 布局)、poetry(含项目目录之外的环境)、pipenv。
  • 机制:把首命令改写成 venv 里的绝对路径,并注入 VIRTUAL_ENV / PATH,保持工具链一致;探测不到 venv 时零行为变化(命令原样执行)。
  • 缓存:项目外的发现(poetry / pipenv)有 5 分钟缓存 + 15 秒探测超时,避免每轮都 fork。
  • 逃生口:环境变量 AVLCODE_DISABLE_VENV_AUTODETECT 非空即全局关闭自动感知。
  • pyright 语言服务也接入 venv 感知,本地和 SSH 远端都生效,代码智能里的类型推导走的就是你真实的 venv。

10.10.5 全库代码检索与问答(code.search / code.ask)

在「找定义 / 找引用」这种精确点查之外,新增两个全库语义检索工具,回答「登录逻辑在哪、怎么走的」这类问题:

工具 用途
code.search 用自然语言 / 关键词在整个项目里检索符号(函数 / 类型 / 方法),返回排好序、带 文件:行 的命中
code.ask 在 code.search 基础上,直接基于检索到的片段合成「人话答案 + 文件:行 引用」,找不到会直说、不编造

特点:

  • 纯本地、零向量 / 零 embedding,可离线(air-gap):用 BM25 + 标识符切分(驼峰 / 下划线 / 连字符自动拆词)+ 中日韩二元切分(中文注释、中文提问同样能命中),不依赖任何模型或联网。
  • 命中可解释:每条结果都给出 文件:行 和命中的关键词,看得清为什么是这条。
  • 索引自动跟上:首次调用自动建索引;之后按文件 mtime 增量刷新(改 / 增 / 删自动更新)。本地索引落在工作区 .avlcode/rag/。建索引与启动语言服务的进度以单条原地更新的进度条显示(不再刷一屏 toast)。
  • 超大仓库如实告知:索引最多覆盖 4000 个文件。超过时会明确告诉你有多少文件没被索引,而不是悄悄少搜一部分 —— 「没搜到」到底是真不存在,还是没覆盖到,一看便知。
  • 结构感知重排:结合中心度与调用关系把「被调用更多 = 更核心」的符号排前(code.search 传 expand=true 显式启用;code.ask 现在默认就做这道智能重排,回答代码问题更聚焦相关内容)。增量刷新的作用域也已修正,文件变更后更准,不多刷、不漏刷。
  • 远程也可用:SSH 远程工作区同样支持(远端枚举 / 读取,索引在内存)。
  • 用哪个模型:code.ask 按「模型路由策略」在澜砥系列里选模型(默认效果优先),没有可用的澜砥模型时改用当前会话的模型;本地模式下只用当前会话的模型(见 §8.2.1)。检索到的代码片段会发给选中的模型,结果里的 provider / model 标明实际去向。

这两个工具与上表的 code.*(LSP 精确查询)互补:精确定位用 code.definition / code.references,「这事在哪、怎么实现的」用 code.search / code.ask。

[!warning] 旧版索引「整仓搜不到」已修复:此前某些仓库上 code.search / code.ask 会表现为"索引完全失效、什么都搜不到"(缓存里只剩删除记录、已索引文件数为 0)。根因是内容提取失败被当成了"空文件" 写进索引。现已改为:提取失败就是失败,不写空记录、也不覆盖已有内容。升级后首次调用会重建索引。

10.11 测试运行工具 test.run

AI 现在能自己跑测试、读懂失败、再回去修,形成「跑测试 → 看失败 → 改代码 → 再跑验证」的闭环。

  • 自动识别测试框架:按项目文件判断 — go.mod → Go / pyproject.toml·pytest.ini → pytest / Cargo.toml → cargo / package.json → Node(vitest、jest);也可手动指定 framework 或 command 完全覆盖。
  • 结构化失败:跑完把输出解析成 failures[](每条含 test 名 / 文件 / 第几行 / 错误信息),最多回灌 50 条 + 一小段原始 tail,不把整篇测试日志灌进对话 — 省 token。
  • 未知框架兜底:解析失败时退回到退出码 + 末尾输出,保证至少能告诉你失败了什么。
  • 范围限定:可传 path 限定(Go 包模式如 ./pkg/...、pytest 目录或文件、cargo 包名)。
  • 远程也能用:本地 + SSH 远端工作区一视同仁。
  • plan(筹划)与 assess(评估)模式禁用:跑测试 = 执行项目代码,会产生写副作用,与两个只读模式的定位冲突。

10.11.1 自检门禁(opt-in,四类检查器)

「自检门禁」是测试门禁的泛化:开启后 AI 每轮干完活后自动跑一组检查;如果有失败,把结构化失败信息回灌给它并强制接着修,直到全绿 — 不用你手动催「再跑下测试」。

六类检查器(可单选 / 组合):

检查器 用途 后端 / 工具
test 跑测试 test.run(Go / pytest / cargo / Node)
diagnostics 编译 / 类型错误 code.diagnostics(LSP,无需额外工具)
lint 代码风格 / 潜在 bug lint.run(golangci-lint / eslint / ruff)
sast 安全静态分析 sast.run(semgrep)
testgen 改动的源文件缺测试 → 自动补(opt-in) 用 fs.glob 经 zMCP 网关查约定路径(Go / Python / JS / TS)
judge LLM 收尾自审(opt-in,默认 report) 独立 fresh-context 模型调用,只看「目标 + 本轮 diff」,挑机检抓不到的逻辑 / 语义 / 安全意图错

每个检查器都可调:

  • mode:block(默认,注入失败强制修) / report(只通报不拦)
  • submode:
    • test:changed(默认,只测改动的)/ full(全跑)
    • lint:strict(警告也算红)/ lenient
    • sast:quick(限大文件加速)/ deep(完整)
  • scope:changed(默认,本轮改动文件)/ workspace(全库)
  • path / linters / config:精细化覆盖

testgen / judge 不在默认集:要显式把 testgen / judge 加进 checks 才生效;前者只判 Go / Python / JS / TS 这些有明确测试约定的语言,跳过测试文件本身与生成物(如 .pb.go),共用门禁的 3 次注入硬上限。

judge 永不误拦:模型乱报 / JSON 脏 / 超时一律视为「非权威」放行;门禁里 judge 安排在所有便宜检查器之后、且仅当 block 检查器全绿时才跑(省一次模型调用,也不在客观失败上堆主观意见)。few-shot 示例 + 当轮诊断背景注入到 judge prompt,判断更准。

为什么是独立模型调用:judge 在全新上下文里评审,不带 agent 自己的对话历史,消除「自己查自己」的自我合理化偏见 — 这正是 LLM-as-judge 区别于「让 agent 自我反思」的核心。

省开销:仅在本轮真正改过文件后才跑;没有编辑的轮次整组跳过。 结构化展示:失败在前端用结构化面板展示,不是一坨裸 JSON。 不会死循环:同会话连续注入硬上限(默认 3 次)— 撞到即停门禁、交回常规流程,避免 flaky / 不可修复的检查把 AI 困死;真正的用户介入立即清零。

10.11.2 「继承 + 覆盖」三级开关

层 来源 作用
全局默认 设置 → 测试与自检 tab(落到 ~/.config/avlcode/checkgate-default.yaml) 没配工作区时的默认检查集
工作区默认 工作区 .avlcode/testgate.yaml(侧栏 / Header 工作区菜单一键开关 + Strip 全参数编辑器) 新会话默认值;按当前工作模式(plan / prepare / execute / assess)还可单独覆盖
会话覆盖 会话菜单的检查项勾选清单 单个会话可逐条勾选哪些检查器启用,工作区开了仍能把某条单独关掉,反之亦然

会话菜单和工作区菜单显示的都是实际生效状态,所见即所得。

10.11.3 CheckGateStrip — 输入框上方的全参数编辑器

工作区开启自检门禁后,输入框上方会出现一条 CheckGateStrip:

  • 一行 chip 显示每个检查器的当前生效态(mode / submode / scope)。
  • 点 chip 展开全参数编辑器,可改 mode / submode / scope / path / linters / config / timeout,所改即时写入工作区配置。
  • 关掉门禁后 Strip 隐藏,不挡视线。

10.11.4 工具安装状态

设置 → 测试与自检 页底部展示外部工具的安装状态(主进程 PATH 可见性):

类别 工具 缺失时的安装指引
test go / pytest / npm / cargo 装相应工具链
lint golangci-lint / eslint / ruff go install … / npm i … / pip install ruff
sast semgrep pip install semgrep 或 brew install semgrep

macOS GUI 进程的 PATH 可能不含 shell(.zshrc)里 pip / npm 装的路径,这里是主进程可见性的尽力探测;真正运行仍由工作区 interpreter 在工作区上下文跑,可能成功 — 不必拘泥于此处的 ✗。

10.11.5 lint.run 与 sast.run — 也可单独被 AI 调用

lint.run 与 sast.run 不只是自检门禁的后端,AI 也能在 plan / assess 等只读模式下直接调用做静态审计 — 它们是只读分析(不改源、不执行项目代码),不在 read-only 拒绝列表里。

lint.run:

  • 自动识别 linter:go.mod → golangci-lint / .eslintrc·eslint.config → eslint / pyproject.toml·ruff.toml → ruff;也可 linter / command 手动覆盖。
  • 结构化发现:findings[](每条 {file, line, rule, severity, message}),与 test.run 同形。
  • submode:strict(如 eslint 警告也算失败)/ lenient。

sast.run:

  • 后端:semgrep(--config auto 默认,需联网拉规则;离线请用 config 指定本地 .semgrep.yml)。
  • 结构化发现:findings[] 同形。
  • submode:quick(限大文件加速)/ deep(完整)。

10.12 工具自省 tools.list / tools.describe / tools.load

AI 可以枚举自己当前可用的工具及其用法,减少「不知道有没有某个能力」的猜测:

工具 用途
tools.list 列本会话可调用的工具(名 + 描述);prefix 可按命名空间过滤(如 fs / code / test)
tools.describe 返回指定工具的完整 schema(描述 + 参数定义)
tools.load 按分类加载工具;不带参数时返回分类目录,每类附一句话能力说明与工具名清单

列出的结果会按本会话实际可用的范围裁剪(受工具三态与分类延迟加载影响),并提示"当前表里看不见并不代表不存在,可以先加载对应分类"。