whiskeyjimbo/veska
GitHub: whiskeyjimbo/veska
一个本地运行的代码智能守护进程,将 Go 仓库解析为代码图并通过 MCP 协议为编辑器和 AI agent 提供结构化的代码检索、分析和审查能力。
Stars: 0 | Forks: 0
# Veska
[](https://whiskeyjimbo.github.io/veska/getting-started/quickstart/)
[](https://github.com/whiskeyjimbo/veska/actions/workflows/ci.yml)
[](https://github.com/whiskeyjimbo/veska/actions/workflows/docs.yml)
[](https://goreportcard.com/report/github.com/whiskeyjimbo/veska)
[](go.mod)
[](LICENSE)

**Veska** 是一个本地代码智能守护进程。它在你的笔记本电脑上运行,将
你的仓库解析为代码图(节点 + 边),在语义上嵌入该图,
并通过 MCP 将两者服务于你的编辑器和 AI agent——因此
它们从相同的结构事实进行推理,而不是靠猜。
## 它能为你提供什么
- **有根据的结构化答案。** 每个函数、类型、文件和调用都可以
追踪到节点、边或提交。在
保存 → 暂存的新鲜度预算范围内,结构化召回保持最新。
- **最终一致的语义搜索。** `semantic_search` 使用进程内嵌入器(默认为 model2vec——
无需外部服务)嵌入图;
在索引滞后窗口期间,它会回退到 BM25 词汇索引,并
将响应当标记为 `degraded_reasons`。
- **晋升检查。** 在每次提交时,同步检查会发出建议性的
`Finding`:死代码、契约偏移、泄露的密钥,以及通过
OSV.dev 咨询数据库发现的易受攻击的 `go.mod` 依赖项。这四项默认开启:
`veska init` 会向 `~/.veska/config.toml` 写入一个活动的 `[vuln_source]` 配置块(参见
[`docs/operations/CONFIG-SURFACE.md`](docs/operations/CONFIG-SURFACE.md)),
除非你使用 `veska init --no-vuln` 选择退出(或在
交互式提示下回答“no”)。**生命周期:**该块在守护进程启动时读取,因此
它从下一次 `veska service start` 开始生效(编辑该块后请重启
服务)。新的扫描会自动识别它;要对
已晋升的仓库进行追溯扫描,请运行 `veska reindex `。
- **重复和相似代码检测。** 查找复制粘贴和漂移的克隆
以进行去重分类:`eng_find_clones` 一次用于一种符号组模式
(`exact` 字节相同,或通过存储的相似度进行 `near` 模糊匹配),以及
`eng_find_clusters` 用于跨 `exact`、
`structural`(Type-2,重命名后形状相同)和 `near` 层级的全仓库(或跨仓库)扫描,
按最紧密的优先排序。Exact/structural 是确定性哈希;near 读取
自动链接已存储的相似度分数(无新的嵌入扫描)。
- **可选的 LLM 功能。** 默认关闭的晋升后审查流水线
和按节点的摘要(由 Ollama 支持)。
- **机械化 wiki。** 从图中计算热点区域和入口点,
路径中没有 LLM。`eng_get_hot_zone` 和 `eng_get_entry_points`
MCP 工具在内存中返回数据并且不写入任何内容;`veska wiki`
CLI 将相同的数据渲染为仓库内的 `docs/veska/{hot_zones,entry_points}.md`
(可重新运行,幂等——每个
页面中的括号标记会保留管理块之外的任何手动编辑)。一个 context-pack
工具与之并存。
- **跨参与者归因。** 一个简单的 `actor_kind: human | agent | system`
枚举用于区分谁在审计日志中更改了什么。
## 进程拓扑——一个二进制文件,三种角色
`make build` 在 `bin/veska` 生成单个二进制文件;`bin/veska-daemon` 和
`bin/veska-mcp` 是它的符号链接。`cmd/veska/main.go` 中的 argv[0] 分发器将每次调用路由到各自的包中。
| 调用 | 角色 |
|---|---|
| `veska` | CLI——`init`, `repo`, `reindex`, `service`, `doctor`, `backup`, `wiki`, …。运行 `veska --help` 查看完整列表。 |
| `veska-daemon` (符号链接) | 长期运行的进程——拥有 SQLite 存储、fsnotify 监视器、嵌入器和晋升后队列。组合根:`internal/cli/daemon/wire.go`。 |
| `veska-mcp` (符号链接) | 轻量级的 stdio 垫片,将编辑器的 MCP 连接代理到守护进程的 Unix 套接字。路由到 `internal/cli/mcp`。 |
## 要求
- **Go 1.26+**
- **目前仅支持 Go 仓库。** tree-sitter 解析器提供了一个单一的
Go 语法,因此代码图是由 `.go` 文件构建的。其他语言是
经过深思熟虑的未来步骤,而不是当前的能力。
- **核心用途无需外部服务。** SQLite、向量索引和
默认嵌入器都在进程内运行。全新的机器无需
安装或运行任何其他东西即可进行索引和搜索。
### 嵌入器
语义搜索需要一个嵌入器。Veska **在启动时按照优先**
顺序选择一个——它从不混合向量空间,因此一次只有一个嵌入器拥有索引:
1. **model2vec** (`potion-code-16M`)——一个快速的、进程内的静态 *代码*
嵌入器。默认且推荐的选择。可以通过任一方式获取:
- **胖二进制文件** (`make build`, 默认)——模型被编译到
二进制文件中。零设置:无需安装,无需下载,无需网络。
- **瘦二进制文件** (`make build-small`) + `veska install model2vec`——
一次性将约 62 MB 下载到 `~/.veska/`。
2. **static-v2**——一个位于二进制文件中的回退方案,在根本没有模型文件的
情况下工作(质量较低)。仅在 model2vec 不可用时使用。
搜索不需要 Ollama、网络和单独的进程。
### 可选:Ollama
Ollama **仅**用于可选的 **LLM 功能**——晋升后
**审查流水线**和按节点的 **摘要**(两者默认关闭)。它
**不**用于默认配置中的嵌入。(高级用户可以使用 `VESKA_EMBEDDER=ollama` 强制使用
Ollama 嵌入模型,但 model2vec 在代码上速度更快且
质量更高,因此这很少值得。)
仅在需要这些 LLM 功能时安装 Ollama:
```
# macOS: brew install ollama && ollama serve &
# Linux (snap): sudo snap install ollama && ollama serve &
# Linux (curl): curl -fsSL https://ollama.com/install.sh | sh && ollama serve &
```
## 构建
默认情况下,`make build` 是胖二进制文件——它将
model2vec 权重嵌入到二进制文件中,因此安装是零设置的:无需单独
下载,无需网络,启动时没有 static-v2 回退。
```
make build # default: ~104 MB fat binary (model2vec ~62 MB embedded
# into a ~42 MB thin build). Zero setup at runtime.
make build-small # ~42 MB thin: veska, veska-daemon, veska-mcp (+ layercheck).
# Use this only when you want size-sensitive binaries
# (CI, containers); you must then run `veska install model2vec`
# to avoid booting on the low-quality static-v2 fallback.
make test # go test ./...
make all # build-small + test + vet + lint + layercheck
# (uses the thin build to keep the test loop fast)
```
二进制文件位于 `./bin/` 中。使用 `export PATH="$PWD/bin:$PATH"` 或在下面的快速入门中使用
`./bin/` 前缀。
### 安装到你的 `PATH`
执行 `make build` 之后,一步将二进制文件放入用户 bin 目录中:
```
make install # → ~/.local/bin (default)
VESKA_INSTALL_DIR=/usr/local/bin sudo make install # system-wide
```
对于自包含的 tarball(三个胖二进制文件 + `install.sh` + 一个
README),运行 `make release-archive`。位于
`dist/veska---.tar.gz` 的归档文件与未来的
GitHub 发布版本格式相同——解压后的目录中运行 `./install.sh`
与执行 `make install` 效果相同。
## 快速入门
```
# 1. 构建 veska(默认:fat,零配置 embedder)。
make build
# 对大小敏感的构建可以改用 `make build-small`,然后运行
# `./bin/veska install model2vec` 以避免使用低质量的 static-v2 fallback。
# 2. 在 ~/.veska/ 初始化 veska 的数据目录。
./bin/veska init
# 3. 启动 daemon。
# # 选择一项:
# - 只是随便试试?将其放入后台运行: ./bin/veska-daemon &
# - 希望每次开机自动运行,崩溃时
# 自动重启,日志保存在 ~/.veska/logs 下? 使用下面的 service 形式。
# # 对于真正的安装,请将其作为 OS service 运行(在 Linux 上使用 systemd --user,
# 在 macOS 上使用 launchd)。使用 `./bin/veska service uninstall` 卸载。
./bin/veska service install
./bin/veska service start
# 4. 注册 repo。--wait 会阻塞直到冷扫描完成(大多数 repo 只需几秒
# ),这样下面的首次搜索就已经是热状态。
# 不加 --wait,扫描会在后台开始;接下来的
# `eng_search_semantic` 调用可能会返回 `[]` 且带有
# `degraded_reasons=embeddings_pending`,直到索引完成。
# 使用 tail 查看 ~/.veska/logs/daemon.log 中的 "cold scan: complete" 行。
./bin/veska repo add /path/to/your/repo --wait
# 5. 健全性检查。
./bin/veska doctor status
```
第一次 `veska repo add` 注册仓库,使用指向 `veska` 二进制文件的绝对路径
安装 git post-commit 钩子,并通过
守护进程调度冷扫描。随后的提交通过守护进程的 MCP 套接字上的 `eng_promote_repo` 驱动晋升。
要强制重新扫描已注册的仓库(例如在模型切换之后):
```
./bin/veska reindex /path/to/your/repo
```
在守护进程运行时执行是安全的——CLI 通过
守护进程的 `eng_reindex_repo` MCP 工具调度冷扫描,因此你的
编辑器的 MCP 连接不会中断。在守护进程停止的情况下,
相同的命令将回退到直接的进程内重新解析。
### 第一次调用——60 秒完整性检查
一旦 `cold scan: complete` 显示在 `~/.veska/logs/daemon.log` 中,从 shell 驱动两个
MCP 工具,以便在将编辑器指向守护进程之前看到真实的输出:
```
# 按名称查找 symbol。非限定匹配即可——"Run" 可以找到
# Server.Run、Command.Run 等,且精确匹配排在最前。
printf '{"jsonrpc":"2.0","id":1,"method":"eng_find_symbol","params":{"symbol":"Run"}}\n' \
| ./bin/veska-mcp | jq '.result.nodes[0]'
# 自然语言搜索;结果包含内联 snippets,因此通常无需后续的
# Read。
printf '{"jsonrpc":"2.0","id":1,"method":"eng_search_semantic","params":{"query":"parse config"}}\n' \
| ./bin/veska-mcp | jq '.result.results[:3]'
```
任一工具的响应都应包含 `file_path`, `line_start/line_end`,
和一个 `name`——如果你看到了这些,说明守护进程正在正确解析并提供你的
仓库。在嵌入完成填充之前,`eng_search_semantic` 在刚注册的
仓库上可能会返回 `[]`;检查
`eng_get_status` 的 `pending_embeds` 计数。
### 编辑器集成
将你的 MCP 客户端指向 `bin/veska-mcp` 作为 stdio 命令。将
`/abs/path/to/bin/veska-mcp` 替换为你机器上的实际路径。
**Claude Desktop** (macOS 上位于 `~/Library/Application Support/Claude/claude_desktop_config.json`;
Windows 上位于 `%APPDATA%\Claude\claude_desktop_config.json`):
```
{
"mcpServers": {
"veska": {
"command": "/abs/path/to/bin/veska-mcp"
}
}
}
```
**Cursor** (`~/.cursor/mcp.json`) 和 **Zed** (`~/.config/zed/settings.json`,
在 `context_servers` 下) 接受相同的 `command` 格式。
**Continue** (`~/.continue/config.yaml`):
```
mcpServers:
- name: veska
command: /abs/path/to/bin/veska-mcp
```
### 每个 agent 的指令片段
`veska init --agent ` 在当前项目中写入(或安全地追加到)特定于 agent 的
指令文件,以便 agent 知道 Veska 工具
表面可用。支持的 agent:
`claude`, `codex`, `copilot`, `cursor`, `gemini`, `kiro`, `opencode`。
```
cd /path/to/your/repo
./bin/veska init --agent claude # creates or updates CLAUDE.md
```
该片段被 `` 标记括起来,因此重新运行
命令只会更新 Veska 部分,而保留文件的其余部分不变。
如果你的 `VESKA_HOME` 是非默认的,请将其传递过去:
```
{
"mcpServers": {
"veska": {
"command": "/abs/path/to/bin/veska-mcp",
"env": { "VESKA_HOME": "/path/to/veska/home" }
}
}
}
```
### 从 shell 调用工具
跳过编辑器并直接驱动 `veska-mcp`——这非常适合调试或
脚本编写。协议是换行符分隔的 JSON-RPC;方法就是
工具名称(没有 `tools/call` 封装):
```
printf '{"jsonrpc":"2.0","id":1,"method":"eng_get_status","params":{}}\n' \
| ./bin/veska-mcp \
| jq .
printf '{"jsonrpc":"2.0","id":1,"method":"eng_find_symbol",
"params":{"repo_id":"","branch":"main","symbol":"Foo"}}\n' \
| ./bin/veska-mcp | jq .result
```
从 `eng_list_repos` 获取 ``。完整的工具表面在下面的
[MCP 工具](#mcp-tools) 中。
### 配置
状态位于 `~/.veska/` (`VESKA_HOME`) 下。守护进程配置是
`~/.veska/config.toml`——参见 [`docs/operations/CONFIG-SURFACE.md`](docs/operations/CONFIG-SURFACE.md)。
关键环境变量:
| 变量 | 用途 | 默认值 |
|---|---|---|
| `VESKA_HOME` | 数据根目录 | `~/.veska` |
| `VESKA_EMBEDDER` | 嵌入器选择:`auto` (model2vec→static-v2),或强制 `model2vec` / `static` / `ollama` | `auto` |
| `VESKA_VECTOR_BACKEND` | `memory` (进程内 `memvec` 线性扫描) 或 `usearch` (HNSW) | `memory` |
| `VESKA_OLLAMA_URL` | Ollama 端点——LLM 审查 + 摘要,以及 `VESKA_EMBEDDER=ollama` | `http://localhost:11434` |
| `VESKA_EMBED_MODEL` | Ollama 嵌入模型——仅在 `VESKA_EMBEDDER=ollama` 时有效 | `nomic-embed-text` |
选定的嵌入器记录在 `~/.veska/embedder.locked` 中。切换
嵌入器需要重新索引(`veska reindex`),因为它们的向量不可
比较。
## 架构
```
cmd/veska/ single binary entry point; argv[0] dispatcher in main.go
internal/
core/
domain/ pure entities: Node, Edge, Graph, Task, Finding
ports/ interface contracts (GraphStorage, VectorStorage, VulnSource, …)
application/ use-case services: ingester, promoter, embedder, checks, review, wiki
cli/ composition roots: daemon/wire.go and the mcp stdio shim
infrastructure/ adapters: sqlite, vector, embedding/{model2vec,static,ollama,elect}, treesitter, mcp, git
repo/ repos-table registry
platform/ cross-cutting operational concerns (config, doctor, health, …)
docs/ user manual, architecture summary, and operational runbooks
```
## MCP 工具
守护进程通过 Unix 套接字 JSON-RPC 服务器公开了 38 个工具(由
`veska-mcp` 转发给编辑器)。工具名称遵循 `eng_<动词>_<对象>`。快速映射:
| 家族 | 工具 |
|---|---|
| 管理 | `eng_get_status`, `eng_get_config`, `eng_get_current_repo`, `eng_get_repo`, `eng_list_repos` |
| 仓库生命周期 | `eng_add_repo`, `eng_remove_repo `eng_promote_repo`, `eng_reindex_repo`, `eng_set_repo_alias`, `eng_remove_repo_alias` |
| 图 | `eng_find_symbol`, `eng_get_node`, `eng_get_file_nodes`, `eng_get_call_chain` |
| 搜索 | `eng_search_semantic`, `eng_search_similar`, `eng_find_related` (位于 `file_path`+`line` 代码的语义邻居) |
| 重复项 | `eng_find_clones` (针对一种模式的重复组:`exact` 字节相同或 `near` 模糊匹配), `eng_find_clusters` (跨 `exact`/`structural`/`near` 层级的全仓库 / 跨仓库去重分类,最紧密的优先) |
| 影响范围 | `eng_get_blast_radius`, `eng_get_diff_blast_radius`, `eng_get_dirty_blast_radius` |
| 上下文 | `eng_get_context_pack`, `eng_find_changed_symbols` (接受 `ref_a`/`ref_b` 或别名 `base`/`head`;默认为 `HEAD~1..HEAD`;分块已过滤,仅注释的差异在 `degraded_reasons` 中显示 `non_symbol_changes_only`) |
| 依赖项 | `eng_list_dependencies` (仓库调用的外部模块,按调用点计数排序) |
| 杂项 | `eng_find_owner`, `eng_find_todos` |
| 发现 | `eng_list_findings`, `eng_get_finding`, `eng_close_finding`, `eng_reopen_finding` |
| 抑制 | `eng_list_suppressions`, `eng_get_suppression`, `eng_suppress_finding`, `eng_close_suppression` |
| Wiki | `eng_get_hot_zone`, `eng_get_entry_points` |