feat: AI Pull Request action — opencode 自動產生 PR 並通過 AI review #2

Open
jiantw83 wants to merge 24 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 { Git } from './lib/git.js';
import { GiteaClient } from './lib/gitea.js'; import { GiteaClient } from './lib/gitea.js';
import { OpenCode } from './lib/opencode.js'; import { OpenCode } from './lib/opencode.js';
import { log } from './lib/util.js'; import { log, maskSecrets } from './lib/util.js';
async function main() { async function main() {
const inputs = loadInputs(); const inputs = loadInputs();
2
@@ -143,7 +143,9 @@ function buildResolveBranchName(target, source) {
const runId = process.env.GITHUB_RUN_NUMBER || process.env.GITHUB_RUN_ID || ''; const runId = process.env.GITHUB_RUN_NUMBER || process.env.GITHUB_RUN_ID || '';
const safe = (s) => s.replace(/[^a-zA-Z0-9._/-]/g, '-'); const safe = (s) => s.replace(/[^a-zA-Z0-9._/-]/g, '-');
const suffix = runId ? `-${runId}` : ''; 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}`;
} }
/** 解衝突 PR 的描述。 */ /** 解衝突 PR 的描述。 */
1
@@ -180,6 +182,8 @@ function reportPull(pull, created) {
} }
main().catch((err) => { 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); process.exit(1);
}); });
+9 -4
View File
@@ -1,6 +1,6 @@
import { writeFileSync, mkdtempSync } from 'node:fs'; import { writeFileSync, mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os'; import { tmpdir, homedir } from 'node:os';
import { join } from 'node:path'; import { join, dirname } from 'node:path';
import { run, log, maskSecrets } from './util.js'; import { run, log, maskSecrets } from './util.js';
/** /**
@@ -74,6 +74,7 @@ export class OpenCode {
const configPath = this._writeConfig(); const configPath = this._writeConfig();
const prompt = buildPrompt({ ...ctx, language: this.language }); const prompt = buildPrompt({ ...ctx, language: this.language });
try {
log.info(`呼叫 opencode${this.provider}/${this.model})分析 diff...`); log.info(`呼叫 opencode${this.provider}/${this.model})分析 diff...`);
const result = run( const result = run(
'opencode', 'opencode',
@@ -84,7 +85,7 @@ export class OpenCode {
...process.env, ...process.env,
OPENCODE_CONFIG: configPath, OPENCODE_CONFIG: configPath,
// 確保 opencode 有可寫的 HOME / 設定目錄 // 確保 opencode 有可寫的 HOME / 設定目錄
HOME: process.env.HOME || '/root', HOME: process.env.HOME || homedir(),
}, },
timeout: 5 * 60 * 1000, timeout: 5 * 60 * 1000,
}, },
@@ -101,6 +102,10 @@ export class OpenCode {
return null; return null;
} }
return parsed; return parsed;
} finally {
// 清理 _writeConfig 建立的暫存設定目錄,避免堆積
rmSync(dirname(configPath), { recursive: true, force: true });
}
} }
} }
1