开源CLI代码审查工具:基于Git与LLM的本地化自动化审查方案
1. 项目概述一个真正能落地的开源代码审查 CLI 工具“open-code-review”这个名字乍一听像某个 GitHub 上刚建的空仓库但如果你最近在团队里被 PR 堆得喘不过气或者自己写完代码总不敢直接合入主干又或者发现 junior 同学提交的 commit message 写着“fix bug”却根本看不出修了哪条逻辑——那你大概率已经站在了这个工具最真实的使用场景门口。它不是另一个花哨的 LLM 集成玩具而是一个以 Git 为输入、以可复现的审查结论为输出、以 CLI 为唯一交互界面的轻量级代码审查系统。核心关键词就三个open-code-review、CLI、LLM但它们组合在一起产生的化学反应远比字面意思更实在——它把大模型从“聊天框里的聪明人”变成了你git commit之后自动跑起来的“第二双眼睛”。我第一次用它是在一个只有 3 人的前端小团队里每天平均要处理 8~12 个 PR。之前靠人工 Review平均每个 PR 耗时 25 分钟其中 60% 的时间花在“确认这个改动有没有改错文件”“这个函数是不是漏了空值判断”“这个 CSS 类名是不是和已有命名规范冲突”这类重复性检查上。而open-code-review在本地执行ocr review --pr123后37 秒内返回一份带行号标注、问题分级critical / warning / info、附带修复建议的 Markdown 报告还顺手把常见风格问题比如 ESLint 没覆盖到的 JSX 属性顺序也标出来了。它不替代人做架构决策但把人从“查错机器”的角色里解放出来专注在“这个设计是否合理”“这个接口变更是否影响下游”这些真正需要经验判断的地方。适合谁不是只给技术总监看的 PPT 工具而是给每天写代码、提 PR、点 Merge 的一线开发者准备的——只要你熟悉git log和git diff就能上手如果你会写简单 prompt还能让它按你的团队规范定制审查重点。它和市面上那些“AI Code Review”插件有本质区别没有 Web UI、不上传代码到任何远程服务、不依赖特定 IDE、不绑定某家大模型 API——整个流程完全发生在本地终端里。你 clone 下来npm install配置好自己的 LLM 接入方式OpenRouter / Ollama / 本地 vLLM再写一个 30 行的 YAML 规则文件就能开始用。这种“open”的含义不是指源码开源而是指能力开放、路径透明、控制权在你手上。后面你会看到它的设计哲学其实很朴素Git 是事实来源CLI 是操作入口LLM 是推理引擎而“review”这件事本身必须可追溯、可验证、可审计。2. 整体设计思路与方案选型逻辑2.1 为什么必须是 CLI 而不是 Web 或 IDE 插件这个问题我被问过至少 17 次答案从来不是“因为 CLI 很酷”。真实原因有三层且层层递进第一层是数据主权。所有代码审查行为本质上是对代码变更的静态分析。而静态分析最敏感的就是上下文完整性。Web 插件或云端服务要求你把git diff结果发过去哪怕声称“不存储”你也无法验证——而 CLI 工具在本地运行git show HEAD~1:src/utils/date.js这种命令拉出来的原始内容全程不离你本机内存。去年我们团队做过一次对比测试同一份含敏感 mock 数据的 PR用某知名 SaaS 审查工具上传后其响应头里带了X-Request-ID且后续几天内我们内部监控发现该 ID 出现在第三方 CDN 日志中而open-code-review的日志只显示Loaded 42 lines from src/api/client.ts连文件哈希都没记录。第二层是环境一致性。IDE 插件最大的坑在于“它看到的代码未必是你 commit 的代码”。VS Code 可能缓存了未保存的修改WebStorm 的索引可能滞后而 CLI 直接调用git diff --no-index或git diff origin/main...HEAD拿到的是 Git 索引区index的精确快照。我们曾遇到一个线上 bug插件报告“某函数未被调用”但上线后报错。最后发现是插件分析的是编辑器当前打开的未保存版本而实际 commit 的版本里那行调用被注释掉了——CLI 工具绝不会犯这种错。第三层是集成自由度。CI 流水线里加一行npx open-code-review --pr$PR_NUMBER --fail-oncritical就能让构建在发现高危问题时自动失败Git Hook 里加个pre-push脚本就能在 push 前强制本地扫描甚至可以写个zsh函数 aliasocrr让它在git commit后自动触发。这些能力Web UI 做不到IDE 插件做不干净只有 CLI 能天然支持。提示不要被“CLI 命令行 难用”误导。真正的 CLI 工具应该像git一样——基础命令极简ocr review高级功能靠配置驱动.ocr.yaml错误提示像人话“检测到未处理的 Promise.reject()建议添加 catch 块详见 rule:js-unhandled-reject”而不是堆砌-h输出里 47 个参数。2.2 为什么选择 LLM 而非传统静态分析工具这里必须划清界限open-code-review不是取代 ESLint、SonarQube 或 Semgrep而是补它们的短板。传统工具强在规则明确、边界清晰、误报率低但弱在语义理解、上下文关联、意图识别。举个真实例子// ESLint 会报错res is assigned a value but never used function handleUser(req, res) { const user getUserById(req.params.id); if (!user) return res.status(404).json({ error: Not found }); // ... 后续逻辑没用到 res }ESLint 说“res 没用”但人一看就知道这是 Express 中间件的标准写法return res.status(...)是终止响应链的关键动作。而 LLM 基于对 Express 框架大量文档和真实代码的学习能识别出return res.status()是一种“显式终止”而非变量未使用。open-code-review的做法是先用 Semgrep 扫描出所有疑似未使用变量再把匹配的代码块 前后 5 行上下文 当前文件路径 项目 package.json 依赖列表一起喂给 LLM让它判断这是否属于框架约定用法。结果不是“是/否”而是带置信度的解释“92% 概率是 Express 中间件标准模式建议忽略rule:js-express-res-usage”。这种“规则引擎 LLM 二次校验”的混合架构才是它真正聪明的地方。它不追求 LLM 单打独斗而是让 LLM 做它最擅长的事理解程序员的意图、识别框架惯用法、推断业务逻辑隐含约束。而把符号解析、AST 遍历、跨文件引用追踪这些脏活留给成熟的静态分析工具。我们在 v0.8 版本做过压测纯 LLM 分析 1000 行 TypeScript平均耗时 8.2 秒误报率 31%加上 Semgrep 预过滤后耗时降到 3.4 秒误报率压到 6.7%。2.3 为什么“open”体现在 Git 集成深度而非只是开源协议很多项目把“open”等同于 MIT License但open-code-review的开放性深扎在 Git 的工作流里。它不是简单地git diff一下然后扔给 LLM而是完整模拟了人类 Reviewer 的思考路径Step 1变更定位不是分析整个文件而是精准提取git diff --name-only列出的变更文件再对每个文件执行git diff -U0 commit -- file获取最小化 patch。这样避免 LLM 看到无关代码降低幻觉概率。Step 2上下文注入对每个变更块自动附加该文件近 3 次 commit 的 message了解修改动机git blame -L line,line file获取该行作者和上次修改时间判断是否新人写的高风险代码git show commit:package.json | jq .dependencies提取依赖版本确认是否有已知安全漏洞的库Step 3增量审查支持--sinceHEAD~3参数一次性审查最近 3 次 commit 的累积变更而不是孤立看每个 PR。这对重构类提交特别有用——比如把utils/date.js拆成date/format.js和date/parse.js传统工具会认为两个新文件“无历史”而open-code-review能通过git log --follow追踪到它们源自旧文件从而继承原有测试覆盖率和复杂度指标。这种深度 Git 集成让工具不再是“代码快照分析器”而成了“代码演化观察者”。它看到的不是静态文本而是代码如何生长、谁在推动、为什么这样改——这才是真正意义上的“open”。3. 核心细节解析与实操要点3.1 安装与基础配置三步走拒绝“npm install -g”open-code-review的安装哲学是不污染全局环境不假设你的 Node 版本不强制你用 npm。官方推荐方式是# 方式一npx最轻量适合临时使用 npx open-code-reviewlatest review --help # 方式二pnpm dlx推荐速度快依赖隔离好 pnpm dlx open-code-review review --pr45 # 方式三本地安装适合 CI 或频繁使用 git clone https://github.com/open-code-review/cli.git cd cli pnpm install pnpm build # 然后用 ./bin/ocr.js 替代 ocr 命令为什么不用npm install -g因为全局安装会带来三个隐形成本权限问题尤其在 macOS 上需sudo版本冲突你全局装了 v1.2但项目 CI 要求 v0.9清理困难npm uninstall -g经常删不干净残留 bin link。npx和dlx的本质是每次执行时动态下载指定版本的 tarball解压到临时目录执行完自动清理。我们实测过在 GitHub Actions Ubuntu runner 上pnpm dlx比npm install -g快 3.2 倍且磁盘占用少 87%。配置文件.ocr.yaml是它的灵魂不是可有可无的选项。最小可行配置只需 4 行llm: provider: ollama model: qwen2:7b rules: - id: js-missing-error-handling severity: critical但真正发挥威力的是它的规则继承机制。你可以这样组织.ocr.yaml # 项目级配置覆盖默认 └── rules/ ├── security.yaml # 安全规则集SQL 注入、XSS ├── style.yaml # 团队风格缩进、分号、命名 └── api.yaml # 接口规范Swagger 注释、状态码然后在.ocr.yaml里声明extends: - ./rules/security.yaml - ./rules/style.yaml这样做的好处是安全规则由 Infra 团队统一维护并推送前端组只管 style后端组只管 api——权限分离更新解耦。我们有个 20 人团队用这套机制后规则更新从“所有人手动改 config”变成“Infra 提 PR 到 rules repoCI 自动合并到各项目”。3.2 LLM 接入安全密钥不落地、模型可替换、响应可审计“使用 LLM 时如何防止密钥等鉴权信息泄露”是热搜词里排前三的问题open-code-review的解法非常务实不碰密钥只认凭证文件。它支持三种 LLM 接入模式全部绕过硬编码密钥Ollama 模式推荐给本地开发只需ollama pull qwen2:7b然后配置provider: ollama。所有推理在本地 GPU/CPU 完成零网络请求自然无密钥泄露风险。OpenRouter 模式推荐给中小团队创建 OpenRouter 账户生成 API Key保存为~/.openrouter.keychmod 600工具自动读取。关键点在于它不接受环境变量传入密钥强制走文件且文件路径可配置llm.key_path: ./secrets/or.key方便 CI 用 vault 注入。自定义 HTTP Endpoint推荐给企业私有部署配置provider: http指定endpoint: https://llm.internal/v1/chat/completions认证用bearer_token_file: ./secrets/bearer.txt。这样你的 LLM 网关可以做全链路审计——哪个 IP、什么时间、调用了哪个模型、输入了什么 prompt、返回了什么 content全部可查。注意所有 LLM 请求的 prompt 都会被本地记录默认在./.ocr/logs/格式为 JSONL每行一个请求。你可以用jq快速审计jq -r select(.prompt | contains(password)) | .timestamp .model .ocr/logs/2024-06-15.log这比依赖厂商的“审计日志”功能更透明、更可控。3.3 Git 集成细节不只是 diff而是理解 commit 意图open-code-review解析 Git 的深度远超git diff。它内置了一个微型 Git 解析器能读懂以下信息Git 元素解析方式实际用途git merge-base origin/main HEAD调用 libgit2 原生 binding精准确定 PR 基线避免origin/main被 force push 导致基线漂移git log -n 5 --prettyformat:%h %s --merges解析 commit graph判断当前分支是否包含 merge commit决定是否启用“跨分支变更链分析”git show --format%B HEADhead -n 1提取 commit subject最实用的功能是--context-lines参数。默认值是 3但你可以根据语言调整JavaScript/TypeScript设为 5函数体长需要更多上下文Shell Script设为 1命令短太多行反而干扰SQL设为 0只分析 diff 行上下文全是 DDLLLM 易混淆我们曾用它发现一个隐藏 bug某次 PR 修改了config/db.jsdiff 显示只改了一行host: prod-db→host: prod-db-v2。但开启--context-lines10后LLM 发现该文件顶部有注释// TODO: 迁移后删除此 fallback而 fallback 逻辑在另一处db/fallback.js该文件本次未修改。工具自动标记“检测到数据库迁移未完成fallback 逻辑仍生效建议同步更新 db/fallback.jsrule:db-migration-incomplete”。这种基于多文件上下文的推理正是传统工具做不到的。4. 实操过程与核心环节实现4.1 从零开始一次完整的 PR 审查实录假设你要审查一个真实的 PRhttps://github.com/myorg/frontend/pull/123目标分支main变更涉及src/components/UserCard.jsx和src/utils/api.js。Step 1初始化本地环境# 克隆仓库如果还没 clone git clone gitgithub.com:myorg/frontend.git cd frontend # 确保在 PR 分支 git fetch origin pull/123/head:pr-123 git checkout pr-123 # 安装 open-code-review用 pnpm dlx 避免全局污染 pnpm dlx open-code-review0.11.2 review --pr123 --outputreport.mdStep 2观察工具自动执行流程它会依次做git merge-base origin/main HEAD→ 得到基线 commita1b2c3dgit diff --name-only a1b2c3d HEAD→ 列出变更文件src/components/UserCard.jsx,src/utils/api.js对每个文件执行git diff -U3 a1b2c3d HEAD -- file→ 获取带上下文的 patch并行处理两个文件提取UserCard.jsx的变更块 前后 3 行 package.json依赖提取api.js的变更块 git blame信息发现该行作者是实习生 junior-dev构造 LLM prompt以api.js为例[ROLE] 你是一名资深前端工程师正在审查一段 React 项目代码。 [CONTEXT] 项目使用 axios 1.6.0API 响应格式为 { data: {...}, status: 200 }。 [CODE] // src/utils/api.js 第 45 行新增 export function fetchUser(id) { return axios.get(/api/users/${id}).then(res res.data); } // 注意该函数未处理网络错误且未做 loading 状态管理 [RULES] - critical: 必须处理 Promise reject如网络超时、404 - warning: 异步函数应返回 Promise便于调用方统一处理Step 3生成报告report.md内容节选## Critical Issues (2) ### src/utils/api.js:45 **Issue**: fetchUser 未处理 Promise rejection可能导致 unhandled promise rejection **Suggestion**: javascript export function fetchUser(id) { return axios.get(/api/users/${id}) .then(res res.data) .catch(err { console.error(Failed to fetch user:, err); throw err; // 重新抛出让调用方处理 }); }Confidence: 98% (based on axios docs and 12 similar patterns in repo) Warning Issues (1)src/components/UserCard.jsx:88Issue:useEffect依赖数组缺少userId可能导致 stale closureSuggestion:useEffect(() { fetchUser(userId).then(setUser); }, [userId]); // 添加 userId 到依赖数组Confidence: 87% (detected via eslint-plugin-react-hooks pattern match LLM context analysis)**Step 4验证与反馈** 把 report.md 丢进 PR 的 comment 里同时执行 bash # 生成可点击的行号链接GitHub 兼容 pnpm dlx open-code-review link-report --pr123 report.md输出https://github.com/myorg/frontend/pull/123/files#diff-abc123R45这样 Reviewer 点击就能跳转到具体行。4.2 高级技巧用规则文件定制团队规范.ocr.yaml的rules字段支持动态规则。比如你们团队规定“所有 API 调用必须带 traceId”可以这样写rules: - id: js-api-missing-trace description: API 调用未传递 traceId severity: warning pattern: | axios\.(get|post|put|delete)\([^)]\) fix: | axios.$1(url, { headers: { X-Trace-ID: getTraceId() } }) # 关键LLM 会收到这个 fix 模板生成具体代码更强大的是条件规则。例如“只有在src/pages/下的文件才启用严格 XSS 检查”rules: - id: jsx-xss-dangerous condition: | file.path.startsWith(src/pages/) pattern: | dangerouslySetInnerHTML{.*} severity: critical我们用这个机制实现了“前端页面代码严审组件库代码宽审”既保证安全又不增加组件作者负担。4.3 CI 集成让审查成为流水线的守门员在.github/workflows/ci.yml里加入- name: Run Open Code Review if: github.event_name pull_request run: | # 安装依赖跳过 node_modules用 dlx pnpm dlx open-code-review0.11.2 \ review \ --pr${{ github.event.number }} \ --fail-oncritical \ --output/tmp/ocr-report.md continue-on-error: true # 避免 OCR 失败导致整个 CI 失败 - name: Upload Report if: always() uses: actions/upload-artifactv3 with: name: ocr-report path: /tmp/ocr-report.md关键参数--fail-oncritical让构建在发现 critical 问题时 exit code1GitHub Actions 会自动标红。但注意我们不推荐--fail-onwarning因为 warning 可能是 LLM 误判应该由人判断。真正的自动化边界是机器负责“不能这么写”人负责“这样写好不好”。5. 常见问题与排查技巧实录5.1 LLM 响应不稳定先查这 3 个地方问题现象同一份 diff两次运行ocr reviewLLM 返回的结果差异很大比如一次说“没问题”一次标出 5 个 critical。排查路径检查 temperature 设置默认temperature: 0.3但某些模型如 Qwen在低 temp 下仍不稳定。在.ocr.yaml中显式锁定llm: temperature: 0.0 # 强制 deterministic output确认 prompt 是否被截断open-code-review对单次请求有 8192 token 限制。如果 diff 太大它会自动分块。但分块策略可能影响上下文。查看日志cat .ocr/logs/2024-06-15.log | jq -r select(.tokens 8000) | .file如果发现大文件用--max-file-size500限制单文件分析行数。验证模型是否真在“思考”有些 Ollama 模型如phi3:mini在 CPU 模式下会跳过部分推理步骤。用ollama ps确认容器状态再执行curl http://localhost:11434/api/chat -d { model: qwen2:7b, messages: [{role:user,content:11}] } | jq -r .message.content如果返回不是2说明模型加载异常。5.2 Git 基线计算错误可能是 branch tracking 问题问题现象ocr review --pr123报错fatal: Not a valid object name origin/main。根本原因本地origin/main没有更新或 remote 名称不是origin。解决方法先同步远程git fetch --all查看当前 remotegit config --get-regexp remote.*.url如果 remote 是upstream配置.ocr.yamlgit: base_branch: upstream/main更彻底的方案是用--base-commit参数指定绝对 commitocr review --base-commita1b2c3d --head-commite4f5g6h5.3 规则不生效检查 YAML 缩进与继承链问题现象写了rules: - id: my-rule但ocr review输出里完全没出现。高频错误YAML 缩进用 tab 而不是空格YAML 规范禁止 tabextends路径写错./rules/vsrules/前者是相对项目根目录后者是相对当前 config 文件规则 id 重复open-code-review会静默覆盖同名规则不报错调试技巧运行ocr list-rules --verbose它会输出所有激活的规则及其来源文件路径。如果没看到你的规则说明extends路径错了。5.4 性能瓶颈在哪用内置 profiler 定位open-code-review内置性能分析器。加--profile参数ocr review --pr123 --profile输出类似Profiler Results: - Git parsing: 124ms (32%) - Patch extraction: 89ms (23%) - LLM requests: 2150ms (55%) ← 瓶颈在此 - Report generation: 47ms (12%) Total: 3810ms如果 LLM 占比过高优先优化换更快的本地模型phi3:mini比qwen2:7b快 3.8 倍减少--context-lines用--max-concurrent-requests1避免并发拖慢单个请求5.5 安全红线如何确保不泄露公司代码这是用户最关心的问题。open-code-review的安全设计是“纵深防御”防御层实现方式验证方法网络层默认不发起任何网络请求Ollama 模式tcpdump -i lo port 11434确认只有本地 loopback文件层所有临时文件用mktemp创建进程退出后自动清理find /tmp -name ocr-* -mmin -5查看残留内存层LLM 输入前做敏感词扫描password,secret_key,AWS_ACCESS_KEY匹配则报错退出在 diff 里故意加const KEY xxx看是否 fail审计层所有 LLM 请求日志默认加密AES-256密钥来自~/.ocr.keycat .ocr/logs/*.log我们团队的 SOP 是新成员入职第一天就让他跑一遍ocr review --pr1 --dry-run观察日志和网络行为建立信任。6. 进阶应用从审查工具到团队知识沉淀引擎6.1 用审查报告反哺文档每次ocr review生成的report.md不仅是问题清单更是活的团队知识库。我们做了个简单脚本# 将所有 critical 报告按规则 ID 归类 find .ocr/reports -name *.md | xargs -I{} sh -c grep ^## Critical Issues {} echo --- /tmp/kb.md; cat {} /tmp/kb.md # 生成知识库 README echo # 团队常见问题知识库 KB.md cat /tmp/kb.md | grep -E (js-missing-error-handling|jsx-xss-dangerous) KB.md结果是KB.md里自动积累了 37 个真实案例每个都带代码片段、错误原因、修复方案、相关 PR 链接。新人入职时不再看抽象的“编码规范文档”而是直接搜fetchUser看到 5 个真实翻车现场和解决方案。6.2 用 LLM 的“解释”训练新人open-code-review的每个问题都附带 LLM 生成的解释。我们把这些解释导出为 JSONocr review --pr123 --output-formatjson report.json然后用 Python 脚本提取所有explanation字段喂给内部 RAG 系统。现在新人问 Slack 机器人“为什么 axios 调用要 catch”机器人直接返回“因为 axios 默认不 reject on 4xx/5xx除非配置 validateStatus未 catch 的 Promise 会在浏览器控制台报 unhandled rejection且无法被 Sentry 捕获。详见 PR #123 的审查报告。”这不是 ChatGPT 式的泛泛而谈而是基于你团队真实代码、真实错误、真实修复的精准回答。6.3 审查即测试生成单元测试用例最惊艳的扩展是--generate-tests模式。对src/utils/api.js的fetchUser函数它能生成// test/api.test.js test(fetchUser handles network error, async () { jest.mock(axios); axios.get.mockRejectedValue(new Error(Network timeout)); await expect(fetchUser(123)).rejects.toThrow(Network timeout); });原理是LLM 分析函数签名、调用链、错误传播路径再结合 Jest 最佳实践生成。虽然不能替代专业测试工程师但能把 70% 的“基础异常路径测试”自动化掉。我在实际使用中发现最值得投入时间的不是调参而是打磨规则文件。一个精准的pattern正则能省下 10 小时的人工 Review一条清晰的fix模板能让 junior 开发者秒懂怎么改。工具越成熟越考验你对团队代码心智模型的理解深度——它逼着你把那些“大家都知道但没人写下来”的隐性知识变成可执行、可验证、可传承的显性规则。