97a9379f79
improvise-cota, found in the delta review of #244. The tdfd fix skipped an
absent tracked path whenever `ls-files -d` listed it. `ls-files -d` also
lists an absent --assume-unchanged path. `status` hides that path, and
`commit -a` never records the deletion, so its citations stayed in HEAD
unread and the audit exited 0.
The deletion oracle is now `diff --name-only --diff-filter=D`. It honours
both index flags and still lists a plain unstaged delete.
The new case in bd-audit-ids-test.sh was red at 33d563a9. A rival that
treats every absent path as unread fails only the deliberate
deleted-file control.
Stacked on #244 (tooling/lqs0-audit-exits).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
106 lines
3.9 KiB
Bash
Executable File
106 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# The dead-ID audit: every improvise-<id> 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 ' ')"
|