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

Closed
jiantw83 wants to merge 10 commits from ai-review-resolve/develop-20260626-114117 into develop
2 changed files with 38 additions and 29 deletions
Showing only changes of commit 73c53e11de - Show all commits
+7 -3
View File
@@ -2,7 +2,7 @@ import { loadInputs, logInputs } from './lib/inputs.js';
import { Git } from './lib/git.js';
import { GiteaClient } from './lib/gitea.js';
import { OpenCode } from './lib/opencode.js';
import { log } from './lib/util.js';
import { log, maskSecrets } from './lib/util.js';
async function main() {
Ghost marked this conversation as resolved
Review

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

**嚴重等級**:🟡 警告 **審查員**:Leo **問題**:函式 `main()` 承擔過多責任,違反單一職責原則。 **建議**:將職責拆解,抽離衝突處理邏輯為 `ConflictManager`,並封裝 Gitea API 互動。
const inputs = loadInputs();
4
@@ -143,7 +143,9 @@ function buildResolveBranchName(target, source) {
const runId = process.env.GITHUB_RUN_NUMBER || process.env.GITHUB_RUN_ID || '';
const safe = (s) => s.replace(/[^a-zA-Z0-9._/-]/g, '-');
const suffix = runId ? `-${runId}` : '';
return `resolve-conflict/${safe(target)}-into-${safe(source)}${suffix}`;
// 截斷主體長度,避免 target/source 過長使分支名稱超出 Git 限制
const stem = `${safe(target)}-into-${safe(source)}`.slice(0, 180);
return `resolve-conflict/${stem}${suffix}`;
}
Ghost marked this conversation as resolved
Review

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

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

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

**嚴重等級**:🔵 建議 **審查員**:Maya **問題**:缺乏測試案例驗證 fallbackSummary 的結果是否符合預期格式。 **建議**:補上單元測試,驗證 fallbackSummary 在不同輸入下的產出格式。
/** 解衝突 PR 的描述。 */
1
@@ -180,6 +182,8 @@ function reportPull(pull, created) {
}
Ghost marked this conversation as resolved
Review

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

**嚴重等級**:🟡 警告 **審查員**:Assassin **問題**:錯誤處理中的 `maskSecrets` 基於字串取代,可能無法處理所有 Token 變體導致敏感資訊洩漏。 **建議**:確保 `maskSecrets` 處理所有可能的變體,並在生產環境中禁止輸出原始錯誤物件。
main().catch((err) => {
log.error(err?.stack || err?.message || String(err));
const detail = err?.stack || err?.message || String(err);
// 錯誤訊息/stack 可能夾帶 token,輸出到 CI 日誌前先遮蔽
log.error(maskSecrets(detail, [process.env.GITEA_TOKEN]));
process.exit(1);
});
+9 -4
View File
@@ -1,6 +1,6 @@
import { writeFileSync, mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { writeFileSync, mkdtempSync, rmSync } from 'node:fs';
import { tmpdir, homedir } from 'node:os';
import { join, dirname } from 'node:path';
import { run, log, maskSecrets } from './util.js';
/**
2
@@ -74,6 +74,7 @@ export class OpenCode {
const configPath = this._writeConfig();
const prompt = buildPrompt({ ...ctx, language: this.language });
try {
Ghost marked this conversation as resolved
Review

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

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

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

**嚴重等級**:🔵 建議 **審查員**:Assassin **問題**:傳遞整個 `process.env` 導致敏感環境變數暴露。 **建議**:明確篩選並只傳遞必要環境變數。
log.info(`呼叫 opencode${this.provider}/${this.model})分析 diff...`);
const result = run(
'opencode',
@@ -84,7 +85,7 @@ export class OpenCode {
...process.env,
OPENCODE_CONFIG: configPath,
// 確保 opencode 有可寫的 HOME / 設定目錄
HOME: process.env.HOME || '/root',
HOME: process.env.HOME || homedir(),
},
timeout: 5 * 60 * 1000,
},
@@ -101,6 +102,10 @@ export class OpenCode {
return null;
}
return parsed;
} finally {
// 清理 _writeConfig 建立的暫存設定目錄,避免堆積
Ghost marked this conversation as resolved Outdated
Outdated
Review

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

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