feat: AI Pull Request action — 以 opencode 自動產生並建立 PR #1

Closed
jiantw83 wants to merge 10 commits from ai-review-resolve/develop-20260626-114117 into develop
Member

變更摘要

將原本的 Docker Action 範本改寫為「AI Pull Request」action:抓取來源/目標分支的差異,呼叫 opencode 將 git diff 總結成 PR 標題與描述,並透過 Gitea token 自動建立 Pull Request;偵測到合併衝突時改建解衝突分支並開對應 PR。

重點變更

  • Dockerfile:基底由 alpine 換成 node:20-bookworm-slim,安裝 gitbashca-certificatescurl,全域安裝 opencode-ai CLI,並複製 Node.js 應用程式(app/)。
  • action.yaml:重新定義 inputs(source_branchtarget_branchopencode_base_urlopencode_modelopencode_provider);GITEA_TOKEN 改為固定由 gitea.token 注入,不再經 inputs。
  • entrypoint.sh:加上 set -euo pipefail,啟動訊息改為來源/目標分支,進入點改為 node /app/index.js
  • app/:新增 Node.js 應用程式:
    • index.js:主流程(計算差異 → opencode 摘要 → 衝突偵測 → 建立 PR/解衝突 PR)。
    • lib/inputs.js:讀取/驗證環境變數;PR 語言固定繁體中文、diff 截斷上限固定,皆不透過參數控制。
    • lib/git.jslib/gitea.jslib/opencode.jslib/util.js:git 操作、Gitea API、opencode 呼叫與共用工具。
  • README.md:新增 action 功能、輸入參數與使用範例說明。

影響範圍與注意事項

  • 此 action 不再對外暴露 opencode_api_keylanguagemax_diff_chars 參數,也不輸出 outputs。
  • GITEA_TOKEN 需由 Gitea Actions 自動注入的 gitea.token 提供。
  • opencode 不可用或解析失敗時,會自動以 commit 訊息與檔案統計產生 fallback 標題/描述。

備註:本次 .gitea/ai-review/findings.json 不存在,無 AI review findings 需處理;此 PR 僅將工作區既有變更分類提交後送出。

## 變更摘要 將原本的 Docker Action 範本改寫為「AI Pull Request」action:抓取來源/目標分支的差異,呼叫 [opencode](https://opencode.ai) 將 git diff 總結成 PR 標題與描述,並透過 Gitea token 自動建立 Pull Request;偵測到合併衝突時改建解衝突分支並開對應 PR。 ## 重點變更 - **`Dockerfile`**:基底由 `alpine` 換成 `node:20-bookworm-slim`,安裝 `git`/`bash`/`ca-certificates`/`curl`,全域安裝 `opencode-ai` CLI,並複製 Node.js 應用程式(`app/`)。 - **`action.yaml`**:重新定義 inputs(`source_branch`/`target_branch`/`opencode_base_url`/`opencode_model`/`opencode_provider`);`GITEA_TOKEN` 改為固定由 `gitea.token` 注入,不再經 inputs。 - **`entrypoint.sh`**:加上 `set -euo pipefail`,啟動訊息改為來源/目標分支,進入點改為 `node /app/index.js`。 - **`app/`**:新增 Node.js 應用程式: - `index.js`:主流程(計算差異 → opencode 摘要 → 衝突偵測 → 建立 PR/解衝突 PR)。 - `lib/inputs.js`:讀取/驗證環境變數;PR 語言固定繁體中文、diff 截斷上限固定,皆不透過參數控制。 - `lib/git.js`、`lib/gitea.js`、`lib/opencode.js`、`lib/util.js`:git 操作、Gitea API、opencode 呼叫與共用工具。 - **`README.md`**:新增 action 功能、輸入參數與使用範例說明。 ## 影響範圍與注意事項 - 此 action 不再對外暴露 `opencode_api_key`、`language`、`max_diff_chars` 參數,也不輸出 outputs。 - `GITEA_TOKEN` 需由 Gitea Actions 自動注入的 `gitea.token` 提供。 - opencode 不可用或解析失敗時,會自動以 commit 訊息與檔案統計產生 fallback 標題/描述。 --- 備註:本次 `.gitea/ai-review/findings.json` 不存在,無 AI review findings 需處理;此 PR 僅將工作區既有變更分類提交後送出。
jiantw83 added 2 commits 2026-06-26 03:42:44 +00:00
jiantw83 added 1 commit 2026-06-26 04:17:01 +00:00
fix: ci&cd
CI / AI Code Review (pull_request) Failing after 41s
c6823fa0a7

🤖 AI Code Review 團隊

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

🔍 服務:opencode 模型:gemini-2.5-flash

## 🤖 AI Code Review 團隊 | 👤 角色 | 🎯 面向 | 🧠 個性 | |--------|--------|--------| | **🗡️ Assassin** | security | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | **🎼 Bard** | style | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | **🧰 Leo** | maintainability | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | **🔮 Mage** | logic | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | **🧪 Maya** | testing | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | **⚡ Rogue** | efficiency | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 | > 🔍 服務:opencode 模型:gemini-2.5-flash
gitea-actions bot reviewed 2026-06-26 04:17:41 +00:00
gitea-actions bot left a comment

AI Code Review 統計

類型 🔴 嚴重 🟡 警告 🔵 建議 無法標示
新問題 4 筆 7 筆 4 筆 0 筆
舊問題 0 筆 0 筆 0 筆 0 筆

🤖 AI 助理使用量

本次審查(opencode / gemini-2.5-flash,共 23 次呼叫)

提示 token 回應 token 合計
168,595 6,516 344,451

剩餘可用

剩餘可用:無法計算百分比(自架服務,無帳號額度概念)

## AI Code Review 統計 | 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 | | --- | --- | --- | --- | --- | | 新問題 | 4 筆 | 7 筆 | 4 筆 | 0 筆 | | 舊問題 | 0 筆 | 0 筆 | 0 筆 | 0 筆 | ## 🤖 AI 助理使用量 **本次審查**(opencode / gemini-2.5-flash,共 23 次呼叫) | 提示 token | 回應 token | 合計 | | --- | --- | --- | | 168,595 | 6,516 | 344,451 | **剩餘可用** 剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -0,0 +55,4 @@
source: inputs.sourceBranch,
target: inputs.targetBranch,
commitMessages,
diffStat,

嚴重等級🔴 嚴重
審查員:Maya
問題:在偵測到衝突並建立解衝突分支後,程式雖然嘗試透過 git.createResolveBranch 建立並 commit 衝突檔案,但後續缺乏邏輯處理衝突檔案,亦無測試驗證「自動解衝突分支是否真的被建立」以及「提交的內容是否正確」。
建議:應補上整合測試,模擬合併衝突,驗證 detectConflict 能偵測衝突,且 createResolveBranch 產生的分支確實包含預期的衝突檔案與 commit。

**嚴重等級**:🔴 嚴重 **審查員**:Maya **問題**:在偵測到衝突並建立解衝突分支後,程式雖然嘗試透過 `git.createResolveBranch` 建立並 commit 衝突檔案,但後續缺乏邏輯處理衝突檔案,亦無測試驗證「自動解衝突分支是否真的被建立」以及「提交的內容是否正確」。 **建議**:應補上整合測試,模擬合併衝突,驗證 `detectConflict` 能偵測衝突,且 `createResolveBranch` 產生的分支確實包含預期的衝突檔案與 commit。
Ghost marked this conversation as resolved
@@ -0,0 +81,4 @@
log.warn(`偵測到衝突檔案 (${files.length}): ${files.join(', ')}`);
const resolveBranch = buildResolveBranchName(inputs.targetBranch, inputs.sourceBranch);
log.step('建立解衝突分支並合併來源分支');

嚴重等級🟡 警告
審查員:Mage
問題:在 buildResolveBranchName 中,處理衝突分支名稱時,雖然使用了 safe 函數替換特殊字元,但如果分支名稱過長,加上 suffix(runId)可能導致分支名稱過長而超出 Git 對 branch 名稱長度的極限(雖然通常很大,但這是不必要的風險)。
建議:建議對 resolveBranch 的總長度進行截斷,確保其不會超過 Git 的建議長度限制。

**嚴重等級**:🟡 警告 **審查員**:Mage **問題**:在 `buildResolveBranchName` 中,處理衝突分支名稱時,雖然使用了 `safe` 函數替換特殊字元,但如果分支名稱過長,加上 `suffix`(runId)可能導致分支名稱過長而超出 Git 對 branch 名稱長度的極限(雖然通常很大,但這是不必要的風險)。 **建議**:建議對 `resolveBranch` 的總長度進行截斷,確保其不會超過 Git 的建議長度限制。
Ghost marked this conversation as resolved
@@ -0,0 +147,4 @@
}
/** 解衝突 PR 的描述。 */
function buildResolveBody({ source, target, resolveBranch, files, summary }) {

嚴重等級🔵 建議
審查員:Maya
問題:對於 fallbackSummary 函數,當 opencode 產生摘要失敗時會觸發,但目前缺乏測試案例驗證在各種輸入下,fallback 的結果是否符合預期格式。
建議:補上單元測試,驗證 fallbackSummary 在不同輸入下(如為空、多行訊息、stat 為空)產生的標題與描述格式是否正確。

**嚴重等級**:🔵 建議 **審查員**:Maya **問題**:對於 `fallbackSummary` 函數,當 opencode 產生摘要失敗時會觸發,但目前缺乏測試案例驗證在各種輸入下,fallback 的結果是否符合預期格式。 **建議**:補上單元測試,驗證 `fallbackSummary` 在不同輸入下(如為空、多行訊息、stat 為空)產生的標題與描述格式是否正確。
Ghost marked this conversation as resolved
@@ -0,0 +177,4 @@
} else {
log.info(`PR 已存在 #${number}: ${url}`);
}
}

嚴重等級🟡 警告
審查員:Assassin
問題:在 main 函數的 catch 區塊中,直接將 err.stack 輸出到標準錯誤流(log.error)。如果錯誤物件中包含了敏感資訊(如 token、API 參數),這些機密將被寫入到 CI/CD 的執行日誌中,極易洩漏。
建議:在輸出 err.stack 前,必須使用類似 maskSecrets 的函式,過濾掉所有可能的機密資訊。

**嚴重等級**:🟡 警告 **審查員**:Assassin **問題**:在 `main` 函數的 catch 區塊中,直接將 `err.stack` 輸出到標準錯誤流(log.error)。如果錯誤物件中包含了敏感資訊(如 token、API 參數),這些機密將被寫入到 CI/CD 的執行日誌中,極易洩漏。 **建議**:在輸出 `err.stack` 前,必須使用類似 `maskSecrets` 的函式,過濾掉所有可能的機密資訊。
Ghost marked this conversation as resolved
@@ -0,0 +49,4 @@
*
* @param {string[]} branches
*/
fetchBranches(branches) {

嚴重等級🟡 警告
審查員:Assassin
問題:Git 的 --no-tags 參數在某些舊版 git 可能無法完全防禦標籤帶來的惡意遠端物件下載。雖然 fetchBranches 限制了 refspec,但使用不可信的 remoteUrl 進行 fetch 操作時,仍存在與 git 協定漏洞相關的風險。
建議:建議確保容器內的 git 版本為最新,並考慮在 fetch 前驗證 remoteUrl 是否為預期的 Gitea 網域,而非任意使用者輸入的網址。

**嚴重等級**:🟡 警告 **審查員**:Assassin **問題**:Git 的 `--no-tags` 參數在某些舊版 git 可能無法完全防禦標籤帶來的惡意遠端物件下載。雖然 `fetchBranches` 限制了 refspec,但使用不可信的 `remoteUrl` 進行 fetch 操作時,仍存在與 git 協定漏洞相關的風險。 **建議**:建議確保容器內的 git 版本為最新,並考慮在 fetch 前驗證 `remoteUrl` 是否為預期的 Gitea 網域,而非任意使用者輸入的網址。
Ghost marked this conversation as resolved
@@ -0,0 +60,4 @@
const result = this._git(['rev-list', '--count', `refs/remotes/pr/${target}..refs/remotes/pr/${source}`]);
return result.status === 0 ? parseInt(result.stdout.trim(), 10) || 0 : 0;
}

嚴重等級🟡 警告
審查員:Rogue
問題:在 getCommitMessages 中使用 git log --max-count=50,若專案歷史悠久,此數量可能不足以產生精確的 AI 摘要,且若取得數量過多則浪費處理資源。
建議:評估實際使用場景調整 limit,或改用時間區間(例如 --since)來抓取相關變更。

**嚴重等級**:🟡 警告 **審查員**:Rogue **問題**:在 `getCommitMessages` 中使用 `git log --max-count=50`,若專案歷史悠久,此數量可能不足以產生精確的 AI 摘要,且若取得數量過多則浪費處理資源。 **建議**:評估實際使用場景調整 `limit`,或改用時間區間(例如 `--since`)來抓取相關變更。
Ghost marked this conversation as resolved
@@ -0,0 +92,4 @@
}
/**
* 偵測 source 合併進 target 是否會衝突不會留下任何變更

嚴重等級🔵 建議
審查員:Rogue
問題detectConflict 函式透過 git merge --no-commit --no-ff 進行完整合併測試,極其耗時且佔用大量磁碟空間。
建議:考慮改用 git merge-tree (Git 2.29+) 檢查衝突,在不觸碰工作區的情況下快速檢測。

**嚴重等級**:🔵 建議 **審查員**:Rogue **問題**:`detectConflict` 函式透過 `git merge --no-commit --no-ff` 進行完整合併測試,極其耗時且佔用大量磁碟空間。 **建議**:考慮改用 `git merge-tree` (Git 2.29+) 檢查衝突,在不觸碰工作區的情況下快速檢測。
Ghost marked this conversation as resolved
@@ -0,0 +104,4 @@
const merge = this._git(['merge', '--no-commit', '--no-ff', `refs/remotes/pr/${source}`]);
let hasConflict = merge.status !== 0;
let files = [];

嚴重等級🔵 建議
審查員:Leo
問題:detectConflict 使用固定名稱的暫存分支(_conflict_check${target}),若程式意外中斷可能導致分支殘留,下次執行可能引發命名衝突或狀態異常。
建議:建議在分支名稱中加入隨機字串(如 uuid 或時間戳),並確保在 finally 區塊中有強制清理該分支的機制。

**嚴重等級**:🔵 建議 **審查員**:Leo **問題**:detectConflict 使用固定名稱的暫存分支(__conflict_check_${target}),若程式意外中斷可能導致分支殘留,下次執行可能引發命名衝突或狀態異常。 **建議**:建議在分支名稱中加入隨機字串(如 uuid 或時間戳),並確保在 finally 區塊中有強制清理該分支的機制。
Ghost marked this conversation as resolved
@@ -0,0 +151,4 @@
runOrThrow(
'git',
['commit', '--no-verify', '-m', `Merge branch '${source}' into ${resolveBranch}(含衝突標記,待人工解衝突)`],
{ cwd: this.cwd },

嚴重等級🔴 嚴重
審查員:Mage
問題:在 createResolveBranch 中,執行 git add -Agit commit 會強制提交包括未追蹤檔案在內的所有變更,可能污染分支且掩蓋衝突內容,此流程亦缺乏測試。
建議:僅針對衝突檔案(--diff-filter=U)執行 git add,並補上整合測試,驗證模擬衝突時,產生的分支確實包含正確的衝突標記與檔案。

**嚴重等級**:🔴 嚴重 **審查員**:Mage **問題**:在 `createResolveBranch` 中,執行 `git add -A` 與 `git commit` 會強制提交包括未追蹤檔案在內的所有變更,可能污染分支且掩蓋衝突內容,此流程亦缺乏測試。 **建議**:僅針對衝突檔案(`--diff-filter=U`)執行 `git add`,並補上整合測試,驗證模擬衝突時,產生的分支確實包含正確的衝突標記與檔案。
Ghost marked this conversation as resolved
@@ -0,0 +25,4 @@
headers: {
Authorization: `token ${this.token}`,
'Content-Type': 'application/json',
Accept: 'application/json',

嚴重等級🔴 嚴重
審查員:Assassin
問題:Gitea API 請求在 headers 中直接放入了 this.token。雖然這在正常情況下是必要的,但如果 this.token 來源於不可信的輸入且未經嚴格驗證,這將導致 token 洩漏風險(透過請求日誌或中間人攻擊)。
建議:在 GiteaClient 的所有請求方法中增加對 token 的處理,並確保在任何可能將請求細節(包含 headers)輸出到日誌的邏輯中,必須將 token 遮蔽。

**嚴重等級**:🔴 嚴重 **審查員**:Assassin **問題**:Gitea API 請求在 headers 中直接放入了 `this.token`。雖然這在正常情況下是必要的,但如果 `this.token` 來源於不可信的輸入且未經嚴格驗證,這將導致 token 洩漏風險(透過請求日誌或中間人攻擊)。 **建議**:在 `GiteaClient` 的所有請求方法中增加對 token 的處理,並確保在任何可能將請求細節(包含 headers)輸出到日誌的邏輯中,必須將 token 遮蔽。
Ghost marked this conversation as resolved
@@ -0,0 +49,4 @@
*/
async findOpenPull(head, base) {
// Gitea pulls 不直接支援 head/base 過濾,這裡撈開啟中的 PR 自行比對
const { ok, json } = await this._request(

嚴重等級🔴 嚴重
審查員:Mage
問題:1. findOpenPull 方法僅撈取前 50 個 PR,若數量眾多可能導致重複建立。2. _request 進行 GET 操作時,未對 API 回傳的 json 內容結構進行嚴格合法性驗證,可能導致執行時錯誤。
建議:1. 實作分頁(pagination)機制確保完整性。2. 在確保 HTTP 狀態碼為 200 後,強化對 json 的防禦性檢測(如檢查是否為 undefined 或預期陣列)。

**嚴重等級**:🔴 嚴重 **審查員**:Mage **問題**:1. `findOpenPull` 方法僅撈取前 50 個 PR,若數量眾多可能導致重複建立。2. `_request` 進行 `GET` 操作時,未對 API 回傳的 `json` 內容結構進行嚴格合法性驗證,可能導致執行時錯誤。 **建議**:1. 實作分頁(pagination)機制確保完整性。2. 在確保 HTTP 狀態碼為 200 後,強化對 `json` 的防禦性檢測(如檢查是否為 undefined 或預期陣列)。
Ghost marked this conversation as resolved
@@ -0,0 +31,4 @@
*
* @returns {string} config 檔路徑
*/
_writeConfig() {

嚴重等級🟡 警告
審查員:Leo
問題:1. _writeConfig 使用 mkdtempSync 建立暫存目錄後未清理,造成空間堆積。2. summarize 函式重複建立零散設定檔,增加 I/O 與清理負擔。
建議:在程式執行完畢後的 finally 區塊中,統一實作檔案系統清理邏輯(如 fs.rmSync)以刪除暫存目錄與檔案。同時考慮設定檔重用性,減少頻繁的檔案操作。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:1. `_writeConfig` 使用 mkdtempSync 建立暫存目錄後未清理,造成空間堆積。2. `summarize` 函式重複建立零散設定檔,增加 I/O 與清理負擔。 **建議**:在程式執行完畢後的 finally 區塊中,統一實作檔案系統清理邏輯(如 fs.rmSync)以刪除暫存目錄與檔案。同時考慮設定檔重用性,減少頻繁的檔案操作。
Ghost marked this conversation as resolved
@@ -0,0 +103,4 @@
return parsed;
}
}

嚴重等級🟡 警告
審查員:Mage
問題:在 summarize 方法中將 HOME 環境變數硬編碼為 /root,若 Dockerfile 變更使用者,將導致無法寫入設定檔。
建議:建議動態獲取當前環境的使用者家目錄(如使用 os.homedir()),增加相容性。

**嚴重等級**:🟡 警告 **審查員**:Mage **問題**:在 `summarize` 方法中將 `HOME` 環境變數硬編碼為 `/root`,若 Dockerfile 變更使用者,將導致無法寫入設定檔。 **建議**:建議動態獲取當前環境的使用者家目錄(如使用 `os.homedir()`),增加相容性。
Ghost marked this conversation as resolved
@@ -0,0 +173,4 @@
if (escape) {
out += ch;
escape = false;
continue;

嚴重等級🔵 建議
審查員:Maya
問題extractResult 函數處理 JSON 解析與清理邏輯,雖然複雜,但目前沒有單元測試驗證其對「LLM 容易輸出的各種非標準 JSON」的處理能力(例如字串內含未跳脫換行)。
建議:補上單元測試,提供幾種 LLM 常見的「壞」JSON 格式,驗證 extractResult 能否正確解析出 titledescription

**嚴重等級**:🔵 建議 **審查員**:Maya **問題**:`extractResult` 函數處理 JSON 解析與清理邏輯,雖然複雜,但目前沒有單元測試驗證其對「LLM 容易輸出的各種非標準 JSON」的處理能力(例如字串內含未跳脫換行)。 **建議**:補上單元測試,提供幾種 LLM 常見的「壞」JSON 格式,驗證 `extractResult` 能否正確解析出 `title` 與 `description`。
Ghost marked this conversation as resolved
@@ -0,0 +11,4 @@
export function run(command, args = [], options = {}) {
const result = spawnSync(command, args, {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024, // 64MB,避免大型 diff 被截斷

嚴重等級🟡 警告
審查員:Rogue
問題run 函式設定 maxBuffer: 64 * 1024 * 1024 (64MB)。雖然避免了截斷,但如果 git diff 內容極大,這會一次性將大量文字讀入記憶體,極易引發記憶體不足 (OOM) 或過高的 GC 壓力。
建議:改用 stream 方式讀取 git 指令輸出,而非一次性載入 buffer。

**嚴重等級**:🟡 警告 **審查員**:Rogue **問題**:`run` 函式設定 `maxBuffer: 64 * 1024 * 1024` (64MB)。雖然避免了截斷,但如果 `git diff` 內容極大,這會一次性將大量文字讀入記憶體,極易引發記憶體不足 (OOM) 或過高的 GC 壓力。 **建議**:改用 stream 方式讀取 `git` 指令輸出,而非一次性載入 buffer。
Ghost marked this conversation as resolved
gitea-actions bot added 1 commit 2026-06-26 04:17:42 +00:00
jiantw83 added 3 commits 2026-06-26 05:58:55 +00:00

🤖 AI Code Review 團隊

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

🔍 服務:opencode 模型:gemini-2.5-flash

## 🤖 AI Code Review 團隊 | 👤 角色 | 🎯 面向 | 🧠 個性 | |--------|--------|--------| | **🗡️ Assassin** | security | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | **🎼 Bard** | style | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | **🧰 Leo** | maintainability | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | **🔮 Mage** | logic | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | **🧪 Maya** | testing | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | **⚡ Rogue** | efficiency | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 | > 🔍 服務:opencode 模型:gemini-2.5-flash
gitea-actions bot reviewed 2026-06-26 05:59:38 +00:00
gitea-actions bot left a comment

AI Code Review 統計

類型 🔴 嚴重 🟡 警告 🔵 建議 無法標示
新問題 3 筆 7 筆 4 筆 0 筆
舊問題 0 筆 0 筆 0 筆 0 筆

🤖 AI 助理使用量

本次審查(opencode / gemini-2.5-flash,共 24 次呼叫)

提示 token 回應 token 合計
175,572 6,203 381,612

剩餘可用

剩餘可用:無法計算百分比(自架服務,無帳號額度概念)

## AI Code Review 統計 | 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 | | --- | --- | --- | --- | --- | | 新問題 | 3 筆 | 7 筆 | 4 筆 | 0 筆 | | 舊問題 | 0 筆 | 0 筆 | 0 筆 | 0 筆 | ## 🤖 AI 助理使用量 **本次審查**(opencode / gemini-2.5-flash,共 24 次呼叫) | 提示 token | 回應 token | 合計 | | --- | --- | --- | | 175,572 | 6,203 | 381,612 | **剩餘可用** 剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -0,0 +4,4 @@
import { OpenCode } from './lib/opencode.js';
import { log, maskSecrets } from './lib/util.js';
async function main() {

嚴重等級🟡 警告
審查員:Leo
問題:函式 main() 承擔過多責任,違反單一職責原則。
建議:將職責拆解,抽離衝突處理邏輯為 ConflictManager,並封裝 Gitea API 互動。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:函式 `main()` 承擔過多責任,違反單一職責原則。 **建議**:將職責拆解,抽離衝突處理邏輯為 `ConflictManager`,並封裝 Gitea API 互動。
Ghost marked this conversation as resolved
@@ -0,0 +34,4 @@
// 3. 蒐集 diff 內容
log.step('蒐集 git diff');
const commitMessages = git.getCommitMessages(inputs.targetBranch, inputs.sourceBranch);

嚴重等級🔵 建議
審查員:Rogue
問題:在 ahead 為 0 時,仍執行昂貴的 diff 採集與分析。
建議:先執行 countAheadCommits,若 ahead === 0 則直接終止。

**嚴重等級**:🔵 建議 **審查員**:Rogue **問題**:在 `ahead` 為 0 時,仍執行昂貴的 diff 採集與分析。 **建議**:先執行 `countAheadCommits`,若 `ahead === 0` 則直接終止。
Ghost marked this conversation as resolved
@@ -0,0 +147,4 @@
const stem = `${safe(target)}-into-${safe(source)}`.slice(0, 180);
return `resolve-conflict/${stem}${suffix}`;
}

嚴重等級🔵 建議
審查員:Maya
問題:缺乏測試案例驗證 fallbackSummary 的結果是否符合預期格式。
建議:補上單元測試,驗證 fallbackSummary 在不同輸入下的產出格式。

**嚴重等級**:🔵 建議 **審查員**:Maya **問題**:缺乏測試案例驗證 fallbackSummary 的結果是否符合預期格式。 **建議**:補上單元測試,驗證 fallbackSummary 在不同輸入下的產出格式。
Ghost marked this conversation as resolved
@@ -0,0 +180,4 @@
log.info(`PR 已存在 #${number}: ${url}`);
}
}

嚴重等級🟡 警告
審查員:Assassin
問題:錯誤處理中的 maskSecrets 基於字串取代,可能無法處理所有 Token 變體導致敏感資訊洩漏。
建議:確保 maskSecrets 處理所有可能的變體,並在生產環境中禁止輸出原始錯誤物件。

**嚴重等級**:🟡 警告 **審查員**:Assassin **問題**:錯誤處理中的 `maskSecrets` 基於字串取代,可能無法處理所有 Token 變體導致敏感資訊洩漏。 **建議**:確保 `maskSecrets` 處理所有可能的變體,並在生產環境中禁止輸出原始錯誤物件。
Ghost marked this conversation as resolved
@@ -0,0 +103,4 @@
const merge = this._git(['merge', '--no-commit', '--no-ff', `refs/remotes/pr/${source}`]);
let hasConflict = merge.status !== 0;
let files = [];

嚴重等級🔴 嚴重
審查員:Maya
問題:detectConflict 函式在執行 git 合併失敗後的 abort 嘗試若失敗,會導致工作區殘留錯誤狀態,且未經測試驗證。
建議:增加測試案例來模擬 git 合併失敗與 abort 失敗的場景,確保狀態正確復原。

**嚴重等級**:🔴 嚴重 **審查員**:Maya **問題**:detectConflict 函式在執行 git 合併失敗後的 abort 嘗試若失敗,會導致工作區殘留錯誤狀態,且未經測試驗證。 **建議**:增加測試案例來模擬 git 合併失敗與 abort 失敗的場景,確保狀態正確復原。
Ghost marked this conversation as resolved
@@ -0,0 +74,4 @@
const configPath = this._writeConfig();
const prompt = buildPrompt({ ...ctx, language: this.language });
try {

嚴重等級🔵 建議
審查員:Assassin
問題:呼叫外部指令時傳遞整個 process.env,導致敏感環境變數暴露。
建議:應明確篩選並只傳遞必要環境變數。

**嚴重等級**:🔵 建議 **審查員**:Assassin **問題**:呼叫外部指令時傳遞整個 `process.env`,導致敏感環境變數暴露。 **建議**:應明確篩選並只傳遞必要環境變數。
Ghost marked this conversation as resolved
@@ -0,0 +173,4 @@
let inString = false;
let escape = false;
for (let i = 0; i < text.length; i++) {
const ch = text[i];

嚴重等級🔵 建議
審查員:Maya
問題:缺乏單元測試驗證 extractResult 對非標準 JSON 的解析能力。
建議:補上單元測試,驗證 extractResult 能否正確解析壞 JSON。

**嚴重等級**:🔵 建議 **審查員**:Maya **問題**:缺乏單元測試驗證 `extractResult` 對非標準 JSON 的解析能力。 **建議**:補上單元測試,驗證 extractResult 能否正確解析壞 JSON。
Ghost marked this conversation as resolved
gitea-actions bot added 1 commit 2026-06-26 05:59:41 +00:00
jiantw83 added 1 commit 2026-06-26 06:03:54 +00:00
chore(ai-review): 更新 findings 與 exclusions 解決狀態
CI / AI Code Review (pull_request) Failing after 40s
af4bb899e6

🤖 AI Code Review 團隊

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

🔍 服務:opencode 模型:gemini-2.5-flash

## 🤖 AI Code Review 團隊 | 👤 角色 | 🎯 面向 | 🧠 個性 | |--------|--------|--------| | **🗡️ Assassin** | security | 多疑偏執、以攻擊者視角看世界,假設每筆輸入都是惡意的,每個信任都會被濫用 | | **🎼 Bard** | style | 唯美龜毛、追求優雅,把可讀性與一致性當作旋律,最受不了走調的命名與排版 | | **🧰 Leo** | maintainability | 有遠見、重視長期維護成本,凡事先問「六個月後的自己還看得懂嗎?」,討厭把債留給未來 | | **🔮 Mage** | logic | 嚴謹冷靜、滴水不漏,凡事推演到最壞情況,深信「沒驗證過的假設都是 bug」 | | **🧪 Maya** | testing | 對測試覆蓋率有執念,深信「沒有測試的程式碼等於沒寫完」,溫和但堅持,最在意邊界與失敗路徑 | | **⚡ Rogue** | efficiency | 急性子、講求速度,最痛恨被浪費的 CPU 週期與記憶體,凡事先問「這能不能更快、更省」 | > 🔍 服務:opencode 模型:gemini-2.5-flash
admin closed this pull request 2026-06-26 06:04:11 +00:00
admin reopened this pull request 2026-06-26 06:04:28 +00:00
gitea-actions bot reviewed 2026-06-26 06:04:33 +00:00
gitea-actions bot left a comment

AI Code Review 統計

類型 🔴 嚴重 🟡 警告 🔵 建議 無法標示
新問題 3 筆 3 筆 5 筆 0 筆
舊問題 0 筆 1 筆 2 筆 0 筆

🤖 AI 助理使用量

本次審查(opencode / gemini-2.5-flash,共 24 次呼叫)

提示 token 回應 token 合計
168,325 6,104 378,148

剩餘可用

剩餘可用:無法計算百分比(自架服務,無帳號額度概念)

## AI Code Review 統計 | 類型 | 🔴 嚴重 | 🟡 警告 | 🔵 建議 | ⚪ 無法標示 | | --- | --- | --- | --- | --- | | 新問題 | 3 筆 | 3 筆 | 5 筆 | 0 筆 | | 舊問題 | 0 筆 | 1 筆 | 2 筆 | 0 筆 | ## 🤖 AI 助理使用量 **本次審查**(opencode / gemini-2.5-flash,共 24 次呼叫) | 提示 token | 回應 token | 合計 | | --- | --- | --- | | 168,325 | 6,104 | 378,148 | **剩餘可用** 剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -2,1 +13,4 @@
# 應用程式無第三方相依套件,僅在有 package-lock 時安裝
RUN if [ -f /app/package-lock.json ]; then cd /app && npm ci --omit=dev; fi

嚴重等級🔵 建議
審查員:Bard
問題:在 RUN 指令中使用 cd 切換目錄,導致環境隱晦。
建議:使用 WORKDIR /app

**嚴重等級**:🔵 建議 **審查員**:Bard **問題**:在 RUN 指令中使用 cd 切換目錄,導致環境隱晦。 **建議**:使用 `WORKDIR /app`。
@@ -0,0 +74,4 @@
body: summary.description,
});
reportPull(pull, created);
return;

嚴重等級🔵 建議
審查員:Maya
問題:解衝突的 PR 產出缺乏人工檢查機制。
建議:加入「檢查清單(Checklist)」要求人工確認。

**嚴重等級**:🔵 建議 **審查員**:Maya **問題**:解衝突的 PR 產出缺乏人工檢查機制。 **建議**:加入「檢查清單(Checklist)」要求人工確認。
@@ -0,0 +103,4 @@
const merge = this._git(['merge', '--no-commit', '--no-ff', `refs/remotes/pr/${source}`]);
let hasConflict = merge.status !== 0;
let files = [];

嚴重等級🔴 嚴重
審查員:Maya
問題:detectConflict 執行 git 合併失敗後的 abort 嘗試若失敗,會導致工作區殘留錯誤狀態,且未經測試。
建議:增加測試案例模擬 git 合併失敗與 abort 失敗的場景,確保狀態正確復原。

**嚴重等級**:🔴 嚴重 **審查員**:Maya **問題**:detectConflict 執行 git 合併失敗後的 abort 嘗試若失敗,會導致工作區殘留錯誤狀態,且未經測試。 **建議**:增加測試案例模擬 git 合併失敗與 abort 失敗的場景,確保狀態正確復原。
@@ -0,0 +119,4 @@
}
/**
* 建立解衝突分支 target 為基礎合併 source保留衝突標記後 commit

嚴重等級🔵 建議
審查員:Leo
問題:臨時分支名稱可能衝突或殘留。
建議:產生臨時分支名稱時加入 process ID 或隨機字串,並在 finally 區塊清理。

**嚴重等級**:🔵 建議 **審查員**:Leo **問題**:臨時分支名稱可能衝突或殘留。 **建議**:產生臨時分支名稱時加入 process ID 或隨機字串,並在 finally 區塊清理。
@@ -0,0 +142,4 @@
let files = [];
if (merge.status !== 0) {
// 合併產生衝突:將含有衝突標記的檔案標記為已解決後 commit,

嚴重等級🟡 警告
審查員:Maya
問題:合併衝突後未檢查是否存在殘留衝突標記。
建議:在 git add 之後,使用 grep 掃描檔案中是否仍有未處理的衝突標記。

**嚴重等級**:🟡 警告 **審查員**:Maya **問題**:合併衝突後未檢查是否存在殘留衝突標記。 **建議**:在 `git add` 之後,使用 grep 掃描檔案中是否仍有未處理的衝突標記。
@@ -0,0 +37,4 @@
const config = {
$schema: 'https://opencode.ai/config.json',
provider: {

嚴重等級🔵 建議
審查員:Rogue
問題:頻繁寫入讀取 opencode.json 設定檔造成無謂的 I/O。
建議:若支援,透過參數或環境變數傳入配置。

**嚴重等級**:🔵 建議 **審查員**:Rogue **問題**:頻繁寫入讀取 `opencode.json` 設定檔造成無謂的 I/O。 **建議**:若支援,透過參數或環境變數傳入配置。
@@ -0,0 +74,4 @@
const configPath = this._writeConfig();
const prompt = buildPrompt({ ...ctx, language: this.language });
try {

嚴重等級🔵 建議
審查員:Assassin
問題:傳遞整個 process.env 導致敏感環境變數暴露。
建議:明確篩選並只傳遞必要環境變數。

**嚴重等級**:🔵 建議 **審查員**:Assassin **問題**:傳遞整個 `process.env` 導致敏感環境變數暴露。 **建議**:明確篩選並只傳遞必要環境變數。
@@ -0,0 +124,4 @@
`=== Commits ===`,
commitMessages || '(no commit messages)',
``,
`=== Changed files (stat) ===`,

嚴重等級🟡 警告
審查員:Mage
問題summarize 方法中使用 spawnSync 執行指令,未處理退出訊號可能導致清理競態。
建議:明確處理 spawnSync 的退出訊號,並確保清理操作是原子性的。

**嚴重等級**:🟡 警告 **審查員**:Mage **問題**:`summarize` 方法中使用 `spawnSync` 執行指令,未處理退出訊號可能導致清理競態。 **建議**:明確處理 `spawnSync` 的退出訊號,並確保清理操作是原子性的。
@@ -0,0 +151,4 @@
for (const candidate of findJsonObjects(text)) {
// LLM 常在字串值內輸出未跳脫的換行,先嘗試原始解析,失敗再嘗試修正
for (const variant of [candidate, escapeControlCharsInStrings(candidate)]) {
try {

嚴重等級🟡 警告
審查員:Leo
問題:summarize 函式使用了 5 分鐘固定 timeout,大型 diff 可能導致分析失敗。
建議:將 timeout 設定為可配置參數或根據 diff 大小動態計算。

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:summarize 函式使用了 5 分鐘固定 timeout,大型 diff 可能導致分析失敗。 **建議**:將 timeout 設定為可配置參數或根據 diff 大小動態計算。
@@ -0,0 +177,4 @@
if (inString) {
if (escape) {
out += ch;
escape = false;

嚴重等級🔴 嚴重
審查員:Assassin
問題:AI 模型產生的 PR 描述未經 sanitization,易遭 Prompt Injection 導致 Stored XSS 攻擊。
建議:在 extractResult 中對 obj.description 使用成熟的 HTML Sanitizer 過濾惡意標籤。

**嚴重等級**:🔴 嚴重 **審查員**:Assassin **問題**:AI 模型產生的 PR 描述未經 sanitization,易遭 Prompt Injection 導致 Stored XSS 攻擊。 **建議**:在 `extractResult` 中對 `obj.description` 使用成熟的 HTML Sanitizer 過濾惡意標籤。
gitea-actions bot added 1 commit 2026-06-26 06:04:36 +00:00
admin closed this pull request 2026-06-26 06:04:53 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: old-docker-actions/ai-pull-request#1