req-cli 0.2.5

Managed requirements CLI for LLM agents and humans
# CI for req-cli. Runs on every push and PR; matrix across the three
# platforms that the release workflow ships binaries for.
name: ci

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    name: test (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    steps:
      - uses: actions/checkout@v5
        with:
          # Need history for `req audit` and `req diff` tests.
          fetch-depth: 0

      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: cargo build (release, warnings as errors)
        env:
          RUSTFLAGS: "-D warnings"
        run: cargo build --release --locked

      - name: cargo test (serial within and across binaries)
        # --test-threads=1 serialises tests WITHIN a binary. Cargo also runs
        # different test binaries in parallel; the concurrency suite spawns
        # child `req` processes that race with other binaries' children for
        # OS resources on slow / loaded CI runners. Run each test binary
        # one at a time to keep the file-lock contention test inside its
        # 30s acquisition timeout.
        shell: bash
        run: |
          set -euo pipefail
          for t in cli concurrency edge_cases slice_a slice_b slice_c slice_g storage validator; do
            echo "::group::cargo test --test $t"
            cargo test --release --locked --test "$t" -- --test-threads=1
            echo "::endgroup::"
          done

      - name: req validate (zero errors AND zero warnings)
        shell: bash
        run: |
          set -euo pipefail
          OUT=$(./target/release/req${{ matrix.os == 'windows-latest' && '.exe' || '' }} validate)
          echo "$OUT"
          echo "$OUT" | grep -qE "^OK — [0-9]+ requirement\\(s\\), no findings\\.$"

      - name: req coverage --strict (gates orphans and ghosts)
        shell: bash
        # REQ-0051 / REQ-0052 are verification-only requirements with no
        # code site. REQ-0061 / REQ-0082 are policy meta-requirements
        # exercised only by test files (test-only). All four are documented
        # exclusions; the strict gate enforces that no NEW orphans / ghosts
        # appear.
        run: |
          ./target/release/req${{ matrix.os == 'windows-latest' && '.exe' || '' }} \
            coverage --path . --strict \
              --allow REQ-0051 --allow REQ-0052 \
              --allow REQ-0061 --allow REQ-0082

      - name: req doctor (per-clone setup audit)
        shell: bash
        # Doctor exits non-zero on any failing check (pre-commit hook,
        # gitattributes pin, merge driver, commit signing). On hosted CI
        # the signing check will fail (no key configured) — that's the
        # honest signal we want surfaced, not silenced. Treat as advisory
        # via `|| true` to avoid blocking the build on signing setup.
        run: |
          ./target/release/req${{ matrix.os == 'windows-latest' && '.exe' || '' }} \
            doctor || true

      - name: req stale (informational)
        shell: bash
        # Staleness is by definition transient — every change to a tested
        # file invalidates that file's records until the next promote run.
        # Print the report for visibility but don't gate on it.
        run: |
          ./target/release/req${{ matrix.os == 'windows-latest' && '.exe' || '' }} \
            stale --path . || true

      - name: req review --gate (spec hygiene on changed files)
        shell: bash
        # Gates on changed source files that contain zero REQ markers
        # — the rule that catches "new code shipped without a backing
        # requirement". Fires on BOTH push and pull_request so that
        # direct pushes to main, force-pushes, and PR-merge commits
        # all face the same check the pre-commit hook applies locally.
        # The base ref differs:
        #   - pull_request: origin/<base-branch> (the PR target)
        #   - push:         HEAD~1 (the previous commit on this branch)
        # REQ-0100: --marker-near-hunks 50 matches the strict pre-commit
        # hook this project installs (`req hooks install --strict`).
        run: |
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            BASE="origin/${{ github.base_ref }}"
          else
            BASE="HEAD~1"
          fi
          ./target/release/req${{ matrix.os == 'windows-latest' && '.exe' || '' }} \
            review --base "$BASE" --path . --gate \
              --marker-near-hunks 50

  lint:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo fmt --all -- --check
      - run: cargo clippy --release --locked -- -D warnings