fix: 用develop版本的pr_auto_scan.py解决冲突
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 20s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 18s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 15s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 26s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 1m44s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m46s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m4s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 59s
AI Code Review / AI Code Review (pull_request) Successful in 3m53s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 45s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 5m36s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m8s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 3m12s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 13m48s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 20s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 18s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 15s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 26s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 1m44s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m46s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m4s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 59s
AI Code Review / AI Code Review (pull_request) Successful in 3m53s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 45s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 5m36s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m8s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 3m12s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 13m48s
This commit is contained in:
@@ -2,10 +2,15 @@
|
||||
"""
|
||||
PR自动扫描器:扫描所有open PR,对CI全绿的进行自动审批/合并
|
||||
作为短作业模式的兜底机制,每5分钟运行一次
|
||||
|
||||
新增:AI审查联动 - AI代码审查发现严重问题时,不自动审批
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
@@ -108,7 +113,55 @@ def has_approval(token, repo, pr_number):
|
||||
return any(r.get("state") == "APPROVED" for r in reviews if isinstance(r, dict))
|
||||
|
||||
|
||||
def approve_pr(token, repo, pr_number):
|
||||
def get_ai_review_result(token, repo, pr_number):
|
||||
"""
|
||||
检查AI代码审查结果,返回 (has_critical, review_body)
|
||||
has_critical: 是否有严重问题(需修改的问题 > 0)
|
||||
review_body: 最新的AI审查评论文本
|
||||
"""
|
||||
# AI审查评论标记
|
||||
AI_REVIEW_MARKER = "AI_CODE_REVIEW_AUTO_COMMENT"
|
||||
|
||||
comments, code = api_request(token, repo, f"issues/{pr_number}/comments")
|
||||
if code != 200:
|
||||
return False, None
|
||||
|
||||
# 找最新的AI审查评论
|
||||
ai_comments = [c for c in comments if isinstance(c, dict) and AI_REVIEW_MARKER in c.get("body", "")]
|
||||
|
||||
if not ai_comments:
|
||||
return False, None
|
||||
|
||||
# 按时间排序,取最新的
|
||||
latest = max(ai_comments, key=lambda c: c.get("created_at", ""))
|
||||
body = latest.get("body", "")
|
||||
|
||||
# 解析严重问题数量
|
||||
# 匹配 "严重问题数量:X 个" 或 "需修改的问题(严重)" 下的列表
|
||||
critical_count = 0
|
||||
|
||||
# 方式1:直接匹配数字
|
||||
match = re.search(r"严重问题数量[::]\s*(\d+)\s*个", body)
|
||||
if match:
|
||||
critical_count = int(match.group(1))
|
||||
else:
|
||||
# 方式2:数 "需修改的问题" 章节下的条目数
|
||||
critical_section = re.search(
|
||||
r"###\s*[❌⚠️].*?(?:需修改|问题).*?\n(.*?)(?=\n###|\Z)",
|
||||
body,
|
||||
re.DOTALL,
|
||||
)
|
||||
if critical_section:
|
||||
section_text = critical_section.group(1)
|
||||
# 数编号条目 1. 2. 3.
|
||||
items = re.findall(r"^\d+\.\s+\*\*", section_text, re.MULTILINE)
|
||||
critical_count = len(items)
|
||||
|
||||
has_critical = critical_count > 0
|
||||
return has_critical, body
|
||||
|
||||
|
||||
def approve_pr(token, repo, pr_number, reason="CI全绿,自动审批通过。"):
|
||||
"""审批PR"""
|
||||
# 创建review
|
||||
data, code = api_request(
|
||||
@@ -116,7 +169,7 @@ def approve_pr(token, repo, pr_number):
|
||||
repo,
|
||||
f"pulls/{pr_number}/reviews",
|
||||
method="POST",
|
||||
data={"event": "PENDING", "body": "CI全绿,自动审批通过。"},
|
||||
data={"event": "PENDING", "body": reason},
|
||||
)
|
||||
|
||||
if code not in (200, 201):
|
||||
@@ -135,7 +188,7 @@ def approve_pr(token, repo, pr_number):
|
||||
repo,
|
||||
f"pulls/{pr_number}/reviews/{review_id}/events",
|
||||
method="POST",
|
||||
data={"event": "APPROVED", "body": "CI全绿,自动审批通过。"},
|
||||
data={"event": "APPROVED", "body": reason},
|
||||
)
|
||||
|
||||
if code2 in (200, 201):
|
||||
@@ -147,13 +200,25 @@ def approve_pr(token, repo, pr_number):
|
||||
repo,
|
||||
f"pulls/{pr_number}/reviews/{review_id}",
|
||||
method="POST",
|
||||
data={"event": "APPROVED", "body": "CI全绿,自动审批通过。"},
|
||||
data={"event": "APPROVED", "body": reason},
|
||||
)
|
||||
if code3 in (200, 201):
|
||||
return True, "审批提交成功(备用端点)"
|
||||
return False, f"审批提交失败: HTTP {code2}/{code3}"
|
||||
|
||||
|
||||
def add_pr_label(token, repo, pr_number, label):
|
||||
"""给PR添加标签"""
|
||||
data, code = api_request(
|
||||
token,
|
||||
repo,
|
||||
f"issues/{pr_number}/labels",
|
||||
method="POST",
|
||||
data={"labels": [label]},
|
||||
)
|
||||
return code in (200, 201)
|
||||
|
||||
|
||||
def merge_pr(token, repo, pr_number):
|
||||
"""合并PR(squash merge)"""
|
||||
# 等待几秒让状态同步
|
||||
@@ -198,6 +263,7 @@ def main():
|
||||
parser.add_argument("--merge", action="store_true", help="执行自动合并")
|
||||
parser.add_argument("--dry-run", default="false", help="试运行模式")
|
||||
parser.add_argument("--max-prs", type=int, default=20, help="最多处理的PR数")
|
||||
parser.add_argument("--skip-ai-review", action="store_true", help="跳过AI审查检查(强制审批)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -231,12 +297,13 @@ def main():
|
||||
approved_count = 0
|
||||
merged_count = 0
|
||||
skipped_count = 0
|
||||
ai_blocked_count = 0
|
||||
|
||||
for pr in prs[: args.max_prs]:
|
||||
pr_num = pr["number"]
|
||||
pr_title = pr["title"]
|
||||
head_sha = pr["head"]["sha"]
|
||||
base_ref = pr.get("base", {}).get("ref", "")
|
||||
base_ref = pr.get("base", {}).get("re", "")
|
||||
|
||||
# 跳过draft
|
||||
if pr.get("draft"):
|
||||
@@ -267,8 +334,19 @@ def main():
|
||||
# 检查审批用的CI状态
|
||||
all_ok, pending, failed, _ = check_required_contexts(args.token, args.repo, head_sha, approve_contexts)
|
||||
|
||||
# === AI审查检查 ===
|
||||
ai_has_critical = False
|
||||
if not args.skip_ai_review and all_ok and not failed and args.approve:
|
||||
ai_has_critical, ai_body = get_ai_review_result(args.token, args.repo, pr_num)
|
||||
if ai_has_critical:
|
||||
print(" ⚠️ AI审查发现严重问题,阻止自动审批")
|
||||
ai_blocked_count += 1
|
||||
# 给PR打标签便于人工识别
|
||||
if not dry_run:
|
||||
add_pr_label(args.token, args.repo, pr_num, "ai-review/需修改")
|
||||
|
||||
# === 自动审批 ===
|
||||
if args.approve and all_ok and not failed:
|
||||
if args.approve and all_ok and not failed and not ai_has_critical:
|
||||
if has_approval(args.token, args.repo, pr_num):
|
||||
print(" ✅ 已有审批,跳过")
|
||||
else:
|
||||
@@ -282,6 +360,8 @@ def main():
|
||||
approved_count += 1
|
||||
else:
|
||||
print(f" ❌ 审批失败: {msg}")
|
||||
elif ai_has_critical:
|
||||
print(" 🚫 AI审查阻止审批(人工可手动审批覆盖)")
|
||||
elif failed:
|
||||
print(" ❌ CI有失败项,跳过审批")
|
||||
elif pending:
|
||||
@@ -319,8 +399,9 @@ def main():
|
||||
print(f" 处理PR数: {min(len(prs), args.max_prs)}")
|
||||
print(f" 自动审批: {approved_count} 个")
|
||||
print(f" 自动合并: {merged_count} 个")
|
||||
print(f" AI审查阻止: {ai_blocked_count} 个")
|
||||
print(f" 跳过: {skipped_count} 个")
|
||||
print(f" 模式: {'DRY-RUN' if dry_run else '正式执行'}")
|
||||
print(" 模式: {'DRY-RUN' if dry_run else '正式执行'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user