feat(lint): wire Vale as deterministic prefilter for skill-audit/agent-audit #85
@@ -34,19 +34,29 @@ fi
|
||||
# instead of hand-maintaining a parallel list — the manifest is the single
|
||||
# source of truth for what external consumers actually pull at a pinned rev,
|
||||
# so a hook added/removed/renamed there can't silently drift out of sync here.
|
||||
# Each entry is "<script> [--config <path>] [...]"; the script itself and the
|
||||
# directory holding any --config target (vale-wrap.sh needs its .vale.ini's
|
||||
# sibling styles/ tree, not just the ini file) are release-relevant.
|
||||
# Everything is derived from tokens[0], the hook's script: pre-commit prefixes
|
||||
# only entry[0] with the hook-repo clone path, so any later token that looks
|
||||
# like a path resolves against the *consuming* repo and can never name a file
|
||||
# this repo ships. A hook's bundled data therefore has to be self-located
|
||||
# relative to the script — vale-wrap.sh reads its own
|
||||
# <script-dir>/../assets/vale/.vale.ini plus the sibling styles/ tree — which
|
||||
# makes <script-dir>/../assets release-relevant alongside the script itself.
|
||||
# The ../ is normalised by stripping a path component rather than with
|
||||
# `realpath -m`, which is a GNU-only extension. Two guards keep the derivation
|
||||
# from inventing paths: a bundle root of "." is skipped, because a script in a
|
||||
# top-level directory (scripts/skill-size-check.sh) would derive the repo's own
|
||||
# shared assets/, which no hook owns and whose churn must not demand a release;
|
||||
# and the directory is added only when it exists, since a hook that bundles
|
||||
# nothing must not contribute a pathspec matching nothing.
|
||||
RELEASE_PATHS=("$HOOKS_MANIFEST")
|
||||
while IFS= read -r entry; do
|
||||
read -ra tokens <<< "$entry"
|
||||
[[ ${#tokens[@]} -eq 0 ]] && continue
|
||||
RELEASE_PATHS+=("${tokens[0]}")
|
||||
for ((i = 1; i < ${#tokens[@]}; i++)); do
|
||||
if [[ "${tokens[i]}" == "--config" && -n "${tokens[i + 1]:-}" ]]; then
|
||||
RELEASE_PATHS+=("$(dirname "${tokens[i + 1]}")")
|
||||
fi
|
||||
done
|
||||
bundle_root="$(dirname "$(dirname "${tokens[0]}")")"
|
||||
if [[ "$bundle_root" != "." && -d "$bundle_root/assets" ]]; then
|
||||
RELEASE_PATHS+=("$bundle_root/assets")
|
||||
fi
|
||||
done < <(sed -n 's/^[[:space:]]*entry:[[:space:]]*//p' "$HOOKS_MANIFEST")
|
||||
|
||||
# Only vX.Y.Z release tags count as a baseline — an incidental checkpoint or
|
||||
|
||||
@@ -9,32 +9,45 @@ FAIL=0
|
||||
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
||||
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
||||
|
||||
# Helper: write a minimal .pre-commit-hooks.yaml exposing one script entry and
|
||||
# one --config-bearing entry, so RELEASE_PATHS (derived from the manifest, not
|
||||
# hand-maintained) has both shapes to parse.
|
||||
# Both entry shapes the real .pre-commit-hooks.yaml ships: a bare script with no
|
||||
# bundled data, and a bare script whose sibling assets/ tree it self-locates at
|
||||
# runtime. Neither carries arguments — pre-commit only rewrites entry[0] to the
|
||||
# hook-repo clone path, so an argument path would resolve against the consuming
|
||||
# repo. RELEASE_PATHS is derived from the manifest rather than hand-maintained,
|
||||
# so it has to cope with both.
|
||||
HOOK_DIR="plugins/demo/skills/demo-audit"
|
||||
|
||||
write_manifest() {
|
||||
local dir="$1"
|
||||
mkdir -p "$dir/vale"
|
||||
cat > "$dir/.pre-commit-hooks.yaml" <<'EOF'
|
||||
cat > "$dir/.pre-commit-hooks.yaml" <<EOF
|
||||
- id: fake-size-check
|
||||
entry: scripts/skill-size-check.sh
|
||||
language: script
|
||||
- id: fake-vale-check
|
||||
entry: scripts/vale-wrap.sh --config vale/.vale.ini
|
||||
entry: $HOOK_DIR/scripts/vale-wrap.sh
|
||||
language: script
|
||||
EOF
|
||||
}
|
||||
|
||||
# Helper: a fixture repo with a manifest and its two referenced release-relevant
|
||||
# paths, committed and tagged v1.0.0.
|
||||
# Helper: writes the files both manifest entries expose — the two hook scripts
|
||||
# plus the bundled Vale config and style rule the second one self-locates.
|
||||
write_release_paths() {
|
||||
local dir="$1"
|
||||
mkdir -p "$dir/scripts" "$dir/$HOOK_DIR/scripts" "$dir/$HOOK_DIR/assets/vale/styles/Kyberforge"
|
||||
echo "v1" > "$dir/scripts/skill-size-check.sh"
|
||||
echo "v1" > "$dir/$HOOK_DIR/scripts/vale-wrap.sh"
|
||||
echo "cfg" > "$dir/$HOOK_DIR/assets/vale/.vale.ini"
|
||||
echo "rule: v1" > "$dir/$HOOK_DIR/assets/vale/styles/Kyberforge/DemoRule.yml"
|
||||
}
|
||||
|
||||
# Helper: a fixture repo with a manifest and every release-relevant path it
|
||||
# exposes, committed and tagged v1.0.0.
|
||||
make_tagged_fixture() {
|
||||
local dir
|
||||
dir="$(mktemp -d)"
|
||||
(cd "$dir" && git init -q && git config user.email t@t.t && git config user.name t)
|
||||
write_manifest "$dir"
|
||||
mkdir -p "$dir/scripts"
|
||||
echo "v1" > "$dir/scripts/skill-size-check.sh"
|
||||
echo "cfg" > "$dir/vale/.vale.ini"
|
||||
write_release_paths "$dir"
|
||||
(cd "$dir" && git add -A && git commit -q -m "initial" && git tag v1.0.0)
|
||||
echo "$dir"
|
||||
}
|
||||
@@ -65,9 +78,7 @@ echo "--- exits 1 when targeting main and no tag exists ---"
|
||||
FIXTURE2="$(mktemp -d)"; track "$FIXTURE2"
|
||||
(cd "$FIXTURE2" && git init -q && git config user.email t@t.t && git config user.name t)
|
||||
write_manifest "$FIXTURE2"
|
||||
mkdir -p "$FIXTURE2/scripts"
|
||||
echo "v1" > "$FIXTURE2/scripts/skill-size-check.sh"
|
||||
echo "cfg" > "$FIXTURE2/vale/.vale.ini"
|
||||
write_release_paths "$FIXTURE2"
|
||||
(cd "$FIXTURE2" && git add -A && git commit -q -m "initial")
|
||||
if run_check "$FIXTURE2" "refs/heads/main" > /dev/null; then
|
||||
fail "exited 0 when targeting main with no tag — expected exit 1"
|
||||
@@ -116,10 +127,10 @@ fi
|
||||
echo ""
|
||||
echo "--- exits 1 when a release-relevant path was deleted since the tag, not just modified ---"
|
||||
FIXTURE6="$(make_tagged_fixture)"; track "$FIXTURE6"
|
||||
rm -rf "$FIXTURE6/vale"
|
||||
(cd "$FIXTURE6" && git add -A && git commit -q -m "delete the vale config dir")
|
||||
rm -f "$FIXTURE6/$HOOK_DIR/assets/vale/.vale.ini"
|
||||
(cd "$FIXTURE6" && git add -A && git commit -q -m "delete the bundled vale config")
|
||||
OUT6=$(run_check "$FIXTURE6" "refs/heads/main" || true)
|
||||
if echo "$OUT6" | grep -q "vale/.vale.ini"; then
|
||||
if echo "$OUT6" | grep -q "assets/vale/.vale.ini"; then
|
||||
pass "flags a deleted release-relevant path instead of silently dropping it from the diff"
|
||||
else
|
||||
fail "did not flag deletion of a release-relevant path since the tag"
|
||||
@@ -166,6 +177,40 @@ else
|
||||
fail "flagged a file that no .pre-commit-hooks.yaml entry actually exposes"
|
||||
fi
|
||||
|
||||
# --- 10. A change confined to a hook's bundled styles/ tree is release-relevant ---
|
||||
# The manifest entry names only the wrapper script; the Vale rules it enforces
|
||||
# live in the sibling assets/ tree it self-locates at runtime. If that tree is
|
||||
# not covered, editing a rule and landing it on main demands no new tag, and a
|
||||
# consumer pinned to the old rev keeps the stale rules forever.
|
||||
echo ""
|
||||
echo "--- exits 1 when only a bundled Vale style rule changed since the tag ---"
|
||||
FIXTURE10="$(make_tagged_fixture)"; track "$FIXTURE10"
|
||||
echo "rule: v2" > "$FIXTURE10/$HOOK_DIR/assets/vale/styles/Kyberforge/DemoRule.yml"
|
||||
(cd "$FIXTURE10" && git add -A && git commit -q -m "tighten a vale rule")
|
||||
OUT10=$(run_check "$FIXTURE10" "refs/heads/main" || true)
|
||||
if echo "$OUT10" | grep -q "assets/vale/styles/Kyberforge/DemoRule.yml"; then
|
||||
pass "flags a change confined to a hook's bundled assets/vale/styles/ tree"
|
||||
else
|
||||
fail "a bundled Vale style rule changed since the tag without demanding a release"
|
||||
fi
|
||||
|
||||
# --- 11. The assets/ derivation must not invent a path for a bundle-less hook ---
|
||||
# scripts/skill-size-check.sh has no sibling assets/ tree, so its derived
|
||||
# candidate normalises to a bare top-level assets/ — a directory this repo does
|
||||
# not ship. Adding it unconditionally would make any unrelated repo-root
|
||||
# assets/ file falsely demand a release.
|
||||
echo ""
|
||||
echo "--- exits 0 when a top-level assets/ file changed but no hook bundles one ---"
|
||||
FIXTURE11="$(make_tagged_fixture)"; track "$FIXTURE11"
|
||||
mkdir -p "$FIXTURE11/assets"
|
||||
echo "unrelated" > "$FIXTURE11/assets/logo.txt"
|
||||
(cd "$FIXTURE11" && git add -A && git commit -q -m "add an unrelated top-level assets file")
|
||||
if run_check "$FIXTURE11" "refs/heads/main" > /dev/null; then
|
||||
pass "exits 0 for a top-level assets/ file that no manifest entry bundles"
|
||||
else
|
||||
fail "invented a bogus assets/ path for a hook script with no bundled tree"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
|
||||
Reference in New Issue
Block a user