name: "AI Code Review (Skill-Driven)" on: pull_request: types: [opened, synchronize] jobs: review_with_skill: runs-on: ubuntu-latest steps: # 1. 拉取代码及 Skill 规则文件 - name: Checkout Code & Skills uses: actions/checkout@v4 with: fetch-depth: 0 # 2. 运行 AI 审查脚本 - name: Run Review Engine env: # 🔑 中转站 API Key(在 Gitea Secrets 配置) API_KEY: ${{ secrets.LLM_API_KEY }} # 🌐 中转站 Base URL(在 Gitea Secrets 配置) BASE_URL: ${{ secrets.LLM_BASE_URL }} # 🤖 选择的模型名称(按需修改) MODEL_NAME: "gpt-4o" # Gitea 自动注入的 Token,无需手动配置 GITEA_TOKEN: ${{ gitea.token }} GITEA_SERVER_URL: ${{ gitea.server_url }} GITEA_REPOSITORY: ${{ gitea.repository }} GITEA_PR_NUMBER: ${{ gitea.event.number }} GITEA_BASE_REF: ${{ gitea.base_ref }} GITEA_HEAD_REF: ${{ gitea.head_ref }} run: | # 提取当前 PR 的代码差异 git diff origin/$GITEA_BASE_REF...origin/$GITEA_HEAD_REF > diff.txt # 使用 Python 执行审查(纯标准库,无需 pip install) python3 -c " import os, json, urllib.request, urllib.error # ========== 1. 读取 Skill 规则书 ========== skill_base_path = '.claude/skills/code-review-skill/' try: skill_config = open(os.path.join(skill_base_path, 'SKILL.md'), encoding='utf-8').read() python_rules = open(os.path.join(skill_base_path, 'reference/python.md'), encoding='utf-8').read() except FileNotFoundError as e: print(f'❌ 未找到 Skill 规则文件: {e}'); exit(1) full_skill_prompt = f'{skill_config}\n\n以下是针对当前语言的补充规范:\n{python_rules}' # ========== 2. 读取代码变动 ========== diff_data = open('diff.txt', encoding='utf-8').read() if not diff_data.strip(): print('✅ 没有检测到代码变更,跳过审查。'); exit(0) # ========== 3. 调用中转站大模型 API ========== base_url = os.getenv('BASE_URL', 'https://api.maxmodel.com/v1').rstrip('/') api_url = f'{base_url}/chat/completions' model_name = os.getenv('MODEL_NAME', 'gpt-4o') print(f'🚀 正在调用模型: {model_name} @ {base_url}') headers = { 'Authorization': f'Bearer {os.getenv(\"API_KEY\")}', 'Content-Type': 'application/json' } payload = { 'model': model_name, 'messages': [ {'role': 'system', 'content': full_skill_prompt}, {'role': 'user', 'content': f'请严格对照上述 Skill 规范,评审以下 FastAPI 代码改动,并用中文输出评审意见:\n\n{diff_data}'} ], 'temperature': 0.2 } req = urllib.request.Request(api_url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST') try: with urllib.request.urlopen(req, timeout=120) as response: res = json.loads(response.read().decode('utf-8')) comment_body = res['choices'][0]['message']['content'] print('✅ 大模型已返回审查意见') except urllib.error.HTTPError as e: error_body = e.read().decode('utf-8') print(f'❌ 请求中转站失败 [{e.code}]: {error_body}'); exit(1) except Exception as e: print(f'❌ 请求中转站异常: {e}'); exit(1) # ========== 4. 发表到 Gitea PR 评论区 ========== gitea_url = f'{os.getenv(\"GITEA_SERVER_URL\")}/api/v1/repos/{os.getenv(\"GITEA_REPOSITORY\")}/issues/{os.getenv(\"GITEA_PR_NUMBER\")}/comments' gitea_headers = { 'Authorization': f'token {os.getenv(\"GITEA_TOKEN\")}', 'Content-Type': 'application/json' } gitea_payload = {'body': f'### 🤖 AI 自动化审查报告 (基于 Code Review Skill)\n\n> 模型: \`{model_name}\` | 中转站: maxmodel\n\n{comment_body}'} gitea_req = urllib.request.Request(gitea_url, data=json.dumps(gitea_payload).encode('utf-8'), headers=gitea_headers, method='POST') try: urllib.request.urlopen(gitea_req, timeout=30) print('🎉 Skill 审查报告已成功发表到 PR!') except Exception as e: print(f'❌ 发表评论失败: {e}'); exit(1) "