docs(ai-review): 更新對話收斂三分類與防守方誤報裁決說明
This commit is contained in:
@@ -11,10 +11,10 @@
|
|||||||
- 若有提供 `GITEA_COMMENT_TOKEN`,額外用它驗證可用(呼叫 `GET /api/v1/user`),確保後續發 comment 不會因 token 失效而中斷
|
- 若有提供 `GITEA_COMMENT_TOKEN`,額外用它驗證可用(呼叫 `GET /api/v1/user`),確保後續發 comment 不會因 token 失效而中斷
|
||||||
- git push 認證可用:用與第 8 點 commit/push 完全相同的 askpass + remote URL 機制跑一次唯讀的 `git ls-remote`,提前抓出 askpass 無法執行或 HTTP 認證失敗(例如 `could not read Username`)的問題。此路徑與上面的 REST API 不同,API token 有效不代表 git push 一定能用,故獨立驗證
|
- git push 認證可用:用與第 8 點 commit/push 完全相同的 askpass + remote URL 機制跑一次唯讀的 `git ls-remote`,提前抓出 askpass 無法執行或 HTTP 認證失敗(例如 `could not read Username`)的問題。此路徑與上面的 REST API 不同,API token 有效不代表 git push 一定能用,故獨立驗證
|
||||||
- 已選定一個 LLM provider,且其 API Key 至少有一把通過驗證:實際送出一個最小請求確認認證可用;逗號分隔的多把 Key 只要一把成功即可,逐把記錄成敗;Ollama 無 Key,改為檢查 `OLLAMA_BASE_URL` 可連線
|
- 已選定一個 LLM provider,且其 API Key 至少有一把通過驗證:實際送出一個最小請求確認認證可用;逗號分隔的多把 Key 只要一把成功即可,逐把記錄成敗;Ollama 無 Key,改為檢查 `OLLAMA_BASE_URL` 可連線
|
||||||
2.5. PR 對話收斂(前置驗證通過、且非 AI 助理自動提交後):讀取 PR 上所有行內 review comment,依「檔案路徑+行號」收斂成對話,跳過已 resolve 的對話;取每個對話所在檔案在 PR head 的最新內容,請 AI 判斷該對話指出的問題是否已解決,再依下列三種情況處置:(a) 程式碼已修復 → 呼叫 Gitea 官方 API(`POST /repos/{repo}/pulls/comments/{id}/resolve`)解決對話,並在 Step4 從問題清單移除對應問題;(b) 未修復但該問題已存在於舊問題(`findings.json`,以「檔案路徑+建議內容」比對)→ 一樣解決對話(已被追蹤,不重複加回);(c) 未修復且不在舊問題 → 不解決對話,於 Step4 將該問題加入舊問題清單(涵蓋「問題仍在但 `findings.json` 已遺漏」的情況)。納入判斷的對話包含所有人的留言;任一外部呼叫失敗都降級為「視為未解決」,不中斷整體流程
|
2.5. PR 對話收斂(前置驗證通過、且非 AI 助理自動提交後):讀取 PR 上所有行內 review comment,依「檔案路徑+行號」收斂成對話,跳過已 resolve 的對話;對**所有未解決的對話一律**呼叫 Gitea 官方 API(`POST /repos/{repo}/pulls/comments/{id}/resolve`)關閉(`findings.json` 為唯一待辦來源,下次 review 會依其重新貼 comment)。接著取每個對話所在檔案在 PR head 的最新內容,請 AI 將每個對話判為三類,決定其在問題清單的去向:(a) `resolved`(程式碼已修復)→ Step4 從問題清單移除;(b) `false_positive`(誤報)→ 寫入 `exclusions.json` 並從問題清單移除;(c) `open`(仍成立)→ Step4 加入問題清單。納入判斷的對話包含所有人的留言;任一外部呼叫失敗都降級為「視為 open」,不中斷整體流程
|
||||||
3. 檢查是否為 AI 助理自動提交;若不是,選定 LLM provider/model、載入角色、取得 PR diff,將服務名稱、模型名稱與角色資訊 Comment 到 Pull Request,並讓每個角色個別分析 Git Diff 產生新問題表格(問題等級、角色名稱、問題位置或行數、修改建議)
|
3. 檢查是否為 AI 助理自動提交;若不是,選定 LLM provider/model、載入角色、取得 PR diff,將服務名稱、模型名稱與角色資訊 Comment 到 Pull Request,並讓每個角色個別分析 Git Diff 產生新問題表格(問題等級、角色名稱、問題位置或行數、修改建議)
|
||||||
4. 讀取來源分支中的所有未解決舊問題(問題檔案 `.gitea/ai-review/findings.json`),先套用步驟 2.5 的對話收斂結果(移除已解決對話對應的問題、加回未解決但已遺漏的問題;以「檔案路徑+建議內容」比對,避免行號漂移誤判),再加上新問題後,去除重複產生本次 PR 的問題表格(PR問題表格)覆蓋問題檔案
|
4. 讀取來源分支中的所有未解決舊問題(問題檔案 `.gitea/ai-review/findings.json`),先套用步驟 2.5 的對話收斂結果(移除已修復與誤報對應的問題、加回仍成立但已遺漏的問題;以「檔案路徑+建議內容」比對,避免行號漂移誤判),再加上新問題後,去除重複產生本次 PR 的問題表格(PR問題表格)覆蓋問題檔案
|
||||||
5. 讀取來源分支中的排除問題檔案(`.gitea/ai-review/exclusions.json`),用來過濾 PR 問題表格中不需要處理的問題
|
5. 讀取來源分支中的排除問題檔案(`.gitea/ai-review/exclusions.json`),用來過濾 PR 問題表格中不需要處理的問題;接著由「防守方」角色(Paladin)對剩餘問題逐條判斷是否為誤報——每條問題各派一個 sub-agent,多條問題時平行處理,判為誤報者剔除、成立者保留(任一裁決失敗則保守保留該問題)
|
||||||
6. 將 PR 問題表格寫入 `.gitea/ai-review/findings.json`,並發布一個 Gitea Review:Review 本文先以「嚴重/警告/建議/無法標示」四欄分列新問題與舊問題兩列的數量(無法標示=等級無法歸入前三類者),接著附上「AI 助理使用量」區塊(本次審查累計的 token 消耗,以及目前的帳號額度);之後只將「新問題」中可找出檔案與行數者依照嚴重等級排序後加入 Review Comments 內(舊問題只計入上方統計,不再重複標註檔案與行數),每個 Comment 包含嚴重等級/審查員/問題/建議,其中「問題」是審查員判斷該處有問題的原因,不是檔案路徑或行號
|
6. 將 PR 問題表格寫入 `.gitea/ai-review/findings.json`,並發布一個 Gitea Review:Review 本文先以「嚴重/警告/建議/無法標示」四欄分列新問題與舊問題兩列的數量(無法標示=等級無法歸入前三類者),接著附上「AI 助理使用量」區塊(本次審查累計的 token 消耗,以及目前的帳號額度);之後只將「新問題」中可找出檔案與行數者依照嚴重等級排序後加入 Review Comments 內(舊問題只計入上方統計,不再重複標註檔案與行數),每個 Comment 包含嚴重等級/審查員/問題/建議,其中「問題」是審查員判斷該處有問題的原因,不是檔案路徑或行號
|
||||||
7. 驗證來源分支中的 `findings.json` 與 `exclusions.json` 是否為合法 JSON array;格式錯誤時先嘗試透過 AI 修正內容,再重新驗證;修正後仍不合法才 exit 1;檔案不存在則建立並寫入 `[]`
|
7. 驗證來源分支中的 `findings.json` 與 `exclusions.json` 是否為合法 JSON array;格式錯誤時先嘗試透過 AI 修正內容,再重新驗證;修正後仍不合法才 exit 1;檔案不存在則建立並寫入 `[]`
|
||||||
8. Commit 問題檔案,只將 workspace 中實際存在的 `.gitea/ai-review/findings.json` 與 `.gitea/ai-review/exclusions.json` 覆蓋到記憶區;workspace 沒有的問題檔就略過。自動提交的 commit message 會帶上 `[ai-review-bot]`,供 workflow 判斷是否要跳過重跑
|
8. Commit 問題檔案,只將 workspace 中實際存在的 `.gitea/ai-review/findings.json` 與 `.gitea/ai-review/exclusions.json` 覆蓋到記憶區;workspace 沒有的問題檔就略過。自動提交的 commit message 會帶上 `[ai-review-bot]`,供 workflow 判斷是否要跳過重跑
|
||||||
@@ -34,16 +34,16 @@
|
|||||||
10. 執行時會額外記錄來源分支狀態、`findings.json` / `exclusions.json` 的檔案路徑、大小、mtime 與 raw/normalized 筆數,方便追查讀檔與分支內容不一致的問題
|
10. 執行時會額外記錄來源分支狀態、`findings.json` / `exclusions.json` 的檔案路徑、大小、mtime 與 raw/normalized 筆數,方便追查讀檔與分支內容不一致的問題
|
||||||
11. action 一啟動就先做「前置驗證」(流程第 2 點):集中檢查 Gitea REST API token、comment token、git push 認證與 LLM 的所有驗證相關設定是否可用,全部通過才往下跑。驗證邏輯獨立成 `app/preflight.js`(git push 驗證委派給 `app/git.js` 的 `verifyRemoteAccess`),由 `main.js` 在 Step1 之後、其餘步驟之前呼叫;任何一項失敗都印出是哪一項、原因為何後 `exit 1`,避免在分析到一半、發 comment 或最後 push 時才因 token / key / 認證無效而中斷
|
11. action 一啟動就先做「前置驗證」(流程第 2 點):集中檢查 Gitea REST API token、comment token、git push 認證與 LLM 的所有驗證相關設定是否可用,全部通過才往下跑。驗證邏輯獨立成 `app/preflight.js`(git push 驗證委派給 `app/git.js` 的 `verifyRemoteAccess`),由 `main.js` 在 Step1 之後、其餘步驟之前呼叫;任何一項失敗都印出是哪一項、原因為何後 `exit 1`,避免在分析到一半、發 comment 或最後 push 時才因 token / key / 認證無效而中斷
|
||||||
12. PR 對話收斂(流程第 2.5 點)邏輯獨立成 `app/resolve.js`,由 `main.js` 在前置驗證與自動提交檢查之後以 `Step2` 呼叫:
|
12. PR 對話收斂(流程第 2.5 點)邏輯獨立成 `app/resolve.js`,由 `main.js` 在前置驗證與自動提交檢查之後以 `Step2` 呼叫:
|
||||||
- 透過 `app/gitea.js` 的 `listAllReviewComments` 取得所有行內 review comment,`groupConversations` 以「path+line」收斂並偵測 `resolver`(已解決);`reconcileConversations` 取 PR head 最新檔案內容(`getFileContentAtRef`,contents API base64 解碼)取目標行附近視窗,交 `judgeConversationsResolved` 由 AI 批次判斷
|
- 透過 `app/gitea.js` 的 `listAllReviewComments` 取得所有行內 review comment,`groupConversations` 以「path+line」收斂並偵測 `resolver`(已解決);`reconcileConversations` 取 PR head 最新檔案內容(`getFileContentAtRef`,contents API base64 解碼)取目標行附近視窗,交 `judgeConversations` 由 AI 批次判為 `resolved` / `false_positive` / `open`
|
||||||
- `reconcileConversations` 依 deps.oldFindings(由 `main.js` 在 Step2 以 `loadOldFindings(WORKSPACE)` 載入的來源分支舊問題)對每個待判斷對話分三類處置:`resolved`(程式碼已修復)→ resolve 對話並記錄供移除;`duplicate`(未修復但簽章已存在於舊問題)→ resolve 對話、不重複加回;`carry`(未修復且不在舊問題)→ 不 resolve、加回舊問題
|
- `reconcileConversations` 對所有未解決對話一律呼叫 `resolvePullReviewComment`(`POST /pulls/comments/{id}/resolve`)關閉,再依 AI 判斷把對應 finding 分流:`resolved`→`resolvedFindings`(供移除)、`false_positive`→`excludedFindings`(供寫入 exclusions 並移除)、`open`→`carriedFindings`(加回舊問題)
|
||||||
- 已解決/重複對話以官方 API `resolvePullReviewComment`(`POST /pulls/comments/{id}/resolve`)解決
|
- 與既有 findings 流程的銜接:`main.js` 在 Step4 以 `dropResolvedFindings` 移除(已修復+誤報)、`addCarriedFindings` 加回仍成立者(以「檔案路徑+正規化建議內容」為簽章比對,對行號漂移與標點差異穩定);Step5 以 `findings.js` 的 `appendExclusions` 把誤報寫入 `exclusions.json`(workspace 與 cloned repo 各一份,供本次過濾與後續 commit)
|
||||||
- 與既有 findings 流程的銜接:`dropResolvedFindings` 移除已解決問題、`addCarriedFindings` 加回未解決且遺漏的問題,皆以「檔案路徑+正規化建議內容」為簽章比對,對行號漂移與標點差異穩定,避免重複
|
- 為降低 token 用量只送目標行附近視窗;任一外部呼叫失敗都降級為「視為 open」並繼續流程
|
||||||
- 為降低 token 用量只送目標行附近視窗;任一外部呼叫失敗都降級為「視為未解決」並繼續流程
|
|
||||||
13. AI 助理使用量統計獨立成 `app/usage.js`,於 `Step6` 發布 Review 前蒐集,同時寫入 action log 與 Review 本文,核心是呈現「剩餘可用百分比」:
|
13. AI 助理使用量統計獨立成 `app/usage.js`,於 `Step6` 發布 Review 前蒐集,同時寫入 action log 與 Review 本文,核心是呈現「剩餘可用百分比」:
|
||||||
- 本次 token 消耗:每次 LLM 呼叫都經由 `app/llm.js` 的 `chat` 集中以 `recordUsage` 累計;`extractUsage` 容錯解析各平台回應的 usage 欄位(OpenAI 相容 `usage`、OpenAI Responses `input/output_tokens`、Gemini `usageMetadata`、Ollama `eval_count`、OpenCode `tokens`)
|
- 本次 token 消耗:每次 LLM 呼叫都經由 `app/llm.js` 的 `chat` 集中以 `recordUsage` 累計;`extractUsage` 容錯解析各平台回應的 usage 欄位(OpenAI 相容 `usage`、OpenAI Responses `input/output_tokens`、Gemini `usageMetadata`、Ollama `eval_count`、OpenCode `tokens`)
|
||||||
- 剩餘可用百分比(`resolveRemainingPercent`)依優先序擇一:(1) 帳號額度有上限時用「剩餘 credits ÷ 上限」;(2) 否則用回應 header 的速率配額「當前視窗剩餘 ÷ 上限」。`recordRateLimit` 從回應 header 擷取 `x-ratelimit-*-tokens`(OpenAI 相容)或 `anthropic-ratelimit-tokens-*`(Claude),缺 token 維度時退用 requests 維度——此來源零額外憑證、零 CLI,直接取自既有呼叫的回應
|
- 剩餘可用百分比(`resolveRemainingPercent`)依優先序擇一:(1) 帳號額度有上限時用「剩餘 credits ÷ 上限」;(2) 否則用回應 header 的速率配額「當前視窗剩餘 ÷ 上限」。`recordRateLimit` 從回應 header 擷取 `x-ratelimit-*-tokens`(OpenAI 相容)或 `anthropic-ratelimit-tokens-*`(Claude),缺 token 維度時退用 requests 維度——此來源零額外憑證、零 CLI,直接取自既有呼叫的回應
|
||||||
- 帳號額度:`fetchAccountQuota` 依平台採不同策略——OpenRouter(`openai` slot 指向 openrouter.ai 時)以 `GET /auth/key` 取得 USD credits 已用/上限/剩餘;Ollama、OpenCode 為本地/自架服務回報「不適用」;OpenAI、Claude、Gemini、Amazon Q 的帳號額度需 org/admin 權限,API key 無法取得時誠實回報原因
|
- 帳號額度:`fetchAccountQuota` 依平台採不同策略——OpenRouter(`openai` slot 指向 openrouter.ai 時)以 `GET /auth/key` 取得 USD credits 已用/上限/剩餘;Ollama、OpenCode 為本地/自架服務回報「不適用」;OpenAI、Claude、Gemini、Amazon Q 的帳號額度需 org/admin 權限,API key 無法取得時誠實回報原因
|
||||||
- 兩種來源皆無法取得(例如帳號無上限且回應無速率 header)時,降級為「無法計算百分比」並附原因,不中斷流程;`formatUsageStats` 產生 Review 本文區塊,`formatUsageStatsLine` 產生單行 log 摘要
|
- 兩種來源皆無法取得(例如帳號無上限且回應無速率 header)時,降級為「無法計算百分比」並附原因,不中斷流程;`formatUsageStats` 產生 Review 本文區塊,`formatUsageStatsLine` 產生單行 log 摘要
|
||||||
|
14. 誤報判斷套用「防守方」角色:`app/findings.js` 的 `filterFalsePositivesWithAI` 以 `app/roles.js` 的 `loadRole('Paladin')` 載入防守方角色,並用 `buildVerdictPrompt(role, exclusionHint)` 組出帶其個性與裁決準則的 system prompt;對每一條 finding 各派一個防守方 sub-agent(`judgeFindingIsFalsePositive`)裁決 `confirmed`/`false_positive`,多條問題時以 `Promise.all` 平行處理;判為誤報者剔除、成立者保留,任一 sub-agent 失敗(含解析失敗)保守視為成立保留,不中斷流程。角色檔遺失時 `buildVerdictPrompt(null)` 退回通用裁判 prompt。
|
||||||
|
|
||||||
# 使用說明
|
# 使用說明
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user