Hutch:为轻度边缘化者打造的 Emacs 本地代码审查

Lobsters Hottest 工具

摘要

作者介绍了 Hutch,这是一个基于开源 Emacs/Magit 的界面,可在本地对已暂存、未推送或分支 diff 的变更运行 LLM 驱动的代码审查,让开发者在熟悉的键盘驱动工作流中排队并批量应用建议补丁。

<p><a href="https://lobste.rs/s/vqm1fy/hutch_local_code_reviews_emacs_for_mildly">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/06 07:21

# hutch:为那些略微被边缘化的人提供的 emacs 本地代码审查 来源:https://kitallis.in/p/hutch-a-local-code-review-interface-for-magit/ 我已经四年没有正经工作过了。我上个月刚关掉了自己一直在做的创业公司 [Tramline](https://tramline.app/)。这些年里,我把大部分时间都花在了 AI agent 前沿的末端。不过我终于赶上来了。距离 coding agent 真正腾飞已经过去了大约 [500 天](https://en.wikipedia.org/wiki/List_of_large_language_models#2025),而它们现在确实比以前被 [指定用途](https://www.youtube.com/watch?v=U_cSLPv34xk) 的时候更有生产力了。 尽管我仍然更偏爱 AI 的教学面向,而不是它作为任务完成机器的那一面,但后者推动了围绕代码审查的各类工具的涌现——远不止编写和部署代码。 典型的 review agent 官方论调是:agent 们抢在你的同事之前跳进来,在你还没来得及醒来查看手机之前,就已经把二十个 pull request 撒满了喋喋不休的评论。这对某些人、在某些场合下是行得通的。但如果你和我一样,代码在交付给用户之前仍然由人类审查,那么尊重这些人、尊重他们的时间,是更好的做法。 无论你站在“游戏规则改变者”与“老顽固”的光谱的哪一端,情况都是如此。 说这么多,只是为了表达:无论 agent 们未来如何朝更好的方向演进,我希望我们依然*在意*一些事情。不是那种把“在意”形式化的做法——比如高保真的 agent 指令和提示词,或者某种维护品味的崇高使命感——而是简单到像是在说:*嘿,我还在这里,这一切我都懂*。 因此,作为一个长期 emacs 用户,我再一次尝试把 LLM、agent 和 coding harness 楔入你的文本缓冲区里(!),这次是通过 [Hutch](https://github.com/adjaecent/magit-hutch)。这是一个小巧的、由 Magit 驱动的代码审查界面,可以融入标准的 Magit 提交-推送工作流,*在本地运行*,并有望减轻上游(即 GitHub 等平台)带来的一些负担。 ## 快速导览 打开 Magit,按下分发器绑定键(通常是 `d`),你会看到一个 `Hutch code review` 动作出现在 DWIM 绑定旁边。 Hutch 支持三种不同的作用范围:已暂存(staged)的变更、未推送的变更,以及当前分支与工作分支之间的变更。默认只处理已暂存的变更,因为那是最有用的。 在 Hutch 中选择已暂存的变更 一旦审查开始,你会在一个新的 `*magit-hutch: code review*` 缓冲区中看到一个漂亮的进度条,直到所有发现项¹完成。这是一个只读缓冲区,但你仍然可以执行[预先绑定的操作](https://github.com/adjaecent/magit-hutch#usage)。 Hutch 正在审查变更 每个发现项都有一个类型(suggestion 建议、comment 评论或 LGTM 通过)、文件名、相关的行号、一个标题和一个描述。suggestion 额外带有补丁 diff。 Hutch 借助 [Magit](https://magit.vc/) 和 [Transient](https://github.com/magit/transient) 来渲染这些菜单,因此它的行为非常像自己的原生界面:键盘驱动的子菜单、diff 着色和高亮。 suggestion 是特殊的,因为它们可以被应用。要将一个 suggestion 标记为待应用,你按 `m` 将它加入队列。 在 Hutch 中加入待处理修复 然后按 `A` 批量应用所有排队的 suggestion。应用过程是感知作用范围的,所以如果你为已暂存的变更排队了一个发现项,修复会直接应用到已暂存的文件上。 在 Hutch 中应用修复 就是这样!对于现有的 emacs 用户来说,上手应该相当简单直观。 当然,背后有一些有趣的技术细节,我会在接下来的几节中介绍。 在缓冲区内展示审查评论和建议补丁,一个很大的 UX 缺陷是:目前不存在一个现成的、可将它们串联起来的评论系统。在 GitHub 上,review UI 会将过时的评论折叠到新提交之下,而且大多数 review bot 如果要提出修改建议,都会依赖 [suggestion 机制](https://docs.github.com/en/pull-requests/how-tos/review-pull-requests/incorporating-feedback-in-your-pull-request#applying-suggested-changes)。 Hutch 的设计偏向于补丁,而非单纯的散文式评论。根据 [Aider 排行榜](https://aider.chat/docs/leaderboards)(以及我自己的一些实验),`SEARCH/REPLACE` 形式的 diff 在不同模型之间的服从性,要远高于仅仅要求模型写出带有精确行号的正确补丁。 对于 [Aider 风格的 diff](https://aider.chat/docs/more/edit-formats.html),你需要确保有足够的上下文使 `SEARCH` 唯一,并且最好保留缩进。在 Hutch 的情况下,工具的函数 schema 自然地将*文件、搜索、替换*这几个字段分解开了: ``` src/utils.clj <<<<<<< SEARCH (defn add [a b] (+ a b)) ======= (defn add [a b c] (+ a b c)) >>>>>>> REPLACE ``` 我注意到,很多较旧(或较便宜)的模型在被要求生成 diff 时,倾向于凭记忆“回忆”出 `SEARCH` 块,而不是从 `read_file`、`read_diff` 或 `surrounding_context` 调用中逐字复制出来。这无一例外地会把 diff 弄糟。因此,我们会在提交之前对它们进行验证。 如果 `SEARCH` 缺失,或者匹配到多处,该发现项会被降级为普通的 comment。在唯一命中时,Hutch 会在本地生成一个 unified diff:SEARCH/REPLACE 块先对照文件进行验证,然后分两个阶段输出为 unified diff。 流程图分两个阶段。Agent 循环:agent 调用工具(`read_*`),然后验证 `old_lines`。如果验证结果为 AMBIGUOUS(有歧义),则修正 `old_lines` 并回到工具调用步骤。如果为 OK,则提交审查内容:评论和补丁。 后处理循环:每个补丁都要经过一个补丁验证关口。如果通过(OK),hutch 会输出一个 unified diff 并在 UI 中渲染出来。如果失败(FAIL),该补丁会被降级为普通评论,同样在 UI 中渲染。 一旦一系列 udiff 和评论被渲染出来,它们就可以被标记并批量应用。Hutch 会按文件逐一应用,从最底部的 hunk 开始(自底向上),这样对行位置的扰动最小。每个发现项都会运行自己的 `git apply`,如果应用失败,会自我标记为 `invalid`,从而让其余的补丁能够继续落地。 这一整套打补丁和评论的基础设施是物有所值的,因为只需几次按键,你就能减少阅读和解析的工作量,转而去做更多可执行的分类处理。 当然,这并不能保证补丁永远可用,也不应该如此。随着模型能力增强,一种更简单的 diff 方法可能通常也能表现不错。但对于一个要面向不同且更便宜的模型构建的工具来说,提供最大程度的支持是至关重要的。 总的来看,我觉得我们构建的许多 agentic 基础设施的一个关键要点,就是要提供一些旋钮来优化 token:cost 比率。这通常意味着要为那些“勉强够用”的模型想出棘手的变通方案。 Hutch 拥有一套相当精简的上下文获取工具集: 1. `read_diff` 2. `read_file` 3. `search_codebase` 4. `surrounding_context` 其中,`surrounding_context` 是最有趣的一个。它封装了 [Tree-sitter](https://batsov.com/articles/2026/02/27/building-emacs-major-modes-with-treesitter-lessons-learned/#why-tree-sitter),并使用已经安装好的语法。它的工作方式是允许模型向外扩展到包含相关行的更大定义块,必要时再进一步向外扩展。 在我的测试中,与直接粗暴地调用 `read_file` 相比,`surrounding_context` 的整体读取 token 消耗根据轶事来看更低,同时审查质量水平相当²。 来自模型的所有发现项会被一次性提交给 agent。在写入方面,`verify_block` 会本地验证 diff,与其他评论和 LGTM 通知一起,通过 `submit_review` 工具调用提交。`submit_review` 本身会执行一些后处理工作,比如过滤掉关于文件和行号的幻觉、裁剪描述的长度,以及在补丁无法干净应用时将其降级为评论。 一旦提交落地,所有这些工作的输出会被持久化保存在 `refs/hutch/id` 下,可以单独提交以便分享(配合 `magit-post-commit-hook`),也可以用于后续重新渲染。 如果你仔细看,它可能看起来像是一个 [stacked-diff 审查工具](https://blog.tangled.org/stacking) 的变更标识符,但它的目的是把审查保留在 git 树中,而不是为人类审查标识变更集。我们并不真正关心多方人工审查,因为一切都是本地的。 ## 评估 关于 Hutch 基准测试,唯一不太理想的地方是从文本缓冲区中提取可对比输出的过程相当笨拙。我最初通过调用多个 headless emacs 实例,并在 agent 输出渲染之前将其重定向到 `tee` 来运行评估,但最终还是选择了输出 [Perfetto](https://perfetto.dev/) trace,并以它们作为评估的基础媒介。 Hutch 评估的 Perfetto trace 我还没有在其他地方见过 agent 通过 Perfetto 进行追踪。这很可能有充分的理由——它们本来就不是为这类事情设计的。Perfetto 并没有对什么是“prompt”或什么是“tool call”的一等公民概念。它是为内核和浏览器设计的,而不是为一个充满大量散文的抽象系统。 但对于一个单人、emacs 本地的 agent 来说,它勉强够用。你可以回答各种结构性问题,比如*这次审查为什么花了 40 轮?*、*哪些工具是并行执行的?*、*在读取 diff 上花了多少实际时间?*等等。 但更重要的是,它免费且无需任何基础设施。你只需设置 `hutch-trace-dir`,它就会输出 Perfetto trace,你只需要在 [ui.perfetto.dev](https://ui.perfetto.dev/) 上加载它们即可。很简单。 下面是一个获取工具调用及其总耗时的示例。这是整条管道,不需要任何仪表盘或 SDK: ``` SELECT name AS tool, COUNT(*) AS calls, ROUND(SUM(dur) / 1e6, 1) AS total_ms FROM slice WHERE category = 'tool' GROUP BY name ORDER BY total_ms DESC; -- tool calls total_ms -- -------------------------------------- -- search_codebase 18 4210.3 -- read_diff 7 2103.1 -- surrounding_context 12 880.5 -- read_file 3 412.7 -- submit_review 1 42.9 ``` 有了这套设置,我们采用了 [Martian 的 code review](https://github.com/withmartian/code-review-benchmark) 基准测试和 [CR-Bench 预印本](https://arxiv.org/html/2603.11078v1) 中的混合策略,并计算 Precision、Recall 和 Fβ 分数。 评估的更详细描述³可以在 [eval/README.org](https://github.com/adjaecent/magit-hutch/blob/main/eval/README.org) 一节中找到。但大致来说,我们在 40 个 PR、132 个 golden 样本上运行了基准测试,并使用 GPT 5.2 作为分类评判。评估管道会直接在 trace 上执行查询。 从数字来看,我相信我们大致落在 Martian 的 [Offline Benchmark 排行榜](https://codereview.withmartian.com/?mode=offline)的第 16 名左右,对于一个无记忆、单轮出的 agent 来说,这相当有竞争力。 除了分类得分之外,agent 本身还有几件有趣的事情: 各模型命中的不同 golden 样本数量堆叠柱状图,分为独有、与其他一个模型共享、与两者共享。 - opus-4.8:15 个独有,17 个与另一模型共享,18 个与两者共享(共 50 个)。 - glm-5.2:13、19、18(共 50 个)。 - gpt-5.5:6、16、18(共 40 个)。 三个模型的并集为 78。 ``` 0102030405060708090 distinct goldens hit opus-4.8 glm-5.2 gpt-5.5 union = 78 ``` ``` 独有 | 与 1 个共享 | 与两者共享 ``` 不同的模型倾向于捕捉不同的 bug。在 132 个 golden 样本中,每个模型命中 40-50 个,三个模型之间的重叠命中为 18 个。 这意味着,理论上,如果三个模型一起运行,它们捕捉到的 bug 将比单个模型多约 55%。 我认为,模型之间对于“什么是 bug”的一致性相当糟糕。 40 个 PR 上 agent 轮次的三个柱状图,按升序排列。 - opus-4.8:中位数 8 轮,最大 26 轮。 - glm-5.2:中位数 11 轮,最大 39 轮。 - gpt-5.5:中位数 35 轮,最大 81 轮,有几个 PR 逼近 80 轮的上限。 ``` 020406080 rounds opus-4.8 glm-5.2 gpt-5.5 ``` ``` 按轮次排序的 40 个 PR | 按轮次排序的 40 个 PR | 按轮次排序的 40 个 PR ``` GPT 5.5 在命中率大致相同的情况下,触及我默认轮次上限(80)的频率远高于其他模型。Opus 4.8 完成任务所需的轮次少 3 倍。 每个有效发现项的输出 token 数的三个柱状图,每个 PR 一根柱子,按升序排列。 - opus-4.8:31 个 PR,中位数约 2,600,最大约 13,000。 - glm-5.2:34 个 PR,中位数约 3,600,最大约 15,000。 - gpt-5.5:34 个 PR,中位数约 3,000,最大约 27,500。 ``` 05,00010,00015,00020,00025,000 output tokens / useful finding opus-4.8 glm-5.2 gpt-5.5 ``` ``` 按每有效发现项 token 数排序的 PR | 按每有效发现项 token 数排序的 PR | 按每有效发现项 token 数排序的 PR ``` 在 token 效率方面,Opus 在每个好发现项所用的输出 token 上要便宜得多,优势明显,但在输入上烧掉的 context 多 3 倍,这可能是因为 Hutch 每轮都会重新发送不断增长的上下文。 ## 出师未捷身先死 如我所言,这一切可能来得太迟了。如今已经没有人真正手写代码、手审代码,或手动使用编辑器和版本控制了。 我做这个是为了我自己,为了我仍然在践行的工作流。我不想就“该不该在 emacs 中使用 LLM”这一话题发表任何主张。这个工具更多关乎解锁某一种工作流,而非 agent 在小众场景中的过度入侵。 如果它继续有用,我希望能为每个发现项添加一个对话模式(类似 CodeRabbit),或许还能维护一个从代码库中学习得来、且可以提交回代码库的上下文树,以提升审查的质量和速度。 --- ¹ 在示例中,我使用 GLM-5.2 作为底层模型,但这可以配置为 [gptel](https://github.com/karthink/gptel) 这一优秀项目所支持的任何后端。↩ ² 特性化测试和评估在 [评估](https://kitallis.in/p/hutch-a-local-code-review-interface-for-magit/#evaluating) 一节中有所涵盖,但我还没有机会通过实证验证这一说法。↩ ³ 在将这些硬性指标视为真正客观之前,还有一些偏差和细微之处需要考虑。但我在正文中省略了它们,因为它们在 [README](https://github.com/adjaecent/magit-hutch/blob/main/eval/README.org) 中有更详细的描述。↩

相似文章

Show HN: Codiff,本地差异审查工具

Hacker News Top

Codiff 是一款轻量级本地 diff 查看器,用于审查 Git 暂存和未暂存的更改,支持基于 LLM 的逐步讲解和内联审查评论。

用于 Thunderbird 的 Git 邮件补丁审核插件

Lobsters Hottest

这是一个 Thunderbird 附加组件,用于审核来自电子邮件的 git 补丁,允许用户对代码块进行评论、以邮件列表回复的形式发送审核意见,并将系列补丁应用到本地仓库。