log-center/docs/pr-review-and-retry-workflow.md
zyc 5611839fd8
Some checks failed
Build and Deploy Log Center / build-and-deploy (push) Failing after 1m55s
fix git pr
2026-02-25 10:55:26 +08:00

33 KiB
Raw Permalink Blame History

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

关键发现

  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. 修复原始 BugTypeError                              │
│    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

  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. 防止无限重试

配置项:

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

测试内容:

  1. 创建 PR
  2. 合并 PR验证可在日志中台直接操作
  3. 关闭 PR验证可在日志中台直接操作
  4. 添加拒绝原因评论
  5. 获取评论(验证可读取拒绝原因)
  6. 重新打开 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:

  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 次,超过则人工介入

下一步:是否开始实施?