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

Open
opened 2026-07-21 09:25:38 +00:00 by admin · 10 comments
Owner

變更摘要

本分支持續處理 AI Code Review findings,最新推送新增以下修正:

  • 依 issue #29 人工裁示,將 commitFindings fail-open 相關嚴重 finding 寫入 exclusions.json;現行主流程已在 failure 結果 commit 未成功時直接回傳 1。
  • 新增正式 src/lib/gitref.js 模組匯出 assertSafeBranchRef,移除 gitrepo.__test 測試出口。
  • 精簡 pushWithCredential 的 JSDoc,保留關鍵安全不變式。
  • 統一 action.yml 中文敘述中的全形斜線標點。
  • 回寫 findings wrapper:本輪移除 4 筆已解決或已排除 findings;目前剩餘 30 筆,皆為警告/建議。

影響範圍

  • src/lib/gitref.js / src/lib/gitrepo.js:分支 ref 驗證模組邊界與推送文件整理。
  • test/gitrepo.test.js:改測正式 gitref API。
  • action.yml:中文標點一致性。
  • .gitea/ai-review/exclusions.json / .gitea/ai-review/findings/*.json:排除事項與 findings 狀態回寫。

驗證

  • npm test 通過:11 tests / 0 failed。
  • git diff --check 通過。

風險與注意事項

  • 剩餘 findings 未在本輪硬改,包含 README 深連結、手動時間戳、merge-base / push 流程測試與較大的主流程抽象拆分。

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

<!-- ai-code-review --> ## 變更摘要 本分支持續處理 AI Code Review findings,最新推送新增以下修正: - 依 issue #29 人工裁示,將 `commitFindings` fail-open 相關嚴重 finding 寫入 `exclusions.json`;現行主流程已在 failure 結果 commit 未成功時直接回傳 1。 - 新增正式 `src/lib/gitref.js` 模組匯出 `assertSafeBranchRef`,移除 `gitrepo.__test` 測試出口。 - 精簡 `pushWithCredential` 的 JSDoc,保留關鍵安全不變式。 - 統一 `action.yml` 中文敘述中的全形斜線標點。 - 回寫 findings wrapper:本輪移除 4 筆已解決或已排除 findings;目前剩餘 30 筆,皆為警告/建議。 ## 影響範圍 - `src/lib/gitref.js` / `src/lib/gitrepo.js`:分支 ref 驗證模組邊界與推送文件整理。 - `test/gitrepo.test.js`:改測正式 `gitref` API。 - `action.yml`:中文標點一致性。 - `.gitea/ai-review/exclusions.json` / `.gitea/ai-review/findings/*.json`:排除事項與 findings 狀態回寫。 ## 驗證 - `npm test` 通過:11 tests / 0 failed。 - `git diff --check` 通過。 ## 風險與注意事項 - 剩餘 findings 未在本輪硬改,包含 README 深連結、手動時間戳、merge-base / push 流程測試與較大的主流程抽象拆分。 --- > 本問題由 AI Code Review 依 PR #6 的審查結果自動建立,問題明細見下方留言。
Author
Owner

🤖 AI Code Review|審查工具

項目 內容
工具 codex
版本 codex-cli 0.144.6
模型 gpt-5.5
審查 commit 44c33b6c1ecb66ad56423a49142d54397d6cf5fd
Run Job #79
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` | | 模型 | `gpt-5.5` | | 審查 commit | `44c33b6c1ecb66ad56423a49142d54397d6cf5fd` | | Run Job | [#79](https://gitea.jsc.idv.tw/node-actions/ai-code-review/actions/runs/1648) | ```mermaid flowchart LR A[整理 git diff] --> B[⚔️ 攻擊方找問題] B --> C[🛡️ 防守方裁決] C --> D[保存 findings] D --> E[留言到 PR] ```
Author
Owner

📋 變更摘要(送審 git diff)

檔案 用途 git diff 長度 最後更新時間
action.yml 定義 Action 輸入與執行入口 86 行/3883 字元 2026/07/21 17:21:57
package.json 管理套件資訊與測試指令 15 行/417 字元 2026/07/21 13:46:43
readme.md 說明 Action 用法與流程 280 行/23916 字元(過長截斷送審) 2026/07/21 17:21:57
src/index.js 編排 AI 審查主流程 427 行/19153 字元(過長截斷送審) 2026/07/21 17:21:57
src/lib/agents.js 偵測並執行 AI CLI 工具 14 行/503 字元 2026/07/20 09:46:12
src/lib/context.js 載入 workflow 與輸入脈絡 15 行/769 字元 2026/07/20 17:24:18
src/lib/diagnostics.js 整理並遮罩失敗診斷 89 行/3096 字元 2026/07/21 15:57:53
src/lib/gitea.js 封裝 Gitea API 操作 130 行/5824 字元 2026/07/21 14:39:42
src/lib/gitref.js 驗證安全 Git 分支名稱 39 行/1098 字元 2026/07/21 16:26:30
src/lib/gitrepo.js 封裝 Git 差異與推送操作 231 行/9499 字元 2026/07/21 16:26:30
src/lib/review.js 處理審查差異與 findings 589 行/24978 字元(過長截斷送審) 2026/07/21 17:21:57
src/lib/roles.js 載入審查角色設定 14 行/586 字元 2026/07/20 09:46:12
src/lib/templates.js 產生 PR 留言模板 165 行/5978 字元 2026/07/21 14:39:42
test/gitea.test.js 測試 Gitea API 封裝 80 行/2220 字元 2026/07/21 14:39:42
test/gitrepo.test.js 測試 Git ref 與 merge-base 59 行/1605 字元 2026/07/21 17:21:57
test/review.test.js 測試審查與診斷邏輯 167 行/5050 字元 2026/07/21 17:21:57

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

<!-- ai-code-review --> ## 📋 變更摘要(送審 git diff) | 檔案 | 用途 | git diff 長度 | 最後更新時間 | | --- | --- | --- | --- | | `action.yml` | 定義 Action 輸入與執行入口 | 86 行/3883 字元 | 2026/07/21 17:21:57 | | `package.json` | 管理套件資訊與測試指令 | 15 行/417 字元 | 2026/07/21 13:46:43 | | `readme.md` | 說明 Action 用法與流程 | 280 行/23916 字元(過長截斷送審) | 2026/07/21 17:21:57 | | `src/index.js` | 編排 AI 審查主流程 | 427 行/19153 字元(過長截斷送審) | 2026/07/21 17:21:57 | | `src/lib/agents.js` | 偵測並執行 AI CLI 工具 | 14 行/503 字元 | 2026/07/20 09:46:12 | | `src/lib/context.js` | 載入 workflow 與輸入脈絡 | 15 行/769 字元 | 2026/07/20 17:24:18 | | `src/lib/diagnostics.js` | 整理並遮罩失敗診斷 | 89 行/3096 字元 | 2026/07/21 15:57:53 | | `src/lib/gitea.js` | 封裝 Gitea API 操作 | 130 行/5824 字元 | 2026/07/21 14:39:42 | | `src/lib/gitref.js` | 驗證安全 Git 分支名稱 | 39 行/1098 字元 | 2026/07/21 16:26:30 | | `src/lib/gitrepo.js` | 封裝 Git 差異與推送操作 | 231 行/9499 字元 | 2026/07/21 16:26:30 | | `src/lib/review.js` | 處理審查差異與 findings | 589 行/24978 字元(過長截斷送審) | 2026/07/21 17:21:57 | | `src/lib/roles.js` | 載入審查角色設定 | 14 行/586 字元 | 2026/07/20 09:46:12 | | `src/lib/templates.js` | 產生 PR 留言模板 | 165 行/5978 字元 | 2026/07/21 14:39:42 | | `test/gitea.test.js` | 測試 Gitea API 封裝 | 80 行/2220 字元 | 2026/07/21 14:39:42 | | `test/gitrepo.test.js` | 測試 Git ref 與 merge-base | 59 行/1605 字元 | 2026/07/21 17:21:57 | | `test/review.test.js` | 測試審查與診斷邏輯 | 167 行/5050 字元 | 2026/07/21 17:21:57 | > 共 16 個檔案納入審查;另有 14 個檔案依 `.reviewignore` 排除。
Author
Owner

⚔️ 攻擊方登場

角色 面向 個性
🗡️ 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 週期與記憶體,凡事先問「這能不能更快、更省」 |
Author
Owner

🛡️ 防守方登場

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

🟠 警告|🧰 Leo

位置src/index.js 第 72–115 行

問題描述

commitFindings() 的回傳值把「無實際變更」與「commit/push 失敗」都壓成 false。這個 API 六個月後很容易被誤用:呼叫端看到 boolean 只能猜是正常 no-op 還是遠端寫回失敗,後續 shouldFailMissingResultCommit() 也必須靠外部條件再推論,維護成本會隨流程分支增加。

修改建議

改回傳具名狀態,例如 { status: 'pushed' | 'unchanged' | 'failed', error },讓呼叫端直接依狀態決定是否阻擋 workflow,也讓日誌與測試能明確覆蓋每種情境。

建議寫法

function commitFindings(...) {
  try {
    const committed = gitrepo.commitAndPushFindings(...);
    return committed ? { status: 'pushed' } : { status: 'unchanged' };
  } catch (err) {
    log('收尾', 'WRN', `commit/push 審查結果檔失敗:${err.message}。`);
    return { status: 'failed', error: err };
  }
}
<!-- ai-code-review --> ### 🟠 警告|🧰 Leo **位置**:`src/index.js` 第 72–115 行 **問題描述** `commitFindings()` 的回傳值把「無實際變更」與「commit/push 失敗」都壓成 `false`。這個 API 六個月後很容易被誤用:呼叫端看到 boolean 只能猜是正常 no-op 還是遠端寫回失敗,後續 `shouldFailMissingResultCommit()` 也必須靠外部條件再推論,維護成本會隨流程分支增加。 **修改建議** 改回傳具名狀態,例如 `{ status: 'pushed' | 'unchanged' | 'failed', error }`,讓呼叫端直接依狀態決定是否阻擋 workflow,也讓日誌與測試能明確覆蓋每種情境。 **建議寫法** ``` function commitFindings(...) { try { const committed = gitrepo.commitAndPushFindings(...); return committed ? { status: 'pushed' } : { status: 'unchanged' }; } catch (err) { log('收尾', 'WRN', `commit/push 審查結果檔失敗:${err.message}。`); return { status: 'failed', error: err }; } } ```
Author
Owner

🟠 警告|🧪 Maya

位置src/index.js 第 107–117 行

問題描述

commitFindings 現在把 commit/push 失敗轉成 false,而主流程文件也宣告「需要推送結果檔卻未成功推送」要 exit 1;但測試只驗證了 shouldFailMissingResultCommit 這個純 helper,沒有驗證 main() 真的會把 commitFindingsfalse 接成失敗 exit code。這條結果提交失敗路徑若接錯,workflow 可能仍靜默通過。

修改建議

補主流程層級測試:stub gitrepo.commitAndPushFindingscommitFindings 讓它回傳 false,並建構「有 filesToCommit」的情境,斷言 main() 回傳 1;同時補 filesToCommit=[] 的建問題模式無保留問題情境,斷言不會因沒有 commit 而失敗。

<!-- ai-code-review --> ### 🟠 警告|🧪 Maya **位置**:`src/index.js` 第 107–117 行 **問題描述** `commitFindings` 現在把 commit/push 失敗轉成 `false`,而主流程文件也宣告「需要推送結果檔卻未成功推送」要 exit 1;但測試只驗證了 `shouldFailMissingResultCommit` 這個純 helper,沒有驗證 `main()` 真的會把 `commitFindings` 的 `false` 接成失敗 exit code。這條結果提交失敗路徑若接錯,workflow 可能仍靜默通過。 **修改建議** 補主流程層級測試:stub `gitrepo.commitAndPushFindings` 或 `commitFindings` 讓它回傳 `false`,並建構「有 filesToCommit」的情境,斷言 `main()` 回傳 `1`;同時補 `filesToCommit=[]` 的建問題模式無保留問題情境,斷言不會因沒有 commit 而失敗。
Author
Owner

🟠 警告|🔮 Mage

位置src/index.js 第 213–216 行

問題描述

建問題模式建立追蹤 issue 後,openTrackingIssue 逐則寫入暫存情境留言時會一邊成功一邊 shift()。最小重現:pendingIssueCommentBodies = [工具留言, diff留言, 角色留言],issue 建立成功、第一則留言成功後第二則 API 失敗;catch 會改走 fallbackToPrComments(),但第一則已被移出 buffer,只留在一個不再被連結的半成品 issue,PR fallback 也少了該則留言。這會讓降級流程的輸出不完整,且留下孤立 issue 副作用。

修改建議

在所有暫存留言都成功寫入後才清空 buffer;失敗時保留完整 buffer 讓 fallback 能完整回貼 PR。若已建立 issue 但寫入失敗,也應明確標記或連結該 issue,避免留下無人知道的半成品。

建議寫法

const openTrackingIssue = async (labelIds = []) => {
  trackingIssue = await gitea.createIssue(ctx, {
    title: ctx.prTitle || `AI Code Review:PR #${ctx.prNumber}`,
    body: templates.issueBody({ prNumber: ctx.prNumber, prBody: ctx.prBody }),
    labels: labelIds,
  });
  log('建問題', 'INF', `已建立追蹤 issue #${trackingIssue.number},寫入 ${pendingIssueCommentBodies.length} 則情境留言。`);
  for (const body of pendingIssueCommentBodies) {
    await gitea.createCommentOnIssue(ctx, trackingIssue.number, body);
  }
  pendingIssueCommentBodies.length = 0;
};
<!-- ai-code-review --> ### 🟠 警告|🔮 Mage **位置**:`src/index.js` 第 213–216 行 **問題描述** 建問題模式建立追蹤 issue 後,`openTrackingIssue` 逐則寫入暫存情境留言時會一邊成功一邊 `shift()`。最小重現:`pendingIssueCommentBodies = [工具留言, diff留言, 角色留言]`,issue 建立成功、第一則留言成功後第二則 API 失敗;catch 會改走 `fallbackToPrComments()`,但第一則已被移出 buffer,只留在一個不再被連結的半成品 issue,PR fallback 也少了該則留言。這會讓降級流程的輸出不完整,且留下孤立 issue 副作用。 **修改建議** 在所有暫存留言都成功寫入後才清空 buffer;失敗時保留完整 buffer 讓 fallback 能完整回貼 PR。若已建立 issue 但寫入失敗,也應明確標記或連結該 issue,避免留下無人知道的半成品。 **建議寫法** ``` const openTrackingIssue = async (labelIds = []) => { trackingIssue = await gitea.createIssue(ctx, { title: ctx.prTitle || `AI Code Review:PR #${ctx.prNumber}`, body: templates.issueBody({ prNumber: ctx.prNumber, prBody: ctx.prBody }), labels: labelIds, }); log('建問題', 'INF', `已建立追蹤 issue #${trackingIssue.number},寫入 ${pendingIssueCommentBodies.length} 則情境留言。`); for (const body of pendingIssueCommentBodies) { await gitea.createCommentOnIssue(ctx, trackingIssue.number, body); } pendingIssueCommentBodies.length = 0; }; ```
Author
Owner

🟠 警告|🔮 Mage

位置src/lib/review.js 第 574–575 行

問題描述

resultFilesToCommit 在建問題模式只於 severeCount > 0 時提交 findings。最小重現:create-issue=true、本輪只有「警告」或「建議」finding、exclusionsChanged=false。主流程會建立追蹤 issue,但 filesToCommit 會是空陣列,因此不會產生帶 [success] 的結果 commit;下一次 workflow 沒有步驟 1 的快速回報標記,會重新完整審查並可能再建立一個內容相同的追蹤 issue。

修改建議

建問題模式只要本輪已建立追蹤 issue 或有保留 findings,就應寫回一個可供下輪辨識的結果標記。若不想把非嚴重 findings 進版控,至少提交一個最小狀態檔;最直接的修法是讓函式接收 keptCount,有任何保留 finding 時都提交本輪 findings。

建議寫法

function resultFilesToCommit({ createIssue, keptCount, relativePath, exclusionsChanged }) {
  const files = createIssue ? (keptCount > 0 ? [relativePath] : []) : [relativePath];
  if (exclusionsChanged) {
    files.push(path.join('.gitea', 'ai-review', 'exclusions.json'));
  }
  return files;
}
<!-- ai-code-review --> ### 🟠 警告|🔮 Mage **位置**:`src/lib/review.js` 第 574–575 行 **問題描述** `resultFilesToCommit` 在建問題模式只於 `severeCount > 0` 時提交 findings。最小重現:`create-issue=true`、本輪只有「警告」或「建議」finding、`exclusionsChanged=false`。主流程會建立追蹤 issue,但 `filesToCommit` 會是空陣列,因此不會產生帶 `[success]` 的結果 commit;下一次 workflow 沒有步驟 1 的快速回報標記,會重新完整審查並可能再建立一個內容相同的追蹤 issue。 **修改建議** 建問題模式只要本輪已建立追蹤 issue 或有保留 findings,就應寫回一個可供下輪辨識的結果標記。若不想把非嚴重 findings 進版控,至少提交一個最小狀態檔;最直接的修法是讓函式接收 `keptCount`,有任何保留 finding 時都提交本輪 findings。 **建議寫法** ``` function resultFilesToCommit({ createIssue, keptCount, relativePath, exclusionsChanged }) { const files = createIssue ? (keptCount > 0 ? [relativePath] : []) : [relativePath]; if (exclusionsChanged) { files.push(path.join('.gitea', 'ai-review', 'exclusions.json')); } return files; } ```
Author
Owner

🔵 建議|🎼 Bard

位置src/lib/gitea.js 第 159–161 行

問題描述

註解提到 createIssueAndFlushBufferedComments,但本次新增的實際閉包名稱是 openTrackingIssue。文件與樂譜上的主旋律不同調,讀者循著函式名回頭找脈絡時會撲空。

修改建議

把 JSDoc 中的函式名稱改成實際存在的 openTrackingIssue,或若想強調語意,請同步調整實作命名,避免文件與程式碼各唱各的。

<!-- ai-code-review --> ### 🔵 建議|🎼 Bard **位置**:`src/lib/gitea.js` 第 159–161 行 **問題描述** 註解提到 `createIssueAndFlushBufferedComments`,但本次新增的實際閉包名稱是 `openTrackingIssue`。文件與樂譜上的主旋律不同調,讀者循著函式名回頭找脈絡時會撲空。 **修改建議** 把 JSDoc 中的函式名稱改成實際存在的 `openTrackingIssue`,或若想強調語意,請同步調整實作命名,避免文件與程式碼各唱各的。
Author
Owner

🔵 建議|🎼 Bard

位置src/lib/templates.js 第 305–307 行

問題描述

issueBody 的使用情境同樣引用了不存在的 createIssueAndFlushBufferedComments,但實作裡負責建立 issue 並沖掉暫存留言的是 openTrackingIssue。這種過期命名像錯拍的註腳,會削弱註解可信度。

修改建議

統一使用實際函式名稱;若未來想保留「flush buffered comments」這個語意,可將 openTrackingIssue 重新命名成相同概念,讓文件與程式碼保持押韻。

<!-- ai-code-review --> ### 🔵 建議|🎼 Bard **位置**:`src/lib/templates.js` 第 305–307 行 **問題描述** `issueBody` 的使用情境同樣引用了不存在的 `createIssueAndFlushBufferedComments`,但實作裡負責建立 issue 並沖掉暫存留言的是 `openTrackingIssue`。這種過期命名像錯拍的註腳,會削弱註解可信度。 **修改建議** 統一使用實際函式名稱;若未來想保留「flush buffered comments」這個語意,可將 `openTrackingIssue` 重新命名成相同概念,讓文件與程式碼保持押韻。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: node-actions/ai-code-review#31