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
124
125
126
127
128
129
130
131
132
133
134
# 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 conform (zero errors; warnings are advisory)
shell: bash
# `validate` was renamed `conform` (V&V terminology). conform exits
# non-zero only on errors; advisory warnings (e.g. REQ-V-0037
# author==verifier on the SRs, REQ-V-0038 awaiting human co-sign,
# REQ-V-0010 statement style) are expected and do not gate.
run: |
set -euo pipefail
OUT=$(./target/release/req${{ matrix.os == 'windows-latest' && '.exe' || '' }} conform)
echo "$OUT"
echo "$OUT" | grep -qE "^0 error\\(s\\)"
- 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: file-level matching here (changed file must carry a REQ
# marker), NOT --marker-near-hunks. The strict hunk-level check stays
# on the per-commit pre-commit hook (`req hooks install --strict`),
# where it's cheap and precise; applying it to the *cumulative*
# branch/merge diff just flags large-rename churn far from a marker
# in files that are already requirement-backed.
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
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