Files
ai-code-review/.gitea/ai-review/findings.json
T
AI Review Bot 4b4b934cda
CI / 2. TEST (pull_request) Failing after 28s
CI / 3. RESULT (pull_request) Has been skipped
CI / 1. BUILD (pull_request) Successful in 1s
chore: update ai-review findings [ai-review-bot][failure]
2026-07-03 04:08:32 +00:00

291 lines
23 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"level": "critical",
"role": "Assassin",
"location": "src/resolve.js:253",
"problem": "這裡會把 PR 上所有未解決的 review comment ID 全部送去 resolve,而不是只處理 AI Review bot 自己建立的 thread。攻擊者只要開 PR 觸發這個 action,就可能讓 bot 關閉人類審查者留下的安全疑慮或阻擋性對話,繞過人工審查流程。",
"suggestion": "只 resolve 可證明由本 bot 建立且格式符合預期的 comment,例如檢查作者、固定 marker、review body 簽章或 botFinding 解析結果;人類留言與未知格式留言不得自動關閉。",
"is_new": false
},
{
"level": "critical",
"role": "Mage",
"location": "src/findings.js:471",
"problem": "當排除條目有 location 或 role 時,這裡直接把文字比對結果短路成 true。最小重現:exclusions.json 只有 `{ \"location\": \"app/a.js:10\", \"original_finding\": \"誤報 A\" }`,新的 finding 是 `app/a.js:99` 且 suggestion 完全不同,仍會因同檔案而被排除,導致真問題被靜默丟掉。",
"suggestion": "不要用 `exPath || ex.role ? true : textMatches` 跳過文字比對;應至少要求位置精確匹配到同一行,或在同檔/同角色時仍必須通過 `textMatches`,例如 `return locationMatches && roleMatches && textMatches`,並明確定義 suggestion 空白時才是萬用規則。",
"is_new": false
},
{
"level": "critical",
"role": "Mage",
"location": "src/findings.js:382",
"problem": "這個排除條件只要命中 `location` 或 `role`,就直接放行,不再檢查文字內容;最小重現:同一個 `app/a.js` 裡有一筆「誤報」排除後,`app/a.js` 的其他不同 finding 也會一起被濾掉。結果是單一排除項目可以吞掉整個檔案的有效問題。",
"suggestion": "不要在有 `location` 或 `role` 時跳過文字比對;至少要同時驗證檔案、角色與正規化後的問題文字都匹配才排除,或改成以更穩定的 finding 簽章精準比對。",
"is_new": true
},
{
"level": "critical",
"role": "Mage",
"location": "src/gitea.js:86",
"problem": "這裡只要 `commit` 或 `branch` 訊息包含 `[ai-review-bot]` 就回傳 true,但沒有區分 `[success]` 與 `[failure]`;最小重現:上一輪 bot commit 是 `[ai-review-bot][failure]`,而 `getCommitMessageBySha` 讀取失敗時,流程會被當成「可跳過」直接結束,原本應該讓 workflow 失敗的訊號被吃掉。",
"suggestion": "讓這個函式回傳結構化結果,例如 `success / failure / unknown`,或至少在偵測到 `[failure]` 時明確回傳失敗狀態,交由 `main()` 先處理失敗再決定是否跳過。",
"is_new": true
},
{
"level": "critical",
"role": "Maya",
"location": "src/main.js:44",
"problem": "這個 `main()` 是整個 action 的流程總管,但目前沒有任何整合測試或端到端測試去驗證 Step3~Step11 的分支切換與 `process.exit()` 行為。像是前置驗證失敗、偵測到 bot 自動提交、diff 為空、所有角色分析都失敗、JSON 驗證失敗、出現 critical finding、以及 commit/push 降級路徑,現在都只靠人工推演,實際接線後一旦流程順序或退出條件出錯,現有單元測試抓不到。",
"suggestion": "補一組 `main.test.js`,把各模組依賴都 mock 掉,分別覆蓋 `runPreflight=false`、`shouldSkipBotCommit=true`、`getPRDiff=''`、分析全失敗、JSON 驗證拋錯、filtered 含 critical、push 失敗但流程不中斷等分支,並斷言對應的 exit code、呼叫順序與關鍵 log。",
"is_new": true
},
{
"level": "warning",
"role": "Assassin",
"location": "src/findings.js:14",
"problem": "這裡把未信任的 Git diff 直接送進 LLM。攻擊者可以在新增程式碼或註解中塞入提示詞注入內容,例如要求模型忽略安全問題、回傳空陣列或偽造低風險 findings,藉此讓自動安全審查失明。",
"suggestion": "在分析 prompt 中明確標示 diff 是不可信資料,要求模型忽略 diff 內任何指令;同時加入結構化封裝、輸出 schema 驗證與必要的規則式安全檢查,避免完全依賴可被 prompt injection 操控的 LLM 判斷。",
"is_new": false
},
{
"level": "warning",
"role": "Leo",
"location": "src/main.js:36",
"problem": "`main()` 把前置驗證、bot commit 判斷、對話收斂、角色分析、合併去重、排除、發布、JSON 驗證、commit/push 與 gate 全部塞在同一個 190 行左右的函式裡,且中間散落多個 `process.exit()`。六個月後要改其中任一步驟時,很難隔離副作用,也不容易針對單一階段寫單元測試。",
"suggestion": "將每個 Step 拆成可注入相依、回傳明確結果的函式,例如 `runAnalysisStep()`、`runFilteringStep()`、`runPublishStep()`;最外層再統一把結果轉成 exit code,讓流程控制與業務邏輯分離。",
"is_new": false
},
{
"level": "warning",
"role": "Leo",
"location": "src/findings.js:381",
"problem": "`loadExclusions()` 同時負責讀檔、解析多種格式、正規化、去重、記錄 repo 狀態、改寫原檔、鏡像寫入與建立 AI prompt 摘要。這個函式的職責過多,之後只要調整 exclusions 格式或同步策略,就很容易牽動不相關行為。",
"suggestion": "拆成 `readExclusionsFile()`、`normalizeExclusionsData()`、`canonicalizeExclusionsFile()`、`logExclusionMetadata()` 等小函式,讓讀取、轉換、寫回與診斷各自可測。",
"is_new": false
},
{
"level": "warning",
"role": "Mage",
"location": "src/findings.js:466",
"problem": "這裡優先使用 `ex.textKey`,但 `textKey` 是由 `toKeyText()` 產生的無分隔且未轉小寫文字,而 findingText 是 `normalizeText()` 產生的小寫、以空白分隔文字。最小重現:排除文字 `Update tests` 會變成 `Updatetests`finding suggestion 會變成 `update tests`,兩邊互相 `includes` 都不成立,導致純文字排除規則失效。",
"suggestion": "排除條目與 finding 應使用同一套正規化函式比對;例如改存並使用 `normalizeText(ex.text || ex.suggestion || ex.title || '')`,或讓 finding 也轉成同樣的 compact/lowercase key。",
"is_new": false
},
{
"level": "warning",
"role": "Maya",
"location": "src/main.js:118",
"problem": "Step5 的角色分析與流程分支是整個 action 的核心,但目前測試沒有覆蓋 main orchestrator:例如所有角色分析都失敗時應 exit 1、部分角色失敗時仍繼續、diff 為空時 exit 0、critical finding 最後應讓 workflow 失敗。這些行為沒有被驗證,等於 pipeline 成敗判斷還沒通過試煉。",
"suggestion": "補上 main 流程層級測試,透過 mock getPRDiff、loadRoles、analyzeWithRole、postFindingsReview、process.exit 等相依,至少覆蓋:diff 空、全部分析失敗、部分分析失敗但繼續、產生 critical 後 exit 1、無 critical 後正常通過。",
"is_new": false
},
{
"level": "warning",
"role": "Maya",
"location": "src/main.js:166",
"problem": "Step7 會把 reconcile.excludedFindings 追加到 exclusions,接著再載入並套用排除規則,但目前缺少整合測試驗證「誤報對話 → 寫入 exclusions → 後續 findings 被排除」這條關鍵路徑。若 append/load/apply 任一環節接錯 workspace 或 mirror,單元測試不一定會抓到。",
"suggestion": "補一個接近流程層級的測試,mock reconcileConversations 回傳 excludedFindings,準備一筆會被排除的新 finding,驗證 appendExclusions 寫入的檔案被 loadExclusions 讀到,且最後 save/post 的 filtered findings 不含該誤報。",
"is_new": false
},
{
"level": "warning",
"role": "Maya",
"location": "src/findings.js:416",
"problem": "applyExclusions 的核心比對支援「只有文字、沒有路徑/角色」的排除規則,但現有測試多半靠相同檔案路徑命中,沒有驗證純文字排除、空文字排除、大小寫/標點差異等邊界。這條排除規則的最脆弱分支還沒被測到。",
"suggestion": "補上 applyExclusions 的邊界測試:只有 suggestion/title 文字沒有 location 的 exclusion 應如何比對;空文字 exclusion 不應意外排除全部;標點、空白、大小寫正規化後相同的文字應依預期排除。",
"is_new": false
},
{
"level": "warning",
"role": "Maya",
"location": "src/gitea.js:95",
"problem": "shouldSkipBotCommit 目前只看到命中 bot marker 的測試,缺少「commit API 失敗、分支查詢失敗、sha/branch 都沒有 marker」時應回 false 的失敗與保守路徑驗證。這是避免 workflow 誤跳過審查的關鍵判斷,不能只測快樂路徑。",
"suggestion": "新增測試讓 getCommitMessageBySha / getBranchHeadCommitMessage 對應的 axios 呼叫拋錯或回一般 commit message,斷言 shouldSkipBotCommit 回 false,且不會把查詢失敗誤判成 bot commit。",
"is_new": false
},
{
"level": "warning",
"role": "Maya",
"location": "src/llm.js:91",
"problem": "runAssistantCLI 有 timeout 與 maxBuffer 兩條重要失敗路徑,但目前測試只覆蓋 CLI 非零退出,沒有驗證逾時會 kill 子程序並拒絕、輸出超過限制會中止且不產生未處理的重複 reject。這些是 CI 上最常見的失敗情境。",
"suggestion": "新增 llm 測試:用假的 CLI sleep 超過 AI_ASSISTANT_TIMEOUT_MS,斷言錯誤訊息包含逾時;再用大量 stdout/stderr 超過 AI_ASSISTANT_MAX_BUFFER,斷言錯誤訊息正確且測試過程沒有 unhandled rejection。",
"is_new": false
},
{
"level": "warning",
"role": "Rogue",
"location": "src/main.js:132",
"problem": "這裡把每個角色的 LLM 分析逐一 await,6 個角色就把總耗時堆成約 6 倍單次模型延遲;這些分析彼此獨立,CPU 沒偷到時間,反而把整條 pipeline 卡在序列網路/CLI 呼叫上。",
"suggestion": "改用 Promise.allSettled 平行執行 roles.map(role => analyzeWithRole(role, diff)),再彙整 fulfilled 結果與 warning;保留 fulfilledAnalyses 的判斷即可。",
"is_new": false
},
{
"level": "warning",
"role": "Rogue",
"location": "src/gitea.js:239",
"problem": "這裡逐一 await 每個 review 的 commentsPR review 一多就變成 N 次遠端呼叫的線性延遲累加;例如 30 個 review 就是 30 個 round-trip 排隊等,時間都被網路空轉偷走。",
"suggestion": "把 reviews.map(review => getPullReviewComments(review.id).catch(...)) 丟進 Promise.all 或 Promise.allSettled 平行抓取,再 flat 結果;單筆失敗仍可記 warn 後略過。",
"is_new": false
},
{
"level": "warning",
"role": "Rogue",
"location": "src/findings.js:430",
"problem": "applyExclusions 在 findings × exclusions 的巢狀比對裡,每遇到一條 exclusion 就重算同一個 finding 的 normalizeTextF 筆 finding、E 條 exclusion 會做最多 F×E 次正規化與正則替換,這是很明顯的 CPU 浪費。",
"suggestion": "先把 findings 預處理成含 fPath、normalizedFindingText 的陣列,exclusions 也先補齊 normalizedExclusionText,再做比對;同一筆文字只正規化一次。",
"is_new": false
},
{
"level": "warning",
"role": "Assassin",
"location": "src/llm.js:21",
"problem": "這裡把未清洗的 `userContent` 直接塞進模型提示詞,等於讓 PR 內容、留言內容或其他外部文字能反過來操控 LLM。攻擊者可以在 diff 裡埋入『忽略前述規則、回傳空陣列』這類指令,讓審查模型漏報真正的風險或把嚴重問題降級成誤報。",
"suggestion": "不要把不可信內容當成可執行指令使用。至少要把 diff/留言做更強的結構化封裝與逸出處理,並在輸出端加上嚴格的 JSON schema 驗證與 deterministic guardrail,避免 LLM 直接決定安全性結論。",
"is_new": true
},
{
"level": "warning",
"role": "Leo",
"location": "src/findings.js:111",
"problem": "`normalizeText` 與 `toKeyText` 兩套正規化規則不一致,前者會轉小寫,後者不會,而且註解還直接寫了「不確定」。這會讓排除、去重、比對在不同路徑出現微妙分歧,半年後很難追出到底是哪個標準才是正確來源。",
"suggestion": "把文字正規化抽成單一共用 helper,讓大小寫是否敏感變成明確參數或不同命名的意圖函式,並補上覆蓋兩種路徑的測試,避免未來兩套規則繼續漂移。",
"is_new": true
},
{
"level": "warning",
"role": "Leo",
"location": "src/resolve.js:183",
"problem": "`reconcileConversations` 同時負責收 comment、分組、關閉、讀檔、AI 裁決、結果分類與降級處理,職責太多而且彼此耦合。任何一個小規則變動,都得先看完整條流程,單元測試也很難只鎖定某一段行為。",
"suggestion": "拆成幾個可測的純函式與薄編排層,例如 `groupConversations`、`closeOpenComments`、`buildJudgeItems`、`applyVerdicts` 分開處理,讓主流程只保留資料流轉與錯誤收斂。",
"is_new": true
},
{
"level": "warning",
"role": "Leo",
"location": "src/git.js:222",
"problem": "`commitAndPush` 把 repo 對齊、檔案複製、stage、commit、push、失敗降級全部塞在一起,還保留了一個目前沒用到的 `_sourceRoot` 參數。這種 API 會越長越像腳本,之後要改 staging 規則或推送策略時,維護者很難快速定位應該改哪一段。",
"suggestion": "把它拆成 `syncRepo`、`stageReviewFiles`、`createCommit`、`pushCommit` 幾個步驟,再由一個很薄的 orchestrator 串起來;同時移除或真正使用 `_sourceRoot`,避免留下誤導性的簽章。",
"is_new": true
},
{
"level": "warning",
"role": "Leo",
"location": "src/main.js:54",
"problem": "`main()` 已經變成整條 pipeline 的超級入口,11 個 step、exit 判斷、資料收集、排序/過濾與發布全部擠在同一個函式裡。未來只要某一步的前置條件改了,維護者就得在這個巨型函式裡追完整條狀態流,認知負擔很高。",
"suggestion": "把每個 step 拆成獨立函式並回傳明確的 context,讓 `main()` 只負責流程編排與最終 exit 決策;這樣之後新增步驟或調整順序時,不會把整條 pipeline 綁死在同一個函式裡。",
"is_new": true
},
{
"level": "warning",
"role": "Mage",
"location": "src/comments.js:40",
"problem": "這裡把 `file:0` 也視為有效行號;最小重現:只要上游傳進 `app/foo.js:0``parseLocation()` 會回傳 line=0,後續 `postPullReviewComment` 會帶著 `new_position: 0` 發到 Gitea,通常會被拒絕或定位失敗。也就是說,0 行號沒有被當成缺值處理。",
"suggestion": "把行號門檻改成 `> 0`,`0` 與負數都應視為無效;同時讓需要行號的呼叫端把這種情況當作缺行號,重新定位或降級處理。",
"is_new": true
},
{
"level": "warning",
"role": "Mage",
"location": "src/json.js:88",
"problem": "這裡只檢查 `JSON.parse` 能不能過,沒有確認解析結果一定是陣列;最小重現:repairer 回傳 `{}` 或檔案本身就是 `{}` 時,函式仍會回報 valid 並寫回磁碟,但後續程式都把它當陣列讀取,最後會悄悄被當成空資料或造成形狀錯誤。",
"suggestion": "在驗證成功前先檢查 `Array.isArray(parsed)`,只有真正的陣列才算通過;修復後也要同樣做陣列檢查,否則就丟錯並保留原檔。",
"is_new": true
},
{
"level": "warning",
"role": "Mage",
"location": "src/resolve.js:213",
"problem": "`findingSig` 只用檔案路徑加上 `suggestion` 來識別問題,忽略了 `problem`、`role`,也沒有留下任何穩定的 thread 識別;最小重現:同一個 `a.js` 內有兩條都建議「加上 null 檢查」但其實是不同位置的 finding,先解掉其中一條後,另一條也會被當成同一筆而被 `dropResolvedFindings` / `addCarriedFindings` 誤合併或誤刪。",
"suggestion": "把識別鍵改成更穩定的組合,例如檔案路徑 + 正規化後的 `problem` + `suggestion` + `role`,或直接使用可追蹤的 thread/issue id;不要只靠 `suggestion` 斷言是不是同一個問題。",
"is_new": true
},
{
"level": "warning",
"role": "Maya",
"location": "src/config.js:31",
"problem": "這裡是整個 action 讀取 `INPUT_*`、`GITEA_*` 與事件 payload 的入口,但測試只覆蓋了 `getLLMConfig()`,沒有把 `GITEA_TOKEN`、`GITEA_COMMENT_TOKEN`、`PR_NUMBER`、`PR_HEAD_SHA` 這些環境與 payload 的優先序鎖住。特別是 comment token 退回主 token、以及 event 檔讀不到時回到空值的情境,都是 CI 最容易因環境差異壞掉的地方。",
"suggestion": "新增 config 相關測試,分別用假 `process.env` 和暫存 event payload 檔驗證:`INPUT_*` 會蓋過 ambient env、`GITEA_COMMENT_TOKEN` 缺值時會 fallback 到主 token、`GITEA_EVENT_PATH` / `GITHUB_EVENT_PATH` 讀取失敗時不會拋錯且回傳預設值。",
"is_new": true
},
{
"level": "warning",
"role": "Maya",
"location": "src/comments.js:211",
"problem": "`postFindingsReview()` 的主快樂路徑有測到,但它在批次 review 失敗後還有第二層降級邏輯:先重送 summary-only review,若 summary 也失敗才改走一般 comment,再逐筆補行內 comment。這條失敗鏈現在沒有被驗證,等於最重要的容錯行為只被程式碼描述,沒有被試煉。",
"suggestion": "補測 `postReview({ body, comments })` 先丟錯、再讓 `postReview({ body, comments: [] })` 也丟錯的情境,斷言最後會呼叫 `postIssue(body)`,且原本的 comments 仍會逐筆走 `postInline`。如果要更完整,也順便補 `postOldFindingsComment()` 與 `postNewNonCriticalComment()` 的篩選與空集合跳過案例。",
"is_new": true
},
{
"level": "warning",
"role": "Maya",
"location": "src/llm.js:66",
"problem": "`runAssistantCLI()` 目前只有成功與一般失敗的測試,沒有覆蓋 timeout、`maxBuffer` 超限、以及 `opencode` 分支建立的暫存 prompt 檔在例外發生時是否確實清理。這些都是外部 CLI 整合最常出問題的失敗路徑,沒有測到就很難確定不會留下殘檔或把流程卡死。",
"suggestion": "補 fake CLI 測試,讓子程序超時、輸出超過 `AI_ASSISTANT_MAX_BUFFER`、以及 `opencode` 在 `spawn`/`close` 前後失敗,分別斷言會回傳對應錯誤,且暫存目錄與 `prompt.md` 會被清掉。",
"is_new": true
},
{
"level": "warning",
"role": "Rogue",
"location": "src/main.js:98",
"problem": "這裡把每個角色的 LLM 分析用 `for...of + await` 串成一條龍,角色數一多就把總等待時間從「最慢那個角色」拉成「全部角色耗時相加」。每多一個角色,就白白多吃一輪模型呼叫延遲,熱路徑會被拖得很明顯。",
"suggestion": "改成平行發出各角色分析,例如先 `Promise.all` 收集結果,再依原順序合併與排序;如果擔心單一失敗中斷,搭配 `Promise.allSettled` 保留容錯。",
"is_new": true
},
{
"level": "warning",
"role": "Rogue",
"location": "src/preflight.js:152",
"problem": "前置驗證的 Gitea token、comment token、git remote、LLM 驗證彼此沒有相依,卻被拆成連續等待。每個步驟都可能卡網路與 30 秒級 timeout,最差會把啟動時間疊成多倍,白白浪費整段等待。",
"suggestion": "把互不相依的檢查改成並行執行,至少讓 token / remote / LLM 這幾項同時跑,只保留必要的 env 檢查先行。",
"is_new": true
},
{
"level": "warning",
"role": "Rogue",
"location": "src/findings.js:413",
"problem": "這裡對每一筆 finding 都用 `exclusions.some(...)` 線性掃完整份排除清單,還在內層反覆做文字正規化,複雜度直接變成 O(findings × exclusions)。排除規則一多,這段會把 CPU 週期浪費在重複比對上。",
"suggestion": "先把 exclusions 依 `filePath`、`role` 或正規化後的 `textKey` 建索引/分桶,再做比對;這樣可以把熱路徑從雙層掃描降到接近線性。",
"is_new": true
},
{
"level": "info",
"role": "Bard",
"location": "src/git.js:226",
"problem": "_sourceRoot` 的參數文件寫著「不確定,待確認」,讓公開函式簽名帶著未完成的旁白,破壞 API 文件的一致與可信度。",
"suggestion": "若參數已不使用,移除它;若為相容性保留,明確寫成 deprecated/compatibility note,不要留下模糊語句。",
"is_new": false
},
{
"level": "info",
"role": "Leo",
"location": "src/json.js:13",
"problem": "`stripCodeFence()` 與 `src/llm.js` 內的 `stripOuterFence()` 幾乎是同一個功能,未來如果要支援更多 fence 格式或修 bug,兩邊需要同步修改,容易產生行為漂移。",
"suggestion": "抽成共用的 JSON/text utility,例如 `src/text.js` 或 `src/json.js` 匯出單一 fence 清理函式,讓 LLM JSON 解析與 JSON repair 共用同一套邏輯。",
"is_new": false
},
{
"level": "info",
"role": "Leo",
"location": "src/findings.js:104",
"problem": "文字正規化邏輯分散在 `normalizeText()`、`toKeyText()`,而 `src/resolve.js` 也有另一套 `normalizeKey()`。這些函式對大小寫、標點與空白的處理不完全一致,長期會讓 finding 去重、排除與對話收斂出現難追的差異。",
"suggestion": "建立單一 normalization 模組,明確定義 `normalizeForDisplayMatch`、`normalizeForSignature` 等用途,再讓 findings、resolve、exclusions 共用,並補上跨模組測試鎖定語意。",
"is_new": false
},
{
"level": "info",
"role": "Maya",
"location": "src/comments.js:25",
"problem": "Markdown 表格列直接嵌入 role、location、suggestion,但測試沒有覆蓋 suggestion 含 `|`、換行或 Markdown 特殊字元時的輸出。這不是要求現在一定要改格式,而是目前缺少案例確認表格在真實 LLM 輸出下不會被破壞。",
"suggestion": "補一個 comment body 格式測試,輸入 suggestion 含 pipe、換行與粗體符號,斷言輸出的 Markdown 結構符合預期;若目前行為會破表,應先定義轉義或替換規則再測。",
"is_new": false
},
{
"level": "info",
"role": "Rogue",
"location": "src/findings.js:382",
"problem": "loadExclusions 前面已經 normalizeExclusionEntry + dedupeExclusions,這裡又呼叫 buildExclusionContext(exclusions) 重新 normalize、dedupe、group 一輪,只為了 log groups 數;排除規則多時會多跑一趟 O(e log e) 的整理成本。",
"suggestion": "讓 buildExclusionContext 可接受已正規化/已去重的 exclusions,或直接在 loadExclusions 重用現有 exclusions 進行 group 統計,避免重複正規化與排序。",
"is_new": false
}
]