docs(AI Code Review): 補齊 action 與 workflow 註解說明 #3

Merged
admin merged 1 commits from develop into master 2026-07-11 11:57:54 +00:00
7 changed files with 241 additions and 42 deletions
+64 -1
View File
@@ -1,49 +1,112 @@
# 用途:CI workflow 的 command-file 草稿,保留原始流程並補上逐行說明。
# 更新時間:2026/07/11 19:00:45
# workflow 名稱,對應 Gitea UI 中的顯示標題。
name: CI name: CI
# 定義此 workflow 的觸發事件。
on: on:
# 在 pull request 事件時執行。
pull_request: pull_request:
# 只在建立與同步更新 PR 時觸發。
types: [opened, synchronize] types: [opened, synchronize]
# workflow 內的工作列表。
jobs: jobs:
# 第一個 job:負責版本計算與 release 發佈。
build: build:
# job 顯示名稱,用來區分執行階段。
name: 1. BUILD name: 1. BUILD
# 使用 Ubuntu runner 執行。
runs-on: ubuntu runs-on: ubuntu
# job 層級環境變數。
env: env:
VERSION: "0.0.0-beta.${{ gitea.run_number }}" # 目標分支為 develop 時視為 beta。
IS_BETA: ${{ gitea.base_ref == 'develop' }} IS_BETA: ${{ gitea.base_ref == 'develop' }}
# 對外輸出供後續 job 使用。
outputs: outputs:
# 輸出版本字串。
version: ${{ env.VERSION }} version: ${{ env.VERSION }}
# 輸出是否為 beta。
is_beta: ${{ env.IS_BETA }} is_beta: ${{ env.IS_BETA }}
# build job 的執行步驟。
steps: steps:
# 先依 repo 狀態計算版本號。
- name: Calculate Version
# 供後續步驟讀取輸出用的 step id。
id: calculate-version
# 使用版本計算 action。
uses: https://gitea.jsc.idv.tw/actions/calculate-version@${{ vars.ACTION_CALCULATE_VERSION }}
# 傳入 action 參數。
with:
# 告知 action 是否為 beta 分支情境。
is_beta: ${{ env.IS_BETA }}
# 依計算出的版本建立 release。
- name: Publishing Release - name: Publishing Release
# 使用 release action。
uses: akkuman/gitea-release-action@${{ vars.ACTION_GITEA_RELEASE_VERSION }} uses: akkuman/gitea-release-action@${{ vars.ACTION_GITEA_RELEASE_VERSION }}
# 這個 step 的環境變數。
env:
# 取前一步算出的版本號。
VERSION: ${{ steps.calculate-version.outputs.version }}
# release action 的參數。
with: with:
# release 名稱。
name: "${{ gitea.event.repository.name }} v${{ env.VERSION }}" name: "${{ gitea.event.repository.name }} v${{ env.VERSION }}"
# release tag 名稱。
tag_name: "v${{ env.VERSION }}" tag_name: "v${{ env.VERSION }}"
# 指向目前提交。
target_commitish: ${{ gitea.sha }} target_commitish: ${{ gitea.sha }}
# beta 情境時標記為 prerelease。
prerelease: ${{ env.IS_BETA }} prerelease: ${{ env.IS_BETA }}
# 第二個 job:在 beta 情境執行 AI Code Review。
test: test:
# job 顯示名稱。
name: 2. TEST name: 2. TEST
# 使用 Ubuntu runner。
runs-on: ubuntu runs-on: ubuntu
# 必須等 build job 完成。
needs: [build] needs: [build]
# 只有 beta 情境才執行。
if: ${{ needs.build.outputs.is_beta == 'true' }} if: ${{ needs.build.outputs.is_beta == 'true' }}
# job 層級環境變數。
env: env:
# 使用 build job 輸出的版本號。
VERSION: ${{ needs.build.outputs.version }} VERSION: ${{ needs.build.outputs.version }}
# test job 的步驟。
steps: steps:
# 安裝或設定 LLM CLI。
- name: Setup LLM CLI - name: Setup LLM CLI
# 使用對應的 setup action。
uses: https://gitea.jsc.idv.tw/actions/setup-${{ vars.ACTION_SETUP_LLM_CLI }} uses: https://gitea.jsc.idv.tw/actions/setup-${{ vars.ACTION_SETUP_LLM_CLI }}
# 傳入設定。
with: with:
# LLM CLI 的 OAuth 憑證。
oauth: ${{ secrets.LLM_OAUTH }} oauth: ${{ secrets.LLM_OAUTH }}
# 執行 AI Code Review action。
- name: Run AI Code Review - name: Run AI Code Review
# step id,方便追蹤。
id: ai-code-review id: ai-code-review
# 使用本 repo 發佈的 action。
uses: https://gitea.jsc.idv.tw/actions/ai-code-review@v${{ env.VERSION }} uses: https://gitea.jsc.idv.tw/actions/ai-code-review@v${{ env.VERSION }}
# action 參數。
with: with:
# 存取 Gitea API 的 token。
token: ${{ secrets.TOKEN }} token: ${{ secrets.TOKEN }}
# 指定 LLM 模型名稱。
model: ${{ vars.LLM_NAME }} model: ${{ vars.LLM_NAME }}
# 第三個 job:輸出最終版本資訊。
result: result:
# job 顯示名稱。
name: 3. RESULT name: 3. RESULT
# 使用 Ubuntu runner。
runs-on: ubuntu runs-on: ubuntu
# 需等待前兩個 job。
needs: [build,test] needs: [build,test]
# job 層級環境變數。
env: env:
# 延續 build 的版本號。
VERSION: ${{ needs.build.outputs.version }} VERSION: ${{ needs.build.outputs.version }}
# result job 的步驟。
steps: steps:
# 顯示版本號。
- name: Show Version - name: Show Version
# 將版本輸出到 log。
run: echo "$VERSION" run: echo "$VERSION"
+27
View File
@@ -1,25 +1,52 @@
# 用途:master workflow 的 command-file 草稿,保留原始流程並補上逐行說明。
# 更新時間:2026/07/11 19:00:45
# workflow 名稱,對應 Gitea UI 中的顯示標題。
name: CD name: CD
# 定義此 workflow 的觸發事件。
on: on:
# 在 push 事件時執行。
push: push:
# 限定觸發分支。
branches: branches:
# 只有推送到 master 分支才執行。
- master - master
# workflow 內的工作列表。
jobs: jobs:
# 單一 job:輸出 context、檢查 commit tag。
deploy: deploy:
# job 顯示名稱。
name: DEPLOY name: DEPLOY
# 使用 Ubuntu runner。
runs-on: ubuntu runs-on: ubuntu
# job 層級環境變數。
env: env:
# 將完整 Gitea context 轉成 JSON 字串。
GITEA_CONTEXT: ${{ toJSON(gitea) }} GITEA_CONTEXT: ${{ toJSON(gitea) }}
# 取第二筆 commit 的 id 作為查詢目標。
COMMIT_SHA: ${{ gitea.event.commits[1].id }} COMMIT_SHA: ${{ gitea.event.commits[1].id }}
# deploy job 的步驟。
steps: steps:
# 顯示 Gitea context。
- name: Show Gitea Context - name: Show Gitea Context
# 將 context 格式化輸出。
run: echo "$GITEA_CONTEXT" | jq . run: echo "$GITEA_CONTEXT" | jq .
# 取回完整原始碼與 tags。
- name: Source Code Checkout - name: Source Code Checkout
# 使用 checkout action。
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }} uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
# checkout 參數。
with: with:
# 取得完整歷史。
fetch-depth: 0 fetch-depth: 0
# 一併抓取 tags。
fetch-tags: true fetch-tags: true
# 查詢指定 commit 對應的 tag。
- name: Get Commit Tag - name: Get Commit Tag
# 供後續步驟讀取輸出。
id: commit id: commit
# 把查到的 tag 寫入 action 輸出。
run: echo "tag=$(git describe --contains ${{ env.COMMIT_SHA }})" >> $GITEA_OUTPUT run: echo "tag=$(git describe --contains ${{ env.COMMIT_SHA }})" >> $GITEA_OUTPUT
# 顯示剛查到的 tag。
- name: Show Tag - name: Show Tag
# 將 tag 印到 log。
run: echo "${{ steps.commit.outputs.tag }}" run: echo "${{ steps.commit.outputs.tag }}"
+48 -8
View File
@@ -1,9 +1,49 @@
# GITEA NODE ACTION 工作流列表 # GITEA NODE ACTION 工作流說明草稿
- CI 更新時間:2026/07/11 18:54:51
- BUILD
- TEST ## 總覽
- RESULT
- CD 此專案目前包含兩個 workflow:
- BUILD
- DEPLOY - `CI`:處理 pull request 期間的版本計算、釋出與 AI 程式碼審查。
- `CD`:處理推送到 `master` 分支後的部署相關檢查與資訊輸出。
## Workflow 明細
### CI
- 檔案位置:`.gitea/workflows/ci.yaml`
- 用途:在 pull request 事件中計算版本,必要時建立 release,並在 beta 情境下執行 AI 程式碼審查。
- 觸發條件:`pull_request`,事件類型為 `opened``synchronize`
- 主要輸入 / 環境參數:
- `gitea.base_ref`:用來判斷是否為 `develop`,進而決定 `IS_BETA`
- `vars.ACTION_CALCULATE_VERSION`:提供 `calculate-version` action 的版本。
- `vars.ACTION_GITEA_RELEASE_VERSION`:提供 release action 的版本。
- `secrets.LLM_OAUTH`:設定 LLM CLI 的 OAuth。
- `secrets.TOKEN`:提供 AI 程式碼審查 action 存取 Gitea API。
- `vars.LLM_NAME`:指定審查使用的模型名稱。
- 重要注意事項:
- `test` job 只會在 `IS_BETA == true` 時執行,也就是 pull request 目標分支為 `develop` 時。
- `Publishing Release` 會使用 `VERSION``gitea.sha` 建立 release 與 tag。
- 若變數或 secret 未設定,對應步驟會失敗,需人工確認部署前置條件。
### CD
- 檔案位置:`.gitea/workflows/master.yaml`
- 用途:在 `master` 分支推送後輸出 Gitea context、檢查提交標籤,作為後續部署流程的基礎。
- 觸發條件:`push``master` 分支。
- 主要輸入 / 環境參數:
- `gitea` 事件內容:轉成 `GITEA_CONTEXT` 後交給 `jq` 顯示。
- `gitea.event.commits[1].id`:作為 `COMMIT_SHA`,用來查詢 commit tag。
- `vars.ACTION_CHECKOUT_VERSION`:提供 `actions/checkout` 的版本。
- `GITEA_OUTPUT`:寫入 `git describe --contains` 的結果。
- 重要注意事項:
- `COMMIT_SHA` 取用 commits 陣列的第 2 筆資料,若 push 事件實際只有 1 筆 commit,需人工確認是否會發生索引風險。
- `Get Commit Tag` 依賴完整的 git 歷史與 tags,因此 checkout 已設定 `fetch-depth: 0``fetch-tags: true`
- `Show Gitea Context` 會輸出完整事件內容,若包含敏感資訊,需注意執行環境的日誌保存策略。
## 備註
- 本檔為草稿版本,僅整理 workflow 行為與設定重點,不修改任何 workflow 實際邏輯。
- 若後續要覆蓋正式檔,請先確認 `ci.yaml``master.yaml` 的變數與 secret 已在目標環境中正確配置。
+18
View File
@@ -1,16 +1,34 @@
# 用途:AI Code Review Action 的設定檔草稿,保留原始輸入與 Node 入口,並補上逐行說明。
# 更新時間:2026/07/11 18:54:51
# Action 顯示名稱,讓工作流程與使用者介面可以辨識此動作。
name: 'AI Code Review' name: 'AI Code Review'
# Action 的簡短用途說明。
description: 'AI 程式碼審查' description: 'AI 程式碼審查'
# 作者或維護者名稱。
author: 'Jeffery' author: 'Jeffery'
# 定義此 Action 對外提供的輸入參數。
inputs: inputs:
# 用於存取 Gitea API 的授權 Token。
token: token:
# token 參數的用途說明。
description: '操作 Gitea API 的 Token' description: '操作 Gitea API 的 Token'
# 此參數為必要,執行時必須提供。
required: true required: true
# 用於存取 Gitea Commit API 的授權 Token。
comment_token: comment_token:
# comment_token 參數的用途說明。
description: '操作 Gitea Commit API 的 Token' description: '操作 Gitea Commit API 的 Token'
# 此參數為選填,沒有提供時不影響 Action 啟動。
required: false required: false
# 指定執行 AI 程式碼審查時所使用的模型名稱。
model: model:
# model 參數的用途說明。
description: '執行 AI 程式碼審查使用的 LLM 名稱' description: '執行 AI 程式碼審查使用的 LLM 名稱'
# 此參數為選填,未提供時由執行環境或預設值決定。
required: false required: false
# 宣告 Action 的執行方式與主要進入點。
runs: runs:
# 使用 Node.js 24 執行此 Action。
using: 'node24' using: 'node24'
# Action 的主要進入檔,實際邏輯從此檔案開始。
main: 'src/main.js' main: 'src/main.js'
+18
View File
@@ -51,6 +51,12 @@ export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';
* @returns {import('https').Agent} 已關閉憑證驗證的 HTTPS Agent 單例。 * @returns {import('https').Agent} 已關閉憑證驗證的 HTTPS Agent 單例。
*/ */
let _insecureHttpsAgent = null; let _insecureHttpsAgent = null;
/**
* 取得一個關閉 TLS 憑證驗證的 HTTPS Agent 單例,供內部服務連線使用。
*
* @remarks 只應在信任的內網或測試環境使用;若需要完整 TLS 安全性,應改用預設
* `https.Agent`,不要調用這個函式。
*/
export function getInsecureHttpsAgent() { export function getInsecureHttpsAgent() {
return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false })); return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false }));
} }
@@ -86,10 +92,22 @@ const CLI_CANDIDATES = [
}, },
]; ];
/**
* 取得目前支援的 LLM CLI 指令名稱清單。
*
* @remarks 內容直接取自 `CLI_CANDIDATES`,若日後候選清單增減,輸出會同步變動。
*/
export function getLLMCLICommands() { export function getLLMCLICommands() {
return CLI_CANDIDATES.map(c => c.command); return CLI_CANDIDATES.map(c => c.command);
} }
/**
* 檢查指定 CLI 指令是否可在目前環境中執行。
*
* @param {string} command - 要檢查的指令名稱。
* @returns {boolean} 找得到指令時回傳 `true`,否則回傳 `false`。
* @remarks 透過 `/bin/sh -lc "command -v <command>"` 檢查,屬於同步存在性檢查。
*/
function commandExists(command) { function commandExists(command) {
try { try {
execFileSync('/bin/sh', ['-lc', `command -v ${command}`], { stdio: 'ignore' }); execFileSync('/bin/sh', ['-lc', `command -v ${command}`], { stdio: 'ignore' });
+6
View File
@@ -111,6 +111,12 @@ function cleanText(value) {
* 以模組層級 Map 對「字串輸入」做 memoization,避免重複跑 NFKC/正則替換。 * 以模組層級 Map 對「字串輸入」做 memoization,避免重複跑 NFKC/正則替換。
*/ */
const _normalizeTextCache = new Map(); const _normalizeTextCache = new Map();
/**
* 將文字正規化成比對用形式。
*
* @param {*} value - 任意值。
* @remarks 適合用於誤報過濾與排除條目比對。
*/
export function normalizeText(value) { export function normalizeText(value) {
if (typeof value === 'string' && _normalizeTextCache.has(value)) return _normalizeTextCache.get(value); if (typeof value === 'string' && _normalizeTextCache.has(value)) return _normalizeTextCache.get(value);
const result = cleanText(value) const result = cleanText(value)
+28 -1
View File
@@ -56,6 +56,16 @@ function buildPrompt(systemPrompt, userContent) {
].join('\n'); ].join('\n');
} }
/**
* 依不同 AI provider 產生 CLI 參數。
*
* @param {*} provider - AI provider 名稱。
* @param {*} model - 模型名稱。
* @param {*} promptFile - prompt 檔路徑,供 `opencode` 使用。
* @param {*} prompt - 直接傳給 CLI 的 prompt 文字,供部分 provider 使用。
* @remarks 適合把不同 CLI 的參數差異集中管理。
* @remarks 目前支援的 provider 名稱是硬編碼的,新增 provider 時需人工確認是否同步更新所有呼叫端。
*/
function cliArgs({ provider, model, promptFile = null, prompt = null }) { function cliArgs({ provider, model, promptFile = null, prompt = null }) {
if (provider === 'codex') { if (provider === 'codex') {
return ['exec', '--model', model, '--sandbox', 'read-only', '--skip-git-repo-check', '-']; return ['exec', '--model', model, '--sandbox', 'read-only', '--skip-git-repo-check', '-'];
@@ -92,12 +102,30 @@ export function extractMeaningfulError(raw, limit = 1000) {
return picked.length > limit ? picked.slice(-limit) : picked; return picked.length > limit ? picked.slice(-limit) : picked;
} }
/**
* 將 CLI 例外整理成較精簡的錯誤摘要。
*
* @param {*} e - 被拋出的錯誤物件,可能含 `stderr`、`stdout`、`message`。
* @remarks 適合在 log 與錯誤重新拋出前先整理訊息。
* @remarks 若錯誤物件結構和預期不同,仍會退回字串化處理,屬保守容錯。
*/
function summarizeCliError(e) { function summarizeCliError(e) {
const stderr = String(e.stderr || '').trim(); const stderr = String(e.stderr || '').trim();
const stdout = String(e.stdout || '').trim(); const stdout = String(e.stdout || '').trim();
return extractMeaningfulError(stderr || stdout || e.message || String(e)); return extractMeaningfulError(stderr || stdout || e.message || String(e));
} }
/**
* 執行 AI 助理 CLI 並回傳純文字結果。
*
* @param {*} provider - CLI provider 名稱。
* @param {*} command - 實際可執行指令。
* @param {*} model - 要使用的模型名稱。
* @param {*} prompt - 送給 CLI 的完整 prompt 內容。
* @remarks 適合用在需呼叫外部 AI CLI 的情境。
* @remarks 逾時與輸出上限由環境變數控制,預設值是保守設定。
* @remarks 若子行程回傳非 0,錯誤訊息會由上層摘要處理。
*/
async function runAssistantCLI({ provider, command, model }, prompt) { async function runAssistantCLI({ provider, command, model }, prompt) {
let tempDir = null; let tempDir = null;
let promptFile = null; let promptFile = null;
@@ -120,7 +148,6 @@ async function runAssistantCLI({ provider, command, model }, prompt) {
child.kill('SIGTERM'); child.kill('SIGTERM');
reject(new Error(`${provider} CLI 逾時 (${timeout}ms)`)); reject(new Error(`${provider} CLI 逾時 (${timeout}ms)`));
}, timeout); }, timeout);
const append = (kind, chunk) => { const append = (kind, chunk) => {
if (kind === 'stdout') stdout += chunk; if (kind === 'stdout') stdout += chunk;
else stderr += chunk; else stderr += chunk;