Three divergences between what the audit skills claim and what the hooks enforce, each of which fails silently rather than loudly: - `skill-size-check.sh` blocked at 2900 words while `validate.sh` checked only the 500-line ceiling, so `/skill-audit` could report a skill ready to ship that the commit hook then rejected. `validate.sh` now checks the same pair on the same inclusive terms; the constants are duplicated with a comment naming the other file, because a plugin skill's scripts cannot read outside the plugin directory once installed to the cache. - Both audit skills' Step 1 passed `--config assets/vale/.vale.ini`, which is redundant (the wrapper self-locates its sibling config) and fragile: an agent that resolves the script path against the skill directory but not the config path gets E100, exit 2, which the surrounding fallback clause misreads as "vale unavailable" and downgrades to full LLM judgment with no signal. - The external-consumer test registered only the two Vale hooks, never the third shipped hook, so a lost executable bit would have broken every consumer while the local suite stayed green. Verified by mutation: `chmod 644` on the copied script now turns three passes into two failures. Also corrects the size hook's calibration comment, which claimed ~5.7-6.5 characters per word against a corpus whose measured median is 6.79 — the stated upper bound sat below the median, so the "calibrated with margin" claim was inverted for prose-dense files. MAX_WORDS is unchanged pending a decision; the comment is now explicit that the gate holds under 5,000 tokens for typical prose density, not for any file. Refs: #85
162 lines
5.6 KiB
Bash
Executable File
162 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Integration test for .pre-commit-hooks.yaml as an EXTERNAL hook repo — the
|
|
# contract ADR-0014 exists to provide, and the one thing running pre-commit
|
|
# inside this repo can never exercise: `repo: local` makes pre-commit's clone
|
|
# prefix equal to the consuming repo's root, so a hook entry that only works
|
|
# because those two coincide passes here and hard-fails everywhere else.
|
|
# (It did: every argument after entry[0] resolves against the CONSUMING repo,
|
|
# so a `--config plugins/.../.vale.ini` argument gave external consumers
|
|
# `E100 [--config] Runtime error ... does not exist`, exit 2, on both Vale hooks.)
|
|
#
|
|
# The hook repo is built from the WORKING TREE, not from HEAD, so an uncommitted
|
|
# change to the manifest or the wrapper is what gets tested.
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
|
|
|
for bin in pre-commit vale git; do
|
|
if ! command -v "$bin" &>/dev/null; then
|
|
echo "SKIP: $bin is not installed — cannot stand up a consumer repo"
|
|
exit 77
|
|
fi
|
|
done
|
|
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
HOOK_REPO="$WORK/hookrepo"
|
|
CONSUMER="$WORK/consumer"
|
|
export PRE_COMMIT_HOME="$WORK/pc-home"
|
|
|
|
mkdir -p "$HOOK_REPO/plugins/kyberforge/skills" "$HOOK_REPO/scripts"
|
|
cp "$REPO_ROOT/.pre-commit-hooks.yaml" "$HOOK_REPO/"
|
|
cp "$REPO_ROOT/scripts/skill-size-check.sh" "$HOOK_REPO/scripts/"
|
|
for skill in skill-audit agent-audit; do
|
|
mkdir -p "$HOOK_REPO/plugins/kyberforge/skills/$skill"
|
|
cp -R "$REPO_ROOT/plugins/kyberforge/skills/$skill/scripts" \
|
|
"$REPO_ROOT/plugins/kyberforge/skills/$skill/assets" \
|
|
"$HOOK_REPO/plugins/kyberforge/skills/$skill/"
|
|
done
|
|
git -C "$HOOK_REPO" init -q
|
|
git -C "$HOOK_REPO" add -A
|
|
git -C "$HOOK_REPO" -c user.email=test@example.invalid -c user.name=test commit -qm "hook repo"
|
|
HOOK_REV="$(git -C "$HOOK_REPO" rev-parse HEAD)"
|
|
|
|
# Every hook scopes by filename, so the consumer needs one file of each shape:
|
|
# a hook with nothing to match reports `Skipped` and proves nothing. All three
|
|
# hooks .pre-commit-hooks.yaml ships are registered — an unregistered one would
|
|
# let a regression (a lost `100755` bit, a bad entry path) reach every external
|
|
# consumer while this repo's own `repo: local` runs stayed green.
|
|
mkdir -p "$CONSUMER/skills/demo" "$CONSUMER/agents"
|
|
git -C "$CONSUMER" init -q
|
|
cat > "$CONSUMER/.pre-commit-config.yaml" <<EOF
|
|
repos:
|
|
- repo: file://$HOOK_REPO
|
|
rev: $HOOK_REV
|
|
hooks:
|
|
- id: kyberforge-vale-audit-skill
|
|
- id: kyberforge-vale-audit-agent
|
|
- id: kyberforge-skill-size-check
|
|
EOF
|
|
|
|
write_fixtures() {
|
|
local body="$1"
|
|
cat > "$CONSUMER/skills/demo/SKILL.md" <<EOF
|
|
---
|
|
name: demo
|
|
description: >
|
|
Use when the caller wants a demonstration skill $body across two
|
|
physical lines of one folded block scalar.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
cat > "$CONSUMER/agents/demo.md" <<EOF
|
|
---
|
|
name: demo
|
|
description: >
|
|
Use when the caller wants a demonstration agent $body across two
|
|
physical lines of one folded block scalar.
|
|
---
|
|
|
|
Body.
|
|
EOF
|
|
git -C "$CONSUMER" add -A
|
|
}
|
|
|
|
run_hooks() {
|
|
(cd "$CONSUMER" && pre-commit run --all-files 2>&1) || true
|
|
}
|
|
|
|
# --- 1. Both hooks resolve their config and actually gate on a bad file ---
|
|
echo ""
|
|
echo "--- both Vale hooks run and fail a bad file in an external consumer repo ---"
|
|
write_fixtures "that helps with and utilize things"
|
|
OUT_BAD="$(run_hooks)"
|
|
if echo "$OUT_BAD" | grep -q "does not exist"; then
|
|
fail "hooks hard-errored on a path resolved against the consumer repo (E100) — the bug this test guards against"
|
|
echo "$OUT_BAD" | sed 's/^/ /'
|
|
elif echo "$OUT_BAD" | grep -q "Skipped"; then
|
|
fail "a hook matched no files, so it proved nothing"
|
|
echo "$OUT_BAD" | sed 's/^/ /'
|
|
elif [[ "$(echo "$OUT_BAD" | grep -c "VagueWording")" -ge 2 ]]; then
|
|
pass "both hooks flatten and flag the folded description in a consumer repo"
|
|
else
|
|
fail "hooks did not flag both fixtures"
|
|
echo "$OUT_BAD" | sed 's/^/ /'
|
|
fi
|
|
|
|
# --- 2. Clean files pass — the hooks gate, they don't just always fail ---
|
|
echo ""
|
|
echo "--- all three hooks pass clean files in an external consumer repo ---"
|
|
write_fixtures "of the packaged hook contract"
|
|
set +e
|
|
(cd "$CONSUMER" && pre-commit run --all-files > "$WORK/clean.log" 2>&1)
|
|
RC_CLEAN=$?
|
|
set -e
|
|
if grep -q "Skipped" "$WORK/clean.log"; then
|
|
fail "a hook matched no files on the clean run, so it proved nothing"
|
|
sed 's/^/ /' "$WORK/clean.log"
|
|
elif [[ $RC_CLEAN -eq 0 ]]; then
|
|
pass "all three hooks exit 0 on clean files"
|
|
else
|
|
fail "hooks failed on clean files (rc=$RC_CLEAN)"
|
|
sed 's/^/ /' "$WORK/clean.log"
|
|
fi
|
|
|
|
# --- 3. The size hook gates too. It ran clean above, which is what proves it
|
|
# is executable and its entry path resolves; this half proves it still fails a
|
|
# file that breaks the ceiling rather than passing everything. ---
|
|
echo ""
|
|
echo "--- kyberforge-skill-size-check fails an oversized SKILL.md in an external consumer repo ---"
|
|
mkdir -p "$CONSUMER/skills/oversized"
|
|
{
|
|
echo "---"
|
|
echo "name: oversized"
|
|
echo "description: Use when the caller wants an oversized fixture."
|
|
echo "---"
|
|
for ((i = 1; i <= 600; i++)); do
|
|
echo "word"
|
|
done
|
|
} > "$CONSUMER/skills/oversized/SKILL.md"
|
|
git -C "$CONSUMER" add -A
|
|
set +e
|
|
(cd "$CONSUMER" && pre-commit run kyberforge-skill-size-check --all-files > "$WORK/size.log" 2>&1)
|
|
RC_SIZE=$?
|
|
set -e
|
|
if [[ $RC_SIZE -ne 0 ]] && grep -q "500-line ceiling" "$WORK/size.log"; then
|
|
pass "kyberforge-skill-size-check exits non-zero and names the ceiling it broke"
|
|
else
|
|
fail "kyberforge-skill-size-check did not gate an oversized SKILL.md (rc=$RC_SIZE)"
|
|
sed 's/^/ /' "$WORK/size.log"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
[[ $FAIL -eq 0 ]]
|