diff --git a/app/findings.js b/app/findings.js index 3975a80..7c38515 100644 --- a/app/findings.js +++ b/app/findings.js @@ -61,3 +61,35 @@ export function mergeFindings(oldFindings, newFindings) { export function sortByLevel(findings) { return [...findings].sort((a, b) => LEVELS.indexOf(a.level) - LEVELS.indexOf(b.level)); } + +/** + * 呼叫 LLM 進行語意去重,回傳去重後的 findings + * 失敗時降級回傳原始 findings + */ +export async function deduplicateWithAI(findings) { + if (findings.length === 0) return findings; + + const systemPrompt = `你是一位程式碼審查問題去重專家。 +給你一份問題清單(JSON 陣列),請移除語意重複的問題(即使描述文字不同,但指的是同一個問題)。 +保留等級較高的版本,優先保留 critical > warning > info。 +只回傳去重後的 JSON 陣列,不要有其他文字。`; + + const userContent = `以下是問題清單,請去除語意重複的項目:\n\n${JSON.stringify(findings, null, 2)}`; + + try { + const result = await chatJSON(systemPrompt, userContent); + if (Array.isArray(result) && result.length > 0) { + console.log(` AI 去重: ${findings.length} -> ${result.length} 筆`); + return result; + } + throw new Error('AI 回傳空陣列'); + } catch (e) { + const status = e.response?.status; + if (status === 402 || status === 429) { + console.log(` ⚠️ AI 去重失敗(${status} 額度/限流),降級:保留所有問題`); + } else { + console.log(` ⚠️ AI 去重失敗(${e.message}),降級:保留所有問題`); + } + return findings; + } +} diff --git a/app/main.js b/app/main.js index e317d61..d9db8c9 100644 --- a/app/main.js +++ b/app/main.js @@ -1,7 +1,7 @@ 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 } from './findings.js'; +import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI } from './findings.js'; const WORKSPACE = process.env.GITHUB_WORKSPACE || '/workspace'; @@ -66,8 +66,13 @@ async function main() { console.log('\n🔀 Step3: Findings 合併'); const oldFindings = loadOldFindings(WORKSPACE); const mergedFindings = mergeFindings(oldFindings, newFindings); - const sorted = sortByLevel(mergedFindings); - console.log(` Step3 merged 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})`); + 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})`); console.log('\n📝 Step4: Findings 寫入與 Comment 發布(待實作)'); console.log(' [stub] 寫入 findings.json,發布 comment...');