Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dd640da87 | |||
| 4749071c16 | |||
| 3353865f5b | |||
| 7061d7e672 | |||
| 6efdfbe194 | |||
| 02e3246f5a | |||
| 5cdafd2559 | |||
| a6afb344ba |
@@ -47,22 +47,33 @@ jobs:
|
||||
id: protected_images
|
||||
if: gitea.event_name != 'pull_request_target' && !gitea.event.inputs.pr_sha
|
||||
env:
|
||||
STAGING_SSH_KEY: ${{ secrets.PREVIEW_SSH_KEY }}
|
||||
STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
|
||||
STAGING_SSH_PORT: ${{ secrets.STAGING_SSH_PORT }}
|
||||
STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }}
|
||||
STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
||||
run: |
|
||||
set +e
|
||||
echo "获取staging服务器运行中镜像作为白名单..."
|
||||
mkdir -p ~/.ssh
|
||||
echo "$STAGING_SSH_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
staging_host="${STAGING_SSH_HOST:-47.98.113.167}"
|
||||
staging_port="${STAGING_SSH_PORT:-22222}"
|
||||
staging_user="${STAGING_SSH_USER:-root}"
|
||||
|
||||
key_path=~/.ssh/id_rsa
|
||||
if [ -n "${STAGING_SSH_KEY:-}" ]; then
|
||||
printf '%s\n' "$STAGING_SSH_KEY" > "$key_path"
|
||||
chmod 600 "$key_path"
|
||||
echo "Using key from STAGING_SSH_KEY secret"
|
||||
else
|
||||
echo "⚠️ STAGING_SSH_KEY not set, skipping whitelist"
|
||||
echo "protected_tags=" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ssh-keyscan -p "$staging_port" -H "$staging_host" >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
# 获取所有运行容器的镜像,提取tag部分
|
||||
IMAGES=$(ssh -p "$staging_port" -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no \
|
||||
"root@$staging_host" "docker ps --format '{{.Image}}' 2>/dev/null" 2>/dev/null | grep -v "^$" | sort -u)
|
||||
IMAGES=$(ssh -p "$staging_port" -i "$key_path" -o StrictHostKeyChecking=no \
|
||||
"$staging_user@$staging_host" "docker ps --format '{{.Image}}' 2>/dev/null" 2>/dev/null | grep -v "^$" | sort -u)
|
||||
|
||||
PROTECTED_TAGS=""
|
||||
if [ -n "$IMAGES" ]; then
|
||||
@@ -80,7 +91,7 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "staging运行中镜像tag: ${PROTECTED_TAGS:-(无)}"
|
||||
echo "protected_tags=$PROTECTED_TAGS" >> $GITEA_OUTPUT
|
||||
echo "protected_tags=$PROTECTED_TAGS" >> $GITHUB_OUTPUT
|
||||
|
||||
# ====== Docker登录 ======
|
||||
- name: Docker login to ACR
|
||||
|
||||
@@ -323,6 +323,73 @@ jobs:
|
||||
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
||||
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|
||||
|
||||
code-review:
|
||||
name: AI Code Review
|
||||
runs-on: ci-l2
|
||||
timeout-minutes: 8
|
||||
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
|
||||
steps:
|
||||
- name: Checkout code
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash
|
||||
- name: Record job start time
|
||||
shell: sh
|
||||
run: bash scripts/ci/step_timer_start.sh
|
||||
- name: Install dependencies
|
||||
shell: sh
|
||||
run: |
|
||||
if ! python3 -m pip --version >/dev/null 2>&1; then
|
||||
apt-get update -qq && apt-get install -y -qq python3-pip python3-venv >/dev/null 2>&1
|
||||
fi
|
||||
if ! python3 -m pip --version >/dev/null 2>&1; then
|
||||
python3 -m ensurepip --upgrade 2>/dev/null || curl -sS https://bootstrap.pypa.io/get-pip.py | python3
|
||||
fi
|
||||
python3 -m pip install --quiet requests
|
||||
- name: Run AI Code Review
|
||||
shell: sh
|
||||
env:
|
||||
GITEA_API_URL: ${{ gitea.server_url }}
|
||||
GITEA_TOKEN: ${{ secrets.REVIEW_GITEA_TOKEN }}
|
||||
REPO_NAME: ${{ gitea.repository }}
|
||||
PR_NUMBER: ${{ gitea.event.pull_request.number }}
|
||||
PR_HEAD_SHA: ${{ gitea.event.pull_request.head.sha }}
|
||||
LLM_PROVIDER: "coze"
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
COZE_BOT_ID: ${{ secrets.COZE_BOT_ID }}
|
||||
LLM_MODEL: ${{ secrets.LLM_MODEL }}
|
||||
MAX_DIFF_CHARS: "30000"
|
||||
LLM_TIMEOUT: "120"
|
||||
run: |
|
||||
python3 scripts/ci_code_review.py
|
||||
- name: Job duration summary
|
||||
if: always()
|
||||
shell: sh
|
||||
run: bash scripts/ci/step_timer_end.sh
|
||||
- name: Notify on failure
|
||||
continue-on-error: true
|
||||
if: failure()
|
||||
shell: sh
|
||||
env:
|
||||
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
|
||||
run: |
|
||||
set +e
|
||||
NOTIFY_MODE=failure JOB_NAME="AI Code Review" python3 scripts/ci_notify.py
|
||||
- name: Report CI trace
|
||||
if: always()
|
||||
shell: sh
|
||||
env:
|
||||
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
|
||||
run: |
|
||||
STATUS="ok"
|
||||
[ ${{ job.status }} = "success" ] || STATUS="error"
|
||||
START_TIME=""
|
||||
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
|
||||
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
|
||||
|
||||
unit-tests:
|
||||
needs: check-frontend-only
|
||||
if: always() && needs.check-frontend-only.outputs.skip_backend != 'true'
|
||||
@@ -1481,18 +1548,26 @@ jobs:
|
||||
shell: sh
|
||||
run: bash scripts/ci/step_timer_start.sh
|
||||
- name: Run production browser E2E
|
||||
shell: sh
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
docker run --rm --ipc=host \
|
||||
# DooD模式下不能用-v挂载(宿主机路径与CI容器路径不一致)
|
||||
# 改用 docker create + docker cp 方式把代码拷进容器
|
||||
CONTAINER_NAME="production-e2e-$$"
|
||||
docker create --name "$CONTAINER_NAME" --ipc=host \
|
||||
-e E2E_BASE_URL=https://saas.xiaoxiajianji.com \
|
||||
-e E2E_API_BASE=https://api.xiaoxiajianji.com/api/v1 \
|
||||
-e E2E_BROWSER_CHANNEL=chromium \
|
||||
-e PLAYWRIGHT_HEADLESS=1 \
|
||||
-v "$PWD:/workspace" \
|
||||
-w /workspace/apps/web \
|
||||
git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \
|
||||
sh -lc 'npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts'
|
||||
sh -lc "npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts"
|
||||
docker cp apps "$CONTAINER_NAME:/workspace/"
|
||||
docker cp package-lock.json "$CONTAINER_NAME:/workspace/" 2>/dev/null || true
|
||||
docker start -a "$CONTAINER_NAME"
|
||||
EXIT_CODE=$(docker wait "$CONTAINER_NAME")
|
||||
docker rm "$CONTAINER_NAME" 2>/dev/null || true
|
||||
exit $EXIT_CODE
|
||||
|
||||
- name: Job duration summary
|
||||
if: always()
|
||||
@@ -1672,6 +1747,7 @@ jobs:
|
||||
- validate-code-quality
|
||||
- validate-type-check
|
||||
- validate-migration
|
||||
- code-review
|
||||
- unit-tests
|
||||
- integration-tests
|
||||
- frontend-lint
|
||||
@@ -1699,6 +1775,7 @@ jobs:
|
||||
RESULT_CODE_QUALITY: ${{ needs.validate-code-quality.result }}
|
||||
RESULT_TYPE_CHECK: ${{ needs.validate-type-check.result }}
|
||||
RESULT_MIGRATION: ${{ needs.validate-migration.result }}
|
||||
RESULT_CODE_REVIEW: ${{ needs.code-review.result }}
|
||||
RESULT_UNIT_TESTS: ${{ needs.unit-tests.result }}
|
||||
RESULT_INTEGRATION: ${{ needs.integration-tests.result }}
|
||||
RESULT_FRONTEND_LINT: ${{ needs.frontend-lint.result }}
|
||||
@@ -1713,6 +1790,7 @@ jobs:
|
||||
echo " validate-code-quality: $RESULT_CODE_QUALITY"
|
||||
echo " validate-type-check: $RESULT_TYPE_CHECK"
|
||||
echo " validate-migration: $RESULT_MIGRATION"
|
||||
echo " code-review: $RESULT_CODE_REVIEW"
|
||||
echo " unit-tests: $RESULT_UNIT_TESTS"
|
||||
echo " integration-tests: $RESULT_INTEGRATION"
|
||||
echo " frontend-lint: $RESULT_FRONTEND_LINT"
|
||||
@@ -1731,6 +1809,7 @@ jobs:
|
||||
"validate-code-quality:$RESULT_CODE_QUALITY"
|
||||
"validate-type-check:$RESULT_TYPE_CHECK"
|
||||
"validate-migration:$RESULT_MIGRATION"
|
||||
"code-review:$RESULT_CODE_REVIEW"
|
||||
"frontend-lint:$RESULT_FRONTEND_LINT"
|
||||
"build-pr:$RESULT_BUILD_PR"
|
||||
)
|
||||
@@ -1805,7 +1884,7 @@ jobs:
|
||||
echo "❌ CI Gate: FAILED"
|
||||
echo "失败项: ${FAILED_ITEMS[*]}"
|
||||
echo "gate_result=failure" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Report CI trace
|
||||
|
||||
@@ -48,6 +48,7 @@ jobs:
|
||||
GITEA_TOKEN: ${{ secrets.REVIEW_GITEA_TOKEN }}
|
||||
REPO_NAME: ${{ gitea.repository }}
|
||||
PR_NUMBER: ${{ gitea.event.pull_request.number }}
|
||||
PR_HEAD_SHA: ${{ gitea.event.pull_request.head.sha }}
|
||||
# LLM 提供商: coze (扣子原生Bot) / openai (OpenAI兼容)
|
||||
LLM_PROVIDER: "coze"
|
||||
# 扣子模式配置(默认国内站 api.coze.cn)
|
||||
@@ -60,8 +61,9 @@ jobs:
|
||||
LLM_TIMEOUT: "120"
|
||||
run: |
|
||||
python3 scripts/ci_code_review.py
|
||||
# 审查脚本异常不影响 CI 通过
|
||||
continue-on-error: true
|
||||
# 注意:脚本退出码决定job状态
|
||||
# - 有阻塞级问题 → exit 1 → job失败 → 门禁拦截
|
||||
# - 无阻塞级问题/LLM异常 → exit 0 → 通过(fail-open)
|
||||
|
||||
- name: Report CI trace
|
||||
if: always()
|
||||
|
||||
@@ -10,7 +10,7 @@ from __future__ import annotations
|
||||
from app.auth import AuthenticatedUser
|
||||
from app.auth import get_current_user as get_authenticated_user
|
||||
from app.dependencies import get_user_repository
|
||||
from fastapi import Depends
|
||||
from fastapi import Depends, HTTPException
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from packages.domain.entities import User
|
||||
|
||||
@@ -5,3 +5,45 @@ target-version = ["py312"]
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 120
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py311"
|
||||
line-length = 120
|
||||
exclude = [
|
||||
".git",
|
||||
"__pycache__",
|
||||
".venv",
|
||||
"venv",
|
||||
"node_modules",
|
||||
"alembic",
|
||||
".gitea",
|
||||
".next",
|
||||
"dist",
|
||||
"build",
|
||||
"hostexecutor",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors(同 flake8 默认)
|
||||
"F", # pyflakes(同 flake8 默认)
|
||||
]
|
||||
ignore = [
|
||||
"E203",
|
||||
"E501", # line-too-long(black管)
|
||||
"E302",
|
||||
"E402", # module-import-not-at-top(循环导入多)
|
||||
"E722", # bare-except
|
||||
"W291",
|
||||
"W293",
|
||||
"F401",
|
||||
"F403",
|
||||
"F405",
|
||||
"F841",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["F401", "F403", "F405"]
|
||||
"tests/**" = ["E402", "F401", "F821", "F841"]
|
||||
"packages/ports/*" = ["E301"]
|
||||
"apps/api/app/api/routes/auth.py" = ["ALL"]
|
||||
|
||||
@@ -258,7 +258,7 @@ def main():
|
||||
req_commits = urllib.request.Request(commits_url, headers={"Authorization": f"token {token}"})
|
||||
with urllib.request.urlopen(req_commits) as resp_commits:
|
||||
commits = json.loads(resp_commits.read())
|
||||
latest_msg = commits[-1].get("commit", {}).get("message", "") if commits else ""
|
||||
latest_msg = commits[0].get("commit", {}).get("message", "") if commits else ""
|
||||
if skip_marker in latest_msg:
|
||||
print(f"检测到最新commit包含 {skip_marker} 标记,跳过格式修复(防循环)")
|
||||
print("本次格式检查失败是格式修复commit触发的CI回跑,属正常现象")
|
||||
|
||||
+109
-16
@@ -10,6 +10,7 @@ import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Optional, Tuple
|
||||
|
||||
@@ -183,6 +184,37 @@ class GiteaClient:
|
||||
return False
|
||||
return True
|
||||
|
||||
def create_commit_status(
|
||||
self, sha: str, state: str, context: str, description: str = "", target_url: str = ""
|
||||
) -> bool:
|
||||
"""
|
||||
给指定 commit 打 status。
|
||||
state: pending / success / failure / error / warning
|
||||
Gitea API: POST /repos/{owner}/{repo}/statuses/{sha}
|
||||
"""
|
||||
url = self._api_url(f"statuses/{sha}")
|
||||
logger.info(f"设置 commit status: sha={sha[:12]}..., state={state}, context={context}")
|
||||
|
||||
payload = {
|
||||
"state": state,
|
||||
"context": context,
|
||||
"description": description[:200] if description else "",
|
||||
}
|
||||
if target_url:
|
||||
payload["target_url"] = target_url
|
||||
|
||||
resp = self.session.post(
|
||||
url,
|
||||
data=json.dumps(payload),
|
||||
timeout=GITEA_TIMEOUT,
|
||||
)
|
||||
if resp.status_code not in (200, 201):
|
||||
logger.error(f"设置 status 失败: HTTP {resp.status_code} - {resp.text[:200]}")
|
||||
return False
|
||||
|
||||
logger.info(f"Status 设置成功: {context} = {state}")
|
||||
return True
|
||||
|
||||
|
||||
def call_llm_openai(
|
||||
prompt: str,
|
||||
@@ -416,7 +448,22 @@ def build_review_prompt(diff_text: str, pr_number: int, file_list: list) -> str:
|
||||
```
|
||||
|
||||
## 审查要求
|
||||
请从以下维度进行审查,重点关注严重问题:
|
||||
请从以下维度进行审查,重点关注**阻塞级问题**:
|
||||
|
||||
### 问题分级标准
|
||||
- **🔴 阻塞级(BLOCKER)**:必须修复,否则不允许合并。包括:
|
||||
1. **明显逻辑bug**:条件判断错误、死循环、返回值错误、空指针/None引用未处理、边界条件遗漏导致功能异常
|
||||
2. **安全漏洞**:SQL注入、XSS、命令注入、敏感信息明文存储/泄露、权限绕过、认证缺失
|
||||
3. **语法错误**:代码存在语法层面的错误,无法运行
|
||||
4. **数据损坏风险**:可能导致数据丢失、数据不一致、脏数据写入的问题
|
||||
|
||||
- **💡 建议级(SUGGESTION)**:不阻塞合并,仅供参考改进。包括:
|
||||
1. 命名不规范、代码风格问题
|
||||
2. 最佳实践建议、设计模式优化
|
||||
3. 格式问题(缩进、空行、import顺序等)
|
||||
4. 代码可读性改进、注释补充
|
||||
5. 非关键路径的轻微性能优化建议
|
||||
6. 重复代码、过长函数等代码质量问题
|
||||
|
||||
1. **逻辑正确性**:是否有明显的逻辑错误、边界条件遗漏、空指针/None引用风险
|
||||
2. **异常处理**:异常捕获是否合理,是否有裸except,错误处理是否完善
|
||||
@@ -426,20 +473,24 @@ def build_review_prompt(diff_text: str, pr_number: int, file_list: list) -> str:
|
||||
6. **安全问题**:是否有注入风险、敏感信息泄露、权限控制问题
|
||||
|
||||
## 输出格式
|
||||
请使用以下格式输出,语言为中文:
|
||||
请使用以下格式输出,语言为中文。**必须严格按照格式输出,尤其是【阻塞级判定】部分**:
|
||||
|
||||
### 【阻塞级判定】
|
||||
- 是否存在阻塞级问题:(是 / 否)
|
||||
- 阻塞级问题数量:X 个
|
||||
|
||||
### 📊 审查概览
|
||||
- 整体评价:(通过 / 有建议 / 需修改)
|
||||
- 严重问题数量:X 个
|
||||
- 一般建议数量:X 个
|
||||
- 建议级问题数量:X 个
|
||||
|
||||
### ❌ 需修改的问题(严重)
|
||||
(如果没有严重问题,写"无")
|
||||
### 🔴 阻塞级问题(必须修复)
|
||||
(如果没有阻塞级问题,写"无")
|
||||
1. **[文件: 行号] 问题标题**
|
||||
- 问题类型:(逻辑bug / 安全漏洞 / 语法错误 / 数据损坏风险)
|
||||
- 问题描述:...
|
||||
- 修改建议:...
|
||||
|
||||
### 💡 改进建议(一般)
|
||||
### 💡 改进建议(不阻塞合并)
|
||||
(如果没有建议,写"无")
|
||||
1. **[文件: 行号] 建议标题**
|
||||
- 具体内容:...
|
||||
@@ -448,10 +499,46 @@ def build_review_prompt(diff_text: str, pr_number: int, file_list: list) -> str:
|
||||
(可选,列出值得肯定的地方)
|
||||
|
||||
请务必基于代码实际内容审查,不要编造不存在的问题。如果代码质量良好,直接给出通过结论即可。
|
||||
**重要:【阻塞级判定】必须准确,只有确实存在严重问题时才写"是"。**
|
||||
"""
|
||||
return prompt
|
||||
|
||||
|
||||
def parse_blocker_result(review_text: str) -> Tuple[bool, int]:
|
||||
"""
|
||||
从审查结果中解析是否存在阻塞级问题。
|
||||
返回 (has_blocker, blocker_count)
|
||||
"""
|
||||
# 先找【阻塞级判定】部分的明确标记
|
||||
pattern = r"【阻塞级判定】[\s\S]*?是否存在阻塞级问题[::]\s*(是|否)"
|
||||
match = re.search(pattern, review_text)
|
||||
if match:
|
||||
has_blocker = match.group(1) == "是"
|
||||
else:
|
||||
# fallback 1: 找"阻塞级问题数量"
|
||||
count_pattern = r"阻塞级问题数量[::]\s*(\d+)"
|
||||
count_match = re.search(count_pattern, review_text)
|
||||
if count_match:
|
||||
has_blocker = int(count_match.group(1)) > 0
|
||||
else:
|
||||
# fallback 2: 检查是否有"阻塞级问题"section且内容不是"无"
|
||||
has_blocker = False
|
||||
blocker_section = re.search(r"### 🔴 阻塞级问题[\s\S]*?(?=### |\Z)", review_text)
|
||||
if blocker_section:
|
||||
section_text = blocker_section.group(0)
|
||||
# 如果有编号列表项,说明有问题
|
||||
if re.search(r"\d+\.\s*\*\*", section_text):
|
||||
has_blocker = True
|
||||
|
||||
# 提取数量
|
||||
count_pattern = r"阻塞级问题数量[::]\s*(\d+)"
|
||||
count_match = re.search(count_pattern, review_text)
|
||||
blocker_count = int(count_match.group(1)) if count_match else (1 if has_blocker else 0)
|
||||
|
||||
logger.info(f"阻塞级问题解析: 存在={has_blocker}, 数量={blocker_count}")
|
||||
return has_blocker, blocker_count
|
||||
|
||||
|
||||
def call_llm_for_review(
|
||||
diff_text: str,
|
||||
pr_number: int,
|
||||
@@ -628,7 +715,7 @@ def main():
|
||||
|
||||
if not review_result:
|
||||
logger.error("LLM 审查失败")
|
||||
sys.exit(1)
|
||||
sys.exit(0) # fail-open: LLM调用失败不阻塞合并
|
||||
|
||||
# 7. 加上审查时间和标识(便于识别是自动审查)
|
||||
from datetime import datetime
|
||||
@@ -669,18 +756,24 @@ def main():
|
||||
logger.error("评论发布失败")
|
||||
sys.exit(1)
|
||||
|
||||
# 10. 判断是否有严重问题(可选阻断)
|
||||
# 目前只做建议,不阻断合并,始终返回 0
|
||||
has_critical = "问题" in review_result and ("❌" in review_result or "需修改" in review_result)
|
||||
if has_critical:
|
||||
logger.warning("检测到需修改的问题,但当前配置为仅建议,不阻断合并")
|
||||
# 10. 解析阻塞级问题,用退出码决定 job 状态
|
||||
# 有阻塞级问题 → exit 1 → job失败 → Gitea自动打failure status → 门禁拦截
|
||||
# 无阻塞级问题 → exit 0 → job成功 → Gitea自动打success status
|
||||
# LLM调用失败等异常 → exit 0 → fail-open,不阻塞正常开发
|
||||
has_blocker, blocker_count = parse_blocker_result(review_result)
|
||||
|
||||
logger.info("代码审查完成")
|
||||
sys.exit(0)
|
||||
if has_blocker:
|
||||
logger.error(f"检测到 {blocker_count} 个阻塞级问题,审查不通过")
|
||||
logger.info("代码审查完成(失败)")
|
||||
sys.exit(1)
|
||||
else:
|
||||
logger.info("无阻塞级问题,审查通过")
|
||||
logger.info("代码审查完成(通过)")
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"审查脚本发生未预期的异常: {e}")
|
||||
sys.exit(1)
|
||||
sys.exit(0) # fail-open: 异常不阻塞正常开发
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user