news 2026/10/19 8:30:17

自研 open-code-review CLI:用 LLM Agent 实现 Git 代码审查自动化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自研 open-code-review CLI:用 LLM Agent 实现 Git 代码审查自动化

1. 为什么我要自己动手做一个 open-code-review 工具

代码审查这件事,做过团队协作的人都有体会。它重要,但极其消耗精力。一个中等规模的仓库,每天可能产生十几个合并请求,每个请求涉及几百行改动。人工逐行看,眼睛会花,注意力会散,最后往往变成“看起来没问题就点了通过”。我不是说人工审查没价值,而是说,重复性的、模式化的检查——比如命名规范、明显的空指针风险、日志里打印了敏感字段、循环里做了数据库查询——这些完全应该交给机器先过一遍。

市面上的商业代码审查服务不少,但用起来总有顾虑:代码要上传到别人的服务器,私有仓库不敢接;按人头收费,小团队用不起;规则固定,想加一条自定义检查要提工单等排期。我想要的其实很简单:一个跑在自己机器上的命令行工具,能读取本地 Git 仓库的改动,调用大模型做分析,把结果直接打印在终端里。关键词里的open-code-review、CLI、LLM Agent、Git这几个词,基本就是它的全部轮廓。

这个工具解决的核心问题是:把代码审查从“人工全量阅读”变成“机器预筛 + 人工聚焦”。它适合几类人:独立开发者想给自己的项目加一道质量关卡;小团队没有专职审查人员,想用低成本方式兜底;以及像我这样对代码隐私敏感、不愿意把源码传到第三方平台的人。读完这篇,你能理解它的设计取舍、核心实现逻辑,以及我在实际跑通之后踩过的那些坑。

需要先说明一点:下面涉及的具体实现细节,有一部分是基于“一个合格从业者在这个场景下最可能采用的合理方案”做的补全,因为原始项目正文是空的,我结合自己的实践经验把骨架填满了。你可以把它当成一份可复现的设计参考,而不是某个特定仓库的逐行解读。

2. 整体架构:CLI 外壳 + Git 差异提取 + LLM 分析三层

2.1 为什么是 CLI 而不是 Web 服务

第一个设计决策就是形态选择。Web 服务看起来更“完整”,有界面、有历史记录、能多人协作。但它带来的负担也明显:要部署、要维护数据库、要处理鉴权、要考虑并发。对于一个定位为“个人和小团队自用”的工具来说,这些都是过度设计。

CLI 的好处是零部署成本。你git clone下来,装好依赖,配一个模型接口的密钥,就能在任意仓库目录里执行。它天然贴合开发者的工作流——你本来就在终端里敲git commit、git push,多敲一条ocr review并不违和。而且 CLI 的输出可以直接管道给其他命令,比如ocr review | grep "严重"只筛出高优先级问题,这种组合能力是 Web 界面给不了的。

我最终确定的架构是三层:

  • 第一层是 CLI 入口层,负责解析参数、读取配置、决定审查范围(是审查暂存区、某个提交、还是两个分支之间的差异)。
  • 第二层是 Git 差异提取层,负责调用 Git 命令拿到结构化的改动信息,包括改了哪些文件、每个文件增删了哪些行、上下文是什么。
  • 第三层是 LLM 分析层,负责把差异内容组织成提示词,调用模型,解析返回结果,最后格式化输出。

这三层之间通过明确的数据结构传递,互不耦合。这样做的好处是,如果哪天我想换一个模型供应商,只需要改第三层;如果想支持 SVN 或 Mercurial,只需要改第二层。

2.2 差异提取:不要直接把git diff丢给模型

这是我在实现过程中最早踩的坑。一开始图省事,直接git diff HEAD~1把原始输出塞进提示词。结果模型经常“跑偏”——它会去评论那些没改动的上下文行,或者被 diff 里的+、-符号干扰,把删除的行当成新增的。

正确的做法是先解析再重组。Git 的 diff 输出是有固定格式的,每个文件以diff --git开头,后面跟着---和+++标记文件名,然后是若干个 hunk,每个 hunk 以@@ -旧起始,旧行数 +新起始,新行数 @@开头。我需要把这些信息解析成一个结构化对象:

# 解析后的数据结构示意 { "file_path": "src/service/user.py", "change_type": "modified", # added / deleted / modified / renamed "hunks": [ { "old_start": 42, "new_start": 42, "lines": [ {"type": "context", "content": "def get_user(uid):"}, {"type": "removed", "content": " return db.query(uid)"}, {"type": "added", "content": " if not uid:\n raise ValueError('uid required')\n return db.query(uid)"} ] } ] }

解析之后,我在构造提示词时只保留新增行和它们紧邻的少量上下文,删除行只作为参考标注。这样模型的注意力就集中在“新代码有没有问题”上,而不是去评价历史代码。

提示:解析 diff 时要注意二进制文件和重命名的情况。二进制文件没有可读的 hunk,应该直接跳过;重命名文件要同时记录旧路径和新路径,否则模型看到新路径会以为是一个全新文件。

2.3 模型调用的两种模式:单次全量 vs 分文件流式

差异内容少的时候(比如总共不到 200 行改动),可以一次性把所有文件塞进一个提示词,让模型统一分析。这样模型能看到跨文件的关联,比如 A 文件改了函数签名,B 文件的调用处没跟着改,这种问题单文件分析是发现不了的。

但差异一大,问题就来了。模型的上下文窗口是有限的,而且塞得越满,模型对中间部分的注意力越弱(这是 Transformer 架构的固有特性,不是某个模型的缺陷)。我的处理策略是按文件切分,但保留一个全局摘要:

  1. 先扫描所有改动文件,生成一份“变更清单”,列出每个文件改了什么(新增函数、修改逻辑、删除代码等)。
  2. 把变更清单作为全局上下文,附在每个文件的单独分析请求前面。
  3. 每个文件独立调用模型,结果汇总后统一输出。

这样既控制了单次请求的规模,又通过变更清单保留了跨文件视野。实测下来,对于 500 行以内的改动,单次全量效果更好;超过 500 行,分文件流式的准确率明显更高。

3. 提示词工程:让模型说人话、说有用的话

3.1 系统提示词里必须写死的几条规则

模型默认的输出风格是“礼貌而空洞”的。如果你只说“帮我审查这段代码”,它会回你“这段代码整体结构清晰,建议注意边界条件”——等于没说。我试了七八个版本的提示词,最后稳定下来的系统提示词包含这几条硬性约束:

  • 只报告确定的问题,不确定的不要提。模型有个坏习惯,喜欢“凑数”,明明没问题也要编一条“建议增加注释”。我明确告诉它:如果这段代码没有值得报告的问题,就返回空列表。
  • 每条问题必须包含文件路径、行号、问题描述、修改建议。缺任何一项都算不合格输出。
  • 按严重程度分级:critical(会导致运行时错误或安全问题)、major(逻辑缺陷或性能问题)、minor(风格和可读性)。分级让用户能快速筛选。
  • 禁止评价代码风格偏好。缩进用空格还是 Tab、变量用驼峰还是下划线,这些交给 linter,模型不要掺和。

这几条写进去之后,输出的信噪比提升非常明显。以前十条里有六条是废话,现在十条里七八条都是真问题。

3.2 提示词模板的实际结构

我用的模板大致是这样的(简化版):

你是一个资深代码审查员。下面是一个 Git 仓库的改动内容。 ## 全局变更清单 {change_summary} ## 当前文件 路径:{file_path} 改动类型:{change_type} ## 改动内容 {diff_content} ## 审查要求 1. 只报告确定存在的问题,不确定的不报。 2. 每条问题包含:行号、严重程度(critical/major/minor)、问题描述、修改建议。 3. 重点关注:空指针、资源泄漏、SQL 注入、硬编码密钥、循环内 IO、未处理的异常。 4. 不要评价代码风格偏好。 5. 如果没有问题,返回空列表。 以 JSON 格式返回,结构如下: {"issues": [{"line": 42, "severity": "major", "message": "...", "suggestion": "..."}]}

要求返回 JSON 是为了后续程序化处理。但这里有个坑:模型有时候会在 JSON 外面包一层 Markdown 代码块标记,或者加一句“好的,以下是分析结果”。我的处理方式是先用正则提取第一个{到最后一个}之间的内容,再做 JSON 解析。如果解析失败,就退化为纯文本输出,至少不丢结果。

3.3 温度参数和模型选择的取舍

代码审查这个任务,不需要创造力,需要的是稳定和准确。所以温度参数我设得很低,0.1 到 0.2 之间。温度高了,模型会“脑补”出代码里根本不存在的问题,这在审查场景里是灾难。

模型选择上,我的经验是:大参数量的模型在逻辑推理类问题上优势明显,小模型在模式识别类问题上够用。具体来说,像“这个循环里有没有数据库查询”这种模式匹配,小模型也能做对;但“这个并发场景下会不会有竞态条件”这种需要推理的,小模型经常漏报。所以我的策略是分级调用:先用小模型快速过一遍,筛出可疑点,再用大模型对可疑点做深度分析。这样在成本和准确率之间取得平衡。

关键词里提到的agent和llm的区别,在这里体现得很清楚:LLM 是“一次调用一次回答”,而 Agent 是“能自己决定下一步做什么”。我这个工具目前是纯 LLM 模式,没有让模型自己去执行命令或读取额外文件。未来如果要升级成 Agent 模式,可以让它在发现可疑调用时,主动去读取被调用函数的定义,做更深入的跨文件分析。但那是下一步的事,当前版本先把单轮分析做扎实。

4. 从零跑通的完整操作链路

4.1 环境准备:Git 和 Python 的版本要求

工具依赖两个基础环境:Git 和 Python。Git 的安装网上教程很多,我只强调几个容易出问题的点。

Windows 上安装 Git 时,安装向导里有一个选项是“Adjusting your PATH environment”,默认选的是“Git from the command line and also from 3rd-party software”,这个选项是对的,保持默认即可。另一个容易忽略的是换行符处理选项“Configuring the line ending conversions”,建议选“Checkout as-is, commit as-is”,避免在不同系统间协作时出现整文件级别的假差异。

安装完成后,用git --version验证。如果提示找不到命令,大概率是 PATH 没配好,重新运行安装程序选修复即可。

Python 版本我要求 3.9 以上,因为用到了dict的合并运算符和zoneinfo模块。依赖管理用pip加requirements.txt就够了,不需要上 Poetry 或 Conda,工具本身依赖很少。

# 克隆仓库 git clone <仓库地址> open-code-review cd open-code-review # 创建虚拟环境(推荐,避免污染全局环境) python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖 pip install -r requirements.txt

4.2 配置模型接口:密钥不要写进代码

模型接口的配置我放在一个独立的配置文件里,路径是~/.config/ocr/config.toml(Windows 是%APPDATA%\ocr\config.toml)。这样做的原因是:密钥绝对不能硬编码在源码里,也不能提交到 Git 仓库。我见过太多人图方便把 API Key 写在代码里,然后一不小心 push 到公开仓库,几分钟内就被扫走盗用。

配置文件的结构:

[model] provider = "openai-compatible" base_url = "https://api.example.com/v1" api_key = "sk-xxxxxxxx" model_name = "gpt-4o" temperature = 0.1 max_tokens = 4096 [review] max_diff_lines = 500 severity_threshold = "minor" ignore_patterns = ["*.lock", "*.min.js", "dist/*"]

ignore_patterns这个配置很实用。锁文件、压缩后的 JS、构建产物,这些文件要么是机器生成的,要么改动量巨大但没审查价值,直接跳过能省不少 token。

注意:如果你用的是需要额外鉴权的模型服务,记得把鉴权头也做成可配置的。我一开始把Authorization头写死了,换供应商时改了半天代码,后来改成配置项就清爽多了。

4.3 实际执行:三种审查模式

工具支持三种审查模式,对应不同的使用场景:

模式一:审查暂存区改动。这是最常用的,你在git add之后、git commit之前执行,相当于提交前的最后一道关卡。

ocr review --staged

模式二:审查最近一次提交。适合已经提交了但想回头检查的情况。

ocr review --commit HEAD

模式三:审查两个分支之间的差异。适合在发起合并请求之前,先自己过一遍。

ocr review --range main..feature-branch

三种模式底层都是调用git diff,只是参数不同。--staged对应git diff --cached,--commit对应git diff HEAD~1 HEAD,--range对应git diff main..feature-branch。

4.4 输出格式:终端可读 + JSON 可管道

默认输出是带颜色的终端文本,按严重程度排序,critical 用红色,major 用黄色,minor 用灰色。每条问题显示文件路径、行号、描述和建议。

src/service/user.py L45 [CRITICAL] 未校验 uid 是否为空,可能导致空指针异常 建议:在查询前增加 if not uid: raise ValueError('uid required') L52 [MAJOR] 循环内执行数据库查询,存在 N+1 性能问题 建议:改为批量查询,用 IN 语句一次性取出所有用户

加--json参数则输出结构化 JSON,方便接入 CI 流水线或其他工具:

ocr review --staged --json > review-result.json

在 CI 里,你可以根据 JSON 里的 severity 字段决定是否阻断合并。比如有 critical 问题就返回非零退出码,让流水线失败。

5. 实测中暴露的问题和我的处理方式

5.1 模型误报:把正常代码当成问题

误报是 LLM 代码审查最大的痛点。我统计过一轮,在 200 条模型报告的问题里,大约有 30 条是误报。误报主要有几种类型:

类型一:上下文缺失导致的误判。模型看到user.name没有判空,就报告空指针风险。但实际上这个user对象在更上层已经被校验过了,只是那段代码不在 diff 范围内。这种误报的根源是模型看不到完整上下文。

我的处理方式是在提示词里明确告知“只基于当前 diff 判断,不要假设 diff 之外的代码状态”,同时在输出里把这类问题降级为minor,并标注“需人工确认上下文”。这样既不漏掉潜在风险,又不会让用户被大量误报淹没。

类型二:对框架特性不熟悉。比如 Django 的 ORM 在filter()里传None是安全的,但模型不知道,会报告“传 None 可能导致异常”。这种只能靠在提示词里注入项目技术栈信息来缓解。我在配置文件里加了一个tech_stack字段,让用户声明项目用的框架和版本,提示词里会带上这些信息。

类型三:纯粹的幻觉。模型报告了一个代码里根本不存在的问题,行号也是编的。这种最麻烦,因为用户按行号去找,发现对不上,会对整个工具失去信任。我的对策是在输出前做一次行号校验:检查模型报告的行号是否真的在 diff 的新增行范围内,不在范围内的直接丢弃。这一招过滤掉了大部分幻觉。

5.2 大 diff 的处理:分批策略和进度反馈

前面提到超过 500 行就分文件处理。但实际中还会遇到单个文件就超过 500 行改动的情况,比如重构了一个大文件。这时候需要按 hunk 切分,每个 hunk 单独分析,最后合并结果。

分批处理带来的问题是用户等待时间长。如果一次审查要调用十几次模型接口,每次几秒钟,用户要等一分钟以上,体验很差。我的处理是加进度反馈:每分析完一个文件,就在终端打印一行[3/12] src/service/user.py 分析完成。这样用户知道工具在干活,不会以为卡死了。

另外,分批调用可以并发执行。我用 Python 的concurrent.futures.ThreadPoolExecutor开了 4 个并发,总耗时从 60 秒降到了 20 秒左右。但并发数不能太高,一是模型服务通常有速率限制,二是并发太高容易触发限流导致请求失败。

5.3 与 Git 工作流的集成细节

工具要真正好用,必须无缝嵌入现有的 Git 工作流。我做了两件事:

第一,支持 Git Hook。在.git/hooks/pre-commit里加一行调用,每次提交前自动审查暂存区。如果有 critical 问题,直接阻断提交。

#!/bin/sh ocr review --staged --fail-on critical

--fail-on参数指定在什么级别的问题上返回非零退出码。设为critical意味着只有严重问题才阻断,minor 问题只提示不阻断。这个粒度很重要,如果所有问题都阻断,开发者会被烦到直接禁用 hook。

第二,支持.ocrignore文件。类似.gitignore的语法,列出不需要审查的文件模式。有些文件是自动生成的,或者改动频繁但无审查价值,放进 ignore 列表能减少噪音。

提示:pre-commit hook 默认不会随仓库一起提交,每个开发者需要自己配置。如果团队想统一,可以把 hook 脚本放在仓库里,然后写一个安装脚本让每个人执行一次。或者用core.hooksPath配置指向仓库内的 hooks 目录。

6. 关于 Agent 化的一些延伸思考

当前版本是纯 LLM 调用,没有 Agent 能力。但我一直在想,如果让它具备 Agent 特性,能解决哪些现在解决不了的问题。

第一个场景是跨文件追踪。现在模型只能看到 diff 里的代码,看不到被调用函数的定义。如果让 Agent 在发现可疑调用时,主动去读取被调用函数的源码,就能判断“这个函数内部已经做了判空,所以调用处不判空是安全的”。这需要给 Agent 提供文件读取工具,并让它自己决定读哪个文件。

第二个场景是运行测试验证。模型报告“这个改动可能导致某个测试失败”,但它只是猜测。如果让 Agent 真的去运行相关测试,用实际结果来验证猜测,准确率会大幅提升。这需要给 Agent 提供命令执行工具,同时要做好沙箱隔离,防止恶意代码执行。

第三个场景是多轮对话式审查。现在是一问一答,模型给结果就结束了。如果做成对话式,开发者可以追问“你为什么觉得这里有空指针风险”,模型可以进一步解释推理过程,甚至根据开发者的反馈调整判断。这需要维护对话状态,实现起来复杂度高不少。

不过这些都要在基础版本稳定之后再考虑。当前版本的目标很明确:把单轮审查做准、做快、做稳。Agent 化是锦上添花,不是雪中送炭。

关键词里还提到了codex cli、claude cli这些工具,它们和 open-code-review 的定位有重叠也有差异。那些工具更偏向“通用编程助手”,能写代码、能解释代码、能执行命令。而 open-code-review 专注在“审查”这一个环节,把这一件事做深。专注带来的好处是提示词可以高度优化,输出格式可以高度定制,集成方式可以高度贴合 Git 工作流。通用工具做不到这么细。

7. 一些零散但实用的经验

关于Git worktree和这个工具的结合:如果你在用 worktree 同时处理多个分支,每个 worktree 是独立的目录,工具在每个目录里都能正常工作,因为它读取的是当前目录的 Git 状态。这一点不需要额外适配。

关于Windows 终端的兼容性:Windows Terminal 对 ANSI 颜色码的支持是完整的,但老的 cmd.exe 可能显示乱码。我在代码里做了检测,如果终端不支持颜色,就自动降级为纯文本输出。检测方式是检查环境变量TERM和WT_SESSION。

关于模型返回中文还是英文:我在提示词里明确要求用中文输出,因为团队里大家的母语是中文,看中文描述更快。但如果你的团队是英文环境,改一行提示词就行。这个不要做成自动检测,自动检测经常出错,不如让用户显式配置。

关于审查结果的持久化:默认不保存任何结果,审查完就打印到终端。如果想把结果存下来做趋势分析,加--output参数指定文件路径即可。我不建议默认保存,因为审查结果里可能包含代码片段,存到磁盘上多一份泄露风险。

关于密钥轮换:如果你在多个项目里用同一个模型密钥,建议定期轮换。工具本身不管理密钥生命周期,这是用户自己的责任。但我在配置文件读取逻辑里加了一个检查:如果密钥看起来像是占位符(比如包含xxx或your-key-here),就给出明确提示,避免用户忘了替换。

最后说一个我踩过的坑:不要用git diff的--word-diff模式。我一开始觉得按词对比更精细,模型能看得更清楚。但实际上--word-diff的输出格式和普通 diff 完全不同,解析逻辑要重写,而且模型对这种格式的理解也不如标准 diff。折腾了半天,最后还是回到了标准 diff 格式。有些看起来更“高级”的选项,实际用起来未必更好,标准方案往往是最稳的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 8:29:36

陶瓷企业凭什么跻身行业五十强?科技创新与智能制造双轮驱动

1. 开篇&#xff1a;一家日用陶瓷企业&#xff0c;为什么能靠“科技创新”挤进行业头部阵营先说个有意思的现象。提到陶瓷行业&#xff0c;很多人的第一反应还停留在“传统产业”“劳动密集型”“高耗能高排放”这些刻板印象上。但实际上&#xff0c;最近这些年陶瓷行业的洗牌速…

作者头像 李华
网站建设 2026/10/19 8:29:12

KepServerEx汉化版OPC通信配置与调试实战指南

简介&#xff1a;KepServerEx V4.264.401 汉化版资源包&#xff0c;面向工业自动化工程师、上位机组态开发人员及 Kepware 初学者&#xff0c;用于解决英文软件不便使用的问题。压缩包整体约 38.17MB&#xff0c;文件总数未单独标注&#xff0c;内容以安装程序、注册机与汉化文…

作者头像 李华
网站建设 2026/10/19 8:28:22

STM32F4多通道DMA ADC配置实战:从采样抖动到双缓冲稳定采集

简介&#xff1a;这份资源面向STM32F4嵌入式开发者与学习者&#xff0c;聚焦多通道ADC与DMA协同采集这一实战主题&#xff0c;帮助解决多路模拟信号高速采样时CPU占用高、中断频繁的问题。压缩包共103个文件&#xff0c;以47个.h头文件与46个.c源文件为主体&#xff0c;涵盖ADC…

作者头像 李华
网站建设 2026/10/19 8:27:54

CNN与LSTM在邮件分类中的工程化落地实践

1. 为什么垃圾邮件分类还在用CNN和LSTM&#xff1f;一个被低估的“小场景”真相很多人看到“英文垃圾邮件分类”第一反应是&#xff1a;这不早就是个被做烂的NLP入门题吗&#xff1f;用个TF-IDF朴素贝叶斯&#xff0c;准确率95%&#xff0c;跑完就交差。我最初也这么想——直到…

作者头像 李华
网站建设 2026/10/19 8:27:17

CLI驱动的Git代码审查新范式:LLM深度集成pre-commit

1. 项目概述&#xff1a;这不是一个“工具”&#xff0c;而是一套可落地的代码审查新范式 “open-code-review”这个词乍看像某个开源项目名&#xff0c;但实际它代表的是一种正在快速成型的工程实践——把大语言模型&#xff08;LLM&#xff09;深度嵌入到开发者日常的 Git 工…

作者头像 李华
网站建设 2026/10/19 8:27:14

jieba分词Java版:中文搜索与标签系统的分词实践

简介&#xff1a;这是一份面向Java开发者的中文分词工具包&#xff0c;将Python版jieba分词的核心能力移植到Java环境&#xff0c;适用于文本挖掘、搜索引擎索引构建、词频统计等场景。压缩包共30个文件&#xff0c;包含10个Java源文件、11个编译后的class文件&#xff0c;以及…

作者头像 李华