)
1. 為什么我要自己搭一套 open-code-review代碼審查這件事做過團隊協(xié)作的人都有體會。理想狀態(tài)下每次提交都有人認真看、認真提意見把問題攔在合并之前。現(xiàn)實往往是另一回事提交堆成山審查者掃兩眼就點了通過等到線上出問題再回頭翻記錄發(fā)現(xiàn)那個空指針早就寫在 diff 里了只是沒人注意到。我所在的團隊規(guī)模不大七八個人后端前端加起來每天十幾到二十次提交。之前試過幾種方案純?nèi)斯彶榭孔杂X結果就是誰忙誰跳過用平臺自帶的審查功能規(guī)則是死的只能查格式和明顯的靜態(tài)問題業(yè)務邏輯層面的隱患基本查不出來也試過讓每個人提交前自己過一遍清單但人總有惰性清單寫著寫著就變成擺設。后來我把目光轉向了 LLM Agent 這條路。核心想法很直接既然大模型能讀懂代碼、能理解上下文那讓它扮演一個永不疲倦的初級審查者角色先把明顯的問題篩一遍人只需要看它篩出來的重點效率應該能提上來。這個思路落地下來就是我現(xiàn)在用的這套 open-code-review 流程——一個跑在本地、掛在 Git 鉤子上、用 CLI 調(diào) LLM Agent 做代碼審查的小工具鏈。它解決的問題很具體在git commit或git push之前自動把本次改動的 diff 喂給 LLM讓它按預設的審查維度輸出意見有嚴重問題就攔住提交沒大問題就放行并附上建議。適合誰來參考我覺得是三類人一是小團隊里負責工程效率的那個人二是自己寫項目想有個第二雙眼睛的獨立開發(fā)者三是對 CLI、Git 鉤子、LLM Agent 這套組合感興趣、想動手試試的技術愛好者。不需要你是 AI 專家但得會用命令行、懂基本的 Git 操作。下面我把整套東西拆開講從設計思路到具體實現(xiàn)再到踩過的坑盡量說透。2. 整體設計與技術選型思路2.1 為什么是 CLI Git 鉤子 LLM Agent 這個組合先說選型邏輯。做代碼審查工具擺在面前的路其實有幾條做成 IDE 插件、做成 CI 流水線的一環(huán)、做成獨立的 CLI 工具。我最后選了 CLI 加 Git 鉤子理由有三條。第一侵入性最低。IDE 插件要適配不同編輯器團隊里有人用 VS Code有人用 JetBrains 系列還有人習慣 Vim統(tǒng)一不了。CI 流水線的問題在于反饋太晚代碼都推上去了才告訴你哪里有問題改起來要走一遍完整流程。Git 鉤子掛在本地提交那一刻就觸發(fā)反饋最快而且不依賴任何平臺配置。第二CLI 天然適合做管道。Git 本身提供了git diff、git diff --cached這類命令輸出是結構化的文本直接就能喂給下一個程序。LLM Agent 的調(diào)用方式無論是走 API 還是走本地命令行工具本質上也是輸入文本、輸出文本。CLI 把這兩端串起來中間不需要任何圖形界面腳本化程度高改起來也方便。第三LLM Agent 負責理解規(guī)則引擎負責攔截。純規(guī)則的工具查不出業(yè)務邏輯問題純 LLM 又容易幻覺把沒問題的代碼說成有問題。我的做法是讓 LLM 輸出結構化的審查結果再用一層簡單的規(guī)則判斷嚴重程度決定是攔截還是放行。這樣既利用了模型的語義理解能力又保留了確定性的控制權。提示這里說的 LLM Agent指的是能接收文本輸入、按指令返回文本輸出的模型調(diào)用方式。它和傳統(tǒng)意義上的AI 模型區(qū)別在于Agent 通常帶有一層任務編排邏輯比如先讀 diff、再按維度分析、最后格式化輸出而不是單純的一次問答。2.2 審查維度怎么定從什么都查到只查值得查的一開始我想讓模型什么都查命名規(guī)范、注釋完整性、性能、安全、可讀性、測試覆蓋……結果輸出一大堆噪音比信號還多。后來我砍到四個核心維度每個維度給明確的判斷標準。審查維度關注點攔截級別邏輯正確性空指針、邊界條件、死循環(huán)、資源未釋放嚴重攔截安全隱患硬編碼密鑰、SQL 拼接、未校驗輸入嚴重攔截可維護性函數(shù)過長、重復代碼、魔法數(shù)字建議不攔截命名與注釋變量名含義不清、關鍵邏輯缺注釋建議不攔截這個分級很關鍵。如果所有問題都攔截開發(fā)者會被煩死最后直接--no-verify跳過鉤子工具就廢了。只有真正會導致 bug 或安全問題的才攔截其余作為建議輸出讓人自己判斷。2.3 模型調(diào)用的兩種路徑API 直連與本地 CLI調(diào)用 LLM 有兩條路。一條是直接調(diào) API用 HTTP 請求把 diff 發(fā)過去拿回結果。另一條是走本地已經(jīng)裝好的 CLI 工具比如一些命令行形式的模型客戶端通過子進程調(diào)用。我兩條都試過。API 直連的優(yōu)點是可控超時、重試、并發(fā)都能自己管缺點是得管密鑰團隊里每個人都要配還得考慮費用。本地 CLI 的優(yōu)點是復用已有的登錄態(tài)不用額外配密鑰缺點是不同人裝的版本不一樣輸出格式可能有差異而且有些 CLI 工具在 Windows 終端下的行為不太一致。最后我選的是以 API 直連為主、本地 CLI 為備選的方案。主流程走 API配置集中管理如果檢測到本地有可用的 CLI 工具也允許切換過去方便在沒有網(wǎng)絡或想省費用的場景下用。# 檢查本地是否有可用的模型 CLI 工具 which codex 2/dev/null echo codex cli available which claude 2/dev/null echo claude cli available這段檢測邏輯放在腳本開頭根據(jù)檢測結果決定走哪條路徑。實測下來這種雙通道設計讓工具在不同環(huán)境下都能跑起來適應性好很多。3. 核心細節(jié)解析與實操要點3.1 Git 鉤子的選擇pre-commit 還是 pre-pushGit 鉤子有好幾種和代碼審查相關的主要是pre-commit和pre-push。這兩個的區(qū)別直接決定了工具的使用體驗。pre-commit在每次提交時觸發(fā)粒度細反饋快。但問題是提交往往很頻繁有時候只是改個錯別字也要跑一遍審查浪費時間。而且提交階段拿到的 diff 是暫存區(qū)的內(nèi)容如果開發(fā)者習慣git add .一把梭diff 會很大審查質量反而下降。pre-push在推送前觸發(fā)粒度粗但更接近一次完整的改動。推送前通常已經(jīng)完成了若干次提交diff 是這一批提交的合集模型能看到的上下文更完整審查意見也更有價值。我最后選的是pre-push 為主、pre-commit 可選。默認掛在 pre-push 上如果某個項目想更嚴格可以額外掛 pre-commit。這樣既保證了審查質量又不會讓日常提交變得卡頓。# 安裝 pre-push 鉤子 cat .git/hooks/pre-push EOF #!/bin/bash # open-code-review pre-push hook exec $(dirname $0)/../../scripts/review.sh --mode pre-push EOF chmod x .git/hooks/pre-push注意鉤子腳本的路徑要用相對路徑或者環(huán)境變量不要寫死絕對路徑。團隊里每個人的項目目錄不一樣寫死了別人就用不了。3.2 diff 的提取與預處理別把整個倉庫喂給模型這是最容易踩坑的地方。一開始我圖省事直接git diff HEAD~1把最近一次提交的全部改動丟給模型結果 token 消耗巨大而且模型經(jīng)常跑偏去評論一些和本次改動無關的舊代碼。正確的做法是只提取本次推送涉及的改動。pre-push 鉤子會通過標準輸入傳入將要推送的引用信息可以從中解析出本地分支和遠程分支再用git diff拿到精確的差異。# 從 pre-push 的標準輸入解析推送范圍 while read local_ref local_sha remote_ref remote_sha; do if [ $remote_sha 0000000000000000000000000000000000000000 ]; then # 新分支對比默認分支 rangeorigin/main..$local_sha else range$remote_sha..$local_sha fi git diff $range -- . :(exclude)*.lock :(exclude)*.min.js done這里有兩個細節(jié)值得說。一是排除鎖文件和壓縮文件這些文件 diff 又長又沒意義喂給模型純屬浪費。二是限制單次 diff 的大小如果改動超過一定行數(shù)我設的是 800 行就只取核心文件或者提示開發(fā)者拆分提交。模型對超長輸入的處理能力有限塞太多反而效果差。3.3 提示詞的設計讓模型輸出結構化結果提示詞寫得好不好直接決定審查質量。我試過很多版本最后穩(wěn)定下來的結構是這樣的先給角色設定再給審查維度再給輸出格式最后給 diff。你是一名資深代碼審查者。請審查以下代碼改動按四個維度分析 1. 邏輯正確性是否有空指針、邊界條件錯誤、資源泄漏 2. 安全隱患是否有硬編碼密鑰、注入風險、未校驗輸入 3. 可維護性是否有過長函數(shù)、重復代碼、魔法數(shù)字 4. 命名與注釋命名是否清晰、關鍵邏輯是否有注釋 輸出格式要求 - 每個問題一行格式為[級別] 文件:行號 - 問題描述 - 級別只能是 SEVERE 或 SUGGEST - 如果沒有問題輸出 NO_ISSUE - 不要輸出任何額外解釋 代碼改動如下這個提示詞的關鍵在于輸出格式的強約束。模型如果自由發(fā)揮輸出會五花八門腳本沒法解析。限定成[級別] 文件:行號 - 描述這種格式后用簡單的文本處理就能提取出嚴重問題決定是否攔截。提示提示詞里的不要輸出任何額外解釋這句很重要。模型有很強的解釋欲不加這句約束它會在結果前后加一堆總的來說這段代碼……之類的廢話解析起來很麻煩。3.4 結果解析與攔截邏輯模型返回結果后腳本要做兩件事解析出嚴重問題、決定是否攔截。# 解析模型輸出提取 SEVERE 級別的問題 severe_count$(echo $review_result | grep -c ^\[SEVERE\]) if [ $severe_count -gt 0 ]; then echo 發(fā)現(xiàn) $severe_count 個嚴重問題推送已攔截 echo $review_result | grep ^\[SEVERE\] exit 1 else echo 審查通過建議如下 echo $review_result | grep ^\[SUGGEST\] exit 0 fiexit 1會讓 Git 中止推送exit 0則放行。這個邏輯簡單但有效。實測下來攔截率控制在 5% 到 10% 之間比較合適太高說明提示詞太嚴太低說明沒起到作用。4. 完整實操流程與關鍵環(huán)節(jié)實現(xiàn)4.1 環(huán)境準備Git、CLI 工具與模型接入先把基礎環(huán)境搭好。Git 的安裝不用多說Windows 上裝 Git for WindowsMac 上用 HomebrewLinux 用包管理器。裝完之后確認git --version能正常輸出。然后是模型接入。如果走 API 路徑需要準備一個 API 密鑰放在環(huán)境變量里不要寫進代碼。# 把密鑰放進 shell 配置文件不要提交到倉庫 echo export REVIEW_API_KEYyour-key-here ~/.bashrc echo export REVIEW_API_ENDPOINThttps://your-endpoint/v1/chat/completions ~/.bashrc source ~/.bashrc如果走本地 CLI 路徑確認對應的命令行工具已經(jīng)裝好并且能正常調(diào)用。有些 CLI 工具在 Windows 終端下會有編碼問題建議在 Git Bash 或 WSL 里跑兼容性更好。# 驗證本地 CLI 工具是否可用 codex --version 2/dev/null || echo codex cli not found4.2 腳本主體從 diff 提取到結果輸出整個腳本我拆成了幾個函數(shù)主流程清晰一些。核心邏輯是解析推送范圍、提取 diff、調(diào)用模型、解析結果、決定攔截。#!/bin/bash set -euo pipefail REVIEW_MODE${1:-pre-push} MAX_DIFF_LINES800 extract_diff() { local range$1 git diff $range -- . \ :(exclude)*.lock \ :(exclude)*.min.js \ :(exclude)package-lock.json \ | head -n $MAX_DIFF_LINES } call_model() { local diff_content$1 local prompt prompt$(cat PROMPT 你是一名資深代碼審查者。請審查以下代碼改動按四個維度分析 1. 邏輯正確性 2. 安全隱患 3. 可維護性 4. 命名與注釋 輸出格式[級別] 文件:行號 - 問題描述 級別只能是 SEVERE 或 SUGGEST無問題輸出 NO_ISSUE PROMPT ) curl -s -X POST $REVIEW_API_ENDPOINT \ -H Authorization: Bearer $REVIEW_API_KEY \ -H Content-Type: application/json \ -d $(jq -n --arg p $prompt --arg d $diff_content \ {model:review-model, messages:[{role:user,content:($p \n\n $d)}]}) \ | jq -r .choices[0].message.content } main() { local diff_content diff_content$(extract_diff $REVIEW_RANGE) if [ -z $diff_content ]; then echo 無改動跳過審查 exit 0 fi local result result$(call_model $diff_content) # 解析與攔截邏輯見 3.4 節(jié) parse_and_gate $result } main這里用到了jq來構造和解析 JSON它是處理 JSON 的利器建議提前裝好。set -euo pipefail這行讓腳本在出錯時立即退出避免錯誤被吞掉。4.3 參數(shù)計算diff 行數(shù)上限怎么定MAX_DIFF_LINES這個參數(shù)不是拍腦袋定的。模型的上下文窗口有限diff 太長會被截斷導致審查不完整。我按經(jīng)驗算了一下主流模型的上下文窗口在 8K 到 128K token 之間1 行代碼平均 10 到 15 個 token加上提示詞本身占用的部分留出安全余量后diff 控制在 800 行左右比較穩(wěn)妥。如果改動確實很大有兩個處理方式一是按文件拆分逐個文件審查二是只審查核心文件跳過測試文件和文檔。我在腳本里加了個判斷如果 diff 超過上限就按文件分組每組單獨調(diào)用一次模型。# 按文件拆分大 diff if [ $(echo $diff_content | wc -l) -gt $MAX_DIFF_LINES ]; then git diff $range --name-only | while read -r file; do file_diff$(git diff $range -- $file) [ -n $file_diff ] call_model $file_diff done fi4.4 與 Git 工作流的整合工具做好之后要讓它自然地融入日常流程。我的做法是在項目根目錄放一個scripts/文件夾把審查腳本放進去然后在.git/hooks/里放一個薄薄的鉤子腳本調(diào)用它。這樣腳本可以跟著倉庫走團隊成員拉下來就能用。# 在項目里初始化鉤子 mkdir -p scripts cp review.sh scripts/ cat .git/hooks/pre-push EOF #!/bin/bash exec $(git rev-parse --show-toplevel)/scripts/review.sh --mode pre-push EOF chmod x .git/hooks/pre-pushgit rev-parse --show-toplevel能拿到倉庫根目錄這樣不管在哪個子目錄下操作路徑都是對的。這個細節(jié)很多人會忽略導致鉤子在子目錄里跑不起來。注意.git/hooks/目錄不會被提交到倉庫所以每個成員都要手動裝一次鉤子。可以在 README 里寫清楚安裝步驟或者寫個install-hooks.sh腳本一鍵安裝。5. 常見問題與排查技巧實錄5.1 模型調(diào)用失敗從超時到密鑰錯誤模型調(diào)用是最容易出問題的環(huán)節(jié)。我把遇到過的錯誤整理成了一張表方便對照排查?,F(xiàn)象可能原因排查方法請求超時網(wǎng)絡慢或 diff 太長縮短 diff增加超時時間401 未授權密鑰錯誤或過期檢查環(huán)境變量重新生成密鑰429 限流調(diào)用頻率過高加退避重試降低并發(fā)返回空結果提示詞或 diff 格式問題打印原始響應檢查 JSON 結構輸出格式不對模型沒遵守格式約束強化提示詞加格式示例超時這個問題我遇到最多。默認的 curl 超時是無限的模型如果卡住整個推送就掛在那里。后來我加了--max-time 60超過 60 秒就放棄提示開發(fā)者手動審查。curl -s --max-time 60 -X POST $REVIEW_API_ENDPOINT ...5.2 誤報與漏報怎么調(diào)提示詞誤報是指模型把沒問題的代碼說成有問題漏報是指真正的問題沒被發(fā)現(xiàn)。這兩個是此消彼長的關系調(diào)提示詞就是在找平衡點。誤報多的時候我會在提示詞里加一句只報告確定的問題不確定的不要報告。漏報多的時候我會在提示詞里加具體的檢查清單比如特別注意數(shù)組越界、空指針解引用、未關閉的文件句柄。實測下來給具體的檢查項比給抽象的要求效果好得多。模型對檢查是否有空指針這種具體指令的執(zhí)行率明顯高于檢查代碼質量這種模糊指令。5.3 Windows 環(huán)境下的兼容性問題Windows 下跑這套東西坑比 Linux 和 Mac 多。最常見的是路徑分隔符和換行符的問題。Git Bash 里路徑用/但有些工具期望\混用會出錯。換行符方面Windows 用 CRLFLinux 用 LF腳本從 Windows 傳到 Linux 上跑經(jīng)常因為\r導致命令找不到。解決辦法是在倉庫根目錄加一個.gitattributes文件強制腳本文件用 LF 換行。*.sh text eollf另外Windows 終端下調(diào)用某些 CLI 工具時輸出編碼可能是 GBK 而不是 UTF-8導致中文亂碼。可以在腳本開頭設置export LANGen_US.UTF-8或者chcp 65001來統(tǒng)一編碼。5.4 鉤子被繞過怎么讓團隊真正用起來技術上做好了不代表團隊會用。我見過太多人遇到鉤子攔截第一反應是git push --no-verify跳過。要讓工具真正發(fā)揮作用得從兩方面入手。一是降低誤報率。誤報是繞過鉤子的頭號原因。如果十次攔截里有五次是誤報沒人會認真對待。我花了大概兩周時間調(diào)提示詞把誤報率壓到 20% 以下團隊的接受度明顯提高。二是讓審查結果有價值。如果模型輸出的都是變量名不夠清晰這種無關痛癢的建議大家看兩次就不看了。我在提示詞里強調(diào)只報告會導致 bug 或安全問題的情況讓每條意見都值得一看。提示可以在團隊里約定繞過鉤子需要在提交信息里說明原因。這不是強制但是一種軟約束能減少隨意繞過的行為。5.5 費用控制別讓審查變成燒錢機器走 API 路徑的話費用是個現(xiàn)實問題。每次推送都調(diào)用模型一天下來調(diào)用次數(shù)不少。我的控制策略有三條。第一只在有實際改動時調(diào)用。如果 diff 為空直接跳過不浪費調(diào)用。第二限制 diff 長度。前面說的 800 行上限既是為了審查質量也是為了控制 token 消耗。第三緩存相同 diff 的結果。如果同一個 diff 被審查過直接讀緩存不再調(diào)用。用 diff 的哈希值做 key存在本地文件里。cache_key$(echo $diff_content | md5sum | cut -d -f1) cache_file/tmp/review-cache/$cache_key if [ -f $cache_file ]; then result$(cat $cache_file) else result$(call_model $diff_content) echo $result $cache_file fi這三條加起來費用能降下來一大半。實測下來一個七八人的團隊每月的審查費用控制在很低的水平。6. 我在這套流程里踩過的坑和攢下的經(jīng)驗6.1 關于提示詞迭代的一點心得提示詞不是一次寫好的是迭代出來的。我最初的版本只有一句話審查這段代碼輸出質量慘不忍睹。后來每次遇到誤報或漏報就針對性地加一條約束或檢查項慢慢打磨出現(xiàn)在這個版本。我的建議是建一個提示詞的版本記錄每次改動都記下來改了什么、為什么改、效果如何。這樣過一段時間回頭看能清楚知道哪些約束是有效的哪些是多余的。我現(xiàn)在的提示詞里有幾條約束是早期加的后來發(fā)現(xiàn)根本沒用刪掉之后輸出反而更干凈。6.2 關于模型選擇的實際體驗不同模型在代碼審查上的表現(xiàn)差異挺大。有的模型對語法細節(jié)敏感能揪出很隱蔽的空指針有的模型擅長理解業(yè)務邏輯能發(fā)現(xiàn)這個判斷條件寫反了這類問題還有的模型輸出特別啰嗦每條意見都要解釋半天。我的做法是按維度選模型。邏輯正確性和安全隱患這兩個維度用對代碼理解深的模型可維護性和命名注釋這兩個維度用輸出簡潔的模型。如果只用一個模型就選綜合表現(xiàn)最均衡的那個。提示模型的能力在快速變化今天表現(xiàn)好的模型過幾個月可能就被超越了。建議每隔一段時間重新評估一次不要一套配置用到底。6.3 關于團隊協(xié)作的觀察工具上線之后我觀察到一個有意思的現(xiàn)象審查意見的質量比數(shù)量重要得多。一開始模型輸出一大堆建議大家看都不看。后來我把建議精簡到每次不超過五條而且每條都標注了具體文件和行號大家的閱讀率明顯提高。另一個觀察是開發(fā)者對被攔截的容忍度取決于攔截的準確性。如果攔截的都是真問題大家會認可這個工具如果攔截里有誤報信任度會迅速下降。所以寧可漏報不要誤報這是我在調(diào)提示詞時一直堅持的原則。6.4 后續(xù)可以擴展的方向這套流程目前跑得挺穩(wěn)但還有幾個方向可以繼續(xù)做。一是把審查結果沉淀下來存到數(shù)據(jù)庫里定期分析哪些類型的問題出現(xiàn)最多反過來指導編碼規(guī)范。二是支持自定義審查規(guī)則讓每個項目可以配置自己的檢查項比如某個項目特別在意性能就加一條性能檢查。三是和 CI 流水線打通本地審查通過之后CI 上再跑一遍雙重保險。這些擴展我還在陸續(xù)做有進展了再單獨寫一篇分享。代碼審查這件事工具能幫上忙但最終還是要靠人。工具的價值在于把人的注意力從找明顯問題轉移到判斷復雜邏輯上這才是它真正省時間的地方。