feat(ai-review 對話收斂): 未修復但已在舊問題者改為解決對話、不重複加回
This commit is contained in:
+5
-3
@@ -46,10 +46,12 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
step('Step2', 'PR 對話收斂');
|
step('Step2', 'PR 對話收斂');
|
||||||
let reconcile = { resolvedFindings: [], carriedFindings: [], resolvedCount: 0, unresolvedCount: 0 };
|
let reconcile = { resolvedFindings: [], carriedFindings: [], resolvedCount: 0, duplicateCount: 0, unresolvedCount: 0 };
|
||||||
try {
|
try {
|
||||||
reconcile = await reconcileConversations();
|
// 載入來源分支既有的舊問題,供對話收斂判斷「未修復但已存在於舊問題」的情況
|
||||||
ok(`Step2 完成: resolved=${reconcile.resolvedCount} unresolved=${reconcile.unresolvedCount} 加回=${reconcile.carriedFindings.length}`);
|
const oldFindingsForReconcile = loadOldFindings(WORKSPACE);
|
||||||
|
reconcile = await reconcileConversations({ oldFindings: oldFindingsForReconcile });
|
||||||
|
ok(`Step2 完成: resolved=${reconcile.resolvedCount} duplicate=${reconcile.duplicateCount} 加回=${reconcile.carriedFindings.length}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn(`Step2 對話收斂失敗(繼續執行): ${e.message}`);
|
warn(`Step2 對話收斂失敗(繼續執行): ${e.message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-19
@@ -134,11 +134,12 @@ function isSafeRepoPath(p) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 對話收斂主流程:
|
* 對話收斂主流程:取得 PR 所有行內 review comment、收斂成對話、跳過已 resolve 的,
|
||||||
* 1. 取得 PR 所有行內 review comment,收斂成對話,跳過已 resolve 的;
|
* 取最新程式碼請 AI 判斷後,對每個待判斷對話做下列處置:
|
||||||
* 2. 取每個對話所在檔案的最新內容,請 AI 判斷問題是否已解決;
|
* 1. 程式碼已修復(AI 判定已解決)→ 解決對話,並記錄其 finding 供從舊問題移除;
|
||||||
* 3. 已解決者呼叫 Gitea resolve API 解決對話,並記錄其 finding(供移除舊問題);
|
* 2. 未修復但已存在於舊問題(以檔案+建議簽章比對)→ 解決對話(已被追蹤,不重複加回);
|
||||||
* 4. 未解決且可解析為 bot finding 者,收集為「加回問題列表」清單。
|
* 3. 未修復且不在舊問題 → 不解決對話,將其 finding 加入舊問題集合(carriedFindings)。
|
||||||
|
* deps.oldFindings 提供來源分支既有的舊問題清單以供第 2 步比對。
|
||||||
* 任一外部呼叫失敗都降級處理(保守視為未解決),不中斷整體 pipeline。
|
* 任一外部呼叫失敗都降級處理(保守視為未解決),不中斷整體 pipeline。
|
||||||
*/
|
*/
|
||||||
export async function reconcileConversations(deps = {}) {
|
export async function reconcileConversations(deps = {}) {
|
||||||
@@ -147,6 +148,7 @@ export async function reconcileConversations(deps = {}) {
|
|||||||
resolveComment = resolvePullReviewComment,
|
resolveComment = resolvePullReviewComment,
|
||||||
getFileContent = getFileContentAtRef,
|
getFileContent = getFileContentAtRef,
|
||||||
judge = judgeConversationsResolved,
|
judge = judgeConversationsResolved,
|
||||||
|
oldFindings = [],
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
let comments;
|
let comments;
|
||||||
@@ -196,39 +198,63 @@ export async function reconcileConversations(deps = {}) {
|
|||||||
verdicts = items.map(it => ({ idx: it.idx, resolved: false }));
|
verdicts = items.map(it => ({ idx: it.idx, resolved: false }));
|
||||||
}
|
}
|
||||||
const resolvedSet = new Set(verdicts.filter(v => v.resolved).map(v => v.idx));
|
const resolvedSet = new Set(verdicts.filter(v => v.resolved).map(v => v.idx));
|
||||||
|
const oldSigs = new Set((oldFindings || []).map(findingSig));
|
||||||
|
|
||||||
const resolvedFindings = [];
|
// 分類每個待判斷對話:
|
||||||
const carriedFindings = [];
|
// - 'resolved':程式碼已修復 → 解決對話,並從舊問題移除
|
||||||
|
// - 'duplicate':未修復但已存在於舊問題 → 解決對話(不重複加回)
|
||||||
|
// - 'carry':未修復且不在舊問題 → 加入舊問題集合(不解決對話)
|
||||||
|
const dispositions = open.map((c, i) => {
|
||||||
|
if (resolvedSet.has(i)) return 'resolved';
|
||||||
|
const sig = c.botFinding ? findingSig(c.botFinding) : null;
|
||||||
|
if (sig && oldSigs.has(sig)) return 'duplicate';
|
||||||
|
return 'carry';
|
||||||
|
});
|
||||||
|
|
||||||
// 並行 resolve 所有 AI 判定已解決的對話(allSettled:個別失敗不中斷其他)
|
// 並行對 resolved 與 duplicate 的對話呼叫 resolve API(allSettled:個別失敗不中斷其他)
|
||||||
const resolveTargets = open
|
const resolveTargets = open
|
||||||
.map((c, i) => ({ c, i }))
|
.map((c, i) => ({ c, i }))
|
||||||
.filter(({ i }) => resolvedSet.has(i));
|
.filter(({ i }) => dispositions[i] === 'resolved' || dispositions[i] === 'duplicate');
|
||||||
const settled = await Promise.allSettled(
|
const settled = await Promise.allSettled(
|
||||||
resolveTargets.map(({ c }) => resolveComment(c.commentIds[0])),
|
resolveTargets.map(({ c }) => resolveComment(c.commentIds[0])),
|
||||||
);
|
);
|
||||||
const resolveOutcome = new Map();
|
const resolveOutcome = new Map();
|
||||||
resolveTargets.forEach(({ i }, j) => resolveOutcome.set(i, settled[j]));
|
resolveTargets.forEach(({ i }, j) => resolveOutcome.set(i, settled[j]));
|
||||||
|
|
||||||
|
const resolvedFindings = [];
|
||||||
|
const carriedFindings = [];
|
||||||
let resolvedCount = 0;
|
let resolvedCount = 0;
|
||||||
|
let duplicateCount = 0;
|
||||||
for (let i = 0; i < open.length; i++) {
|
for (let i = 0; i < open.length; i++) {
|
||||||
const c = open[i];
|
const c = open[i];
|
||||||
const outcome = resolveOutcome.get(i);
|
const disp = dispositions[i];
|
||||||
if (outcome?.status === 'fulfilled') {
|
|
||||||
resolvedCount += 1;
|
if (disp === 'carry') {
|
||||||
if (c.botFinding) resolvedFindings.push({ ...c.botFinding, is_new: false });
|
pushCarried(carriedFindings, c);
|
||||||
ok(`對話已解決並 resolve: ${c.path}:${c.line}`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (outcome?.status === 'rejected') {
|
|
||||||
warn(`resolve 對話失敗(保留為未解決): ${c.path}:${c.line} error=${outcome.reason?.message}`);
|
const outcome = resolveOutcome.get(i);
|
||||||
|
if (outcome?.status === 'fulfilled') {
|
||||||
|
if (disp === 'resolved') {
|
||||||
|
resolvedCount += 1;
|
||||||
|
if (c.botFinding) resolvedFindings.push({ ...c.botFinding, is_new: false });
|
||||||
|
ok(`對話已解決並 resolve(程式碼已修復): ${c.path}:${c.line}`);
|
||||||
|
} else {
|
||||||
|
duplicateCount += 1;
|
||||||
|
ok(`對話已解決並 resolve(已存在於舊問題): ${c.path}:${c.line}`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
pushCarried(carriedFindings, c);
|
|
||||||
|
warn(`resolve 對話失敗: ${c.path}:${c.line} error=${outcome?.reason?.message}`);
|
||||||
|
// resolved 但無法關閉對話時,保守加回舊問題避免遺漏;duplicate 本就在舊問題中,無須加回
|
||||||
|
if (disp === 'resolved') pushCarried(carriedFindings, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unresolvedCount = open.length - resolvedCount;
|
const unresolvedCount = open.length - resolvedCount;
|
||||||
ok(`對話收斂完成: resolved=${resolvedCount} unresolved=${unresolvedCount} 加回 findings=${carriedFindings.length}`);
|
ok(`對話收斂完成: 已修復 resolved=${resolvedCount} 已存在舊問題 duplicate=${duplicateCount} 加入舊問題 carried=${carriedFindings.length}`);
|
||||||
return { resolvedFindings, carriedFindings, resolvedCount, unresolvedCount };
|
return { resolvedFindings, carriedFindings, resolvedCount, duplicateCount, unresolvedCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileOf(location) {
|
function fileOf(location) {
|
||||||
|
|||||||
Reference in New Issue
Block a user