本篇要解決的問題
在團隊協作開發中,Code Review 是確保程式碼品質的重要環節。
但很常要審核的人,自己也在趕案子,要落實很難,如果可以每次都自動由 AI 來審,至少可以讓開發工程師藉由 AI 回饋,來知道自己哪些部份需要注意。
之前有寫過 GitHub、GitLab 的版本:
但不是每間公司都會使用外部雲端服務,也有公司是自行架設 Git 服務。
自行架設 Git 服務時,一般會採用 GitLab 或 Gitea。
本筆記文將示範如何使用 Gitea Actions 搭配 OpenAI API,建立自動化的 PR Code Review 流程。當開發者提交 Pull Request 時,系統會自動:
- 分析 PR 中的程式碼變更
- 透過 OpenAI 識別潛在的錯誤、安全問題和可維護性問題
- 在 PR 中自動留言,提供詳細的改善建議
- 發送 Discord 通知(可選)
這套自動化流程能夠:
- 提早發現程式碼問題,減少 bug 進入主分支的機會
- 節省人工審查時間,讓團隊專注於更複雜的邏輯檢查
- 提供一致性的審查標準
- 幫助新手開發者學習最佳實踐
一、安裝 Gitea
準備 docker-compose.yml
建立一個專案目錄,並建立 docker-compose.yml 檔案:
services:
server:
image: gitea/gitea:latest
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
# Database config
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea123
# Actions config
- GITEA__actions__ENABLED=true
- GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com
ports:
- "8123:3000" # Web UI
volumes:
- ./gitea:/data
depends_on:
db:
condition: service_healthy
db:
image: postgres:15
container_name: gitea-postgres
restart: always
environment:
- POSTGRES_DB=gitea
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gitea123
volumes:
- ./postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea -d gitea"]
interval: 10s
timeout: 5s
retries: 5
關鍵設定說明
在這個 docker-compose 設定中,我們已經透過環境變數啟用了 Gitea Actions 功能:
-
GITEA__actions__ENABLED=true:啟用 Actions 功能 -
GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com:設定 Actions 的預設來源為 GitHub
這樣就不需要在 UI 介面另外設定,Gitea 啟動後即可直接使用 Actions 功能。
啟動 Gitea
在專案目錄下執行:
docker-compose up -d
等待容器啟動完成後,開啟瀏覽器訪問 http://192.168.xx.xxx:8123(請將 IP 改為我們的實際 IP 位址)。
完成 Gitea 的初始設定(建立管理員帳號等),即可開始使用。
二、安裝 Gitea Runner
取得 Runner Registration Token
首先需要從 Gitea 後台取得 Runner 的註冊 Token。
登入 Gitea 後,進入管理後台:
在「站點管理」→「Actions」→「Runner」頁面,點選「建立新的 Runner」或查看註冊 Token:
複製這個 Token,稍後會用到。
建立 Runner 的 docker-compose.yml
在另一個目錄,建立 Runner 的 docker-compose.yml:
services:
runner:
image: docker.io/gitea/act_runner:latest
container_name: gitea-runner
restart: always
environment:
GITEA_INSTANCE_URL: "http://192.168.68.61:8123"
GITEA_RUNNER_REGISTRATION_TOKEN: "請替換為我們的 Token"
GITEA_RUNNER_NAME: "code_review"
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
重要提醒:
- 將
GITEA_INSTANCE_URL中的 IP 改為我們的實際 IP - 將
GITEA_RUNNER_REGISTRATION_TOKEN替換為剛才複製的 Token
啟動 Runner
docker-compose up -d
確認 Runner 連線狀態
回到 Gitea 管理後台的 Runner 頁面,應該可以看到剛才啟動的 Runner 已經成功註冊並處於「閒置」狀態:
看到綠色的「閒置」狀態,表示 Runner 已經準備好接受任務。
三、建立測試專案
建立新專案並設定 Secrets
在 Gitea 上建立一個新的專案(或使用現有專案):
進入專案後,前往「設定」→「Actions」→「Secrets」:
新增以下三個 Secrets:
- OPENAIAPIKEY:我們的 OpenAI API Key
- GITEATOKEN:Gitea 的 Access Token(用於自動留言)
在「設定」→「應用程式」→「管理存取令牌」中產生。
設定令牌權限時:
- issue:讀取和寫入
- repository:讀取
再把產生的 Token 存到 Secrets 裡。
- DISCORDWEBHOOK:Discord Webhook URL(可選)
新增完後,Secrets 管理裡最多就會看到三組設定好的清單:
新增 Workflow 檔案
在要執行 Code Review 的專案根目錄建立 .gitea/workflows/ 目錄結構,並在其中新增 openai-pr-review.yml 檔案:
your-project/
├── .gitea/
│ └── workflows/
│ └── openai-pr-review.yml
└── (其他專案檔案)
openai-pr-review.yml 內容如下:
name: OpenAI PR Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
pr-review:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate PR diff
run: |
git fetch origin pull/${{ github.event.pull_request.number }}/head:pr_head
git diff --diff-filter=AM origin/${{ github.event.pull_request.base.ref }}...pr_head > diff.txt
echo "Diff generated."
- name: Review PR with OpenAI and post results
uses: actions/github-script@v7
env:
OPENAI_API_KEY: ${{ secrets.OPENAIAPIKEY }}
GITEA_TOKEN: ${{ secrets.GITEATOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORDWEBHOOK }}
SERVER_URL: ${{ github.server_url }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
with:
script: |
const fs = require("fs");
const apiKey = process.env.OPENAI_API_KEY;
const giteaToken = process.env.GITEA_TOKEN;
const discordWebhook = process.env.DISCORD_WEBHOOK;
const serverUrl = process.env.SERVER_URL;
const repo = process.env.REPO;
const prNumber = process.env.PR_NUMBER;
const prTitle = process.env.PR_TITLE;
const prAuthor = process.env.PR_AUTHOR;
if (!apiKey) {
core.setFailed("缺少 OPENAI_API_KEY");
return;
}
// 讀取 diff
let diff = "";
try {
diff = fs.readFileSync("diff.txt", "utf8");
} catch (e) {
core.info("無法讀取 diff.txt");
diff = "";
}
diff = diff.slice(0, 12000);
if (!diff.trim()) {
core.info("No diff to review.");
return;
}
// 精簡版 system prompt
const systemPrompt = [
"你是專業 code reviewer,請根據 PR diff 找出『最重要的 5 項問題』,依優先順序:1. 錯誤 2. 資安 3. 可維護性。",
"找不到重要問題時,只回覆:**No major issues found.**(Markdown)。",
"",
"# Findings Summary (Top 5)",
"請產生一份 Markdown 表格:",
"| # | 分類 | 問題摘要 | Risk | 建議摘要 |",
"|---|------|----------|------|-----------|",
"Risk = High / Medium / Low。",
"",
"# Detailed Review",
"依序對每項輸出:",
"## 問題 X(分類)",
"### 問題摘要",
"- 1~2 句說明",
"### Risk Score",
"- High / Medium / Low",
"### 為何重要",
"- 說明風險與影響",
"### 改善建議",
"若有程式碼,請提供『原始程式碼』與『建議後程式碼』兩段 Markdown 程式碼區塊,使用 diff 語法。",
"若無程式碼,提供可執行條列建議。",
"",
"請:",
"- 只輸出最重要的五項(不足五項則寫實際數量)",
"- 嚴格使用 Markdown",
"- 不要產生前言、後記、寒暄或多餘敘述",
"- 回覆簡潔、可採取行動"
].join("\n");
const payload = {
model: "gpt-5-nano",
reasoning_effort: "low",
messages: [
{
role: "system",
content: systemPrompt
},
{
role: "user",
content: `請 review 以下 PR diff:\n\n${diff}`
}
]
};
core.info("Calling OpenAI...");
const openaiRes = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
if (!openaiRes.ok) {
const text = await openaiRes.text();
core.setFailed(`OpenAI API Error: ${openaiRes.status} ${text}`);
return;
}
const data = await openaiRes.json();
const reviewContent =
data.choices?.[0]?.message?.content?.trim() || "無法取得回應";
core.info("OpenAI review completed.");
core.info("Preview (first 200 chars):");
core.info(reviewContent.slice(0, 200));
// ===== 把結果貼到 Gitea PR comment =====
if (giteaToken && serverUrl && repo && prNumber) {
const apiUrl = `${serverUrl}/api/v1/repos/${repo}/issues/${prNumber}/comments`;
const body = {
body: `### 🤖 OpenAI Code Review\n\n${reviewContent}`
};
core.info(`Posting review comment to: ${apiUrl}`);
const res = await fetch(apiUrl, {
method: "POST",
headers: {
"Authorization": `token ${giteaToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
if (!res.ok) {
const text = await res.text();
core.setFailed(`Failed to post comment: ${res.status} ${text}`);
return;
} else {
core.info("Comment posted successfully.");
}
} else {
core.info("GITEA_TOKEN / SERVER_URL / REPO / PR_NUMBER 不完整,略過貼 PR comment。");
}
// ===== 發 Discord 通知 =====
if (discordWebhook) {
const message =
`🤖 **Code Review 完成**\n\n` +
`OpenAI 已完成程式碼審查,請前往查看建議。\n\n` +
`標題:${prTitle}\n` +
`作者:${prAuthor}\n` +
`連結:${serverUrl}/${repo}/pulls/${prNumber}`;
const discordRes = await fetch(discordWebhook, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ content: message })
});
if (!discordRes.ok) {
const text = await discordRes.text();
core.setFailed(`Failed to send Discord notification: ${discordRes.status} ${text}`);
} else {
core.info("Discord notification sent.");
}
} else {
core.info("No DISCORD_WEBHOOK provided, skip Discord notification.");
}
將此 workflow 檔案 commit 到主分支(main 或 master)。
新增分支
建立一個新的開發分支來進行測試,可以用介面操作,也可以下指令:
git checkout -b feature/test-review
建立測試用 JS 檔
在專案根目錄建立 index.js,並故意寫入一些常見的錯誤:
// 測試用程式碼 - 包含一些常見問題
function calculateTotal(price, quantity) {
// 問題 1:缺少參數驗證
var total = price * quantity;
// 問題 2:使用 var 而非 let/const
var discount = 0.1;
// 問題 3:可能的精度問題
return total - total * discount;
}
// 問題 4:未處理的 Promise
fetch("https://api.example.com/data").then((response) => response.json());
// 問題 5:使用 == 而非 ===
if (calculateTotal(100, 2) == 180) {
console.log("計算正確");
}
這個範例包含了多個常見問題,OpenAI 應該能夠識別並提供改善建議。
Commit 並建立 Pull Request
將變更 commit 並推送到遠端,可以用介面操作,也可以下指令:
git add index.js
git commit -m "Add test file for code review"
git push origin feature/test-review
回到 Gitea 介面,建立一個從 feature/test-review 到 main 的 Pull Request:
填寫 PR 標題和描述後,點選「建立合併請求」:
在 Gitea 上查看結果
PR 建立後,Gitea Actions 會自動觸發。我們可以在以下位置查看執行狀態:
1. Actions 執行狀態
在 PR 頁面下方可以看到 Actions 的執行狀態:
點選可以查看詳細的執行 log:
第一次執行會比較久,後續再執行就會比較快。
2. OpenAI 的 Review 結果
當 Actions 執行完成後,OpenAI 會在 PR 中自動留言,提供詳細的程式碼審查報告:
Review 報告會包含:
- Findings Summary 表格:列出前 5 大問題的摘要。
- Detailed Review:每個問題的詳細說明,包括風險評估和改善建議。
- 程式碼範例:使用 diff 格式展示建議的修改。
3. Discord 通知(若有設定)
如果有設定 Discord Webhook,團隊成員也會在 Discord 頻道收到通知:
結語
透過這套自動化流程,我們可以在每次 Pull Request 時獲得即時的程式碼審查建議。
雖然 AI 無法完全取代人工審查,但它能有效地:
- 捕捉常見的程式碼問題和 anti-patterns。
- 提供一致性的審查標準。
- 節省人工審查的時間。
- 作為程式碼品質的第一道防線。
如果將 AI 審查作為輔助工具,在重要的 PR 中搭配人工審查,確保程式碼品質和業務邏輯的正確性。















Top comments (0)