docs(ai-code-review): 補齊各模組 JSDoc、指令檔逐行註解並重建 README

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-26 14:16:04 +08:00
co-authored by Claude Opus 4.8
parent 303104bb20
commit 1378f03595
18 changed files with 2243 additions and 68 deletions
+96
View File
@@ -8,6 +8,21 @@ const REVIEW_FILE_PATHS = [FINDINGS_PATH, '.gitea/ai-review/exclusions.json'];
const remoteUrl = `${GITEA_SERVER_URL.replace(/\/$/, '')}/${GITEA_REPOSITORY}.git`;
export const BOT_COMMIT_MARKER = '[ai-review-bot]';
/**
* 建立一個同步執行 git 子行程的 runner。透過注入 `spawn` 以利測試
* (正式環境傳入 `child_process.spawnSync`,測試可傳入 stub)。
*
* 回傳的 `run(args, cwd, env)` 會以 utf8 編碼執行 `git <args>`
* 成功回傳經 trim 的 stdout,失敗則丟出 Error。
*
* @param {(cmd: string, args: string[], opts: object) => {error?: Error & {code?: string}, status?: number, stdout?: string, stderr?: string}} spawn
* 同步 spawn 實作(依賴注入,通常為 `spawnSync`)。
* @returns {(args: string[], cwd?: string, env?: object) => string}
* 執行 git 的函式:回傳 trim 後的 stdout。
* @throws {Error} 找不到 git 指令時(`ENOENT`)丟出中文提示。
* @throws {Error} git 子行程本身的 `error`(非 ENOENT)原樣丟出。
* @throws {Error} git 離開碼非 0 時,以 stderr/stdout 內容丟出。
*/
function makeRunner(spawn) {
return function run(args, cwd, env) {
const opts = { cwd, encoding: 'utf8' };
@@ -22,6 +37,23 @@ function makeRunner(spawn) {
};
}
/**
* 包裝一段需要 git HTTP 認證的工作:先在 workspace 寫出暫時的
* `.git-askpass.sh`(透過 `GIT_ASKPASS` 提供 token),呼叫 `fn(credEnv)`
* 再清除該暫存腳本。
*
* 清理時機會依 `fn` 回傳型別自動判斷:
* 同步回傳會立即清理;回傳 Promise(含 async 回呼)則延後到 Promise
* settle 後才清理,避免在第一個 await 就刪掉腳本,導致後續 git push
* 因 `cannot exec .git-askpass.sh` 而失敗。
*
* @template T
* @param {string} workspace 寫入暫存 askpass 腳本的目錄。
* @param {(credEnv: NodeJS.ProcessEnv) => T} fn 帶入憑證環境變數執行的回呼。
* @returns {T} 即 `fn` 的回傳值(Promise 會被包成 `.finally(cleanup)` 後回傳)。
* @throws 透傳 `fn` 丟出的任何例外(同步路徑會先清理暫存腳本再 re-throw)。
* @remarks askpass 腳本以權限 0o700 寫出;token 取自 config 的 `GITEA_TOKEN`。
*/
function withAskpass(workspace, fn) {
const askpassScript = path.join(workspace, '.git-askpass.sh');
fs.writeFileSync(askpassScript, '#!/bin/sh\necho "$GIT_TOKEN"\n', { mode: 0o700 });
@@ -44,6 +76,19 @@ function withAskpass(workspace, fn) {
return result;
}
/**
* 以容錯方式執行 git 讀取指令:成功回傳 trim 後的輸出,
* 任何錯誤都吞掉並回傳空字串。適用於「失敗也不該中斷流程」的唯讀查詢
* (例如取 HEAD SHA、分支名、commit 時間)。
*
* @param {(args: string[], cwd?: string, env?: object) => string} run
* 由 `makeRunner` 產生的 git 執行函式。
* @param {string[]} args git 子指令與參數。
* @param {string} [cwd] 執行目錄。
* @param {object} [env] 環境變數覆寫。
* @returns {string} git 的 trim 輸出;失敗時回傳空字串。
* @remarks 不會拋出例外,也不記錄錯誤。
*/
function readGitOutput(run, args, cwd, env) {
try {
return run(args, cwd, env);
@@ -52,6 +97,17 @@ function readGitOutput(run, args, cwd, env) {
}
}
/**
* 讀取指定 repo 目錄的目前狀態(HEAD SHA、短 SHA、目前分支、commit 時間)。
* 所有查詢皆採容錯讀取,任一失敗對應欄位即為空字串,不會丟出例外。
*
* @param {string} repoDir git 工作目錄路徑。
* @param {typeof import('child_process').spawnSync} [_spawnSync=spawnSync]
* 測試用依賴注入:覆寫底層的同步 spawn 實作。
* @returns {{repoDir: string, branch: string, headSha: string, shortSha: string, commitTime: string}}
* repo 狀態快照;無法取得的欄位為空字串。
* @remarks `commitTime` 為 `%cI` 格式(committer date, ISO 8601 嚴格格式)。
*/
export function getRepoState(repoDir, _spawnSync = spawnSync) {
const run = makeRunner(_spawnSync);
const headSha = readGitOutput(run, ['rev-parse', 'HEAD'], repoDir);
@@ -61,11 +117,30 @@ export function getRepoState(repoDir, _spawnSync = spawnSync) {
return { repoDir, branch, headSha, shortSha, commitTime };
}
/**
* 取得 HEAD commit 的完整 commit message`%B`,含 subject 與 body)。
* 容錯讀取:失敗時回傳空字串。
*
* @param {string} repoDir git 工作目錄路徑。
* @param {typeof import('child_process').spawnSync} [_spawnSync=spawnSync]
* 測試用依賴注入。
* @returns {string} HEAD 的完整 commit 訊息;失敗時為空字串。
*/
export function getHeadCommitMessage(repoDir, _spawnSync = spawnSync) {
const run = makeRunner(_spawnSync);
return readGitOutput(run, ['show', '-s', '--format=%B', 'HEAD'], repoDir);
}
/**
* 判斷 HEAD commit 是否為 AI Review 機器人自己產生的自動 commit
* commit message 含 `BOT_COMMIT_MARKER`)。常用於避免機器人 commit
* 反覆觸發新一輪審查。
*
* @param {string} repoDir git 工作目錄路徑。
* @param {typeof import('child_process').spawnSync} [_spawnSync=spawnSync]
* 測試用依賴注入。
* @returns {boolean} HEAD 訊息含機器人標記時為 true;讀取失敗時安全地回傳 false。
*/
export function isBotAutoCommit(repoDir, _spawnSync = spawnSync) {
return getHeadCommitMessage(repoDir, _spawnSync).includes(BOT_COMMIT_MARKER);
}
@@ -108,6 +183,27 @@ export function cloneRepo(workspace, _spawnSync = spawnSync) {
});
}
/**
* 將 AI 審查產出的 review 檔(findings / exclusions)結轉到 repo,並 commit、
* push 回 PR head branch。流程:設定機器人 git 身分 → fetch + hard reset 對齊
* 遠端 → 從 workspace 複製存在的 review 檔到 repo 並 add → 若無變更則跳過 →
* 以含 `BOT_COMMIT_MARKER` 與結果標籤的訊息 commit → push。
*
* 失敗策略:push 失敗只記 warning(commit 已在本地完成);其餘步驟的例外
* 由外層捕捉並記 warning,函式整體**不丟出例外**,以免中斷上層流程。
*
* @param {string} workspace review 檔來源目錄、askpass 腳本所在目錄。
* @param {string} repoDir 目標 git repo 目錄(commit/push 的工作目錄)。
* @param {typeof import('child_process').spawnSync} [_spawnSync=spawnSync]
* 測試用依賴注入:覆寫底層同步 spawn。
* @param {string|null} [_sourceRoot=null] 測試用依賴注入保留參數;
* 目前函式主體未使用(不確定,待確認其他呼叫端是否依賴)。
* @param {'success'|'failure'} [reviewOutcome='success']
* 審查結果,決定 commit 訊息標籤(`[success]` / `[failure]`)。
* @returns {Promise<void>} 無回傳值;所有失敗皆以 log 記錄後吞掉。
* @remarks `git reset --hard origin/<branch>` 會丟棄本地未對齊變更,請確認
* review 檔是在 reset 之後才複製進來(流程已如此安排)。
*/
export async function commitAndPush(workspace, repoDir, _spawnSync = spawnSync, _sourceRoot = null, reviewOutcome = 'success') {
const run = makeRunner(_spawnSync);