whiskeyjimbo/veska

GitHub: whiskeyjimbo/veska

一个本地运行的代码智能守护进程,将 Go 仓库解析为代码图并通过 MCP 协议为编辑器和 AI agent 提供结构化的代码检索、分析和审查能力。

Stars: 0 | Forks: 0

# Veska [![快速入门](https://img.shields.io/badge/Quick_Start-blue)](https://whiskeyjimbo.github.io/veska/getting-started/quickstart/) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/550c97efad105819.svg)](https://github.com/whiskeyjimbo/veska/actions/workflows/ci.yml) [![文档](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/67b5002460105820.svg)](https://github.com/whiskeyjimbo/veska/actions/workflows/docs.yml) [![Go 报告卡](https://goreportcard.com/badge/github.com/whiskeyjimbo/veska)](https://goreportcard.com/report/github.com/whiskeyjimbo/veska) [![Go 版本](https://img.shields.io/github/go-mod/go-version/whiskeyjimbo/veska)](go.mod) [![许可证: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE) ![Veska - 自然语言查询输入,精确的文件:行号答案输出](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/5309468552105827.gif) **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` |