feat: 階段一 - 改用 Node.js 實作基本流程骨架
- Dockerfile: 改用 node:20-slim - entrypoint.sh: 執行 app/main.js - app/package.json: axios + js-yaml + openai - app/config.js: 環境變數與 LLM 自動偵測(10 種服務) - app/llm.js: OpenAI-compatible 統一介面 - app/gitea.js: PR diff 取得與 comment 發布 - app/roles.js: 從 prompts/roles/*.yaml 載入角色 - app/main.js: pipeline 骨架,log 每個主要階段
This commit is contained in:
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11-slim
|
FROM node:20-slim
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
git \
|
git \
|
||||||
@@ -9,7 +9,7 @@ WORKDIR /action
|
|||||||
COPY app/ /action/app/
|
COPY app/ /action/app/
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -r /action/app/requirements.txt && \
|
RUN cd /action/app && npm install && \
|
||||||
chmod +x /entrypoint.sh
|
chmod +x /entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
export const GITEA_TOKEN = process.env.GITEA_TOKEN || '';
|
||||||
|
export const GITEA_SERVER_URL = process.env.GITEA_SERVER_URL || 'https://gitea.com';
|
||||||
|
export const GITEA_REPOSITORY = process.env.GITEA_REPOSITORY || '';
|
||||||
|
export const PR_NUMBER = process.env.PR_NUMBER || '';
|
||||||
|
export const PR_HEAD_BRANCH = process.env.PR_HEAD_BRANCH || '';
|
||||||
|
export const PR_BASE_BRANCH = process.env.PR_BASE_BRANCH || '';
|
||||||
|
|
||||||
|
export const FINDINGS_PATH = '.gitea/ai-review/findings.json';
|
||||||
|
|
||||||
|
export function getLLMConfig() {
|
||||||
|
const checks = [
|
||||||
|
['openai', process.env.OPENAI_API_KEY, process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1', process.env.OPENAI_MODEL || 'gpt-4o-mini'],
|
||||||
|
['claude', process.env.CLAUDE_API_KEY, process.env.CLAUDE_BASE_URL || 'https://api.anthropic.com/v1', process.env.CLAUDE_MODEL || 'claude-3-haiku-20240307'],
|
||||||
|
['gemini', process.env.GEMINI_API_KEY, process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta', process.env.GEMINI_MODEL || 'gemini-1.5-flash'],
|
||||||
|
['ollama', 'ollama', process.env.OLLAMA_BASE_URL, process.env.OLLAMA_MODEL],
|
||||||
|
['amazonq', process.env.AMAZONQ_API_KEY, process.env.AMAZONQ_BASE_URL || 'https://q.api.aws', process.env.OPENAI_MODEL || 'amazon-q'],
|
||||||
|
['kilo', process.env.KILO_API_KEY, process.env.KILO_BASE_URL || 'https://api.kilocode.com/v1', process.env.OPENAI_MODEL || 'kilo-default'],
|
||||||
|
['roo', process.env.ROO_API_KEY, process.env.ROO_BASE_URL || 'https://api.roocode.com/v1', process.env.OPENAI_MODEL || 'roo-default'],
|
||||||
|
['cline', process.env.CLINE_API_KEY, process.env.CLINE_BASE_URL || 'https://api.cline.dev/v1', process.env.OPENAI_MODEL || 'cline-default'],
|
||||||
|
['continue', process.env.CONTINUE_API_KEY, process.env.CONTINUE_BASE_URL || 'https://api.continue.dev/v1', process.env.OPENAI_MODEL || 'continue-default'],
|
||||||
|
['kade', process.env.KADE_API_KEY, process.env.KADE_BASE_URL || 'https://api.kade.dev/v1', process.env.OPENAI_MODEL || 'kade-default'],
|
||||||
|
];
|
||||||
|
for (const [provider, key, baseURL, model] of checks) {
|
||||||
|
if (key && baseURL) return { provider, apiKey: key, baseURL, model };
|
||||||
|
}
|
||||||
|
return { provider: null, apiKey: null, baseURL: null, model: null };
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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,15 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { GITEA_TOKEN, GITEA_SERVER_URL, GITEA_REPOSITORY, PR_NUMBER } from './config.js';
|
||||||
|
|
||||||
|
const headers = () => ({ Authorization: `token ${GITEA_TOKEN}`, 'Content-Type': 'application/json' });
|
||||||
|
const api = (path) => `${GITEA_SERVER_URL.replace(/\/$/, '')}/api/v1${path}`;
|
||||||
|
|
||||||
|
export async function getPRDiff() {
|
||||||
|
const resp = await axios.get(api(`/repos/${GITEA_REPOSITORY}/pulls/${PR_NUMBER}.diff`), { headers: headers(), timeout: 60000 });
|
||||||
|
return resp.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postComment(body) {
|
||||||
|
const resp = await axios.post(api(`/repos/${GITEA_REPOSITORY}/issues/${PR_NUMBER}/comments`), { body }, { headers: headers(), timeout: 30000 });
|
||||||
|
return resp.data;
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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()
|
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { getLLMConfig } from './config.js';
|
||||||
|
|
||||||
|
export async function chat(systemPrompt, userContent) {
|
||||||
|
const { provider, apiKey, baseURL, model } = getLLMConfig();
|
||||||
|
if (!provider) throw new Error('未設定任何 LLM API Key');
|
||||||
|
|
||||||
|
console.log(` [LLM] provider=${provider} model=${model}`);
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${apiKey}`,
|
||||||
|
};
|
||||||
|
if (provider === 'claude') headers['anthropic-version'] = '2023-06-01';
|
||||||
|
|
||||||
|
const resp = await axios.post(
|
||||||
|
`${baseURL.replace(/\/$/, '')}/chat/completions`,
|
||||||
|
{ model, messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userContent }], temperature: 0.2 },
|
||||||
|
{ headers, timeout: 120000 }
|
||||||
|
);
|
||||||
|
return resp.data.choices[0].message.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function chatJSON(systemPrompt, userContent) {
|
||||||
|
try {
|
||||||
|
let text = await chat(systemPrompt, userContent);
|
||||||
|
text = text.trim().replace(/^```[^\n]*\n?/, '').replace(/```$/, '').trim();
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` [LLM] 解析失敗: ${e.message}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
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 []
|
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig } from './config.js';
|
||||||
|
import { loadRoles, getRoleIntro } from './roles.js';
|
||||||
|
import { getPRDiff, postComment } from './gitea.js';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
console.log('🚀 Step1: Pipeline 啟動');
|
||||||
|
console.log(` repo=${GITEA_REPOSITORY} PR=#${PR_NUMBER}`);
|
||||||
|
console.log(` ${PR_HEAD_BRANCH} -> ${PR_BASE_BRANCH}`);
|
||||||
|
|
||||||
|
// 偵測 LLM
|
||||||
|
const { provider, baseURL, model } = getLLMConfig();
|
||||||
|
if (!provider) {
|
||||||
|
console.error('❌ 未設定任何 LLM API Key,請檢查 action inputs');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(` LLM: provider=${provider} model=${model} base_url=${baseURL}`);
|
||||||
|
|
||||||
|
// 載入角色
|
||||||
|
const roles = loadRoles();
|
||||||
|
console.log(` 已載入 ${roles.length} 個角色: [${roles.map(r => r.name).join(', ')}]`);
|
||||||
|
|
||||||
|
// 取得 PR diff
|
||||||
|
console.log('\n📋 Step1: 取得 PR Diff');
|
||||||
|
let diff;
|
||||||
|
try {
|
||||||
|
diff = await getPRDiff();
|
||||||
|
console.log(` diff 長度: ${diff.length} 字元`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(` ❌ 取得 diff 失敗: ${e.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!diff.trim()) {
|
||||||
|
console.log(' ⚠️ diff 為空,無需審查');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 發布角色介紹 comment
|
||||||
|
console.log('\n💬 Step1: 發布角色介紹 Comment');
|
||||||
|
try {
|
||||||
|
const intro = getRoleIntro(roles) + `\n\n> 🔍 服務:${provider} 模型:${model}`;
|
||||||
|
await postComment(intro);
|
||||||
|
console.log(' ✅ 角色介紹 comment 發布成功');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` ⚠️ comment 發布失敗(繼續執行): ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n📊 Step2: Findings 產生(待實作)');
|
||||||
|
console.log(' [stub] 各角色分析 diff...');
|
||||||
|
|
||||||
|
console.log('\n🔀 Step3: Findings 合併與去重(待實作)');
|
||||||
|
console.log(' [stub] 合併新舊 findings...');
|
||||||
|
|
||||||
|
console.log('\n📝 Step4: Findings 寫入與 Comment 發布(待實作)');
|
||||||
|
console.log(' [stub] 寫入 findings.json,發布 comment...');
|
||||||
|
|
||||||
|
console.log('\n💾 Step5: 記憶區 Commit/Push(待實作)');
|
||||||
|
console.log(' [stub] commit & push findings.json...');
|
||||||
|
|
||||||
|
console.log('\n🚦 Step6: 嚴重問題檢查(待實作)');
|
||||||
|
console.log(' [stub] 檢查 critical findings...');
|
||||||
|
|
||||||
|
console.log('\n✅ Pipeline 完成');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(e => {
|
||||||
|
console.error('❌ Runner failed:', e.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
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,10 @@
|
|||||||
|
{
|
||||||
|
"name": "ai-code-review",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"openai": "^4.28.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
requests==2.31.0
|
|
||||||
pyyaml==6.0.1
|
|
||||||
openai==1.12.0
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
|
const ROLES_DIR = '/action/app/prompts/roles';
|
||||||
|
|
||||||
|
export function loadRoles() {
|
||||||
|
return fs.readdirSync(ROLES_DIR)
|
||||||
|
.filter(f => f.endsWith('.yaml'))
|
||||||
|
.sort()
|
||||||
|
.map(f => yaml.load(fs.readFileSync(path.join(ROLES_DIR, f), 'utf8')));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRoleIntro(roles) {
|
||||||
|
const lines = ['## 🤖 AI Code Review 團隊', ''];
|
||||||
|
for (const r of roles) {
|
||||||
|
lines.push(`- **${r.name}** (${r.role}):${r.personality}`);
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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)
|
|
||||||
+1
-1
@@ -5,4 +5,4 @@ echo "🚀 AI Code Review Action 啟動"
|
|||||||
echo "Repository: $GITEA_REPOSITORY"
|
echo "Repository: $GITEA_REPOSITORY"
|
||||||
echo "PR: #$PR_NUMBER ($PR_HEAD_BRANCH -> $PR_BASE_BRANCH)"
|
echo "PR: #$PR_NUMBER ($PR_HEAD_BRANCH -> $PR_BASE_BRANCH)"
|
||||||
|
|
||||||
exec python /action/app/main.py
|
exec node /action/app/main.js
|
||||||
|
|||||||
Reference in New Issue
Block a user