feat(ai-review 對話收斂與誤報裁決): 對話一律關閉並三分類,誤報改由防守方角色平行 sub-agent 裁決

This commit is contained in:
Jeffery
2026-06-23 14:26:06 +08:00
parent abe9fde6d1
commit 98c60541c4
4 changed files with 177 additions and 97 deletions
+11 -8
View File
@@ -2,7 +2,7 @@ import path from 'path';
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig, FINDINGS_PATH, EXCLUSIONS_PATH } from './config.js';
import { loadRoles, getRoleIntro } from './roles.js';
import { getPRDiff, postComment, getCommitMessageBySha, getBotReviewOutcome, shouldSkipBotCommit } from './gitea.js';
import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI, loadExclusions, applyExclusions, filterFalsePositivesWithAI } from './findings.js';
import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI, loadExclusions, applyExclusions, filterFalsePositivesWithAI, appendExclusions } from './findings.js';
import { reconcileConversations, dropResolvedFindings, addCarriedFindings } from './resolve.js';
import { saveFindings, postFindingsReview, formatFindingsStatsLine } from './comments.js';
import { getRunUsage, getRateLimit, fetchAccountQuota, formatUsageStats, formatUsageStatsLine } from './usage.js';
@@ -46,12 +46,10 @@ async function main() {
}
step('Step2', 'PR 對話收斂');
let reconcile = { resolvedFindings: [], carriedFindings: [], resolvedCount: 0, duplicateCount: 0, unresolvedCount: 0 };
let reconcile = { resolvedFindings: [], excludedFindings: [], carriedFindings: [], resolvedCount: 0, falsePositiveCount: 0, openCount: 0, closedCount: 0 };
try {
// 載入來源分支既有的舊問題,供對話收斂判斷「未修復但已存在於舊問題」的情況
const oldFindingsForReconcile = loadOldFindings(WORKSPACE);
reconcile = await reconcileConversations({ oldFindings: oldFindingsForReconcile });
ok(`Step2 完成: resolved=${reconcile.resolvedCount} duplicate=${reconcile.duplicateCount} 加回=${reconcile.carriedFindings.length}`);
reconcile = await reconcileConversations();
ok(`Step2 完成: 關閉=${reconcile.closedCount} 已修復=${reconcile.resolvedCount} 誤報=${reconcile.falsePositiveCount} 加回=${reconcile.carriedFindings.length}`);
} catch (e) {
warn(`Step2 對話收斂失敗(繼續執行): ${e.message}`);
}
@@ -115,9 +113,10 @@ async function main() {
let oldFindings = loadOldFindings(repoDir || WORKSPACE);
logFindingsStats('Step4 舊 findings 統計', oldFindings);
const beforeReconcile = oldFindings.length;
oldFindings = dropResolvedFindings(oldFindings, reconcile.resolvedFindings);
const reconcileDropped = [...reconcile.resolvedFindings, ...reconcile.excludedFindings];
oldFindings = dropResolvedFindings(oldFindings, reconcileDropped);
oldFindings = addCarriedFindings(oldFindings, reconcile.carriedFindings);
line(`Step4 對話收斂套用: ${beforeReconcile} -> ${oldFindings.length} 筆(移除已解決 ${reconcile.resolvedFindings.length}加回未解決 ${reconcile.carriedFindings.length}`);
line(`Step4 對話收斂套用: ${beforeReconcile} -> ${oldFindings.length} 筆(移除已修復 ${reconcile.resolvedFindings.length}移除誤報 ${reconcile.excludedFindings.length}、加回仍成立 ${reconcile.carriedFindings.length}`);
logFindingsStats('Step4 收斂後舊 findings 統計', oldFindings);
logFindingsStats('Step4 新 findings 統計', newFindings);
const mergedFindings = mergeFindings(oldFindings, newFindings);
@@ -130,6 +129,10 @@ async function main() {
logFindingsStats('Step4 排序後統計', sorted);
step('Step5', 'AI 排除問題過濾');
// 先把對話收斂判定的誤報寫入 exclusions.jsonworkspace 與 cloned repo 各一份),供本次過濾與後續 commit
if (reconcile.excludedFindings.length > 0) {
appendExclusions(WORKSPACE, reconcile.excludedFindings, repoDir || WORKSPACE);
}
const exclusions = loadExclusions(repoDir || WORKSPACE, repoState, WORKSPACE);
const ruleFiltered = applyExclusions(sorted, exclusions);
logFindingsStats('Step5 規則排除後統計', ruleFiltered);