
1. 为什么我要自己搭一套 open-code-review 流程团队里代码合并前的 review 环节长期靠人肉盯。项目一多、提交一频繁review 就变成走过场要么拖到第二天才看要么只扫一眼 diff 就点通过。我试过用现成的托管平台自带检查规则死板稍微复杂一点的逻辑问题根本发现不了。后来我把目光转向open-code-review这个思路——用 CLI 把 Git 的变更抓出来交给 LLM Agent 做一轮结构化审查再把结果回写到本地或流水线里。这套东西解决的核心问题很明确把重复、机械、容易漏的代码审查动作自动化同时保留人对关键决策的最终判断权。它适合三类人一是小团队里没有专职 review 角色的开发者二是想给自己提交加一道自检关卡的个人开发者三是已经在用 Git、想低成本引入 AI 辅助审查的工程团队。哪怕你之前只敲过git clone和git commit跟着走也能搭起来。我先把结论放前面整套流程的关键不在于模型多强而在于怎么把 diff 切得干净、怎么给 Agent 喂对上下文、怎么让输出可落地。下面按我实际搭建的顺序拆开讲。2. 整体设计思路与方案选型2.1 为什么是 CLI LLM Agent 而不是纯平台方案一开始我也想过直接用平台插件。问题是平台插件通常只能拿到仓库级别的信息对本地未推送的改动、跨分支的临时对比、以及自定义的审查规则支持很弱。而CLI方案的好处是它跑在开发者自己的机器上能直接读工作区、暂存区、任意两个 commit 之间的差异灵活性高一个量级。再叠加LLM Agent价值在于它能理解语义。传统的 lint 工具只能查格式和已知模式比如变量未使用、括号不匹配。但像“这个函数在异常路径下没有释放资源”“这个循环边界可能差一”这类问题规则引擎写起来极其痛苦而 Agent 读一遍 diff 加周边代码就能给出提示。所以我的选型逻辑是lint 管格式Agent 管语义CLI 管调度三者各司其职。至于为什么强调“open”是因为我希望整个链路是可替换的模型可以换、提示词可以改、输出格式可以自定义。闭源黑盒一旦不满足需求就只能等厂商更新这在工程实践里太被动了。2.2 核心组件拆解与职责边界我把整套流程拆成四个组件每个组件只干一件事组件职责常见实现变更采集器从 Git 抓取待审查的 diffgit diff、git diff --cached、git diff A...B上下文组装器把 diff 和周边代码、提交信息拼成提示词脚本 模板审查 Agent调用 LLM 做语义分析并输出结构化结果任意支持函数调用的模型接口结果处理器把结果写回终端、文件或流水线评论脚本 格式化这里有个容易踩的坑很多人一上来就把整个仓库塞给模型结果 token 爆炸、成本失控、还容易让模型抓不住重点。我的做法是只送变更行加上下文窗口比如每个改动块前后各取 20 行这样既保留了语义环境又把输入量压到可控范围。2.3 与 Git 工作流的衔接方式open-code-review 要真正用起来必须嵌进现有的 Git 习惯里而不是让开发者额外记一套命令。我设计了三种触发时机提交前用 Git 的钩子在pre-commit阶段对暂存区做一次快速审查只报严重问题不阻塞提交。推送前在pre-push阶段对本次要推送的 commit 范围做完整审查输出报告。流水线中在 CI 里对合并请求的 diff 做审查结果作为评论回写。这三种时机的审查深度和超时策略完全不同。提交前要快控制在几秒内推送前可以稍慢流水线里则可以跑完整规则。这个分层设计是我踩过坑之后才定下来的——早期我把完整审查放在 pre-commit结果每次提交都要等半分钟团队直接把这个钩子禁用了。3. 核心细节解析与实操要点3.1 diff 采集把变更切得干净是第一步diff 采集看着简单其实细节很多。最基础的三条命令你得先分清楚# 工作区与暂存区的差异还没 add 的改动 git diff # 暂存区与最后一次提交的差异已 add 未 commit git diff --cached # 两个提交之间的差异常用于分支对比 git diff main...feature注意main...feature里的三个点它表示“从共同祖先到 feature 的变更”这才是 review 分支时真正想看的东西。如果用两个点main..feature会把 main 上后来新增的提交也算进去审查范围就错了。这个细节我在早期搞混过导致 Agent 报了一堆跟本次改动无关的问题。还有一个坑是重命名和移动。Git 默认能识别重命名但 diff 输出里可能只显示少量改动行Agent 会误以为这个文件被大改。我的处理办法是在采集时加上-M参数显式开启重命名检测并在提示词里说明“本次 diff 包含重命名请结合文件路径变化理解”。git diff --cached -M --stat git diff --cached -M -U20-U20是把上下文行数从默认的 3 行扩到 20 行。这个值我调过好几轮3 行太少Agent 看不到函数签名50 行太多token 浪费严重。20 行是个比较平衡的点能覆盖大多数函数的头部信息。3.2 上下文组装给 Agent 喂对信息比喂多信息重要组装提示词是整套流程里最考验经验的部分。我的模板结构是这样的你是一名资深代码审查者。请审查以下 Git 变更。 【提交信息】 {commit_message} 【变更文件列表】 {changed_files} 【变更内容】 {diff_content} 【审查要求】 1. 只针对变更行及其直接影响范围提出问题 2. 按严重程度分级blocker / major / minor / nit 3. 每个问题给出文件、行号、原因、修改建议 4. 如果没有问题明确说明“未发现需要修改的问题”这里有几个关键设计。第一明确要求分级否则模型会把所有问题都写成“建议优化”你根本没法判断优先级。第二要求给出修改建议只指出问题不给方案的审查等于把活又推回给人。第三允许说没问题这点很重要——如果不给这个出口模型会为了“显得有用”而强行挑刺制造大量噪音。我还试过把整个文件的完整内容也塞进去想让 Agent 有全局视野。实测下来效果并不好token 消耗翻了好几倍但问题发现率提升有限反而因为信息过载导致模型注意力分散。后来改成只送变更块加固定上下文窗口性价比最高。3.3 审查规则的可配置化不同项目对审查的容忍度完全不同。基础库可能要求零 nit业务代码则更关注逻辑正确性。所以我把审查规则做成可配置的放在仓库根目录的一个配置文件里review: severity_threshold: major max_issues: 20 rules: - name: error_handling enabled: true description: 检查异常路径是否被正确处理 - name: resource_leak enabled: true description: 检查文件、连接、锁是否在异常路径释放 - name: naming enabled: false description: 命名规范检查交给 lintseverity_threshold控制只报告某个级别以上的问题。max_issues防止模型输出过长超过就截断并提示“问题过多建议拆分提交”。这个上限很有必要我有一次遇到一个巨型提交Agent 输出了上百条问题终端直接刷屏根本没法看。提示规则描述要写得具体不要写“检查代码质量”这种空话。模型对具体指令的响应远好于抽象指令。3.4 输出格式与落地方式审查结果如果只是一堆文字用起来很痛苦。我要求 Agent 输出结构化数据然后由脚本渲染成不同格式{ summary: 本次变更共发现 3 个问题, issues: [ { file: src/service.py, line: 42, severity: major, reason: 异常分支未释放已获取的锁, suggestion: 在 except 块中调用 lock.release() } ] }有了结构化输出就能做很多事在终端里用颜色区分严重程度、在流水线里转成评论、在本地生成 Markdown 报告。我个人的习惯是本地跑的时候直接看彩色终端输出流水线里则回写成评论方便留痕。4. 实操过程与核心环节实现4.1 环境准备与 Git 基础配置先把 Git 装好配好这是所有后续操作的前提。Windows 上直接去官网下载安装包一路默认即可安装完在终端里验证git --version能打印出版本号就说明装好了。接着配置身份信息这两条是必须的git config --global user.name 你的名字 git config --global user.email 你的邮箱如果你用 Gitee 之类的平台还需要配置密钥。生成密钥的命令是ssh-keygen -t ed25519 -C 你的邮箱一路回车然后把公钥内容复制到平台的密钥设置里。这里有个细节私钥文件绝对不能提交到仓库我见过有人把.ssh目录整个 add 进去的那等于把钥匙挂在门上。4.2 搭建审查脚本的完整步骤我用的是一段 Python 脚本做调度核心逻辑分四步。第一步采集 diffimport subprocess def get_staged_diff(): result subprocess.run( [git, diff, --cached, -M, -U20], capture_outputTrue, textTrue ) return result.stdout第二步组装提示词。把 diff 和提交信息拼进模板def build_prompt(diff, commit_msg): template open(prompt_template.txt).read() return template.format( commit_messagecommit_msg, diff_contentdiff )第三步调用模型接口。这里我用的是通用的 HTTP 调用方式具体接口地址和参数按你选用的服务填import requests def call_agent(prompt): resp requests.post( AGENT_ENDPOINT, json{prompt: prompt, temperature: 0.2}, headers{Authorization: fBearer {API_KEY}} ) return resp.json()[content]温度参数我设成 0.2因为审查任务需要稳定和一致不需要创造性。设太高会导致同一个 diff 每次审查结果都不一样没法复现。第四步解析并渲染结果。把模型返回的 JSON 解析出来按严重程度排序后打印def render(issues): order {blocker: 0, major: 1, minor: 2, nit: 3} issues.sort(keylambda x: order.get(x[severity], 9)) for issue in issues: print(f[{issue[severity]}] {issue[file]}:{issue[line]}) print(f 原因: {issue[reason]}) print(f 建议: {issue[suggestion]})4.3 接入 Git 钩子的具体配置脚本写好后挂到 Git 钩子上。在.git/hooks/目录下创建pre-commit文件没有扩展名内容如下#!/bin/sh python3 path/to/review_script.py --mode staged然后给它执行权限chmod x .git/hooks/pre-commit这里有个团队协作的坑.git/hooks/目录不会被提交每个人都要手动配一遍。我的解决办法是把钩子脚本放在仓库里一个hooks/目录然后写一个安装脚本新成员克隆后跑一次就配好了。或者用git config core.hooksPath hooks把钩子目录指到仓库内这样就能跟着版本走。注意pre-commit 钩子里不要跑耗时超过 5 秒的审查否则会严重影响提交体验。我的做法是 pre-commit 只跑 blocker 级别的快速检查完整审查放到 pre-push。4.4 参数调优的实际记录我拿一个真实的中型项目做了几轮参数对比记录如下上下文行数平均 token 消耗问题发现数误报数3120041102100612034008250780084可以看到上下文从 20 行加到 50 行问题发现数没有提升但误报翻倍、token 翻倍。所以 20 行是我实测下来的甜点值。误报主要来自模型看到了太多无关代码开始对没改动的地方指手画脚。另一个参数是max_issues。我一开始不设上限结果遇到大提交时输出几百条其中大量是重复的同类问题。后来设成 20并在提示词里加一句“如果同类问题超过 3 处合并为一条并说明涉及范围”输出质量明显提升。5. 常见问题与排查技巧实录5.1 模型输出格式不稳定怎么办最常见的问题是模型不按 JSON 格式输出夹杂一堆解释性文字导致解析失败。我的处理分三层第一层在提示词里明确“只输出 JSON不要任何额外文字”第二层解析时用正则先提取 JSON 块容忍前后有杂音第三层如果还是失败把原始输出存到日志文件人工看一眼再调整提示词。实测下来光靠第一层就能解决 80% 的问题。剩下 20% 通常是 diff 太长导致模型“忘了”格式要求这时候缩短输入或拆分提交就能解决。5.2 审查结果误报太多怎么收敛误报是劝退团队的最大杀手。我总结了几个收敛手段提高严重程度阈值只报 major 以上nit 直接过滤掉。在提示词里加负面示例明确告诉模型“不要报告纯格式问题、不要报告命名风格问题这些交给 lint”。按文件类型过滤自动生成的代码、第三方库、测试快照文件直接跳过审查。我踩过最深的一个坑是早期没排除自动生成的文件Agent 对着一个几千行的生成文件报了几十条“建议重构”团队看到报告直接对这套流程失去信任。后来加了文件白名单和黑名单问题立刻消失。5.3 大提交导致超时或截断一个提交改了 50 个文件、几千行直接送进去必然超时或截断。我的策略是按文件拆分审查把 diff 按文件切块逐块送审最后合并结果。这样单次输入可控还能并行处理加速。但拆分也有代价跨文件的关联问题可能漏掉。比如 A 文件改了函数签名B 文件改了调用处拆开看各自都没问题合起来才发现参数不匹配。所以我的做法是先做一次全局的变更文件列表分析再做逐文件细审。全局那一步只送文件名和改动行数让 Agent 判断有没有跨文件影响有的话再针对性送相关文件。5.4 常见问题速查表现象可能原因解决方向解析 JSON 失败模型输出夹杂文字强化提示词加正则提取审查超时单次输入过大按文件拆分设 max_issues误报泛滥规则太宽、上下文过多提高阈值加负面示例钩子不生效权限或路径问题检查执行权限和 hooksPath结果每次不同温度参数过高降到 0.2 以下漏掉跨文件问题拆分审查增加全局预分析步骤5.5 几个我踩过的独家坑第一个坑别在钩子里调用需要联网的接口而不做超时处理。有一次网络抖动pre-commit 卡了整整两分钟我差点以为 Git 坏了。后来所有网络调用都加了 10 秒超时超时就跳过审查并提示“审查服务不可用已跳过”。第二个坑API 密钥不要硬编码在脚本里。我早期图省事直接写在代码里结果差点跟着仓库提交上去。现在统一用环境变量读取脚本里只留os.environ.get(REVIEW_API_KEY)。第三个坑审查结果要能一键忽略。有些问题开发者判断后认为不用改但每次提交都重复报。我加了一个忽略机制在代码行尾加// review-ignore注释脚本采集时自动跳过这些行。这个小功能极大提升了团队接受度。6. 让这套流程真正跑起来的几个经验搭起来容易让它长期活下去难。我最大的体会是审查流程的价值不在于发现多少问题而在于不制造额外负担。如果每次提交都要等、每次报告都一堆噪音、每个问题都要手动确认团队很快就会绕过它。所以我的原则是宁可少报不可误报。blocker 级别的问题必须准major 级别可以稍宽minor 和 nit 默认关闭。让开发者先信任这套流程再逐步放开规则比一上来就全量审查效果好得多。另外Agent 的审查结果永远只是建议最终判断权必须留给人。我在报告开头会明确写一句“以下为自动审查建议请结合业务上下文判断”。这既是技术上的谨慎也是让团队接受 AI 辅助审查的心理前提。后续如果还想扩展可以考虑把审查历史存下来做趋势分析——比如某个文件反复出问题说明它可能需要重构某类问题高频出现说明团队需要补充规范文档。这些数据积累起来比单次审查本身更有价值。