feat: 嚴重問題改用 Gitea 行內 review comment 標註檔案行數
AI / 計算版本號 (pull_request) Successful in 4s
AI / Code Review (pull_request) Successful in 1m35s

每個新的嚴重問題改以行內 review comment 標註在問題所在的檔案與行數上,
留言內容為等級/審查員/建議;無法解析出行號(未標行號或一次列出多個
檔案),或行內留言失敗(該行不在 diff 範圍)時,降級為原本的一般 PR comment。

- gitea.js:新增 postPullReviewComment,呼叫 pull reviews API,以 new_position
  對應新版檔案行號、commit_id 帶 PR_HEAD_SHA
- comments.js:新增 parseLocation(支援 file:19 / file:70-82,取起始行)與
  行內留言內容組裝;postNewCriticalComments 先試行內、失敗降級,deps 可注入
- 補 11 個測試(API payload、parseLocation 各情境、行內成功與兩種降級路徑)
- README 更新流程第 7 步說明

app/ 測試 123 pass。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-16 14:40:36 +08:00
parent 7caf3d0490
commit caebd2b112
5 changed files with 163 additions and 9 deletions
+38 -6
View File
@@ -1,8 +1,8 @@
import fs from 'fs';
import path from 'path';
import { postComment } from './gitea.js';
import { postComment, postPullReviewComment } from './gitea.js';
import { FINDINGS_PATH } from './config.js';
import { ok, line } from './log.js';
import { ok, line, warn } from './log.js';
const LEVEL_EMOJI = { critical: '🔴', warning: '🟡', info: '🔵' };
const LEVEL_LABEL = { critical: '嚴重', warning: '警告', info: '建議' };
@@ -16,6 +16,26 @@ function buildTable(findings) {
return `| 等級 | 審查員 | 位置 | 建議 |\n|------|--------|------|------|\n${rows}`;
}
const levelText = f => `${LEVEL_EMOJI[f.level] || ''} ${LEVEL_LABEL[f.level] || f.level}`.trim();
/**
* 解析 finding 的 location 取出檔案與行號,供行內 comment 標註使用。
* 支援 "file:19" 與 "file:70-82"(取起始行);無行號或含多個檔案(逗號)時回傳 null。
*/
export function parseLocation(location) {
if (typeof location !== 'string') return null;
const trimmed = location.trim();
if (trimmed.includes(',')) return null;
const match = trimmed.match(/^(.+?):(\d+)(?:-\d+)?$/);
if (!match) return null;
return { file: match[1], line: Number(match[2]) };
}
/** 行內 comment 內容:等級/審查員/建議 */
function inlineCommentBody(f) {
return `**等級**${levelText(f)}\n**審查員**${f.role}\n**建議**${f.suggestion}`;
}
/**
* 寫入 findings.json。
* 預設寫到 workspace;若提供 mirrorDir,則同步寫入另一份供 repo commit 使用。
@@ -61,17 +81,29 @@ export async function postNewNonCriticalComment(findings) {
}
/**
* 每個新 critical 問題各發一個 comment
* 每個新 critical 問題各發一個 comment
* 優先用 Gitea 行內 review comment 標註問題檔案與行數(內容為等級/審查員/建議);
* 若 location 無法解析出行號,或行內發布失敗(例如該行不在 diff 範圍),則降級為一般 comment。
*/
export async function postNewCriticalComments(findings) {
export async function postNewCriticalComments(findings, deps = {}) {
const { postInline = postPullReviewComment, postIssue = postComment } = deps;
const criticals = findings.filter(f => f.is_new && f.level === 'critical');
if (criticals.length === 0) {
line('無新的嚴重問題,跳過');
return;
}
for (const f of criticals) {
const body = `## 🚨 嚴重問題\n\n${buildTable([f])}`;
await postComment(body);
const loc = parseLocation(f.location);
if (loc) {
try {
await postInline({ path: loc.file, line: loc.line, body: inlineCommentBody(f) });
ok(`嚴重問題 行內 comment 發布: [${f.role}] ${loc.file}:${loc.line}`);
continue;
} catch (e) {
warn(`行內 comment 發布失敗,改用一般 comment: [${f.role}] ${f.location} error=${e.message}`);
}
}
await postIssue(`## 🚨 嚴重問題\n\n${buildTable([f])}`);
ok(`嚴重問題 comment 發布: [${f.role}] ${f.location}`);
}
}