fix(評論發布): 只統計新問題並排序 comments #32

Merged
jiantw83 merged 3 commits from develop into master 2026-06-22 15:15:24 +00:00
4 changed files with 21 additions and 10 deletions
+1 -1
View File
@@ -14,7 +14,7 @@
3. 檢查是否為 AI 助理自動提交;若不是,選定 LLM provider/model、載入角色、取得 PR diff,將服務名稱、模型名稱與角色資訊 Comment 到 Pull Request,並讓每個角色個別分析 Git Diff 產生新問題表格(問題等級、角色名稱、問題位置或行數、修改建議)
4. 讀取來源分支中的所有未解決舊問題(問題檔案 `.gitea/ai-review/findings.json`)加上新問題後,去除重複產生本次 PR 的問題表格(PR問題表格)覆蓋問題檔案
5. 讀取來源分支中的排除問題檔案(`.gitea/ai-review/exclusions.json`),用來過濾 PR 問題表格中不需要處理的問題
6. 將 PR 問題表格寫入 `.gitea/ai-review/findings.json`,並發布一個 Gitea Review:Review 本文用「嚴重/警告/建議」三欄統計各等級數量;之後將可找出檔案與行數的問題依照嚴重等級排序後加入 Review Comments 內,每個 Comment 包含嚴重等級/審查員/問題/建議,其中「問題」是審查員判斷該處有問題的原因,不是檔案路徑或行號
6. 將 PR 問題表格寫入 `.gitea/ai-review/findings.json`,並發布一個 Gitea ReviewReview 本文只統計本次新發現的問題,使用「嚴重/警告/建議」三欄呈現各等級數量;之後將可找出檔案與行數的問題依照嚴重等級排序後加入 Review Comments 內,每個 Comment 包含嚴重等級/審查員/問題/建議,其中「問題」是審查員判斷該處有問題的原因,不是檔案路徑或行號
7. 驗證來源分支中的 `findings.json``exclusions.json` 是否為合法 JSON array;格式錯誤時先嘗試透過 AI 修正內容,再重新驗證;修正後仍不合法才 exit 1;檔案不存在則建立並寫入 `[]`
8. Commit 問題檔案,只將 workspace 中實際存在的 `.gitea/ai-review/findings.json``.gitea/ai-review/exclusions.json` 覆蓋到記憶區;workspace 沒有的問題檔就略過。自動提交的 commit message 會帶上 `[ai-review-bot]`,供 workflow 判斷是否要跳過重跑
9. 如果 PR 問題表格中有嚴重問題,先嘗試透過 Gitea API 對 PR head commit 建立 failure status 來阻擋 PR 合併;若 API 阻擋失敗,才改用原本的 workflow failure 處理(`exit 1`
+12 -7
View File
@@ -84,16 +84,21 @@ function toReviewComment(f) {
}
/**
* 發布單一 Gitea review本文統計可找出檔案與行數的問題,
* comments 只包含可定位到檔案與行數的 findings,並依嚴重等級排序。
* 發布單一 Gitea review
* - summaryFindings 只用來統計本文數字
* - commentFindings 用來產生 review comments,並依嚴重等級排序
*/
export async function postFindingsReview(findings, deps = {}) {
const { postReview = postPullReview } = deps;
const sorted = [...findings].sort(bySeverity);
const comments = sorted.map(toReviewComment).filter(Boolean);
const body = buildReviewSummary(sorted);
const {
postReview = postPullReview,
summaryFindings = findings,
commentFindings = findings,
} = deps;
const sortedComments = [...commentFindings].sort(bySeverity);
const comments = sortedComments.map(toReviewComment).filter(Boolean);
const body = buildReviewSummary(summaryFindings);
await postReview({ body, comments });
ok(`review 發布: total=${sorted.length} commentable=${comments.length}`);
ok(`review 發布: summary=${summaryFindings.length} total=${sortedComments.length} commentable=${comments.length}`);
}
/**
+3 -1
View File
@@ -196,12 +196,14 @@ describe('postFindingsReview', () => {
];
await postFindingsReview(findings, {
summaryFindings: findings.filter(f => f.is_new !== false),
commentFindings: findings,
postReview: async (args) => { reviewCalls.push(args); },
});
assert.equal(reviewCalls.length, 1);
assert.match(reviewCalls[0].body, /\| 🔴 嚴重 \| 🟡 警告 \| 🔵 建議 \|/);
assert.match(reviewCalls[0].body, /\| 1 筆 \| 1 筆 \| 1 筆 \|/);
assert.match(reviewCalls[0].body, /\| 0 筆 \| 1 筆 \| 1 筆 \|/);
assert.deepEqual(
reviewCalls[0].comments.map(c => c.path),
['app/a.js', 'app/b.js', 'app/c.js'],
+5 -1
View File
@@ -111,7 +111,11 @@ async function main() {
const reviewDir = repoDir || WORKSPACE;
saveFindings(WORKSPACE, filtered, reviewDir);
try {
await postFindingsReview(filtered);
const newFindings = filtered.filter(f => f.is_new !== false);
await postFindingsReview(filtered, {
summaryFindings: newFindings,
commentFindings: filtered,
});
ok('Step6 完成');
} catch (e) {
warn(`review 發布失敗(繼續執行): ${e.message}`);