# CI 管道 — moziplus v2.0 # # 触发条件: # - pull_request(opened, synchronize) # # 注意:只保留 pull_request 触发,避免 push + pull_request 双倍触发 # # Gitea v1.23.4 限制注意: # - 不支持 failure() 表达式,用 always() + shell 条件判断替代 # - 不支持 concurrency / continue-on-error / timeout-minutes / permissions # - 无内置 GITEA_TOKEN,需手动配置 PAT 为 secret # - runs-on 只支持单个 label name: CI on: pull_request: types: [opened, synchronize] jobs: # ── Job 1: Lint ────────────────────────────────────── lint: runs-on: macos-arm64 steps: - uses: actions/checkout@v4 - name: Setup Python run: | python3 -m venv /tmp/ci-venv-lint /tmp/ci-venv-lint/bin/pip install --quiet --upgrade pip /tmp/ci-venv-lint/bin/pip install --quiet flake8 - name: Lint with flake8 run: | /tmp/ci-venv-lint/bin/flake8 src/ --max-line-length=120 --extend-ignore=E501 # ── Job 2: Test ────────────────────────────────────── test: runs-on: macos-arm64 needs: lint steps: - uses: actions/checkout@v4 - name: Setup Python run: | rm -rf /tmp/ci-venv-test python3 -m venv /tmp/ci-venv-test /tmp/ci-venv-test/bin/pip install --quiet --upgrade pip /tmp/ci-venv-test/bin/pip install --quiet --no-cache-dir fastapi pydantic pyyaml uvicorn requests pytest pytest-asyncio httpx - name: Debug environment run: | echo "PWD=$(pwd)" echo "PYTHONPATH=$PYTHONPATH" python3 -c "import sys; [print(p) for p in sys.path if 'sanguo' in p.lower() or 'openclaw' in p.lower()]" grep -c "assignee = agent_id" src/daemon/toolchain_handler.py || true grep -c "_BUSINESS_FAIL_THRESHOLD" src/daemon/toolchain_handler.py || true - name: Run tests (exclude E2E) run: | PYTHONPATH=$(pwd) /tmp/ci-venv-test/bin/pytest tests/ -m "not e2e" -x -q || \ (echo '=== RETRY WITH VERBOSE ===' && \ PYTHONPATH=$(pwd) /tmp/ci-venv-test/bin/pytest tests/ -m "not e2e" -x -v 2>&1 | tail -30) # ── Job 3: Frontend Build ─────────────────────────── frontend: runs-on: macos-arm64 needs: lint steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 20 - name: Install & Build run: | cd src/frontend npm ci || npm install npm run build # ── Job 4: CI 失败通知 ─────────────────────────────── # 使用 needs..result 直接判断,不查询 commit status API # 根因:notify 自身的 pending status 会污染 commit status 查询结果(竞态条件) notify-on-failure: runs-on: macos-arm64 needs: [lint, test, frontend] if: always() steps: - name: Check results and notify env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} LINT_RESULT: ${{ needs.lint.result }} TEST_RESULT: ${{ needs.test.result }} FRONTEND_RESULT: ${{ needs.frontend.result }} run: | echo "Lint result: $LINT_RESULT" echo "Test result: $TEST_RESULT" # 只有 lint 或 test 明确失败时才发通知 if [ "$LINT_RESULT" = "failure" ] || [ "$TEST_RESULT" = "failure" ] || [ "$FRONTEND_RESULT" = "failure" ]; then echo "CI has failures, sending notification..." # 如果是 PR 事件,写评论通知 PR_NUMBER="${{ gitea.event.pull_request.number }}" if [ -n "$PR_NUMBER" ]; then # 构建失败摘要 FAILED_JOBS="" [ "$LINT_RESULT" = "failure" ] && FAILED_JOBS="${FAILED_JOBS}lint " [ "$TEST_RESULT" = "failure" ] && FAILED_JOBS="${FAILED_JOBS}test " [ "$FRONTEND_RESULT" = "failure" ] && FAILED_JOBS="${FAILED_JOBS}frontend " curl -sf -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ "${{ gitea.api_url }}/repos/${{ gitea.repository }}/issues/${PR_NUMBER}/comments" \ -d "{\"body\": \"[CI] 失败\\n\\n分支: ${{ gitea.ref_name }}\\n触发 commit: \`${{ gitea.sha }}\`\\n失败 Job: ${FAILED_JOBS}\\n请检查 CI 日志并修复。\"}" \ || echo "Failed to post PR comment" echo "PR comment posted." else echo "Not a PR event, skipping PR comment." fi else echo "No explicit failures (results: lint=$LINT_RESULT, test=$TEST_RESULT), no notification needed." fi