内容
# 标记者
[](https://github.com/d-weg/Marksman/actions/workflows/ci.yml)
[](LICENSE)
**为编码代理提供精确的代码检索和类型检查编辑,基于MCP。**
Marksman 是一个本地优先的 [模型上下文协议](https://modelcontextprotocol.io) 服务器,用 Rust 编写,为 AI 编码代理提供了一种可靠的方式来处理代码库:
- **查找** 与任务相关的精确代码 — 编译器准确的符号和导入图,与语义和关键词搜索相结合,以行范围引用返回,而不是整个文件。
- **安全更改** — 结构化编辑(重命名、移动、替换等)原子应用,并在 **编辑影响范围** 内进行 **类型检查**,然后再提交。跨文件重命名是一个调用,如果可能导致类型错误,则不会提交任何内容。
这两个功能都通过 **两个MCP工具** 公开:`inspect` 用于读取,`apply_edits` 用于写入。
## 为什么
编码代理在搜索上下文时花费了大量令牌,而盲目字符串编辑经常会破坏编译。Marksman 解决了这两个问题:检索直接为代理提供正确的行范围,每次编辑都由语言自身的编译器验证 — 跨更改文件 *和* 依赖于它们的 文件 — 然后提交。当广泛更改被拒绝时,拒绝会枚举每个受影响的站点,并附带一个现成的修复程序,以便类型检查器执行站点查找。
在 10 个任务的端到端代理基准测试中,使用 Marksman 的代理以 **约 45% 的较低成本** 和 **约 42% 的较少壁钟时间** 完成了所有任务(存储库范围的类型重命名:3 次尝试而不是 22 次)。方法论、消融和注意事项:[docs/benchmarks.md](docs/benchmarks.md)。
## 工具
| 工具 | 目的 |
|---|---|
| `inspect` | 读取和定位代码。一个工具,通过 `mode` 调度:`search` (按概念或任务描述查找代码)、`symbol` (名称 → 可寻址句柄)、`file` (文件的符号和导入)、`node` (一个符号的完整源代码或子节点)、`map` (目录/架构概览)。完全本地运行 — 无需 API 调用。 |
| `apply_edits` | 应用结构化编辑:`rename` 、 `move_file` 、 `replace_text` 、 `replace_node` 、 `set_body` 、 `add_parameter` 、 `add_symbol` 等。编辑是原子的,并在影响范围内进行类型检查;符号按名称寻址,因此命名目标无需预先查找。 |
`inspect` 返回的句柄直接馈入 `apply_edits`。
## 语言支持
| 层级 | 语言 | 检索 | 编辑门控 |
|---|---|---|---|
| **稳定** | TypeScript, Rust | 编译器准确(SCIP)+ AST | 由语言自己的工具链进行类型检查,覆盖影响范围 |
| **实验性** | Java, PHP, Swift | AST + 导入图 | 类型检查(`javac` / PHPStan / `swift build`),跨文件重命名通过 LSP — 已落地,但尚未基准测试验证 |
| **尽力而为** | Python, JavaScript, Go, Ruby, C, C++ | AST + 导入图 | 仅语法门控(拒绝不再解析的更改);重命名通过仓库范围的扫描验证,并为不可达站点提供建议的修复程序 |
每个响应都明确说明其承担的保证(`gated: true/false` — Marksman 绝不会静默降低门控)。每种语言的详细信息、工具链和状态:[docs/languages/](docs/languages/).
核心是与语言无关的;添加语言首先需要一个 tree-sitter 语法和一个编译器门控,带有 SCIP 索引器作为 monorepos 和跨包分析的成熟步骤。参见 [roadmap](docs/roadmap.md).
## 工作原理
- **读取路径。** 每个语言的索引器(如果可用,则为 SCIP)与进程内 tree-sitter 解析相结合,用于子符号结构,通过小型原生代码嵌入模型嵌入(无需 GPU,无需嵌入服务器),并为混合检索编制索引:BM25 + 向量搜索 + 符号匹配,与互惠排名融合和沿导入图扩展。
- **写入路径。** 持久的、预热的门控引擎在内存虚拟文件系统后应用编辑,并在 **影响范围** 内对类型诊断进行 diff — 更改文件及其导入者 — 与预编辑基准线。只有干净的 diff 才会落地。
- **快速启动。** 索引由源代码的内容哈希指纹缓存和验证;不变仓库的热启动需要 ~0.1s。如果存在疑虑,则会触发重新索引 — 绝不会加载过时的数据。
- **读取保持最新。** 符号范围在每次读取时都会重新锚定到文件内容,并且已提交的编辑会立即被描述回索引 — 您刚刚添加的符号在重新索引之前对 `inspect` 可见。
架构详情:[docs/architecture.md](docs/architecture.md).
## 安装
三个步骤设置 — 构建、索引、注册 — 加上一个一次性的 **代理前奏** (步骤 4),使您的编码代理实际调用工具。构建之外的所有操作都与语言相关,并且是惰性的:仅当存储库实际包含语言时,才需要工具链。
### 先决条件
- **Rust** (稳定版),用于构建 Marksman — <https://rustup.rs>
- 嵌入模型(~65 MB)在首次 `index` 时自动下载(参见下面的离线设置)
- *TypeScript 存储库:* Node 18+
- *Rust 存储库,用于类型检查编辑:* rust-analyzer (`rustup component add rust-analyzer`)
`marksman doctor <repo>` 报告存储库需要的所有内容、已安装的内容以及如何安装缺少的任何内容。缺少的工具链仅禁用该语言,并带有明确的报告。
### 1. 构建
```bash
git clone https://github.com/d-weg/Marksman.git
cd Marksman
cargo build --release # → target/release/marksman(CLI)和 marksman-mcp(MCP 服务器)
```
### 2. 索引您的存储库
```bash
target/release/marksman index /path/to/your/repo # 写入.marksman/
# 可选的健全性检查:
target/release/marksman retrieve /path/to/your/repo "where is the rate limiter"
```
### 3. 注册 MCP 服务器
**Claude 代码:**
```bash
claude mcp add marksman -- /absolute/path/to/Marksman/target/release/marksman-mcp
```
**任何 MCP 客户端:**
```json
{
"mcpServers": {
"marksman": {
"command": "/absolute/path/to/Marksman/target/release/marksman-mcp"
}
}
}
```
服务器在启动的存储库(其工作目录)上运行;传递 `--root /path/to/repo` 或设置 `MARKSMAN_ROOT` 以指向其他位置。
### 4. 将您的代理指向工具
注册服务器使工具 **可用** — 但它并不能使代理 **使用** 它们。让编码代理默认使用工具,否则它会恢复到 grep + 手动编辑,您永远不会看到 Marksman 的类型检查、单次调用编辑。在您的存储库的 `CLAUDE.md` (或 `AGENTS.md` 或客户端系统提示)中添加以下前奏,以便代理默认调用工具。这是 **确切的提示**,我们的基准测试套件使用的是已发布的数字测量的提示:
```text
You have marksman MCP tools. They are DEFERRED — load them FIRST, in ONE call, with their FULL names:
ToolSearch query="select:mcp__marksman__apply_edits,mcp__marksman__inspect"
What they do: apply_edits (ALL code edits — structural + surgical, type-checked before landing when the language has a checker; TRUST the reply — never re-verify by hand), inspect (ALL reads/locating — mode: search|symbol|file|node|map).
Then EDIT WITH THE TOOL, not grep+Edit: if the task NAMES the symbol, call apply_edits by name DIRECTLY — don't locate it first; if the task also gives the FILE, address as `file#name` (e.g. `src/http/retry.ts#parseResponse`) so it resolves in ONE call; else a bare name works and an ambiguous one just returns candidate ids to re-issue with. This holds even for a ONE-LINE change (change a default, fix a value) — use apply_edits replace_text by name; do NOT reach for Grep/Bash/Read+Edit for a small edit. It's verified server-side and needs no separate search.
File moves/renames: send the BARE move_file/rename as your FIRST action — no find/grep/read survey first, no helper edits alongside (in type-checked languages, imports, module declarations, and needed module files are all part of the one action; ungated replies say exactly what remains). The reply shows every line it rewrote — exactly what a survey would have found — and the gate rejects safely if anything is off.
```
<details>
<summary>离线/断网,或自定义模型位置</summary>
手动放置模型,将其放在默认路径,Marksman 找到它,无需配置:
```bash
mkdir -p ~/.marksman/models/potion-code-16M
curl -fL --output-dir ~/.marksman/models/potion-code-16M \
-O https://huggingface.co/minishlab/potion-code-16M/resolve/main/model.safetensors \
-O https://huggingface.co/minishlab/potion-code-16M/resolve/main/tokenizer.json \
-O https://huggingface.co/minishlab/potion-code-16M/resolve/main/config.json
```
将 `CI_MODEL_DIR` 指向该目录,保持其他位置。`CI_NO_MODEL_FETCH=1` 禁用首次使用时的自动下载。
</details>
## 容器模式(可选)
无需在主机上安装每种语言的工具链,而是从每种语言的 OCI 镜像中运行:一个容器运行时代替 N 个工具链,版本固定,因此主机工具链升级不会静默更改判决。
```bash
scripts/marksman-images.sh check # 运行时?镜像?同步的 pin?
scripts/marksman-images.sh build ts rust # 构建您需要的镜像
CI_SANDBOX=oci marksman-mcp --root /path/to/repo
```
容器模式是严格选择加入的,失败是响亮的 — 缺少运行时或镜像被报告,而不是用不同的工具链掩盖。完整演练:[docs/container-guide.md](docs/container-guide.md).
## CLI
```
marksman index <repo> # 构建/刷新索引(.marksman/)
marksman retrieve <repo> "<task>" [--top N] [--json] # 查询索引
marksman doctor [<repo>] # 每种语言的依赖报告;
# 如果不健康,则退出 1
```
## 配置
Marksman 无需配置即可工作。对于非默认设置:
| 变量 | 含义 | 默认值 |
|---|---|---|
| `MARKSMAN_ROOT` | MCP 服务器的存储库根目录 | 当前目录 |
| `CI_MODEL_DIR` | 嵌入模型目录 | `~/.marksman/models/potion-code-16M` |
| `CI_NO_MODEL_FETCH` | 禁用首次使用模型下载 | 未设置(启用下载) |
| `CI_SANDBOX` | `oci` 在容器中运行每种语言的工具链 | 主机工具链 |
| `CI_SANDBOX_RUNTIME` | 要使用的 OCI 运行时 CLI | PATH 上的 `container`/`docker`/`podman`/`nerdctl` 之一 |
| `CI_GATE_TIMEOUT_SECS` | 门控判决的时钟天花板;超时拒绝编辑 — 它永远不会通过或降低门控 | `600` |
| `CI_EDIT_ENGINE` | 强制 TypeScript 编写引擎层级:`tsgo` · `tsmorph` · `lsp` | 自动选择 |
| `CI_TSGO` | `tsgo` 二进制文件路径(TypeScript 7 原生)— 最快的门控层级 | 未设置(探测 PATH) |
| `CI_NPM_CACHE` | `npx` 获取工具的 npm 缓存目录 | 系统临时目录 |
| `CI_SCIP_<LANG>` | 按语言启用/禁用 SCIP 索引器(`1`/`0`) | rust: 开启 · 其他:配置值 |
| `CI_PROVIDER` | `sidecar` 以单独的进程运行语言提供程序 | 进程内 |
存储库根目录中的可选 `marksman.config.json` 重写检索和索引设置(前 N 名、融合权重、每种语言的 SCIP 等)。
## 项目状态
Marksman 正在积极开发中。TypeScript 和 Rust 是参考实现 — 完全门控和基准测试验证。Java、PHP 和 Swift 已落地编辑,基准测试验证正在进行中。其余语言通过通用 tree-sitter 提供程序提供语法级门控,直到其编译器门控落地。
代码库是一个 16-crate Rust 工作区:一个与语言无关的核心(`ci-*` crate)加上 `crates/langs/` 下的每种语言提供程序,坚持零警告策略,并在 CI 中进行单元测试,真正的工具链集成套件在提供程序更改之前运行。参见 [docs/](docs/) 了解架构、提供程序合同、路线图和基准测试。
## 贡献
欢迎 PR — [CONTRIBUTING.md](CONTRIBUTING.md) 涵盖构建、两层测试、基准测试规则和项目的不可妥协事项(绝不提供过时读取,绝不静默降低门控)。
## 许可证
MIT — 参见 [LICENSE](LICENSE).