集中 AI Review 留言並補齊 TLS 測試 #26
+7
-5
@@ -55,28 +55,30 @@ export function saveFindings(workspace, findings, mirrorDir = null) {
|
|||||||
/**
|
/**
|
||||||
* 發布所有舊問題 comment(一次發布,依等級排序)
|
* 發布所有舊問題 comment(一次發布,依等級排序)
|
||||||
*/
|
*/
|
||||||
export async function postOldFindingsComment(findings) {
|
export async function postOldFindingsComment(findings, deps = {}) {
|
||||||
|
const { postIssue = postComment } = deps;
|
||||||
const old = findings.filter(f => !f.is_new);
|
const old = findings.filter(f => !f.is_new);
|
||||||
if (old.length === 0) {
|
if (old.length === 0) {
|
||||||
line('無舊問題,跳過');
|
line('無舊問題,跳過');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const body = `## 📋 舊有未解決問題(${old.length} 筆)\n\n${buildTable(old)}`;
|
const body = `## 📋 舊有未解決問題(${old.length} 筆)\n\n${buildTable(old)}`;
|
||||||
await postComment(body);
|
await postIssue(body);
|
||||||
ok(`舊問題 comment 發布 (${old.length} 筆)`);
|
ok(`舊問題 comment 發布 (${old.length} 筆)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 發布新問題中非 critical 的 comment(一次發布)
|
* 發布新問題中非 critical 的 comment(一次發布)
|
||||||
*/
|
*/
|
||||||
export async function postNewNonCriticalComment(findings) {
|
export async function postNewNonCriticalComment(findings, deps = {}) {
|
||||||
|
const { postIssue = postComment } = deps;
|
||||||
const items = findings.filter(f => f.is_new && f.level !== 'critical');
|
const items = findings.filter(f => f.is_new && f.level !== 'critical');
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
line('無新的非嚴重問題,跳過');
|
line('無新的非嚴重問題,跳過');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const body = `## 🔍 新發現問題(${items.length} 筆)\n\n${buildTable(items)}`;
|
const body = `## 🔍 新發現問題(${items.length} 筆)\n\n${buildTable(items)}`;
|
||||||
await postComment(body);
|
await postIssue(body);
|
||||||
ok(`新問題(非嚴重)comment 發布 (${items.length} 筆)`);
|
ok(`新問題(非嚴重)comment 發布 (${items.length} 筆)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ export async function postNewCriticalComments(findings, deps = {}) {
|
|||||||
}
|
}
|
||||||
for (const f of criticals) {
|
for (const f of criticals) {
|
||||||
const loc = parseLocation(f.location);
|
const loc = parseLocation(f.location);
|
||||||
if (loc) {
|
if (postInline && loc) {
|
||||||
try {
|
try {
|
||||||
await postInline({ path: loc.file, line: loc.line, body: inlineCommentBody(f) });
|
await postInline({ path: loc.file, line: loc.line, body: inlineCommentBody(f) });
|
||||||
ok(`嚴重問題 行內 comment 發布: [${f.role}] ${loc.file}:${loc.line}`);
|
ok(`嚴重問題 行內 comment 發布: [${f.role}] ${loc.file}:${loc.line}`);
|
||||||
|
|||||||
@@ -118,6 +118,15 @@ export async function postComment(body) {
|
|||||||
return resp.data;
|
return resp.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateComment(commentId, body) {
|
||||||
|
const resp = await axios.patch(
|
||||||
|
api(`/repos/${GITEA_REPOSITORY}/issues/comments/${commentId}`),
|
||||||
|
{ body },
|
||||||
|
{ headers: headers(GITEA_COMMENT_TOKEN || GITEA_TOKEN), timeout: 30000, httpsAgent },
|
||||||
|
);
|
||||||
|
return resp.data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 PR 指定檔案的指定行數發布行內 review comment(標註程式碼位置)。
|
* 在 PR 指定檔案的指定行數發布行內 review comment(標註程式碼位置)。
|
||||||
* 透過 Gitea 的 pull reviews API,以 new_position 對應新版檔案的行號。
|
* 透過 Gitea 的 pull reviews API,以 new_position 對應新版檔案的行號。
|
||||||
|
|||||||
+19
-6
@@ -1,7 +1,7 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig, FINDINGS_PATH, EXCLUSIONS_PATH } from './config.js';
|
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 { loadRoles, getRoleIntro } from './roles.js';
|
||||||
import { getPRDiff, postComment, getCommitMessageBySha, getBotReviewOutcome, shouldSkipBotCommit } from './gitea.js';
|
import { getPRDiff, postComment, updateComment, 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 } from './findings.js';
|
||||||
import { saveFindings, postOldFindingsComment, postNewNonCriticalComment, postNewCriticalComments } from './comments.js';
|
import { saveFindings, postOldFindingsComment, postNewNonCriticalComment, postNewCriticalComments } from './comments.js';
|
||||||
import { cloneRepo, commitAndPush, getRepoState } from './git.js';
|
import { cloneRepo, commitAndPush, getRepoState } from './git.js';
|
||||||
@@ -63,9 +63,22 @@ async function main() {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let reviewComment = null;
|
||||||
|
let reviewCommentBody = getRoleIntro(roles) + `\n\n> 🔍 服務:${provider} 模型:${model}`;
|
||||||
|
const appendReviewComment = async (body) => {
|
||||||
|
if (!body) return null;
|
||||||
|
if (!reviewComment?.id) {
|
||||||
|
reviewCommentBody = body;
|
||||||
|
reviewComment = await postComment(reviewCommentBody);
|
||||||
|
return reviewComment;
|
||||||
|
}
|
||||||
|
reviewCommentBody += `\n\n---\n\n${body}`;
|
||||||
|
reviewComment = await updateComment(reviewComment.id, reviewCommentBody);
|
||||||
|
return reviewComment;
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const intro = getRoleIntro(roles) + `\n\n> 🔍 服務:${provider} 模型:${model}`;
|
reviewComment = await postComment(reviewCommentBody);
|
||||||
await postComment(intro);
|
|
||||||
ok('角色介紹 comment 發布成功');
|
ok('角色介紹 comment 發布成功');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn(`comment 發布失敗(繼續執行): ${e.message}`);
|
warn(`comment 發布失敗(繼續執行): ${e.message}`);
|
||||||
@@ -111,9 +124,9 @@ async function main() {
|
|||||||
const reviewDir = repoDir || WORKSPACE;
|
const reviewDir = repoDir || WORKSPACE;
|
||||||
saveFindings(WORKSPACE, filtered, reviewDir);
|
saveFindings(WORKSPACE, filtered, reviewDir);
|
||||||
try {
|
try {
|
||||||
await postOldFindingsComment(filtered);
|
await postOldFindingsComment(filtered, { postIssue: appendReviewComment });
|
||||||
await postNewNonCriticalComment(filtered);
|
await postNewNonCriticalComment(filtered, { postIssue: appendReviewComment });
|
||||||
await postNewCriticalComments(filtered);
|
await postNewCriticalComments(filtered, { postInline: null, postIssue: appendReviewComment });
|
||||||
ok('Step5 完成');
|
ok('Step5 完成');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn(`comment 發布失敗(繼續執行): ${e.message}`);
|
warn(`comment 發布失敗(繼續執行): ${e.message}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user