The "pre-#113 corpus on main trips the gate" case reconstructed the historical (pre-sweep) corpus from the live `main` ref. `main` is the moving integration branch, and the #113 fix (ed8c99e) landed back onto it — so the moment that fix merged, `main` stopped containing the bare `git remote get-url origin` drift the case exists to catch, and the assertion "the gate should fail on this corpus" silently flipped to false. This blocked `git push` on every branch via the run-tests pre-push hook, unrelated to whatever was actually being pushed. Pin to598a7c3, the last commit beforeed8c99ewhere gitea-issues/SKILL.md still had the unprefixed call. A specific commit SHA is immutable, unlike `main`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PDj6F7SPXzh3FtPN78dZ88
235 lines
8.2 KiB
Bash
235 lines
8.2 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Tests for scripts/check-rtk-prefix.sh — ADR-0023 clause 1.
|
|
#
|
|
# Case 1 is the one that earns the rest: it runs the gate against the corpus as
|
|
# it stood BEFORE the #113 sweep, and asserts it fails there. A gate that only
|
|
# passes on the already-fixed tree proves nothing about whether it would have
|
|
# caught the drift it was written for.
|
|
#
|
|
# Pinned to a commit SHA, not the `main` ref: `main` is the moving integration
|
|
# branch, and the #113 fix (ed8c99e) landed back on it, so querying live
|
|
# `main` for the "pre-sweep" corpus stopped reproducing the drift the moment
|
|
# that fix merged — the case silently started asserting the opposite of what
|
|
# it says. 598a7c3 is the last commit where gitea-issues/SKILL.md still had
|
|
# the bare `git remote get-url origin` call ed8c99e fixed.
|
|
#
|
|
# Everything after that is synthetic. The false-NEGATIVE cases (a bare command
|
|
# the gate must catch) and the false-POSITIVE cases (correct content the gate
|
|
# must leave alone) carry equal weight: this hook's failure mode is not missing
|
|
# a violation, it is firing on deliberately-bare clause-2 and clause-3 content
|
|
# until someone adds it to SKIP.
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
SCRIPT="$REPO_ROOT/scripts/check-rtk-prefix.sh"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
|
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "SKIP: python3 is not installed — the script under test fails closed on it, so every case here would only re-assert the missing-dependency guard"
|
|
exit 77
|
|
fi
|
|
|
|
RUN_TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$RUN_TMP"' EXIT
|
|
|
|
# Writes $2 to a .md file and asserts the gate's verdict. $1 is "clean" or
|
|
# "dirty"; $3 is the case description.
|
|
expect() {
|
|
local want="$1" body="$2" desc="$3"
|
|
local f="$RUN_TMP/case.md"
|
|
printf '%s\n' "$body" > "$f"
|
|
local rc=0
|
|
bash "$SCRIPT" "$f" > "$RUN_TMP/out" 2>&1 || rc=$?
|
|
if [[ "$want" == "dirty" && $rc -eq 0 ]]; then
|
|
fail "$desc — expected a violation, exited 0"
|
|
elif [[ "$want" == "clean" && $rc -ne 0 ]]; then
|
|
fail "$desc — expected no violation, exited $rc"
|
|
sed 's/^/ /' "$RUN_TMP/out"
|
|
else
|
|
pass "$desc"
|
|
fi
|
|
}
|
|
|
|
echo "--- the pre-#113 corpus trips the gate ---"
|
|
|
|
# The commit set is derived from git, not hardcoded, at this fixed SHA: the
|
|
# point is "the drift this gate exists for", and a hardcoded path list goes
|
|
# stale the moment a file is renamed. The SHA itself is deliberately hardcoded
|
|
# — see the header comment for why it must NOT be a moving ref like `main`.
|
|
PRE_SWEEP_REF="598a7c326a4bbfa4a39007eb95d944c4150686e8"
|
|
if ! git -C "$REPO_ROOT" rev-parse --verify -q "$PRE_SWEEP_REF" >/dev/null; then
|
|
echo "SKIP: pre-sweep commit $PRE_SWEEP_REF not reachable locally (shallow clone?) — the historical-corpus case cannot be reconstructed"
|
|
exit 77
|
|
fi
|
|
|
|
PRE="$RUN_TMP/pre"
|
|
# A read loop, not the bash-4 array builtin: tests/test-vale-wrap.sh scans every
|
|
# script under tests/ for bash-4-only constructs, because these run on macOS's
|
|
# bash 3.2. Same reason for the `[@]+` guards on every array expansion below.
|
|
PRE_FILES=()
|
|
PRE_PATHS=()
|
|
while IFS= read -r PRE_REL; do
|
|
PRE_FILES+=("$PRE_REL")
|
|
PRE_PATHS+=("$PRE/$PRE_REL")
|
|
done < <(
|
|
git -C "$REPO_ROOT" ls-tree -r --name-only "$PRE_SWEEP_REF" -- 'plugins' \
|
|
| grep -E '^plugins/[^/]+/\.apm/(skills/.*\.md|agents/.*\.agent\.md)$' \
|
|
| grep -v '/README\.md$'
|
|
)
|
|
if [[ ${#PRE_FILES[@]} -eq 0 ]]; then
|
|
fail "no plugin skill/agent files found at $PRE_SWEEP_REF — the historical case checked nothing"
|
|
else
|
|
for f in ${PRE_FILES[@]+"${PRE_FILES[@]}"}; do
|
|
mkdir -p "$PRE/$(dirname "$f")"
|
|
git -C "$REPO_ROOT" show "$PRE_SWEEP_REF:$f" > "$PRE/$f"
|
|
done
|
|
if bash "$SCRIPT" ${PRE_PATHS[@]+"${PRE_PATHS[@]}"} > "$RUN_TMP/pre.out" 2>&1; then
|
|
fail "the pre-sweep corpus passed — the gate would not have caught the #113 drift"
|
|
else
|
|
hits="$(grep -c '^ADR-0023: ' "$RUN_TMP/pre.out" || true)"
|
|
if [[ "$hits" -lt 20 ]]; then
|
|
fail "the pre-sweep corpus produced only $hits findings — too few to be the known drift"
|
|
elif ! grep -q 'gitea-issues/SKILL.md.*git remote get-url origin' "$RUN_TMP/pre.out"; then
|
|
fail "the pre-sweep corpus failed, but not on the known gitea drift"
|
|
sed 's/^/ /' "$RUN_TMP/pre.out" | head -5
|
|
else
|
|
pass "the pre-sweep corpus fails with $hits findings, including the gitea sweep gap"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- shell fences: what must fail ---"
|
|
|
|
expect dirty '```bash
|
|
git commit -m "x"
|
|
```' "a bare git command in a bash fence"
|
|
|
|
expect dirty '```sh
|
|
rtk git add -u && git commit -m "x"
|
|
```' "a bare git after && on a line that starts with rtk git"
|
|
|
|
expect dirty '```bash
|
|
SKIP=check-yaml git commit -m "x"
|
|
```' "a bare git behind an environment-variable prefix"
|
|
|
|
expect dirty '```bash
|
|
url=$(git config remote.origin.url)
|
|
```' "a bare git inside a command substitution"
|
|
|
|
expect dirty '```console
|
|
$ git status
|
|
```' "a bare git behind a copied shell prompt"
|
|
|
|
echo ""
|
|
echo "--- shell fences: what must NOT fail ---"
|
|
|
|
expect clean '```bash
|
|
rtk git commit -m "x"
|
|
rtk git push
|
|
```' "prefixed commands"
|
|
|
|
expect clean '```bash
|
|
git stash list # bare per ADR-0023: rtk prints "No stashes" where git prints nothing
|
|
```' "a bare command carrying the ADR-0023 opt-out marker"
|
|
|
|
expect clean '```text
|
|
git worktree list --porcelain -z
|
|
```' "a non-shell fence (text) is out of scope"
|
|
|
|
expect clean '```yaml
|
|
entry: git
|
|
```' "a yaml fence is out of scope"
|
|
|
|
expect clean '```bash
|
|
# never reach for git commit --no-verify here
|
|
rtk git commit
|
|
```' "a bare git inside a shell comment"
|
|
|
|
expect clean 'Run `git switch <branch>` — no wait, this is prose, not a fence.' \
|
|
"a bare git in a prose line outside any fence"
|
|
|
|
echo ""
|
|
echo "--- dispatch tables ---"
|
|
|
|
expect dirty '| Operation | Run |
|
|
|---|---|
|
|
| List | `git worktree list -v` |' "a bare command opening a Run cell"
|
|
|
|
expect clean '| Operation | Run |
|
|
|---|---|
|
|
| List | `rtk git worktree list -v` |' "a prefixed command in a Run cell"
|
|
|
|
expect clean '| Operation | Run |
|
|
|---|---|
|
|
| List | `git worktree list -v` — bare per ADR-0023 |' \
|
|
"a bare Run cell carrying the opt-out marker"
|
|
|
|
# The clause-2 shapes that made an every-span check unusable. Both are real rows
|
|
# from git-worktrees/SKILL.md.
|
|
expect clean '| Operation | Run |
|
|
|---|---|
|
|
| Track | `rtk git worktree add --track -b <b> <p> <r>/<b>` — always correct. `git worktree add <p> <b>` expands to exactly this |' \
|
|
"a referential bare mention AFTER the instructed command in a Run cell"
|
|
|
|
expect clean '| Operation | Run |
|
|
|---|---|
|
|
| **Never** `git worktree add <p> <r>/<b>` | That ref resolves, so `git push` needs an explicit refspec |' \
|
|
"an anti-pattern row whose Run cell opens with prose"
|
|
|
|
expect clean '| Flag | Meaning |
|
|
|---|---|
|
|
| `-L` | as in `git log -L` |' "a table with no Run column is out of scope"
|
|
|
|
echo ""
|
|
echo "--- degenerate inputs ---"
|
|
|
|
expect clean '' "an empty file"
|
|
|
|
expect clean '```bash
|
|
rtk git status' "an unterminated fence does not crash the parser"
|
|
|
|
if bash "$SCRIPT" > "$RUN_TMP/noargs.out" 2>&1; then
|
|
pass "no filenames exits 0 rather than erroring"
|
|
else
|
|
fail "no filenames should exit 0 — pre-commit calls hooks with an empty file list"
|
|
fi
|
|
|
|
if bash "$SCRIPT" "$RUN_TMP/does-not-exist.md" > "$RUN_TMP/missing.out" 2>&1; then
|
|
fail "an unreadable file exited 0 — unreadable must be an error, never a silent pass"
|
|
elif ! grep -q 'could not read file' "$RUN_TMP/missing.out"; then
|
|
fail "an unreadable file failed for the wrong reason"
|
|
sed 's/^/ /' "$RUN_TMP/missing.out"
|
|
else
|
|
pass "an unreadable file exits 1 and says so"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- the live corpus is clean ---"
|
|
|
|
LIVE=()
|
|
while IFS= read -r LIVE_REL; do
|
|
LIVE+=("$LIVE_REL")
|
|
done < <(
|
|
git -C "$REPO_ROOT" ls-files -- 'plugins' \
|
|
| grep -E '^plugins/[^/]+/\.apm/(skills/.*\.md|agents/.*\.agent\.md)$' \
|
|
| grep -v '/README\.md$'
|
|
)
|
|
if [[ ${#LIVE[@]} -eq 0 ]]; then
|
|
fail "no plugin skill/agent files matched the hook's files: pattern"
|
|
elif (cd "$REPO_ROOT" && bash "$SCRIPT" ${LIVE[@]+"${LIVE[@]}"} > "$RUN_TMP/live.out" 2>&1); then
|
|
pass "the ${#LIVE[@]} in-scope corpus files pass"
|
|
else
|
|
fail "the live corpus has ADR-0023 clause-1 violations"
|
|
sed 's/^/ /' "$RUN_TMP/live.out"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
[[ $FAIL -eq 0 ]]
|