feat: 階段一 - 基本流程串接骨架
- 重寫 action.yaml:支援所有 LLM providers 的 inputs - 重寫 Dockerfile:python:3.11-slim + git - 重寫 entrypoint.sh:啟動 app/main.py - app/config.py:環境變數與 LLM 自動偵測 - app/llm.py:OpenAI-compatible 統一介面 - app/gitea.py:PR diff 取得與 comment 發布 - app/roles.py:從 prompts/roles/*.yaml 載入角色 - app/main.py:pipeline 骨架,log 每個主要階段 - app/prompts/roles/:五個角色定義(Aria/Rex/Zara/Leo/Maya)
This commit is contained in:
+11
-6
@@ -1,10 +1,15 @@
|
||||
FROM alpine:latest
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 安裝必要的工具
|
||||
RUN apk add --no-cache --no-check-certificate bash
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /action
|
||||
|
||||
COPY app/ /action/app/
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN chmod +x /entrypoint.sh
|
||||
RUN pip install --no-cache-dir -r /action/app/requirements.txt && \
|
||||
chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
+152
-14
@@ -1,20 +1,158 @@
|
||||
name: 'Docker Action Template'
|
||||
description: 'Docker Action 範本'
|
||||
name: 'AI Code Review'
|
||||
description: 'AI 多角色 Code Review Action,自動分析 PR 並發布問題 Comment'
|
||||
author: 'Jeffery'
|
||||
inputs:
|
||||
runner_token:
|
||||
description: 'Gitea Runner Token'
|
||||
required: true
|
||||
text:
|
||||
description: '輸入的文字'
|
||||
default: "Hello, World!"
|
||||
outputs:
|
||||
text:
|
||||
description: '輸出的文字'
|
||||
# Gitea 相關(可從 gitea context 自動取得)
|
||||
GITEA_TOKEN:
|
||||
description: 'Gitea API Token'
|
||||
required: false
|
||||
GITEA_SERVER_URL:
|
||||
description: 'Gitea Server URL'
|
||||
required: false
|
||||
GITEA_REPOSITORY:
|
||||
description: 'Gitea Repository (owner/repo)'
|
||||
required: false
|
||||
PR_NUMBER:
|
||||
description: 'Pull Request Number'
|
||||
required: false
|
||||
PR_HEAD_BRANCH:
|
||||
description: 'PR 來源分支'
|
||||
required: false
|
||||
PR_BASE_BRANCH:
|
||||
description: 'PR 目標分支'
|
||||
required: false
|
||||
|
||||
# OpenAI-compatible
|
||||
OPENAI_API_KEY:
|
||||
description: 'OpenAI / OpenRouter API Key'
|
||||
required: false
|
||||
OPENAI_BASE_URL:
|
||||
description: 'OpenAI-compatible Base URL'
|
||||
required: false
|
||||
OPENAI_MODEL:
|
||||
description: 'OpenAI-compatible Model Name'
|
||||
required: false
|
||||
|
||||
# Anthropic Claude
|
||||
CLAUDE_API_KEY:
|
||||
description: 'Anthropic Claude API Key'
|
||||
required: false
|
||||
CLAUDE_BASE_URL:
|
||||
description: 'Claude Base URL'
|
||||
required: false
|
||||
CLAUDE_MODEL:
|
||||
description: 'Claude Model Name'
|
||||
required: false
|
||||
|
||||
# Google Gemini
|
||||
GEMINI_API_KEY:
|
||||
description: 'Google Gemini API Key'
|
||||
required: false
|
||||
GEMINI_BASE_URL:
|
||||
description: 'Gemini Base URL'
|
||||
required: false
|
||||
GEMINI_MODEL:
|
||||
description: 'Gemini Model Name'
|
||||
required: false
|
||||
|
||||
# Ollama
|
||||
OLLAMA_BASE_URL:
|
||||
description: 'Ollama Base URL'
|
||||
required: false
|
||||
OLLAMA_MODEL:
|
||||
description: 'Ollama Model Name'
|
||||
required: false
|
||||
|
||||
# Amazon Q
|
||||
AMAZONQ_API_KEY:
|
||||
description: 'Amazon Q API Key'
|
||||
required: false
|
||||
AMAZONQ_BASE_URL:
|
||||
description: 'Amazon Q Base URL'
|
||||
required: false
|
||||
|
||||
# SonarQube
|
||||
SONARQUBE_TOKEN:
|
||||
description: 'SonarQube Token'
|
||||
required: false
|
||||
SONARQUBE_URL:
|
||||
description: 'SonarQube URL'
|
||||
required: false
|
||||
|
||||
# Kilo Code
|
||||
KILO_API_KEY:
|
||||
description: 'Kilo Code API Key'
|
||||
required: false
|
||||
KILO_BASE_URL:
|
||||
description: 'Kilo Code Base URL'
|
||||
required: false
|
||||
|
||||
# Roo Code
|
||||
ROO_API_KEY:
|
||||
description: 'Roo Code API Key'
|
||||
required: false
|
||||
ROO_BASE_URL:
|
||||
description: 'Roo Code Base URL'
|
||||
required: false
|
||||
|
||||
# Cline
|
||||
CLINE_API_KEY:
|
||||
description: 'Cline API Key'
|
||||
required: false
|
||||
CLINE_BASE_URL:
|
||||
description: 'Cline Base URL'
|
||||
required: false
|
||||
|
||||
# Continue
|
||||
CONTINUE_API_KEY:
|
||||
description: 'Continue API Key'
|
||||
required: false
|
||||
CONTINUE_BASE_URL:
|
||||
description: 'Continue Base URL'
|
||||
required: false
|
||||
|
||||
# Kade
|
||||
KADE_API_KEY:
|
||||
description: 'Kade API Key'
|
||||
required: false
|
||||
KADE_BASE_URL:
|
||||
description: 'Kade Base URL'
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
env:
|
||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
RUNNER_TOKEN: ${{ inputs.runner_token || secrets.GITEA_TOKEN || secrets.RUNNER_TOKEN }}
|
||||
# Gitea context(優先用 inputs,否則從 gitea context 取)
|
||||
GITEA_TOKEN: ${{ inputs.GITEA_TOKEN || secrets.GITEA_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ inputs.GITEA_SERVER_URL || gitea.server_url }}
|
||||
GITEA_REPOSITORY: ${{ inputs.GITEA_REPOSITORY || gitea.repository }}
|
||||
PR_NUMBER: ${{ inputs.PR_NUMBER || gitea.event.pull_request.number }}
|
||||
PR_HEAD_BRANCH: ${{ inputs.PR_HEAD_BRANCH || gitea.event.pull_request.head.ref }}
|
||||
PR_BASE_BRANCH: ${{ inputs.PR_BASE_BRANCH || gitea.event.pull_request.base.ref }}
|
||||
# LLM
|
||||
OPENAI_API_KEY: ${{ inputs.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ inputs.OPENAI_BASE_URL }}
|
||||
OPENAI_MODEL: ${{ inputs.OPENAI_MODEL }}
|
||||
CLAUDE_API_KEY: ${{ inputs.CLAUDE_API_KEY }}
|
||||
CLAUDE_BASE_URL: ${{ inputs.CLAUDE_BASE_URL }}
|
||||
CLAUDE_MODEL: ${{ inputs.CLAUDE_MODEL }}
|
||||
GEMINI_API_KEY: ${{ inputs.GEMINI_API_KEY }}
|
||||
GEMINI_BASE_URL: ${{ inputs.GEMINI_BASE_URL }}
|
||||
GEMINI_MODEL: ${{ inputs.GEMINI_MODEL }}
|
||||
OLLAMA_BASE_URL: ${{ inputs.OLLAMA_BASE_URL }}
|
||||
OLLAMA_MODEL: ${{ inputs.OLLAMA_MODEL }}
|
||||
AMAZONQ_API_KEY: ${{ inputs.AMAZONQ_API_KEY }}
|
||||
AMAZONQ_BASE_URL: ${{ inputs.AMAZONQ_BASE_URL }}
|
||||
SONARQUBE_TOKEN: ${{ inputs.SONARQUBE_TOKEN }}
|
||||
SONARQUBE_URL: ${{ inputs.SONARQUBE_URL }}
|
||||
KILO_API_KEY: ${{ inputs.KILO_API_KEY }}
|
||||
KILO_BASE_URL: ${{ inputs.KILO_BASE_URL }}
|
||||
ROO_API_KEY: ${{ inputs.ROO_API_KEY }}
|
||||
ROO_BASE_URL: ${{ inputs.ROO_BASE_URL }}
|
||||
CLINE_API_KEY: ${{ inputs.CLINE_API_KEY }}
|
||||
CLINE_BASE_URL: ${{ inputs.CLINE_BASE_URL }}
|
||||
CONTINUE_API_KEY: ${{ inputs.CONTINUE_API_KEY }}
|
||||
CONTINUE_BASE_URL: ${{ inputs.CONTINUE_BASE_URL }}
|
||||
KADE_API_KEY: ${{ inputs.KADE_API_KEY }}
|
||||
KADE_BASE_URL: ${{ inputs.KADE_BASE_URL }}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
|
||||
# Gitea
|
||||
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
|
||||
GITEA_SERVER_URL = os.environ.get("GITEA_SERVER_URL", "https://gitea.com")
|
||||
GITEA_REPOSITORY = os.environ.get("GITEA_REPOSITORY", "")
|
||||
PR_NUMBER = os.environ.get("PR_NUMBER", "")
|
||||
PR_HEAD_BRANCH = os.environ.get("PR_HEAD_BRANCH", "")
|
||||
PR_BASE_BRANCH = os.environ.get("PR_BASE_BRANCH", "")
|
||||
|
||||
FINDINGS_PATH = ".gitea/ai-review/findings.json"
|
||||
|
||||
|
||||
def get_llm_config():
|
||||
"""依優先順序偵測可用的 LLM,回傳 (provider, api_key, base_url, model)"""
|
||||
checks = [
|
||||
("openai", os.environ.get("OPENAI_API_KEY"), os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"), os.environ.get("OPENAI_MODEL", "gpt-4o-mini")),
|
||||
("claude", os.environ.get("CLAUDE_API_KEY"), os.environ.get("CLAUDE_BASE_URL", "https://api.anthropic.com/v1"), os.environ.get("CLAUDE_MODEL", "claude-3-haiku-20240307")),
|
||||
("gemini", os.environ.get("GEMINI_API_KEY"), os.environ.get("GEMINI_BASE_URL", "https://generativelanguage.googleapis.com/v1beta"), os.environ.get("GEMINI_MODEL", "gemini-1.5-flash")),
|
||||
("ollama", "ollama", os.environ.get("OLLAMA_BASE_URL", ""), os.environ.get("OLLAMA_MODEL", "")),
|
||||
("amazonq", os.environ.get("AMAZONQ_API_KEY"), os.environ.get("AMAZONQ_BASE_URL", "https://q.api.aws"), os.environ.get("OPENAI_MODEL", "amazon-q")),
|
||||
("kilo", os.environ.get("KILO_API_KEY"), os.environ.get("KILO_BASE_URL", "https://api.kilocode.com/v1"), os.environ.get("OPENAI_MODEL", "kilo-default")),
|
||||
("roo", os.environ.get("ROO_API_KEY"), os.environ.get("ROO_BASE_URL", "https://api.roocode.com/v1"), os.environ.get("OPENAI_MODEL", "roo-default")),
|
||||
("cline", os.environ.get("CLINE_API_KEY"), os.environ.get("CLINE_BASE_URL", "https://api.cline.dev/v1"), os.environ.get("OPENAI_MODEL", "cline-default")),
|
||||
("continue", os.environ.get("CONTINUE_API_KEY"), os.environ.get("CONTINUE_BASE_URL", "https://api.continue.dev/v1"), os.environ.get("OPENAI_MODEL", "continue-default")),
|
||||
("kade", os.environ.get("KADE_API_KEY"), os.environ.get("KADE_BASE_URL", "https://api.kade.dev/v1"), os.environ.get("OPENAI_MODEL", "kade-default")),
|
||||
]
|
||||
for provider, key, base_url, model in checks:
|
||||
if key and base_url:
|
||||
return provider, key, base_url, model
|
||||
return None, None, None, None
|
||||
@@ -0,0 +1,26 @@
|
||||
import requests
|
||||
from config import GITEA_TOKEN, GITEA_SERVER_URL, GITEA_REPOSITORY, PR_NUMBER
|
||||
|
||||
|
||||
def _headers():
|
||||
return {"Authorization": f"token {GITEA_TOKEN}", "Content-Type": "application/json"}
|
||||
|
||||
|
||||
def _api(path: str) -> str:
|
||||
return f"{GITEA_SERVER_URL.rstrip('/')}/api/v1{path}"
|
||||
|
||||
|
||||
def get_pr_diff() -> str:
|
||||
"""取得 PR 的 git diff 內容"""
|
||||
url = _api(f"/repos/{GITEA_REPOSITORY}/pulls/{PR_NUMBER}.diff")
|
||||
resp = requests.get(url, headers=_headers(), timeout=60)
|
||||
resp.raise_for_status()
|
||||
return resp.text
|
||||
|
||||
|
||||
def post_comment(body: str) -> dict:
|
||||
"""在 PR 發布 comment"""
|
||||
url = _api(f"/repos/{GITEA_REPOSITORY}/issues/{PR_NUMBER}/comments")
|
||||
resp = requests.post(url, headers=_headers(), json={"body": body}, timeout=30)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import json
|
||||
import requests
|
||||
from config import get_llm_config
|
||||
|
||||
|
||||
def chat(system_prompt: str, user_content: str) -> str:
|
||||
"""呼叫 LLM,回傳回應文字。失敗時拋出例外。"""
|
||||
provider, api_key, base_url, model = get_llm_config()
|
||||
if not provider:
|
||||
raise RuntimeError("未設定任何 LLM API Key")
|
||||
|
||||
print(f" [LLM] provider={provider} model={model}")
|
||||
|
||||
# 所有服務統一用 OpenAI-compatible chat completions 介面
|
||||
url = f"{base_url.rstrip('/')}/chat/completions"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
}
|
||||
# Claude 額外 header
|
||||
if provider == "claude":
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_content},
|
||||
],
|
||||
"temperature": 0.2,
|
||||
}
|
||||
|
||||
resp = requests.post(url, headers=headers, json=payload, timeout=120)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["choices"][0]["message"]["content"]
|
||||
|
||||
|
||||
def chat_json(system_prompt: str, user_content: str) -> list:
|
||||
"""呼叫 LLM 並解析 JSON 陣列回應。失敗時回傳空陣列。"""
|
||||
try:
|
||||
text = chat(system_prompt, user_content)
|
||||
# 去除可能的 markdown code block
|
||||
text = text.strip()
|
||||
if text.startswith("```"):
|
||||
text = text.split("\n", 1)[1]
|
||||
text = text.rsplit("```", 1)[0]
|
||||
return json.loads(text)
|
||||
except Exception as e:
|
||||
print(f" [LLM] 解析失敗: {e}")
|
||||
return []
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import sys
|
||||
import traceback
|
||||
from config import GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, get_llm_config
|
||||
from roles import load_roles, get_role_intro
|
||||
from gitea import get_pr_diff, post_comment
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("🚀 Step1: Pipeline 啟動")
|
||||
print(f" repo={GITEA_REPOSITORY} PR=#{PR_NUMBER}")
|
||||
print(f" {PR_HEAD_BRANCH} -> {PR_BASE_BRANCH}")
|
||||
|
||||
# 偵測 LLM
|
||||
provider, _, base_url, model = get_llm_config()
|
||||
if not provider:
|
||||
print("❌ 未設定任何 LLM API Key,請檢查 action inputs")
|
||||
sys.exit(1)
|
||||
print(f" LLM: provider={provider} model={model} base_url={base_url}")
|
||||
|
||||
# 載入角色
|
||||
roles = load_roles()
|
||||
print(f" 已載入 {len(roles)} 個角色: {[r['name'] for r in roles]}")
|
||||
|
||||
# 取得 PR diff
|
||||
print("\n📋 Step1: 取得 PR Diff")
|
||||
try:
|
||||
diff = get_pr_diff()
|
||||
print(f" diff 長度: {len(diff)} 字元")
|
||||
except Exception as e:
|
||||
print(f" ❌ 取得 diff 失敗: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if not diff.strip():
|
||||
print(" ⚠️ diff 為空,無需審查")
|
||||
sys.exit(0)
|
||||
|
||||
# 發布角色介紹 comment
|
||||
print("\n💬 Step1: 發布角色介紹 Comment")
|
||||
try:
|
||||
intro = get_role_intro(roles)
|
||||
intro += f"\n\n> 🔍 服務:{provider} 模型:{model}"
|
||||
post_comment(intro)
|
||||
print(" ✅ 角色介紹 comment 發布成功")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ comment 發布失敗(繼續執行): {e}")
|
||||
|
||||
print("\n📊 Step2: Findings 產生(待實作)")
|
||||
print(" [stub] 各角色分析 diff...")
|
||||
|
||||
print("\n🔀 Step3: Findings 合併與去重(待實作)")
|
||||
print(" [stub] 合併新舊 findings...")
|
||||
|
||||
print("\n📝 Step4: Findings 寫入與 Comment 發布(待實作)")
|
||||
print(" [stub] 寫入 findings.json,發布 comment...")
|
||||
|
||||
print("\n💾 Step5: 記憶區 Commit/Push(待實作)")
|
||||
print(" [stub] commit & push findings.json...")
|
||||
|
||||
print("\n🚦 Step6: 嚴重問題檢查(待實作)")
|
||||
print(" [stub] 檢查 critical findings...")
|
||||
|
||||
print("\n✅ Pipeline 完成")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception:
|
||||
print("❌ Runner failed:")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,23 @@
|
||||
name: "Leo"
|
||||
role: "可維護性審查員"
|
||||
personality: "有遠見、重視長期維護成本,常常思考「六個月後的自己能看懂嗎?」"
|
||||
focus: "程式碼複雜度、模組化、重複程式碼、文件完整性、錯誤處理、可測試性"
|
||||
system_prompt: |
|
||||
你是 Leo,一位重視長期維護成本的審查員。你的工作是審查程式碼的可維護性,包含複雜度、模組化、重複程式碼、文件完整性、錯誤處理。
|
||||
|
||||
請分析以下 Git Diff,找出所有可維護性相關問題。
|
||||
|
||||
回傳 JSON 陣列,每個問題格式如下:
|
||||
{
|
||||
"level": "critical|warning|info",
|
||||
"role": "Leo",
|
||||
"location": "檔案路徑:行號 或 檔案路徑",
|
||||
"suggestion": "繁體中文的具體修改建議"
|
||||
}
|
||||
|
||||
等級定義:
|
||||
- critical:嚴重影響可維護性,會造成技術債(如超長函式、完全無文件的公開 API)
|
||||
- warning:建議改善的可維護性問題
|
||||
- info:可選的改善建議
|
||||
|
||||
只回傳 JSON 陣列,不要有其他文字。如果沒有問題,回傳空陣列 []。
|
||||
@@ -0,0 +1,23 @@
|
||||
name: "Zara"
|
||||
role: "效能優化專家"
|
||||
personality: "追求極致效能,對任何不必要的資源消耗都感到不舒服,喜歡用數據說話"
|
||||
focus: "時間複雜度、空間複雜度、資料庫查詢效率、快取策略、不必要的重複運算"
|
||||
system_prompt: |
|
||||
你是 Zara,一位追求極致效能的優化專家。你的工作是審查程式碼的效能問題,包含時間複雜度、空間複雜度、資料庫查詢效率、快取策略。
|
||||
|
||||
請分析以下 Git Diff,找出所有效能相關問題。
|
||||
|
||||
回傳 JSON 陣列,每個問題格式如下:
|
||||
{
|
||||
"level": "critical|warning|info",
|
||||
"role": "Zara",
|
||||
"location": "檔案路徑:行號 或 檔案路徑",
|
||||
"suggestion": "繁體中文的具體修改建議"
|
||||
}
|
||||
|
||||
等級定義:
|
||||
- critical:會造成明顯效能瓶頸或系統崩潰的問題(如 N+1 query、無限迴圈風險)
|
||||
- warning:值得優化的效能問題
|
||||
- info:效能最佳實踐建議
|
||||
|
||||
只回傳 JSON 陣列,不要有其他文字。如果沒有問題,回傳空陣列 []。
|
||||
@@ -0,0 +1,23 @@
|
||||
name: "Rex"
|
||||
role: "資安審查員"
|
||||
personality: "謹慎、多疑、對任何潛在風險都保持高度警覺,寧可誤報也不放過漏洞"
|
||||
focus: "安全漏洞、注入攻擊、敏感資料洩漏、認證授權問題、依賴套件風險"
|
||||
system_prompt: |
|
||||
你是 Rex,一位謹慎的資安審查員。你的工作是審查程式碼中的安全漏洞、注入攻擊風險、敏感資料洩漏、認證授權問題。
|
||||
|
||||
請分析以下 Git Diff,找出所有安全相關問題。
|
||||
|
||||
回傳 JSON 陣列,每個問題格式如下:
|
||||
{
|
||||
"level": "critical|warning|info",
|
||||
"role": "Rex",
|
||||
"location": "檔案路徑:行號 或 檔案路徑",
|
||||
"suggestion": "繁體中文的具體修改建議"
|
||||
}
|
||||
|
||||
等級定義:
|
||||
- critical:可被直接利用的安全漏洞(如 SQL injection、hardcoded secret、RCE)
|
||||
- warning:潛在安全風險,需要關注
|
||||
- info:安全最佳實踐建議
|
||||
|
||||
只回傳 JSON 陣列,不要有其他文字。如果沒有問題,回傳空陣列 []。
|
||||
@@ -0,0 +1,23 @@
|
||||
name: "Aria"
|
||||
role: "程式碼風格審查員"
|
||||
personality: "嚴謹、注重細節、對程式碼整潔度有高度要求,說話直接但不失禮貌"
|
||||
focus: "程式碼風格、命名規範、格式一致性、可讀性"
|
||||
system_prompt: |
|
||||
你是 Aria,一位嚴謹的程式碼風格審查員。你的工作是審查程式碼的風格、命名規範、格式一致性與可讀性。
|
||||
|
||||
請分析以下 Git Diff,找出所有風格相關問題。
|
||||
|
||||
回傳 JSON 陣列,每個問題格式如下:
|
||||
{
|
||||
"level": "critical|warning|info",
|
||||
"role": "Aria",
|
||||
"location": "檔案路徑:行號 或 檔案路徑",
|
||||
"suggestion": "繁體中文的具體修改建議"
|
||||
}
|
||||
|
||||
等級定義:
|
||||
- critical:嚴重違反規範,會影響團隊協作或工具運作
|
||||
- warning:建議修正的風格問題
|
||||
- info:可選的改善建議
|
||||
|
||||
只回傳 JSON 陣列,不要有其他文字。如果沒有問題,回傳空陣列 []。
|
||||
@@ -0,0 +1,23 @@
|
||||
name: "Maya"
|
||||
role: "測試品質審查員"
|
||||
personality: "對測試覆蓋率有執念,相信沒有測試的程式碼等於沒有完成,溫和但堅持"
|
||||
focus: "測試覆蓋率、測試品質、邊界條件、錯誤情境測試、測試可讀性"
|
||||
system_prompt: |
|
||||
你是 Maya,一位對測試品質有高度要求的審查員。你的工作是審查程式碼的測試覆蓋率、測試品質、邊界條件處理。
|
||||
|
||||
請分析以下 Git Diff,找出所有測試相關問題。
|
||||
|
||||
回傳 JSON 陣列,每個問題格式如下:
|
||||
{
|
||||
"level": "critical|warning|info",
|
||||
"role": "Maya",
|
||||
"location": "檔案路徑:行號 或 檔案路徑",
|
||||
"suggestion": "繁體中文的具體修改建議"
|
||||
}
|
||||
|
||||
等級定義:
|
||||
- critical:完全缺少測試的核心功能,或測試邏輯有嚴重錯誤
|
||||
- warning:測試覆蓋不足或測試品質有待改善
|
||||
- info:測試最佳實踐建議
|
||||
|
||||
只回傳 JSON 陣列,不要有其他文字。如果沒有問題,回傳空陣列 []。
|
||||
@@ -0,0 +1,3 @@
|
||||
requests==2.31.0
|
||||
pyyaml==6.0.1
|
||||
openai==1.12.0
|
||||
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
import yaml
|
||||
|
||||
ROLES_DIR = "/action/app/prompts/roles"
|
||||
|
||||
|
||||
def load_roles() -> list[dict]:
|
||||
"""載入所有角色定義"""
|
||||
roles = []
|
||||
for fname in sorted(os.listdir(ROLES_DIR)):
|
||||
if fname.endswith(".yaml"):
|
||||
with open(os.path.join(ROLES_DIR, fname), "r", encoding="utf-8") as f:
|
||||
roles.append(yaml.safe_load(f))
|
||||
return roles
|
||||
|
||||
|
||||
def get_role_intro(roles: list[dict]) -> str:
|
||||
"""產生角色介紹文字(用於 comment)"""
|
||||
lines = ["## 🤖 AI Code Review 團隊", ""]
|
||||
for r in roles:
|
||||
lines.append(f"- **{r['name']}** ({r['role']}): {r['personality']}")
|
||||
return "\n".join(lines)
|
||||
+5
-8
@@ -1,11 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Gitea Server Url: $GITEA_SERVER_URL"
|
||||
echo "🚀 AI Code Review Action 啟動"
|
||||
echo "Repository: $GITEA_REPOSITORY"
|
||||
echo "PR: #$PR_NUMBER ($PR_HEAD_BRANCH -> $PR_BASE_BRANCH)"
|
||||
|
||||
echo "Gitea Repository: $GITEA_REPOSITORY"
|
||||
|
||||
echo "Gitea Runner Token: $RUNNER_TOKEN"
|
||||
|
||||
echo "Input Text: $INPUT_TEXT"
|
||||
|
||||
echo "text=$INPUT_TEXT" >> "$GITHUB_OUTPUT"
|
||||
exec python /action/app/main.py
|
||||
|
||||
Reference in New Issue
Block a user