关于 osnova
Osnova 将代码仓库索引为确定性的符号与调用图,面向 AI 编码 Agent。它通过 MCP 服务和命令行提供精确的文件行号证据,并将未解析调用与已确认关系分开呈现。
主要功能
- 01使用 tree-sitter 索引 20 种语言
- 02查询符号与调用图
- 03提供精确文件行号证据
- 04生成仓库地图并分析变更影响
- 05通过命令行和 MCP 服务访问
适用场景
- 01编辑函数前追踪其调用方
- 02为任务查找测试和相关符号
- 03检查代码变更影响的依赖项
- 04在 CI 中核对索引到的代码关系
MCP 相关
将仓库搜索、符号、调用图、影响分析和测试查询作为 MCP 工具提供。响应包含索引代次,并标明不完整结果和省略项。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
10收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| in | string | 否 | 限制为文件或目录路径(相对仓库路径) |
| full | boolean | 否 | 内联完整定义,而非 8 行摘录 |
| lean | boolean | 否 | 默认值为 true:仅保留 file:line、类型、定义范围和签名,不含源代码;false 会内联源代码;显式指定 true 会覆盖 full |
| limit | number | 否 | 命中项上限(默认 8) |
| question | string | 是 | 自然语言或关键词查询 |
详细介绍
Osnova
为 AI 编码 Agent 提供确定性代码地图。 Osnova 使用 tree-sitter 为代码仓库建立索引,生成符号图和调用图,然后通过 MCP 客户端或命令行提供这些信息。输入相同,输出就逐字节相同。无需嵌入,不收集遥测数据;除非你输入 osnova update-check,否则不会建立网络连接。支持二十种语言,其中七种(TypeScript、JavaScript、Python、Go、Rust、Java、C#)配有深度适配器。
Osnova 在斯拉夫语中意为基础或根基。这正是它的作用:在 Agent 修改代码之前,为其提供坚实的立足点。
需要 Node.js 22.13 或更高版本。将仓库提供给任意 MCP 客户端:
npx -y @getdomovoi/osnova mcp --workspace /path/to/repo
运行 npm install -g @getdomovoi/osnova 后,针对每类 Agent 各运行一条命令:claude 会配置 Claude Code(MCP 入口、会话、提示词和停止钩子,以及技能);agents 会配置所有读取 AGENTS.md 的已安装工作环境(Codex、OpenCode、Kilo、Pi、Cursor),并为每个工作环境配置其钩子、插件或扩展,此外还会在 ~/.agents/skills/ 中配置一个共享技能。每条命令都会预览所做的更改;使用 --apply 写入更改,并备份每个被修改的文件。
osnova setup claude --apply
osnova setup agents --apply
也可以手动添加 MCP 入口;如果未全局安装,请将 osnova 替换为 npx -y @getdomovoi/osnova。
{ "mcpServers": { "osnova": { "command": "osnova", "args": ["mcp"] } } }
你将获得的结果
在此仓库上运行 osnova warp src/api.ts#refreshWorkspace,结果精简为十二行(如果前两行不再能复现,test/docs-readme-warp.test.ts 就会失败):
function src/api.ts#refreshWorkspace: 83 indexed edges
reach: d1 callers 83 in 14 files (3 dirs); d2 +16 in 2 files; unresolved same-name 10; tests 18
d1 calls src/cli/cli.ts#ensureIndex:78 [import-binding]
d1 calls src/cli/hook.ts#runHook:191,203,225,240 [import-binding]
d1 calls src/mcp/server.ts#createOsnovaMcpServer.refresh:297 [import-binding]
d1 calls test/verification-fastpath.test.ts#<module>:37,47,48,51,53,57,58,64,68,74,78,85,110,114,132,145,154,166,171,197 [re-export-binding]
via src/index.ts:65 refreshWorkspace -> src/api.ts (export refreshWorkspace)
This does not prove absence of callers or that deletion is safe.
unresolved evidence (10); not confirmed relationships
candidates for refreshWorkspace (2, unverified): src/api.ts#refreshWorkspace, test/mcp-watch.test.ts#refreshWorkspace
d1 calls refreshWorkspace test/artifact-format9.test.ts:78,85,104,120,140 [binding-blocked]
d1 calls refreshWorkspace scripts/perf.mjs:485,486,491 [import-target-unresolved]
每个已确认的调用行都会标明调用方、对应行号,以及将该调用与此定义关联起来的证据:导入绑定、同文件定义、带跳转步骤的重新导出链,或已识别的接收者。索引无法关联到定义的调用会作为未解析证据单独列出,并附上找到的同名候选项和停止查找的原因,因此名称匹配绝不会被误认为调用方。reach 行提供精确计数,而非评分;当列表超出预算时,capped: 行和 omitted: 页脚会统计被省略的内容。
图谱中的精确部分有多少
当索引能够根据可指明的证据,将调用点关联到一个定义时,该调用点就算已解析;其余调用点都会保留为未解析,并附上原因。以下是在固定检出版本上的占比,数据来自 benchmarks/corpora/,记录于 benchmarks/results/resolution-coverage-2026-09-21b.json;最后一列不计来自仓库外部包的调用和对内置项的调用,因为这些调用永远无法在本地解析;参考文档定义了每一列。
| 语料库 | 语言 | 调用点 | 已解析 | 占比 | 排除外部调用后 |
|---|---|---|---|---|---|
| click | 全部 | 6593 | 2903 | 44.0% | 62.4% |
| cobra | 全部 | 4374 | 1980 | 45.3% | 88.9% |
| gson | 全部 | 23382 | 8473 | 36.2% | 56.7% |
| humanizer | 全部 | 30517 | 7798 | 25.6% | 51.3% |
| pyright | 全部 | 58578 | 30070 | 51.3% | 74.5% |
| ripgrep | 全部 | 13387 | 6121 | 45.7% | 71.6% |
| zod | 全部 | 53417 | 21630 | 40.5% | 73.2% |
按语言划分的行见参考文档。osnova coverage 会按语言和原因报告你自己的仓库中的相同数据。
已解析不代表正确,因此还会将调用边与各语言的类型检查器或编译器结果进行对照评分。将六个固定检出版本中的每个调用点都提交给被调用方声明对应的检查器;如果 osnova 调用边指向的定义包含该声明,则标记为正确,否则标记为错误。以下数据测于 0.12.0:
| 语料库 | 检查器 | 已判定边数 | 错误数 | 错误边率 | 覆盖的仓库内调用点 |
|---|---|---|---|---|---|
| click | pyright 1.1.414 | 2883 | 0 | 0% | 90.4% |
| zod | TypeScript 5.9.3 | 21593 | 4 | 0.02% | 78.2% |
| cobra | go/types, Go 1.27.1 | 1980 | 0 | 0% | 87.5% |
| ripgrep | rust-analyzer 1.98.1 | 6066 | 29 | 0.48% | 85.8% |
| humanizer | Roslyn 5.9.0 | 7363 | 3 | 0.04% | 62.2% |
| gson | javac 27 | 8133 | 4 | 0.05% | 72.3% |
在 Java 和 C# 中,重载共用索引中的同一个符号。因此,只有当调用中写出的实参数量恰好符合该类型、其 partial 部分及其已声明基类中的一个声明时,调用边才会指明它绑定的重载;如果符合多个或没有一个符合,该边会保留方法,但不指明声明,oracle 也不会将其计入任何一项比率(gson 中有 2526 条边,humanizer 中有 602 条)。0.11.0 会将每次调用都指向最后声明的重载,将调用记录在调用表达式的第一行,并在遇到主构造函数或原始字符串后丢失 C# 文件的其余部分;其测得 gson 有 2045 条错误边(24.75%)、humanizer 有 617 条(8.25%),ripgrep 有 112 条(2.06%)。ripgrep 剩余的 29 条中,11 条对应在每个 #[cfg] 分支中各声明一次的函数,6 条对应名称与父模块函数相同的测试模块函数,5 条是对局部闭包或参数的调用,而它们的名称与其他位置的函数相同。如果某个调用目标只有一个符合条件的声明,即使基类类型不在索引中,也会保留该调用边;因为此时未纳入索引的基类重载可能正是编译器绑定的对象。之所以保留这一限制,是因为撤销这些调用边会让 humanizer 的召回率下降 10 个百分点,却只减少一条错误边。每条错误边的成因及相关方法和限制,都记录在 benchmarks/results/type-checker-oracle-2026-09-30.json 中。生成这些数据的脚本位于 benchmarks/oracle/。
Grep 与图
保留调用图而不进行文本搜索,原因并非速度。真正的原因是,人们第一次输入的正则表达式往往并不正确,而这一点比看起来更常被忽略。我们逐行核验了 6 种语言中的 9 组调用点;每个单元格都显示了找到的调用点(精确率 / 召回率),方法列出了双方各自遗漏或误判的内容。
| 代码库 | 目标 | 已核验的调用点 | 文本搜索 | 已解析的图 |
|---|---|---|---|---|
| click | Context.invoke depth 2 | 13 | 12 (0.92 / 0.85) | 12 (1.00 / 0.92) |
| pyright | getChildNodes depth 2 | 28 | 5 (0.80 / 0.14) | 28 (1.00 / 1.00) |
| cobra | Command.Root depth 1 | 30 | 30 (1.00 / 1.00) | 30 (1.00 / 1.00) |
| cobra | Command.PersistentFlags depth 1 | 12 | 13 (0.92 / 1.00) | 12 (1.00 / 1.00) |
| humanizer | Configurator.GetFormatter depth 1 | 18 | 19 (0.74 / 0.78) | 18 (1.00 / 1.00) |
| ripgrep | Searcher.line_terminator depth 1 | 12 | 32 (0.38 / 1.00) | 12 (1.00 / 1.00) |
| ripgrep | LineTerminator.as_byte depth 1 | 26 | 26 (1.00 / 1.00) | 26 (1.00 / 1.00) |
| gson | JsonReader.beginObject depth 1 | 10 | 29 (0.34 / 1.00) | 10 (1.00 / 1.00) |
| gson | TypeToken.getRawType depth 1 | 27 | 43 (0.63 / 1.00) | 27 (1.00 / 1.00) |
图从未返回过非目标调用点。记录位于 benchmarks/results/grep-vs-graph-2026-09-21.json。
十种工具
这些名称取自基础图像。CLI 使用不带前缀的相同名称:osnova ground 和 osnova_ground 是同一个查询。
| 工具 | 含义 | 功能 |
|---|---|---|
osnova_ground | 你脚下的地面 | 关键词搜索:按相关性排序的定义,并提供精确的 file:line |
osnova_thread | 你穿过织物追踪的线 | 文本搜索:按符号分组的正则表达式或字面匹配 |
osnova_outline | 某个部分的轮廓 | 单个文件的签名和行跨度 |
osnova_warp | 支撑织物的经线 | 调用图:直接或传递的调用方和被调用方 |
osnova_groundwork | 一切事物之下的基础工作 | 代码库映射:目录集群、枢纽和热点 |
osnova_footing | 你赖以构建的立足点 | 任务上下文:围绕问题提供定义、关系和候选测试 |
osnova_settle | 更改后地面如何沉降 | 变更影响:差异所触及的符号及其索引中的依赖项 |
osnova_plumb | 垂直贯穿的铅垂线 | 检查断言:索引确认了哪些列出的调用点,以及遗漏了哪些依赖项 |
osnova_tests | 拉动织物,查看哪些地方牢固 | 测试:引用某个符号的测试文件,或某个测试文件所触及的符号 |
osnova_unreferenced | 边缘松散的线 | 没有索引调用方的定义,每个定义都附带线索;仅为候选项,并非证明 |
每个 MCP 响应都会以其索引生成代次开头,注明索引是否不完整,并统计预算限制导致遗漏的内容;预算以及哪些 CLI 子命令会携带生成代次,详见参考文档。
Hooks 和客户端
一次全局安装即可服务于所有仓库和客户端:每个客户端的全局配置中只需添加一项,不必逐个项目配置,也不会在仓库内写入任何内容。osnova setup claude 和 osnova setup agents 会显示差异,并且只有在指定 --apply 后才会写入;如果某个 harness 的配置目录不存在,agents 会跳过它;--only codex,pi 可缩小范围;你编辑过的技能或插件文件会予以保留并报告。--uninstall 可用相同方式卸载这两类配置,并先预览。下表列出了各客户端的配置方式,每次配置一项。各个 hook 会输出什么、何时保持静默,以及试验测量了什么,请参阅参考文档。
| 客户端 | 配置方式 | 添加的内容 |
|---|---|---|
| Claude Code | osnova setup --apply --client claude-code --hooks | MCP 条目,以及 session、prompt 和 stop hooks;--skill 会添加技能,--nudge 会添加可选的 grep 提示 |
| Claude Code,作为插件 | /plugin marketplace add getdomovoi/osnova 然后 /plugin install osnova@osnova | 相同的 MCP 条目、hooks 和技能,通过 npx -y @getdomovoi/osnova@<version> 运行,并固定到插件的发布版本,无需全局安装;更新由 marketplace 提供 |
| Codex | osnova setup --apply --client codex --hooks | MCP 条目和相同的三个 hooks;需在 /hooks 中信任这些 hooks,否则 Codex 会静默跳过;分步说明见 Osnova 与 Codex |
| Cursor | osnova setup --apply --client cursor --hooks | MCP 条目,以及作为后续消息的 stop hook |
| OpenCode | osnova setup --apply --client opencode --plugin | MCP 条目,以及一个会向每条消息追加起点的插件;工具指引来自 MCP instructions |
| Kilo | osnova setup --apply --client kilo --plugin | MCP 条目和相同的插件 |
| Pi | osnova setup --apply --client pi --plugin | 通过 pi-mcp-adapter 添加 MCP 条目,以及一个执行相同功能的扩展 |
在 CI 中
此 Action 会针对拉取请求的基准分支运行 osnova settle --base-ref,并列出拉取请求所更改符号的所有已索引依赖项;该报告仅提供已索引的结构性证据,因此未列出某个依赖项并不能证明没有任何内容依赖此次更改。
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: getdomovoi/[email protected]
with:
depth: "2"
输入项和本地用法见参考文档。
osnova 不做什么
- 不进行类型推断,也不做动态分派。边来自语法:直接调用、导入、名称引用、声明的继承关系(写明的超类或接口名称)以及框架路由(注册的接收方绑定到列出的框架导入,且动词和字面路径写在注册处),并针对 TypeScript、JavaScript 和 Python 使用词法绑定和接收方提示。解析是启发式的,并会明确说明这一点。
- 不生成路由表。
routes边是与一个处理程序关联的单个注册位置;挂载、蓝图和控制器的前缀会记录在各自的边上,不会组合成完整路径;计算所得路径不会记录路径,而内联闭包或包装后的处理程序会记录路由,但不记录目标。可读取 Express、NestJS、Flask 和 FastAPI;不支持 gin、axum、Django、Spring、ASP.NET、Rails 和基于文件的路由器。 - 这种边界有可量化的代价。与类型检查器在两个固定语料库上的评分结果相比,osnova 未解析的调用大多是接收方类型从未写明的调用:zod 中 6359 个未命中的位置有 2945 个,click 中 307 个未命中的位置有 164 个,是没有注解的普通名称;zod 中另有 1478 个是调用结果或属性链。只有 zod 中 349 个、click 中 10 个未命中的位置在接收方声明处写明了类型,其中 349 个里的 248 个属于同一种库惯用写法。全部解析后,zod 的召回率会从 76.9% 提升到 78.2%,click 的召回率会从 90.4% 提升到 90.7%,因此这种边界造成的召回率损失约为 1 个百分点,而不是 10 个百分点。完整统计见
benchmarks/results/receiver-boundary-census-2026-09-21.json。 - 不做语义搜索。
osnova_ground是基于定义的字段词法排序。它速度快、结果确定且可解释,但无法匹配释义。 - 不证明安全性。调用方列表为空,表示索引未找到调用方,并不表示调用方不存在。
- 不声称能降低成本。目前的 Agent 试验表明,在小型任务中,使用或不使用图谱的正确性相当。区分两者的基准测试正在进行中。
如何保持诚实
- 每次查询都会先从工作树(包括未提交的编辑)刷新索引,并报告其代次。
- 每个截断的输出都会说明截断量。每个排序列表都会说明丢弃的候选项数量。部分索引会在每个响应中明确标示。
- 每个命中项都带有
file:line范围、源哈希和索引代次,因此你可以核对 Agent 引用的内容。 benchmarks/results/中的每个基准测试结果都会连同其语料库指纹一起冻结;被拒绝的实验也会与被接受的实验一并留档。- 缓存会在解析前验证结构核心的 SHA-256,并在读取时对每段源文本进行哈希校验。缓存之外不会写入任何内容。
CLI
每个工具都是一个子命令:osnova build <root>、osnova warp <symbol>、osnova settle --base-ref <ref>,以及 coverage、check、doctor、setup 和 mcp。完整列表请见参考文档。
库
import { buildIndex, ask, callersDetailed, renderMapCard } from "@getdomovoi/osnova"。结构化 API 会返回包含省略项数量的完整结果;文本预算仅适用于 CLI 和 MCP 展示。所有导出项请见参考文档。
贡献
欢迎提交错误报告、语言适配器、基准测试语料库和 Agent 试验。请阅读 CONTRIBUTING.md,了解变更必须通过的检查:lint、类型检查、测试、构建、性能预算和软件包冒烟测试。
pnpm install
pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm perf
确定性是核心不变量。任何导致增量刷新与完整重建之间哪怕相差 1 个字节的变更,都是错误。
许可证
Apache-2.0
隐私:PRIVACY.md。安全政策与报告:SECURITY.md。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。
