ocr review
→ bootstrap 解析 LLM 端點(config → env → rc files)、載入模板/工具/規則
→ diff provider git diff / ls-files / show → []model.Diff
→ filter & rules 五重門過濾(preview.go)+ 每檔選規則
→ subtask dispatch 每檔並行(concurrency=N):Plan 階段(可選)→ Main 迴圈 → Comments
→ output writer 同步行解析 + 評審過濾;渲染 text 或 JSON
編排邏輯在 internal/agent/,分布在 agent.go(主迴圈與分發)、compression.go(記憶壓縮)、preview.go(檔案過濾)、util.go(輔助)。兩個入口點:Agent.Run(管線頂部)與 Agent.dispatchSubtasks(per-file 扇出)。
internal/diff/git.go 定義 Provider 結構,未匯出欄位 mode 選三種模式之一:
| 模式 | 觸發 | 返回 |
|---|---|---|
Workspace | 無參數 | staged + unstaged + untracked 變更 |
Commit | --commit | git show <sha> 引入的變更 |
Range | --from --to | merge-base(a,b)..b |
每個 diff 帶 old/new path、hunk、插入/刪除計數、二進位旗標、重新命名偵測。untracked 檔從磁碟讀取當整檔新增處理。
whyExcluded 依序檢查:binary → user_exclude → user_include(立即保留)→ unsupported_ext → default_path。雜訊目錄(vendor/、node_modules/…)在更早的 diff-provider 層過濾。
每個通過過濾的檔案啟動一個子 agent,在自己的 goroutine 中跑,受 --concurrency(預設 8)約束。
threshold := template.PlanModeLineThreshold // 50
changeLines := d.Insertions + d.Deletions
if changeLines < threshold { skip plan }
小 diff 跳過 plan。較大 diff 做一次 PLAN_TASK 呼叫(不給工具),產出清單作為 {{plan_guidance}}。
loop up to MAX_TOOL_REQUEST_TIMES (default 30):
response = llm.complete(messages, tools)
if response.toolCalls is empty: nudge "You did not successfully call any tools…"
for each call: execute → collect result
if any call was task_done: break
addNextMessage(...) # 可能觸發壓縮
五個退出條件:呼叫 task_done、30 輪耗盡、連續 3 輪無有效結果、context 取消、壓縮無法壓回閾值以下。
| 閾值(MAX_TOKENS=58888) | 動作 |
|---|---|
| 60% | 非同步背景壓縮,迴圈繼續 |
| 80% | 同步壓縮(下一個請求前) |
三分區:frozen(前 2 則)、compress(摘要成一則)、active(最近可裝下的完整輪次)。
existing_code 滑動視窗匹配 → 精確行號。RE_LOCATION_TASK。REVIEW_FILTER_TASK 移除可證明為錯的評論。diff.ResolveLineNumbers。internal/config/template/task_template.json 含五個 prompt:PLAN_TASK、MAIN_TASK、MEMORY_COMPRESSION_TASK、REVIEW_FILTER_TASK、RE_LOCATION_TASK。佔位符如 {{system_rule}}、{{diff}}、{{change_files}}、{{requirement_background}}。
~/.opencodereview/sessions/<encoded-repo-path>/<session-id>.jsonl
每行一個事件(prompt、LLM 回應、工具呼叫、結果、評論)。Web UI(ocr viewer)直接讀這些檔案——沒有資料庫,只有 append-only 日誌。
這些選擇讓運行按檔案確定性、成本可預測。
| 關注點 | 檔案 |
|---|---|
| 頂層命令分派 | cmd/opencodereview/main.go |
| review 參數解析 | cmd/opencodereview/flags.go |
| agent 編排與壓縮 | internal/agent/ |
| 檔案過濾 / 預覽 | internal/agent/preview.go |
| diff 載入 | internal/diff/git.go |
| 規則解析鏈 | internal/config/rules/system_rules.go |
| 工具註冊表 | internal/tool/ |
| LLM 端點解析器 | internal/llm/resolver.go |
| 會話 JSONL 寫入 | internal/session/persist.go |
| Web 檢視器 | internal/viewer/server.go |
把 ocr review 想像成一條工廠管線:bootstrap(開機校準)→ diff provider(原料進貨)→ filter & rules(分揀品質管控)→ subtask dispatch(並行加工車間)→ output writer(成品包裝)。每個階段只做一件事,做完了交給下一站。這就是為什麼 OCR 的行為高度可預測——確定性管線 + Agent 動態決策。
五重門不是隨意堆的——它們有嚴格的因果順序:binary(二進位沒意義)→ user_exclude(你說不要)→ user_include(你特別要求,立即保留)→ unsupported_ext(擴展名不在白名單)→ default_path(測試檔排除)。include 放在第三門是刻意的:它能繞過後面兩門,讓你強制保留本來會被排除的檔案。
每個檔案一個 goroutine,但受 --concurrency(預設 8)限制。這不是「越多越好」——LLM API 有 rate limit,太多並行反而觸發 429。Plan 階段只在 diff ≥ 50 行時跑,是因為小 diff 的 plan 收益太低(多一次 LLM 呼叫但產不出有價值的指引)。