给 AI 编码 agent 用的代码检索工具。双引擎:向量语义检索(云端 Milvus,只读)+ 调用图(本地 SQLite)。
一次 search 同时回答"代码在哪"和"谁在调用它"——这是 grep 做不到的部分。
agent 在陌生仓库里读代码,默认只有 grep/read:知道确切符号名时很好用,不知道时要么关键词命中上百处, 要么根本猜不到该 grep 什么。而"谁调用了它 / 改它会波及谁 / 这段是死代码吗"这类结构问题,grep 从原理上无法回答。
search 补的正是这两块:
| 能力 | grep/read | search |
|---|---|---|
| 已知确切字符串/符号/路径 | ✅ 即时、零成本 | ❌ 更慢,别用 |
| 只知道意图,不知道叫什么 | ❌ 噪声淹没 | ✅ 语义 + 词干 + 缩写归一 |
| 谁调用它 / 调用链 / 影响面 | ❌ 无法判断 | ✅ 调用图直接给出 |
死代码 [unused] / 入口 [entry] |
❌ | ✅ 标记在结果里 |
| 要逐字完整文件 | ✅ | ❌ 用 Read |
用法定位:search 是"定位第一跳",不是 Read 的替代品。 先 search 拿到 file:line + 调用链,
再用 Read 的 offset/limit 定点读那几行——这才是省 token 的关键动作。
link # 会话内一次,绑定云端向量索引 + 建本地图(后台自动)
↓
search(query="how does auth work") # 拿到 file:line + 签名 + 调用关系
↓
Read(file, offset, limit) # 只读需要的那几行,不要通读
link 之后图索引会随你改代码自动增量更新,不需要任何手动 index(本地向量写入按设计禁用,
向量只由云端按保护分支每日索引)。
| mode | 召回 | token | 延迟 | 需要 link | 什么时候用 |
|---|---|---|---|---|---|
graph |
86% | ~300 | 60–105ms | 否 | 关系问题(谁调用 X / 影响面 / 死代码 / 入口);只要位置和调用链、不要代码时的默认选择 |
both(默认) |
93% | ~2200 | 128–220ms | 是(向量部分) | 需要代码片段本身;或要找的东西没有被任何标识符拼出来(底层库、"零拷贝共享内存"这类概念) |
vector |
83% | ~1700 | ~50ms | 是 | 语义找实现,不需要调用图 |
graph 模式接受自然语言,不必点出符号名:标识符按词切分并做了词干化,
所以 "initialize logging and create the log manager" 能命中 InitLogging / LogManager,
"supervised entity recovery action" 能同时命中两个类。
省 token 开关:style:"compact"(只给 file:line,约 1/10 token)、limit:5、enrich:false(不带调用图)。
片段共享一个整体响应预算,limit 越大每条越短——想要完整片段就少要几条。
- 小仓库(< ~300 文件):实测在 flask/requests 上 grep 基线 8 个场景全胜。grep 更便宜且给的更全。
- 已知精确 token/符号/路径 → 直接 Grep/Read。
- 非 AST 语言(图不生效,只剩弱语义):Ruby、PHP、Kotlin、Swift、Vue 模板,以及配置/YAML/JSON/锁文件/Markdown。
- AST/图支持:JS/TS、Python、Java、C/C++、Go、Rust、C#、Scala。
- 要逐字完整内容(编辑整文件、格式敏感)→ Read。
| 工具 | 作用 |
|---|---|
link |
绑定当前仓库到云端某保护分支的 collection(会话级,不落盘)+ 后台建/更新本地图。省略 branch 则列出云端候选分支 |
search |
检索。参数:query、mode、limit、style、enrich、extensionFilter、docs、tests、path |
status |
link 状态(云端 repo@branch + 连通性)+ 本地图统计(节点/边/类型/路由) |
clear |
清空本地图索引(云端向量不受影响) |
unlink |
解绑云端索引,本地图保留 |
docs / tests 默认 false:文档与测试文件被降权,让生产代码胜出;找 README/用例时显式打开。
┌─────────────────────────┐
│ MCP search API │
│ mode: both/vector/graph │
└───────────┬─────────────┘
┌───────────────────┴───────────────────┐
┌───────▼────────┐ ┌────────▼─────────┐
│ 向量索引 (Milvus)│ 并行执行 │ 图索引 (SQLite) │
│ 云端只读检索 │ │ 调用图/影响面 │
│ 按 repo:branch │ │ 跨文件引用解析 │
│ dense+BM25 RRF │ │ FTS5 全文 │
└───────┬────────┘ └────────┬─────────┘
┌───────▼────────┐ ┌────────▼─────────┐
│ Milvus Server │ │ .context/graph/ │
│ <SERVER_HOST> │ │ <project> 本地 │
│ (团队共享) │ │ (已 gitignore) │
└────────────────┘ └──────────────────┘
- 两侧共用的那层(
packages/shared):语言清单、AST/文本切分器、忽略规则引擎、 tree-sitter 语法加载。向量索引用它产出送给 embedding 的分块,图索引用它做一致的语言 判定 —— 同一份实现,不存在"两边各写一套然后慢慢不一致"的可能。 - 向量:本地不做任何写入 —— MCP 构造引擎时硬编码
readOnly: true,建/删 collection、 insert/delete、索引编排一律拒绝,这是代码级保证不是约定。云端git-indexer按仓库:保护分支每日增量索引到 Milvus;本地只做 ①query 向量化 ②直连云端只读检索。 - 图:
<project>/.context/graph/knowledge-graph.db,每开发者本地构建,与 git 不耦合。 Merkle 内容哈希检测变更,对git reset/rebase/stash免疫。 一个 MCP 进程可同时持有多个仓库的图(LRU 上限 8),并发搜多个仓库互不干扰。
模块依赖方向与"为什么某些能力必须只有一份"见上一节;检索质量历次实测见 MCP-SEARCH-BENCHMARK.md。
这不是丢功能,而是"不再盲目降级"。重构时把两套忽略/切分实现合并成一份共享表, 过程中暴露出几处旧行为不一致,按下述原则统一。列在这里是为了避免后人看到 "某个扩展名不见了"就当成 bug 去补回来。
| 项目 | 旧行为 | 现行为 | 为什么 |
|---|---|---|---|
无 tree-sitter 语法的代码扩展名(.vue .svelte .zig .r .pl .ex .erl .clj .groovy .vb .pyx .pxd .nim .gd .astro .cljs .pm .exs,共 18 个) |
用文本切分器盲切后入库 | 完全不扫描 | 本项目原则:没有真解析器就不要这个语言,绝不降级成"按字符切"——那种分块在检索时是噪声 |
tmp/ temp/(图索引侧) |
按目录名忽略 | 会扫描 | 这两个名字在真实项目里常装源码(Unity Temp/、Lua Library/),共享表有保护断言禁止按裸名收录;图索引只做名字匹配,无法用 tmp/** 表达。想恢复请写进该仓库的忽略配置 |
Library/ Temp/ Obj/ Logs/ Intermediate/ Saved/ |
旧 core 按根锚定 glob 忽略 | 会扫描 | 它们是 Unreal / Windows 构建目录,但同时也是极常见的源码目录名(Lua 项目的 Library/)。按名字任意层级忽略会静默丢源码,故不收 |
moc_*.cpp ui_*.h qrc_*.cpp(Qt 生成物) |
旧 core 忽略 | 会扫描 | 共享表按"后缀"匹配,前缀通配(moc_*)表达不了。这些文件在 AST 白名单内确实会被索引 —— 若你的仓库有大量 Qt 生成物,请用 .contextignore 排除 |
install/(ROS colcon 安装树)、deps/(Elixir Mix 依赖) |
旧 core 忽略 | 忽略 | 抽取共享表时曾遗漏,已补回 |
.md / .mdx |
旧版注释明确排除("doc noise") | 会索引 | 改由 SEARCH_DOC_PENALTY=0.5 降权处理,而不是一刀切不索引 |
# 1. 全局配置(不要放进代码仓库目录)
cp .env.example ~/.context/.env # 按需改 MILVUS_ADDRESS / OLLAMA_HOST
# 2. 安装并注册 MCP
./install.shinstall.sh 会构建各 package、把 MCP 装到 ~/.claude-context/、注册到 Claude Code,
并安装 commands/(/seeway-link 等斜杠命令)与 rules/code-context-policy.md(触发策略)。
pnpm install
pnpm build # 全量构建
pnpm test # shared + core + graph 三包单测
pnpm typecheck
cd packages/mcp && pnpm dev # 本地启动 MCP包结构(pnpm monorepo):
| 包 | 职责 | 跑在哪 |
|---|---|---|
packages/shared |
公共依赖:语言清单、AST/文本切分器、忽略规则引擎、tree-sitter 语法加载 | 两侧共用 |
packages/core |
向量索引引擎(Milvus 客户端、embedding provider、索引编排) | 云端写入 / 本地只读 |
packages/graph |
图索引引擎(tree-sitter 提取、SQLite/FTS5、跨文件解析、遍历) | 开发者本机 |
packages/git-indexer |
服务端定时索引服务 | 服务器容器 |
packages/mcp |
MCP 服务(对外入口,工具注册与 handler) | 开发者本机 |
依赖方向:
shared ← 公共依赖(纯能力,不依赖任何一侧)
▲ ▲
│ │
core graph ← 两大索引模块,互不依赖
▲ ▲
│ │
mcp └── git-indexer → core(云端向量索引)
shared 承载"同一个文件在两侧必须被认成同一种语言、按同一套规则决定是否索引"
这类契约。之所以必须只有一份:历史上语言清单、语法加载器、忽略清单都各自存过两份,
每次都漂移,而且漂移不报错 —— 只表现为"同一个仓库在向量侧和图侧覆盖的文件集
不同、统计数字对不上"。
- 本地 MCP:
node packages/mcp/dist/index.js - 云端基础设施与容器编排:见 DEPLOY.md
- 检索质量历次实测:见 MCP-SEARCH-BENCHMARK.md
见 LICENSE。