114 lines
4.1 KiB
JavaScript
114 lines
4.1 KiB
JavaScript
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig } from './config.js';
|
||
import { loadRoles, getRoleIntro } from './roles.js';
|
||
import { getPRDiff, postComment } from './gitea.js';
|
||
import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI } from './findings.js';
|
||
import { saveFindings, postOldFindingsComment, postNewNonCriticalComment, postNewCriticalComments } from './comments.js';
|
||
import { commitAndPush } from './git.js';
|
||
|
||
const WORKSPACE = process.env.GITHUB_WORKSPACE || '/workspace';
|
||
|
||
async function main() {
|
||
console.log('='.repeat(60));
|
||
console.log('🚀 Step1: Pipeline 啟動');
|
||
console.log(` repo=${GITEA_REPOSITORY} PR=#${PR_NUMBER}`);
|
||
console.log(` ${PR_HEAD_BRANCH} -> ${PR_BASE_BRANCH}`);
|
||
|
||
// 偵測 LLM
|
||
const { provider, baseURL, model } = getLLMConfig();
|
||
if (!provider) {
|
||
console.error('❌ 未設定任何 LLM API Key,請檢查 action inputs');
|
||
process.exit(1);
|
||
}
|
||
console.log(` LLM: provider=${provider} model=${model} base_url=${baseURL}`);
|
||
|
||
// 載入角色
|
||
const roles = loadRoles();
|
||
console.log(` 已載入 ${roles.length} 個角色: [${roles.map(r => r.name).join(', ')}]`);
|
||
|
||
// 取得 PR diff
|
||
console.log('\n📋 Step1: 取得 PR Diff');
|
||
let diff;
|
||
try {
|
||
diff = await getPRDiff();
|
||
console.log(` diff 長度: ${diff.length} 字元`);
|
||
} catch (e) {
|
||
console.error(` ❌ 取得 diff 失敗: ${e.message}`);
|
||
process.exit(1);
|
||
}
|
||
|
||
if (!diff.trim()) {
|
||
console.log(' ⚠️ diff 為空,無需審查');
|
||
process.exit(0);
|
||
}
|
||
|
||
// 發布角色介紹 comment
|
||
console.log('\n💬 Step1: 發布角色介紹 Comment');
|
||
try {
|
||
const intro = getRoleIntro(roles) + `\n\n> 🔍 服務:${provider} 模型:${model}`;
|
||
await postComment(intro);
|
||
console.log(' ✅ 角色介紹 comment 發布成功');
|
||
} catch (e) {
|
||
console.log(` ⚠️ comment 發布失敗(繼續執行): ${e.message}`);
|
||
}
|
||
|
||
// Step2: 各角色分析 diff 產生新 findings
|
||
console.log('\n📊 Step2: Findings 產生');
|
||
const newFindings = [];
|
||
for (const role of roles) {
|
||
try {
|
||
const found = await analyzeWithRole(role, diff);
|
||
newFindings.push(...found);
|
||
} catch (e) {
|
||
console.log(` ⚠️ [${role.name}] 分析失敗(跳過): ${e.message}`);
|
||
}
|
||
}
|
||
console.log(` Step2 完成: 新 findings 總計 ${newFindings.length} 筆`);
|
||
|
||
// Step3: 讀取舊 findings,合併去重
|
||
console.log('\n🔀 Step3: Findings 合併');
|
||
const oldFindings = loadOldFindings(WORKSPACE);
|
||
const mergedFindings = mergeFindings(oldFindings, newFindings);
|
||
console.log(` Step3 merged findings total=${mergedFindings.length}`);
|
||
|
||
// Step3b: AI 語意去重
|
||
console.log('\n🤖 Step3b: AI 語意去重');
|
||
const deduped = await deduplicateWithAI(mergedFindings);
|
||
const sorted = sortByLevel(deduped);
|
||
console.log(` Step3b dedup findings total=${sorted.length} (critical=${sorted.filter(f=>f.level==='critical').length} warning=${sorted.filter(f=>f.level==='warning').length} info=${sorted.filter(f=>f.level==='info').length})`);
|
||
|
||
// Step4: 寫入 findings.json,依序發布 comment
|
||
console.log('\n📝 Step4: Findings 寫入與 Comment 發布');
|
||
saveFindings(WORKSPACE, sorted);
|
||
|
||
try {
|
||
await postOldFindingsComment(sorted);
|
||
await postNewNonCriticalComment(sorted);
|
||
await postNewCriticalComments(sorted);
|
||
console.log(' Step4 完成');
|
||
} catch (e) {
|
||
console.log(` ⚠️ comment 發布失敗(繼續執行): ${e.message}`);
|
||
}
|
||
|
||
// Step5: commit/push findings.json 到來源分支
|
||
console.log('\n💾 Step5: 記憶區 Commit/Push');
|
||
await commitAndPush(WORKSPACE);
|
||
|
||
// Step6: 有 critical 問題則 exit 1
|
||
console.log('\n🚦 Step6: 嚴重問題檢查');
|
||
const criticalCount = sorted.filter(f => f.level === 'critical').length;
|
||
if (criticalCount > 0) {
|
||
console.log(` ❌ 發現 ${criticalCount} 個嚴重問題,workflow 結束(exit 1)`);
|
||
console.log('='.repeat(60));
|
||
process.exit(1);
|
||
}
|
||
console.log(' ✅ 無嚴重問題');
|
||
|
||
console.log('\n✅ Pipeline 完成');
|
||
console.log('='.repeat(60));
|
||
}
|
||
|
||
main().catch(e => {
|
||
console.error('❌ Runner failed:', e.message);
|
||
process.exit(1);
|
||
});
|