feat: 導入 AI 程式碼審查 action 並修正進入點與參數接線 #1
@@ -0,0 +1,13 @@
|
||||
# AI Code Review 忽略清單
|
||||
# 符合下列前綴/路徑的檔案不會納入送給 LLM 的 git diff。
|
||||
# 規則:每行一個路徑前綴(相對 repo 根),# 開頭為註解,空行略過。
|
||||
# 註:任何深度的 node_modules/ 一律排除(程式內建保險),此處列出僅為明示。
|
||||
|
||||
.gitea/
|
||||
.github/
|
||||
README.md
|
||||
TODO.md
|
||||
package-lock.json
|
||||
src/package-lock.json
|
||||
dist/
|
||||
node_modules/
|
||||
@@ -41,15 +41,9 @@ export function getBotReviewOutcome(message) {
|
||||
return match?.[1]?.toLowerCase() || 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得目前 PR 的完整 Git diff,並排除 CI/文件等不需審查的路徑(.gitea/、.github/、README.md、TODO.md)。
|
||||
* 透過 Gitea `GET /repos/{repo}/pulls/{index}.diff`(純文字 diff),授權使用 GITEA_TOKEN。
|
||||
* @returns {Promise<string>} 過濾後的 diff 文字。
|
||||
* @throws {Error} 當 Gitea API 請求失敗(網路錯誤、逾時或非 2xx 狀態)時拋出 axios 例外。
|
||||
*/
|
||||
export async function getPRDiff() {
|
||||
const resp = await axios.get(api(`/repos/${GITEA_REPOSITORY}/pulls/${PR_NUMBER}.diff`), { headers: headers(), timeout: 60000, httpsAgent });
|
||||
return filterDiff(resp.data, [
|
||||
// 找不到 .reviewignore 時(例如其他 repo 未提供)採用的內建預設排除清單。
|
||||
// 任何深度的 node_modules/ 另由 filterDiff 內建強制排除,不倚賴此清單。
|
||||
export const DEFAULT_REVIEW_IGNORE = [
|
||||
'.gitea/',
|
||||
'.github/',
|
||||
'README.md',
|
||||
@@ -57,7 +51,45 @@ export async function getPRDiff() {
|
||||
'package-lock.json',
|
||||
'src/package-lock.json',
|
||||
'dist/',
|
||||
]);
|
||||
];
|
||||
|
||||
/**
|
||||
* 解析 .reviewignore 文字為排除前綴陣列(gitignore 風格)。
|
||||
* 規則:每行一個路徑前綴,trim 後略過空行與 `#` 開頭的註解行。
|
||||
* @param {string} text - .reviewignore 檔案內容。
|
||||
* @returns {string[]} 排除前綴清單。
|
||||
*/
|
||||
export function parseReviewIgnore(text) {
|
||||
return String(text || '')
|
||||
.split('\n')
|
||||
.map(l => l.trim())
|
||||
.filter(l => l && !l.startsWith('#'));
|
||||
|
admin marked this conversation as resolved
|
||||
}
|
||||
|
||||
/**
|
||||
* 從被審 PR 的 head ref 取得 `.reviewignore` 並解析為排除清單。
|
||||
* 檔案不存在或為空時退回 {@link DEFAULT_REVIEW_IGNORE}。
|
||||
|
admin marked this conversation as resolved
admin
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Assassin
**問題**:`.reviewignore` 是從被審查的 PR head 直接讀回來的,提交者自己就能在同一個 PR 裡新增排除規則,把惡意檔案或關鍵目錄整批從 diff 中消失。攻擊者只要加幾條前綴,就能讓這個審查流程根本看不到真正危險的變更。
**建議**:不要信任 PR 內容裡的 `.reviewignore`;改從受保護的 base branch、獨立設定檔或固定白名單載入,並禁止同一次 PR 修改忽略規則時自動生效,改為人工覆核。
|
||||
* @returns {Promise<string[]>} 套用於 diff 過濾的排除前綴清單。
|
||||
*/
|
||||
export async function getReviewIgnore() {
|
||||
const patterns = parseReviewIgnore(await getFileContentAtRef('.reviewignore'));
|
||||
if (patterns.length > 0) {
|
||||
line(`已套用 .reviewignore:${patterns.length} 條排除規則`);
|
||||
return patterns;
|
||||
}
|
||||
return DEFAULT_REVIEW_IGNORE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得目前 PR 的完整 Git diff,並依 `.reviewignore`(讀不到時用內建預設)排除不需審查的路徑。
|
||||
* 透過 Gitea `GET /repos/{repo}/pulls/{index}.diff`(純文字 diff),授權使用 GITEA_TOKEN。
|
||||
* @returns {Promise<string>} 過濾後的 diff 文字。
|
||||
|
admin marked this conversation as resolved
admin
commented
嚴重等級:🔴 嚴重 **嚴重等級**:🔴 嚴重
**審查員**:Mage
**問題**:這裡只要 `commit` 或 `branch` 訊息包含 `[ai-review-bot]` 就回傳 true,但沒有區分 `[success]` 與 `[failure]`;最小重現:上一輪 bot commit 是 `[ai-review-bot][failure]`,而 `getCommitMessageBySha` 讀取失敗時,流程會被當成「可跳過」直接結束,原本應該讓 workflow 失敗的訊號被吃掉。
**建議**:讓這個函式回傳結構化結果,例如 `success / failure / unknown`,或至少在偵測到 `[failure]` 時明確回傳失敗狀態,交由 `main()` 先處理失敗再決定是否跳過。
|
||||
* @throws {Error} 當 Gitea 取 diff 的 API 請求失敗(網路錯誤、逾時或非 2xx 狀態)時拋出 axios 例外。
|
||||
*/
|
||||
export async function getPRDiff() {
|
||||
const patterns = await getReviewIgnore();
|
||||
const resp = await axios.get(api(`/repos/${GITEA_REPOSITORY}/pulls/${PR_NUMBER}.diff`), { headers: headers(), timeout: 60000, httpsAgent });
|
||||
return filterDiff(resp.data, patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
|
admin marked this conversation as resolved
admin
commented
嚴重等級:🟡 警告 **嚴重等級**:🟡 警告
**審查員**:Maya
**問題**:shouldSkipBotCommit 目前只看到命中 bot marker 的測試,缺少「commit API 失敗、分支查詢失敗、sha/branch 都沒有 marker」時應回 false 的失敗與保守路徑驗證。這是避免 workflow 誤跳過審查的關鍵判斷,不能只測快樂路徑。
**建議**:新增測試讓 getCommitMessageBySha / getBranchHeadCommitMessage 對應的 axios 呼叫拋錯或回一般 commit message,斷言 shouldSkipBotCommit 回 false,且不會把查詢失敗誤判成 bot commit。
|
||||
|
||||
嚴重等級:🔴 嚴重
審查員:Assassin
問題:這裡直接從 PR head 讀取
.reviewignore,再拿它當成排除規則。攻擊者可以在自己的分支塞入排除條目,讓 bot 故意跳過包含惡意變更的檔案或整個目錄,等於自己決定哪些地方不被審查。建議:不要信任 PR head 裡的
.reviewignore來決定安全掃描範圍;改從受保護的 base branch 或 maintainer 管控的位置讀取,且要與固定的預設排除清單合併,而不是讓它覆蓋預設規則。