内容
# 描述
[](https://github.com/jKiler/descry/actions/workflows/ci.yml)
[](https://github.com/jKiler/descry/releases)
> **描述** *(v.)* — 发现某物。
一个本地的、混合的代码搜索索引,用于AI编码代理和人类,由Go编写。
描述会遍历仓库,将每个文件分成语义块,嵌入它们与
一个句子转换器模型,并提供**混合检索** — 稠密向量
搜索与BM25词汇搜索融合 — 在一个持久的SQLite索引上。它还
构建一个Go调用/依赖图用于影响和跟踪查询。

- **混合检索** — 向量(all-MiniLM-L6-v2)+ BM25,融合互惠
排名融合。语义召回加上精确标识符精度。
- **索引和快速扩展** — 持久的BM25倒排索引和int8
向量扫描与float32重排序保持一个~96k块仓库(Kubernetes)在~0.5s
启动和~25ms查询。结果是精确的 — 没有近似。
- **AST感知块** — Go文件在实际声明边界
(函数/类型/方法)上分割,并附带文档注释;其他文件则回退
到段落块。
- **持久、自愈索引** — 块和向量存储在一个单独的
`.descry/index.db`。索引带有指纹,当模型、块或管道更改时会自动重建。
- **快速嵌入** — ONNX运行时(~4.7ms/嵌入)背后是一个并发工作
池和一个持久向量缓存,因此更改后重新索引很便宜。
- **调用图** — 基于名称或类型检查(`go/types`)的依赖图,
渲染为Mermaid。
- **MCP服务器** — 通过模型上下文协议(JSON-RPC在stdio上)向AI代理公开`search`、`read_relevant`、`graph_impact`、
`graph_trace`和`status`。
- **评估框架** — 召回率@k和MRR针对标记查询集。
无需外部服务:模型在首次使用时从Hugging Face下载一次并
缓存在本地;其他所有操作都在您的机器上运行。
## 安装
```bash
go install github.com/jKiler/descry/cmd/descry@latest
```
或者从[发布页面](https://github.com/jKiler/descry/releases)获取macOS(Apple silicon)或Linux(amd64/arm64)的预编译二进制文件。
嵌入运行在ONNX运行时。Go绑定*dlopens*库而不是
链接它,因此**构建只需要C编译器(cgo)** — onnxruntime
库本身被下载并缓存在首次使用时(固定版本,通过
sha256验证)。无需手动安装。
如果您想提供自己的库(一个发行版包、Homebrew、自定义
构建),请将`DESCRY_ORT_LIB`指向它,描述将使用它而不是
自动安装的库。现有的系统安装也会被自动检测。
`Makefile`包装了常见的开发者任务 — 运行`make help`列出它们
(`make build`、`make test`、`make check`等)。
## 用法
```bash
# 动词:任何不是子命令的参数都是搜索查询。
# 在没有索引的仓库中,描述会命名目录并在索引之前询问
# (第一次运行还会下载~90MB的嵌入模型,一次)。
descry "在哪里处理身份验证"
descry 如何实现身份验证 # 引号可选
# 裸`descry`显示此仓库的索引状态(或提供构建索引)。
descry
# 显式表单,用于脚本:索引/状态/搜索无需提示。
descry index .
descry status
descry search "在哪里处理身份验证"
# 将Go调用图打印为Mermaid(默认情况下为类型;--named强制启发式)。
descry graph . > graph.mmd
# 针对标记查询集评估检索质量。
descry eval eval_queries.json
# 通过模型上下文协议(stdio)为AI代理提供索引服务。
descry mcp
# 安装代理技能,以便编码代理在搜索时调用描述。
descry skill install
# 运行健康检查,确保所有需要解决的组件都能正常工作。
descry doctor
```
### `descry doctor`
首次运行时,解析链很深 — cgo、ONNX运行时库、模型文件、索引及其指纹 — 当它正常工作时,所有这些都是不可见的。`doctor`一次性报告整个链条:
```
✓ 构建 descry 0.1.0 darwin/arm64 go1.26.2 cgo启用
✓ onnxruntime /opt/homebrew/lib/libonnxruntime.dylib (系统安装)
✓ 模型 all-MiniLM-L6-v2 (缓存,86.2 MB)
– 索引 .descry/index.db中的483个块 — 已过时:嵌入器all-MiniLM-L6-v2 → all-MiniLM-L6-v2-q8
下一次索引或搜索重建会自动进行
✓ 嵌入缓存 1.0 MB — 重建会重用这些向量
– 代理技能 未安装
修复:运行`descry skill install`以便编码代理使用描述进行搜索
```
它严格只读:它从不下载(未缓存的模型报告为
“首次索引时下载”而不是从诊断中获取~90MB),并且从不重建 — 它直接读取索引的记录指纹而不是打开它,因为打开具有移动指纹的索引*会清除*它按照设计。一个过时的索引会命名漂移的字段,因此惊讶的重建有一个可见的原因。`✗`标记描述无法正常工作的组件并退出1,因此脚本和CI可以控制它;`–`是建议性的。环境中遗留的任何检索旋钮(`DESCRY_VEC_WEIGHT`等)都会被报告,因为过时的覆盖会默默地更改每个结果而没有任何其他症状。
针对冷仓库的查询会请求同意(命名目录)并
然后索引并回答一次运行;非交互式运行永远不会提示或
默默索引 — 它们会退出并带有`descry index`提示。后续运行
重用持久索引。索引在stderr上显示进度条。
### MCP
`descry mcp [dir]`通过stdio使用模型上下文协议进行通信,因此代理可以通过五个工具查询您的仓库:`search`、`read_relevant`(搜索+
内联源)、`graph_impact`、`graph_trace`和`status`。
配置它一次,全局生效,它会跟随您打开的项目:
```json
{
"mcpServers": {
"descry": { "command": "descry", "args": ["mcp"] }
}
}
```
无需项目特定配置。仓库在每次调用时解析 **,按顺序:**
1. 调用自己的`root`参数 — 让代理针对多个打开的项目之一, 按路径或仅按名称(`"kubernetes"`);
2. 显式路径(`descry mcp /path/to/repo`或`DESCRY_ROOT`)— 将服务器固定到一个仓库;
3. 客户端的**MCP根目录** — 编辑器当前打开的项目,因此一个全局服务器为每个您切换到的项目提供服务;
4. 进程工作目录,作为最后的手段。
每个结果都命名了它来自的仓库,因此来自错误
项目的答案很明显而不是沉默的。如果几个项目是打开的,`status`
列出它们所有具有索引状态,并且`root_source`解释了哪个规则
选择了当前的项目。
**索引从不阻塞调用。** 查询一个尚未索引的仓库
会在后台启动构建并立即返回进度(`"索引中...: 4200/95953块嵌入(4%)"`),因此即使一个非常大的
仓库也不能阻塞或超时请求 — 几秒钟后重试。运行
`descry index`以使结果立即可用。
仓库延迟打开并缓存每个根目录,因此切换项目只需
打开另一个。索引写入`<root>/.descry/`,因此根目录必须是
可写的 — 如果不是,工具调用会返回一个明确的错误而不是
服务器死亡。
### 代理技能
`descry skill install`将`/descry`技能写入用户级代理
技能目录(`~/.claude/skills`和`~/.agents/skills`,由Claude
Code、Codex、OpenCode等读取)。该技能教会代理何时调用
描述 — 按含义、概念或错误消息定位代码 — 以及如何驱动
MCP工具和CLI备用。 它是一个文件,一次安装在整个机器上;
重新运行命令以升级描述后刷新它。 提交的副本
位于`skills/descry/SKILL.md`,从`internal/skill`生成(`make
skill`)。
### 配置
运行时可以覆盖检索权重和深度(无需重建),这使得
调整很便宜 — 构建索引一次并用不同的值重新运行`eval`:
| 环境变量 | 含义 | 默认值 |
| --- | --- | --- |
| `DESCRY_VEC_WEIGHT` | 向量排名的融合权重 | 1.1 |
| `DESCRY_LEX_WEIGHT` | 块级BM25排名的融合权重 | 0.5 |
| `DESCRY_LEXFILE_WEIGHT` | 整个文件BM25排名的融合权重 | 1.0 |
| `DESCRY_RRF_K` | RRF常数(越大,排名越高) | 25 |
| `DESCRY_FUSE_ALPHA` | 共识表:1 = 普通RRF总和,0 = 仅最佳列表 | 0.40 |
| `DESCRY_CAND_MULT` | 每个排名器的候选人深度,作为topK的倍数 | 8 |
| `DESCRY_BM25_PATH` | 设置为`0`以从BM25中删除路径/符号令牌 | 开启 |
| `DESCRY_RERANK_MULT` | 从int8向量扫描中为精确float32重排序保留的候选人 | 6 |
| `DESCRY_MODEL` | 设置为`q8`以使用int8量化模型(索引更快) | fp32 |
| `DESCRY_ORT_LIB` | 您自己的onnxruntime共享库的路径 | 自动提供 |
## 工作原理
```
遍历 → 块 → 嵌入 → 存储 (索引)
查询 → 嵌入 ┐
├─ 排名融合 → 结果 (搜索)
查询 → BM25 ─┘
```
每个阶段都位于一个小的接口后面(`Chunker`、`Embedder`、`Store`),因此
实现可以互换而无需触及调用者。
### 检索
查询生成 **三个排名**,每个排名折叠为文件(文件的排名是
其最佳块的排名)后再融合:
1. **向量** — MiniLM嵌入的余弦;
2. **块BM25** — 词汇匹配在声明粒度上,仍然
表面一个大型文件中的一个清晰匹配函数;
3. **整个文件BM25** — 第二个BM25,其文档是整个文件,因此
词频和长度归一化在检索的实际单元上运行。
最强的词汇信号。
融合基于排名(跨排名器的分数不可比较)但不是普通的
RRF:`score = best_list + α · (sum − best_list)`,其中α = 0.40,因此一个
强烈有信服力的排名器可以带来一个结果,而其他排名器则忽略了它 — 普通
求和RRF埋葬了那些确切的。两个额外的信号有助于查询命名一个
组件:每个块的路径和符号令牌加入其BM25袋中,每个块嵌入为
`path\ncontent`,因此向量携带组件名称。
两个半部分都索引而不是扫描:BM25加载持久的倒排索引,向量搜索扫描一个int8量化的矩阵,然后重排序顶部的
候选人在确切的float32中 — 无损(经过详尽的float
搜索验证),在int8速度上。在Kubernetes(~96k块)上,这保持启动在
~0.5s和一个温暖的查询在~25ms。
### 重新索引和指纹
索引记录一个指纹:模式版本、管道版本、嵌入器ID
和维度,以及块器ID。任何不匹配时打开都会清除和重建
索引透明地 — 无需手动失效,无需过时结果。重建很便宜
因为向量存储在一个单独的读穿缓存中,按
`(embedder id, sha256 of the embed text)`键控,因此索引清除时
只有新的或编辑的块才会触及模型。
`pipelineVersion`(在`cmd/descry/main.go`中)在更改时递增
改进了存储数据的*质量*。每个增幅的来源:
| 版本 | 更改 |
| ------- | ------ |
| 1 | 基线:AST块、BM25 + RRF、调用图。 |
| 2 | 从索引中排除 `_test.go` 和生成的文件。 |
| 3 | 每个块嵌入为 `path\ncontent`,以便向量携带组件名称。 |
### 嵌入
**all-MiniLM-L6-v2**(384维),标准的句子转换器配方:
WordPiece分词 → ONNX前向传递 → 掩码加权均值池化 → L2
归一化。推理运行在ONNX运行时(~4.7ms/块)跨越一个
`GOMAXPROCS`工作池;`DESCRY_MODEL=q8`交换int8模型以获得~1.9×
更快的索引速度,但质量会略有下降。查询延迟受检索绑定,
不是模型绑定 — 查询嵌入一个短字符串,缓存后首次使用。
### 衡量质量
Kubernetes语料库(~96k块,排除测试和生成代码),120
个关键词密集查询,金牌 = 每个文件,全部默认设置:
| 模型 | R@5 | R@10 | R@20 | MRR |
| --- | --- | --- | --- | --- |
| q8 | 88.3% | 95.8% | 98.3% | 0.720 |
| fp32 | 89.2% | 95.8% | 98.3% | 0.726 |
66个自然语言查询集在descry自己的源代码上读取R@10 ~97%。发货
的权重是Kubernetes集上扫描的优化结果;`descry eval`
针对当前配置重新评估任何标记的查询集。
## 布局
```
cmd/descry/ CLI(索引/状态/搜索/图/评估/mcp/技能)
cmd/genskill/ 从internal/skill生成skills/descry/SKILL.md
skills/descry/ 提交的代理技能(生成 — 编辑internal/skill)
internal/core/ 共享域类型(Chunk, SearchResult)
internal/chunk/ 文件 → 块(LineChunker, ASTChunker)
internal/tokenize/ camelCase / snake_case标识符分割
internal/embed/ 文本 → 向量(ONNX MiniLM, HashEmbedder)+ 缓存 + 模型/运行时获取
internal/store/ 块存储 + 最近邻(MemStore, SQLiteStore)
internal/search/ BM25, RRF融合, 混合检索器
internal/graph/ 依赖/调用图(基于名称和类型)+ Mermaid
internal/index/ 遍历 → 块 → 嵌入 → 存储的编排器
internal/eval/ 召回率@k, MRR, 评估框架
internal/mcp/ 模型上下文协议服务器(stdio)公开工具
internal/skill/ 规范的代理技能内容 + 用户级安装程序
```
## 需求
- Go 1.26+(按`go.mod`)和C编译器(cgo)构建。
- onnxruntime共享库在首次使用时下载;无需手动安装。
## 许可证
[Apache-2.0](./LICENSE)