refactor(審查流程): 將舊留言標記移到偵測 AI 工具之前
This commit is contained in:
+27
-25
@@ -42,9 +42,9 @@ const BOT_COMMIT_PREFIX = 'chore: update ai-review findings [ai-review-bot]';
|
||||
* @param {Array<Object>} params.excluded - 被裁決為誤報而排除的問題清單。
|
||||
* @returns {string} findings JSON 檔相對於 repo 根目錄的路徑(例如 `.gitea/ai-review/findings/xxx.json`)。
|
||||
* @remarks
|
||||
* 使用情境:`main()` 步驟 7 於防守方裁決、`review.sortFindings(kept)` 排序後呼叫本函式保存結果,
|
||||
* 使用情境:`main()` 步驟 8 於防守方裁決、`review.sortFindings(kept)` 排序後呼叫本函式保存結果,
|
||||
* 再將回傳的相對路徑交給 `commitFindings` commit 並 push 回 PR 來源分支;
|
||||
* 另在步驟 3 判定無可審查變更時,也會以空清單保存一份空 findings 後以 success 收場。
|
||||
* 另在步驟 4 判定無可審查變更時,也會以空清單保存一份空 findings 後以 success 收場。
|
||||
* 本函式無 try/catch,檔案系統錯誤會往上拋出,由 `main().catch` 以 exit code 1 收場。
|
||||
*/
|
||||
function saveFindings({ cwd, ctx, tool, kept, excluded }) {
|
||||
@@ -61,7 +61,7 @@ function saveFindings({ cwd, ctx, tool, kept, excluded }) {
|
||||
};
|
||||
fs.writeFileSync(findingsPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
||||
const relativePath = path.relative(cwd, findingsPath);
|
||||
log('步驟7', 'INF', `findings 已保存:${relativePath}(保留 ${kept.length} 條、排除 ${excluded.length} 條)。`);
|
||||
log('步驟8', 'INF', `findings 已保存:${relativePath}(保留 ${kept.length} 條、排除 ${excluded.length} 條)。`);
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ function saveFindings({ cwd, ctx, tool, kept, excluded }) {
|
||||
* @remarks
|
||||
* 使用情境:`main()` 於流程尾端依 `severe.length === 0 ? 'success' : 'failure'` 決定 result、
|
||||
* 依模式組出 filesToCommit(一般模式:findings 檔+有變更時的 exclusions.json;
|
||||
* 建問題模式:只有 exclusions.json)後呼叫本函式;另在步驟 3 判定無可審查變更且非建問題模式時,
|
||||
* 建問題模式:只有 exclusions.json)後呼叫本函式;另在步驟 4 判定無可審查變更且非建問題模式時,
|
||||
* 也會以 result: 'success' 提交空 findings。
|
||||
* 注意 commit 訊息與模組常數 `BOT_COMMIT_PREFIX` 耦合,修改前綴會使步驟 1 的快速回報失效。
|
||||
*/
|
||||
@@ -119,13 +119,13 @@ function commitFindings({ cwd, ctx, files, result }) {
|
||||
*
|
||||
* 流程概要:
|
||||
* 1. 快速回報 — 最新 commit 若為 ai-review-bot 的結果 commit([success]/[failure]),直接回報 0/1 不重審;
|
||||
* 2. 偵測 AI 工具(antigravity/codex/claude)並留言;
|
||||
* 3. 讀 .reviewignore、整理 git diff 並留言(無可審查變更時:留言+保存空 findings,
|
||||
* 2. 將 PR 既有舊留言標記為解決(跳過本回合留言);
|
||||
* 3. 偵測 AI 工具(antigravity/codex/claude)並留言;
|
||||
* 4. 讀 .reviewignore、整理 git diff 並留言(無可審查變更時:留言+保存空 findings,
|
||||
* 一般模式 commit success、建問題模式略過 commit,回傳 0);
|
||||
* 4–5. 攻擊方登場留言、每位攻擊方一個 sub agent 並行找問題;
|
||||
* 6–7. 防守方登場留言、裁決誤報後排序並保存 findings JSON,
|
||||
* 5–6. 攻擊方登場留言、每位攻擊方一個 sub agent 並行找問題;
|
||||
* 7–8. 防守方登場留言、裁決誤報後排序並保存 findings JSON,
|
||||
* 並以 appendExclusions 把誤判/重複問題回寫 .gitea/ai-review/exclusions.json;
|
||||
* 8. 將 PR 既有舊留言標記為解決(跳過本回合留言);
|
||||
* 9. 嚴重問題逐條掛在程式碼行上留言;
|
||||
* 10. 警告+建議彙整為單一表格留言;
|
||||
* 建問題模式(input: create-issue):保留問題另建 issue(createIssueWithFindings)逐條留言明細;
|
||||
@@ -167,7 +167,7 @@ async function main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 本回合發出的一般留言 id:步驟 8 標註過時時要跳過這些。
|
||||
// 本回合發出的一般留言 id:步驟 2 標註過時時要跳過這些。
|
||||
const currentRunCommentIds = new Set();
|
||||
/**
|
||||
* 建立本回合 PR 一般留言並記錄留言 id,供後續舊留言處理排除。
|
||||
@@ -185,13 +185,18 @@ async function main() {
|
||||
return created;
|
||||
};
|
||||
|
||||
// ── 步驟 2:偵測 AI agent 工具並留言 ──────────────────────────────────
|
||||
// ── 步驟 2:將 PR 既有留言標記為解決(本回合留言除外)───────────────────
|
||||
// 早於偵測工具與所有本回合留言:先把上一回合的 bot 留言標為過時;
|
||||
// 此時本回合尚未發出任何留言(currentRunCommentIds 為空),之後發出的留言自然不受影響。
|
||||
await review.resolveOldComments({ ctx, gitea, currentRunCommentIds });
|
||||
|
||||
// ── 步驟 3:偵測 AI agent 工具並留言 ──────────────────────────────────
|
||||
const tool = agents.detectTool();
|
||||
if (!tool) {
|
||||
log('步驟2', 'ERR', '找不到可用的 AI 工具(antigravity/codex/claude)。');
|
||||
log('步驟3', 'ERR', '找不到可用的 AI 工具(antigravity/codex/claude)。');
|
||||
return 1;
|
||||
}
|
||||
log('步驟2', 'INF', `選用工具:${tool.name}(${tool.version})。`);
|
||||
log('步驟3', 'INF', `選用工具:${tool.name}(${tool.version})。`);
|
||||
const runLink = `${ctx.serverUrl}/${ctx.repository}/actions/runs/${ctx.runId}`;
|
||||
await postComment(
|
||||
templates.toolComment({
|
||||
@@ -204,13 +209,13 @@ async function main() {
|
||||
}),
|
||||
);
|
||||
|
||||
// ── 步驟 3:讀取 .reviewignore、整理 git diff 並留言 ───────────────────
|
||||
// ── 步驟 4:讀取 .reviewignore、整理 git diff 並留言 ───────────────────
|
||||
const ignores = review.loadReviewIgnore(cwd);
|
||||
const base = gitrepo.resolveMergeBase(cwd, ctx.baseRef);
|
||||
const allFiles = gitrepo.changedFiles(cwd, base);
|
||||
const files = allFiles.filter((file) => !review.isIgnored(file, ignores));
|
||||
const ignoredCount = allFiles.length - files.length;
|
||||
log('步驟3', 'INF', `變更檔案 ${allFiles.length} 個,套用 .reviewignore 後送審 ${files.length} 個(排除 ${ignoredCount} 個)。`);
|
||||
log('步驟4', 'INF', `變更檔案 ${allFiles.length} 個,套用 .reviewignore 後送審 ${files.length} 個(排除 ${ignoredCount} 個)。`);
|
||||
|
||||
if (files.length === 0) {
|
||||
// 沒有可審查的變更:留言說明、保存空 findings、以 success 收場。
|
||||
@@ -229,20 +234,20 @@ async function main() {
|
||||
await review.fillPurposes({ tool, model: ctx.model, cwd, diffRows });
|
||||
await postComment(templates.diffComment(diffRows, ignoredCount));
|
||||
|
||||
// ── 步驟 4:攻擊方角色登場留言 ─────────────────────────────────────────
|
||||
// ── 步驟 5:攻擊方角色登場留言 ─────────────────────────────────────────
|
||||
const roles = loadRoles(path.join(ctx.actionPath, 'src', 'prompts', 'roles'));
|
||||
const attackers = attackersOf(roles);
|
||||
const defenders = defendersOf(roles);
|
||||
log('步驟4', 'INF', `攻擊方 ${attackers.length} 位、防守方 ${defenders.length} 位。`);
|
||||
log('步驟5', 'INF', `攻擊方 ${attackers.length} 位、防守方 ${defenders.length} 位。`);
|
||||
await postComment(templates.rolesComment({ title: '⚔️ 攻擊方登場', roles: attackers }));
|
||||
|
||||
// ── 步驟 5:每個攻擊方一個 sub agent 並行分析,合併問題列表 ────────────
|
||||
// ── 步驟 6:每個攻擊方一個 sub agent 並行分析,合併問題列表 ────────────
|
||||
const findings = await review.runAttackers({ tool, model: ctx.model, cwd, attackers, diffRows });
|
||||
|
||||
// ── 步驟 6:防守方角色登場留言 ─────────────────────────────────────────
|
||||
// ── 步驟 7:防守方角色登場留言 ─────────────────────────────────────────
|
||||
await postComment(templates.rolesComment({ title: '🛡️ 防守方登場', roles: defenders }));
|
||||
|
||||
// ── 步驟 7:防守方裁決 → 排除 → 排序 → 保存 findings ──────────────────
|
||||
// ── 步驟 8:防守方裁決 → 排除 → 排序 → 保存 findings ──────────────────
|
||||
const { kept, excluded } = await review.runDefenders({ tool, model: ctx.model, cwd, defenders, findings });
|
||||
review.sortFindings(kept);
|
||||
const relativePath = saveFindings({ cwd, ctx, tool, kept, excluded });
|
||||
@@ -250,13 +255,10 @@ async function main() {
|
||||
// 誤判/重複的問題附加到 exclusions.json(之後與審查結果一起 commit)。
|
||||
const exclusionsChanged = review.appendExclusions({ cwd, excluded, prNumber: ctx.prNumber });
|
||||
|
||||
// ── 步驟 7(分組):依嚴重等級分組(嚴重/警告+建議),組內已依檔案與行數排序 ─
|
||||
// ── 步驟 8(分組):依嚴重等級分組(嚴重/警告+建議),組內已依檔案與行數排序 ─
|
||||
const severe = kept.filter((finding) => finding.severity === '嚴重');
|
||||
const others = kept.filter((finding) => finding.severity !== '嚴重');
|
||||
log('步驟7', 'INF', `分組結果:嚴重 ${severe.length} 條、警告+建議 ${others.length} 條。`);
|
||||
|
||||
// ── 步驟 8:將 PR 既有留言標記為解決(本回合留言除外)───────────────────
|
||||
await review.resolveOldComments({ ctx, gitea, currentRunCommentIds });
|
||||
log('步驟8', 'INF', `分組結果:嚴重 ${severe.length} 條、警告+建議 ${others.length} 條。`);
|
||||
|
||||
// ── 步驟 9:嚴重問題逐條掛在程式碼行上留言(開發者可回覆)──────────────
|
||||
if (severe.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user