33 KiB
PR 审核与重试修复完整流程
文档版本: v1.0 创建日期: 2026-02-25 用途: 日志中台直接操作 PR + Close 后重新修复流程设计
📋 目录
✅ 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 |
关键发现
- ✅ 无需跳转到 Gitea:所有操作都可以通过 API 在日志中台完成
- ✅ 支持添加原因:Close PR 时可以通过评论记录详细原因
- ✅ 可以重新打开: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. 拒绝原因记录
数据库字段:
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;
记录内容:
{
"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 模板:
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:
-
修复原始 Bug:{bug.error.type} - {bug.error.message}
-
解决被指出的问题: {generate_fix_requirements(rejection_info['reason'])}
-
测试要求:
- 必须覆盖审核人员提出的测试场景
- 确保测试用例能够验证修复效果
- 测试必须通过
-
代码质量:
- 参考上次修复的方向,但避免同样的错误
- 代码应该更加健壮和完善
- 添加必要的注释说明改进点
修复说明要求
修复完成后,请在测试文件顶部添加注释说明:
- 本次修复针对哪些审核意见进行了改进
- 新增了哪些测试场景
- 与上次修复的主要区别
请立即开始修复。 """ 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. 防止无限重试
配置项:
max_rejection_retry_count: int = 3 # 最多拒绝 3 次
逻辑:
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
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
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() 中检测拒绝原因:
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
运行测试:
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
测试内容:
- ✅ 创建 PR
- ✅ 合并 PR(验证可在日志中台直接操作)
- ✅ 关闭 PR(验证可在日志中台直接操作)
- ✅ 添加拒绝原因评论
- ✅ 获取评论(验证可读取拒绝原因)
- ✅ 重新打开 PR(可选)
📊 完整数据流
数据库变更
-- 新增字段
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 结构
{
"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:
- 用户体验更好:无需在两个系统间跳转
- 操作更直观:直接在 Bug 详情页操作,上下文清晰
- 可扩展性强:可以添加更多审核功能(如批量审核)
- 统一管理:所有操作记录在日志中台
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(改进版)
↓
再次审核
🎯 核心价值
- 提升审核效率:在日志中台直接操作,无需跳转
- 智能重试:Agent 根据拒绝原因针对性改进
- 可追溯:完整记录拒绝原因和改进过程
- 防止死循环:最多重试 3 次,超过则人工介入
下一步:是否开始实施?