#!/usr/bin/env bash # The dead-ID audit: every improvise- the tree cites must be a ticket bd # knows. See AGENTS.md, "Cite a ticket ID you have read back" (improvise-52eq). set -euo pipefail usage() { cat >&2 <<'EOF' usage: bd-audit-ids Run from anywhere inside the repository. Takes no arguments. exit 0 every cited id is a ticket bd lists exit 1 at least one cited id is not; each is printed exit 3 bd could not answer: missing, failing, or listing nothing exit 4 usage error, not inside a git repository, or a cited file could not be read or is absent (only a file `git diff --diff-filter=D` reports as deleted is skipped) The comparison is POSITIVE: a cited id passes only by appearing in what `bd list --all` returns. The recipe this replaced looked for bd's "not found" lines and reported "all resolve" when there were none, so a bd that never ran read as a clean audit. THIS SCRIPT'S `$?` MUST BE THE ONE YOU READ. Nothing may follow it in the same pipeline or `;` list. EOF exit 4 } [ $# -eq 0 ] || usage BD="${BD_AUDIT_BD:-bd}" ROOTS=(context README.md AGENTS.md CLAUDE.md crates src tests examples web harness docs .agents .claude) # Crate, package, artifact and file names that are shaped like ids. Anchored, # because an unanchored `gp` would silently exclude any real ticket id starting with it. EXCLUDE='^improvise-(core|formula|io|protocol|command|gp|ws|worker|wasm|harness|registry|report|side|web)$' work=$(mktemp -d) trap 'rm -rf "$work"' EXIT top=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "bd-audit-ids: not inside a git repository, so nothing was checked." >&2 exit 4 } cd "$top" # A deleted tracked file is skipped ON PURPOSE: its ids are leaving the tree. Only # `diff --diff-filter=D` says deleted; any other absent path is exit 4 (tdfd, cota). : > "$work/absent" git ls-files -z --cached --others --exclude-standard -- "${ROOTS[@]}" | while IFS= read -r -d '' f; do if [ -e "$f" ] || [ -L "$f" ]; then printf '%s\0' "$f" elif [ -z "$(git --literal-pathspecs diff --name-only --diff-filter=D -- "$f")" ]; then printf '%s\n' "$f" >> "$work/absent" fi done > "$work/files" if [ -s "$work/absent" ]; then echo "bd-audit-ids: tracked but absent, and not deleted, so never read:" >&2 sed 's/^/ /' "$work/absent" >&2 exit 4 fi # xargs reports grep's no-match (1) and its read error (2) alike, as 123, so # each batch maps 1 to success and 2 to 255: xargs stops, nonzero (GNU 124, BSD 1). : > "$work/raw" set +e if [ -s "$work/files" ]; then xargs -0 bash -c 'grep -d skip -ohE "$0" "$@"; [ $? -le 1 ] || exit 255' \ 'improvise-[a-z0-9]{3,8}\b' < "$work/files" > "$work/raw" fi rc=$? set -e [ "$rc" -eq 0 ] || { echo "bd-audit-ids: a cited file could not be read (xargs $rc), so the audit is incomplete." >&2 exit 4 } sort -u "$work/raw" | { grep -vE "$EXCLUDE" || true; } > "$work/cited" if ! command -v "$BD" >/dev/null 2>&1; then echo "bd-audit-ids: '$BD' is not on PATH, so nothing was checked." >&2 exit 3 fi if ! "$BD" list --all --limit 0 --json > "$work/known.json" 2> "$work/bd.err"; then echo "bd-audit-ids: '$BD list' failed, so nothing was checked:" >&2 cat "$work/bd.err" >&2 exit 3 fi if ! jq -e 'type == "array" and all(.[]; (.id | type) == "string")' \ "$work/known.json" > /dev/null 2>&1; then echo "bd-audit-ids: '$BD list' did not print a JSON list of issues with ids." >&2 exit 3 fi jq -r '.[].id' "$work/known.json" | sort -u > "$work/known" if [ ! -s "$work/known" ]; then echo "bd-audit-ids: '$BD list' returned no issues; refusing to call that a pass." >&2 exit 3 fi comm -23 "$work/cited" "$work/known" > "$work/dead" if [ -s "$work/dead" ]; then echo "bd-audit-ids: cited but not a ticket bd knows:" sed 's/^/ /' "$work/dead" exit 1 fi printf 'bd-audit-ids: all %d cited ids resolve (bd knows %d)\n' \ "$(wc -l < "$work/cited" | tr -d ' ')" "$(wc -l < "$work/known" | tr -d ' ')"