feat: add AI false positive filtering in Step4
This commit is contained in:
@@ -130,3 +130,37 @@ export function applyExclusions(findings, exclusions) {
|
|||||||
console.log(` 排除過濾: ${before} -> ${filtered.length} 筆(排除 ${before - filtered.length} 筆)`);
|
console.log(` 排除過濾: ${before} -> ${filtered.length} 筆(排除 ${before - filtered.length} 筆)`);
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 呼叫 AI 判斷哪些問題是誤報或不需處理,回傳需保留的 findings
|
||||||
|
* 失敗時降級回傳原始 findings
|
||||||
|
*/
|
||||||
|
export async function filterFalsePositivesWithAI(findings) {
|
||||||
|
if (findings.length === 0) return findings;
|
||||||
|
|
||||||
|
const systemPrompt = `你是一位資深程式碼審查專家,負責判斷審查問題是否為誤報或不需處理。
|
||||||
|
給你一份問題清單(JSON 陣列),每筆包含 level、role、location、suggestion。
|
||||||
|
請移除以下類型的問題:
|
||||||
|
1. 誤報:問題描述與實際程式碼不符(例如:程式碼已正確使用環境變數或 secrets,卻被標記為硬編碼敏感資料)
|
||||||
|
2. 不適用:問題在此專案情境下不需處理(例如:CI/CD action 本來就需要透過環境變數傳遞 token)
|
||||||
|
只回傳需要保留的問題 JSON 陣列,不要有其他文字。`;
|
||||||
|
|
||||||
|
const userContent = `請判斷以下問題清單,移除誤報或不需處理的問題:\n\n${JSON.stringify(findings, null, 2)}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await chatJSON(systemPrompt, userContent);
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+4
-3
@@ -1,7 +1,7 @@
|
|||||||
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig } from './config.js';
|
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig } from './config.js';
|
||||||
import { loadRoles, getRoleIntro } from './roles.js';
|
import { loadRoles, getRoleIntro } from './roles.js';
|
||||||
import { getPRDiff, postComment } from './gitea.js';
|
import { getPRDiff, postComment } from './gitea.js';
|
||||||
import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI, loadExclusions, applyExclusions } from './findings.js';
|
import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI, loadExclusions, applyExclusions, filterFalsePositivesWithAI } from './findings.js';
|
||||||
import { saveFindings, postOldFindingsComment, postNewNonCriticalComment, postNewCriticalComments } from './comments.js';
|
import { saveFindings, postOldFindingsComment, postNewNonCriticalComment, postNewCriticalComments } from './comments.js';
|
||||||
import { cloneRepo, commitAndPush } from './git.js';
|
import { cloneRepo, commitAndPush } from './git.js';
|
||||||
|
|
||||||
@@ -81,10 +81,11 @@ async function main() {
|
|||||||
const sorted = sortByLevel(deduped);
|
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(` 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: 讀取排除問題檔案,過濾 PR 問題表格
|
// Step4: 讀取排除問題檔案,過濾 PR 問題表格,並請 AI 判斷誤報
|
||||||
console.log('\n🚫 Step4: 排除問題過濾');
|
console.log('\n🚫 Step4: 排除問題過濾');
|
||||||
const exclusions = loadExclusions(repoDir || WORKSPACE);
|
const exclusions = loadExclusions(repoDir || WORKSPACE);
|
||||||
const filtered = applyExclusions(sorted, exclusions);
|
const ruleFiltered = applyExclusions(sorted, exclusions);
|
||||||
|
const filtered = await filterFalsePositivesWithAI(ruleFiltered);
|
||||||
console.log(` Step4 完成: findings total=${filtered.length}`);
|
console.log(` Step4 完成: findings total=${filtered.length}`);
|
||||||
|
|
||||||
// Step5: 寫入 findings.json,依序發布 comment
|
// Step5: 寫入 findings.json,依序發布 comment
|
||||||
|
|||||||
Reference in New Issue
Block a user