Skip to content

CI

CI #356

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Nightly fuzz run at 02:15 UTC. Only the `scenario-fuzz` job
# reacts to this trigger — see its `if: github.event_name ==
# 'schedule'` guard.
- cron: '15 2 * * *'
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
jobs:
changes:
name: Change Scope
runs-on: ubuntu-latest
outputs:
docs: ${{ steps.filter.outputs.docs }}
non_docs: ${{ steps.filter.outputs.non_docs }}
docs_only: ${{ steps.docs_only.outputs.value }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
docs:
- 'docs/**'
- 'mkdocs.yml'
- 'scripts/generate-docs.sh'
- '.github/workflows/docs-publish.yml'
non_docs:
- '**'
- '!docs/**'
- '!mkdocs.yml'
- '!scripts/generate-docs.sh'
- '!.github/workflows/docs-publish.yml'
- id: docs_only
shell: bash
run: |
if [ "${{ steps.filter.outputs.docs }}" = "true" ] && [ "${{ steps.filter.outputs.non_docs }}" != "true" ]; then
echo "value=true" >> "$GITHUB_OUTPUT"
else
echo "value=false" >> "$GITHUB_OUTPUT"
fi
check:
name: Rust Checks (${{ matrix.os }})
needs: changes
if: github.event_name == 'push' || needs.changes.outputs.non_docs == 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: Install system dependencies
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
sudo apt-get update && sudo apt-get install -y tmux
elif [ "$RUNNER_OS" = "macOS" ]; then
brew install tmux
fi
shell: bash
- name: Install kanban-md
run: |
KMD_VERSION="0.32.2"
if [ "$RUNNER_OS" = "Linux" ]; then
ARCH="linux_amd64"
elif [ "$RUNNER_OS" = "macOS" ]; then
ARCH="darwin_arm64"
fi
curl -sL "https://github.com/antopolskiy/kanban-md/releases/download/v${KMD_VERSION}/kanban-md_${KMD_VERSION}_${ARCH}.tar.gz" | sudo tar xz -C /usr/local/bin kanban-md
shell: bash
- name: Configure git identity and default branch
run: |
git config --global user.email "ci@batty.test"
git config --global user.name "Batty CI"
git config --global init.defaultBranch main
shell: bash
- name: Lint
run: make lint
- name: Build
run: cargo build
- name: Verify parity equivalence
run: cargo run -- verify
- name: Upload verification report
if: always()
uses: actions/upload-artifact@v4
with:
name: verification-report-${{ matrix.os }}
path: .batty/reports/verification/latest.md
if-no-files-found: ignore
- name: Test
run: >-
cargo test --
--skip tmux::tests::create_session
--skip tmux::tests::create_window
--skip tmux::tests::capture_pane
--skip tmux::tests::send_keys
--skip tmux::tests::session_with_short
--skip orchestrator::tests::status_bar
--skip orchestrator::tests::handle_prompt_tier2
--skip orchestrator::tests::harness_direct_reply
--skip tier2::tests::call_supervisor
--skip work::phase_worktree::tests::prepare_agent_worktrees_creates
--skip team::daemon::tests::startup_cwd_validation_corrects_all_agent_panes
--skip team::daemon::tests::restart_member_corrects_mismatched_cwd_after_respawn
--skip team::daemon::health::tests::check_backend_health
--skip team::daemon::health::tests::uncommitted_diff_lines
--skip team::daemon::health::tests::reconcile_skips_unmerged_branch
--skip team::daemon::health::checks::tests::check_backend_health_emits_event_on_transition
--skip team::daemon::health::checks::tests::check_backend_health_no_event_when_state_unchanged
--skip team::verification::tests::verify_project_updates_parity_and_writes_report
--skip team::verification::tests::verify_project_detects_regressions_from_previous_pass
--skip team::merge::operations::tests::merge_rebase_additive_conflict_keeps_both_sides
--skip team::tact::parser::tests::create_board_tasks_skips_equivalent_generated_specs
--skip team::task_loop::tests::test_run_tests_in_worktree_returns_pass_fail
--skip team::daemon::telegram_bridge::tests::maybe_intervene_triage_backlog_marks_member_working_after_live_delivery
- name: Scenario framework (prescriptive + fuzz smoke)
run: cargo test --test scenarios --features scenario-test
env:
# Default proptest cases; the nightly fuzz job scales this up.
PROPTEST_CASES: '32'
scenario-fuzz:
name: Scenario Framework Fuzz (nightly)
needs: changes
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux
- name: Install kanban-md
run: |
KMD_VERSION="0.32.2"
curl -sL "https://github.com/antopolskiy/kanban-md/releases/download/v${KMD_VERSION}/kanban-md_${KMD_VERSION}_linux_amd64.tar.gz" | sudo tar xz -C /usr/local/bin kanban-md
shell: bash
- name: Run fuzz targets with expanded case budget
run: |
cargo test --test scenarios --features scenario-test --release fuzz_workflow_happy
cargo test --test scenarios --features scenario-test --release fuzz_workflow_with_faults
cargo test --test scenarios --features scenario-test --release fuzz_restart_resilience
env:
PROPTEST_CASES: '2048'
PROPTEST_MAX_SHRINK_ITERS: '8192'
coverage:
name: Code Coverage
needs: changes
if: github.event_name == 'push' || needs.changes.outputs.non_docs == 'true'
runs-on: ubuntu-latest
# Tarpaulin intermittently segfaults on subprocess-heavy tests (child PTYs,
# fake shim channels). Coverage is a reporting metric, not a correctness
# gate — don't block merge on a flaky profiler. The main Rust Checks job
# is the source of truth for test correctness.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y tmux
- name: Install kanban-md
run: |
KMD_VERSION="0.32.2"
if [ "$RUNNER_OS" = "Linux" ]; then
ARCH="linux_amd64"
elif [ "$RUNNER_OS" = "macOS" ]; then
ARCH="darwin_arm64"
fi
curl -sL "https://github.com/antopolskiy/kanban-md/releases/download/v${KMD_VERSION}/kanban-md_${KMD_VERSION}_${ARCH}.tar.gz" | sudo tar xz -C /usr/local/bin kanban-md
shell: bash
- name: Configure git identity and default branch
run: |
git config --global user.email "ci@batty.test"
git config --global user.name "Batty CI"
git config --global init.defaultBranch main
shell: bash
- name: Install cargo-tarpaulin
run: cargo install cargo-tarpaulin
- name: Generate coverage
shell: bash
run: |
set +e
cargo tarpaulin --out xml --output-dir coverage/ --skip-clean --include-tests \
-- \
--skip tmux::tests::create_session \
--skip tmux::tests::create_window \
--skip tmux::tests::capture_pane \
--skip tmux::tests::send_keys \
--skip tmux::tests::session_with_short \
--skip orchestrator::tests::status_bar \
--skip orchestrator::tests::handle_prompt_tier2 \
--skip orchestrator::tests::harness_direct_reply \
--skip tier2::tests::call_supervisor \
--skip work::phase_worktree::tests::prepare_agent_worktrees_creates \
--skip team::daemon::tests::startup_cwd_validation_corrects_all_agent_panes \
--skip team::daemon::tests::restart_member_corrects_mismatched_cwd_after_respawn \
--skip worktree::tests::branch_fully_merged \
--skip worktree::tests::reset_worktree_to_base \
--skip team::daemon::health::checks::tests::check_backend_health_emits_event_on_transition \
--skip team::daemon::health::checks::tests::check_backend_health_no_event_when_state_unchanged \
--skip team::daemon::health::tests::uncommitted_diff_lines \
--skip team::verification::tests::verify_project_updates_parity_and_writes_report \
--skip team::verification::tests::verify_project_detects_regressions_from_previous_pass \
--skip team::merge::operations::tests::merge_rebase_additive_conflict_keeps_both_sides \
--skip team::tact::parser::tests::create_board_tasks_skips_equivalent_generated_specs \
--skip team::task_loop::tests::test_run_tests_in_worktree_returns_pass_fail \
--skip team::daemon::health::preflight::tests::startup_preflight_verifies_worktree_operations \
--skip team::daemon::health::preflight::tests::startup_preflight_accepts_available_agent_binaries \
--skip team::daemon::telegram_bridge::tests::maybe_intervene_triage_backlog_marks_member_working_after_live_delivery
status=$?
if [ "$status" -ne 0 ]; then
echo "::warning::cargo tarpaulin failed with exit code $status; coverage is non-blocking for this repository"
fi
exit 0
- name: Upload to Codecov
if: ${{ hashFiles('coverage/cobertura.xml') != '' }}
uses: codecov/codecov-action@v5
with:
files: coverage/cobertura.xml
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
slug: battysh/batty
docs:
name: Docs Quality
needs: changes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install docs dependencies
run: |
python -m pip install --upgrade pip
python -m pip install "mkdocs>=1.6,<2" mkdocs-material mdformat mdformat-gfm
- name: Refresh generated references
run: ./scripts/generate-docs.sh
- name: Markdown format check
shell: bash
run: |
mapfile -t doc_files < <(printf '%s\n' docs/*.md docs/reference/*.md | sort)
mdformat --check "${doc_files[@]}"
- name: Markdown lint check
run: npx --yes markdownlint-cli2 docs/*.md docs/reference/*.md
- name: Internal link audit
run: mkdocs build --strict
- name: Verify generated docs are committed
run: git diff --exit-code -- docs/reference/cli.md docs/reference/config.md