代码语义搜索工具

yomu是一款面向AI代理的前端代码语义搜索工具,允许用户通过概念而非名称查找代码,适用于大型代码库中的代码导航和依赖分析。

  • by thkt
  • Rust
  • 本地部署
  • 1 星标
  • 更新于

使用说明

英语 | 日本语

yomu

AI代理的前端代码搜索。当你不知道代码的名字时,按概念查找代码。

的问题

你需要找到聊天流挂钩 vercel/ai (3535个文件),但你不知道函数被调用了 useChat.

典型的代理工作流程:

glob "**/chat*"                                      → 12 files. None are it (it's called use-chat.ts).
grep "stream.*hook"                                  → 0 files.
grep "chat"                                          → too many results. Try reading a few...
read packages/react/src/use-chat.ts                  → Found it. What does it import?
read packages/ai/src/ui/process-ui-message-stream.ts → Now I have context.

3-5次工具调用,反复试验,上下文窗口中有噪音。

与yomu:

yomu search "streaming chat hooks"

## packages/react/src/use-chat.ts
Imports: @ai-sdk/provider-utils, @ai-sdk/ui-utils
Siblings: UseChatOptions [type_def], UseChatHelpers [type_def]

1. useChat [hook] — 58:210 (similarity: 0.85)
export function useChat({ api, ...options }: UseChatOptions): UseChatHelpers {
  ...full function body...
}

## packages/rsc/src/streamable-value/use-streamable-value.ts
2. useStreamableValue [hook] — 12:45 (similarity: 0.72)
...

## examples/ai-e2e-next/.../chat-context.tsx
3. useSharedChatContext [hook] — 8:22 (similarity: 0.68)
...

1个电话。实现是第一个结果——在130个包含“useChat”的文件中,索引中总共有9015个块。

每个结果都包括完整的代码体、文件导入和兄弟定义。不需要后续阅读。

为什么不只是grep?

Claude Code的开发人员发现 这种代理搜索——让模型迭代地使用glob和grep——在代码导航方面优于RAG。他们是对的。当代理可以使用不同的关键字重试、读取目录结构并优化其搜索时,grep的工作效果非常好。

yomu不会与这种工作流程竞争。它 减少迭代次数:

方法 调用 上下文窗口成本
grep/glob(迭代) 3–5 每次失误都会增加噪音
yomu搜索 1 代码+导入+兄弟姐妹在一个响应中

经典的RAG问题——索引同步延迟、陈旧嵌入、冷启动——通过保持索引显式和查询只读来解决:

解决方案 详细信息
只读查询 search / impact 永远不要改变索引,这样代理就可以自由调用它们
快速增量索引 yomu index re块仅更改文件(按时间),3535个文件约2.5秒
不需要API密钥 本地嵌入模型,当模型不可用时,FTS5全文回退
单程嵌入 index 块并嵌入到单次通过中(ADR-0002)。无每个查询的嵌入成本

当你知道grep的名字时,它是一个合适的工具。yomu是在那之前的一刻——当你知道概念但不知道标识符的时候。

何时使用yomu(何时不使用)

yomu grep/glob
你不知道代码叫什么 你知道确切的名字(grep "useAuth" 更快)
grep返回的结果太多——你需要impl 你需要正则表达式匹配或精确的字符串搜索
您希望在一次调用中包含代码+导入+相关类型 代码库很小,很熟悉

yomu不能取代grep。它涵盖了grep不能的情况:按概念搜索。

设置

安装

brew install thkt/tap/yomu

或者从源代码构建(需要Rust 1.85+):

cargo build --release

配置

语义搜索使用局部嵌入模型(鲁里V 3约1.2GB)。如果模型已经在本地缓存, search 自动使用它-不需要API密钥。

如果模型未安装或不可用, search 返回到纯文本模式(FTS5)。所有其他命令(index, rebuild, impact, status)没有模型的工作。

索引是显式的:运行 yomu index 在搜索之前。查询(search / impact)是只读的,从不自动索引。

平台说明

平台 构建命令
macOS(苹果硅) cargo build --release (默认:mlx后端)
Linux/x86 cargo build --release --no-default-features --features candle

命令

全球旗帜

标志 描述
--json 输出为JSON(所有命令)

--json 可以出现在子命令之前或之后:

$ yomu --json status
{"files":42,"chunks":187,"embedded_chunks":187,"embeddable_chunks":187,"embed_percentage":100,"references":156,"last_indexed":"2025-03-29 01:23:45"}

yomu search [query] --按概念搜索

返回带有完整上下文的排名结果。每个结果包括:

包括 为什么
完整代码体 无后续 read 需要
文件导入 不打开另一个文件的依赖关系上下文
Sibling defs 同一文件中的其他函数/类型
块类型 组件/钩子/类型_def/css_rule/rust_fn

选项:

标志 默认值 描述
--limit 10 最大结果(最大值:100)
--offset 0 分页偏移(最大值:500)
--from -- 搜索与文件或符号类似的代码(src/foo.rs 或 src/foo.rs:my_fn).查询变为可选

--from 使用目标的存储嵌入——不需要重新嵌入:

yomu search --from src/query/mod.rs           # files similar to this file
yomu search --from src/query/mod.rs:rerank    # files similar to this function
yomu search --from src/query/mod.rs "filter"  # hybrid: similar to file + FTS on "filter"

yomu impact <target> --爆炸半径变化

显示哪些文件依赖于目标文件或符号。

$ yomu impact "packages/ai/src/ui/ui-messages.ts" --symbol UIMessage --depth 2

## Impact analysis: `packages/ai/src/ui/ui-messages.ts`

### Direct symbol references
- packages/ai/src/ui/process-ui-message-stream.ts
- packages/react/src/use-chat.ts
  ...34 files

### All transitive dependents
#### Depth 1
- packages/ai/src/ui/process-ui-message-stream.ts
  ...37 files
#### Depth 2
- packages/react/src/use-chat.ts
  ...18 files

Total: 55 dependent file(s)

vercel/ai的实际输出。一个调用替换了手动跟踪导入。

选项: --symbol (可选,过滤到特定导出), --depth (默认值:3,最大值:10)

--json 返回结构化响应。 dependents[].references 为直接(深度=1)边填充,并列出每个边 (ref_kind, via_symbol) 从源文件中配对。传递性(深度>=2)依赖项通过中间文件到达目标,因此它们的 references 是 []:

{
  "target": "src/storage.rs",
  "in_index": true,
  "dependents": [
    {
      "file_path": "src/indexer.rs",
      "depth": 1,
      "references": [
        { "ref_kind": "named", "via_symbol": "Db" },
        { "ref_kind": "named", "via_symbol": "open_db" }
      ]
    },
    {
      "file_path": "src/main.rs",
      "depth": 2,
      "references": []
    }
  ],
  "symbol_refs": [],
  "total": 2
}

ref_kind 是其中之一 named / default / namespace / type_only / side_effect. via_symbol 是 null 用于名称空间/副作用导入,其中在导入站点上没有命名单个符号。

yomu index / yomu rebuild / yomu status

命令 详细信息
index 更新块索引(块+嵌入,一次通过)。没有API调用,对3535个文件约2.5秒。文件更改后运行
rebuild 从头开始完全重新解析
status 文件、块、嵌入覆盖率、引用

查询(search / impact)是只读的,永远不会重新索引。决定 当 重新索引是消费者的责任——例如,run yomu index 来自克劳德密码 Stop 或 PostToolUse(Edit|Write) 钩子。自从 index 只重新分块更改的文件(通过mtime),重新运行它既便宜又幂等。

运作原理

Source files → tree-sitter AST → Semantic chunks → Local embeddings (Ruri v3) → Hybrid search
阶段 详细信息
索引 树形图在函数/组件/类型边界处拆分代码。每个块都是一个可搜索的单元。导入图是在同一通道中构建的。在vercel/ai:3535个文件上→ 9,015个区块+5026个导入引用,2.5秒,零API调用
嵌入 在以下过程中,块由本地模型(Ruri v3,310M参数)嵌入 yomu index,与分块(ADR-0002)在同一传球中。无API调用,无每次查询成本
搜索 三层混合:向量相似性→ 名称/路径匹配→ FTS5全文。使用IDF加权关键字评分重新排名。经常导入的文件排名更高,测试文件排名更低

支持的文件类型

类型 解析器 块类型
TypeScript/TSX 树保姆 组件、钩子、type_def、test_case、其他
JavaScript/JSX 树保姆 组件、钩子、type_def、test_case、其他
Rust 树保姆 Rust_fn、Rust_struct、Rust_enum、Rust_trait、Rust_impl
CSS 树形图 CSS_rule(选择器、@media、@关键帧)
HTML 树保姆 HTML_element

其他文件则退回到基于字符的重叠分块。

退出代码

根据ADR-0066组2(sysexits.h),代理和脚本可以根据重试策略的编号进行分支。随着 --json,errors向stderr发出单行信封: {"error":{"code":"<CODE>","message":"<text>"}}.

代码 error.code 触发器
0 (无) 成功
64 USAGE_ERROR CLI输入无效--clap解析失败、查询/任务为空、数值参数超出范围
70 INTERNAL 不变违规,搜索/查询失败
73 CANT_CREAT 索引数据库创建/迁移失败,索引管道失败
74 IO_ERROR 文件系统IO故障
75 TEMP_FAILURE 嵌入器不可用(模型丢失/探测失败)--之后可重试 yomu model download
104 UNKNOWN 保留用于非机密故障(目前未使用——如果路径漂移,则为曲面)

突变(0.16+):发布了以前的版本 1/2/4 对于失败。在这些号码上分支的调用者必须迁移到上面的sysexit值。文本模式stderr保持不变 error: <message> 形状。

备注:clap解析失败(未知标志、超出范围的数字参数、缺少子命令)退出 64 即便当 --json 被要求。这 --json 标志在参数解析后被解析,因此解析时间失败会退回到文本模式 error: <message> stderr 。

局限性

限制 详细信息
模型未自动下载 必须预缓存约1.2 GB的嵌入模型; search 不自动下载
不支持SCSS/Sass 仅支持纯CSS
冷启动 首先 yomu index 加载模型并嵌入所有块(几秒钟)
跳过大文件 超过1 MB的文件将被排除在索引之外

发展

设置

克隆后运行一次:

git config --local core.hooksPath .githooks

这将安装一个运行的预提交钩子 cargo fmt --check 和 cargo clippy --all-targets --all-features -- -D warnings 每次提交之前。违规行为会中止提交。跳过一次提交: git commit --no-verify.

常用命令

cargo nextest run --features test-support                 # all tests (test-support stubs the embedding model so `index` runs without it; install: cargo install cargo-nextest --locked)
cargo clippy --all-targets --all-features -- -D warnings  # lint (matches CI)
cargo fmt -- --check                                      # format check

建筑

src/
├── main.rs              CLI entry point (clap)
├── lib.rs               Crate root, public API
├── config.rs            Runtime configuration
├── modernbert.rs        ModernBERT model (mlx backend)
├── tools/               Application facade — orchestrates indexer, query, storage per command
├── indexer/
│   ├── mod.rs           Orchestration: incremental index, embed budget
│   ├── chunker/         tree-sitter AST → semantic chunks
│   ├── embedder.rs      Local embedding (ModernBERT/Ruri v3 via mlx-rs or candle)
│   └── walker.rs        File discovery, .gitignore filtering
├── resolver.rs          Import path resolution (tsconfig aliases, index.ts probing)
├── query/               Hybrid search + IDF reranking
└── storage/
    ├── mod.rs           Schema, CRUD, types
    ├── search.rs        Vector similarity, name matching, FTS5
    ├── embed.rs         Embedding storage, vec_chunks
    └── graph.rs         Import graph traversal, dependents, siblings

单一二进制,零运行时依赖关系。SQLite和SQLite-vec是静态链接的。

查询日志

可以输出用于观测搜索管线的append-only JSONL的query log(default off,Issue#182)。

  • 选择加入: yomu --log-query search "..." 中写入1search=1record
  • 目标: $XDG_DATA_HOME/yomu/query_log.jsonl (未设定env时 ~/.local/share/yomu/query_log.jsonl)
  • 用途:Hit@k低下のquery抽出、ranking実験のbaseline蓄积、回归検知

original_query 中直接记录实际的检索字符串。在作为bug report共享之前确认内容。yomu因为是本地个人使用前提,所以不通过default进行redact。

许可证

麻省理工学院