
1. 项目概述这不是一个“工具”而是一套可落地的开源代码审查工作流“open-code-review”这四个字乍看像某个 GitHub 仓库名或是某款新出的 CLI 工具代号但真正把它拆开来看——open开放、code代码、review审查——它指向的其实是一个正在快速成型的工程实践范式用开源理念重构代码审查本身。不是简单地把 PR 评论框换成 LLM 回复框而是从流程设计、权限边界、上下文组织、结果沉淀四个维度重新定义“谁在审、审什么、怎么审、审完留下什么”。我从去年开始在三个中型团队里推动这套实践从最初用git diff --no-indexcurl调 API 的土法炼钢到如今稳定运行在 CI 流水线里的标准化审查节点核心就一条让 LLM 成为审查流程里的“协作者”而不是“替代者”或“黑箱裁判”。你可能正面临这些具体问题每次 CR 都要手动复制粘贴 diff 到 ChatGPT反复调整 prompt漏掉关键上下文新人提交的 PR 总是格式混乱、缺少测试说明但资深同事又没时间逐行盯审查意见散落在 Slack、飞书、GitHub 评论里三个月后根本找不到历史决策依据最怕的是——LLM 在 review 里顺手把.env文件里的密钥、API Token 给“总结”进回复里一发就炸。“open-code-review”正是为解决这些真实痛点而生。它不绑定任何特定模型DeepSeek、Qwen、Claude、本地 Llama3 都能接入不强制使用某家云服务也不要求你改写整个 Git 工作流。它本质是一组可组合的 CLI 命令、一套轻量级配置规范、一份明确的上下文裁剪规则以及最关键的——一套防止敏感信息外泄的预处理机制。关键词里反复出现的CLI、git、LLM不是并列关系而是层级依赖git提供原始输入源CLI是执行载体LLM是能力引擎三者必须通过明确契约连接而非简单管道拼接。接下来我会带你从零搭起这个系统每一步都基于我们踩过的坑、压测过的参数、线上跑过半年的真实日志。2. 整体架构设计为什么必须放弃“一键 review”幻觉2.1 传统 LLM 代码审查工具的三大死穴市面上多数“AI Code Review”工具包括某些知名 IDE 插件默认走的是“单次大块输入 → 单次大块输出”路径。比如把整个 PR 的 200 行 diff 直接塞给模型让它返回一段自然语言评论。这种设计在 Demo 视频里很炫但在真实工程中会迅速暴雷上下文失焦LLM 的 attention 机制天然倾向关注局部高频词。当 diff 里混着config.py的密钥变更、utils.py的算法优化、test_*.py的断言补充时模型大概率只“看到”os.getenv(API_KEY)这种高亮词反而忽略def calculate_discount()里那个边界条件错误。我们实测过在 150 行 diff 中模型对安全类问题的识别率比对逻辑类问题低 47%根源就是噪声干扰。责任模糊化当 LLM 返回“建议检查用户输入校验”但没指明具体哪一行、哪个函数、对应哪个业务场景时开发者只能猜。更糟的是如果模型错了比如误判某处 SQL 拼接为安全没人能追溯它是基于哪段上下文做的判断——因为输入本身就是一团混沌。密钥泄露无防护这是最致命的。很多工具直接git diff输出原始文本送入 LLM而git diff默认包含文件头diff --git a/.env b/.env、甚至二进制文件的 base64 片段。去年某团队用某款热门 CLI 工具扫描内部项目结果 LLM 在 review 结果里原样复述了DB_PASSWORDxxx——不是模型“记住了”而是输入里明晃晃写着模型只是忠实反射。提示真正的 open-code-review 必须在 LLM 触达前完成三道过滤① 文件类型白名单只允许.py,.js,.ts,.go等源码拒.env,.yml,.json② 行级敏感词拦截匹配password,token,secret,key等关键词等号/冒号模式③ diff 上下文裁剪只保留行及紧邻的-行剥离无关函数体。2.2 我们采用的分层流水线架构我们最终落地的架构是四层流水线每一层都有明确输入/输出契约且全部通过标准 Unix CLI 工具链实现不依赖任何闭源 SDK[Git Event] ↓ 1. Context Extractor上下文提取器→ 输出结构化 JSON{ files: [...], diffs: [...], commits: [...] } ↓ 2. Sanitizer净化器→ 过滤敏感文件、脱敏行、裁剪 diff → 输出 clean_diff.json ↓ 3. LLM Orchestrator编排器→ 按文件粒度分发请求、设置 temperature0.3、强制 JSON Schema 输出 → 收集 review.json ↓ 4. Reporter报告器→ 合并结果、生成 Markdown 报告、注入 GitHub PR Comment API → 完成闭环这个设计的关键在于解耦Context Extractor只管“从 Git 里挖出什么”用git show,git diff-tree,git log -n1组合实现输出绝对可复现Sanitizer是安全核心用jqsed 自定义 Python 脚本三层过滤所有规则可审计、可回滚LLM Orchestrator不碰 Git只消费 JSON支持热切换模型端点OpenRouter / Ollama / 自建 vLLM失败自动降级Reporter专注呈现把review.json转成带行号锚点的 GitHub 评论开发者点击就能跳转。之所以坚持 CLI 而非 GUI 或 Web UI是因为CI/CD 流水线里没有浏览器审查必须发生在git push后的毫秒级响应窗口开发者需要在终端里open-code-review --last-commit快速复现问题而不是打开网页再登录。2.3 为什么拒绝“Agent”和“Embedding”噱头热搜词里频繁出现的agent、embedding、skill等概念在 open-code-review 场景里是过度设计。我们做过对比实验用 RAG检索增强方案先向量库存入公司编码规范 PDF再让 LLM 基于检索结果 review —— 结果模型 83% 的回复引用了错误条款向量相似度高但语义错位且延迟增加 3.2 秒用 Agent 框架让 LLM 自主决定“是否需要查 Git Log”、“是否需要读 README” —— 实际运行中它 92% 的时间在循环调用git log -n5却从不真正用这些信息做判断所谓 “LLM Skill 编配经验层”在代码审查里就是个伪需求审查规则是确定性的PEP8、SOLID、OWASP Top 10不需要“持续进化”需要的是确定性、可验证、可审计。所以我们的原则很粗暴LLM 只做一件事——基于明确输入生成符合 Schema 的结构化评论。其他事交给 Git、Shell、jq 和人类。这不是技术保守而是对工程可靠性的敬畏。当你在生产环境每天处理 200 PR 时你会感激每一行代码都走在确定性轨道上。3. 核心模块详解从 Git 提取到安全输出的完整链路3.1 Context Extractor用原生 Git 命令挖出精准上下文很多团队卡在第一步不知道该给 LLM 送什么。送整个仓库太重送单个文件太窄送git diff原始输出太脏。我们的解法是——用 Git 自身的能力精准定位“这次变更真正影响了什么”。核心命令链已封装为ocr-extractCLI# 1. 获取本次推送的 commit 范围支持单 commit 和多 commit git merge-base origin/main HEAD | xargs -I {} git rev-list --reverse {}..HEAD # 2. 对每个 commit提取变更文件列表排除文档、配置等非代码文件 git show --name-only --prettyformat: $commit | grep -E \.(py|js|ts|go|java|cpp)$ | sort -u # 3. 对每个文件生成最小化 diff只含变更行及上下文不含文件头 git show $commit:$file 2/dev/null | \ awk NRFNR{a[$0]1;next} $0 in a (git diff-tree -p -U0 $commit -- $file | grep -E ^\(?!\\)|^-) -这段脚本的关键细节git merge-base确保只分析相对于主干的增量避免把历史垃圾 diff 也拉进来grep -E \.(py|js|ts|go|java|cpp)$是硬性白名单.env,.yml,.md等文件直接过滤不进入后续流程git diff-tree -p -U0使用-U0参数生成“无上下文 diff”即只显示和-行不带行号标记——这恰恰是 LLM 最需要的“干净变更快照”awk部分是精妙所在它把git show输出的当前文件内容与git diff-tree输出的变更行做交集确保 LLM 看到的行确实是在这个文件的正确位置插入的避免因文件重命名导致的行号错位。输出 JSON 示例context.json{ commit_hash: a1b2c3d, files: [ { path: src/auth/jwt.py, language: python, diff: [ def verify_token(self, token: str) - bool:, try:, payload jwt.decode(token, self.secret_key, algorithms[HS256]), return payload.get(user_id) is not None, except Exception as e:, logger.error(fJWT verify failed: {e}), return False ] } ] }注意这里diff数组里只有行新增代码没有-行删除代码。因为我们发现LLM 对“缺失什么”的判断远不如对“新增了什么”的判断准确。删除逻辑的审查我们交给静态分析工具如 Semgrep和人工确认。3.2 Sanitizer三重防线守住敏感信息这是整个流程的守门员。我们绝不信任任何外部输入所有送到 LLM 的数据必须经过三道过滤第一道文件级过滤Shell 层在ocr-extract输出后立即执行# 检查 context.json 中是否有禁止文件类型 if jq -e .files[] | select(.path | test(\\.(env|yml|yaml|json|toml|ini)$)) context.json /dev/null; then echo ERROR: Forbidden file type detected 2 exit 1 fi第二道行级敏感词扫描Python 脚本用sensitive-scan.py处理每个diff数组import re import sys import json PATTERNS [ (r(password|passwd|pwd|token|secret|key|api[_-]?key|auth[_-]?token)\s*[:]\s*[\]([^\])[\], SECRET_VALUE), (r(https?://[^\]/api/[^\])[\]?, API_ENDPOINT), ] def sanitize_line(line): for pattern, tag in PATTERNS: match re.search(pattern, line, re.IGNORECASE) if match: # 替换为占位符保留位置和长度 placeholder f[REDACTED_{tag}_{len(match.group(2))}] return re.sub(pattern, f\\1 {placeholder}, line) return line # 主逻辑遍历 context.json 的每个 diff 数组 data json.load(sys.stdin) for file in data[files]: file[diff] [sanitize_line(line) for line in file[diff]] print(json.dumps(data))关键点不是简单删掉整行而是用[REDACTED_SECRET_VALUE_32]这种占位符替换既消除风险又保留代码结构比如self.api_key [REDACTED_SECRET_VALUE_32]仍能被 LLM 理解为“密钥赋值”正则表达式用re.IGNORECASE覆盖PASSWORD,password,Password等变体占位符长度与原密钥一致len(match.group(2))避免因字符串长度突变导致 LLM 注意力偏移。第三道Diff 结构校验jq 层最后用jq确保输出格式合规jq if (.files | length) 0 then error(No valid files found) else . end clean_context.json这三道防线全部失败才报错任一环节异常都会中断流程绝不让可疑数据流入 LLM。我们线上环境半年来0 次密钥泄露事件靠的就是这种“宁可错杀不可放过”的机械式严谨。3.3 LLM Orchestrator小步快跑稳扎稳打很多团队一上来就想“全量 review”结果模型崩、成本高、效果差。我们的策略是按文件粒度分发按问题类型归类按置信度分级。orchestrator 的核心逻辑ocr-reviewCLI# 1. 读取 clean_context.json context$(cat clean_context.json) # 2. 对每个文件构造独立请求避免长上下文干扰 for file in $(echo $context | jq -r .files[] | base64); do _jq() { echo ${file} | base64 -d | jq -r ${1} } path$(_jq .path) lang$(_jq .language) diff$(echo $context | jq -r --arg p $path .files[] | select(.path$p) | .diff | join(\n)) # 3. 构造 prompt严格控制长度 prompt$(cat EOF You are a senior $lang developer reviewing code changes. Focus ONLY on these aspects: - Security: hardcoded secrets, SQL injection, XSS, auth bypass - Correctness: off-by-one errors, null pointer dereference, race conditions - Best practices: naming, error handling, logging, test coverage hints Input diff (only new lines, no file headers): $diff Output EXACTLY in this JSON format: { file: $path, issues: [ { line: 12, severity: high|medium|low, message: Short description, suggestion: Concrete fix suggestion } ] } EOF ) # 4. 发送请求带重试和超时 response$(curl -s -X POST $LLM_ENDPOINT \ -H Content-Type: application/json \ -d {\prompt\:\$(echo $prompt | jq -Rr uri)\,\temperature\:0.3} \ --max-time 30 --retry 2 --retry-delay 1) # 5. 解析并合并结果 echo $response | jq -r .issues[] | {file: $path, line: .line, severity: .severity, message: .message, suggestion: .suggestion} review_raw.json done关键设计点Prompt 严格限定范围开头就声明角色senior dev明确只关注三类问题Security/Correctness/Best practices杜绝模型自由发挥输出强制 JSON Schema用jq解析时如果返回非 JSON 或字段缺失直接丢弃该文件结果不污染整体报告temperature0.3实测发现0.1 太死板总返回模板话术0.5 太跳跃开始编造不存在的行号0.3 是平衡点--max-time 30防止某个文件卡住整个流程超时自动跳过保证整体时效性。我们对比过不同模型Qwen2-72B本地部署准确率最高89%但单次耗时 8.2 秒Claude-3-HaikuAPI速度最快1.3 秒准确率 76%适合初筛DeepSeek-Coder-33BOllama性价比最优6.4 秒/次准确率 83%是我们主力选择。3.4 Reporter让机器评论真正有用LLM 输出的 JSON 再漂亮如果不能无缝融入开发者工作流就是废纸。我们的 reporter 目标只有一个让每条评论都像资深同事亲手写的那样精准、可操作、可追溯。核心功能行号锚点生成解析review_raw.json为每个line字段生成 GitHub 兼容的锚点链接https://github.com/org/repo/blob/a1b2c3d/src/auth/jwt.py#L12这样开发者点击评论就能直接跳转到代码行。Markdown 报告生成## AI Code Review Report (a1b2c3d) ### ✅ Files Reviewed (1/1) - src/auth/jwt.py ### ⚠️ Issues Found (2) #### src/auth/jwt.py line 15 **Severity**: high **Message**: JWT decode lacks algorithm validation, vulnerable to algnone attacks **Suggestion**: Use algorithms[HS256] explicitly and validate payload.get(alg) [Jump to code](https://github.com/org/repo/blob/a1b2c3d/src/auth/jwt.py#L15) #### src/auth/jwt.py line 17 **Severity**: medium **Message**: Generic Exception catch hides root cause; use specific exceptions **Suggestion**: Catch jwt.InvalidTokenError, jwt.ExpiredSignatureError separately [Jump to code](https://github.com/org/repo/blob/a1b2c3d/src/auth/jwt.py#L17)GitHub PR Comment 注入调用 GitHub REST API/repos/{owner}/{repo}/issues/{issue_number}/comments将报告作为 comment 发送。关键技巧用body字段传 MarkdownGitHub 原生渲染在 comment 开头加!-- open-code-review-report --注释方便后续脚本识别和更新如果同个文件已有同类评论用PATCH更新而非POST新建避免刷屏。我们刻意避免“AI 评论”字样所有报告都以Code Review Bot名义发出语气保持专业、中立、建设性。毕竟开发者不需要知道背后是 LLM 还是实习生他们只需要知道这条建议对不对、能不能立刻执行、执行后有没有效果。4. 实操部署指南从本地测试到 CI 集成4.1 本地快速启动5 分钟验证别被前面的架构吓到最小可行版本只需 3 个文件ocr-extract.shContext Extractorsensitive-scan.pySanitizerocr-review.shOrchestrator Reporter步骤克隆示例仓库git clone https://github.com/your-org/open-code-review-demo.git安装依赖pip install pyyaml requests jqjq 需系统级安装brew install jq或apt-get install jq设置模型端点export LLM_ENDPOINThttp://localhost:8000/v1/chat/completions指向你的 Ollama 或 vLLM运行测试# 在任意 Git 仓库内对最近一次 commit 做 review ./ocr-extract.sh | ./sensitive-scan.py | ./ocr-review.sh输出即为 Markdown 报告。验证要点检查context.json是否只含.py文件查看clean_context.json中是否有[REDACTED_SECRET_VALUE_32]占位符确认review.json的line字段是否与实际代码行号一致我们用git show校验过误差为 0。4.2 CI/CD 流水线集成GitHub Actions 示例这才是 open-code-review 的价值爆发点。我们把它嵌入pull_request事件# .github/workflows/code-review.yml name: Open Code Review on: pull_request: types: [opened, synchronize, reopened] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 # 必须获取完整历史用于 git merge-base - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y jq pip install requests - name: Run open-code-review env: LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }} LLM_API_KEY: ${{ secrets.LLM_API_KEY }} run: | ./scripts/ocr-extract.sh | \ ./scripts/sensitive-scan.py | \ ./scripts/ocr-review.sh report.md - name: Post review report if: always() uses: actions/github-scriptv6 with: script: | const report require(fs).readFileSync(report.md, utf8); github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, body: ## AI Code Review\n\n${report} })关键配置说明fetch-depth: 0是必须的否则git merge-base找不到主干secrets.LLM_ENDPOINT存储模型 API 地址如https://api.anthropic.com/v1/messagessecrets.LLM_API_KEY是对应密钥绝不在代码中硬编码if: always()确保即使 review 失败如模型超时也会发一条“Review failed, check logs” 的 fallback 评论避免静默失败。我们线上环境平均耗时 12.4 秒/PR含网络延迟99.2% 的 PR 能在 30 秒内收到首条评论。4.3 模型选型与参数调优实战不是所有 LLM 都适合代码审查。我们实测过 12 款主流模型结论很清晰模型本地/云端单次耗时准确率推荐场景DeepSeek-Coder-33B本地A1006.4s83%主力平衡速度与精度Qwen2-72B本地H1008.2s89%关键模块深度审查Claude-3-Haiku云端1.3s76%初筛、大量 PR 快速过Llama3-70B云端9.7s71%不推荐幻觉率高参数调优黄金组合temperature0.3如前所述最佳平衡点max_tokens1024足够生成详细建议又不会让模型啰嗦top_p0.9保留一定多样性避免陷入模板话术禁用streamtrue流式输出无法保证 JSON 完整性必须等全部返回再解析。Prompt 工程避坑❌ 错误写法“Please review this code and give feedback.”太宽泛模型自由发挥✅ 正确写法“You are a security-focused Python reviewer. Identify ONLY SQL injection, XSS, or auth bypass risks in the following diff. Output JSON with keys: file, line, severity (high/medium/low), message, suggestion.”角色范围格式三重约束我们把所有 prompt 模板存在prompts/目录下按语言和问题类型分类python-security.j2,js-performance.j2用 Jinja2 渲染确保可维护性。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因排查命令解决方案review 报告为空ocr-extract.sh未找到有效文件./ocr-extract.sh | jq .files检查.gitignore是否误屏蔽了源码文件行号错位跳转到错误行git diff-tree与git show版本不一致git show HEAD:src/file.py | head -n20对比git diff-tree -p HEAD -- src/file.py统一使用git show $commit:$file获取基准内容LLM 返回非 JSONPrompt 过长触发截断echo $prompt | wc -c应 8192启用 diff 裁剪git diff -U0awk过滤敏感信息未脱敏sensitive-scan.py正则未覆盖新密钥格式echo API_TOKENabc123 | python sensitive-scan.py在PATTERNS中添加rAPI[_-]?TOKEN\s*[:]\s*[\]([^\])[\]GitHub 评论不显示API 权限不足curl -H Authorization: token $GITHUB_TOKEN https://api.github.com/rate_limit在 GitHub Settings → Secrets 中添加GITHUB_TOKEN权限勾选contents5.2 真实踩坑记录与独家技巧坑 1Git Submodule 导致的 diff 错乱某次上线后review 总是漏掉vendor/下的 Go 依赖变更。排查发现git diff-tree默认不递归 submodule。解决方案# 在 ocr-extract.sh 中添加 git submodule foreach --recursive git diff-tree -p $sha1 $sha2 -- .但更优解是——直接禁用 submodule 审查因为依赖变更应由 Dependabot 或 Renovate 专项处理不属于 open-code-review 范畴。坑 2Windows 换行符导致 LLM 解析失败CI 在 Windows runner 上运行时git diff输出 CRLF而 LLM 模型训练数据多为 LF。结果模型把\r\n当作特殊字符影响注意力分布。解决方案# 在 sanitizer 之前统一转换 sed s/\r$// input.json clean.json坑 3LLM 对“TODO”注释的过度反应模型看到# TODO: refactor this就疯狂建议重构但实际这是已知技术债。技巧在 prompt 中加入例外规则Ignore comments containing TODO, FIXME, HACK — these are intentional placeholders.独家技巧用 review 结果反哺静态分析规则我们把半年积累的review.json中的high级问题聚类发现 68% 都是同一类datetime.now()未带 timezone。于是我们用 Semgrep 写了这条规则rules: - id: datetime-now-without-tz patterns: - pattern: datetime.now() message: datetime.now() without timezone is ambiguous languages: [python]现在这条规则在 pre-commit 阶段就拦截review 阶段只处理更复杂的问题。让 LLM 做探索性发现让静态分析做确定性拦截这才是人机协作的正确姿势。6. 进阶扩展从单点工具到团队知识库open-code-review 的终极形态不是取代人类而是把人类的审查智慧沉淀为可复用、可演进、可量化的团队资产。6.1 审查意见知识图谱每次 review 生成的review.json我们存入 SQLite 数据库并建立关联file_path→module_architecture通过 AST 分析自动打标issue.message→rule_id映射到公司《代码审查手册》第 3.2.1 条suggestion→code_snippet_id存入 snippet 库带语言、框架、上下文标签这样当新人提交类似代码时系统不仅能给出通用建议还能推送“张工上周在auth_service也遇到同样问题他的修复方案是…”——把隐性经验变成显性知识。6.2 动态阈值调优我们发现不同模块的“合理 review 时长”差异巨大core/utils.py平均 4.2 秒/文件逻辑简单payment/gateway.py平均 18.7 秒/文件涉及多第三方 API于是我们在 orchestrator 中加入动态超时timeout$(jq -r --arg p $path select(.path$p) | .timeout // 10 module_timeout.json) curl --max-time $timeout ...module_timeout.json由历史数据自动更新确保不因某个慢模块拖垮整体。6.3 人类反馈闭环最关键的一环让开发者能一键标记 review 是否有用。我们在 GitHub 评论末尾加[ This helped] [ Not relevant] [ Suggest improvement]点击后调用 webhook 记录反馈并用于降低被多次标记的 prompt 权重将率 95% 的suggestion加入 snippet 库对提供的改进点自动生成 Jira ticket 给 LLM 团队。这个闭环运行三个月后review 的率从 63% 提升到 89%证明——最好的 LLM永远是那个听得懂人类反馈的 LLM。我在实际推动这个项目时最大的体会是技术从来不是难点难的是让团队相信“AI 不是来抢饭碗的而是来帮我们把重复劳动卸下来的”。当一位 Senior Dev 第一次看到 review 报告里精准指出他忘了加try/except的那行代码并附上三行修复建议时他主动申请成为项目 Champion。那一刻我知道open-code-review 不再是一个 CLI 工具而是一种新的工程文化——开放、透明、可验证、可进化。