name: CI on: pull_request: types: [opened, synchronize] workflow_dispatch: env: PIP_BREAK_SYSTEM_PACKAGES: "1" PYTHONPATH: src CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} CI_GITEA_USERNAME: ${{ vars.CI_GITEA_USERNAME }} jobs: # Single validation job that merges: quality, detect-changes, # release-dry-run, pr-review, and pre-merge-check. # Uses ci-full image (has git-cliff for release-dry-run). # Saves ~4x checkout+setup overhead vs 5 separate jobs. validate: runs-on: docker container: image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-full:latest credentials: username: ${{ vars.CI_GITEA_USERNAME }} password: ${{ secrets.CI_GITEA_API_TOKEN }} timeout-minutes: 15 defaults: run: shell: bash outputs: user-facing-changed: ${{ steps.detect.outputs.user-facing-changed }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up environment env: CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} run: make setup-image # --- quality steps --- - name: Lint all run: | . .venv/bin/activate 2>/dev/null || true export PATH="$HOME/.local/bin:$PATH" make lint-all - name: Unit tests with 100% coverage run: | . .venv/bin/activate 2>/dev/null || true make pytest-cov - name: Check unit test speed run: | . .venv/bin/activate 2>/dev/null || true python3 -m devx.tools.check_test_speed --max-seconds 15 --max-single-seconds 0.5 - name: Documentation gate (coverage + stale refs + lint + version refs + prose) env: DEVX_DOC_COVERAGE_STRICT: "1" DEVX_VALE_LEVEL: warning run: | . .venv/bin/activate 2>/dev/null || true export PATH="$HOME/.local/bin:$PATH" make devx-docs-check - name: Translation completeness check run: | . .venv/bin/activate 2>/dev/null || true python3 -m devx.ci.check_translations - name: Dependency security scan run: | . .venv/bin/activate 2>/dev/null || true # Install pip in venv if missing (needed by pip-audit) .venv/bin/python -m ensurepip 2>/dev/null || true PIPAPI_PYTHON_LOCATION=$PWD/.venv/bin/python \ pip-audit --desc --skip-editable 2>&1 || true - name: Workflow dry-run validation run: | . .venv/bin/activate 2>/dev/null || true export PATH="$HOME/.local/bin:$PATH" # Best-effort: only runs if act_runner is installed if command -v act_runner >/dev/null 2>&1; then make workflow-dryrun else echo "act_runner not found — skipping workflow dry-run (static lint still passed)" fi # --- detect-changes step --- - name: Detect changed paths id: detect run: | . .venv/bin/activate 2>/dev/null || true python3 -m devx.ci.classify_changes \ --base "origin/master" \ --head "${{ github.event.pull_request.head.sha || github.sha }}" \ --github-output # --- validate-pr + pr-review steps (PR only) --- - name: Validate auto-merge preconditions if: github.event_name == 'pull_request' env: VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }} DEVX_VIKUNJA_PROJECT_ID: "8" HEAD_REF: ${{ github.head_ref }} PR_TITLE: ${{ github.event.pull_request.title }} REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ github.event.number }} run: | . .venv/bin/activate 2>/dev/null || true python3 -m devx.ci.check_auto_merge_ready \ --branch "$HEAD_REF" \ --pr-title "$PR_TITLE" \ --repo "$REPOSITORY" \ --pr-number "$PR_NUMBER" - name: Validate spec file if: github.event_name == 'pull_request' env: DEVX_TASK_PREFIX: DEVX PYTHONPATH: ${{ env.PYTHONPATH }} HEAD_REF: ${{ github.head_ref }} run: | . .venv/bin/activate 2>/dev/null || true python3 -m devx.ci.validate_spec \ --branch "$HEAD_REF" \ --github-output - name: Check PR size if: github.event_name == 'pull_request' env: PYTHONPATH: ${{ env.PYTHONPATH }} run: | . .venv/bin/activate 2>/dev/null || true python3 -m devx.ci.check_pr_size \ --base "origin/master" \ --head "${{ github.event.pull_request.head.sha || github.sha }}" \ --github-output # --- release-dry-run step (conditional) --- - name: Release dry-run validation if: steps.detect.outputs.user-facing-changed == 'true' run: | . .venv/bin/activate 2>/dev/null || true export PATH="$HOME/.local/bin:$PATH" python3 -m devx.ci.release --dry-run - name: Notify on failure if: failure() env: CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} run: | . .venv/bin/activate 2>/dev/null || true export PATH="$HOME/.local/bin:$PATH" python3 -m devx.ci.notify_failure \ --repo "${{ github.repository }}" \ --run-id "${{ github.run_id }}" \ --workflow "ci/validate" \ --commit "${{ github.sha }}" \ --auto-login auto-merge: # Auto-merge runs after validate passes. It reads the task ID # from the branch name, validates the PR title, and squash-merges. needs: [validate] if: >- always() && github.event_name == 'pull_request' && needs.validate.result == 'success' runs-on: docker container: image: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest credentials: username: ${{ vars.CI_GITEA_USERNAME }} password: ${{ secrets.CI_GITEA_API_TOKEN }} timeout-minutes: 10 defaults: run: shell: bash steps: - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.CI_GITEA_API_TOKEN }} - name: Set up environment env: CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} run: make setup-image - name: Post approval review env: REVIEWER_GITEA_API_TOKEN: ${{ secrets.REVIEWER_GITEA_API_TOKEN }} PR_NUMBER: ${{ github.event.number }} GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_REPOSITORY: ${{ github.repository }} run: | . .venv/bin/activate 2>/dev/null || true # Post APPROVE review via Gitea API to satisfy branch protection curl -s -X POST \ "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews" \ -H "Authorization: token ${REVIEWER_GITEA_API_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"event":"APPROVED","body":"Auto-approved: all CI checks passed (validate job)."}' \ || echo "::warning::Failed to post approval review (best-effort)." - name: Squash merge with task ID env: CI_GITEA_API_TOKEN: ${{ secrets.CI_GITEA_API_TOKEN }} VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }} DEVX_VIKUNJA_PROJECT_ID: "8" HEAD_REF: ${{ github.head_ref }} PR_TITLE: ${{ github.event.pull_request.title }} REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ github.event.number }} run: | . .venv/bin/activate 2>/dev/null || true python3 -m devx.ci.auto_merge \ "$HEAD_REF" \ "$PR_TITLE" \ "$REPOSITORY" \ "$PR_NUMBER"