关于 sonde
sonde 是面向编码 Agent 的本地代码上下文引擎,将 TypeScript、Python 或 Swift 仓库索引为 SQLite 符号图。它通过 MCP 工具提供调用关系、依赖图和变更影响分析,并按令牌预算限制响应。
主要功能
- 01索引 TypeScript、Python 和 Swift
- 02构建 SQLite 符号图
- 03查找符号及图关系
- 04分析变更影响范围
- 05生成架构文档
适用场景
- 01查找符号的调用方
- 02评估代码变更影响
- 03定位相关测试
- 04生成并检查架构文档
MCP 相关
sonde 提供 find_symbols、query_graph 和 get_impact_radius 三个 MCP 工具,供 Agent 查询仓库符号、关系和影响范围。sonde init 可为项目索引代码并注册 MCP 服务。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
Sonde
面向 AI 编码 Agent 的本地代码上下文引擎。Sonde 会将 TypeScript、
Python 或 Swift 仓库索引为 SQLite 中的符号级图,并提供三个 MCP 工具——
find_symbols、query_graph 和 get_impact_radius——让
Agent 通过一次调用而不是反复搜索,就能回答谁调用了这个符号、修改后会破坏什么以及哪些
测试与它相关。
基准测试实际展示了什么
我们并不声称“能找到 grep 找不到的内容”。能力出色的 Agent 搜索 循环也能找到相同的结构证据——我们在一个真实的 19,409 行仓库上进行了测试,每项任务的召回率 都达到 1.000。
我们的主张是:在预算范围内,以极低成本获得相同答案:
| 在真实的生产 TypeScript 项目中 | Sonde | Agent 搜索 |
|---|---|---|
| 结构任务的召回率 | 1.00 | 1.00 |
| 工具调用次数 | 1.0 | 8.0 |
| 上下文 token 数 | 1,262 | 3,621 |
| 延迟 | 263 ms | 38,602 ms |
| 超出 token 预算的运行次数 | 0 of 6 | 3 of 6 |
Sonde 在每项结构任务上的答案都与基线相同,同时使用的上下文约少 3 倍、 调用次数少 8 倍,耗时少约 147 倍;并且它绝不会超出调用方的 token 预算,因为打包器的设计 就是按预算截断。这正是我们提供的取舍。
Sonde 的不足。 对于没有共同词汇的行为查询——“重试退避在哪里决定?”——得分为 0.00。我们构建并测试了本地语义检索,但它并不能解决这个问题(见设计文档 §2.2);因此我们不声称 具备这项能力。如果你的问题大多属于这种类型,那么目前 Agent 搜索循环是更好的工具。
数据可复现:npm run bench:fixture && npm run bench:large。
完整结果见 BENCHMARK-LARGE.md 和
BENCHMARK.md。
安装和运行
npm install -g @cheppulabs/sonde
cd your-project
sonde init
sonde init 会为仓库建立索引,并在此项目的 .mcp.json 中将 sonde 注册为 MCP 服务;
在写入任何内容前会先询问(使用 sonde init --yes 可跳过提示)。如果 .mcp.json 无法安全合并,
它绝不会修改该文件——如果现有的 sonde 条目与 init 将写入的内容不同,就会保留原样并报告,
不会覆盖。
对于 Python 仓库,请使用 sonde init --resolve;默认的 tree-sitter
级别未通过该项目针对结构查询的准入门槛。
如果你想手动执行并查看每个步骤,也可以按以下方式操作:
sonde index .
# then add to .mcp.json:
# { "mcpServers": { "sonde": { "command": "sonde", "args": ["mcp", "serve", "."] } } }
无需账户或托管服务。
架构文档
sonde doc 会生成 ARCHITECTURE.md,描述仓库的模块、模块之间的依赖关系以及各模块公开的内容——文档由依赖图生成,因此反映的是代码的实际行为,而不是某人的记忆。
sonde doc # write ARCHITECTURE.md
sonde doc --stdout # print it instead
sonde doc --check # fail if it is out of date (for CI)
sonde doc --module src/store # symbol-level detail, never committed
该文档旨在提交并重新生成,而非手动编辑。如果内容没有变化,重新生成后的字节内容完全相同,因此不会造成差异文件膨胀;如果两个分支都重新生成了该文档,请再次运行 sonde doc 解决冲突,而不要手动合并。它会拒绝覆盖并非由它生成的 ARCHITECTURE.md。
它有意不做以下三件事:
- 不会绘制无法举证的依赖关系。 仅共享符号名称的模块对会被排除,并单独计数。在此仓库中,两个适配器共享文件名
symbols.ts、parser.ts和references.ts,这导致模块之间凭空出现了 62 条启发式“引用”,而这些模块实际上从未相互导入——这曾是图中第二粗的箭头。 - 不会假装图表完整无缺。 图表展示依赖关系中最重要的部分,并注明省略了多少条;下方的表格展示更多内容,而
--module则能显示其余内容。包含所有依赖关系的图表难以阅读,因此也就什么都无法展示。 - 不会在内容过时的时候声称其仍是最新的。 页眉会列出文档描述的提交,并在文件发生更改时发出警告。
保证内容
- 绝不返回过时的源代码字节。 每当响应包含源代码时,Sonde 都会在返回前重新读取并重新计算已索引字节范围的哈希值(规范 §8.1,保证 A)。
- 始终报告结构性偏差,而不会声称实现了无法验证的完整性(规范 §8.1,保证 B)。
sonde status会显示相同的偏差情况,以及工具响应封装中包含的层级分布。 - 每条边都会标注其发现方式对应的层级——
COMPILER(由内置类型检查器通过--resolve精确解析:TypeScript 使用 TypeScript 编译器,Python 使用 pyright)、LEXICAL(通过导入绑定或词法作用域解析)、HEURISTIC(成员访问或其他需要类型推断的关系)、EXTERNAL(目标位于已索引仓库之外)或UNRESOLVED(确实无法定位,并附有原因)。 - 绝不伪造边。 无法解析的引用会归为
EXTERNAL或UNRESOLVED——绝不会猜测目标,也绝不会静默丢弃引用。
准确性
Sonde 会在固定测试样本上,将其零配置 tree-sitter 路径与 TypeScript 编译器进行对比,并公布结果,包括不尽如人意的数据(规范 §12)。COMPILER 边直接使用该编译器,因此再用同一权威来源对其进行对比,并非独立的准确性测试。
Sonde 边准确性与 TypeScript 编译器的对比
生成时间:2026-08-23T18:16:41.557Z TypeScript:5.9.3(内置;不会加载仓库中的 TypeScript)
这些数据涵盖的范围。 作为判定基准的预言机衡量的是 tree-sitter 解析路径——这是默认的零配置路径,也是唯一需要评估准确性的层级。COMPILER 层级的边由 TypeScript 编译器本身生成,因此用同一编译器评分毫无意义;这些边在构造时便已精确,因此不纳入这些数据。运行 sonde index --resolve 可生成这些数据。
预言机仅针对仓库内目标进行筛选;node_modules 和 .d.ts 声明均不纳入。仅限类型的引用、JSX 内置元素、export =、装饰器和声明合并,都是已知的预期差异(规范 §10)。各层级行仅将该层级与完整预言机进行比较,以展示各层级的独立贡献;ALL 表示合并后的结果。
为什么这里的精确率低于 1.000 是预期结果
这些差异是结构性的,因此将精确率读作“Sonde 出错的频率”会夸大错误率:
- 对有歧义的成员调用,会输出所有候选项。 对于有两个可见
foo声明的x.foo(),Sonde 会将二者都作为带置信度权重的HEURISTIC边输出。最多只有一个与编译器结果匹配,因此另一个必然计为假阳性。另一种做法是猜测单一目标,但不变量 1 禁止这样做——一条看似已解析但实际错误的边,比两条如实标记为启发式的边更糟。因此,只要夹具中包含有歧义的调用,精确率就会被限制在 1.000 以下。 - 构造函数调用仅由我们处理。 Sonde 会为
new Foo()输出CALLS,而 oracle 不对此建模,因此相对于省略该项的真实值,每一项都计为假阳性。 - 成员级 IMPLEMENTS 仅由我们处理。 一旦类声明实现了接口,Sonde 就会从
RegExpRouter.add推导出指向Router.add的 IMPLEMENTS 边。tsc 只会在类型层级报告继承子句,因此相对于不对此建模的真实值,每一条成员级边都计为假阳性。正是这项能力使得针对接口方法的影响分析能够生效,因此这里会披露精确率上的代价,而不是移除该功能。
计数为绝对值,并非大型语料库中的百分比。下方列出了夹具的边总数,以便看出单条边对各项数据的影响。
tests/fixtures/repos/small
夹具配置 SHA-256:e02e2d5003f96d1ad22519f04e10d687fe689cf9298e7fcbc588eab525dce1ad
Oracle 边数:9 · Sonde 边数:7 · oracle 中的一条边会使召回率变动 11.1%
| 边类型 | 层级 | 精确率 | 召回率 | TP | FP | FN |
|---|---|---|---|---|---|---|
| CALLS | ALL | 0.500 | 1.000 | 2 | 2 | 0 |
| CALLS | LEXICAL | 0.500 | 0.500 | 1 | 1 | 1 |
| CALLS | HEURISTIC | 0.500 | 0.500 | 1 | 1 | 1 |
| IMPLEMENTS | ALL | 0.500 | 1.000 | 1 | 1 | 0 |
| IMPLEMENTS | LEXICAL | 0.500 | 1.000 | 1 | 1 | 0 |
| IMPLEMENTS | HEURISTIC | 1.000 | 0.000 | 0 | 0 | 1 |
| INHERITS | ALL | 1.000 | 1.000 | 1 | 0 | 0 |
| INHERITS | LEXICAL | 1.000 | 1.000 | 1 | 0 | 0 |
| INHERITS | HEURISTIC | 1.000 | 0.000 | 0 | 0 | 1 |
| REFERENCES | ALL | 0.571 | 0.800 | 4 | 3 | 1 |
| REFERENCES | LEXICAL | 0.800 | 0.800 | 4 | 1 | 1 |
| REFERENCES | HEURISTIC | 0.000 | 0.000 | 0 | 2 | 5 |
**总体:**精确率 0.571,召回率 0.889
使用 npm run bench:oracle 重新生成。
CLI
sonde init [path] [--resolve] [--yes] # index and register project MCP config
sonde index [path] [--resolve] # full index; optional compiler pass
sonde update [path] [--resolve] # update; optional compiler pass
sonde status [path] # freshness and tier distribution
sonde search <query> [path] # find_symbols
sonde query <pattern> <symbol> [path] # query_graph
sonde impact [path] --symbol <name> # get_impact_radius by symbol
sonde impact [path] --from-git-diff # impact from working-tree diff
sonde doctor [path] # parser/database/tsc health
sonde clean [path] # remove the cached index
sonde mcp serve [path] # MCP server over stdio
impact 还接受可重复使用的 --symbol 选项以及 --token-budget <n>。init、index、update、status、search、query、impact、doctor 和 clean 命令均接受 --json。
已知限制
- 需要 Node 22+。
better-sqlite3需要此版本;在较旧的 Node 上安装会打印EBADENGINE警告,但仍会完成。如果之后sonde无法运行,原因就在这里——请升级 Node,而不是忽略警告。 - Python 需要
--resolve,且其边未经 oracle 验证。 不加此参数时,Python 仅在 tree-sitter 层级建立索引;在一个真实的 56 个文件的项目中,测得 62.81% 未解析,在 pydantic 中为 57.39%,远超本项目要求的 30% 上限,因此不适用于结构查询。加上--resolve后,内置的 pyright 会驱动COMPILER层级,同一批代码的未解析率分别为 27.00% 和 17.42%。该门槛测量的是定位情况——引用是否找到了目标——而非目标是否正确;TypeScript 的边会在ORACLE.md中与tsc的结果进行评分,Python 则没有同等的 oracle。在表现较差的代码集上,余量很小:校正已知的向上偏差后只有 0.28 个百分点。参见probes/python-placement/FINDINGS.md。 - 仅支持 TypeScript、Python 和 Swift;没有其他语言适配器。
- Swift 解析采用启发式方法,并非由编译器支持,且有一条缩窄规则未经验证。 引用会根据文件可见性和显式的局部类型注解进行缩窄,而非基于完整的类型推断。在一个真实的 376 个文件的 Swift 应用中,定位率达到 74.84%/未解析率为 25.16%——完整测量结果见
probes/swift-narrowing/FINDINGS.md。第三条缩窄规则(SwiftPM 目标边界)从未经过测试:验证所用的代码集是 Xcode 项目,不具备Package.swift布局,因而无法提供该信号。精心整理的 Swift SDK 符号表也刻意保持保守——一些有歧义的名称被舍弃,而非猜测,因此某些有效的 SDK 引用仍可能显示为UNRESOLVED,而不是EXTERNAL。 - TypeScript 的编译器解析(
--resolve)默认关闭,因为它明显较慢,且会使用更多临时内存。在包含 19,409 行代码的 Hono 测试样例中,默认索引耗时 3.58 秒;--resolve耗时 13.70 秒,新增了 10,329 个精确定位/提升结果,并将callers_of Hono.route从零个图结果变为八个编译器调用方。索引完成后会丢弃 Program;内联刷新不会调用编译器,并会明确降低受影响证据的等级,直到完整运行sonde update --resolve。 - 编译器解析使用内置的 TypeScript 5.9.3,而非仓库自身的编译器,因此可能存在版本偏差;
doctor和响应封套中会披露这一点。 - 存在解析错误的文件仍会贡献 tree-sitter 从中恢复的内容——这是有意为之(参见设计规范 §8)。若错误发生后仍恢复出真实声明,其
parse_state为partial;只有完全未能恢复出可用内容时,才为failed。 TESTS边仅表示结构相关性;绝不代表覆盖情况。- 仅限类型的引用、JSX 内建项、
export =/import =、装饰器和声明合并都是 tree-sitter 提取路径中已知的缺口。 - 不推断重命名。 重命名文件会改变所有根据其路径生成的稳定键;任何持有旧键的内容(已保存的查询、Agent 之前的轮次)都将无法继续解析,且不会跟随重命名。
- 不支持语义/行为搜索。我们曾构建并测量此功能——测试了两种嵌入模型和四种文档配置——但在它旨在解决的任务类别上,它们都不如词法/结构检索。具体数据见设计规范 §2.2。该功能未接入
find_symbols。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。