opencrabs 0.3.52

The autonomous, self-improving AI agent. Single Rust binary. Every channel. Install with: cargo install opencrabs
Documentation
name: CI

# Trigger model (single-maintainer OSS, GitHub Flow):
#   - pull_request: full gate on contributions — required to review
#     external PRs safely.
#   - push to main: catch direct pushes and keep main provably green.
#     Docs-only commits are skipped via paths-ignore so they don't burn
#     the matrix; rapid pushes cancel older runs via `concurrency`. The
#     heavy cross-platform BUILD job is gated off routine main pushes
#     (see its `if:`) so a normal commit only pays for lint + Linux test
#     + audit (~5-8 min, cached, cancellable). This replaces the old
#     opt-out `[skip ci]`-on-every-commit toil with sensible defaults.
#   - workflow_dispatch: run the full matrix on demand (e.g. to verify
#     Windows/macOS before cutting a release tag).
# CI deliberately does NOT run on tags — the release workflow owns tags
# and trusts the commit already passed CI on its way to main. That also
# kills the old double-build where ci.yml and release.yml both compiled
# Windows/macOS on the same tag event.
on:
  pull_request:
  push:
    branches: [main]
    paths-ignore:
      - '**.md'
      - 'LICENSE*'
      - '.gitignore'
      - 'docs/**'
  workflow_dispatch:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  CARGO_INCREMENTAL: 0

jobs:
  # ── Stage 1: Lint (fast gate — fails early before burning compute) ────────
  # Pattern lifted from ZeroClaw: run fmt + clippy first. If either fails,
  # every downstream job is skipped. Avoids burning ~25 min on the
  # cross-platform build/test matrix for a PR that won't lint anyway.
  lint:
    name: Lint
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v5

      - name: Install Rust (from rust-toolchain.toml)
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
          components: rustfmt, clippy

      - uses: swatinem/rust-cache@v2
        with:
          cache-on-failure: true
          save-if: ${{ github.ref == 'refs/heads/main' }}

      - name: Install ALSA dev (for local-stt feature)
        run: sudo apt-get update -qq && sudo apt-get install -y libasound2-dev

      - name: Check formatting
        # Soft-fail: legacy un-formatted code blocks shouldn't block PRs
        # yet. Promote to hard-fail once a fmt sweep lands on main.
        continue-on-error: true
        run: cargo fmt --all -- --check

      - name: Clippy
        run: cargo clippy --locked --lib --bins --tests --all-features -- -D warnings

  # ── Stage 2: Build verification (parallel, gated on lint + audit) ────────
  # Cross-platform BUILD on Windows + macOS confirms target compatibility
  # without running tests there (tests run on Linux only — see `test`
  # below). Why: Windows runners are 3-5x slower than Linux for cargo,
  # and most of our test surface is platform-independent. ZeroClaw uses
  # the same pattern. If a Windows/macOS-specific test ever regresses
  # we can promote it via the matrix.
  build:
    name: Build (${{ matrix.target }})
    needs: [lint, audit]
    # Cross-platform build verification is the expensive job (Windows is
    # 3-5x slower). Run it where it pays off — on PRs (gate contributions)
    # and on-demand (workflow_dispatch, e.g. before a release tag) — but
    # NOT on every routine push to main. Direct main pushes still get
    # lint + Linux test + audit. Any platform regression that slips
    # through is caught by release.yml's build-release matrix at tag time.
    if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
    runs-on: ${{ matrix.os }}
    timeout-minutes: 40
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            # release.yml uses --all-features on Windows too (line 147),
            # so the local-stt/local-tts crate chain DOES build on
            # windows-msvc. Mirror that here to catch any platform
            # regression before release time.
            features: --all-features
          - os: macos-latest
            target: aarch64-apple-darwin
            features: --all-features
    steps:
      - uses: actions/checkout@v5

      - name: Install Rust (from rust-toolchain.toml)
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
          targets: ${{ matrix.target }}

      # rust-cache deliberately skipped on Windows — ZeroClaw confirmed
      # the cache rarely pays off there and sometimes flakes the runner.
      - uses: swatinem/rust-cache@v2
        if: runner.os != 'Windows'
        with:
          cache-on-failure: true
          save-if: ${{ github.ref == 'refs/heads/main' }}

      - name: Install CMake (Windows)
        if: runner.os == 'Windows'
        uses: lukka/get-cmake@latest

      - name: Install NASM (Windows)
        if: runner.os == 'Windows'
        uses: ilammy/setup-nasm@v1

      - name: Build (Windows)
        if: runner.os == 'Windows'
        env:
          AWS_LC_SYS_CMAKE_BUILDER: "1"
        run: cargo build --locked --profile ci --target ${{ matrix.target }} ${{ matrix.features }}

      - name: Build (macOS)
        if: runner.os == 'macOS'
        env:
          CFLAGS: -march=armv8-a+crypto
          CXXFLAGS: -march=armv8-a+crypto
        run: cargo build --locked --profile ci --target ${{ matrix.target }} ${{ matrix.features }}

  # ── Stage 3: Test on Linux (gated on lint + audit, parallel with build) ──
  # Tests run on Linux only — Windows/macOS get build verification via
  # the `build` job above. ZeroClaw uses the same split because the
  # cross-platform matrix triples wall-clock for little extra coverage
  # on this codebase (most tests are platform-independent).
  #
  # Stays on `cargo test` for now: a few timing-sensitive tests
  # (rate_limiter pacing) rely on cargo test's threadpool semantics
  # and fail under nextest's process-per-test isolation. nextest
  # migration is a follow-up once those tests are made portable.
  test:
    name: Test (Linux)
    needs: [lint, audit]
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v5

      - name: Install Rust (from rust-toolchain.toml)
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable

      - uses: swatinem/rust-cache@v2
        with:
          cache-on-failure: true
          save-if: ${{ github.ref == 'refs/heads/main' }}

      - name: Install mold + ALSA dev (Linux)
        run: sudo apt-get update -qq && sudo apt-get install -y mold clang libasound2-dev

      - name: Run tests
        env:
          CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: clang
          CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C link-arg=-fuse-ld=mold"
        run: cargo test --locked --profile ci --all-features --verbose

  # ── Stage 1b: Security (fast gate — runs alongside lint, before build/test)
  # cargo-audit only scans Cargo.lock; it needs no compile, so it runs in
  # PARALLEL with lint as a second fast gate rather than after it. build/test/
  # coverage all `needs: [lint, audit]`, so a fresh advisory (this fails in
  # ~4s) skips the ~22-min Linux test and the cross-platform build matrix
  # instead of racing them. Lockfile-only fix → no recompile to re-gate.

  audit:
    name: Cargo Audit
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v5

      - name: Install Rust (from rust-toolchain.toml)
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable

      - name: Install cargo-audit
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-audit

      - name: Run cargo audit
        # Ignored advisory:
        #   RUSTSEC-2024-0437  protobuf 2.x via prometheus 0.13.4 → crabrace 0.1.0
        #     DOS-class (uncontrolled recursion). No upstream fix without a
        #     major-version bump in the prometheus chain we don't control.
        #     Re-evaluate when crabrace ships a release that drops prometheus
        #     0.13.x.
        run: cargo audit --ignore RUSTSEC-2024-0437

  # ── Stage 5: Coverage (on-demand only) ───────────────────────────────────
  # Tarpaulin is the slowest job (~12 min). Running it on every main push
  # would re-add the cost we just trimmed, so it's on-demand: trigger the
  # CI workflow manually (workflow_dispatch) when you want a fresh coverage
  # report. PRs and pushes still get full test coverage via the `test` job;
  # this job only publishes the percentage report.
  coverage:
    name: Code Coverage
    needs: [lint, audit]
    if: github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v5

      - uses: swatinem/rust-cache@v2
        with:
          cache-on-failure: true
          save-if: ${{ github.ref == 'refs/heads/main' }}

      - name: Install Rust (from rust-toolchain.toml)
        uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable

      - name: Install ALSA dev
        run: sudo apt-get update -qq && sudo apt-get install -y libasound2-dev

      - name: Install tarpaulin
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-tarpaulin

      - name: Generate coverage
        run: cargo tarpaulin --locked --out Xml --no-default-features --features "telegram,whatsapp,discord,slack,trello"
        # Note: local-stt excluded — tarpaulin uses rust-lld which can't handle
        # duplicate ggml symbols from whisper-rs-sys + llama-cpp-sys-2.
        # local-tts added when feature lands on main.

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v5
        with:
          files: ./cobertura.xml

  # ── Required gate ────────────────────────────────────────────────────────
  # Single status check for branch protection. Internal job structure
  # can change without touching branch protection settings.
  # Coverage is intentionally excluded — it's main-only.
  ci-gate:
    name: CI Required Gate
    if: always()
    needs: [lint, build, test, audit]
    runs-on: ubuntu-latest
    steps:
      - name: Check results
        run: |
          if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
            echo "::error::One or more CI jobs failed or were cancelled"
            exit 1
          fi