feat(ai-code-review): 建問題模式導向 issue,並整併流程調整、文件與 CI #10

Closed
opened 2026-07-20 07:44:22 +00:00 by gitea-actions · 17 comments

變更摘要

本 PR 將 ai-code-review action 分支的完整成果併入 develop,主要含四塊:

  1. 建問題模式重構(feat)create-issue: 'true' 時,審查留言(工具/diff/角色/嚴重問題/警告+建議)全部改發到追蹤 issue、不再張貼到 PR;不執行「舊留言標過時/resolve」;issue 延後到「確定有保留問題」才建立;無保留問題或無可審查變更則不建 issue、PR 也完全不留言(靜默通過);有問題時最後在 PR 回貼一則 issue 連結並補掛標籤,形成雙向關聯。
  2. 審查流程步驟調整(refactor):將「標記舊留言過時/resolve」提前到偵測 AI 工具之前,並依新順序重編所有步驟編號與說明。
  3. 修正(gitrepo):補抓淺層 checkout 的 base 歷史,避免 merge-base 計算失敗。
  4. 文件與 CI:補齊 action.ymlsrc 各模組文件、重建 readme.md,新增 .gitea/workflows/ci.yaml(多工具 code review 驗證)並啟用建問題模式。

影響範圍

範圍 說明
action 執行流程 src/index.js main() 依模式分流:留言去向、跳過 resolveOldComments、issue 延後建立、severe/others 導向 issue、有問題才在 PR 回貼連結
Gitea API src/lib/gitea.js 新增 addLabelsToIssue
留言模板 src/lib/templates.js 新增 issueLinkComment
審查核心 src/lib/review.js 新增 postSevereToIssue,移除 createIssueWithFindingssortFindingsForIssue
git base 解析 src/lib/gitrepo.js 補抓淺層 checkout 的 base 歷史
文件與 CI readme.mdaction.yml.gitea/workflows/ci.yaml.gitea/workflows/readme.md

風險與注意事項

  • 建問題模式行為明顯改變:啟用後 PR 上不再有審查留言(有問題時改為 issue + 一則連結留言;無問題時完全不留言),請確認團隊流程可接受。
  • 端對端行為需靠 CI 實測:本地僅完成語法與模組載入驗證,實際 issue/PR 互動需一次 CI 觸發確認。
  • readme.md 尚未反映「建問題模式導向 issue」的最新行為與新/移除的函式,建議後續以 doc-funcs 重建文件。
  • 各檔頭部「更新時間」時間戳仍為 2026/07/17 18:49:58,未刷新。

本問題由 AI Code Review 依 PR #6 的審查結果自動建立,問題明細見下方留言。

<!-- ai-code-review --> ## 變更摘要 本 PR 將 ai-code-review action 分支的完整成果併入 `develop`,主要含四塊: 1. **建問題模式重構(feat)**:`create-issue: 'true'` 時,審查留言(工具/diff/角色/嚴重問題/警告+建議)全部改發到追蹤 issue、不再張貼到 PR;不執行「舊留言標過時/resolve」;issue 延後到「確定有保留問題」才建立;**無保留問題或無可審查變更則不建 issue、PR 也完全不留言(靜默通過)**;有問題時最後在 PR 回貼一則 issue 連結並補掛標籤,形成雙向關聯。 2. **審查流程步驟調整(refactor)**:將「標記舊留言過時/resolve」提前到偵測 AI 工具之前,並依新順序重編所有步驟編號與說明。 3. **修正(gitrepo)**:補抓淺層 checkout 的 base 歷史,避免 merge-base 計算失敗。 4. **文件與 CI**:補齊 `action.yml`/`src` 各模組文件、重建 `readme.md`,新增 `.gitea/workflows/ci.yaml`(多工具 code review 驗證)並啟用建問題模式。 ## 影響範圍 | 範圍 | 說明 | | --- | --- | | action 執行流程 | `src/index.js` `main()` 依模式分流:留言去向、跳過 resolveOldComments、issue 延後建立、severe/others 導向 issue、有問題才在 PR 回貼連結 | | Gitea API | `src/lib/gitea.js` 新增 `addLabelsToIssue` | | 留言模板 | `src/lib/templates.js` 新增 `issueLinkComment` | | 審查核心 | `src/lib/review.js` 新增 `postSevereToIssue`,移除 `createIssueWithFindings`、`sortFindingsForIssue` | | git base 解析 | `src/lib/gitrepo.js` 補抓淺層 checkout 的 base 歷史 | | 文件與 CI | `readme.md`、`action.yml`、`.gitea/workflows/ci.yaml`、`.gitea/workflows/readme.md` | ## 風險與注意事項 - **建問題模式行為明顯改變**:啟用後 PR 上不再有審查留言(有問題時改為 issue + 一則連結留言;無問題時完全不留言),請確認團隊流程可接受。 - **端對端行為需靠 CI 實測**:本地僅完成語法與模組載入驗證,實際 issue/PR 互動需一次 CI 觸發確認。 - `readme.md` 尚未反映「建問題模式導向 issue」的最新行為與新/移除的函式,建議後續以 doc-funcs 重建文件。 - 各檔頭部「更新時間」時間戳仍為 `2026/07/17 18:49:58`,未刷新。 --- > 本問題由 AI Code Review 依 PR #6 的審查結果自動建立,問題明細見下方留言。

🤖 AI Code Review|審查工具

項目 內容
工具 codex
版本 codex-cli 0.144.6
模型 (工具預設)
審查 commit cfbbbe5f6c3beb591848162100bdbc6073759731
Run Job #23
flowchart LR
    A[整理 git diff] --> B[⚔️ 攻擊方找問題]
    B --> C[🛡️ 防守方裁決]
    C --> D[保存 findings]
    D --> E[留言到 PR]
<!-- ai-code-review --> ## 🤖 AI Code Review|審查工具 | 項目 | 內容 | | --- | --- | | 工具 | `codex` | | 版本 | `codex-cli 0.144.6` | | 模型 | (工具預設) | | 審查 commit | `cfbbbe5f6c3beb591848162100bdbc6073759731` | | Run Job | [#23](https://gitea.jsc.idv.tw/node-actions/ai-code-review/actions/runs/1591) | ```mermaid flowchart LR A[整理 git diff] --> B[⚔️ 攻擊方找問題] B --> C[🛡️ 防守方裁決] C --> D[保存 findings] D --> E[留言到 PR] ```

📋 變更摘要(送審 git diff)

檔案 用途 git diff 長度 最後更新時間
action.yml 定義 Action 輸入與執行入口 95 行/4205 字元 2026/07/20 15:00:23
readme.md 說明專案功能、流程與使用方式 274 行/23780 字元(過長截斷送審) 2026/07/20 09:46:12
src/index.js 編排多角色程式碼審查主流程 320 行/14204 字元 2026/07/20 15:00:15
src/lib/agents.js 偵測並執行可用的 AI 工具 14 行/503 字元 2026/07/20 09:46:12
src/lib/context.js 載入 Action 輸入與執行環境 31 行/1398 字元 2026/07/20 15:00:23
src/lib/gitea.js 封裝 Gitea API 操作 143 行/6234 字元 2026/07/20 15:00:15
src/lib/gitrepo.js 處理 Git 差異、提交與推送 177 行/7374 字元 2026/07/20 14:59:49
src/lib/review.js 執行審查、裁決與結果處理 579 行/24767 字元(過長截斷送審) 2026/07/20 15:41:17
src/lib/roles.js 載入並分類審查角色設定 14 行/586 字元 2026/07/20 09:46:12
src/lib/templates.js 產生審查留言與報告模板 165 行/5949 字元 2026/07/20 15:00:32

共 10 個檔案納入審查;另有 6 個檔案依 .reviewignore 排除。

<!-- ai-code-review --> ## 📋 變更摘要(送審 git diff) | 檔案 | 用途 | git diff 長度 | 最後更新時間 | | --- | --- | --- | --- | | `action.yml` | 定義 Action 輸入與執行入口 | 95 行/4205 字元 | 2026/07/20 15:00:23 | | `readme.md` | 說明專案功能、流程與使用方式 | 274 行/23780 字元(過長截斷送審) | 2026/07/20 09:46:12 | | `src/index.js` | 編排多角色程式碼審查主流程 | 320 行/14204 字元 | 2026/07/20 15:00:15 | | `src/lib/agents.js` | 偵測並執行可用的 AI 工具 | 14 行/503 字元 | 2026/07/20 09:46:12 | | `src/lib/context.js` | 載入 Action 輸入與執行環境 | 31 行/1398 字元 | 2026/07/20 15:00:23 | | `src/lib/gitea.js` | 封裝 Gitea API 操作 | 143 行/6234 字元 | 2026/07/20 15:00:15 | | `src/lib/gitrepo.js` | 處理 Git 差異、提交與推送 | 177 行/7374 字元 | 2026/07/20 14:59:49 | | `src/lib/review.js` | 執行審查、裁決與結果處理 | 579 行/24767 字元(過長截斷送審) | 2026/07/20 15:41:17 | | `src/lib/roles.js` | 載入並分類審查角色設定 | 14 行/586 字元 | 2026/07/20 09:46:12 | | `src/lib/templates.js` | 產生審查留言與報告模板 | 165 行/5949 字元 | 2026/07/20 15:00:32 | > 共 10 個檔案納入審查;另有 6 個檔案依 `.reviewignore` 排除。

⚔️ 攻擊方登場

角色 面向 個性
🗡️ Assassin 安全性(security) 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用
🎼 Bard 風格(style) 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版
🧰 Leo 可維護性(maintainability) 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來
🔮 Mage 邏輯(logic) 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」
🧪 Maya 測試(testing) 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑
Rogue 效率(efficiency) 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」
<!-- ai-code-review --> ## ⚔️ 攻擊方登場 | 角色 | 面向 | 個性 | | --- | --- | --- | | 🗡️ **Assassin** | 安全性(security) | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | 🎼 **Bard** | 風格(style) | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | 🧰 **Leo** | 可維護性(maintainability) | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | 🔮 **Mage** | 邏輯(logic) | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | 🧪 **Maya** | 測試(testing) | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | ⚡ **Rogue** | 效率(efficiency) | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 |

🛡️ 防守方登場

角色 面向 個性
🛡️ Paladin 裁決(verdict) 沉穩公正、就事論事,不護短也不冤枉,只依排除事項與原始碼脈絡裁定問題成立與否
<!-- ai-code-review --> ## 🛡️ 防守方登場 | 角色 | 面向 | 個性 | | --- | --- | --- | | 🛡️ **Paladin** | 裁決(verdict) | 沉穩公正、就事論事,不護短也不冤枉,只依排除事項與原始碼脈絡裁定問題成立與否 |

🔴 嚴重|🔮 Mage

位置src/lib/gitrepo.js 第 145–145 行

問題描述

名為 deepen HEAD 的補抓策略實際執行 git fetch ... origin HEAD;這裡的 HEAD 由遠端解析,通常代表遠端預設分支,不是目前 checkout 的 PR head。最小重現為:PR 分支使用 shallow checkout、--unshallow 不可用,且共同祖先位於目前 PR head 的淺層邊界之外;此策略只加深預設分支,PR head 歷史仍不完整,最後錯誤回報找不到 merge-base。

修改建議

以明確的 PR head ref 或 SHA 補抓目前 head 的歷史;若介面沒有 head ref,可取得目前 HEAD SHA 後用伺服器支援的 refspec fetch,或使用會加深目前設定 refspec 的 fetch 方式,避免把遠端符號 HEAD 當成本地目前 HEAD。

建議寫法

const headSha = gitTrim(cwd, 'rev-parse', 'HEAD');
strategies.push(['deepen PR HEAD', 'fetch', '--no-tags', '--deepen=1000', 'origin', headSha]);
<!-- ai-code-review --> ### 🔴 嚴重|🔮 Mage **位置**:`src/lib/gitrepo.js` 第 145–145 行 **問題描述** 名為 `deepen HEAD` 的補抓策略實際執行 `git fetch ... origin HEAD`;這裡的 `HEAD` 由遠端解析,通常代表遠端預設分支,不是目前 checkout 的 PR head。最小重現為:PR 分支使用 shallow checkout、`--unshallow` 不可用,且共同祖先位於目前 PR head 的淺層邊界之外;此策略只加深預設分支,PR head 歷史仍不完整,最後錯誤回報找不到 merge-base。 **修改建議** 以明確的 PR head ref 或 SHA 補抓目前 head 的歷史;若介面沒有 head ref,可取得目前 HEAD SHA 後用伺服器支援的 refspec fetch,或使用會加深目前設定 refspec 的 fetch 方式,避免把遠端符號 `HEAD` 當成本地目前 HEAD。 **建議寫法** ``` const headSha = gitTrim(cwd, 'rev-parse', 'HEAD'); strategies.push(['deepen PR HEAD', 'fetch', '--no-tags', '--deepen=1000', 'origin', headSha]); ```

🔴 嚴重|🗡️ Assassin

位置src/lib/gitrepo.js 第 274–278 行

問題描述

攻擊者若能讀取 runner 的程序命令列、診斷資訊或失敗例外,就可能取得完整 PAT。此處把 pushToken 直接嵌入遠端 URL,再作為 git push 的命令列參數;推送失敗時,Node/Git 產生的錯誤也可能包含帶憑證的完整指令,並沿呼叫鏈進入頂層錯誤日誌。新增的 PAT 通常權限與效期都高於自動 token,外洩後可被用來竄改存取庫或存取其他資源。

修改建議

不要把 PAT 放進 argv 或遠端 URL。改用僅存在於子程序環境的 GIT_ASKPASS/credential helper,並確保暫時 credential 設定於推送後清除;Git 執行封裝也應攔截錯誤並重新拋出不含命令列、URL、stdout 與 stderr 的安全錯誤。

建議寫法

// 以受限權限的暫存 GIT_ASKPASS 提供帳密,push 參數只保留不含憑證的 HTTPS URL。
// catch 時重新拋出固定訊息,禁止轉送原始 Git 錯誤:
try {
  gitWithAskPass(cwd, pushToken, 'push', remoteUrl, `HEAD:refs/heads/${headRef}`);
} catch {
  throw new Error('Git push 失敗(認證與遠端細節已隱藏)');
}
<!-- ai-code-review --> ### 🔴 嚴重|🗡️ Assassin **位置**:`src/lib/gitrepo.js` 第 274–278 行 **問題描述** 攻擊者若能讀取 runner 的程序命令列、診斷資訊或失敗例外,就可能取得完整 PAT。此處把 `pushToken` 直接嵌入遠端 URL,再作為 `git push` 的命令列參數;推送失敗時,Node/Git 產生的錯誤也可能包含帶憑證的完整指令,並沿呼叫鏈進入頂層錯誤日誌。新增的 PAT 通常權限與效期都高於自動 token,外洩後可被用來竄改存取庫或存取其他資源。 **修改建議** 不要把 PAT 放進 argv 或遠端 URL。改用僅存在於子程序環境的 `GIT_ASKPASS`/credential helper,並確保暫時 credential 設定於推送後清除;Git 執行封裝也應攔截錯誤並重新拋出不含命令列、URL、stdout 與 stderr 的安全錯誤。 **建議寫法** ``` // 以受限權限的暫存 GIT_ASKPASS 提供帳密,push 參數只保留不含憑證的 HTTPS URL。 // catch 時重新拋出固定訊息,禁止轉送原始 Git 錯誤: try { gitWithAskPass(cwd, pushToken, 'push', remoteUrl, `HEAD:refs/heads/${headRef}`); } catch { throw new Error('Git push 失敗(認證與遠端細節已隱藏)'); } ```

🔴 嚴重|🔮 Mage

位置src/lib/gitrepo.js 第 276–276 行

問題描述

提供 pushToken 時仍把 Git HTTP 使用者名稱固定為 ai-review-bot。若 PAT 屬於其他帳號,最小重現為:傳入開發者帳號的有效 PAT 後執行結果推送;伺服器會以錯誤的帳號/PAT 組合驗證,導致 push 失敗,新增的專用 PAT 功能無法運作。

修改建議

新增與 push-token 配對的使用者名稱輸入,或先以該 PAT 查詢實際帳號,再用取得的 login 組成認證 URL;不可假設所有 PAT 都屬於 ai-review-bot

建議寫法

url.username = pushUsername;
url.password = pushToken;
<!-- ai-code-review --> ### 🔴 嚴重|🔮 Mage **位置**:`src/lib/gitrepo.js` 第 276–276 行 **問題描述** 提供 `pushToken` 時仍把 Git HTTP 使用者名稱固定為 `ai-review-bot`。若 PAT 屬於其他帳號,最小重現為:傳入開發者帳號的有效 PAT 後執行結果推送;伺服器會以錯誤的帳號/PAT 組合驗證,導致 push 失敗,新增的專用 PAT 功能無法運作。 **修改建議** 新增與 `push-token` 配對的使用者名稱輸入,或先以該 PAT 查詢實際帳號,再用取得的 login 組成認證 URL;不可假設所有 PAT 都屬於 `ai-review-bot`。 **建議寫法** ``` url.username = pushUsername; url.password = pushToken; ```

🟠 警告|🧰 Leo

位置src/index.js 第 257–307 行

問題描述

流程步驟編號同時硬編碼在 log 字串、區段註解、JSDoc、README 與多個函式庫中。本次只是插入一個步驟,就必須跨大量檔案把「步驟 3~8」全面改號;這種散落的同步責任很容易讓半年後新增或調整流程時產生文件與實際紀錄不一致。

修改建議

程式內改用穩定的語意階段名稱,例如 diffattackdefendpublish,由單一流程定義集中決定顯示順序;JSDoc 以函式或階段名稱互相引用,不再引用易變動的數字。README 的流程圖則由同一份階段資料產生,或只在文件層維護展示編號。

建議寫法

const PHASE = Object.freeze({
  RESOLVE_OLD: '清理舊留言',
  DETECT_TOOL: '偵測工具',
  COLLECT_DIFF: '整理差異',
  ATTACK: '攻擊方審查',
  DEFEND: '防守方裁決',
});

log(PHASE.COLLECT_DIFF, 'INF', `變更檔案 ${allFiles.length} 個…`);
<!-- ai-code-review --> ### 🟠 警告|🧰 Leo **位置**:`src/index.js` 第 257–307 行 **問題描述** 流程步驟編號同時硬編碼在 log 字串、區段註解、JSDoc、README 與多個函式庫中。本次只是插入一個步驟,就必須跨大量檔案把「步驟 3~8」全面改號;這種散落的同步責任很容易讓半年後新增或調整流程時產生文件與實際紀錄不一致。 **修改建議** 程式內改用穩定的語意階段名稱,例如 `diff`、`attack`、`defend`、`publish`,由單一流程定義集中決定顯示順序;JSDoc 以函式或階段名稱互相引用,不再引用易變動的數字。README 的流程圖則由同一份階段資料產生,或只在文件層維護展示編號。 **建議寫法** ``` const PHASE = Object.freeze({ RESOLVE_OLD: '清理舊留言', DETECT_TOOL: '偵測工具', COLLECT_DIFF: '整理差異', ATTACK: '攻擊方審查', DEFEND: '防守方裁決', }); log(PHASE.COLLECT_DIFF, 'INF', `變更檔案 ${allFiles.length} 個…`); ```

🟠 警告| Rogue

位置src/lib/gitrepo.js 第 132–136 行

問題描述

淺層 checkout 一旦首次 merge-base 失敗,就優先執行 git fetch --unshallow origin,可能把大型儲存庫的完整歷史一次下載到 runner;原本只需數百或數千筆 commit 的 merge-base,卻可能膨脹成 O(全庫歷史) 的網路傳輸、磁碟占用與處理時間。

修改建議

先採用有限深度的 base/HEAD 補抓並重試 merge-base,只有多輪漸進加深仍失敗時,才把 --unshallow 當最後手段,避免為一次 diff 浪費整份歷史的 I/O。

建議寫法

const strategies = [
  ['deepen base', 'fetch', '--no-tags', '--deepen=1000', 'origin', `+refs/heads/${baseRef}:refs/remotes/${remoteBase}`],
  ['deepen HEAD', 'fetch', '--no-tags', '--deepen=1000', 'origin', 'HEAD'],
];
if (gitTrim(cwd, 'rev-parse', '--is-shallow-repository') === 'true') {
  strategies.push(['unshallow', 'fetch', '--no-tags', '--unshallow', 'origin']);
}
<!-- ai-code-review --> ### 🟠 警告|⚡ Rogue **位置**:`src/lib/gitrepo.js` 第 132–136 行 **問題描述** 淺層 checkout 一旦首次 `merge-base` 失敗,就優先執行 `git fetch --unshallow origin`,可能把大型儲存庫的完整歷史一次下載到 runner;原本只需數百或數千筆 commit 的 merge-base,卻可能膨脹成 O(全庫歷史) 的網路傳輸、磁碟占用與處理時間。 **修改建議** 先採用有限深度的 base/HEAD 補抓並重試 merge-base,只有多輪漸進加深仍失敗時,才把 `--unshallow` 當最後手段,避免為一次 diff 浪費整份歷史的 I/O。 **建議寫法** ``` const strategies = [ ['deepen base', 'fetch', '--no-tags', '--deepen=1000', 'origin', `+refs/heads/${baseRef}:refs/remotes/${remoteBase}`], ['deepen HEAD', 'fetch', '--no-tags', '--deepen=1000', 'origin', 'HEAD'], ]; if (gitTrim(cwd, 'rev-parse', '--is-shallow-repository') === 'true') { strategies.push(['unshallow', 'fetch', '--no-tags', '--unshallow', 'origin']); } ```

🟠 警告|🧪 Maya

位置src/lib/gitrepo.js 第 252–289 行

問題描述

commitAndPushFindings 新增 pushToken 推送分支,但沒有測試證明:有 PAT 時略過 origin、PAT 推送失敗不會誤用一般 token、空 PAT 時仍維持 origin 失敗後以 token 重試,以及無檔案變更時完全不推送。這直接影響結果 commit 是否能觸發下一輪 CI,是本次變更的核心行為。

修改建議

mock Git 命令並分別測試非空、空字串與未提供 pushToken;精確斷言 push 的呼叫次數、目標 ref 與分支選擇。另保留無 staged diff 的案例,確認回傳 false 且不執行 commit/push。

<!-- ai-code-review --> ### 🟠 警告|🧪 Maya **位置**:`src/lib/gitrepo.js` 第 252–289 行 **問題描述** `commitAndPushFindings` 新增 `pushToken` 推送分支,但沒有測試證明:有 PAT 時略過 `origin`、PAT 推送失敗不會誤用一般 token、空 PAT 時仍維持 origin 失敗後以 `token` 重試,以及無檔案變更時完全不推送。這直接影響結果 commit 是否能觸發下一輪 CI,是本次變更的核心行為。 **修改建議** mock Git 命令並分別測試非空、空字串與未提供 `pushToken`;精確斷言 push 的呼叫次數、目標 ref 與分支選擇。另保留無 staged diff 的案例,確認回傳 `false` 且不執行 commit/push。

🟠 警告|🗡️ Assassin

位置src/lib/review.js 第 64–70 行

問題描述

開啟 ACTIONS_STEP_DEBUG=true 後,攻擊者可讓 AI CLI 回顯 PR diff、提示內容或任意敏感文字,再由此處把 stdout/stderr 寫入 CI log。redactSecrets 只比對少數憑證外觀,無法可靠辨識 PII、短密碼、私鑰片段或不符合既定格式的機密;截斷至 500 字也不是資料防洩措施。只要提交惡意內容誘導 CLI 在失敗前回顯,即可能把存取庫秘密永久留在多人可讀的日誌。

修改建議

CI 日誌即使在 debug 模式也不要輸出 AI CLI 的原始 stdout、stderr 或錯誤訊息;只記錄退出碼、訊號、逾時狀態與隨機診斷 ID。若確實需要內容級診斷,應寫入具存取控制與短保存期限的獨立 artifact,並採 allowlist 結構化欄位,而非依賴正規表示式遮罩。

建議寫法

if (verbose) {
  parts.push('已啟用除錯;為避免洩漏原始碼、PII 或憑證,CLI 輸出仍不寫入日誌');
}
<!-- ai-code-review --> ### 🟠 警告|🗡️ Assassin **位置**:`src/lib/review.js` 第 64–70 行 **問題描述** 開啟 `ACTIONS_STEP_DEBUG=true` 後,攻擊者可讓 AI CLI 回顯 PR diff、提示內容或任意敏感文字,再由此處把 stdout/stderr 寫入 CI log。`redactSecrets` 只比對少數憑證外觀,無法可靠辨識 PII、短密碼、私鑰片段或不符合既定格式的機密;截斷至 500 字也不是資料防洩措施。只要提交惡意內容誘導 CLI 在失敗前回顯,即可能把存取庫秘密永久留在多人可讀的日誌。 **修改建議** CI 日誌即使在 debug 模式也不要輸出 AI CLI 的原始 stdout、stderr 或錯誤訊息;只記錄退出碼、訊號、逾時狀態與隨機診斷 ID。若確實需要內容級診斷,應寫入具存取控制與短保存期限的獨立 artifact,並採 allowlist 結構化欄位,而非依賴正規表示式遮罩。 **建議寫法** ``` if (verbose) { parts.push('已啟用除錯;為避免洩漏原始碼、PII 或憑證,CLI 輸出仍不寫入日誌'); } ```

🔵 建議|🎼 Bard

位置src/index.js 第 195–205 行

問題描述

postComment 這個名字承諾「立即發布留言」,但建問題模式下實際上可能只把內容塞進 issueBuffer,甚至回傳 null。名稱與行為沒有押上同一個韻,呼叫端無法從字面判斷留言究竟已發布或仍在佇列中。

修改建議

改用能涵蓋「發布或暫存」語意的名稱,例如 queueOrPostComment,並讓註解與回傳型別沿用相同詞彙。

建議寫法

const queueOrPostComment = async (body) => {
  if (ctx.createIssue && !issue) {
    issueBuffer.push(body);
    return null;
  }
  if (ctx.createIssue) return gitea.createCommentOnIssue(ctx, issue.number, body);

  const created = await gitea.createIssueComment(ctx, body);
  currentRunCommentIds.add(created.id);
  return created;
};
<!-- ai-code-review --> ### 🔵 建議|🎼 Bard **位置**:`src/index.js` 第 195–205 行 **問題描述** `postComment` 這個名字承諾「立即發布留言」,但建問題模式下實際上可能只把內容塞進 `issueBuffer`,甚至回傳 `null`。名稱與行為沒有押上同一個韻,呼叫端無法從字面判斷留言究竟已發布或仍在佇列中。 **修改建議** 改用能涵蓋「發布或暫存」語意的名稱,例如 `queueOrPostComment`,並讓註解與回傳型別沿用相同詞彙。 **建議寫法** ``` const queueOrPostComment = async (body) => { if (ctx.createIssue && !issue) { issueBuffer.push(body); return null; } if (ctx.createIssue) return gitea.createCommentOnIssue(ctx, issue.number, body); const created = await gitea.createIssueComment(ctx, body); currentRunCommentIds.add(created.id); return created; }; ```

🔵 建議|🎼 Bard

位置src/index.js 第 219–230 行

問題描述

ensureIssueCreated 不只「確保 issue 已建立」,還會依序發布所有暫存留言並清空 buffer;名稱淡化了這段具副作用且可能耗時的工作。此外函式本身沒有檢查既有 issueensure 一詞也暗示了並不存在的冪等性。

修改建議

將名稱改成明確描述完整行為的 createIssueAndFlushBufferedComments;若確實要保留 ensure,則應先檢查 issue 並把 flush 拆成獨立函式。

建議寫法

const createIssueAndFlushBufferedComments = async (labelIds = []) => {
  issue = await gitea.createIssue(ctx, {
    title: ctx.prTitle || `AI Code Review:PR #${ctx.prNumber}`,
    body: templates.issueBody({ prNumber: ctx.prNumber, prBody: ctx.prBody }),
    labels: labelIds,
  });

  for (const body of issueBuffer) {
    await gitea.createCommentOnIssue(ctx, issue.number, body);
  }
  issueBuffer.length = 0;
};
<!-- ai-code-review --> ### 🔵 建議|🎼 Bard **位置**:`src/index.js` 第 219–230 行 **問題描述** `ensureIssueCreated` 不只「確保 issue 已建立」,還會依序發布所有暫存留言並清空 buffer;名稱淡化了這段具副作用且可能耗時的工作。此外函式本身沒有檢查既有 `issue`,`ensure` 一詞也暗示了並不存在的冪等性。 **修改建議** 將名稱改成明確描述完整行為的 `createIssueAndFlushBufferedComments`;若確實要保留 `ensure`,則應先檢查 `issue` 並把 flush 拆成獨立函式。 **建議寫法** ``` const createIssueAndFlushBufferedComments = async (labelIds = []) => { issue = await gitea.createIssue(ctx, { title: ctx.prTitle || `AI Code Review:PR #${ctx.prNumber}`, body: templates.issueBody({ prNumber: ctx.prNumber, prBody: ctx.prBody }), labels: labelIds, }); for (const body of issueBuffer) { await gitea.createCommentOnIssue(ctx, issue.number, body); } issueBuffer.length = 0; }; ```

🔵 建議|🧪 Maya

位置src/lib/gitea.js 第 171–215 行

問題描述

新增的 addLabelsToIssueaddIssueDependency 沒有契約測試驗證 endpoint、HTTP method 與 request body;尤其相依關係的方向由 URL 與 body 決定,一旦參數次序寫反,mock 較粗略的主流程測試也不容易察覺。

修改建議

補 Gitea client 單元測試:空或缺少 labels 時不呼叫 API並回傳 null;有 labels 時送出正確陣列;相依 API 應以 PR 編號置於 URL、追蹤 issue 編號置於 index,並帶入正確 owner/repo。

<!-- ai-code-review --> ### 🔵 建議|🧪 Maya **位置**:`src/lib/gitea.js` 第 171–215 行 **問題描述** 新增的 `addLabelsToIssue` 與 `addIssueDependency` 沒有契約測試驗證 endpoint、HTTP method 與 request body;尤其相依關係的方向由 URL 與 body 決定,一旦參數次序寫反,mock 較粗略的主流程測試也不容易察覺。 **修改建議** 補 Gitea client 單元測試:空或缺少 labels 時不呼叫 API並回傳 `null`;有 labels 時送出正確陣列;相依 API 應以 PR 編號置於 URL、追蹤 issue 編號置於 `index`,並帶入正確 owner/repo。

🔵 建議|🎼 Bard

位置src/lib/review.js 第 75–78 行

問題描述

診斷輸出的 500 字元上限在相鄰兩行重複出現,讀者只能靠猜測理解這是共同政策還是巧合;日後調整時也容易讓 stderr 與 stdout 的節奏走成兩拍。

修改建議

為診斷片段長度建立具名常數,讓限制的用途與一致性一目了然。

建議寫法

const AGENT_DIAGNOSTIC_OUTPUT_LIMIT = 500;

if (stderr) parts.push(`stderr:${stderr.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
if (stdout) parts.push(`stdout:${stdout.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
<!-- ai-code-review --> ### 🔵 建議|🎼 Bard **位置**:`src/lib/review.js` 第 75–78 行 **問題描述** 診斷輸出的 `500` 字元上限在相鄰兩行重複出現,讀者只能靠猜測理解這是共同政策還是巧合;日後調整時也容易讓 stderr 與 stdout 的節奏走成兩拍。 **修改建議** 為診斷片段長度建立具名常數,讓限制的用途與一致性一目了然。 **建議寫法** ``` const AGENT_DIAGNOSTIC_OUTPUT_LIMIT = 500; if (stderr) parts.push(`stderr:${stderr.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`); if (stdout) parts.push(`stdout:${stdout.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`); ```
Owner

🔴 嚴重|🔮 Mage

位置src/lib/gitrepo.js 第 276–276 行

問題描述

提供 pushToken 時仍把 Git HTTP 使用者名稱固定為 ai-review-bot。若 PAT 屬於其他帳號,最小重現為:傳入開發者帳號的有效 PAT 後執行結果推送;伺服器會以錯誤的帳號/PAT 組合驗證,導致 push 失敗,新增的專用 PAT 功能無法運作。

修改建議

新增與 push-token 配對的使用者名稱輸入,或先以該 PAT 查詢實際帳號,再用取得的 login 組成認證 URL;不可假設所有 PAT 都屬於 ai-review-bot

建議寫法

url.username = pushUsername;
url.password = pushToken;

請把這個問題列為誤報

> <!-- ai-code-review --> > ### 🔴 嚴重|🔮 Mage > > **位置**:`src/lib/gitrepo.js` 第 276–276 行 > > **問題描述** > > 提供 `pushToken` 時仍把 Git HTTP 使用者名稱固定為 `ai-review-bot`。若 PAT 屬於其他帳號,最小重現為:傳入開發者帳號的有效 PAT 後執行結果推送;伺服器會以錯誤的帳號/PAT 組合驗證,導致 push 失敗,新增的專用 PAT 功能無法運作。 > > **修改建議** > > 新增與 `push-token` 配對的使用者名稱輸入,或先以該 PAT 查詢實際帳號,再用取得的 login 組成認證 URL;不可假設所有 PAT 都屬於 `ai-review-bot`。 > > **建議寫法** > > ``` > url.username = pushUsername; > url.password = pushToken; > ``` 請把這個問題列為誤報
Member

🤖 AI Code Review Resolve|處理結果

已依嚴重度(🔴 嚴重 → 🟠 警告 → 🔵 建議)逐條處理本 issue 的 11 條問題,結果如下:

# 等級 審查員 位置 結果
1 🔴 嚴重 🔮 Mage src/lib/gitrepo.js 145 已修復:deepen HEAD 改以目前 HEAD 的 commit SHA 補抓(原用遠端符號 HEAD,只會加深遠端預設分支、補不到 PR head 歷史)
2 🔴 嚴重 🗡️ Assassin src/lib/gitrepo.js 274–278 已解決:現行 pushWithCredentialGIT_CONFIG_* 環境變數注入認證、不進 argv,推送目標 URL 不含帳密,且失敗改拋固定訊息
3 🔴 嚴重 🔮 Mage src/lib/gitrepo.js 276 🚫 誤報(依管理員留言指示):Gitea 以 HTTP Basic 密碼欄(token/PAT)認證,使用者名稱欄不影響結果 → 已寫入 exclusions.json
4 🟠 警告 🧰 Leo src/index.js 257–307 ⏭️ 待人工:步驟編號改語意階段涉及跨檔(log/註解/JSDoc/README/多個 lib)大範圍重構與設計取捨
5 🟠 警告 Rogue src/lib/gitrepo.js 132–136 已解決:現行先 deepen base/HEAD,--unshallow 已降為最後手段
6 🟠 警告 🧪 Maya src/lib/gitrepo.js 252–289 ⏭️ 待人工:repo 無測試框架;且原描述的 pushToken 雙軌情境已因重構移除,需重新界定
7 🟠 警告 🗡️ Assassin src/lib/review.js 64–70 ⏭️ 待人工:屬安全 vs 除錯性設計取捨,現行程式碼已明文權衡選擇附遮罩輸出,反轉需維護者裁示
8 🔵 建議 🎼 Bard src/index.js 195–205 已修復:postCommentqueueOrPostComment
9 🔵 建議 🎼 Bard src/index.js 219–230 已修復:ensureIssueCreatedcreateIssueAndFlushBufferedComments
10 🔵 建議 🧪 Maya src/lib/gitea.js 171–215 ⏭️ 待人工:repo 無測試框架,補契約測試屬專案決策
11 🔵 建議 🎼 Bard src/lib/review.js 75–78 已修復:抽出具名常數 AGENT_DIAGNOSTIC_OUTPUT_LIMIT(= 500)

統計

  • 已修復:4 條(F1、F8、F9、F11)
  • 已解決(現行程式碼已符合建議):2 條(F2、F5)
  • 🚫 誤報寫入 exclusions.json1 條(F3)
  • ⏭️ 待人工處理:4 條(F4、F6、F7、F10)

待人工處理項追蹤

4 條待人工處理問題已寫回 findings 追蹤檔 .gitea/ai-review/findings/2026-07-20-18:00:02.json,確保關閉本 issue 後不會遺失待辦:

問題 位置 待人工原因
步驟編號硬編碼 src/index.js 257–307 跨檔大範圍重構+設計取捨
commitAndPushFindings 缺測試 src/lib/gitrepo.js 252–289 repo 無測試框架;原情境已因重構過時
debug 輸出 CLI stdout/stderr src/lib/review.js 64–70 安全 vs 除錯性設計取捨,需維護者裁示
gitea client 缺契約測試 src/lib/gitea.js 171–215 repo 無測試框架,屬專案決策

修復內容已於分支 ai-review-resolve/develop-20260717-185330 提交,隨後對 develop 發 PR。本 issue 問題已全數分流完成(已修復/已解決/誤報/待人工追蹤),隨後關閉本 issue。

## 🤖 AI Code Review Resolve|處理結果 已依嚴重度(🔴 嚴重 → 🟠 警告 → 🔵 建議)逐條處理本 issue 的 11 條問題,結果如下: | # | 等級 | 審查員 | 位置 | 結果 | | --- | --- | --- | --- | --- | | 1 | 🔴 嚴重 | 🔮 Mage | `src/lib/gitrepo.js` 145 | ✅ 已修復:`deepen HEAD` 改以目前 HEAD 的 commit SHA 補抓(原用遠端符號 `HEAD`,只會加深遠端預設分支、補不到 PR head 歷史) | | 2 | 🔴 嚴重 | 🗡️ Assassin | `src/lib/gitrepo.js` 274–278 | ✅ 已解決:現行 `pushWithCredential` 以 `GIT_CONFIG_*` 環境變數注入認證、不進 argv,推送目標 URL 不含帳密,且失敗改拋固定訊息 | | 3 | 🔴 嚴重 | 🔮 Mage | `src/lib/gitrepo.js` 276 | 🚫 誤報(依管理員留言指示):Gitea 以 HTTP Basic 密碼欄(token/PAT)認證,使用者名稱欄不影響結果 → 已寫入 `exclusions.json` | | 4 | 🟠 警告 | 🧰 Leo | `src/index.js` 257–307 | ⏭️ 待人工:步驟編號改語意階段涉及跨檔(log/註解/JSDoc/README/多個 lib)大範圍重構與設計取捨 | | 5 | 🟠 警告 | ⚡ Rogue | `src/lib/gitrepo.js` 132–136 | ✅ 已解決:現行先 deepen base/HEAD,`--unshallow` 已降為最後手段 | | 6 | 🟠 警告 | 🧪 Maya | `src/lib/gitrepo.js` 252–289 | ⏭️ 待人工:repo 無測試框架;且原描述的 pushToken 雙軌情境已因重構移除,需重新界定 | | 7 | 🟠 警告 | 🗡️ Assassin | `src/lib/review.js` 64–70 | ⏭️ 待人工:屬安全 vs 除錯性設計取捨,現行程式碼已明文權衡選擇附遮罩輸出,反轉需維護者裁示 | | 8 | 🔵 建議 | 🎼 Bard | `src/index.js` 195–205 | ✅ 已修復:`postComment` → `queueOrPostComment` | | 9 | 🔵 建議 | 🎼 Bard | `src/index.js` 219–230 | ✅ 已修復:`ensureIssueCreated` → `createIssueAndFlushBufferedComments` | | 10 | 🔵 建議 | 🧪 Maya | `src/lib/gitea.js` 171–215 | ⏭️ 待人工:repo 無測試框架,補契約測試屬專案決策 | | 11 | 🔵 建議 | 🎼 Bard | `src/lib/review.js` 75–78 | ✅ 已修復:抽出具名常數 `AGENT_DIAGNOSTIC_OUTPUT_LIMIT`(= 500) | ### 統計 - ✅ 已修復:**4 條**(F1、F8、F9、F11) - ✅ 已解決(現行程式碼已符合建議):**2 條**(F2、F5) - 🚫 誤報寫入 `exclusions.json`:**1 條**(F3) - ⏭️ 待人工處理:**4 條**(F4、F6、F7、F10) ### 待人工處理項追蹤 4 條待人工處理問題已寫回 findings 追蹤檔 `.gitea/ai-review/findings/2026-07-20-18:00:02.json`,確保關閉本 issue 後不會遺失待辦: | 問題 | 位置 | 待人工原因 | | --- | --- | --- | | 步驟編號硬編碼 | `src/index.js` 257–307 | 跨檔大範圍重構+設計取捨 | | commitAndPushFindings 缺測試 | `src/lib/gitrepo.js` 252–289 | repo 無測試框架;原情境已因重構過時 | | debug 輸出 CLI stdout/stderr | `src/lib/review.js` 64–70 | 安全 vs 除錯性設計取捨,需維護者裁示 | | gitea client 缺契約測試 | `src/lib/gitea.js` 171–215 | repo 無測試框架,屬專案決策 | 修復內容已於分支 `ai-review-resolve/develop-20260717-185330` 提交,隨後對 `develop` 發 PR。本 issue 問題已全數分流完成(已修復/已解決/誤報/待人工追蹤),隨後關閉本 issue。
Sign in to join this conversation.
No labels
3 Participants
Notifications
Due Date
No due date set.
Reference: node-actions/ai-code-review#10