feat: AI Pull Request action — opencode 自動產生 PR 並通過 AI review #2
+7
-3
@@ -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();
|
||||||
@@ -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 的描述。 */
|
||||||
@@ -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
@@ -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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user