name: CI on: pull_request: types: [opened, synchronize] workflow_dispatch: jobs: quality: runs-on: docker timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Set up environment run: make setup-quality - name: Lint all run: | . .venv/bin/activate export PATH="$HOME/.local/bin:$PATH" make lint-all - name: Unit tests with 100% coverage run: | . .venv/bin/activate make pytest-cov - name: Check unit test speed env: PYTHONPATH: src run: | . .venv/bin/activate python3 -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5 - name: Documentation coverage check env: PYTHONPATH: src run: | . .venv/bin/activate python3 -m devx.ci.doc_coverage --fail-on-missing - name: Translation completeness check env: PYTHONPATH: src run: | . .venv/bin/activate python3 -m devx.ci.check_translations - name: Dependency security scan run: | . .venv/bin/activate # 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 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: runs-on: docker timeout-minutes: 10 outputs: user-facing-changed: ${{ steps.detect.outputs.user-facing-changed }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up environment run: make setup-ci - name: Detect changed paths id: detect env: PYTHONPATH: src run: | . .venv/bin/activate python3 -m devx.ci.classify_changes \ --base "origin/master" \ --head "${{ github.event.pull_request.head.sha || github.sha }}" \ --github-output release-dry-run: needs: [quality, detect-changes] if: needs.detect-changes.outputs.user-facing-changed == 'true' runs-on: docker timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up environment run: make setup-release - name: Release dry-run validation env: PYTHONPATH: src run: | . .venv/bin/activate export PATH="$HOME/.local/bin:$PATH" python3 -m devx.ci.release --dry-run || true pr-review: if: github.event_name == 'pull_request' runs-on: docker timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Set up environment run: make setup-ci - name: Run automated PR review env: REPO_TOKEN: ${{ secrets.REPO_TOKEN }} PYTHONPATH: src run: | set -euo pipefail . .venv/bin/activate python3 -m devx.ci.pr_review \ "${{ github.event.number }}" \ "${{ github.repository }}" auto-merge: # Auto-merge runs after all CI checks pass. It reads the task ID # from the branch name, validates the PR title, and squash-merges. # Uses always() so it runs even when detect-changes skips (no user-facing changes). needs: [quality, detect-changes, pr-review] if: >- always() && github.event_name == 'pull_request' && needs.quality.result == 'success' && needs.pr-review.result == 'success' runs-on: docker timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.REPO_TOKEN }} - name: Set up environment run: make setup-ci - name: Squash merge with task ID env: REPO_TOKEN: ${{ secrets.REPO_TOKEN }} VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }} DEVX_VIKUNJA_PROJECT_ID: "8" PYTHONPATH: src HEAD_REF: ${{ github.head_ref }} PR_TITLE: ${{ github.event.pull_request.title }} REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ github.event.number }} run: | . .venv/bin/activate python3 -m devx.ci.auto_merge \ "$HEAD_REF" \ "$PR_TITLE" \ "$REPOSITORY" \ "$PR_NUMBER"