feat(ai-code-review): 建問題模式導向 issue,並整併流程調整、文件與 CI #8
Notifications
Due Date
No due date set.
Blocks
#6 feat(ai-code-review): 建問題模式導向 issue,並整併流程調整、文件與 CI
node-actions/ai-code-review
Reference: node-actions/ai-code-review#8
Reference in New Issue
Block a user
變更摘要
本 PR 將 ai-code-review action 分支的完整成果併入
develop,主要含四塊:create-issue: 'true'時,審查留言(工具/diff/角色/嚴重問題/警告+建議)全部改發到追蹤 issue、不再張貼到 PR;不執行「舊留言標過時/resolve」;issue 延後到「確定有保留問題」才建立;無保留問題或無可審查變更則不建 issue、PR 也完全不留言(靜默通過);有問題時最後在 PR 回貼一則 issue 連結並補掛標籤,形成雙向關聯。action.yml/src各模組文件、重建readme.md,新增.gitea/workflows/ci.yaml(多工具 code review 驗證)並啟用建問題模式。影響範圍
src/index.jsmain()依模式分流:留言去向、跳過 resolveOldComments、issue 延後建立、severe/others 導向 issue、有問題才在 PR 回貼連結src/lib/gitea.js新增addLabelsToIssuesrc/lib/templates.js新增issueLinkCommentsrc/lib/review.js新增postSevereToIssue,移除createIssueWithFindings、sortFindingsForIssuesrc/lib/gitrepo.js補抓淺層 checkout 的 base 歷史readme.md、action.yml、.gitea/workflows/ci.yaml、.gitea/workflows/readme.md風險與注意事項
readme.md尚未反映「建問題模式導向 issue」的最新行為與新/移除的函式,建議後續以 doc-funcs 重建文件。2026/07/17 18:49:58,未刷新。🤖 AI Code Review|審查工具
codexcodex-cli 0.144.610d826d1b17ab26521359a11e08dbe3e7ead8d31📋 變更摘要(送審 git diff)
action.ymlreadme.mdsrc/index.jssrc/lib/agents.jssrc/lib/context.jssrc/lib/gitea.jssrc/lib/gitrepo.jssrc/lib/review.jssrc/lib/roles.jssrc/lib/templates.js⚔️ 攻擊方登場
🛡️ 防守方登場
🔴 嚴重|🔮 Mage
位置:
src/index.js第 268–268 行問題描述
舊審查留言仍在本回合結果完整發布前就被標記為過時;一般審查路徑的同類呼叫也出現在約第 343 行。最小重現:先有一輪有效舊 findings,本輪成功完成 AI 裁決後執行
resolveOldComments,接著postSevereComments、其他問題留言或commitFindings因 API/push 暫時失敗;Action 最終回傳失敗,但舊結果已被清除,新結果又只發布一部分或完全未提交。無可審查變更路徑同樣會在saveFindings與 push 成功前清除舊結果,違反此次改動宣稱的「成功產生結果後才清除」語義。修改建議
把清理舊留言移到本輪所有必要結果均成功產生之後:無變更路徑至少先保存並成功提交空 findings;一般路徑先完成所有新留言與必要提交,再清理舊留言。若 push 會觸發下一輪而不能最後執行,應將發布狀態做成可重入的兩階段流程,確保失敗重跑不會留下「舊結果已清除、新結果不完整」的中間狀態。
🔴 嚴重|🗡️ Assassin
位置:
src/lib/gitrepo.js第 274–276 行問題描述
專用 PAT 被直接嵌入遠端 URL,再作為
git push的命令列參數。攻擊者只要讓推送失敗,Node.js/git 的例外、程序診斷或 runner 程序清單就可能帶出完整命令,將 PAT 洩漏到 CI log 或同機程序;註解宣稱「不輸出 URL」並不能阻止底層例外回顯 argv。修改建議
推送目標應維持不含憑證的 URL,改用暫時性的
GIT_ASKPASS/credential helper 或受 runner 保護的認證機制提供 PAT;同時在 git 包裝層攔截錯誤並建立不含參數與遠端 URL的固定錯誤訊息,清除暫存憑證後再拋出。🔴 嚴重|🗡️ Assassin
位置:
src/lib/review.js第 72–81 行問題描述
開啟
ACTIONS_STEP_DEBUG=true後,會把攻擊者可間接操控的 AI CLI stderr/stdout 寫入長期保存的 CI log。redactSecrets只是黑名單式正規表示式,短 token、JWT、含標點或空白的密碼、非典型金鑰及 PII 都能繞過;惡意 PR 可透過提示注入誘使 CLI 回顯來源內容或環境資訊,再刻意觸發失敗完成外洩。這也違反回應不得包含 PII 的組織規範。修改建議
CI log 不應輸出 AI CLI 原文,即使在除錯模式也只記錄退出碼、signal、逾時狀態與隨機診斷 ID。若確實需要原始輸出,應寫入存取受限、短期保存且經人工授權取得的安全 artifact,並在寫入前套用允許清單式結構化診斷與 PII/機密掃描。
建議寫法
🟠 警告|🧰 Leo
位置:
src/index.js第 176–218 行問題描述
main()內新增了issueBuffer、可變的issue、postComment與ensureIssueCreated閉包,後續流程又在多處依ctx.createIssue分支。留言路由、issue 生命週期、標籤、相依關係與審查編排因此共享同一批可變狀態;未來再增加發布目的地或重試策略時,必須同步理解並修改整個超長主流程,測試也只能透過main()間接覆蓋。修改建議
抽出具明確介面的發布器,例如
PrReviewPublisher與IssueReviewPublisher,由發布器封裝留言暫存、issue 建立、沖刷、問題明細與收束關聯;main()只呼叫一致的publishContext、publishFindings、finalize。如此可分別注入假的 Gitea client 測試兩種模式,也能移除散落的模式判斷。🟠 警告|🧪 Maya
位置:
src/index.js第 184–245 行問題描述
建問題模式新增了「issue 建立前先暫存留言、建立後依序沖刷」的狀態流程,但本次變更沒有對應測試。尚未驗證
createIssue或沖刷途中拋錯、空 buffer、重複呼叫ensureIssueCreated,以及一般模式與建問題模式的留言目的地是否正確;這些失敗路徑可能造成 issue 已建立但內容不完整,或留言誤發到 PR。修改建議
補上主流程整合測試,mock Gitea API 並驗證:一般模式直接發 PR 且記錄留言 id;issue 尚未建立時只暫存;建立後依原順序沖刷並清空 buffer;issue 已建立後直接留言;
createIssue與第 N 則沖刷留言失敗時會以失敗結束,且不會繼續發布後續內容。🟠 警告|🧪 Maya
位置:
src/index.js第 309–395 行問題描述
建問題模式的核心分支被大幅改寫,但沒有測試驗證各種 findings 組合與 API 失敗時的行為。尤其
kept.length === 0、只有嚴重問題、只有警告/建議、兩者皆有,以及標籤挑選失敗、issue 建立失敗、PR 回貼連結失敗、相依 API 失敗等路徑都尚未被試煉;目前無法確認「無問題時完全靜默」、「有問題才建 issue」及相依失敗僅降級等契約真的成立。修改建議
以參數化測試覆蓋 findings 的四種組合,精確斷言 API 呼叫順序、次數、issue number 與問題統計。另分別讓
listLabels、selectLabels、createIssue、問題留言、PR 連結留言及addIssueDependency拋錯,驗證哪些錯誤應中止、哪些只記錄警告並繼續;也要斷言零 findings 時所有寫入 API 都不會被呼叫。🟠 警告|🧪 Maya
位置:
src/lib/gitea.js第 171–215 行問題描述
新增的標籤與問題相依 API 包裝沒有測試驗證 endpoint、HTTP method 與 payload。特別是
addLabelsToIssue的 null/undefined/空陣列短路,以及addIssueDependency中容易顛倒的「PR 相依於 issue」方向都未被斷言;若 index 或 body 欄位放反,測試外觀仍可能正常但合併阻擋語意會相反。修改建議
mock 底層 API,驗證空 labels 完全不送請求並回傳 null,非空 labels 使用正確 issue 編號與
{ labels }。對相依關係使用不同的 PR/issue 編號,精確斷言 URL 指向 PR、body.index 指向阻擋來源 issue,並覆蓋 API 非 2xx 時錯誤原樣往上拋出的案例。🟠 警告|🧪 Maya
位置:
src/lib/gitrepo.js第 103–157 行問題描述
resolveMergeBase新增多階段 fetch/重試策略,卻沒有測試鎖定淺層與失敗路徑。尚未驗證首次 merge-base 成功時會提早返回、非 shallow repo 不執行 unshallow、某次 fetch 失敗後仍會嘗試下一策略、補抓成功後立即停止,以及全部失敗時診斷與cause是否完整;這類流程很容易因呼叫順序或 off-by-one 次數錯誤而在 runner 上才暴露。修改建議
將 git 執行器注入或 stub,建立表格化案例覆蓋:首次成功、unshallow 後成功、deepen base 後成功、deepen HEAD 後成功、各 fetch 個別失敗,以及全部策略失敗。逐案斷言 git 參數與呼叫順序、成功後不再額外 fetch,並檢查最終錯誤包含每個策略的成敗摘要且保留首次錯誤為
cause。🟠 警告|🧪 Maya
位置:
src/lib/gitrepo.js第 249–289 行問題描述
推送流程新增
pushToken分支,但沒有測試驗證兩套認證策略。尚未確認有 PAT 時確實略過 origin、PAT 推送失敗時不會意外退回其他 token、無 PAT 時 origin 成功不重試,以及 origin 失敗才使用一般 token;也沒有案例保護含憑證 URL 不會出現在錯誤或測試輸出中。修改建議
mock git 執行器與 URL 組裝,補測
pushToken有值/空字串、origin 成功/失敗、PAT 推送失敗及 token 含特殊字元等案例。精確斷言 push 目標與呼叫次數,並確保任何拋出的錯誤、log 或快照都不含原始 token。🟠 警告|🧪 Maya
位置:
src/lib/review.js第 16–80 行問題描述
新增的
redactSecrets/agentFailureDetail直接決定 CI 日誌是否洩漏內容及失敗診斷是否可用,但本次沒有測試覆蓋。空值、控制字元、Authorization/Bearer、URL 帳密、各種 token 樣式、500 字截斷,以及ACTIONS_STEP_DEBUG大小寫與未啟用時隱藏 stdout/stderr 等邊界都尚未驗證。修改建議
為這兩個純函式補單元測試;必要時以受控方式匯出或抽到獨立模組。使用不具真實性的假憑證逐一測試所有遮罩規則與換行注入,並在每個案例斷言輸出不含原始秘密。另保存並還原
ACTIONS_STEP_DEBUG,驗證預設、true、混合大小寫、逾時、exit code、signal、無 error 與超長輸出的結果。🟠 警告|⚡ Rogue
位置:
src/lib/review.js第 68–72 行問題描述
除錯模式下,stderr/stdout 會先把完整內容送進
redactSecrets(),經過多輪正規表示式掃描與字串配置後才截成 500 字。若 AI CLI 失敗並產生數 MB 輸出,每份輸出約需 O(k×n) 掃描及多份 O(n) 暫存字串,白白消耗 CPU 與記憶體;攻擊方並行失敗時還會按角色數放大。修改建議
先限制送入遮罩器的字元數,再進行遮罩與最終截斷;保留少量緩衝即可涵蓋跨界 token 樣式,讓診斷成本固定在 O(1) 上限。
建議寫法
🔵 建議|🎼 Bard
位置:
action.yml第 47–61 行問題描述
push-token的用途與退回行為在區塊註解、欄位描述、required註解及default註解中反覆吟唱,且單行description過長。資訊雖完整,主旋律卻被重複說明淹沒,日後修改語意也容易只改到其中一處。修改建議
保留一段解釋「為何需要 PAT」的必要背景,其餘讓欄位名稱、
required與default自行表意;將description收斂成呼叫端真正需要知道的契約。建議寫法
🔵 建議|🎼 Bard
位置:
readme.md第 41–48 行問題描述
流程圖將「舊留言標記解決」畫成步驟 1 後立即執行,但新版主流程實際延後至攻防裁決完成、發布問題留言之前。文件的演奏順序與程式碼錯拍,讀者會誤以為舊結果仍會在審查初期被清除。
修改建議
依實際執行順序重排節點,或明確將此節點標示為「編號 2、延後執行」,避免步驟編號被誤讀為時間順序。
建議寫法
🔵 建議|🎼 Bard
位置:
src/index.js第 203–226 行問題描述
ensureIssueCreated之名帶有「已存在便沿用」的冪等語意,實際卻無條件建立新 issue,並悄悄改寫外層的issue。名稱、行為與副作用沒有押上同一個韻,閱讀呼叫處時容易形成錯誤預期。修改建議
若此函式只允許呼叫一次,改用直接表達建立與沖刷暫存留言的名稱,並回傳建立結果;由呼叫端明確指派
issue,讓資料流一眼可見。建議寫法
🔵 建議|🧰 Leo
位置:
src/lib/gitea.js第 171–195 行問題描述
本次新增
addLabelsToIssue,但新流程已明確改為建立 issue 時一次帶入標籤,且目前沒有任何呼叫端。保留這個推測未來可能使用的公開函式,會擴大 Gitea client 的 API 面積,讓維護者誤以為存在兩套正式的標籤流程,未來也必須持續維護文件與測試。修改建議
若目前沒有實際使用情境,先移除
addLabelsToIssue與匯出項目;等真正需要對既有 issue 追加標籤時,再連同明確呼叫端與測試一起加入。這個功能的目的是把前一輪 PR 產生的訊息標記為已解決或過時,讓審查人員可以知道那些訊息是舊的,所以不能移到本輪結果產生之後執行,這樣會誤導審查人員
🔧 AI Code Review 問題處理進度
已依嚴重度逐條處理議題 #8 的 15 條審查問題,修復已提交並推送至分支
ai-review-resolve/develop-20260717-185330(納入既有 PR #6 →develop)。統計:✅ 已解決 4 條、⏭️ 待人工處理 10 條、🚫 不採納 1 條。
src/lib/gitrepo.jsGIT_CONFIG_*/http.<url>.extraheader(base64 Basic)以 env 傳入、不進 argv,推送 URL 不含帳密;push 失敗改拋固定訊息,避免例外把命令列(含 token)回顯到 CI logsrc/lib/review.jssrc/lib/gitea.jsaddLabelsToIssue(含匯出);現行流程已改為建立 issue 時一次帶入標籤readme.mdsrc/index.jsresolveOldComments用途為標記前一輪舊訊息、刻意在本回合結果前執行,不可延後,否則誤導審查人員。已記入exclusions.json以免後續重複提出src/lib/review.jssrc/index.jsPrReviewPublisher/IssueReviewPublisher屬大範圍重構+設計取捨src/index.jssrc/index.jssrc/lib/gitea.jssrc/lib/gitrepo.jssrc/lib/gitrepo.jssrc/lib/review.jsaction.ymlsrc/index.jsensureIssueCreated重命名與 #4768 的 publisher 重構指向同區、維護者審視中,宜一併處理⏭️ 待人工處理已寫回 findings 追蹤
10 條待人工處理問題已寫成 findings wrapper 檔
.gitea/ai-review/findings/2026-07-20-15:57:46.json(F001–F010),供後續 review/resolve 追蹤,不會因關閉議題而遺失待辦。待人工處理共同原因
多數繞不開「專案目前無測試框架/devDependencies/可測性結構」(Maya 六條)或「對核心流程做大範圍重構/安全政策取捨」(Assassin debug 輸出、Leo publisher、Bard 重命名與註解慣例)。皆屬架構決策,依規範不逕自硬加,避免對未經 CI 驗證的核心流程造成回歸。