#!/usr/bin/env bash # # Bidirectional traceability check (docs/process.md ยง5). # # Verifies HLR -> LLR -> source -> test in both directions: # - every low-level requirement is implemented and tested # - every requirement a test names actually exists # # The third check is the one that is easy to omit and that fails silently when # omitted: it catches stale references left behind by a renumbered or withdrawn # requirement. # # Exits non-zero if any check fails, so it can gate a commit or CI job. set -euo pipefail cd "$(dirname "$0")/.." tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT grep -ohE 'MJB-LLR-[0-9]+' docs/requirements/llr.md | sort -u > "$tmp/defined" grep -rohE 'MJB-LLR-[0-9]+' src/ | sort -u > "$tmp/tagged" grep -rohE 'mjb_llr_[0-9]+' src/ tests/ \ | sed 's/mjb_llr_/MJB-LLR-/' | sort -u > "$tmp/tested" defined=$(wc -l < "$tmp/defined") tagged=$(wc -l < "$tmp/tagged") tested=$(wc -l < "$tmp/tested") printf 'defined=%s tagged=%s tested=%s\n\n' "$defined" "$tagged" "$tested" status=0 report() { # $1=label $2=file $3=explanation if [ -s "$2" ]; then printf '%s:\n' "$1" sed 's/^/ /' "$2" printf ' -> %s\n\n' "$3" status=1 fi } comm -23 "$tmp/defined" "$tmp/tagged" > "$tmp/untagged" comm -23 "$tmp/defined" "$tmp/tested" > "$tmp/untested" comm -13 "$tmp/defined" "$tmp/tested" > "$tmp/unknown" report "Requirements with no implementation" "$tmp/untagged" \ "tag the implementing item with // MJB-LLR-nnn" report "Requirements with no test" "$tmp/untested" \ "add a test named mjb_llr_nnn_" report "Tests naming a requirement that does not exist" "$tmp/unknown" \ "typo, or a stale reference to a withdrawn requirement" if [ "$status" -eq 0 ]; then echo "OK: traceability complete in both directions." fi exit "$status"