Repository navigation
Releases: wojiushirencai/Astrolabe
Release list
Astrolabe v0.17.0
[0.17.0] - 2026-10-23
自 v0.16.0 以来的 PR:#90(npm 改发个人 scope @zlnap)、#91(DeepSeek Harness 原生 hook 插件)、#92(astrolabe setup 一键探测并合并 MCP)、#93(setup 支持 OpenCode / DSH)、#94(setup 支持 Codebuddy / Grok Build / Kimi Code / ZCode)。本版以 npm/CLI 安装体验 为主;Rust 索引与工具行为相对 0.16.0 无功能变更。完整说明见 GitHub Release。
新增
astrolabe setup/npx -y @zlnap/astrolabe setup:探测本机已装客户端,交互多选后合并写入 MCP 配置(不清空其它 server)。支持 Claude Code、Cursor、Codex、Windsurf、Cline、Roo、Continue、OpenCode、DeepSeek Harness、Codebuddy、Grok Build、Kimi Code、ZCode。常用参数:--yes、--clients=…、--dry-run、--native、--list、--with-hooks、--no-cursor-permissions。--with-hooks(可选,效果未做基准):OpenCode 写入守卫插件;Codebuddy / Grok / Kimi / ZCode 写入各自 hooks 配方。ZCode:不写SessionEnd,也不在Stop上挂 cleanup。- DeepSeek Harness:vendored
astrolabe-dsh-guard+~/.dsh/cordis.patch.yml托管块合并(不碰orca-managed-dsh-hooks);DSH 的 MCP 仍按仓库在 UI 配置。
分发
- npm 包发布在个人 scope
@zlnap/astrolabe与@zlnap/<platform>(npm orgastrolabe-ai已被占用,见 #90)。
文档
- 安装指南(中/英)与 README 对齐完整 setup 客户端列表与配置路径。
What's Changed
- chore(npm): 改用个人 scope @zlnap 发布 by @wojiushirencai in #90
- feat(cli): astrolabe setup 探测 agent 并合并 MCP 配置 by @wojiushirencai in #92
- feat(dsh): add native DeepSeek Harness hook plugin by @wendyunfa in #91
- feat(cli): setup 支持 OpenCode 与 DeepSeek Harness by @wojiushirencai in #93
- feat(cli): setup 支持 Codebuddy / Grok Build / Kimi Code / ZCode by @wojiushirencai in #94
- chore(release): v0.17.0 by @wojiushirencai in #95
New Contributors
- @wendyunfa made their first contribution in #91
Full Changelog: v0.16.0...v0.17.0
Astrolabe v0.16.0
Astrolabe v0.16.0:Go 上不再主动启动 gopls,多会话共用一份索引
自 v0.15.0 以来的 PR:#87(第 7 批)、#88(第 8 批),加本次版本提升 #89。工具仍为固定 7 个,
tools/list紧凑 JSON 从 7,356 B 降到 3,607 B(Linux 实测,两版用同一脚本)。解析缓存改成了新格式,升级后首次启动会把仓库重新解析一遍,只发生一次;此后发版不再因为版本号让缓存作废。
头条数据: 11 个任务、每个任务 3 轮,本版(0beebfc)和 v0.15.0(09350fb)在同一个修好的测试环境里同时起跑,共 33 对。两版都是 33/33 全对,没有超时。本版 gopls 一次都没启动(v0.15.0 共 10 次),grep 类调用减半(1.21 → 0.61 次/run,95% 区间 [−0.91, −0.24])。花费 $0.836 → $0.737、token 489k → 428k、API 轮数 16.1 → 15.0,点估计低 7–12%,但区间都跨 0,不能说比 v0.15.0 确定更省。和不接 Astrolabe 相比,本版每 run 花费低 39%、token 少 45%、轮数少 34%¹。
一、发现了哪些问题,如何解决
1. 语言服务启动(gopls)
| 问题 | 解决 |
|---|---|
get_diagnostics 每次都启动语言服务。第 7 批重跑里 7 次 gopls 启动有 4 次来自它,9 次调用的结果全是"没有诊断" |
默认改为先跑工具链:本会话已有该语言服务在跑(或已有共用的 gopls 守护进程)时照旧用它;否则 Go 用 go vet(编译不过再用 go test -c 列全部错误)、Rust 用 cargo check、TS 用 tsc --noEmit、Python 用 compile() 只查语法;工具链缺失或超时退回 tree-sitter 语法检查。都带进程组超时,输出格式不变 |
构建或测试已经跑通,模型还按说明再调一次 get_diagnostics,白费一轮还拉起 gopls |
连接说明和工具说明写明"构建/测试已跑通就不必再调" |
Go 同名声明有多处、而调用方给的 path 不是任何一处声明时,退回启动 gopls(H6 每轮 1 次) |
按调用处消歧(导入的包、包内可见性、接收者类型);选不出唯一一处时按定义分组、由索引作答,不再启动 gopls。不带 path 的多包同名也按定义分组,不再回"无法判断" |
Go goto_definition 每次冷启动 gopls(1.2–2.3 s) |
索引能唯一判定时直接作答(0.25–3.4 ms),无解或有歧义才启动 gopls |
同名多处定义、调用方给了声明所在的 path 时仍退回 gopls |
按 path 选定声明 |
| 多个会话各起一个 gopls | gopls 以 -remote=auto 启动,多会话共用一个守护进程(两会话总 RSS 5.7 GB → 3.0 GB) |
没装语言服务器时 find_references / goto_definition 只回 Unknown |
改由索引作答,附一行安装提示 |
2. find_references 结果质量
| 问题 | 解决 |
|---|---|
Go 方法引用有误报:模板的 t.Execute 等同名方法被算进来,开头还写"全仓唯一定义" |
按接收者类型过滤,判定不了的标"未核实";措辞改为如实说明 |
| 同名的局部变量 / 参数被算作包级名的引用;包文档注释里的示例代码会截断 import 解析 | 两处都已修正 |
| TS/JS 索引引用漏掉转导出(barrel)与改名绑定,trpc monorepo 召回只有 0.503 | 补转导出链、default import / require 改名 / {A as B}、TS namespace 成员;裸包名不再按目录后缀误配;对象字面量键、类型成员不算引用 |
Python 索引引用漏掉字符串类型注解、__init__.py 转导出、as 改名 |
全部补上;方法 / 属性按接收者类核对 |
| 拿到结果后模型仍回头 grep 复核 | 结果确实查全时末行写"已全列";测试命中占多数时加一行非测试调用方汇总 |
| 内联"所在函数上下文"一次 Read 都没省下:第 7 批 find_references 之后 104 次 Read 没有一次落在已贴出的范围内,却每 run 多约 1 KB、后续轮次重发约 10 KB | 默认关闭(ASTROLABE_REFS_CONTEXT_HITS=on 可打开);命中行的所在函数名、调用方汇总、"已全列"保留 |
3. get_diagnostics 的环境问题
| 问题 | 解决 |
|---|---|
| 工具链环境坏掉时刷出一堆级联假错误(E2 一次 10 KB) | 识别环境异常,只报"环境异常,诊断不可信"加根因一行(约 400 B);真实语法 / 类型错误照常列出 |
Go 模块不在 go.work 的 use 列表里时(如 third_party/go-qrcode)报工作区错误 |
这类模块自动加 GOWORK=off 再跑;工作区配置本身的报错不再当成代码诊断 |
Go ≤ 1.25 的 go vet 只有分析器发现时也打印 # pkg 头,被误判成编译失败 |
能编译通过时这些发现按警告报 |
4. 模型在 Go 多模块仓库里绕远路
- 问题: 仓库根目录没有
go.mod(模块在src/)时,模型在根目录跑go test ./...报错,接着绕了十来轮。 - 解决: 连接说明里点名 Go 模块根,并提示改用
cd <模块根> && go test ./<包>/。
5. 会话健壮性
| 问题 | 解决 |
|---|---|
| "改了又改回"后旧符号残留 15 s 以上 | watcher 记录查询屏障修复过的路径,重复事件重新标为疑点;约 22–42 ms 内消失 |
会话锁中毒直接让进程崩溃(20 处 unwrap) |
改为自动恢复 |
| 增量更新失败时静默丢弃改动 | 记录、重试,仍失败则后台重建,期间结果末尾注明"索引可能过期" |
6. 存储、查询与内存
| 问题 | 解决 |
|---|---|
| 解析缓存键带 crate 版本,每次发版整代作废;缓存是 JSON,体积大 | 键去掉版本,改为紧凑二进制;提取结果指纹测试守卫,提取逻辑变了没升 EXTRACTOR_VERSION 测试就失败 |
| 单文件增量要整张图重新组装(最大语料 339 ms) | 组图并行化、调用边线性归并、产物缓存按代淘汰、旧图在后台线程释放 |
查询路径逐命中扫全表;search_code 逐行跑正则 |
去掉逐命中扫描,整块缓冲区搜索;28 个基准查询输出与 v0.15.0 逐字节一致 |
| glibc 编辑后不归还空闲堆 | 建完索引及编辑停下 5 秒后 malloc_trim(Linux glibc) |
| 同一仓库开多个会话时每个会话各建一份索引,内存成倍增长 | 多会话共用一份索引,见第二部分 |
7. 工具定义与依赖
- 工具定义每轮重发、偏大,说明互相冲突("请安装语言服务器"与"语言服务只兜底"矛盾):隐藏 16 个从未用过的参数(照常接受,
ASTROLABE_FULL_SCHEMA=1全部声明),理顺说明。 - Roslyn 设计时构建往用户工程写
obj/: 通过CustomAfterDirectoryBuildProps把IntermediateOutputPath改到~/.astrolabe/roslyn/下;工程自设的不覆盖(ASTROLABE_ROSLYN_OBJ_DIR=off恢复)。 - RUSTSEC-2026-0381 / 0382: 语言服务器下载器
ureq2 → 3,rustls-webpki0.103.16、webpki-roots1.0.10。ureq 3 默认读取HTTPS_PROXY等代理环境变量。
二、新增了什么支持,如何支持
| 功能 | 怎么做到的 | 开关 |
|---|---|---|
| 多会话共用一份索引(Linux / macOS) | 第一个会话拿到 indexer.lock,建索引并在用户私有目录开一个 Unix socket(目录 0700、socket 0600)。后开的会话握手校验协议、版本、提取器 / 存储版本、工具目录、ASTROLABE_* 设置、根目录和工具链环境(PATH、GOPATH、GOWORK 等),一致就不建索引、不起语言服务,把请求转发过去;每个会话的去重、提示、翻页状态各自独立。主会话退出后剩下的会话自动选出新主会话;先单独建了索引、后来拿到锁的会话也会开始共享 |
默认开;ASTROLABE_SHARED_INDEX=off |
get_diagnostics 工具链优先 |
见第一部分 1;要语言服务时传 lsp=true |
ASTROLABE_DIAGNOSTICS=lsp 恢复旧行为;ASTROLABE_DIAG_TIMEOUT_SECS(默认 Go 30 / Rust 90 / TS 45 / Python 10 s) |
| Go 同名多处定义由索引作答 | 按调用处消歧,选不出时按定义分组 | — |
Go goto_definition 索引优先 |
唯一判定时直接作答 | ASTROLABE_DEFINITION_INDEX_FIRST=off |
TS/TSX、Python 顶层符号 find_references 索引优先 |
全仓只有一处定义的顶层函数 / 类 / 接口 / 类型(Python 含模块级常量)直接用索引作答,不启动 tsserver / pyright;方法、字段、属性、多处定义仍走语言服务器,JS 仍语言服务器优先 | ASTROLABE_TS_GRAPH_FIRST=off、ASTROLABE_PY_GRAPH_FIRST=off |
| gopls 共用守护进程 | -remote=auto |
ASTROLABE_GOPLS_REMOTE=off |
| Go 模块根提示 | 仓库根没有 go.mod 时,连接说明里列出模块根 |
— |
get_diagnostics 环境异常检测 |
只报根因 | — |
malloc_trim(Linux glibc) |
空闲 5 秒回收 | ASTROLABE_MALLOC_TRIM=0 |
Roslyn obj/ 重定向 |
不写工程内 obj/ |
ASTROLABE_ROSLYN_OBJ_DIR |
三、改动后的整体覆盖与性能
3.1 AI 端到端:本版对比 v0.15.0(同一环境、同时起跑,配对 n=33)
11 个任务(H1 H2 H3 H6 H7 E1 E2 N1 N3 N6 N7),每个任务 3 轮,每个任务的 6 个 run 共用一个起跑栅栏(实测起跑相差约 1 ms)。模型 gemini-3.8-flash-high,claude CLI,bubblewrap 沙箱,每 run 独立副本、冷建索引。配对 bootstrap(任务等权,20000 次)。
| 每 run 平均 | v0.15.0(09350fb) |
v0.16.0(0beebfc) |
差值 [95% 区间] | 结论 |
|---|---|---|---|---|
| 答对 | 33/33 | 33/33 | 持平 | 两版全对,无超时 |
| 花费 | $0.836 | $0.737 | −$0.099 [−0.277, +0.072] | 跨 0,不显著 |
| 总 token | 489k | 428k | −61k [−177k, +50k] | 跨 0,不显著 |
| API 轮数 | 16.1 | 15.0 | −1.15 [−3.64, +1.30] | 跨 0,不显著 |
| 工具调用 | 15.9 | 14.5 | −1.42 [−3.82, +0.94] | 跨 0,不显著 |
| grep 类调用 | 1.21 | 0.61 | −0.61 [−0.91, −0.24] | 显著,减半 |
| gopls 启动(33 run 合计) | 10 次 | 0 次 | −0.303/run [−0.364, −0.273] | 显著 |
| 墙钟 | 275 s | 245 s | −30 s [−90, +28] | 跨 0,不显著 |
- 花费为什么说不准: v0.15.0 的 N1 有一轮花了 $3.54,把均值拉高;去掉 N1,两版花费、token、轮数基本持平。稳定更便宜的是 H1(−$0.29 [−0.50, −0.02])和 H2(−$0.19 [−0.27, −0.08])。
- E1: 第 7 批时新版比 v0.15.0 贵 $0.55;同时起跑后差值为 −$0.01,拆开看那 $0.55 大半来自上游缓存命中率波动和模型自身的探索方式。
- find_references 之后的 Read 变多(2.97 → 4.42 次/run),但 Read 总数没变(5.73 → 5.91,跨 0):本版更早调用 find_references,本来就要做的 Read 被算到了它后面。
3.2 对比不接 Astrolabe¹
| 每 run 平均 | 不接 Astrolabe | v0.15.0 | v0.16.0 | v0.16.0 相对不接 |
|---|---|---|---|---|
| 答对 | 31/33 | 33/33 | 33/33 | — |
| 花费 | $1.218 | $0.836 | $0.737 | −39%(−$0.48 [−0.66, −0.30]) |
| 总 token | 779k | 489k | 428k | −45% |
| API 轮数 | 22.6 | 16.1 | 15.0 | −34% |
| Read | 9.15 | 5.73 | 5.91 | −35% |
| grep 类调用 | 6.03 | 1.21 | 0.61 | −90% |
¹ 不接 Astrolabe 的一组是 2026-10-19 在旧测试环境跑的,不是同日配对,按任务分层 bootstrap;墙钟不可比(那天上游回合中位 4.98 s,本轮 12–13 s),只看 token 与花费。
3.3 工具级前后对比
| 项目 | v0.15.0 | v0.16.0 |
|---|---|---|
tools/list 紧凑 JSON(同一脚本) |
7,356 B | 3,607 B |
get_diagnostics(Go,暖缓存,6 个文件) |
gopls 首次 1.2–1.4 s,常驻 106–241 MiB | go vet 61–93 ms,峰值 27–52 MiB,跑完即退出 |
Go goto_definition(索引能判定时) |
1.2–2.3 s(冷启动 gopls) | 0.25–3.4 ms |
| 3 个会话同开 aiproxy(3.1k 个 Go 文件)总内存 | 424 MB | 177 MB(−58%) |
| 3 个会话同开 9.8k 个 Rust 文件的语料总内存 | 3,488 MB | 1,215 MB(−65%) |
| 后开会话 | 各自建索引 | 11–13 MB,几乎立即可用;转发每次多约 0.15 ms |
主会话被 kill -9 后 |
— | 下一次调用 0.16–1.5 s 内由新主会话作答 |
| 单文件增量(最大语料) | 339 ms | 105 ms |
| 索引库体积(c-new) | 67.5 MB | 23.7 MB |
| 热重启 | 0.64 s | 0.44 s |
search_code 字面量 / 分组(c-new) |
71 / 449 ms | 12 / 80 ms |
| 连改 10 次后 RSS(435 万行仓库,Linux) | 2,010 MB | 1,372 MB |
| 两个会话 gopls 总 RSS | 5.7 GB | 3.0 GB |
| "改了又改回"后旧符号消失 | 15 s 以上 | 约 22–42 ms |
get_diagnostics(环境坏掉时) |
10 KB 假错误 | 约 400 B 根因 |
3.4 索引作答的准确度(按 (文件, 行, 列) 与语言服务器对照)
| 范围 | 语料 | 召回 | 精度 |
|---|---|---|---|
| Go 同名多处定义(调用点逐一核对) | cobra | 1.000 | 1.000 |
| aicon | 0.991 | 0.993 | |
| aiproxy | 0.994 | 0.975 | |
| TS 顶层符号(抽样 200) | aiproxy web | 1.000 | 0.998 |
| trpc monorepo | 0.999(v0.15.0 规则 0.503) | 0.993 | |
| Python 顶层符号(65 例) | serena + rich | 1.000 | 1.000 |
Go 选错的几处,调用点所在那一行本身也是声明(如 type X = pkg.X),两种理解都说得通。
3.5 局限,请如实看待
- 只用了一个模型、一个代理,11 个任务是我们自己设计的,每任务 3 对;单个长 run 对任务均值影响很大。
- 本轮两组都没有调用
get_diagnostics,"不启动 gopls"这一项在端到端测试里没覆盖到,靠单元测试和单独实测支撑。 - 共享索引在 6 个发布平台上都已随本次 release 构建通过;内存与故障恢复只在 Linux x64 上实测,macOS arm64 上用发布包做了双会话冒烟(主会话 / 跟随会话正常建立、结果一致);musl、Linux arm64、Intel Mac 还没实际跑过多会话。
仍未处理
- 比 v0.15.0 是否更省钱还没有定论: 花费、token、轮数的区间都跨 0。
- Windows 不共享索引: 命名管道还没实现,一律各自建索引。
- 工具链环境不同的客户端不共享索引: 比如从不同 shell 启动、PATH 不一样的 Claude Code 和 Cursor,结果照样正确,只是多占内存。
get_diagnostics的 Rust(cargo check)和 Python(compile())路径只在小例子上测过;被 build tag 排除的 Go 文件go vet不检查,会报"没有诊断"。- JS 仍语言服务器优先(express / three.js 精度 0.76–0.93);TS 与 Python 的方法 / 字段 / 属性仍走语言服务器;Go 以外的
goto_definition仍语言服务器优先。 - 热重启 0.44 s,未到 0.25 s 目标。
- macOS 上
malloc_trim不起作用。 - gopls 共用后 Astrolabe 设的 gopls 内存上限不再生效(
ASTROLABE_GOPLS_REMOTE=off恢复)。 - N3 上偶有长 run: 模型逐个核对接口实现方(本轮新版 N3 r2 单 run $2.25,第 7 批也有类似长 run)。
- 发布渠道: npm(
@astrolabe-ai/*)没有发布;没有提交到任何渠道;GitHub About 没有改。
Full Changelog: v0.15.0...v0.16.0
Astrolabe v0.15.0
Astrolabe v0.15.0:find_references 在 Go 上不再先起语言服务,结果更省 token
自 v0.14.0 以来的 PR:#84(关闭 Roslyn 自动 NuGet 还原)、#85(第 6 批),加本次版本提升 #86。工具仍为固定 7 个,
tools/list为 7,307 B(Linux 实测,上限测试 7,500 B)。EXTRACTOR_VERSION从 5 升到 6,升级后首次启动会把仓库重新解析一遍,只发生一次。
头条数据: 11 个任务、每个任务 3 轮,本版(09350fb)和 v0.14.0(1c77cc1)两个版本同时起跑,共 33 对。本版每个 run 平均花费从 $1.13 降到 $0.90,配对差 −$0.22,95% 区间 [−0.42, −0.05]。token 从 633k 降到 489k,API 轮数从 19.2 降到 16.2,工具调用从 19.1 降到 16.4,gopls 启动从 20 次降到 7 次。没有一次答错;未答对的 run(v0.14.0 3 次、本版 4 次)全部是超时。墙钟时间分不出差别。两处回退和局限见第三部分。
本批的修复来自对上一轮测试会话的排查:没有哪次是 Astrolabe 漏查、靠 grep 才找到;花费主要花在模型反复核对已经正确的答案,以及 find_references 结果不够用导致的回头 Read。
一、发现了哪些问题,如何解决
1. find_references / goto_definition
| 问题 | 解决 |
|---|---|
| 同时给了符号名和行列时只看位置。模型给的列号常差一点,18 次里 6 次指到别的标识符上,返回无关结果,最后退回 git grep | 名字和位置对不上时以 symbol 为准:在该行 ±2 行内找到并对齐,附一行说明;附近找不到就按名字查。goto_definition 同样处理 |
path 没给、写错或文件里没有该符号时返回 Unknown,即使索引里只有一个定义 |
索引里只有一个定义文件就自动改用它,多个则列出候选 |
| 空结果提示不分语言,Go 代码也会收到 Kotlin / Android 的提示 | 提示按语言给出,只有 Kotlin 才提 Kotlin;discovery 已给出安装命令时不再追加通用安装提示 |
| Go 每次都先拉起 gopls:66 次会话里 39 次启动了语言服务,所有超过 1 秒的慢调用都来自这里 | Go 改为索引优先,见第二部分。多处定义的名字和 dry_run_rename 仍走 gopls |
结果不给源码行和所在函数,会话里 find_references 之后有 261 次回头 Read 去看同样的位置 |
每条命中自带源码行、所在函数及行范围、[声明]、[注释] / [字符串];非测试在前、测试在后,按文件分组;超过 150 处先给按文件计数,传 offset 再逐条列 |
2. search_code
| 问题 | 解决 |
|---|---|
| 上下文窗口重叠时重复打印 | 合并重叠窗口 |
| 命中处不说明在哪个函数里,模型要倒着翻文件找函数开头 | 分组命中首次出现时标 in 函数@起-止 |
| 测试文件的命中挤占正文 | 测试文件每文件最多 2 处 |
| 字面量查询不区分大小写,含大写的查询也会混入别的名字 | 含大写的字面量查询区分大小写(smart-case) |
path_filter 把结果全过滤掉时提示"范围太窄",其实符号不在那几个文件里 |
报告过滤范围外有几处命中 |
3. find_symbol
| 问题 | 解决 |
|---|---|
"ld" 返回 1 个精确匹配外加 25 个只是名字含 ld 的结果 |
有精确命中时,子串命中折叠成一行计数 |
"Provider" 字段排在类型前,截断时会丢掉接口实现行 |
类型优先、精确大小写优先;Go 隐式实现方给出方法 N/N 计数,测试替身标 [测试],截断页也保留这一行 |
4. 输出措辞
- 问题: 输出里的"可能有误报或遗漏""请用锚点核验""需要核验",是模型拿到答案后继续核对的诱因之一。
- 解决: 删除这些措辞;图例和页脚每个会话只出一次。
5. 三类假语法错误
- 问题: aiproxy 和 aicon 每次会话开头都带"有文件语法错误"的脚注,来源是三种合法写法被误判:
- TS 调用类型实参里换行的索引签名,如
useState<{ id: number\n [key: string]: unknown }>; - Bash 带引号的 heredoc,正文某行以结束符开头;
- JSX 元素正文里的裸
&,如<a>x & y</a>。
- TS 调用类型实参里换行的索引签名,如
- 解决: 解析出错时做等长替换后重试,结果干净才采用。两个语料不再报语法错误,
search_code不再带语法错误脚注。EXTRACTOR_VERSION5 → 6。
6. 语言服务(只作兜底)
- rust-analyzer 只是 rustup 代理壳时报 Ready:
get_languages显示就绪,get_diagnostics却必然失败。现在探测时带RUSTUP_AUTO_INSTALL=0跑一次--version(5 秒超时,结果缓存 30 秒),失败就标"需要安装",并给出rustup default stable/rustup component add rust-analyzer等具体命令。 - Roslyn 自动 NuGet 还原(#84): Roslyn 询问
projects.dotnet_enable_automatic_restore时我们原来回null,它按默认true处理,加载完项目后自己对整个解决方案跑 NuGet 还原,在 serilog 上超过 100 s 不结束,还往obj/写还原产物。现在 C# 和 VB 对这一项回答false,_roslyn_projectNeedsRestore仍空回复、从不还原。
二、新增了什么支持,如何支持
- Go
find_references索引作答:- 范围: 同语言族里该名字全仓只有一处定义时,直接用语法/图索引作答,不启动 gopls。查找范围是定义所在的包,加上经 import 边到达该包的文件;排除注释、字符串,以及经其他包导入名的选择子(如
fmt.X)。 - 何时仍用 gopls: 名字有多处定义,或用
dry_run_rename需要编译器级清单时。 - 耗时: 每次 1–350 ms,不起进程;gopls 首次调用 0.45–2.3 s。
- TS / JS / Python 仍是语言服务器优先,原因见"仍未处理"。
- 范围: 同语言族里该名字全仓只有一处定义时,直接用语法/图索引作答,不启动 gopls。查找范围是定义所在的包,加上经 import 边到达该包的文件;排除注释、字符串,以及经其他包导入名的选择子(如
find_references命中上下文: 源码行、所在函数@行范围、声明 / 注释 / 字符串 / 测试标记,以及按文件计数的分页。语言服务器结果和文本兜底都带。- 坐标与路径自动纠正:
find_references、goto_definition以symbol为准对齐坐标,path缺失或写错时用索引补全。 - 工具描述:
find_references写明会列出 interface 的实现方。 - 召回测量工具:
REFS_RECALL_REPO=<repo> REFS_RECALL_CASES=path:sym,… REFS_RECALL_SAMPLE='.go:40' cargo test -p astrolabe-mcp --lib recall_harness -- --ignored --nocapture,按(文件, 行, 列)对比索引答案和语言服务器答案。说明见docs/find-references-graph-first.md。
三、改动后的整体覆盖与性能
3.1 AI 端到端:本版对比 v0.14.0(同时起跑,配对 n=33)
11 个任务(H1 H2 H3 H6 H7 E1 E2 N1 N3 N6 N7),每个任务 3 轮,每一格两个版本同时起跑。模型 gemini-3.8-flash-high,claude CLI,运行在 bubblewrap 沙箱里,超时线 1500 s。
| 答对 | 平均花费 | 平均 token | 平均 API 轮数 | 平均工具调用 | gopls 启动(合计) | |
|---|---|---|---|---|---|---|
v0.14.0(1c77cc1) |
30/33 | $1.13 | 633k | 19.2 | 19.1 | 20 次 |
v0.15.0(09350fb) |
29/33 | $0.90 | 489k | 16.2 | 16.4 | 7 次 |
未答对的 run 全部是超时(v0.14.0 3 次、本版 4 次),按规则不计为答错;两组都没有答错的 run。
配对差(本版 − v0.14.0,bootstrap 95% 区间,20000 次):
| 指标 | 配对差 | 95% 区间 | 结论 |
|---|---|---|---|
| 花费 | −$0.22 | [−0.42, −0.05] | 显著 |
| token | −144k | [−273k, −30k] | 显著 |
| API 轮数 | −3.1 | [−5.6, −0.6] | 显著 |
| 工具调用 | −2.7 | [−5.1, −0.5] | 显著 |
| gopls 启动 / run | −0.39 | [−0.64, −0.15] | 显著 |
| 墙钟(Claude 自报 duration) | −12 s | [−177, +161] | 跨零,不显著 |
| 零命中后改用 grep / run | +0.12 | [+0.03, +0.24] | 回退(合计 2 → 6 次) |
分任务看(每个任务各 3 对):
- 11 个任务里 10 个平均花费更低,只有 H6 多了 $0.02($0.29 → $0.30)。
- 省得最多的是 N1($2.08 → $1.32)和 N3($1.63 → $1.16),但单任务区间跨零;N7 区间不跨零(−$0.20 [−0.32, −0.11])。
- 墙钟回退: 去掉两组同时遇到的上游停顿后,H1 多了 107 s [+13, +257],E2 多了 140 s [+17, +283],原因还没查清。
3.2 Go find_references:索引答案 vs gopls
按 (文件, 行, 列) 比较,用例是测试会话里实际查过的符号,外加按固定步长抽样、全仓唯一定义的非测试符号(docs/find-references-graph-first.md)。
| 语料 | 用例 | 索引作答 | 交给 gopls | 召回 | 精度 |
|---|---|---|---|---|---|
| cobra | 33 | 32 | 1 | 1.000 | 0.996(1020 处中多 4 处) |
| aiproxy | 54 | 51 | 3 | 1.000 | 0.999(1159 处中多 1 处) |
| aicon | 32 | 31 | 1 | 1.000 | 1.000 |
多出的 5 处里只有 1 处是真误报(text/template 的 Execute 经局部变量调用,语法上分不出);2 处在 //go:build windows 文件里,gopls 在 Linux 上不加载;1 处是经别名导入、gopls 漏掉的引用。
TS / JS / Python 只做了测量、没有切换:TS 抽样 40 个中 27 个的 tsserver 基线不是 Exact,JS 召回 0.958、精度 0.535,Python 召回 0.779(pyright 每次超时,基线是文本兜底)。
3.3 工具级前后对比
| 项目 | v0.14.0 | v0.15.0 |
|---|---|---|
tools/list 紧凑 JSON |
7,185 B | 7,307 B |
Go find_references(名字全仓唯一) |
先启动 gopls,首次 0.45–2.3 s | 索引作答 1–350 ms,不起进程 |
find_symbol "ld"(cobra) |
2,278 B | 370 B |
find_symbol "Provider"(aiproxy) |
10,048 B | 5,855 B |
search_code "Suggestions"(cobra) |
4,166 B | 2,657 B |
search_code "ShouldDisableChannel"(aiproxy) |
6,281 B | 3,588 B |
resolve_context(cobra,命令拼写建议) |
2,907 B | 2,319 B |
| aiproxy / aicon 假语法错误 | 每次会话带脚注 | 0 |
| Roslyn 加载 serilog 到就绪 | 100 s 内没有就绪(还原未结束) | 3.82 s |
serilog obj/ 下的 NuGet 还原产物 |
25 个 | 0 个 |
find_symbol "controller" 加 path_filter=command.go 从 291 B 变成 405 B,多出来的是"过滤范围外有几处命中"的说明。
3.4 局限,请如实看待
- 样本小: 只用了一个模型、一个代理,11 个任务都是我们自己设计的,每个任务只跑 3 对,单任务区间很宽。
- N6 墙钟不可用: N6 有 3 次 run 跑了约 3 小时才被杀,超时线没有生效,所以 N6 的墙钟数据不能用;花费和 token 不受影响。
- 上游 API 停顿: 本轮多次出现两组同时遇到的 60 s 以上长回合,墙钟噪声很大。
- 两处回退还没修: 零命中后改用 grep 的次数略有上升;H1、E2 去停顿后的墙钟变长。
- 核对行为: 本版删掉了鼓励核对的措辞,但花费下降无法单独归因到这一项;模型拿到答案后仍会继续用 Read/Bash 核对,单靠工具输出可能消不掉。
仍未处理
- 零命中回退上升、H1 / E2 变慢: 见 3.1,原因待查。
- TS / JS / Python 仍是语言服务器优先: tsserver 缺 node_modules、pyright 超时,基线不可靠;Python 经属性访问的引用召回 0.78,JS CommonJS
exports.x精度低,等基线修好再测。 - Go 索引作答的边界: 有多处定义的名字(如
Provider、Register)仍会拉起 gopls;经局部变量的方法调用分不出接收者类型。 - Vue: 模板里的引用仍不在索引里,模板里的类型错误也报不出来。
- 文件监听: 轮询模式下,轮询间隔内的改动仍可能读到旧索引。
- Roslyn 仍会写
obj/Debug/*: 自动还原已关闭,但 MSBuild 设计时构建仍会写;关掉还原后,缺包的项目语义结果会变差,由语法索引兜底。 - 测试脚本超时线: N6 上没有生效,需要修好再重跑墙钟。
- 发布渠道: npm(
@astrolabe-ai/*)没有发布;没有提交到任何渠道;GitHub About 没有改。
Full Changelog: v0.14.0...v0.15.0
Astrolabe v0.14.0
Astrolabe v0.14.0:更省 token 的检索结果,更多语言解析得对
自 v0.13.0 以来的 PR:#76、#77(文档与分发)、#78、#79(审计修复两批)、#80(
get_languages覆盖重做、resolve_context分页)、#81(第 4 批)、#82(第 5 批),加本次版本提升 #83。工具仍为固定 7 个,tools/list为 7,185 B(Linux 实测,上限测试 7,500 B)。EXTRACTOR_VERSION从 2 升到 5,升级后首次启动会把仓库重新解析一遍,只发生一次。
头条数据: 11 个任务、每个任务 3 轮,第 5 批(本版)和第 4 批两个版本同时起跑,共 33 对。本版每个 run 平均花费从 $0.96 降到 $0.78,配对差 −$0.18,95% 区间 [−0.32, −0.05]。token 从 627k 降到 495k,轮数从 19.7 降到 17.5,两组都答对 33/33。作为参考,同一组任务上,不接 Astrolabe(A)平均 $1.22,v0.13.0(B)平均 $1.05。墙钟时间平均少 53 s,但区间跨零,不能下结论。局限见第三部分。
一、发现了哪些问题,如何解决
1. 符号查询(find_references / goto_definition / find_symbol)
| 问题 | 解决 |
|---|---|
多个同名定义合并后仍标 Exact;引用为 Unknown 或定义有歧义时仍给改名预览 |
不再标 Exact;这两种情况下不生成预览,并写明原因 |
find_references 的 path 在 schema 里是可选的,缺了却返回 Unknown |
schema 改为必填 |
goto_definition 只给一处声明;find_references 的分页和改名预览不共用预算 |
列出全部声明;共用一个预算并支持 offset |
find_symbol 首次精确查询约 68–156 ms,路径查询全表扫描 |
按名字排序的倒排表加二分查找,名字索引后台预热:首次 76–156 ms → 11–18 ms,路径未命中 51 ms → 5–9 ms |
传文件路径给 find_symbol 返回 0 条,* 只给噪声建议 |
新增文件大纲(见第二部分) |
| "update cloud client" 这类词序不对的查询返回空,模型转去 grep | 零命中且有多个子词时按子词兜底排序 |
C/C++ 没有 compile_commands.json 时,跳转定义多半只落到头文件 |
图里新增头文件对源文件的配对关系;redis 60 处跨文件调用:clangd 单独 7/60,修前 15/60,修后 58/60 |
2. search_code
| 问题 | 解决 |
|---|---|
group=true 加 offset 翻页失效;count_only / aggregate 被会话去重吞掉 |
修正翻页;计数和聚合每次返回完整表 |
| GBK、UTF-16 带 BOM 的文件被当成二进制或乱码 | 先转码再搜,与建索引共用同一个解码入口 |
角色标签误标:行尾注释里的词泄漏进标签,字符串内部行被标成 [定义] |
标签只看注释之前的代码,字符串内部标 [字符串] |
skip_comments 只认 /* */ 和 Python 三引号;一次搜索先对整文件算掩码(约 45 MB/s) |
补全 Lua、Ruby(% 字面量、heredoc)、PHP(heredoc/nowdoc、{$x})、C#("""、$$"""、@")、Vue、shell、C++、JS 正则和模板字符串等词法;掩码改为惰性计算。修了 composer 里 heredoc 含 foo/* 时后面代码被整段当成注释的 bug |
带 path_filter 的零命中查询约 107–134 ms |
先按路径过滤再算相近符号:7–40 ms |
| "N 处命中"其实是按行数的 | 改成"N 行命中" |
3. 编码检测
- 短 GBK 被判成 EUC-KR: 猜成日韩或 Big5、但按 GB18030 能无错解码且全是汉字时,改用 GB18030(#79)。
- 这条规则又把短日文、韩文几乎全部解成乱码(#81 修): 现在给每种能干净解码的 CJK 编码打分,看解出来的字里常用字占多少,分数相同时才选 GB18030。短 Shift_JIS 0/308 → 288/308,EUC-KR 0/2062 → 1919/2062(完整表见第三部分)。
- 常用字表的许可: 开发中用过来自 chardet / Mozilla 的频率表(LGPL/MPL),与本项目 MIT OR Apache-2.0 冲突。合并前已换成我们自己从维基百科样本统计的字表,生成脚本在
scripts/cjk-freq/。
4. 解析与语法错误文件
- 有语法错误的文件被当成"未进入索引"(#80): 这类文件其实还在索引里,只有部分符号缺失,但输出逐条列出 50 条路径,体积从 2470 B 涨到 8183 B。现在这些文件照常进索引;出错文件不超过 10 个时逐个列出,超过 10 个只给数量,完整清单写到
<仓库>/.astrolabe/coverage-report.md。get_languages输出回到 2689 B,也不再随文件数增长。 - 能修的解析错误靠"等长改写后重试"修掉: 重试的结果干净,或者对 C 族保留了第一遍的全部符号,才会采用。覆盖的写法:
- C#:
#if分支,先按第一个分支解析,再按最后一个分支补回只在那里声明的符号;还有 .NET 10 的#:指令、serilog 里的[method]。 - VB:
Imports前面或中间有空行、注释。 - C/C++:多行
#define里的注释、__has_include、extern "C"守卫,以及一张已知宏的小表。 - TS:类型参数上的
in/out变体修饰符、export type * from。 - JSX:属性值里的裸
&。 - 15 个仓库的语料上,出错文件 667 → 466,剩下的大多是 C/C++ 里把宏当语法用的写法。
get_languages把 C 族的错误分成"宏/预处理导致,符号基本完整"和"其他语法错误"两类分别计数,并报告第一处错误的位置。
- C#:
- 语法错误脚注每次都附: 分页 A/B 的 223 条结果里出现了 108 次。现在每个会话只在第一次、结果为空时,或者命中的文件本身有错误时才附。回放估算从 8964 B 降到约 2.4 KB。
- 其它:
- 解析器池按语言串行,改为每个线程用自己的解析器。
- 查询阶段原来没有超时,现在和解析阶段共用 5 s 截止时间。
- 部分解析的结果现在也写进缓存:热重启 1.85 s → 0.68 s。
- 修了 Vue HTML 注释里有中文时整个索引 panic 的问题。
- 还修了 Ruby、Lua、shell、Dart/Kotlin、TS、Java record 等的若干符号提取问题。
5. 导入解析与图
- 修了 Go、Java/Kotlin、Rust、Python、TS、Swift、Lua、Dart 的若干导入解析问题。
- C# 项目按项目分桶:300 个项目 × 每项目 40 个文件,67 s → 0.9 s。VB 用同样的做法。
- 增量更新时,只重新探测读过被改文件的 resolver,其余的导入解析结果和 C# 命名空间边都复用。
- Java 回退探测和 Kotlin 原来会静默截断,现在上限提高,超限时写日志警告。
- Go 隐式接口: 图里原来没有"类型实现接口"的关系。现在按方法名和参数、返回值个数匹配,
find_symbol和find_references遇到接口时会列出实现方。
6. resolve_context
- 结果偏离任务、被泛词淹没:
- 原来排序和任务无关,loop_guard.rs 在三个不同任务里都排第 12;后来又用固定上限截断,小仓库只列出 4 到 9 个文件。
- 原来的 PageRank 加权按符号逐个累乘,弱匹配多的文件靠数量胜出,aiproxy 上精确命中的
relay.go被排到第 540 名。 - 现在每个文件只取命中强度的最大值;排序顺序是精确标识符、调用方和测试、Go 接口实现方、小 Go 包的同包文件;常见词按文档频率降权;小仓库第一页列出全部文件。
- 截断改成分页,页头写明总数和下一页的大致方向。
- #78 引入的回归: 符号被扫了两遍,warm 约 64 → 122 ms,在 #79 里改回单次遍历。
7. 文件监听与新鲜度
- 已收到但还没应用的变动被当成已处理: 加了水位线屏障,这批变动会补上。
- ignore 规则和全量扫描不一致: 补齐
.ignore、.git/info/exclude、全局和父目录的 ignore;这些规则文件变化时,强制做一次全量检查。 - 新建的文本类文件迟迟搜不到: 原来新建
new.md等 45 s 仍搜不到,现在会发布新索引。 - 全量扫描卡住监听: 扫描移到辅助线程,扫描期间到来的事件记录延迟从 47 ms 降到 7–11 ms。
- 轮询模式下查询频繁触发全量扫描: 新鲜度窗口改按实际轮询周期算,40 秒内触发次数从 5/79 降到 0/80。
- 冷构建不能取消: 现在会话关闭时会停下,中位数约 150 ms,不写缓存,也不发布。
- 其它:
- 后台重建期间的变更会回放。
- 索引库损坏时,把旧文件改名为
index.redb.bad-<时间戳>,然后自动重建。 - 缓存命中、mtime 和大小都没变时,不再读文件。
- inotify 改为逐个目录注册,并跳过被排除的目录。
8. 语言服务(只作兜底,不主动启动)
- 关闭时子进程残留: 现在杀整个进程组,Windows 上用 Job Object(未在真 Windows 上验证)。
- 大请求写入时卡住: stdin 改由独立线程写入。
- 首次自动下载 clangd 会把探测和
get_languages一起堵 8.5 s: 下载放到后台,期间探测 ≤ 0.22 ms 返回;下载失败后 10 分钟内不再重试。 - Vue 桥接重复启动: 现在只启动一次。版本未知时启动 1.30 s → 0.72 s;
get_diagnostics现在能报出.vue的模板编译错误。 - 崩溃后直接返回"未知": 现在重启一次并重发请求,3.03 s 内给出正确结果。
- 编译配置里是相对路径时,clangd 跳转定义返回空: 先把相对路径改写成绝对路径。修后能跳到
src/b.cpp:2,引用从 1 处找到 5 处。 - 其它:
- 新建文件会发
didOpen。 - C# 等到 Roslyn 发出
projectInitializationComplete再查询,不再固定等 3 s。 - 初始化时声明
staleRequestSupport。
- 新建文件会发
9. MCP 服务器与 hooks
- 服务器:
- 工具在
spawn_blocking里执行,带超时和取消。 - 参数错误返回 -32602。
- 索引读锁在运行工具前就释放。
- 索引构建期间,
search_code先用文本扫描给出结果。
- 工具在
- hooks:
- 硬禁用模式在静默窗口内不再放行。
- 遇到非法输入时放行,不再以 exit 2 拦截。
- 崩溃遗留的锁文件原来让每次 hook 多花约 1.1 s,现在改用
flock。 - 第三方工具名里带
astrolabe时,不再被误认成 Astrolabe。
10. 并行调用
- 问题: 7 个工具都没声明
readOnlyHint。Claude Code 靠这个标记决定 MCP 工具能否并行,所以同一轮发出的多次 Astrolabe 调用会排队串行执行。 - 解决: 7 个工具全部声明为只读,并在提示里鼓励同一轮并行发出相互独立的调用。用 claude CLI 实测,同一轮 3 次调用(每次人为加 2 s 延迟):6.0 s → 2.0 s。
二、新增了什么支持,如何支持
工具参数与输出
- 7 个工具: 新增可选参数
root,指定子仓库名或绝对路径。子仓库只有一个时会自动选中。 find_symbol:- 文件大纲:
query可以是文件路径(完整路径、./开头或路径后缀,目录写成dir/),也可以用*加path_filter。返回带L起-止行号的缩进大纲,1 到 3 ms。 - 成员展开:
depth控制展开几层成员。 - 零命中兜底: 查询有多个子词时,用子词倒排做 BM25 式排序。倒排在每个索引快照上第一次用到时建好,约 70–120 ms,之后每次 4–5 ms。
- 其它: 新增
kind=variable;每行带[kind in container]。
- 文件大纲:
resolve_context: 新增offset、path_filter、dedup。find_references: 新增offset。search_code: 新增[字符串]标签;aggregate超过 80 行时用offset翻页。
新的解析器与图关系
- C/C++
#include解析器: 依次查找当前目录、compile_commands.json、CMake 的 include 目录、仓库根目录,最后按路径结尾匹配。 - PHP composer 解析器: 支持 psr-4、psr-0、classmap、
files,以及require/include的字面路径。 - C/C++ 头文件与源文件配对: 只用于跳转定义和
resolve_context,不参与 PageRank。 - Go 隐式接口实现关系。
- csproj 的
Condition: 按三值逻辑求值。
语法支持
- C#:
#if后面分支里的声明、.NET 10#:指令。 - VB:
Imports头部有空行或注释。 - TS: 变体修饰符、
export type *、导入类型。 - C/C++:
__has_include、extern "C"守卫、多行#define里的注释、常见库宏(TEST_CASE会作为一个符号)。
跨进程缓存与报告
get_languages的诊断结果缓存在.astrolabe/syntax-diagnoses.json,重启后第一次调用从 688 ms 降到 3 ms。- 新增
.astrolabe/coverage-report.md。
- 安装: 新增
install.sh,下载 Release 归档并校验 sha256。 - npm 包名: 改为
@astrolabe-ai/astrolabe加平台包,并修好 npm 回退下载地址。npm 尚未发布。 - 文档: README 中英文重写;新增
llms.txt、FAQ,以及bench/评测脚本。 - 致谢链接: OpenVisio 改为
syntaxPriest/openvisio-oss。
清理
- 删除无调用方的
rewrite/、churn.rs、group_graph、neighborhood、trace_tree,连带去掉 4 个oxc_*依赖。
三、改动后的整体覆盖与性能
3.1 AI 端到端:本版对比第 4 批(同时起跑,配对 n=33)
11 个任务(H1 H2 H3 H6 H7 E1 E2 N1 N3 N6 N7),每个任务 3 轮。每一格两个版本同时起跑。模型 gemini-3.8-flash-high,运行在 bubblewrap 沙箱里,超时线 1500 s,0 超时。本轮花费 $57.20。
| 答对 | 平均花费 | 平均 token | 平均轮数 | 平均墙钟 | |
|---|---|---|---|---|---|
| A 不接 Astrolabe(更早时段的基线) | 31/33 | $1.22 | 779k | 22.8 | 208 s |
| B v0.13.0(更早时段的基线) | 33/33 | $1.05 | 751k | 21.7 | 160 s |
第 4 批 main d4f8a3f |
33/33 | $0.96 | 627k | 19.7 | 434 s |
v0.14.0(第 5 批 1c77cc1) |
33/33 | $0.78 | 495k | 17.5 | 381 s |
配对差(本版 − 第 4 批,bootstrap 95% 区间):
| 指标 | 配对差 | 95% 区间 | 结论 |
|---|---|---|---|
| 花费 | −$0.18 | [−0.32, −0.05] | 显著 |
| token | −132k | [−219k, −48k] | 显著 |
| 轮数 | −2.2 | [−4.2, −0.3] | 显著 |
| 墙钟 | −53 s | [−119, +10] | 跨零,不显著 |
分任务看(每个任务各 3 对):
- H1、H7、N7 显著更省。
- N6 反而贵了 $0.15,区间 [+0.06, +0.27]。
- 其余任务的区间跨零。
用法上的变化:
- 查完之后又整文件读取的次数从 11 次降到 7 次。
- 用到大纲的查询从 14 次增加到 20 次。
- 查空从 4 次降到 1 次。
3.2 更早的两轮(中间版本,供参考)
| 对比 | 结果 | 来源 |
|---|---|---|
| #79 后的 main vs v0.13.0 vs 不接(三组同时起跑,33 个三联) | 花费 A $1.22 / B $1.05 / C $0.94;C−A 墙钟 −72 s [−163, −7],花费 −$0.28 [−0.68, +0.01];C−B token −132k [−279k, −2k] | BENCH_013_VS_MAIN |
| #80 后的 main(只重跑 C 组,与基线不同时) | 32/33(唯一一次失败是评分器的字面匹配问题,答案本身是对的),$0.78,543k token;相对 #79:花费 −$0.16 [−0.25, −0.07] | AB_PAGING |
3.3 工具级前后对比
| 项目 | v0.13.0 / 修前 | v0.14.0 / 修后 |
|---|---|---|
tools/list 紧凑 JSON |
7,535 B | 7,185 B |
| 冷启动建索引(15 个仓库、9594 个文件) | 4.2–4.5 s | 2.8–3.0 s(#79 时测,之后没有重测) |
| 冷启动峰值内存 | 约 401 MB | 约 289 MB(同上) |
search_code 字面量,首次 / warm |
318–393 / 65 ms | 55–60 / 36–40 ms |
search_code count + skip_comments |
约 255 ms | 约 50–58 ms |
find_symbol 精确,首次 |
9 ms(v0.13.0)→ 68–156 ms(#79 回归) | 11–18 ms |
search_code 带 path_filter 零命中 |
107–134 ms | 7–40 ms |
| 热重启到可查询 | 1.4 s(v0.13.0)/ 1.17 s(第 4 批前) | 0.69 s |
get_languages 输出(15 个仓库) |
2470 B(v0.13.0)→ 8183 B(#79) | 约 2.7 KB,不随文件数增长 |
| 有语法错误的文件(同一语料) | 667(#79) | 466(#80 时测;#81 又修掉 C#/VB/TSX 的 4 个,未重新统计总数) |
| C# 报错文件(Newtonsoft/serilog/Polly) | 88 | 0 |
resolve_context 召回(36 条查询,第一页 / 全部页) |
v0.13.0 75% / — | 88% / 93% |
| 其中大仓库 14 条 | 44% / — | 73% / 83% |
| redis 跳转定义落到正确源文件 | 15/60 | 58/60 |
| 短 Shift_JIS / EUC-KR / 纯汉字 GBK 识别 | 0/308、0/2062、975/1000(#79 后、#81 前;v0.13.0 未测) | 288/308、1919/2062、986/1000 |
| 同轮 3 次调用(每次人为延迟 2 s) | 6.0 s 串行 | 2.0 s 并行 |
| 15 个语料召回门、5 个语料导入门 | — | 全部 100% |
3.4 局限,请如实看待
- 样本小: 只用了一个模型、一个代理,11 个任务都是我们自己设计的,每个任务只跑 3 对,区间很宽。
- 任务不能全部复现: 有些任务在私有仓库上,外人无法复现。
- 墙钟不能和基线比: A、B 两组的数据来自更早的时段,API 延迟不同(那时每轮约 6.7 s,后来约 13 s)。所以墙钟只能在本版和第 4 批这对同时起跑的组之间比,而这一项并不显著。
- 并行没测出来: 测试模型从不在同一轮发出多个调用,
readOnlyHint带来的并行收益在 A/B 里体现不出来,只有上面那个人为延迟的 CLI 实测。 - 分页没人用:
offset和path_filter在所有 A/B run 里都没被模型用过,分页设计的采用率测不到。 - 召回可能过拟合:
resolve_context召回的"需要的文件"取自 agent 的实际行为,不是人工标注,而且是在同一批 36 条查询上调参后测的。 - **Windo...
Astrolabe v0.13.0
Astrolabe v0.13.0:让模型敢信、会用 search_code,聚合统计一次到位
自 v0.12.0 以来的 PR:#72(README)、#73、#74,加本次版本提升 #75。工具目录仍为固定 7 个。
一、发现了哪些问题,如何解决
基于 v0.12.0 的 225 次实测分析(见文末“数据来源”),C 组的变差集中在少数任务,机制证据如下。
| # | 问题 | 表现 | 解决 |
|---|---|---|---|
| 1 | 模型不信任 search_code 的结果,反复重查 |
结果头写“文本启发式,需核对”,注释行也标 [使用],“另有 N 处”暗示不全;H1 里第一次已有完整结果仍多做 28 次调用;search_code 结果 12% 在 2 步内被 grep 复核 |
注释行标 [注释]、声明行标 [定义];去掉“需核对”;≤40 处命中全部展开;结果末尾一行可核验汇总(汇总:23/23 处命中 / M 个文件,无截断,未列全则写 已列 K/N 处,有截断 并给续查方式) |
| 2 | 换词/重复查询浪费调用 | 去重指针只说“已返回”,模型不知道内容,只好换词重查 | 去重指针带首次结果摘要(命中数、文件数、命中最多的文件、首处位置与所在签名) |
| 3 | 计数/聚合类任务模型根本不用 Astrolabe | N5、N8 在 v0.12.0 采用率 0/5,模型直接写 os.walk 脚本,每题 28 次左右调用;原有 aggregate=dir 只按叶子目录、无文件数、含注释命中,与题目口径错位 |
新增 `aggregate=top |
| 4 | 模型不调 resolve_context,其“下一步”提示无人看到 |
N5、N8 所有 run 都没调用它 | resolve_context 末尾给“下一步”提示;search_code 命中 >40 处时末尾也给(统计用 aggregate,改名用 dry_run_rename) |
| 5 | 批量改名没有轻量入口 | 15 个旧工具删除后,N4 类批量改名只能逐文件读改 | find_references 新增只读 dry_run_rename=新名,按文件列出每处替换位置(不写盘,不新增工具) |
| 6 | 文档混在“生产代码”分组里 | docs/、md/rst/txt 命中干扰计数 |
分组结果单列“文档”组,命中行标 [文档] |
| 7 | 依赖安全公告拦住 CI | zip 8.6 被 cargo-deny 拦截(RUSTSEC-2026-0367/0368/0369) |
zip 升到 9,无需改代码 |
二、新增了什么支持,如何支持
search_code
aggregate=top|dir|file:按顶层目录 / 叶子目录 / 文件汇总,每行给“N 处 / M 个文件”,并给总计(含顶层目录数、目录数)。count_only:只返回计数。skip_comments:跳过行注释、块注释(含 Rust 嵌套块注释)和字符串字面量里的命中;覆盖 Go/Rust/Python/JS/TS/Dart/Java/C 系/Kotlin/C#/Ruby/Shell 等;查询词本身含引号时字符串不跳过;md/rst/txt 文档命中一并跳过并报告跳过数量。可与aggregate、exclude、count_only组合。exclude:glob 列表;裸名(如vendor)匹配其下所有路径;支持_test.go、.min.js这类文件名后缀写法,以及*_test.go。- 角色标签:
[注释]、[定义]、[使用]、[文档];分组结果文档单列。 - 可核验汇总行、命中多时的“下一步”提示、去重摘要(见上)。
find_references:dry_run_rename=新名,只读,输出按文件的逐处替换清单(置信度非 exact 时提示核对)。
resolve_context:结果末尾一行“下一步”提示,按任务措辞指向 find_symbol / search_code / find_references。
依赖与文档:zip 8.6 → 9;README 工具表改为固定 7 个并更新 search_code 说明(#72)。
工具体积:tools/list 仍是 7 个工具;仓库里有体积上限测试(超过 7,600 B 即失败)。
三、改动后的整体覆盖与性能
3.1 tools/list 体积(本次实测,同一台机器,同一协议握手)
| 版本 | 工具数 | result 紧凑 JSON |
整行 JSON-RPC 响应 |
|---|---|---|---|
| v0.11.0 | 22 | 18,547 B | 18,581 B |
| v0.12.0 | 7 | 6,758 B | 6,792 B |
v0.13.0(main 提交 feb77d2 的二进制) |
7 | 7,584 B | 7,618 B |
v0.13.0 比 v0.12.0 多 826 B(+12%),换来聚合、skip_comments、dry_run_rename 等能力;比 v0.11.0 仍小 59%。(v0.12.0 发布说明里写的“7,400 字节”与此实测不符,见勘误。)
3.2 同时开跑的新 C vs A(40 对,最可靠的对比)
N5、N8、N4、E3b 各 10 对,每对新 C 与 A 在同一时刻起跑,模型 gemini-3.8-flash-high,同一代理,bubblewrap 沙箱,超时线 1500s。来源:REPORT_V013E.md。
| 指标 | A(不接 Astrolabe) | 新 C | 配对差(新 C − A,bootstrap 95% 区间) |
|---|---|---|---|
| 花费 均值 | $3.00 | $2.35 | −$0.65 [−0.98, −0.31] |
| 时间 均值 | 600s | 492s | −108s [−178, −37] |
| 轮数 均值 | 50.3 | 40.0 | −10.3 [−14.4, −6.2] |
| 每轮耗时 | 11.5s | 12.2s | +0.7s [−0.5, +2.0](无显著差异) |
| 答对(N5 宽口径) | 38/40 | 40/40 | +5.0 个百分点 [0, +12.5] |
| 答对(严格口径) | 38/40 | 31/40 | −17.5 个百分点 [−32.5, −2.5],差距全在 N5(1/10),见局限 |
| 超时 | 3 | 0 | |
| Astrolabe 采用率 | 0/40 | 37/40 |
分任务(花费差 / 时间差):N5 −$0.64 / −115s;N8 −$0.91 / −110s;N4 −$0.51 [−1.20, +0.21] / −164s;E3b −$0.52 [−1.22, +0.18] / −44s [−240, +146]。N4、E3b 的花费区间跨 0。剔除与 3 次短冻结(76、64、54 秒)重叠的 5 对后,合计花费差 −$0.70 [−1.04, −0.36],时间差 −137s,结论不变。
3.3 采用率前后(聚合类任务)
| 任务 | v0.12.0 C 采用率 | v0.13.0 新 C 采用率(同时开跑 10 次) |
|---|---|---|
| N5 | 0/5 | 10/10 |
| N8 | 0/5 | 10/10 |
| N4 | 3/5 | 7/10 |
| E3b | 4/5 | 10/10 |
3.4 全部 15 个任务的前后对比(非同时开跑,仅供参考)
各列来自不同时段的运行,每格 5 次,75 次/组;来源:REPORT_V012_BASELINE.md(A、B、基线 C,不计超时、按答案判对错)与 REPORT_V013C.md(改进版第一版 C,提交 a93b5bc)。
| 组 | 答对 | 超时 | 花费 中位 / 均值 | 时间 中位 / 均值 |
|---|---|---|---|---|
| A(v0.12.0 基线) | 73/75 | 1 | $1.27 / $1.47 | 274s / 366s |
| B(v0.11.0,22 工具) | 73/75 | 0 | $1.15 / $1.63 | 309s / 408s |
| C(v0.12.0 最终 7 工具版) | 70/75 | 5 | $1.32 / $1.71 | 319s / 418s |
C(a93b5bc,v0.13.0 的前一阶段) |
73/75 | 0 | $1.06 / $1.44 | 306s / 342s |
按任务与轮次号配对:a93b5bc 比 v0.12.0 的 C 便宜 $0.28 [−0.49, −0.07]、快 76s [−140, −17];相对 A 花费 −$0.04 [−0.21, +0.16],无显著差别。对照组 40 次里,该版本均值 $0.87,低于基线 A($0.99)与 B($1.18)。H1 由 4/5、$2.20、657s 变为 5/5、$1.35、328s,search_code 重查合计由 9 次降到 3 次,且没有网络搜索。
最终 v0.13.0 构建(feb77d2,含 #74)在这 15 个任务里只测了上面 4 个聚合/编辑类任务(见 3.2);另外 11 个任务没有对 feb77d2 重测。
3.5 局限(请按此理解上面的数字)
- 同时开跑对照只覆盖 N5、N8、N4、E3b 四个任务,各 10 对;这四个任务本来就是为聚合类选的,不代表全部场景。
- N4、E3b 没有使用
aggregate、skip_comments,它们的花费与轮数改善不能归功于新参数,更可能来自收口信号、去重摘要或噪声,区间也跨 0。 - 严格口径下新 C 只有 31/40,差距全在 N5(1/10):标准答案把 2 处注释里的调用算进去(15/44、321),
skip_comments排除注释后模型答 15/43、319。这是评分口径问题,不是返回了错误数据;宽口径 40/40。 - 其余任务(3.4 表)的对比都是非同时开跑数据:时段不同,代理每轮耗时在同一天内就会从约 5 秒爬到约 15 秒,时间差只能参考,成本与答对率相对可比。
- 3.4 里基线 A、B、C 未禁用网页搜索,且共用可写 go-build 缓存;后续版本禁用了 WebSearch/WebFetch、每个 run 用独立空缓存,这会偏向后者。
- 每格 5 到 10 次,置信区间宽;部分测试期间有机器冻结,已在报告中标注并做了剔除分析。
数据来源
/workspace/ab3/ 下:REPORT_V012_BASELINE.md、REPORT_V013C.md、REPORT_V013D.md、REPORT_V013E.md;tools/list 体积为发版时用本版与 v0.12.0 官方包二进制实测。
勘误:v0.12.0 发布说明中不准确的说法
旧版 Release 不改,仅在此更正:
- “Payload 缩减 70%”:实测 v0.11.0 的 18.5 KB 到 v0.12.0 的 6.8 KB,约缩减 63%。
- “faster 23.8%”是 C 对 B(v0.11.0)的对比;C 对 A(不接 Astrolabe)约 17.7%。
- 当时评测的 C 是 5 工具的 c5 版本,不是最终发布的 7 工具版。随后对最终 7 工具版的 225 次实测(
REPORT_V012_BASELINE.md,不计超时、按答案判对错,按 75 个同时开跑的三联组做 bootstrap 95% 区间)显示:- 对 v0.11.0(B):花费、时间、答对率均无显著差别(C−B 花费 +$0.080 [−0.164, +0.331],时间 +10s [−55, +79],答对率 −4.0 个百分点 [−10.7, +1.3],区间均含 0)。
- 对不接 Astrolabe(A):全部 75 组花费 C−A +$0.244 [+0.035, +0.467](排除 0);对照组 40 组花费 +$0.294 [+0.085, +0.540](排除 0);时间 +53s [−8, +119](含 0);答对率 C−A −4.0 个百分点 [−10.7, +2.7](含 0)。
- “
tools/list7,400 字节”:实测 v0.12.0 为 6,758 B(紧凑 JSON)/ 6,792 B(整行响应)。 - v0.12.0 说明里“旧版 v0.11.0 模式下耗时比完全不开启 Astrolabe 还慢约 8%”,用
REPORT_V012_BASELINE.md(全部 75 组)核对:- 方向成立,数值不成立。该说法对应 B(v0.11.0)对 A:时间中位 309s 对 274s、均值 408s 对 366s,按这两组数字算分别约慢 13%、11%,不是 8%(这是对报告数字的算术换算;报告没有给 B−A 的时间区间,所以不能说差别显著)。
- 若指最终 7 工具版 C 对 A:时间中位 319s 对 274s(约 +16%),均值 418s 对 366s(约 +14%);配对差 +53s [−8, +119],区间含 0,即不能认定 C 比 A 慢。
- 因此“约 8%”不应作为结论引用;可成立的只有“在该批数据里 B、C 的时间点估计都高于 A,但 C−A、C−B 的时间差均不显著”。
Astrolabe v0.12.0
Astrolabe 发版说明:告别冗余工具,回归高效搜索
发布基线:
wojiushirencai/Astrolabe(代码已全量合并至main分支)
涉及 PR:#66、#67、#68、#69、#70
本次更新概览
1. 今日完成的工作与 PR 汇总
本次发版围绕「搜索提效与 MCP 轻量化」,将今日相关的全部 5 个核心 PR 完整合入主干:
- PR #66:
search_code搜索命中自带上下文(自适应展开函数体),解决模型搜完还要反复Read的问题。 - PR #67:
search_code会话去重保留函数签名、宽查询预览排名命中、零命中提供相近名称纠错建议。 - PR #68:
search_code新增group智能分组(区分业务代码与测试,标注[断言]/[引用]/[加载]角色);修复去重计数;彻底下线旧版read-guard hook。 - PR #69:新增
deny_text_search防乱搜开关(可按进程/客户端配置硬拦截宿主 Grep/Glob 与终端文本搜索,默认关闭)。 - PR #70:MCP 工具目录精简固定为 7 个核心工具(彻底删除 15 个冗余入口),极致精简工具描述与参数 schema(Payload 缩减 70%),全量清理死代码与悬空引用。
2. 测试中直面的事实:Serena 臃肿模式的失效与反思
在开启本轮重构前,团队必须直面并承认一个经过大量测试验证的事实:早期仿照 Serena 建立的 22 个工具“大而全”模式,在实际落地中既没有提升执行效率,也没有降低 Token 使用。
真实评测数据显示:
- Token 反向膨胀:完整 22 个工具目录在每次交互时都会重发上万字节的定义和冗长说明,白白消耗大量 token,还分散了模型的决策注意力;
- 执行速度反向变慢:在旧版 v0.11.0 模式下,任务平均耗时甚至比完全不开启 Astrolabe 还要慢约 8%;
- 边缘工具形同摆设:调用链追踪、依赖图谱、重命名、热点分析等 15 个工具在模型实际编码中的调用率极低,不仅没带来收益,反而成了系统的维护包袱。
重构路径:通过 108 次全矩阵严格对比评测与数据归因,我们果断摒弃了盲目堆砌工具的思路,精简砍掉 15 个无用 MCP 工具,将精力集中在真正高频刚需的工具上进行深度打磨与功能重塑,最终才真正跑出了降本提效的正向收益。
核心功能变更详解
一、MCP 工具目录断舍离:固定 7 个核心工具
彻底废除复杂的动态裁剪机制,将工具目录硬性收敛为代码导航必需的 7 个标准工具:
| 保留工具 | 核心职责 |
|---|---|
resolve_context |
智能聚合任务最相关文件与骨架符号 |
search_code |
代码与字面量全量精确搜索(重点重写) |
find_symbol |
按名称或层级路径快速定位函数、类定义体 |
find_references |
LSP 驱动的跨文件精准引用与调用方检索 |
goto_definition |
LSP 驱动的代码定义精确跳转 |
get_diagnostics |
快速获取文件语法和类型诊断信息 |
get_languages |
语言分布、代码规模及环境服务就绪状态查询 |
彻底删除的 15 个入口(不留平替,全面清理死代码):
- 依赖与图谱类:
trace_calls、get_repo_skeleton、get_dependents、get_neighborhood、get_group_graph、get_hotspots、get_symbol_info - 辅助与记忆类:
plan_rename、apply_rename、read_memory、write_memory、delete_memory、list_memories、initial_instructions、ensure_language_server
工具目录前后规格对比
| 项目 | 旧版 v0.11.0 (Serena模式) | 精简测试版 (c5) | 本次最终 7 工具版 |
|---|---|---|---|
| tools/list 工具数 | 22 个 | 5 个 | 7 个 |
| tools/list 响应体积 | 18,582 字节 | 3,200 字节 | 7,400 字节(极致压缩后) |
| 目录管理机制 | 动态过滤与全量保留 | slim_catalog 裁剪 |
硬编码固定 7 个核心工具 |
二、重点武器打磨:search_code 体验全面重写
留下的核心工具必须做到极致。针对模型以往“搜到某一行却看不懂整段函数,不得不二次 Read”的痛点,search_code 做了重大升级:
- 命中自带函数体:
- 少量命中(≤5 处)时,直接返回命中点所在的完整函数体代码;
- 模型一次查询即掌握上下文,不再反复发起
Read。
- 智能分组与角色标签(
group参数):- 命中跨越多个文件时自动按文件分组展示;
- 业务代码优先、测试代码置后,每处匹配自动打上角色标签(
[定义]、[引用]、[断言]、[加载])。
- 会话级智能去重:
- 同一轮对话中已经输出过的函数体,再次命中时自动折叠为指针引用(附带函数签名与行号),坚决不重复浪费 token。
- 大结果汇总与模糊推荐:
- 命中数超过阈值(>60 处)时不再直接刷屏,改为按目录汇总命中计数并推荐
path_filter过滤路径; - 搜索 0 命中时,根据字面相似度自动推荐最接近的符号名称。
- 命中数超过阈值(>60 处)时不再直接刷屏,改为按目录汇总命中计数并推荐
三、行为防护:新增 deny_text_search 硬拦截开关
- 背景:防止模型放着高性能 AST 索引不用,偷懒在终端频繁调用 grep 扫盘。
- 规则:
- 开启后,首次出现宿主
Grep、Glob或终端grep/rg/ag/git grep命令即被硬拦截,并直接提示改用 Astrolabe 工具; ls、cargo test、已知文件路径的cat/Read始终放行。
- 开启后,首次出现宿主
- 配置方式:默认保持关闭(维持原有的连续 3 次温和提示)。如需开启,可通过命令行
--deny-text-search、环境变量ASTROLABE_DENY_TEXT_SEARCH=true或在配置文件中指定。
评测数据与测试表格
评测采用严苛的沙箱隔离环境,使用 gemini-3.8-flash-high 模型,针对 12 个真实典型工程任务(8 个对照类任务 + 4 个发现型任务),每组 36 次独立运行,共计 108 次全矩阵同时间并发对比:
- A 组:完全不开 Astrolabe(基线组)
- B 组:旧版 v0.11.0(22 工具完整 Serena 模式)
- C 组:本次精简版(核心工具重写 + 工具目录大瘦身)
1. 全局测试汇总表(12 个任务 × 3 轮 = 36 次/组)
| 组别 | 运行数 | 答对数 | 超时 | 费用中位数 | 费用合计 | 耗时中位数 | 耗时合计 | Astrolabe 调用 | Bash grep 调用 | Read 调用 |
|---|---|---|---|---|---|---|---|---|---|---|
| A (完全不开) | 36 | 34 | 0 | $1.22 | $56.58 | 178s | 9577s | 0 | 272 | 376 |
| B (旧版 v0.11.0) | 36 | 36 | 0 | $1.20 | $58.26 | 196s | 9238s | 202 | 118 | 347 |
| C (本次精简版) | 36 | 36 | 0 | $0.99 | $46.82 | 192s | 8129s | 154 | 96 | 305 |
注:排除高 CPU 编译干扰项 E3b 后的耗时对比更具代表性:
- 耗时中位数:A 组 175s、B 组 189s(反向变慢)、C 组 144s(提速 23.8%)。
- 费用合计:A 组 $45.56、B 组 $49.52、C 组 $40.33(显著最省)。
2. 对照任务与发现型任务分项对比表
对照任务(代码编辑与精准符号定位:H1、H2、H3、H6、H7、E1、E2、E3b,共 24 次/组)
| 组别 | 答对率 | 费用中位数 | 费用合计 | 耗时中位数 | Astrolabe 调用 | Bash grep 调用 | Read 调用 |
|---|---|---|---|---|---|---|---|
| A (完全不开) | 23/24 | $0.61 | $24.46 | 95s | 0 | 130 | 210 |
| B (旧版 v0.11.0) | 24/24 | $0.78 | $24.79 | 135s | 175 | 3 | 181 |
| C (本次精简版) | 24/24 | $0.51 | $18.44 | 101s | 117 | 5 | 155 |
发现型任务(复杂实现排查、同名干扰项、宽查询:N1、N3、N4、N5,共 12 次/组)
| 组别 | 答对率 | 费用中位数 | 费用合计 | 耗时中位数 | 耗时合计 | Astrolabe 调用 | Bash grep 调用 | Read 调用 |
|---|---|---|---|---|---|---|---|---|
| A (完全不开) | 11/12 | $2.83 | $32.12 | 425s | 5620s | 0 | 142 | 166 |
| B (旧版 v0.11.0) | 12/12 | $2.56 | $33.47 | 412s | 5072s | 27 | 115 | 166 |
| C (本次精简版) | 12/12 | $2.26 | $28.38 | 323s | 4620s | 37 | 91 | 150 |
3. 各单项任务详细表现表(答对数 / 费用中位数 / 耗时中位数)
| 任务类型 | 任务代号 | A 组 (完全不开) | B 组 (旧版 v0.11.0) | C 组 (本次精简版) | 表现评估 |
|---|---|---|---|---|---|
| 精准定位 | H1 | 3/3 $1.22 186s | 3/3 $1.24 203s | 3/3 $0.85 144s | 费用降 30%,耗时明显缩短 |
| 精准定位 | H2 | 2/3 $0.56 86s | 3/3 $0.78 106s | 3/3 $0.68 102s | 修复 A 组偶发判断失误 |
| 精准定位 | H3 | 3/3 $0.66 109s | 3/3 $0.79 143s | 3/3 $0.50 91s | 费用最省、速度最快 |
| 精准定位 | H6 | 3/3 $0.30 52s | 3/3 $0.32 52s | 3/3 $0.25 45s | 表现最优 |
| 精准定位 | H7 | 3/3 $0.19 30s | 3/3 $0.29 40s | 3/3 $0.14 15s | 耗时减半 |
| 代码重构 | E1 | 3/3 $1.00 192s | 3/3 $1.11 185s | 3/3 $1.33 241s | 复杂度较高任务,三者基本持平 |
| 代码重构 | E2 | 3/3 $0.43 75s | 3/3 $0.54 85s | 3/3 $0.39 94s | 费用最优 |
| 代码重构 | E3b | 3/3 $3.15 471s | 3/3 $2.76 458s | 3/3 $1.98 446s | 复杂重构费用直降 37% |
| 干扰排查 | N1 | 2/3 $3.18 738s | 3/3 $3.07 580s | 3/3 $1.38 274s | 耗时从 12 分钟压缩至 4 分半 |
| 接口发现 | N3 | 3/3 $2.17 308s | 3/3 $1.98 295s | 3/3 $2.29 468s | 复杂语义发现,消耗相对稳定 |
| 跨语言搜索 | N4 | 3/3 $3.95 581s | 3/3 $3.41 527s | 3/3 $3.01 520s | 费用与耗时全面领先 |
| 大规模命中 | N5 | 3/3 $1.72 186s | 3/3 $2.36 235s | 3/3 $2.05 289s | 自动分组避免大截断页 |
升级注意与后续计划
- 废弃入口说明:
- 移除的 15 个工具不设向下兼容过渡层,相关底层代码与测试桩已彻底清理;
- 自定义配置中若有
excluded_tools显式排除了已删工具,系统将自动忽略,不影响启动。
- 搜索能力当前边界:
search_code目前暂未提供类似--exclude的递归目录黑名单,超大非代码目录检索建议配合resolve_context先行约束边界。
- 版本发版准备:
- 当前
main分支代码已全量就绪并通过全套 CI 自动化测试,随时可进行版本打 tag 发版。
- 当前
Astrolabe v0.11.0
[0.11.0] - 2026-10-13
1、具体是什么问题?如何解决的。
- Kotlin 首次查询要等约 20 秒才有回答
- 原因:第一次查询要先同步启动 kotlin-lsp(JVM 启动约 5 秒),再等它就绪,最长 15 秒。
- 解决:kotlin-lsp 改到后台线程启动,查询不再等它;首次等待上限从 15 秒降到 2 秒,可用
ASTROLABE_KOTLIN_READY_WAIT_MS调整。启动和导入期间的查询直接给出 tree-sitter 语法结果,标为 Syntactic,并注明“仍在导入”。(#63)
- Kotlin 空结果被标成 Exact;每次重启都要重跑 Gradle 同步
- 解决:空结果改为回退到语法结果,不再标 Exact。kotlin-lsp 按仓库缓存系统目录,并复用导出的项目模型;构建文件没变时,重启后约 12 秒就有精确结果。(#62)
- Linux 和 macOS 上 Swift/ObjC 的 find_references 返回空
- 解决:sourcekit-lsp 开启后台索引;clangd 用合成的编译数据库;仍为空时回退到 tree-sitter,并标明 Syntactic。(#59)
- Volar 3.x 下 Vue 没有语言服务结果
- zsh 顶层变量被语法错误吞掉;一次定义多个名字的函数定位不准
- 导入解析漏项
- Kotlin 多行
@file注解、Shell 的\.source、Rubyrequire_relative以及 spec/test 加载路径,这些情况的导入之前会漏掉,现已修复。(#55)
- Kotlin 多行
- 客户端断开或收到 SIGTERM 时,语言服务器没有被正常关闭
- 原因:Astrolabe 收到断开后 0.03 秒就退出,语言服务器的
shutdown/exit根本没发出去,语言服务器只能靠 stdin 断开自己退出,没人看管(gopls 会多活约 3 秒)。原来的 SIGINT 路径还会卡在读 stdin 上。 - 解决:退出前先把所有语言服务器并行关闭(
shutdown最多等 2 秒,再发exit),整个过程最多 12 秒(ASTROLABE_EXIT_TIMEOUT_MS)。kotlin-lsp 退出后保存索引需要 4.7–5.8 秒,现在给它 10 秒宽限(ASTROLABE_KOTLIN_EXIT_GRACE_MS),其他语言 500 ms。SIGTERM 也走这套流程。(#64)
- 原因:Astrolabe 收到断开后 0.03 秒就退出,语言服务器的
- 其他
2、新增了哪些支持(sql、xml、toml等)?如何支持的
- 8 门新语言的符号索引:Swift、ObjC、ObjC++、Vue、Kotlin、Bash/Shell(含 zsh、.bats)、Ruby、Lua。
- 这些语言的语言服务器:sourcekit-lsp、clangd、Volar(2.x/3.x)、kotlin-lsp(另有 kotlin-language-server 作为兜底)。(#58、#59、#61、#62)
- 召回门:CI 召回门从 7 个语料扩到 15 个,每门新语言再加一个大型真实仓库(ObjC++ 也有一个):
- Alamofire、SDWebImage、MMKV、kotlinx.coroutines、rubocop、nvm、lua-language-server、element-plus。
- 新增的 8 个语料在本机共跑约 23 秒。(#63)
3、调整后整体的覆盖和性能情况
| 项目 | 改动前 | 改动后 |
|---|---|---|
| Kotlin 首次启动:首个可用回答(okhttp) | 249 s(v0.10.2);20.7 s(#62 之后) | 0.2 s(Syntactic) |
| Kotlin 重启:首个可用回答 | 13.3 s | 0.2 s |
| Kotlin 重启:首个 Exact | 14.3 s | 10.8 s |
| Kotlin 首次启动:首个 Exact(含 Gradle 同步) | 343 s(#62 之前);426 s(#62 之后) | 412–450 s(受 Gradle 同步限制,基本不变) |
| 召回门语料数 | 7 | 15 |
| 客户端断开:go/gin 退出 | 0.03 s(gopls 自己拖到 2.96 s 才退) | 1.07 s(先关好 gopls,不留残余进程) |
| 客户端断开:okhttp(kotlin-lsp)退出 | 0.03 s(JVM 0.48 s 后才自己退) | 0.51 s(先关好 kotlin-lsp) |
| 已有语言冷索引:zod / Polly / fmt | 5.28 s / 8.29 s / 1.77 s | 0.76 s / 1.27 s / 0.76 s |
| Swift 召回(Alamofire / argument-parser) | 0% / 0% | 100% / 100% |
| ObjC 召回(SDWebImage / AFNetworking) | 1.11% / 0.41% | 100% / 100% |
| ObjC++ 召回(MMKV) | 2.62% | 100% |
| Kotlin 召回(kotlinx.coroutines / okhttp) | 0% / 0% | 100% / 100% |
| Ruby 召回(rubocop / sinatra) | 0% / 0% | 100% / 100% |
| Shell 召回(nvm / bats-core) | 0% / 0% | 100% / 100% |
| Lua 召回(lua-language-server / nvim-lspconfig) | 0% / 0% | 100% / 100% |
| Vue 召回(element-plus / vue-vben-admin) | 94.69% / 90.09% | 99.96% / 99.98% |
| find_symbol 200 名抽样(全部新语料) | 0–183/200 | 200/200 |
| 冷索引:element-plus / rubocop | 19.3 s / 23.6 s | 1.0 s / 1.8 s |
已知问题
- okhttp 这类“多平台 + Android 库”模块:即使装了 Android SDK,kotlin-lsp 的导入也不会带上 commonJvmAndroid 源码目录,所以这类模块的跳转定义和找引用只能给 Syntactic 结果;悬停仍是 Exact。普通 Android 模块装了 SDK 之后可以拿到源码目录。
- element-plus(Vue)召回率 99.96%:漏掉的 2 条是
docs/examples/table-v2里的局部变量i。
What's Changed
- feat: Kotlin/Shell/Ruby/Lua support + Swift/ObjC/Vue symbol extraction + query cache by @wojiushirencai in #51
- chore(ci,docs): 七门新语言声明召回门禁 + 新语言文档 by @wojiushirencai in #52
- fix(vue): 相同内容的 SFC 共享解析缓存导致组件名错乱 by @wojiushirencai in #54
- fix(objc): ObjC++ 中 C++ 覆盖 + RN/SD/AF 剩余 ObjC 漏抽 by @wojiushirencai in #56
- fix(lsp): 新语言语言服务器实测修复(TS tsserver、ruby-lsp、bash、Vue) by @wojiushirencai in #58
- feat: find_symbol 分页 + 无后缀文件 shebang 探测缓存 by @wojiushirencai in #53
- fix(imports): 新语言 import 解析实测与修复(Kotlin/Shell/Ruby) by @wojiushirencai in #55
- fix(shell,ruby): zsh/.bats 抽取 + Ruby heredoc/%字面量分类 by @wojiushirencai in #57
- fix(shell): 恢复被语法错误吞掉的 zsh 顶层变量;多名函数逐行定位 by @wojiushirencai in #60
- fix(lsp): Swift/ObjC find_references on Linux and macOS (sourcekit-lsp background indexing, clangd compile db, tree-sitter fallback) by @wojiushirencai in #59
- feat(vue): Volar 3.x 桥接 typescript-language-server + @vue/typescript-plugin by @wojiushirencai in #61
- fix(kotlin): kotlin-lsp 新启动器发现、按仓库缓存与项目模型复用、空结果回退不再标 Exact by @wojiushirencai in #62
- perf(kotlin): 后台启动 kotlin-lsp,首个回答 20.7s→0.2s;召回门扩到 15 个语料 by @wojiushirencai in #63
- fix(lsp): 客户端断开 / SIGTERM 时正常关闭语言服务器(kotlin-lsp 留出写入索引的时间,总耗时有上限) by @wojiushirencai in #64
- chore(release): v0.11.0 by @wojiushirencai in #65
Full Changelog: v0.10.2...v0.11.0
Astrolabe v0.10.2
1、具体是什么问题?如何解决的。
- Dart 构造函数和多类声明没有被抽取(#47)。问题:无名构造函数、const / factory / 重定向构造函数没有挂在类名下(provider 漏掉 8 个定义);字段、顶层变量/常量、枚举值、extension、extension type、typedef、运算符,以及
class D = B with M;都没有被抽取。解决:补充 Dart 的 symbols.scm 查询;构造函数按类名记录(命名构造函数同时按自身名字记录);运算符命名为operator +的形式。 - TS 成员没有被抽取(#48)。问题:类型别名里的对象成员(
type X = { a: T })、enum 成员,以及以字符串/数字命名的方法、字段、接口成员都没有被抽取。解决:补充 typescript 的 symbols.scm 规则,并加入ts_inline_object_member过滤(断言和参数里的内联匿名类型字面量有意不收录)。 - search_code 没有分页(#49)。问题:命中数很多时只能截断(zod 第一页只覆盖 57.9%)。解决:新增
offset参数;截断时正文给出总数、本页范围(“第 X–Y 条,共 N 条”)和next_offset;结构化输出带 total / offset / next_offset;结果顺序稳定(先代码命中,再[text-only]命中);loop guard 的签名包含 offset,翻页不会被误拦。
2、新增了哪些支持(sql、xml、toml等)?如何支持的
本版本没有新增可搜索的文件类型(sql、xml、toml 等文本文件的搜索已在 v0.10.1 支持),新增的是符号抽取和分页:
- Dart(#47,通过 symbols.scm 查询实现):构造函数挂在类名下,字段、顶层变量/常量、枚举值、extension / extension type、typedef、运算符、mixin application 类(
class D = B with M;)。 - TypeScript(#48,通过 symbols.scm 规则加
ts_inline_object_member过滤实现):类型别名成员、enum 成员、以字符串/数字命名的成员。 - search_code 分页(#49):通过
offset参数和返回的next_offset翻页。
3、调整后整体的覆盖和性能情况
基线为 v0.10.1。
全部声明的抽取召回率
| 仓库 | 声明数 | 调整前 | 调整后 |
|---|---|---|---|
| provider (Dart) | 1,400 | 63.9% | 100% |
| riverpod (Dart) | 30,445 | 69.4% | 100% |
| dart-lang/http (Dart) | 10,099 | 47.2% | 100% |
| zod (TS) | 7,783 | 88.94% | 99.74% |
| trpc (TS) | 10,427 | 68.22% | 99.88% |
通过 MCP 调用 find_symbol
| 样本 | 调整前 | 调整后 |
|---|---|---|
| provider 209 个定义 | 96% | 100% |
| zod 抽样 50 个符号(141 处定义) | 119/141 (84%) | 126/141 (89%) |
zod 剩下的 15 处是内联匿名类型成员和基准写法引起的,是有意不收录的。
TS 分类召回率(zod / trpc)
| 类别 | 调整前 | 调整后 |
|---|---|---|
| 类型别名成员 | 0.9% / 0% | 100% / 100% |
| enum 成员 | 1% / 0% | 100% / 100% |
| 对象字面量方法(zod) | 73.7% | 100% |
zod search_code(budget 32000)
| 调整前(只有第一页) | 调整后(按 next_offset 翻页) | |
|---|---|---|
| 命中覆盖 | 7,459/12,879 (57.9%) | 12,874/12,879 (99.96%) |
| 页数 | 1 | 最多 4 页 |
| loop guard 拦截 | — | 0 次 |
升级提示:抽取器版本变了,升级后首次启动会重新解析所有文件。Dart 查询类名时,结果里现在也会有构造函数。
已知仍存在的问题:打包产物 build/index.cjs 中有 5 行没有搜到(正在排查);find_symbol 还不支持分页;TS 的 find_references 在测试机上不可用。
Astrolabe v0.10.1
这是一次重大更新,建议所有使用者都升级到最新版本
1、具体是什么问题?如何解决的。
- loop guard 误拦正常重查(#36)。问题:调大
budget_tokens重试、改代码后复查、“索引构建中”的响应都被算作重复查询而被拦。解决:签名包含全部参数和索引代数(索引变化即重置),只统计成功执行的查询;被拦时返回上一次缓存的结果并标注blocked: true。 - LSP 文档不同步、结果误标、请求无超时(#37)。问题:语言服务器一直用首次打开时的内容,收不到后续编辑;服务器自身建索引期间的部分结果被标为 Exact;单次请求没有超时(pyright 卡住超过 120 秒)。解决:每个文档记录版本号和哈希,发送 didChange / didClose / didChangeWatchedFiles;服务器建索引期间降级标注;单次查询 30 秒超时(
ASTROLABE_LSP_QUERY_TIMEOUT),find_references 超时后回退到文本搜索。 - find_symbol 排序与匹配问题(#38)。问题:按路径排序导致真正的定义被截断;
Type.method/A::b/pkg.Func返回 0;kind被忽略;Java/C# 锚点落在@注解行;path_filter 写错时静默返回 0。解决:按 精确 > 前缀 > 子串 排序;支持./::分隔,并加入 Go receiver / impl 启发式;遵守kind;锚点落在名字所在行;path_filter 无匹配时报错并给出最接近的路径。工具描述补充 C#/VB。 - TS
outDir: "."排除整个源码树(#39)。问题:zod 的 332 个文件被排除,却报告 “unindexed: none”。解决:只有当 outDir 是不含源码的独立目录时才排除,被排除的源码会如实报告。 - 索引覆盖遗漏(#40)。问题:任意深度的 build/target/dist/obj 都被排除;非 UTF-8(GBK/Latin-1)文件被跳过;解析失败或文件过大时无提示;大写扩展名以及 .inl .tpp .cu .cppm .ixx 被忽略;.h 一律按 C 处理;Dart 命名构造函数、Go 局部常量缺失。解决:只在仓库/项目根目录排除构建目录;用 chardetng + encoding_rs 识别编码;给出 “N files not indexed” 提示;扩展名大小写不敏感并新增 C++ 扩展名;.h 按内容识别是否为 C++;补齐上述符号。
- find_references 锚点错误(#41)。问题:锚点取第一次文本出现的位置(注释/文档/import),Go 50 个符号中 16 个报错,Rust 返回空结果却标为 Exact(LspPool 32 处引用只找到 1 处)。解决:用 tree-sitter 定位到声明处,合并重载(最多 8 个),支持 line/character 参数,空结果或落在注释上时标为 Unknown,返回结构化的 returned/omitted/truncated。
- search_code 只搜代码文件、空闲仓库频繁提示索引过期(#42)。问题:search_code 只搜已索引的代码文件;未改动的仓库也一直提示 “index may be stale”(Linux 监听把 read/close 事件算作变更,导致反复重建,gson 30 秒约 7.4 万个事件)。解决:对所有扫描到的文本文件做 text-only 搜索(见第 2 节),
[text-only]命中排在代码命中之后,新增case_sensitive参数,跳过 lock 文件;丢弃只读类监听事件;barrier 2 秒超时时给出提示。 - C# 符号抽取遗漏(#43)。问题:漏掉字段、枚举成员、事件、索引器、运算符、转换运算符、析构函数、委托、嵌套局部函数。解决:补充 symbols.scm 查询和命名规则。
- 升级后沿用旧的符号抽取结果(#44)。问题:单文件符号缓存只按语言和文件内容做键,升级后仍用旧版抽取结果,#38/#40/#43 的修复对老仓库不生效。解决:缓存键加入抽取器版本和程序版本。
- 测试补充(#45):按名字定位的 find_references 在服务器卡住时回退到文本搜索。
2、新增了哪些支持(sql、xml、toml等)?如何支持的
- search_code 支持 text-only 搜索(#42):未进入代码索引的文本文件也能被搜到,包括 .md .sql .yaml/.yml .json .xml .toml .ini .cfg .conf .properties .env* .gradle(.kts) .kt Dockerfile Makefile .sh .proto .graphql .csv 以及模板文件等。
- 实现方式:基于 ripgrep 的 grep-searcher / grep-regex 库,用 rayon 按文件并行搜索。
- 规则:遵守 .gitignore;单文件上限 1.5MB;含 NUL 字节视为二进制并跳过;跳过 lock 文件(Cargo.lock、package-lock.json 等);UTF-16 BOM 文件转码,GBK 文件有损解码。
- 其他新增:非 UTF-8 源码可以建索引(#40);新增 C++ 扩展名 .inl .tpp .cu .cppm .ixx(#40);C# 成员类型(#43);Dart 命名构造函数、Go 局部常量(#40)。
3、调整后整体的覆盖和性能情况
基线为 v0.10.0(7094e4c),调整后为 main e53f3e6 及之后。
find_symbol 召回率(budget 2500 tokens,%)
| 仓库 | 调整前 | 调整后 |
|---|---|---|
| Polly (C#) | 99 | 100 |
| astrolabe (Rust) | 100 | 100 |
| cobra (Go) | 96 | 100 |
| express (JS) | 100 | 100 |
| gson (Java) | 98 | 100 |
| provider (Dart) | 96 | 96 |
| requests (Python) | 100 | 100 |
| zod (TS) | 13 | 84 |
search_code 召回率(budget 32000,%)
| 仓库 | 调整前 | 调整后 |
|---|---|---|
| astrolabe | 85 | 85 |
| zod(字面) | 10 | 58 |
| zod(regex) | 10 | 97 |
| 其他仓库 | 100 | 100 |
regex 模式其余仓库不变(Go 的 51 是大小写敏感与基线大小写不敏感的口径差异)。zod 静默截断:39 → 1。
find_references(50 个符号)
| 仓库 | 调整前 | 调整后 |
|---|---|---|
| cobra (Go) | 34 成功 / 16 报错 | 50 成功 / 0 报错 |
| astrolabe (Rust) | 44 成功 / 6 空结果却标 Exact | 50 成功 / 0 空结果 |
| LspPool 引用数(rg 为 32) | 1 | 30 |
C# 声明抽取召回率(%)
| 仓库 | 调整前 | 调整后 |
|---|---|---|
| Polly | 88.3 | 100.0 |
| Newtonsoft.Json | 85.7 | 99.9 |
| Serilog | 84.9 | 99.9 |
| Java gson / jackson-core / spring-petclinic | 100 | 100 |
通过 MCP 调用 find_symbol 的同名冲突样本(Polly、Newtonsoft、gson、jackson-core 各 25 个):100/100 定义都在结果中。
未改动仓库的“索引可能过期”提示
| 仓库 | 调整前 | 调整后 |
|---|---|---|
| gson | 每次查询 123 个疑似变更 | 0 |
| Polly | 约 975 | 0 |
| requests | 反复重建 | 0 |
性能(text-only 搜索,9,510 个 .md 文件,91MB,8 核,5 次取中位数)
| 查询 | 调整前 (ms) | 调整后 (ms) |
|---|---|---|
| xyznotfound | 94 | 29 |
| JsonReader | 117 | 36 |
| version | 101 | 29 |
| = | 181 | 149 |
| the | 140 | 91 |
同目录下 rg 命令行耗时 24–46ms。LSP 单次查询超时从 300 秒降到 30 秒。
升级提示:由于 #44,升级后首次启动会重新解析所有文件,耗时比平时长。
已知仍存在的问题:zod 仍有 22 个定义未召回;Dart provider 召回率 96%;TS 的 find_references 在测试机上为 0%(TS 服务器环境问题);非常常见的名字会被 budget 截断,请用 path_filter 或调大 budget_tokens。
Astrolabe v0.10.0
[0.10.0] - 2026-10-11
Added
-
search_code/find_symbol服务端防循环守卫。 hook 不统计 Astrolabe 自身工具,曾出现子 Agent 48 分钟内调用search_code9,055 次、在 109 条查询间轮换的死循环。现在 MCP 服务端按连接记录查询签名(工具 + 查询串 + path_filter + 选项):同一签名在 10 分钟窗口内执行超过 3 次后不再重新搜索,改为返回“已执行过、请使用先前结果或换方法”的短提示;窗口内搜索总次数超过 300 次时在结果末尾追加循环警告。阈值可用ASTROLABE_SEARCH_REPEAT_MAX/ASTROLABE_SEARCH_REPEAT_WINDOW_SECS/ASTROLABE_SEARCH_RATE_MAX/ASTROLABE_SEARCH_RATE_WINDOW_SECS调整,0关闭对应检查(#34)。 -
astrolabe hooks gc与 remind 内置过期会话 GC。 删除~/.astrolabe/hook_data/下超过 24 小时(ASTROLABE_HOOK_GC_HOURS可调;0关闭 remind 中的机会式 GC)未活动的会话目录(以目录树内文件最新 mtime 判定,当前会话永不删除)。hooks remind借助~/.astrolabe/hook_gc.stamp节流,每小时至多扫描一次。用于无SessionEnd事件的宿主(ZCode)与会话异常退出的兜底清理(#31)。
Fixed
-
ZCode 接入文档纠正(Refs #30)。ZCode 仅支持 7 个 hook 事件且严格校验,配置
SessionEnd会导致整段 hooks 加载失败;README 与docs/CLIENT_ALIGNMENT.md改为给出~/.zcode/cli/config.json(Windows%USERPROFILE%\.zcode\cli\config.json)的正确结构(enabled: true+events包装层,PreToolUse调用astrolabe hooks remind --client=zcode),明确不配SessionEnd、不在Stop上挂 cleanup,清理交由过期 GC(#31)。 -
冷启动索引竞态导致的偶发测试失败(#32)。文件监听已启动但首次全量扫描未完成的空档内新建的文件可能永远不进索引(
cold_build_edit_is_visible_at_ready偶发失败);现在先等待首次扫描完成(最多 60 秒)再开始建索引。
Removed
- README 友情链接一节(#33):移除 linux.do 友情链接(该节仅此一条,标题一并删除)。
What's Changed
- fix(hooks): ZCode 接入纠正 + 过期会话 GC by @wojiushirencai in #31
- fix(mcp): 冷构建等待 watcher 基线,修复 cold_build_edit_is_visible_at_ready 偶发失败 by @wojiushirencai in #32
- docs: 移除 README 中的 LINUX DO 友情链接 by @wojiushirencai in #33
- feat(mcp): search_code/find_symbol 服务端防循环守卫 by @wojiushirencai in #34
- chore(release): v0.10.0 by @wojiushirencai in #35
Full Changelog: v0.9.0...v0.10.0