Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1279cae575 | ||
|
|
47aa199e5e |
@@ -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/
|
||||||
+43
-11
@@ -41,23 +41,55 @@ export function getBotReviewOutcome(message) {
|
|||||||
return match?.[1]?.toLowerCase() || 'unknown';
|
return match?.[1]?.toLowerCase() || 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 找不到 .reviewignore 時(例如其他 repo 未提供)採用的內建預設排除清單。
|
||||||
|
// 任何深度的 node_modules/ 另由 filterDiff 內建強制排除,不倚賴此清單。
|
||||||
|
export const DEFAULT_REVIEW_IGNORE = [
|
||||||
|
'.gitea/',
|
||||||
|
'.github/',
|
||||||
|
'README.md',
|
||||||
|
'TODO.md',
|
||||||
|
'package-lock.json',
|
||||||
|
'src/package-lock.json',
|
||||||
|
'dist/',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取得目前 PR 的完整 Git diff,並排除 CI/文件等不需審查的路徑(.gitea/、.github/、README.md、TODO.md)。
|
* 解析 .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('#'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 從被審 PR 的 head ref 取得 `.reviewignore` 並解析為排除清單。
|
||||||
|
* 檔案不存在或為空時退回 {@link DEFAULT_REVIEW_IGNORE}。
|
||||||
|
* @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。
|
* 透過 Gitea `GET /repos/{repo}/pulls/{index}.diff`(純文字 diff),授權使用 GITEA_TOKEN。
|
||||||
* @returns {Promise<string>} 過濾後的 diff 文字。
|
* @returns {Promise<string>} 過濾後的 diff 文字。
|
||||||
* @throws {Error} 當 Gitea API 請求失敗(網路錯誤、逾時或非 2xx 狀態)時拋出 axios 例外。
|
* @throws {Error} 當 Gitea 取 diff 的 API 請求失敗(網路錯誤、逾時或非 2xx 狀態)時拋出 axios 例外。
|
||||||
*/
|
*/
|
||||||
export async function getPRDiff() {
|
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 });
|
const resp = await axios.get(api(`/repos/${GITEA_REPOSITORY}/pulls/${PR_NUMBER}.diff`), { headers: headers(), timeout: 60000, httpsAgent });
|
||||||
return filterDiff(resp.data, [
|
return filterDiff(resp.data, patterns);
|
||||||
'.gitea/',
|
|
||||||
'.github/',
|
|
||||||
'README.md',
|
|
||||||
'TODO.md',
|
|
||||||
'package-lock.json',
|
|
||||||
'src/package-lock.json',
|
|
||||||
'dist/',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+29
-1
@@ -1,7 +1,7 @@
|
|||||||
import { describe, it, afterEach, mock } from 'node:test';
|
import { describe, it, afterEach, mock } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { getPRDiff, filterDiff, postComment, postPullReviewComment, postPullReview, getCommitMessageBySha, getBranchHeadCommitMessage, shouldSkipBotCommit, getBotReviewOutcome, listPullReviews, getPullReviewComments, listAllReviewComments, resolvePullReviewComment, getFileContentAtRef } from '../gitea.js';
|
import { getPRDiff, filterDiff, parseReviewIgnore, getReviewIgnore, DEFAULT_REVIEW_IGNORE, postComment, postPullReviewComment, postPullReview, getCommitMessageBySha, getBranchHeadCommitMessage, shouldSkipBotCommit, getBotReviewOutcome, listPullReviews, getPullReviewComments, listAllReviewComments, resolvePullReviewComment, getFileContentAtRef } from '../gitea.js';
|
||||||
|
|
||||||
afterEach(() => mock.restoreAll());
|
afterEach(() => mock.restoreAll());
|
||||||
|
|
||||||
@@ -259,3 +259,31 @@ describe('filterDiff', () => {
|
|||||||
assert.ok(result.includes('src/main.js'));
|
assert.ok(result.includes('src/main.js'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parseReviewIgnore', () => {
|
||||||
|
it('parses prefixes, skipping blanks and comments', () => {
|
||||||
|
const text = '# comment\n\n.gitea/\n dist/ \n# another\nREADME.md\n';
|
||||||
|
assert.deepEqual(parseReviewIgnore(text), ['.gitea/', 'dist/', 'README.md']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty array for empty/nullish input', () => {
|
||||||
|
assert.deepEqual(parseReviewIgnore(''), []);
|
||||||
|
assert.deepEqual(parseReviewIgnore(null), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getReviewIgnore', () => {
|
||||||
|
it('uses patterns fetched from .reviewignore when present', async () => {
|
||||||
|
mock.method(axios, 'get', async () => ({
|
||||||
|
data: { content: Buffer.from('a/\nb/\n# c\n').toString('base64'), encoding: 'base64' },
|
||||||
|
}));
|
||||||
|
const patterns = await getReviewIgnore();
|
||||||
|
assert.deepEqual(patterns, ['a/', 'b/']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to the default list when .reviewignore is missing/empty', async () => {
|
||||||
|
mock.method(axios, 'get', async () => ({ data: {} }));
|
||||||
|
const patterns = await getReviewIgnore();
|
||||||
|
assert.deepEqual(patterns, DEFAULT_REVIEW_IGNORE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user