ai-review-resolve/develop-20260626-114117
develop
將原本的 Docker Action 範本改寫為「AI Pull Request」action:抓取來源/目標分支的差異,呼叫 opencode 將 git diff 總結成 PR 標題與描述,並透過 Gitea token 自動建立 Pull Request;偵測到合併衝突時改建解衝突分支並開對應 PR。
Dockerfile
alpine
node:20-bookworm-slim
git
bash
ca-certificates
curl
opencode-ai
app/
action.yaml
source_branch
target_branch
opencode_base_url
opencode_model
opencode_provider
GITEA_TOKEN
gitea.token
entrypoint.sh
set -euo pipefail
node /app/index.js
index.js
lib/inputs.js
lib/git.js
lib/gitea.js
lib/opencode.js
lib/util.js
README.md
opencode_api_key
language
max_diff_chars
備註:本次 .gitea/ai-review/findings.json 不存在,無 AI review findings 需處理;此 PR 僅將工作區既有變更分類提交後送出。
.gitea/ai-review/findings.json
🔍 服務:opencode 模型:gemini-2.5-flash
本次審查(opencode / gemini-2.5-flash,共 23 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -0,0 +55,4 @@
source: inputs.sourceBranch,
target: inputs.targetBranch,
commitMessages,
diffStat,
嚴重等級:🔴 嚴重 審查員:Maya 問題:在偵測到衝突並建立解衝突分支後,程式雖然嘗試透過 git.createResolveBranch 建立並 commit 衝突檔案,但後續缺乏邏輯處理衝突檔案,亦無測試驗證「自動解衝突分支是否真的被建立」以及「提交的內容是否正確」。 建議:應補上整合測試,模擬合併衝突,驗證 detectConflict 能偵測衝突,且 createResolveBranch 產生的分支確實包含預期的衝突檔案與 commit。
git.createResolveBranch
detectConflict
createResolveBranch
@@ -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 的建議長度限制。
buildResolveBranchName
safe
suffix
resolveBranch
@@ -0,0 +147,4 @@
}
/** 解衝突 PR 的描述。 */
function buildResolveBody({ source, target, resolveBranch, files, summary }) {
嚴重等級:🔵 建議 審查員:Maya 問題:對於 fallbackSummary 函數,當 opencode 產生摘要失敗時會觸發,但目前缺乏測試案例驗證在各種輸入下,fallback 的結果是否符合預期格式。 建議:補上單元測試,驗證 fallbackSummary 在不同輸入下(如為空、多行訊息、stat 為空)產生的標題與描述格式是否正確。
fallbackSummary
@@ -0,0 +177,4 @@
} else {
log.info(`PR 已存在 #${number}: ${url}`);
嚴重等級:🟡 警告 審查員:Assassin 問題:在 main 函數的 catch 區塊中,直接將 err.stack 輸出到標準錯誤流(log.error)。如果錯誤物件中包含了敏感資訊(如 token、API 參數),這些機密將被寫入到 CI/CD 的執行日誌中,極易洩漏。 建議:在輸出 err.stack 前,必須使用類似 maskSecrets 的函式,過濾掉所有可能的機密資訊。
main
err.stack
maskSecrets
@@ -0,0 +49,4 @@
*
* @param {string[]} branches
*/
fetchBranches(branches) {
嚴重等級:🟡 警告 審查員:Assassin 問題:Git 的 --no-tags 參數在某些舊版 git 可能無法完全防禦標籤帶來的惡意遠端物件下載。雖然 fetchBranches 限制了 refspec,但使用不可信的 remoteUrl 進行 fetch 操作時,仍存在與 git 協定漏洞相關的風險。 建議:建議確保容器內的 git 版本為最新,並考慮在 fetch 前驗證 remoteUrl 是否為預期的 Gitea 網域,而非任意使用者輸入的網址。
--no-tags
fetchBranches
remoteUrl
@@ -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)來抓取相關變更。
getCommitMessages
git log --max-count=50
limit
--since
@@ -0,0 +92,4 @@
/**
* 偵測 source 合併進 target 是否會衝突(不會留下任何變更)。
嚴重等級:🔵 建議 審查員:Rogue 問題:detectConflict 函式透過 git merge --no-commit --no-ff 進行完整合併測試,極其耗時且佔用大量磁碟空間。 建議:考慮改用 git merge-tree (Git 2.29+) 檢查衝突,在不觸碰工作區的情況下快速檢測。
git merge --no-commit --no-ff
git merge-tree
@@ -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 區塊中有強制清理該分支的機制。
@@ -0,0 +151,4 @@
runOrThrow(
'git',
['commit', '--no-verify', '-m', `Merge branch '${source}' into ${resolveBranch}(含衝突標記,待人工解衝突)`],
{ cwd: this.cwd },
嚴重等級:🔴 嚴重 審查員:Mage 問題:在 createResolveBranch 中,執行 git add -A 與 git commit 會強制提交包括未追蹤檔案在內的所有變更,可能污染分支且掩蓋衝突內容,此流程亦缺乏測試。 建議:僅針對衝突檔案(--diff-filter=U)執行 git add,並補上整合測試,驗證模擬衝突時,產生的分支確實包含正確的衝突標記與檔案。
git add -A
git commit
--diff-filter=U
git add
@@ -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 遮蔽。
this.token
GiteaClient
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 或預期陣列)。
findOpenPull
_request
GET
json
@@ -0,0 +31,4 @@
* @returns {string} config 檔路徑
_writeConfig() {
嚴重等級:🟡 警告 審查員:Leo 問題:1. _writeConfig 使用 mkdtempSync 建立暫存目錄後未清理,造成空間堆積。2. summarize 函式重複建立零散設定檔,增加 I/O 與清理負擔。 建議:在程式執行完畢後的 finally 區塊中,統一實作檔案系統清理邏輯(如 fs.rmSync)以刪除暫存目錄與檔案。同時考慮設定檔重用性,減少頻繁的檔案操作。
_writeConfig
summarize
@@ -0,0 +103,4 @@
return parsed;
嚴重等級:🟡 警告 審查員:Mage 問題:在 summarize 方法中將 HOME 環境變數硬編碼為 /root,若 Dockerfile 變更使用者,將導致無法寫入設定檔。 建議:建議動態獲取當前環境的使用者家目錄(如使用 os.homedir()),增加相容性。
HOME
/root
os.homedir()
@@ -0,0 +173,4 @@
if (escape) {
out += ch;
escape = false;
continue;
嚴重等級:🔵 建議 審查員:Maya 問題:extractResult 函數處理 JSON 解析與清理邏輯,雖然複雜,但目前沒有單元測試驗證其對「LLM 容易輸出的各種非標準 JSON」的處理能力(例如字串內含未跳脫換行)。 建議:補上單元測試,提供幾種 LLM 常見的「壞」JSON 格式,驗證 extractResult 能否正確解析出 title 與 description。
extractResult
title
description
@@ -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。
run
maxBuffer: 64 * 1024 * 1024
git diff
本次審查(opencode / gemini-2.5-flash,共 24 次呼叫)
@@ -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 互動。
main()
ConflictManager
@@ -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 則直接終止。
ahead
countAheadCommits
ahead === 0
const stem = `${safe(target)}-into-${safe(source)}`.slice(0, 180);
return `resolve-conflict/${stem}${suffix}`;
嚴重等級:🔵 建議 審查員:Maya 問題:缺乏測試案例驗證 fallbackSummary 的結果是否符合預期格式。 建議:補上單元測試,驗證 fallbackSummary 在不同輸入下的產出格式。
@@ -0,0 +180,4 @@
嚴重等級:🟡 警告 審查員:Assassin 問題:錯誤處理中的 maskSecrets 基於字串取代,可能無法處理所有 Token 變體導致敏感資訊洩漏。 建議:確保 maskSecrets 處理所有可能的變體,並在生產環境中禁止輸出原始錯誤物件。
嚴重等級:🔴 嚴重 審查員:Maya 問題:detectConflict 函式在執行 git 合併失敗後的 abort 嘗試若失敗,會導致工作區殘留錯誤狀態,且未經測試驗證。 建議:增加測試案例來模擬 git 合併失敗與 abort 失敗的場景,確保狀態正確復原。
@@ -0,0 +74,4 @@
const configPath = this._writeConfig();
const prompt = buildPrompt({ ...ctx, language: this.language });
try {
嚴重等級:🔵 建議 審查員:Assassin 問題:呼叫外部指令時傳遞整個 process.env,導致敏感環境變數暴露。 建議:應明確篩選並只傳遞必要環境變數。
process.env
let inString = false;
let escape = false;
for (let i = 0; i < text.length; i++) {
const ch = text[i];
嚴重等級:🔵 建議 審查員:Maya 問題:缺乏單元測試驗證 extractResult 對非標準 JSON 的解析能力。 建議:補上單元測試,驗證 extractResult 能否正確解析壞 JSON。
@@ -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。
WORKDIR /app
body: summary.description,
});
reportPull(pull, created);
return;
嚴重等級:🔵 建議 審查員:Maya 問題:解衝突的 PR 產出缺乏人工檢查機制。 建議:加入「檢查清單(Checklist)」要求人工確認。
嚴重等級:🔴 嚴重 審查員:Maya 問題:detectConflict 執行 git 合併失敗後的 abort 嘗試若失敗,會導致工作區殘留錯誤狀態,且未經測試。 建議:增加測試案例模擬 git 合併失敗與 abort 失敗的場景,確保狀態正確復原。
@@ -0,0 +119,4 @@
* 建立解衝突分支:以 target 為基礎,合併 source(保留衝突標記後 commit),
嚴重等級:🔵 建議 審查員:Leo 問題:臨時分支名稱可能衝突或殘留。 建議:產生臨時分支名稱時加入 process ID 或隨機字串,並在 finally 區塊清理。
@@ -0,0 +142,4 @@
if (merge.status !== 0) {
// 合併產生衝突:將含有衝突標記的檔案標記為已解決後 commit,
嚴重等級:🟡 警告 審查員:Maya 問題:合併衝突後未檢查是否存在殘留衝突標記。 建議:在 git add 之後,使用 grep 掃描檔案中是否仍有未處理的衝突標記。
@@ -0,0 +37,4 @@
const config = {
$schema: 'https://opencode.ai/config.json',
provider: {
嚴重等級:🔵 建議 審查員:Rogue 問題:頻繁寫入讀取 opencode.json 設定檔造成無謂的 I/O。 建議:若支援,透過參數或環境變數傳入配置。
opencode.json
嚴重等級:🔵 建議 審查員:Assassin 問題:傳遞整個 process.env 導致敏感環境變數暴露。 建議:明確篩選並只傳遞必要環境變數。
@@ -0,0 +124,4 @@
`=== Commits ===`,
commitMessages || '(no commit messages)',
``,
`=== Changed files (stat) ===`,
嚴重等級:🟡 警告 審查員:Mage 問題:summarize 方法中使用 spawnSync 執行指令,未處理退出訊號可能導致清理競態。 建議:明確處理 spawnSync 的退出訊號,並確保清理操作是原子性的。
spawnSync
for (const candidate of findJsonObjects(text)) {
// LLM 常在字串值內輸出未跳脫的換行,先嘗試原始解析,失敗再嘗試修正
for (const variant of [candidate, escapeControlCharsInStrings(candidate)]) {
嚴重等級:🟡 警告 審查員:Leo 問題:summarize 函式使用了 5 分鐘固定 timeout,大型 diff 可能導致分析失敗。 建議:將 timeout 設定為可配置參數或根據 diff 大小動態計算。
if (inString) {
嚴重等級:🔴 嚴重 審查員:Assassin 問題:AI 模型產生的 PR 描述未經 sanitization,易遭 Prompt Injection 導致 Stored XSS 攻擊。 建議:在 extractResult 中對 obj.description 使用成熟的 HTML Sanitizer 過濾惡意標籤。
obj.description
No dependencies set.
The note is not visible to the blocked user.
變更摘要
將原本的 Docker Action 範本改寫為「AI Pull Request」action:抓取來源/目標分支的差異,呼叫 opencode 將 git diff 總結成 PR 標題與描述,並透過 Gitea token 自動建立 Pull Request;偵測到合併衝突時改建解衝突分支並開對應 PR。
重點變更
Dockerfile:基底由alpine換成node:20-bookworm-slim,安裝git/bash/ca-certificates/curl,全域安裝opencode-aiCLI,並複製 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 功能、輸入參數與使用範例說明。影響範圍與注意事項
opencode_api_key、language、max_diff_chars參數,也不輸出 outputs。GITEA_TOKEN需由 Gitea Actions 自動注入的gitea.token提供。備註:本次
.gitea/ai-review/findings.json不存在,無 AI review findings 需處理;此 PR 僅將工作區既有變更分類提交後送出。🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 23 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -0,0 +55,4 @@source: inputs.sourceBranch,target: inputs.targetBranch,commitMessages,diffStat,嚴重等級:🔴 嚴重
審查員:Maya
問題:在偵測到衝突並建立解衝突分支後,程式雖然嘗試透過
git.createResolveBranch建立並 commit 衝突檔案,但後續缺乏邏輯處理衝突檔案,亦無測試驗證「自動解衝突分支是否真的被建立」以及「提交的內容是否正確」。建議:應補上整合測試,模擬合併衝突,驗證
detectConflict能偵測衝突,且createResolveBranch產生的分支確實包含預期的衝突檔案與 commit。@@ -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 的建議長度限制。@@ -0,0 +147,4 @@}/** 解衝突 PR 的描述。 */function buildResolveBody({ source, target, resolveBranch, files, summary }) {嚴重等級:🔵 建議
審查員:Maya
問題:對於
fallbackSummary函數,當 opencode 產生摘要失敗時會觸發,但目前缺乏測試案例驗證在各種輸入下,fallback 的結果是否符合預期格式。建議:補上單元測試,驗證
fallbackSummary在不同輸入下(如為空、多行訊息、stat 為空)產生的標題與描述格式是否正確。@@ -0,0 +177,4 @@} else {log.info(`PR 已存在 #${number}: ${url}`);}}嚴重等級:🟡 警告
審查員:Assassin
問題:在
main函數的 catch 區塊中,直接將err.stack輸出到標準錯誤流(log.error)。如果錯誤物件中包含了敏感資訊(如 token、API 參數),這些機密將被寫入到 CI/CD 的執行日誌中,極易洩漏。建議:在輸出
err.stack前,必須使用類似maskSecrets的函式,過濾掉所有可能的機密資訊。@@ -0,0 +49,4 @@** @param {string[]} branches*/fetchBranches(branches) {嚴重等級:🟡 警告
審查員:Assassin
問題:Git 的
--no-tags參數在某些舊版 git 可能無法完全防禦標籤帶來的惡意遠端物件下載。雖然fetchBranches限制了 refspec,但使用不可信的remoteUrl進行 fetch 操作時,仍存在與 git 協定漏洞相關的風險。建議:建議確保容器內的 git 版本為最新,並考慮在 fetch 前驗證
remoteUrl是否為預期的 Gitea 網域,而非任意使用者輸入的網址。@@ -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)來抓取相關變更。@@ -0,0 +92,4 @@}/*** 偵測 source 合併進 target 是否會衝突(不會留下任何變更)。嚴重等級:🔵 建議
審查員:Rogue
問題:
detectConflict函式透過git merge --no-commit --no-ff進行完整合併測試,極其耗時且佔用大量磁碟空間。建議:考慮改用
git merge-tree(Git 2.29+) 檢查衝突,在不觸碰工作區的情況下快速檢測。@@ -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 區塊中有強制清理該分支的機制。
@@ -0,0 +151,4 @@runOrThrow('git',['commit', '--no-verify', '-m', `Merge branch '${source}' into ${resolveBranch}(含衝突標記,待人工解衝突)`],{ cwd: this.cwd },嚴重等級:🔴 嚴重
審查員:Mage
問題:在
createResolveBranch中,執行git add -A與git commit會強制提交包括未追蹤檔案在內的所有變更,可能污染分支且掩蓋衝突內容,此流程亦缺乏測試。建議:僅針對衝突檔案(
--diff-filter=U)執行git add,並補上整合測試,驗證模擬衝突時,產生的分支確實包含正確的衝突標記與檔案。@@ -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 遮蔽。@@ -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 或預期陣列)。@@ -0,0 +31,4 @@** @returns {string} config 檔路徑*/_writeConfig() {嚴重等級:🟡 警告
審查員:Leo
問題:1.
_writeConfig使用 mkdtempSync 建立暫存目錄後未清理,造成空間堆積。2.summarize函式重複建立零散設定檔,增加 I/O 與清理負擔。建議:在程式執行完畢後的 finally 區塊中,統一實作檔案系統清理邏輯(如 fs.rmSync)以刪除暫存目錄與檔案。同時考慮設定檔重用性,減少頻繁的檔案操作。
@@ -0,0 +103,4 @@return parsed;}}嚴重等級:🟡 警告
審查員:Mage
問題:在
summarize方法中將HOME環境變數硬編碼為/root,若 Dockerfile 變更使用者,將導致無法寫入設定檔。建議:建議動態獲取當前環境的使用者家目錄(如使用
os.homedir()),增加相容性。@@ -0,0 +173,4 @@if (escape) {out += ch;escape = false;continue;嚴重等級:🔵 建議
審查員:Maya
問題:
extractResult函數處理 JSON 解析與清理邏輯,雖然複雜,但目前沒有單元測試驗證其對「LLM 容易輸出的各種非標準 JSON」的處理能力(例如字串內含未跳脫換行)。建議:補上單元測試,提供幾種 LLM 常見的「壞」JSON 格式,驗證
extractResult能否正確解析出title與description。@@ -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。🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 24 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -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 互動。@@ -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則直接終止。@@ -0,0 +147,4 @@const stem = `${safe(target)}-into-${safe(source)}`.slice(0, 180);return `resolve-conflict/${stem}${suffix}`;}嚴重等級:🔵 建議
審查員:Maya
問題:缺乏測試案例驗證 fallbackSummary 的結果是否符合預期格式。
建議:補上單元測試,驗證 fallbackSummary 在不同輸入下的產出格式。
@@ -0,0 +180,4 @@log.info(`PR 已存在 #${number}: ${url}`);}}嚴重等級:🟡 警告
審查員:Assassin
問題:錯誤處理中的
maskSecrets基於字串取代,可能無法處理所有 Token 變體導致敏感資訊洩漏。建議:確保
maskSecrets處理所有可能的變體,並在生產環境中禁止輸出原始錯誤物件。@@ -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 失敗的場景,確保狀態正確復原。
@@ -0,0 +74,4 @@const configPath = this._writeConfig();const prompt = buildPrompt({ ...ctx, language: this.language });try {嚴重等級:🔵 建議
審查員:Assassin
問題:呼叫外部指令時傳遞整個
process.env,導致敏感環境變數暴露。建議:應明確篩選並只傳遞必要環境變數。
@@ -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。
🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 24 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -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。@@ -0,0 +74,4 @@body: summary.description,});reportPull(pull, created);return;嚴重等級:🔵 建議
審查員: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 失敗的場景,確保狀態正確復原。
@@ -0,0 +119,4 @@}/*** 建立解衝突分支:以 target 為基礎,合併 source(保留衝突標記後 commit),嚴重等級:🔵 建議
審查員:Leo
問題:臨時分支名稱可能衝突或殘留。
建議:產生臨時分支名稱時加入 process ID 或隨機字串,並在 finally 區塊清理。
@@ -0,0 +142,4 @@let files = [];if (merge.status !== 0) {// 合併產生衝突:將含有衝突標記的檔案標記為已解決後 commit,嚴重等級:🟡 警告
審查員:Maya
問題:合併衝突後未檢查是否存在殘留衝突標記。
建議:在
git add之後,使用 grep 掃描檔案中是否仍有未處理的衝突標記。@@ -0,0 +37,4 @@const config = {$schema: 'https://opencode.ai/config.json',provider: {嚴重等級:🔵 建議
審查員:Rogue
問題:頻繁寫入讀取
opencode.json設定檔造成無謂的 I/O。建議:若支援,透過參數或環境變數傳入配置。
@@ -0,0 +74,4 @@const configPath = this._writeConfig();const prompt = buildPrompt({ ...ctx, language: this.language });try {嚴重等級:🔵 建議
審查員:Assassin
問題:傳遞整個
process.env導致敏感環境變數暴露。建議:明確篩選並只傳遞必要環境變數。
@@ -0,0 +124,4 @@`=== Commits ===`,commitMessages || '(no commit messages)',``,`=== Changed files (stat) ===`,嚴重等級:🟡 警告
審查員: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 大小動態計算。
@@ -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 過濾惡意標籤。Pull request closed