feat(ai-code-review): 新增 AI 多角色 code review action(攻防審查、findings 保存、建問題模式)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-07-17 16:53:27 +08:00
co-authored by Claude Fable 5
parent 6a0573b7c0
commit d08b97bd87
18 changed files with 2884 additions and 19 deletions
+111
View File
@@ -0,0 +1,111 @@
'use strict';
const fs = require('fs');
const path = require('path');
/**
* 彙整本次 action 執行的上下文:讀取 runner 注入的 GITHUB_* 執行期環境變數、
* INPUT_* 輸入參數與事件 payload 檔(GITHUB_EVENT_PATH),組出後續呼叫
* Gitea API 與執行 AI review 所需的全部資訊。
*
* 事件 payload 不存在或 JSON 壞損時以空物件續行、不拋錯,
* 由呼叫端檢查 prNumber 是否為 null 判斷是否處於 PR 情境。
*
* @returns {{
* serverUrl: string,
* repository: string,
* owner: string,
* repo: string,
* apiBase: string,
* token: string,
* model: string,
* createIssue: boolean,
* event: Object,
* pr: (Object|null),
* prNumber: (number|null),
* prTitle: string,
* prBody: string,
* baseRef: string,
* headRef: string,
* headSha: string,
* runId: string,
* runNumber: string,
* workspace: string,
* actionPath: string
* }} 執行上下文物件:
* - serverUrlGitea 伺服器網址(GITHUB_SERVER_URL,已去除尾端斜線)。
* - repository`owner/repo` 全名(GITHUB_REPOSITORY)。
* - owner / repo:自 repository 拆出的擁有者與專案名,缺值時為空字串。
* - apiBaseGitea REST API 基底網址(`<serverUrl>/api/v1`)。
* - tokenaction input `token`INPUT_TOKEN),用於 API 認證,缺值時為空字串。
* - modelaction input `model`INPUT_MODEL,已 trim),指定 AI 模型,缺值時為空字串。
* - createIssueaction input `create-issue`INPUT_CREATE-ISSUE),是否將問題建到
* 存取庫的問題追蹤(建問題模式);trim + 小寫後與字串 'true' 嚴格比對,預設 false。
* - event:事件 payload 解析後的完整物件;讀取失敗時為空物件。
* - prpayload 內的 pull_request 物件;非 PR 事件時為 null。
* - prNumberPR 編號,優先取 payload,退而從 GITHUB_REFrefs/pull/N/...)解析;皆無時為 null。
* - prTitle / prBodyPR 標題與描述(缺省或非 PR 情境時為空字串);
* 建問題模式下作為新 issue 的標題與本文素材。
* - baseRef / headRefPR 的目標/來源分支名;非 PR 情境時為空字串。
* - headSha:來源分支最新 commit SHA,優先取 payload,退而 GITHUB_SHA。
* - runId / runNumber:本次 workflow 執行識別(GITHUB_RUN_ID / GITHUB_RUN_NUMBER)。
* - workspace:工作目錄(GITHUB_WORKSPACE,退而 process.cwd())。
* - actionPathaction 根目錄(GITHUB_ACTION_PATH,退而以原始碼位置推算),
* 用於定位 action 自帶檔案(如角色提示),不依賴呼叫端工作目錄。
*
* @remarks
* 使用情境:action 主程式(src/index.js)啟動時最先呼叫一次,
* 取得上下文後傳遞給後續各模組使用。前置條件:需在 Gitea / GitHub Actions
* runner 環境下執行(GITHUB_* 環境變數已注入);於本機直接執行時所有欄位
* 退回預設值(空字串 / null / process.cwd()),不會拋錯。呼叫端應先檢查
* prNumber 與 token 是否有值再進行 PR review 流程。注意:若 payload 含
* pull_request 但缺 base/head 結構,讀取 baseRef/headRef 時會拋 TypeError。
*/
function loadContext() {
const serverUrl = (process.env.GITHUB_SERVER_URL || '').replace(/\/+$/, '');
const repository = process.env.GITHUB_REPOSITORY || '';
const [owner = '', repo = ''] = repository.split('/');
// 讀取事件 payloadpull_request 事件時含 PR 完整資訊)。
let event = {};
const eventPath = process.env.GITHUB_EVENT_PATH;
if (eventPath && fs.existsSync(eventPath)) {
try {
event = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
} catch {
event = {}; // payload 壞損時以空物件續行,由呼叫端檢查 prNumber
}
}
const pr = event.pull_request || null;
// PR 編號:優先取 payload,退而從 GITHUB_REFrefs/pull/N/...)解析。
const refMatch = /refs\/pull\/(\d+)\//.exec(process.env.GITHUB_REF || '');
const prNumber = (pr && pr.number) || (refMatch ? Number(refMatch[1]) : null);
return {
serverUrl,
repository,
owner,
repo,
apiBase: `${serverUrl}/api/v1`,
token: process.env.INPUT_TOKEN || '',
model: (process.env.INPUT_MODEL || '').trim(),
// 是否將問題建到存取庫的問題追蹤(input: create-issue,字串 'true' 才啟用,預設否)。
createIssue: (process.env['INPUT_CREATE-ISSUE'] || '').trim().toLowerCase() === 'true',
event,
pr,
prNumber,
prTitle: pr ? pr.title || '' : '',
prBody: pr ? pr.body || '' : '',
baseRef: pr ? pr.base.ref : '',
headRef: pr ? pr.head.ref : '',
headSha: (pr && pr.head.sha) || process.env.GITHUB_SHA || '',
runId: process.env.GITHUB_RUN_ID || '',
runNumber: process.env.GITHUB_RUN_NUMBER || '',
workspace: process.env.GITHUB_WORKSPACE || process.cwd(),
// action 自帶檔案(角色提示等)以 action 根目錄定位,不依賴呼叫端工作目錄。
actionPath: process.env.GITHUB_ACTION_PATH || path.resolve(__dirname, '..', '..'),
};
}
module.exports = { loadContext };