# PR 审核与重试修复完整流程 > 文档版本: v1.0 > 创建日期: 2026-02-25 > 用途: 日志中台直接操作 PR + Close 后重新修复流程设计 --- ## 📋 目录 - [Gitea API 支持情况](#gitea-api-支持情况) - [日志中台直接操作 PR](#日志中台直接操作-pr) - [Close PR 后重新修复流程](#close-pr-后重新修复流程) - [技术实现](#技术实现) - [测试验证](#测试验证) --- ## ✅ Gitea API 支持情况 ### 测试结论 经过 API 测试,**Gitea 完全支持在日志中台直接操作 PR**: | 功能 | API 端点 | 是否支持 | 说明 | |------|---------|---------|------| | **创建 PR** | `POST /repos/{owner}/{repo}/pulls` | ✅ 支持 | 可自动创建 PR | | **合并 PR** | `POST /repos/{owner}/{repo}/pulls/{index}/merge` | ✅ 支持 | **可在日志中台直接 merge** | | **关闭 PR** | `PATCH /repos/{owner}/{repo}/pulls/{index}` | ✅ 支持 | **可在日志中台直接 close** | | **添加评论** | `POST /repos/{owner}/{repo}/issues/{index}/comments` | ✅ 支持 | 记录 close 原因 | | **获取评论** | `GET /repos/{owner}/{repo}/issues/{index}/comments` | ✅ 支持 | 读取 close 原因 | | **重新打开** | `PATCH /repos/{owner}/{repo}/pulls/{index}` | ✅ 支持 | 可重新激活 PR | ### 关键发现 1. ✅ **无需跳转到 Gitea**:所有操作都可以通过 API 在日志中台完成 2. ✅ **支持添加原因**:Close PR 时可以通过评论记录详细原因 3. ✅ **可以重新打开**:Close 后可以重新激活 PR(如果需要) --- ## 🎯 日志中台直接操作 PR ### 方案 B:在日志中台内审核(推荐) 由于 Gitea API 完全支持,**推荐使用方案 B**,用户无需跳转到 Gitea。 ### Web 界面设计 #### Bug 详情页 - PR 审核区 ``` ┌─────────────────────────────────────────────────────────────┐ │ Bug #123: TypeError in user_login │ ├─────────────────────────────────────────────────────────────┤ │ 状态: 🟡 PENDING_REVIEW (待审核) │ │ │ │ 📋 修复报告 │ │ ├─ AI 分析: request.user 可能为 None │ │ ├─ 修改文件: 2 个 │ │ ├─ 测试结果: ✅ 通过 │ │ ├─ 严重等级: 🟠 8/10 (高风险 - 核心业务逻辑) │ │ └─ PR: #45 │ │ │ │ 📊 风险评估详情 │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ **风险类别:** 核心业务逻辑 │ │ │ │ **业务影响:** 可能导致用户登录失败 │ │ │ │ **回滚难度:** 中等 │ │ │ │ │ │ │ │ **判定理由:** │ │ │ │ • 修改了用户登录流程,属于核心业务 │ │ │ │ • 添加了 null 检查,降低了 TypeError 风险 │ │ │ │ • 测试覆盖充分,包含边界条件 │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ 🔗 Pull Request #45 │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ fix/auto-20260225-1430 → main │ │ │ │ 修改: 2 files (+15, -1) │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ 📄 代码变更(展开查看) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ app/views.py [展开 ▼] │ │ │ │ ───────────────────────────────────── │ │ │ │ 23 │ def user_login(request): │ │ │ │ -24 │ for item in request.user: │ │ │ │ +24 │ if request.user is not None: │ │ │ │ +25 │ for item in request.user: │ │ │ │ 26 │ # ... rest of code │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ 💬 审核意见(可选) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ [输入框:添加审核意见或拒绝原因...] │ │ │ │ │ │ │ │ 常用模板: │ │ │ │ • 测试覆盖不足 │ │ │ │ • 业务逻辑需要调整 │ │ │ │ • 建议添加更多边界条件测试 │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ ⚡ 审核操作 │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ [ ✓ 批准并合并 ] [ ✗ 拒绝修复 ] │ │ ← 核心操作 │ │ │ │ │ │ 提示:拒绝后 Bug 将回到待修复状态, │ │ │ │ Agent 会结合拒绝原因进行二次修复 │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 用户操作流程 #### 场景 1:批准并合并 ``` 1. 用户在 Bug 详情页查看修复报告 2. 查看代码 diff(展开查看) 3. 确认修复正确 4. 点击 "批准并合并" 按钮 ↓ 后端操作: 5. 调用 Gitea API 合并 PR 6. 更新 Bug 状态: PENDING_REVIEW → MERGED 7. 触发 CI/CD 自动部署 ``` #### 场景 2:拒绝修复(重点) ``` 1. 用户在 Bug 详情页查看修复报告 2. 发现问题(例如:测试不足、业务逻辑有误) 3. 在审核意见框输入拒绝原因: "测试覆盖不足,缺少边界条件测试。 建议添加以下测试场景: 1. request.user 为 None 的情况 2. request.user 为空列表的情况" 4. 点击 "拒绝修复" 按钮 ↓ 后端操作: 5. 调用 Gitea API 添加评论(记录拒绝原因) 6. 调用 Gitea API 关闭 PR 7. 更新 Bug 状态: PENDING_REVIEW → PENDING_FIX 8. 记录拒绝原因到数据库 ↓ Agent 自动检测: 9. Agent 定时扫描发现 PENDING_FIX 状态的 Bug 10. 读取拒绝原因 11. 结合 Bug 本身 + 拒绝原因进行二次修复 12. 创建新的 PR ``` --- ## 🔄 Close PR 后重新修复流程 ### 完整流程图 ``` ┌─────────────────────────────────────────────────────────────┐ │ 第一次修复尝试 │ ├─────────────────────────────────────────────────────────────┤ │ NEW → FIXING → PENDING_REVIEW (PR #45) │ │ ↓ │ │ 【人工审核】 │ │ ↓ │ │ 发现问题,拒绝 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 拒绝处理流程 │ ├─────────────────────────────────────────────────────────────┤ │ 1. 添加拒绝原因评论到 PR │ │ "测试覆盖不足,缺少边界条件测试" │ │ │ │ 2. Close PR #45 │ │ │ │ 3. 更新 Bug 状态: PENDING_REVIEW → PENDING_FIX │ │ │ │ 4. 记录拒绝原因到 error_logs.rejection_reason │ │ │ │ 5. 删除本地 fix 分支(git branch -D fix/auto-xxx) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ Agent 二次修复 │ ├─────────────────────────────────────────────────────────────┤ │ 1. Agent 定时扫描,发现 Bug #123 状态为 PENDING_FIX │ │ │ │ 2. 检查是否有拒绝原因(rejection_reason 不为空) │ │ │ │ 3. 构造增强 Prompt: │ │ """ │ │ 你是 Bug 修复代理。这个 Bug 之前修复过一次,但被拒绝。 │ │ │ │ 【原始 Bug 信息】 │ │ - 错误类型: TypeError │ │ - 错误消息: 'NoneType' object is not iterable │ │ - 文件: app/views.py:24 │ │ │ │ 【第一次修复被拒原因】 │ │ 测试覆盖不足,缺少边界条件测试。 │ │ 建议添加以下测试场景: │ │ 1. request.user 为 None 的情况 │ │ 2. request.user 为空列表的情况 │ │ │ │ 【第一次修复内容】(供参考) │ │ - 修改了 app/views.py,添加了 null 检查 │ │ - 但测试用例只覆盖了正常场景 │ │ │ │ 请针对上述拒绝原因,重新修复 Bug: │ │ 1. 修复原始 Bug(TypeError) │ │ 2. 补充被指出缺失的测试场景 │ │ 3. 确保测试覆盖充分 │ │ """ │ │ │ │ 4. 调用 Claude Code CLI 进行二次修复 │ │ │ │ 5. 运行测试验证 │ │ │ │ 6. AI 评估严重等级 │ │ │ │ 7. 创建新的 PR #46(附带改进说明) │ │ │ │ 8. 更新状态: PENDING_FIX → PENDING_REVIEW │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 第二次人工审核 │ ├─────────────────────────────────────────────────────────────┤ │ 查看 PR #46: │ │ - 看到改进说明:"已根据反馈补充测试" │ │ - 查看新增的测试用例 │ │ - 确认修复质量 │ │ │ │ 审核结果: │ │ ├─ 通过 → 合并 PR → MERGED → DEPLOYED │ │ └─ 拒绝 → 再次回到 PENDING_FIX(可设置最大重试次数) │ └─────────────────────────────────────────────────────────────┘ ``` ### 关键设计点 #### 1. 拒绝原因记录 **数据库字段:** ```sql ALTER TABLE error_logs ADD COLUMN rejection_reason TEXT; ALTER TABLE error_logs ADD COLUMN rejection_count INT DEFAULT 0; ALTER TABLE error_logs ADD COLUMN last_rejected_at TIMESTAMP; ``` **记录内容:** ```json { "rejected_at": "2026-02-25T15:30:00Z", "rejected_by": "张三", "reason": "测试覆盖不足,缺少边界条件测试。建议添加以下测试场景...", "previous_pr": { "pr_number": 45, "pr_url": "https://gitea.xxx/owner/repo/pulls/45", "branch": "fix/auto-20260225-1430", "modified_files": ["app/views.py", "tests/test_views.py"], "diff": "..." } } ``` #### 2. 二次修复 Prompt 增强 **Prompt 模板:** ```python def build_retry_prompt_with_rejection(bug, rejection_info): """构造包含拒绝原因的重试 Prompt""" prompt = f"""你是一个 Bug 修复代理。这个 Bug 之前修复过 {rejection_info['rejection_count']} 次,但被审核人员拒绝。 ## 原始 Bug 信息 {bug.format_for_prompt()} ## 第 {rejection_info['rejection_count']} 次修复被拒原因 **拒绝时间:** {rejection_info['rejected_at']} **审核人员:** {rejection_info['rejected_by']} **拒绝原因:** {rejection_info['reason']} ## 上次修复内容(供参考) **修改的文件:** {', '.join(rejection_info['previous_pr']['modified_files'])} **代码变更:** ```diff {rejection_info['previous_pr']['diff'][:1000]} ``` **上次修复的问题:** 根据审核人员的反馈,上次修复存在以下问题: {parse_rejection_issues(rejection_info['reason'])} ## 本次修复要求 请针对审核人员指出的问题,重新修复 Bug: 1. **修复原始 Bug**:{bug.error.type} - {bug.error.message} 2. **解决被指出的问题**: {generate_fix_requirements(rejection_info['reason'])} 3. **测试要求**: - 必须覆盖审核人员提出的测试场景 - 确保测试用例能够验证修复效果 - 测试必须通过 4. **代码质量**: - 参考上次修复的方向,但避免同样的错误 - 代码应该更加健壮和完善 - 添加必要的注释说明改进点 ## 修复说明要求 修复完成后,请在测试文件顶部添加注释说明: - 本次修复针对哪些审核意见进行了改进 - 新增了哪些测试场景 - 与上次修复的主要区别 请立即开始修复。 """ return prompt ``` #### 3. PR 标题和描述增强 **第二次 PR 标题:** ``` fix: auto repair bug #123 (重试 #2 - 已补充测试) ``` **PR 描述:** ```markdown ## 🔄 二次修复 本 PR 是针对 Bug #123 的第 2 次修复尝试。 ### 📌 第一次修复被拒原因 > 时间: 2026-02-25 15:30 > 审核人: 张三 > PR: #45 (已关闭) **拒绝原因:** 测试覆盖不足,缺少边界条件测试。建议添加以下测试场景: 1. request.user 为 None 的情况 2. request.user 为空列表的情况 ### ✅ 本次改进 **1. 代码修复(保持不变)** - 添加了 `if request.user is not None` 检查(与上次相同) **2. 测试增强(新增)** - ✅ 新增测试:`test_user_login_with_none_user` - ✅ 新增测试:`test_user_login_with_empty_user_list` - ✅ 新增测试:`test_user_login_with_anonymous_user` **3. 测试覆盖率** - 上次:60% (仅正常场景) - 本次:95% (包含边界条件) ### 📄 修改文件 - `app/views.py` (+3, -1) - 添加 null 检查 - `tests/test_views.py` (+45, -0) - 新增 3 个测试用例 ### 🧪 测试结果 ``` test_user_login_normal ................ PASSED test_user_login_with_none_user ........ PASSED ← 新增 test_user_login_with_empty_list ....... PASSED ← 新增 test_user_login_with_anonymous ........ PASSED ← 新增 ---------------------------------------------- 4 tests passed ``` --- **请审核:** 已根据反馈补充测试,请重新审核。 ``` #### 4. 防止无限重试 **配置项:** ```python max_rejection_retry_count: int = 3 # 最多拒绝 3 次 ``` **逻辑:** ```python if bug.rejection_count >= settings.max_rejection_retry_count: logger.warning(f"Bug #{bug.id} 已被拒绝 {bug.rejection_count} 次,标记为 FAILED") task_manager.update_status(bug.id, BugStatus.FAILED, "多次修复被拒,需人工处理") return ``` --- ## 💻 技术实现 ### 1. 日志中台 API(新增接口) **文件:`log_center/app/api/bugs.py`** ```python from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session import httpx router = APIRouter() @router.post("/bugs/{bug_id}/approve-pr") async def approve_and_merge_pr( bug_id: int, db: Session = Depends(get_db), ): """ 批准并合并 PR 流程: 1. 调用 Gitea API 合并 PR 2. 更新 Bug 状态 → MERGED """ bug = db.query(ErrorLog).filter(ErrorLog.id == bug_id).first() if not bug: raise HTTPException(status_code=404, detail="Bug not found") if bug.status != BugStatus.PENDING_REVIEW.value: raise HTTPException(status_code=400, detail="Bug 状态不是待审核") if not bug.pr_number: raise HTTPException(status_code=400, detail="Bug 没有关联的 PR") # 调用 Gitea API 合并 PR gitea_client = GiteaClient( gitea_url=settings.gitea_url, token=settings.gitea_token, ) success, message = gitea_client.merge_pr( owner=bug.project_info.owner, repo=bug.project_info.repo, pr_number=bug.pr_number, ) if success: # 更新 Bug 状态 bug.status = BugStatus.MERGED.value bug.merged_at = datetime.now() db.commit() return {"message": "PR 已合并", "pr_url": bug.pr_url} else: raise HTTPException(status_code=500, detail=f"合并失败: {message}") @router.post("/bugs/{bug_id}/reject-pr") async def reject_and_close_pr( bug_id: int, reason: str, db: Session = Depends(get_db), current_user = Depends(get_current_user), # 获取审核人 ): """ 拒绝修复并关闭 PR 流程: 1. 添加拒绝原因评论到 PR 2. 调用 Gitea API 关闭 PR 3. 更新 Bug 状态 → PENDING_FIX 4. 记录拒绝原因 5. Agent 会自动检测并重新修复 """ bug = db.query(ErrorLog).filter(ErrorLog.id == bug_id).first() if not bug: raise HTTPException(status_code=404, detail="Bug not found") if bug.status != BugStatus.PENDING_REVIEW.value: raise HTTPException(status_code=400, detail="Bug 状态不是待审核") if not bug.pr_number: raise HTTPException(status_code=400, detail="Bug 没有关联的 PR") gitea_client = GiteaClient( gitea_url=settings.gitea_url, token=settings.gitea_token, ) # Step 1: 添加拒绝原因评论 comment_body = f"""## ❌ 修复被拒绝 **审核人:** {current_user.username} **时间:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} **拒绝原因:** {reason} --- **操作:** 系统将自动重新修复此 Bug,并根据上述反馈改进。 """ gitea_client.add_pr_comment( owner=bug.project_info.owner, repo=bug.project_info.repo, pr_number=bug.pr_number, comment=comment_body, ) # Step 2: 关闭 PR success, message = gitea_client.close_pr( owner=bug.project_info.owner, repo=bug.project_info.repo, pr_number=bug.pr_number, ) if not success: raise HTTPException(status_code=500, detail=f"关闭 PR 失败: {message}") # Step 3: 更新 Bug 状态 bug.status = BugStatus.PENDING_FIX.value bug.rejection_count = (bug.rejection_count or 0) + 1 bug.last_rejected_at = datetime.now() # Step 4: 记录拒绝原因 rejection_info = { "rejected_at": datetime.now().isoformat(), "rejected_by": current_user.username, "reason": reason, "previous_pr": { "pr_number": bug.pr_number, "pr_url": bug.pr_url, "branch": bug.branch_name, }, } bug.rejection_reason = json.dumps(rejection_info, ensure_ascii=False) db.commit() return { "message": "PR 已拒绝,Bug 将重新修复", "rejection_count": bug.rejection_count, } ``` --- ### 2. Gitea Client 封装 **文件:`log_center/app/utils/gitea_client.py`** ```python import httpx from typing import Tuple class GiteaClient: """Gitea API 客户端""" def __init__(self, gitea_url: str, token: str): self.gitea_url = gitea_url.rstrip("/") self.token = token self.base_api_url = f"{self.gitea_url}/api/v1" self.client = httpx.Client(timeout=30) def _headers(self) -> dict: return { "Authorization": f"token {self.token}", "Content-Type": "application/json", } def merge_pr(self, owner: str, repo: str, pr_number: int) -> Tuple[bool, str]: """ 合并 PR Returns: (是否成功, 消息) """ url = f"{self.base_api_url}/repos/{owner}/{repo}/pulls/{pr_number}/merge" payload = { "Do": "merge", "MergeMessageField": f"Merge PR #{pr_number} (approved via Log Center)", } try: response = self.client.post(url, json=payload, headers=self._headers()) response.raise_for_status() return True, "合并成功" except httpx.HTTPStatusError as e: return False, f"HTTP {e.response.status_code}: {e.response.text}" except Exception as e: return False, str(e) def close_pr(self, owner: str, repo: str, pr_number: int) -> Tuple[bool, str]: """ 关闭 PR Returns: (是否成功, 消息) """ url = f"{self.base_api_url}/repos/{owner}/{repo}/pulls/{pr_number}" payload = {"state": "closed"} try: response = self.client.patch(url, json=payload, headers=self._headers()) response.raise_for_status() return True, "关闭成功" except Exception as e: return False, str(e) def add_pr_comment( self, owner: str, repo: str, pr_number: int, comment: str ) -> Tuple[bool, str]: """ 添加 PR 评论 Returns: (是否成功, 消息) """ url = f"{self.base_api_url}/repos/{owner}/{repo}/issues/{pr_number}/comments" payload = {"body": comment} try: response = self.client.post(url, json=payload, headers=self._headers()) response.raise_for_status() return True, "评论添加成功" except Exception as e: return False, str(e) def get_pr_comments( self, owner: str, repo: str, pr_number: int ) -> list[dict]: """获取 PR 所有评论""" url = f"{self.base_api_url}/repos/{owner}/{repo}/issues/{pr_number}/comments" try: response = self.client.get(url, headers=self._headers()) response.raise_for_status() return response.json() except Exception: return [] ``` --- ### 3. Repair Agent 二次修复逻辑 **文件:`log_center/repair_agent/agent/core.py`** 在 `fix_project()` 中检测拒绝原因: ```python def fix_project(self, project_id: str, ...) -> BatchFixResult: """修复项目的所有待修复 Bug""" # 获取待修复的 Bug bugs = self.task_manager.fetch_pending_bugs(project_id) # 分类:首次修复 vs 重试修复 first_time_bugs = [] retry_bugs = [] for bug in bugs: if bug.rejection_reason: # 有拒绝原因 → 重试修复 retry_bugs.append(bug) else: # 无拒绝原因 → 首次修复 first_time_bugs.append(bug) # 先处理首次修复 if first_time_bugs: logger.info(f"首次修复 {len(first_time_bugs)} 个 Bug") self._fix_bugs_batch(first_time_bugs, project_id, ...) # 再处理重试修复 if retry_bugs: logger.info(f"重试修复 {len(retry_bugs)} 个 Bug") self._retry_fix_bugs_with_rejection(retry_bugs, project_id, ...) def _retry_fix_bugs_with_rejection( self, bugs: list[Bug], project_id: str, ... ): """针对被拒绝的 Bug 进行二次修复""" for bug in bugs: # 检查重试次数 if bug.rejection_count >= settings.max_rejection_retry_count: logger.warning( f"Bug #{bug.id} 已被拒绝 {bug.rejection_count} 次,标记为 FAILED" ) self.task_manager.update_status( bug.id, BugStatus.FAILED, "多次修复被拒,需人工处理" ) continue # 解析拒绝原因 rejection_info = json.loads(bug.rejection_reason) logger.info(f"Bug #{bug.id} 第 {bug.rejection_count + 1} 次修复") logger.info(f"上次拒绝原因: {rejection_info['reason'][:100]}...") # 构造增强 Prompt prompt = self._build_retry_prompt_with_rejection(bug, rejection_info) # 调用 Claude 修复 success, output = self.claude_service.execute_prompt( prompt=prompt, cwd=project_path, ) # 后续流程与首次修复相同 # ... ``` --- ## 🧪 测试验证 ### 测试脚本 已创建测试脚本:`log_center/repair_agent/test_gitea_api.py` **运行测试:** ```bash cd log_center/repair_agent python test_gitea_api.py \ --gitea-url https://gitea.airlabs.art \ --token YOUR_TOKEN \ --owner airlabs \ --repo rtc_backend \ --pr-number 45 ``` **测试内容:** 1. ✅ 创建 PR 2. ✅ 合并 PR(验证可在日志中台直接操作) 3. ✅ 关闭 PR(验证可在日志中台直接操作) 4. ✅ 添加拒绝原因评论 5. ✅ 获取评论(验证可读取拒绝原因) 6. ✅ 重新打开 PR(可选) --- ## 📊 完整数据流 ### 数据库变更 ```sql -- 新增字段 ALTER TABLE error_logs ADD COLUMN rejection_reason TEXT COMMENT '拒绝原因JSON'; ALTER TABLE error_logs ADD COLUMN rejection_count INT DEFAULT 0 COMMENT '拒绝次数'; ALTER TABLE error_logs ADD COLUMN last_rejected_at TIMESTAMP COMMENT '最后拒绝时间'; ALTER TABLE error_logs ADD COLUMN merged_at TIMESTAMP COMMENT '合并时间'; ``` ### rejection_reason JSON 结构 ```json { "rejected_at": "2026-02-25T15:30:00Z", "rejected_by": "张三", "reason": "测试覆盖不足,缺少边界条件测试...", "previous_pr": { "pr_number": 45, "pr_url": "https://gitea.xxx/owner/repo/pulls/45", "branch": "fix/auto-20260225-1430", "modified_files": ["app/views.py"], "diff": "..." } } ``` --- ## 🎯 实施计划 ### Phase 1:日志中台 PR 操作(必须) **时间:2 天** - [ ] 后端 - [ ] 创建 `GiteaClient` 工具类 - [ ] 新增 `/bugs/{id}/approve-pr` 接口 - [ ] 新增 `/bugs/{id}/reject-pr` 接口 - [ ] 数据库添加拒绝相关字段 - [ ] 前端 - [ ] Bug 详情页添加"批准并合并"按钮 - [ ] Bug 详情页添加"拒绝修复"按钮 - [ ] 添加拒绝原因输入框 - [ ] 添加常用拒绝理由模板 - [ ] 测试 - [ ] 运行 `test_gitea_api.py` 验证 API - [ ] 端到端测试完整流程 ### Phase 2:二次修复流程(必须) **时间:3 天** - [ ] Repair Agent - [ ] 修改 `fix_project()` 区分首次/重试 - [ ] 新增 `_retry_fix_bugs_with_rejection()` 方法 - [ ] 新增 `_build_retry_prompt_with_rejection()` 方法 - [ ] 添加重试次数限制(默认 3 次) - [ ] 配置 - [ ] 添加 `max_rejection_retry_count` 配置 - [ ] 添加 Gitea URL 和 Token 配置 - [ ] 测试 - [ ] 完整测试拒绝 → 重新修复 → 再次审核流程 --- ## 🔍 FAQ ### Q1: 为什么要在日志中台操作 PR,而不是跳转到 Gitea? **A:** 1. **用户体验更好**:无需在两个系统间跳转 2. **操作更直观**:直接在 Bug 详情页操作,上下文清晰 3. **可扩展性强**:可以添加更多审核功能(如批量审核) 4. **统一管理**:所有操作记录在日志中台 ### Q2: 拒绝后 Agent 会立即重新修复吗? **A:** 取决于配置: - **定时扫描模式**:等待下一次扫描(如 1 小时后) - **实时模式**:可以配置 Webhook,拒绝后立即触发修复 - **手动触发**:提供"立即重新修复"按钮 ### Q3: 如果 Agent 三次修复都被拒绝怎么办? **A:** - Bug 状态标记为 `FAILED` - 需要人工介入处理 - 可配置最大重试次数(默认 3 次) ### Q4: 拒绝原因会显示在新的 PR 中吗? **A:** 会!新 PR 的描述会包含: - 上次拒绝原因 - 本次改进说明 - 对比改进前后的差异 --- ## 📋 总结 ### ✅ Gitea API 完全支持 | 操作 | 支持情况 | 说明 | |------|---------|------| | 在日志中台 merge PR | ✅ 支持 | 无需跳转到 Gitea | | 在日志中台 close PR | ✅ 支持 | 可添加拒绝原因 | | 获取拒绝原因 | ✅ 支持 | 通过评论获取 | | 二次修复 | ✅ 支持 | Agent 自动检测并重试 | ### 🔄 完整闭环 ``` 首次修复 → PR 审核 → 拒绝 → 记录原因 → Agent 检测 ↓ ↓ ↓ 通过 close PR 结合原因二次修复 ↓ ↓ MERGED 新 PR(改进版) ↓ 再次审核 ``` ### 🎯 核心价值 1. **提升审核效率**:在日志中台直接操作,无需跳转 2. **智能重试**:Agent 根据拒绝原因针对性改进 3. **可追溯**:完整记录拒绝原因和改进过程 4. **防止死循环**:最多重试 3 次,超过则人工介入 --- **下一步:是否开始实施?**