feat: 導入 AI 程式碼審查 action 並修正進入點與參數接線 #1

Merged
admin merged 30 commits from ai-review-resolve/develop-20260702-160700 into develop 2026-07-03 10:04:33 +00:00
3 changed files with 19 additions and 8 deletions
Showing only changes of commit 2c0ac71c08 - Show all commits
+11 -6
View File
24
@@ -420,13 +420,18 @@ export async function deduplicateWithAI(findings) {
try {
const result = await chatJSON(systemPrompt, JSON.stringify(toAIPayload(findings)));
if (Array.isArray(result) && result.length > 0) {
ok(`AI 去重: ${findings.length} -> ${result.length}`);
// 以 location+suggestion 為 key,將原始 findings 的完整欄位(含 is_new)補回
const origMap = new Map(findings.map(f => [`${f.location}|${String(f.suggestion).slice(0, 50)}`, f]));
return result.map(r => origMap.get(`${r.location}|${String(r.suggestion).slice(0, 50)}`) ?? r);
// 去重結果數量不得超過輸入(避免 LLM 無中生有),且每筆都必須能對應回原始 finding。
if (Array.isArray(result) && result.length > 0 && result.length <= findings.length) {
const keyOf = f => `${f.location}|${String(f.suggestion).slice(0, 50)}`;
const origMap = new Map(findings.map(f => [keyOf(f), f]));
admin marked this conversation as resolved
Review

嚴重等級🟡 警告
審查員:Mage
問題:這裡只要 exPathex.role 存在,就直接把 textMatches 跳過。實際結果是:同一個檔案、同一個角色的任何其他 finding,只要碰上這筆排除規則就會被整包濾掉,哪怕問題本質完全不同。最小重現:先把 app/a.js 某個誤報加入 exclusions,之後同檔同角色的另一個真問題也會一起消失。
建議:把排除條件改成「路徑、角色、文字」的明確交集,不要在有 exPath 時就略過文字比對。若要容許寬鬆排除,至少也要把正規化後的原文或穩定指紋納進判斷,避免同檔不同問題被誤殺。

**嚴重等級**:🟡 警告 **審查員**:Mage **問題**:這裡只要 `exPath` 或 `ex.role` 存在,就直接把 `textMatches` 跳過。實際結果是:同一個檔案、同一個角色的任何其他 finding,只要碰上這筆排除規則就會被整包濾掉,哪怕問題本質完全不同。最小重現:先把 `app/a.js` 某個誤報加入 exclusions,之後同檔同角色的另一個真問題也會一起消失。 **建議**:把排除條件改成「路徑、角色、文字」的明確交集,不要在有 `exPath` 時就略過文字比對。若要容許寬鬆排除,至少也要把正規化後的原文或穩定指紋納進判斷,避免同檔不同問題被誤殺。
// 只保留能對應回原始 finding 的項目,丟棄無法對應(可能為幻覺)的結果
const mapped = result.map(r => origMap.get(keyOf(r))).filter(Boolean);
if (mapped.length > 0) {
ok(`AI 去重: ${findings.length} -> ${mapped.length}`);
admin marked this conversation as resolved Outdated
Outdated
Review

嚴重等級🟡 警告
審查員:Rogue
問題:applyExclusions 在 findings × exclusions 的巢狀比對裡,每遇到一條 exclusion 就重算同一個 finding 的 normalizeText;F 筆 finding、E 條 exclusion 會做最多 F×E 次正規化與正則替換,這是很明顯的 CPU 浪費。
建議:先把 findings 預處理成含 fPath、normalizedFindingText 的陣列,exclusions 也先補齊 normalizedExclusionText,再做比對;同一筆文字只正規化一次。

**嚴重等級**:🟡 警告 **審查員**:Rogue **問題**:applyExclusions 在 findings × exclusions 的巢狀比對裡,每遇到一條 exclusion 就重算同一個 finding 的 normalizeText;F 筆 finding、E 條 exclusion 會做最多 F×E 次正規化與正則替換,這是很明顯的 CPU 浪費。 **建議**:先把 findings 預處理成含 fPath、normalizedFindingText 的陣列,exclusions 也先補齊 normalizedExclusionText,再做比對;同一筆文字只正規化一次。
return mapped;
}
}
throw new Error('AI 回傳空陣列');
throw new Error('AI 去重結果異常(空、超量或無法對應原始 findings)');
} catch (e) {
return fallback('AI 去重', findings, e);
}
4
+7 -1
View File
14
@@ -209,7 +209,13 @@ async function main() {
step('Step10', '記憶區 Commit/Push');
const reviewOutcome = filtered.some(f => f.level === 'critical') ? 'failure' : 'success';
input(`review outcome=${reviewOutcome}`);
await commitAndPush(WORKSPACE, repoDir || WORKSPACE, undefined, undefined, reviewOutcome);
// clone 失敗(repoDir 為 undefined)時不可把 WORKSPACE(非來源分支 git repo)當 repoDir
// 否則會在錯誤的工作目錄嘗試 commit/pushfindings/exclusions 無法持久化到 PR 分支。
if (!repoDir) {
warn('來源分支 clone 失敗,略過 findings/exclusions 持久化(不以 WORKSPACE 當 repoDir');
} else {
await commitAndPush(WORKSPACE, repoDir, undefined, undefined, reviewOutcome);
}
// Step11 嚴重問題把關
step('Step11', '嚴重問題把關');
admin marked this conversation as resolved Outdated
Outdated
Review

嚴重等級🟡 警告
審查員:Mage
問題:若 Step6 的 cloneRepo() 失敗,repoDir 會是 undefined,但這裡仍呼叫 commitAndPush(WORKSPACE, repoDir || WORKSPACE, ...)。最小重現:遠端 clone 因分支不存在或網路錯誤失敗後,流程降級繼續,最後 Step10 會在 /workspace 這個非 git repo 執行 git config/status/commit,錯誤只被 commitAndPush 吞掉;findings/exclusions 已發布但不會被持久化到 PR 分支,下一輪會遺失記憶狀態。
建議:Step10 應在 repoDir 不存在時明確跳過 commit/push 並標記持久化失敗,或讓 clone 失敗成為會終止流程的錯誤;不要把 WORKSPACE 當成 repoDir fallback。

**嚴重等級**:🟡 警告 **審查員**:Mage **問題**:若 Step6 的 `cloneRepo()` 失敗,`repoDir` 會是 undefined,但這裡仍呼叫 `commitAndPush(WORKSPACE, repoDir || WORKSPACE, ...)`。最小重現:遠端 clone 因分支不存在或網路錯誤失敗後,流程降級繼續,最後 Step10 會在 `/workspace` 這個非 git repo 執行 `git config/status/commit`,錯誤只被 `commitAndPush` 吞掉;findings/exclusions 已發布但不會被持久化到 PR 分支,下一輪會遺失記憶狀態。 **建議**:Step10 應在 `repoDir` 不存在時明確跳過 commit/push 並標記持久化失敗,或讓 clone 失敗成為會終止流程的錯誤;不要把 WORKSPACE 當成 repoDir fallback。
+1 -1
View File
2
@@ -75,7 +75,7 @@ export function groupConversations(comments) {
for (const c of comments || []) {
const filePath = typeof c?.path === 'string' ? c.path : '';
if (!filePath) continue; // 無檔案路徑的留言無法定位,跳過以免併入共用群組
const lineNum = Number(c?.position) || Number(c?.original_position) || 0;
const lineNum = Number(c?.position) || Number(c?.new_position) || Number(c?.original_position) || 0;
const key = `${filePath}|${lineNum}`;
if (!groups.has(key)) {
groups.set(key, { key, path: filePath, line: lineNum, commentIds: [], bodies: [], resolved: false, botFinding: null });
9