Skip to main content
EVOKORE// WORKFLOWS / ci.yml
Agent-33workflow · 244 lines

.github/workflows/ci.yml

CI / automation

View on GitHub →
# Node.js 24 migration: COMPLETE (all actions Node 24 ready)
# - actions/checkout: v6
# - actions/github-script: v8
# - actions/setup-python: v6
# - actions/upload-artifact: v7
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  merge-sequencing-guard:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
    steps:
      - name: Enforce sequence label ordering
        uses: actions/github-script@v9
        with:
          script: |
            const labels = (context.payload.pull_request.labels || []).map((label) => label.name);
            const sequenceLabels = labels.filter((name) => /^sequence:[1-3]$/.test(name));

            if (sequenceLabels.length === 0) {
              core.notice("No sequence label found on PR. Skipping merge sequencing guard.");
              return;
            }

            if (sequenceLabels.length > 1) {
              core.setFailed(`Expected a single sequence label, found: ${sequenceLabels.join(", ")}`);
              return;
            }

            const currentLabel = sequenceLabels[0];
            const currentSequence = Number(currentLabel.split(":")[1]);

            if (currentSequence === 1) {
              core.info("PR is sequence:1. No predecessor required.");
              return;
            }

            const predecessorLabel = `sequence:${currentSequence - 1}`;
            const baseBranch = context.payload.pull_request.base.ref;
            const owner = context.repo.owner;
            const repo = context.repo.repo;

            const closedPulls = await github.paginate(github.rest.pulls.list, {
              owner,
              repo,
              state: "closed",
              base: baseBranch,
              per_page: 100,
            });

            const predecessor = closedPulls.find((pr) => {
              const prLabels = (pr.labels || []).map((label) => label.name);
              return pr.merged_at && prLabels.includes(predecessorLabel);
            });

            if (!predecessor) {
              core.setFailed(
                `Missing merged predecessor on base branch '${baseBranch}'. ` +
                `PRs labeled '${currentLabel}' require a merged '${predecessorLabel}'.`
              );
              return;
            }

            core.info(
              `Merge sequencing check passed. Found predecessor #${predecessor.number} ` +
              `(${predecessorLabel}) merged into '${baseBranch}'.`
            );

  lint:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: engine
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        env:
          AGENT33_SKIP_FRONTEND_BUILD: "1"
        run: pip install -e ".[dev]"

      - name: Runtime import boundaries
        run: python scripts/check_import_boundaries.py

      - name: Runtime compatibility lock
        run: python scripts/check_runtime_compatibility.py

      - name: Ruff check
        run: ruff check src/ tests/

      - name: Ruff format check
        run: ruff format --check src/ tests/

      - name: Mypy strict type check
        run: mypy src/

  test:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: engine
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        env:
          AGENT33_SKIP_FRONTEND_BUILD: "1"
        run: pip install -e ".[dev]"

      - name: Run tests
        run: pytest --cov=agent33 --cov-report=term-missing -x -q

  build:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: engine
    steps:
      - uses: actions/checkout@v6

      - name: Build Docker images
        run: docker compose build

  pip-smoke:
    runs-on: ubuntu-latest
    needs: [lint]
    defaults:
      run:
        working-directory: engine
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Build wheel (skip frontend)
        env:
          AGENT33_SKIP_FRONTEND_BUILD: "1"
        run: |
          pip install hatchling
          python -m hatchling build --target wheel

      - name: Install wheel and smoke test
        run: |
          pip install dist/*.whl
          agent33 --help
          python -c "import agent33; print('agent33 importable OK')"
          python -c "from agent33.static import ui_path; print('static module importable OK')"

  benchmark-smoke:
    runs-on: ubuntu-latest
    continue-on-error: true
    defaults:
      run:
        working-directory: engine
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        env:
          AGENT33_SKIP_FRONTEND_BUILD: "1"
        run: pip install -e ".[dev]"

      - name: Fetch baseline from benchmarks branch
        working-directory: ${{ github.workspace }}
        run: |
          git fetch origin benchmarks:refs/remotes/origin/benchmarks || true
          mkdir -p engine/test-results
          if git show origin/benchmarks:baselines/ctrf-baseline-latest.json > engine/test-results/ctrf-baseline.json 2>/dev/null; then
            echo "Baseline fetched from benchmarks branch"
          else
            echo "No baseline found on benchmarks branch — running without comparison"
            rm -f engine/test-results/ctrf-baseline.json
          fi

      - name: Run smoke benchmarks
        env:
          AGENT33_SKIP_FRONTEND_BUILD: "1"
        run: |
          if [ -f "test-results/ctrf-baseline.json" ]; then
            agent33 bench smoke \
              --baseline test-results/ctrf-baseline.json \
              --output test-results/ctrf-smoke-report.json
          else
            agent33 bench smoke \
              --output test-results/ctrf-smoke-report.json
          fi

      - name: Summarize benchmark regressions
        if: always()
        env:
          AGENT33_SKIP_FRONTEND_BUILD: "1"
        run: |
          if [ -f "test-results/ctrf-smoke-report.json" ]; then
            if [ -f "test-results/ctrf-baseline.json" ]; then
              agent33 bench report \
                test-results/ctrf-smoke-report.json \
                --baseline test-results/ctrf-baseline.json \
                --github-step-summary
            else
              agent33 bench report \
                test-results/ctrf-smoke-report.json \
                --github-step-summary
            fi
          else
            {
              echo "## SkillsBench benchmark report"
              echo
              echo "- Smoke CTRF report was not generated."
            } >> "$GITHUB_STEP_SUMMARY"
          fi

      - name: Upload benchmark artifacts
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: benchmark-smoke-results
          path: engine/test-results/
          retention-days: 30