1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 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:
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:
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
# Wraps validate + coverage + stale + audit + the
# changed-requirement diff into a single PR-style report, and
# — critically — gates on changed source files that contain
# zero REQ markers. This is the rule that catches "new code
# shipped without a backing requirement", the exact failure
# mode that took three releases to surface. On `push` events
# the base ref is HEAD~1 (we only have HEAD..HEAD on PRs from
# the merge commit). The gate is treated as advisory on
# main-branch pushes so the first run after merge does not
# retroactively block the build for files added before the
# gate existed; PRs get the hard gate.
if: github.event_name == 'pull_request'
run: |
./target/release/req${{ matrix.os == 'windows-latest' && '.exe' || '' }} \
review --base "origin/${{ github.base_ref }}" --path . --gate
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