name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
types: [opened, reopened, synchronize]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
rust: ${{ steps.filter.outputs.rust }}
docs: ${{ steps.filter.outputs.docs }}
eval-analyzers: ${{ steps.filter.outputs.eval-analyzers }}
terminal-bench: ${{ steps.filter.outputs.terminal-bench }}
steps:
- uses: actions/checkout@v7
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
rust:
- '**/*.rs'
- '**/Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- '**/build.rs'
- '.github/workflows/ci.yml'
docs:
- '**/*.md'
# The regression-gate analyzers under harness_basic are pure Python
# and decide whether the nightly Search Efficiency Eval passes. They
# never touch the Rust toolchain, so give them their own fast leg that
# runs whenever an analyzer or its tests change.
eval-analyzers:
- 'evals/harness_basic/*.py'
- 'evals/harness_basic/tests/**'
- '.github/workflows/ci.yml'
# The terminal_bench study and its Harbor agent adapter. Its tests
# need no Docker, network, or API key, but they do import the study's
# third-party deps, so they get their own uv leg rather than joining
# the stdlib-only analyzer job above.
terminal-bench:
- 'evals/terminal_bench/**'
- '.github/workflows/ci.yml'
docs-boundary:
name: Docs boundary
needs: changes
if: needs.changes.outputs.docs == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
- name: Validate release and knowledge metadata
run: |
python3 scripts/validate_okf.py knowledge --check-links
python3 scripts/test_validate_okf.py
python3 scripts/test_publish_order.py
test "$(python3 scripts/publish_order.py | tail -1)" = yolop
- name: Public docs boundary
run: |
if grep -R -n -E --include='*.md' '\]\([^)]*(knowledge/|\.agents/)' README.md docs; then
echo "::error::Public documentation must not link to internal knowledge/ or .agents/ files."
exit 1
fi
eval-analyzers:
name: Eval analyzers (Python)
needs: changes
if: needs.changes.outputs.eval-analyzers == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
- name: Run analyzer unit tests
run: python3 -m unittest discover -s evals/harness_basic/tests -v
terminal-bench-study:
name: Terminal-Bench study (Python)
needs: changes
if: needs.changes.outputs.terminal-bench == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Run study + adapter unit tests
working-directory: evals/terminal_bench
env:
PYTHONPATH: .
run: uv run --python 3.12 --with mira-eval --with harbor -m unittest discover -s tests -v
lint:
name: Format + Clippy
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.94.0
with:
components: rustfmt, clippy
- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
shared-key: "debug"
- name: Format check
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
test:
name: Build + Tests + Offline Smoke
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.94.0
- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
shared-key: "debug"
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- name: Build
run: cargo build --locked --all-targets
- name: Unit and integration tests with coverage
run: |
cargo llvm-cov --no-report --all-features --workspace
cargo llvm-cov report --lcov --output-path lcov.info
cargo llvm-cov report --summary-only
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v7
with:
files: lcov.info
fail_ci_if_error: ${{ github.event_name == 'push' }}
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: lcov-coverage
path: lcov.info
if-no-files-found: error
live-smoke:
name: Live Provider Smoke (Doppler)
runs-on: ubuntu-latest
timeout-minutes: 15
if: (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && github.actor != 'dependabot[bot]'
needs: [lint, test]
env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
steps:
- uses: actions/checkout@v7
- name: Verify DOPPLER_TOKEN is set
run: |
if [ -z "${DOPPLER_TOKEN}" ]; then
echo "::error::DOPPLER_TOKEN is required for the live smoke job but is not set. Configure the repo secret or update the job's outer 'if:' to skip this environment."
exit 1
fi
- name: Install Doppler CLI
uses: dopplerhq/cli-action@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.94.0
- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
shared-key: "debug"
- name: Build the test binary (offline)
run: cargo test --no-run --test integration
- name: Live provider smoke tests
env:
YOLOP_REQUIRE_LIVE_TESTS: "1"
run: doppler run -- cargo test --test integration -- --nocapture
sandbox-contract:
name: Sandbox Contract (${{ matrix.os }})
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.94.0
- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
shared-key: "sandbox-${{ runner.os }}"
- name: Run sandbox contract suite
run: cargo test sandbox -- --nocapture
windows:
name: Windows Build + Shell Smoke
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.94.0
- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
shared-key: "windows"
- name: Build the yolop binary
run: cargo build --locked -p yolop
- name: Sandbox + shell tests
run: cargo test -p yolop sandbox -- --nocapture
- name: Offline smoke (PowerShell shell)
run: cargo run -p yolop -- --provider llmsim -p "hi"