代码审查这件事,做久了你会发现一个尴尬的现实:真正有价值的审查意见,往往不是来自"今天有没有空帮我看下这个 PR",而是来自一个稳定、随时在线、不会因为赶进度就敷衍了事的审查者。open-code-review 这个项目,本质上就是在解决这个问题——把代码审查从"依赖人"变成"依赖流程加工具",用 CLI 加 LLM Agent 的方式,让每一次提交都能过一遍结构化的审查逻辑。它适合那些已经在用 Git 做版本管理、团队里有人懂命令行、并且希望把审查标准固化下来的开发者。不管你是刚接触 code review 的新人,还是想给团队搭一套自动化审查流水线的老手,这套思路都值得花时间吃透。
1. 为什么要在命令行里做代码审查
1.1 图形界面审查的隐性成本
大多数人第一次接触 code review,是在 Git 托管平台的网页上:打开 PR、逐行看 diff、留评论、点 approve。这套流程本身没问题,但它有几个被低估的成本。第一是上下文切换,你得从编辑器切到浏览器,找到对应的 PR,等页面加载,再一行行看——这个过程中你的注意力已经被打断了。第二是审查标准不统一,张三觉得命名要改,李四觉得无所谓,最后变成谁声音大听谁的。第三是历史记录散落,审查意见留在平台里,过半年想回溯"当时为什么这么改"基本找不到。
命令行审查的价值就在于把这些环节收拢。你在终端里敲一条命令,diff 直接出来,审查意见直接落到本地文件或者提交记录里,整个过程不离开你熟悉的工作环境。对于习惯用 Vim、Neovim 或者 VS Code 终端的人来说,这种连贯性带来的效率提升是实打实的。
1.2 LLM Agent 在审查环节能补什么
传统的静态检查工具(linter、formatter)能抓语法和风格问题,但它们看不懂"这段逻辑是不是写复杂了""这个函数名是不是误导""这个边界条件是不是漏了"。这些恰恰是 LLM 擅长的。把 LLM 包装成一个 Agent,给它 diff 内容、项目上下文、审查规则,它就能给出接近人类审查者的意见。
但这里有个关键区别:LLM 不是万能的,它会胡说,会漏掉真正的问题,也会对无关紧要的地方过度挑剔。所以 open-code-review 这类项目的核心设计难点,不是"怎么调用 LLM",而是"怎么约束 LLM 的输出,让它只说你关心的、可信的那部分"。这也是为什么它需要一个 Agent 框架,而不是简单地把 diff 丢给模型就完事。
1.3 这套方案适合什么样的团队
不是所有团队都需要这个。如果你的项目一周才提交几次,或者团队只有两三个人,人工审查完全够用。但如果你的项目每天有几十个提交、多个分支并行、审查者经常成为瓶颈,那自动化审查就能明显缓解压力。另外,如果你的团队有明确的编码规范但总是执行不到位,用 LLM Agent 把规范变成可执行的审查规则,比开会强调一百遍都管用。
提示:命令行审查不是要取代人工审查,而是把机械性的、规则明确的部分交给工具,让人专注于架构设计和业务逻辑层面的判断。
2. open-code-review 的核心工作链路拆解
2.1 从 Git diff 到审查输入的转换
整个流程的起点是 Git。open-code-review 需要拿到"这次改了什么",最直接的方式就是解析git diff的输出。但原始 diff 有几个问题:它包含大量上下文行(默认前后各三行),对于大改动来说信息量爆炸;它不包含文件的历史背景;它的格式是给机器看的,不是给模型看的。
所以第一步通常是把 diff 做一次清洗和结构化。常见做法是提取出变更的文件列表、每个文件的变更块(hunk)、每个块的新增和删除行,然后组装成模型能理解的格式。这里有个细节:要不要保留上下文行?我的经验是保留,但要限制数量。上下文太少模型看不懂改动意图,上下文太多又会稀释重点。一般前后各保留 2 到 3 行比较合适。
# 获取当前分支相对于主分支的变更 git diff main...HEAD --unified=3 # 只看变更的文件名 git diff main...HEAD --name-only # 获取某个提交的变更 git show <commit-hash> --unified=32.2 Agent 的角色划分与提示词设计
一个能用的审查 Agent,通常不是单个提示词搞定的,而是拆成几个角色。我见过比较合理的划分是:一个"概览 Agent"负责总结这次改动做了什么,一个"细节 Agent"负责逐块审查具体代码,一个"规则 Agent"负责检查是否符合团队规范。每个 Agent 的提示词侧重点不同,输出格式也不同。
概览 Agent 的提示词要引导模型关注"意图"而不是"语法",比如让它回答"这次改动解决了什么问题""有没有引入新的依赖""影响范围有多大"。细节 Agent 的提示词要给出明确的审查维度,比如可读性、边界条件、错误处理、性能隐患。规则 Agent 则需要把团队的编码规范以清单形式喂进去,让它逐条对照。
这里有个容易踩的坑:提示词里如果只说"请审查这段代码",模型会给你一堆无关痛痒的建议,比如"建议添加注释""变量名可以更清晰"。要得到有价值的输出,必须把审查维度具体化,并且明确告诉模型"如果某个维度没有问题,直接说没有问题,不要硬凑"。
2.3 审查结果的落地形式
审查结果生成之后,怎么用是个关键问题。常见的落地形式有三种:输出到终端、写入文件、回写到 Git 平台。输出到终端适合快速查看,但信息一多就刷屏了。写入文件(比如review-report.md)适合存档和分享。回写到 Git 平台(作为 PR 评论)适合团队协作,但需要额外的 API 集成。
我的建议是至少支持前两种。终端输出用颜色区分严重程度(红黄绿),文件输出用 Markdown 方便阅读。如果要做平台回写,注意控制评论数量,不要每个小问题都发一条评论,否则 PR 页面会被刷爆。比较好的做法是把同类问题合并成一条评论,或者只对严重级别以上的问题发评论。
| 落地形式 | 适用场景 | 优点 | 注意事项 |
|---|---|---|---|
| 终端输出 | 本地快速审查 | 即时、不打断流程 | 信息多时刷屏 |
| 文件输出 | 存档、分享 | 可追溯、格式灵活 | 需要手动清理旧文件 |
| 平台回写 | 团队协作 | 集中、可见 | 控制评论数量 |
3. 搭建环境时最容易忽略的几个细节
3.1 Git 配置与仓库状态检查
在跑审查之前,必须先确认 Git 仓库的状态是干净的。如果工作区有未提交的改动,git diff的结果会混入这些改动,导致审查内容不准确。所以流程的第一步应该是检查git status,如果有未提交的改动,要么先提交,要么明确告诉用户"当前审查的是工作区改动"。
另一个容易忽略的是分支基准。git diff main...HEAD和git diff main..HEAD看起来差不多,但语义不同。三个点表示"从两个分支的共同祖先开始比较",两个点表示"直接比较两个分支的当前状态"。做审查时通常用三个点,因为你想看的是"这个分支相对于主分支引入了什么",而不是"两个分支现在差多少"。
# 检查工作区状态 git status --porcelain # 确认当前分支 git branch --show-current # 确认主分支名称(可能是 main 或 master) git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'3.2 LLM 接口的调用与超时处理
调用 LLM 接口看起来简单,但实际用起来问题不少。最常见的是超时。大 diff 加上长提示词,请求体可能很大,模型处理时间可能超过默认超时时间。如果不做处理,请求会直接失败,用户看到的就是一个莫名其妙的错误。
我的做法是设置一个合理的超时时间(比如 120 秒),并且在超时后自动重试一次。如果还是失败,就把 diff 拆分成更小的块,分多次请求。另外要注意 token 限制,不同模型的上下文窗口不一样,如果 diff 太大,要么截断,要么分块。截断有风险,可能丢掉关键改动,所以优先选分块。
还有一个细节是错误信息的处理。LLM 接口返回的错误有时候很模糊,比如"请求失败",这时候如果直接把错误抛给用户,用户根本不知道该怎么办。比较好的做法是捕获常见错误(超时、限流、认证失败),给出明确的提示,比如"接口限流,请稍后重试"或者"认证失败,请检查 API Key 配置"。
3.3 审查规则的配置方式
审查规则如果硬编码在代码里,改起来很麻烦。比较好的做法是放在一个独立的配置文件里,比如review-rules.yaml或者review-rules.json。这样团队可以根据自己的规范调整规则,不需要改代码。
规则的结构可以设计成"维度加具体条目"的形式。比如:
rules: - dimension: 可读性 items: - 函数名应该使用动词开头 - 避免超过三层的嵌套 - 魔法数字应该提取为常量 - dimension: 错误处理 items: - 所有外部调用都应该有错误处理 - 错误信息应该包含上下文 - 不要吞掉异常这种结构的好处是清晰,模型容易理解,人也容易维护。另外,规则不要写太多,一开始控制在 10 到 15 条以内,跑一段时间之后再根据实际效果调整。规则太多模型会顾此失彼,反而抓不住重点。
注意:审查规则不是越多越好。规则过多会导致模型输出冗长且重点不突出,建议从核心规则开始,逐步迭代。
4. 实测中暴露的问题与应对策略
4.1 模型对 diff 上下文的误读
这是我在实测中遇到最多的问题。模型看到一段 diff,有时候会误以为删除的行是新增的,或者把上下文行当成改动行。这会导致它给出完全错误的审查意见,比如"你新增了一个空指针风险",但实际上那行代码是本来就有的。
解决这个问题的关键是让 diff 的格式对模型更友好。一种做法是在每行前面加上明确的标记,比如[新增]、[删除]、[上下文],而不是依赖+和-符号。另一种做法是在提示词里明确说明 diff 的格式约定,告诉模型"以加号开头的行是新增,以减号开头的行是删除,以空格开头的行是上下文"。
还有一种情况是模型对文件路径不敏感。如果 diff 里包含多个文件,模型有时候会把 A 文件的问题归到 B 文件上。解决办法是在每个文件的 diff 块前面加上清晰的文件路径标记,并且在提示词里强调"请针对每个文件分别给出审查意见"。
4.2 审查意见的噪音过滤
模型生成的审查意见,有很大一部分是噪音。比如"建议添加注释""这个变量名可以更清晰""考虑使用更高效的算法"——这些话放在任何代码上都成立,但没有任何实际价值。如果不做过滤,用户看到的就是一堆废话,久而久之就不信任这个工具了。
过滤噪音有几个思路。第一是在提示词里明确要求"只报告具体问题,不要给泛泛的建议"。第二是设置严重程度分级,让模型给每条意见标注严重程度(高、中、低),然后只展示中高严重程度的。第三是后处理,用规则过滤掉包含特定关键词的意见,比如"建议添加注释""可以考虑"这类模糊表述。
我自己的做法是组合使用这三种。提示词里先约束,输出后按严重程度过滤,最后再用一个简单的关键词黑名单兜底。经过这三层过滤,剩下的意见质量会明显提升。
4.3 大改动下的性能与成本控制
当一次改动涉及几十个文件、上千行代码时,直接把整个 diff 丢给模型是不现实的。一方面是 token 限制,另一方面是成本。这时候需要做分块处理。
分块的策略有几种。按文件分块是最简单的,每个文件单独审查,最后汇总。但这样会丢失文件之间的关联,比如 A 文件改了接口,B 文件改了调用,分开看可能看不出问题。按变更块(hunk)分块更细,但会产生大量请求。还有一种做法是先让模型看一遍文件列表和改动摘要,判断哪些文件需要详细审查,哪些可以跳过,然后再对重点文件做详细审查。
成本控制方面,可以在配置里设置一个阈值,比如"改动超过 500 行时只审查新增行,不审查删除行",或者"只审查特定目录下的文件"。另外,缓存也很有用,同一个 diff 不要重复请求,把结果缓存起来,下次直接读缓存。
| 分块策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 按文件分块 | 文件间关联弱 | 简单、并行容易 | 丢失跨文件关联 |
| 按 hunk 分块 | 改动集中 | 粒度细 | 请求数量多 |
| 先摘要后详审 | 大改动 | 成本可控 | 实现复杂 |
5. 把审查能力接入日常工作流
5.1 本地提交前的自动审查
最自然的接入点是 Git 的 pre-commit 钩子。在提交之前自动跑一次审查,如果发现严重问题就阻止提交。这样可以在问题进入仓库之前就拦住它。
但这里有个平衡问题:如果审查太严格,每次提交都被拦住,开发者会很烦,最后可能会用--no-verify跳过钩子。所以 pre-commit 阶段的审查应该只关注严重问题,比如明显的 bug、安全问题、严重的规范违反。风格类的问题可以放到后面,不要在这个阶段拦。
# .git/hooks/pre-commit 示例 #!/bin/bash # 调用 open-code-review 做提交前审查 open-code-review --staged --severity high if [ $? -ne 0 ]; then echo "审查发现严重问题,提交已阻止" exit 1 fi5.2 与 CI 流水线的结合
CI 流水线是另一个自然的接入点。在 PR 创建或更新时自动触发审查,把结果作为流水线的一部分展示出来。这样审查结果对团队可见,也方便追溯。
在 CI 里跑审查要注意几点。第一是环境准备,CI 环境里可能没有配置 Git 用户信息,需要提前设置。第二是密钥管理,LLM 接口的 API Key 要通过 CI 的密钥管理功能注入,不要硬编码在配置文件里。第三是超时设置,CI 任务通常有超时限制,审查任务要控制在这个限制之内。
5.3 审查结果的追踪与迭代
审查工具本身也需要迭代。哪些规则经常触发、哪些规则从来没触发过、哪些意见被开发者忽略了、哪些意见被采纳了——这些数据可以帮助你优化规则和提示词。
最简单的做法是把每次审查的结果存下来,定期回顾。如果发现某条规则总是产生误报,就把它去掉或者调整。如果发现某个维度的问题总是被漏掉,就加强这个维度的提示词。这个过程不需要很复杂,一个简单的日志文件加定期人工回顾就够了。
提示:审查工具的规则应该随着项目演进不断调整。项目初期可能更关注代码风格,后期可能更关注性能和安全性,规则要跟着变。
6. 几个容易被问到的概念澄清
6.1 Agent、LLM、AI 模型到底是什么关系
这三个词经常被混用,但含义不同。AI 模型是一个宽泛的概念,指任何通过数据训练出来的、能完成特定任务的模型。LLM(大语言模型)是 AI 模型的一种,专门处理文本,比如常见的那些对话模型。Agent 则是一个系统概念,它通常包含一个 LLM 作为核心,再加上工具调用、记忆、规划等能力,能自主完成多步任务。
打个比方:AI 模型像是"会做菜的机器",LLM 是"会做川菜的机器",Agent 是"一个完整的厨房,里面有会做川菜的机器,还有采购、备菜、摆盘的能力"。open-code-review 里的 Agent,就是围绕代码审查这个任务,把 LLM 和 Git 操作、文件读写、结果输出这些能力组合起来。
6.2 CLI 工具相比 GUI 的优势在哪
CLI(命令行界面)和 GUI(图形界面)不是对立的,各有适用场景。CLI 的优势在于可组合、可脚本化、可远程操作。你可以把多个 CLI 工具用管道串起来,可以用脚本批量执行,可以通过 SSH 在远程服务器上操作。GUI 的优势在于直观、易上手、适合可视化展示。
对于代码审查这种需要频繁执行、需要集成到自动化流程里的任务,CLI 更合适。而对于需要展示复杂 diff、需要多人协作讨论的场景,GUI 更合适。open-code-review 选择 CLI 形态,是因为它的目标用户是开发者,使用场景是本地和 CI 环境,这些场景下 CLI 的效率更高。
6.3 Git 在审查流程中扮演的角色
Git 不只是版本控制工具,它还是审查流程的数据源。审查需要知道"改了什么",这个信息只能从 Git 里拿。Git 的 diff、log、blame 等命令,提供了审查所需的基础数据。
理解 Git 的几个关键概念对用好审查工具很重要。HEAD是当前提交,main是主分支,origin是远程仓库。git diff main...HEAD是比较当前分支和主分支的差异。git log可以看提交历史,git blame可以看每一行是谁改的。这些命令在排查审查问题时经常用到。
另外,Git 的钩子机制(hooks)是接入审查的天然入口。pre-commit、pre-push、commit-msg 这些钩子,可以在特定时机自动触发审查。用好钩子,能让审查变成流程的一部分,而不是一个需要额外记得去做的动作。
7. 我在实际使用中总结的几条经验
第一条经验是:不要指望一次配置就能得到理想效果。审查规则和提示词都需要反复调整。我一开始写的提示词,模型输出的意见一半是废话。后来我把审查维度拆细,明确告诉模型"每个维度只报告最严重的一个问题",输出质量才上来。这个过程大概迭代了五六次。
第二条经验是:审查结果要给人看,不要给机器看。我见过一些实现,把审查结果输出成 JSON,然后让用户自己解析。这对开发者来说太麻烦了。好的做法是默认输出人类可读的格式,需要机器处理时再提供 JSON 选项。
第三条经验是:控制审查的频率和范围。不是每次提交都需要全量审查。小改动可以只做快速检查,大改动才做详细审查。另外,审查范围可以按目录或文件类型过滤,比如只审查src/下的代码,不审查测试文件和文档。
第四条经验是:把审查和修复分开。审查工具只负责发现问题,不负责修复问题。有些实现会让模型直接给出修复后的代码,这看起来很美好,但风险很大——模型可能引入新的问题,而且开发者可能不看审查意见就直接用修复代码。审查意见应该是建议性的,最终怎么改由开发者决定。
最后一条经验是:保持工具的可解释性。当审查工具给出一个意见时,用户应该能知道这个意见是基于哪条规则、哪个维度的检查得出的。如果用户不理解为什么会有这个意见,他就不会信任这个工具。所以在输出审查意见时,最好附上规则来源和判断依据。