feat(lint): wire Vale as deterministic prefilter for skill-audit/agent-audit #85

Merged
Defame1297 merged 45 commits from feat/84-vale-audit-prefilter into main 2026-08-10 16:46:59 +00:00
4 changed files with 172 additions and 0 deletions
Showing only changes of commit 4d018af03c - Show all commits

View File

@@ -70,6 +70,15 @@ repos:
pass_filenames: false pass_filenames: false
always_run: true always_run: true
- id: check-release-needed
name: Check a release tag covers .pre-commit-hooks.yaml's paths
description: On push to main only, fail if files exposed via .pre-commit-hooks.yaml changed since the last tag
entry: bash scripts/check-release-needed.sh
language: system
stages: [pre-push]
pass_filenames: false
always_run: true
- id: validate-plugins - id: validate-plugins
name: Validate plugins name: Validate plugins
description: Run claude plugin validate --strict on every plugin directory description: Run claude plugin validate --strict on every plugin directory

View File

@@ -97,3 +97,13 @@ doesn't wonder if it was overlooked.
`SKILL.md`-shaped, and only skill-audit's `.vale.ini` has the matching glob section. `SKILL.md`-shaped, and only skill-audit's `.vale.ini` has the matching glob section.
- The first `vX.Y.Z` tag is cut once this change and its tests pass, giving external - The first `vX.Y.Z` tag is cut once this change and its tests pass, giving external
`.pre-commit-hooks.yaml` consumers something to pin. `.pre-commit-hooks.yaml` consumers something to pin.
- **Cutting the tag is not left to memory.** `scripts/check-release-needed.sh`, wired at
`pre-push`, hard-fails — but only when `PRE_COMMIT_REMOTE_BRANCH` (set by pre-commit's
`hook-impl` for pre-push hooks) is `refs/heads/main` — if any path `.pre-commit-hooks.yaml`
exposes changed since the last tag reachable from `HEAD`. It is a silent no-op on every other
branch: hard-failing on feature-branch pushes mid-review would force a premature tag on a
commit that might not survive a squash-merge, the exact problem `repo: local` (above) already
avoids for this repo's own dev-time gate. A tag not existing at all is also a hard fail on
`main`, covering the very first release. This is deterministic tooling, not a standing
instruction to remember — consistent with `check-manifests.sh`/`check-vale-style-sync.sh`
already using the same pre-push, main-agnostic-elsewhere pattern.

56
scripts/check-release-needed.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
# Hard-fails only when pushing to main: if any file covered by .pre-commit-hooks.yaml
# (the external git-hook/CI contract, see ADR-0014) changed since the last tag,
# a release must be cut before landing on main, or external consumers pinning
# `rev: <tag>` silently miss the change. Pre-commit sets PRE_COMMIT_REMOTE_BRANCH
# for pre-push hooks; on every other branch (feature work mid-review) this is a
# silent no-op — pushing WIP commits there must not be blocked on cutting a
# premature tag (see ADR-0014's repo: local vs pinned self-reference decision).
TARGET_BRANCH="refs/heads/main"
if [[ "${PRE_COMMIT_REMOTE_BRANCH:-}" != "$TARGET_BRANCH" ]]; then
exit 0
fi
REPO_ROOT="$(git rev-parse --show-toplevel)"
cd "$REPO_ROOT"
# Paths whose content .pre-commit-hooks.yaml exposes to external consumers.
# Keep in sync with .pre-commit-hooks.yaml's entry: paths.
RELEASE_PATHS=(
.pre-commit-hooks.yaml
scripts/skill-size-check.sh
plugins/kyberforge/skills/skill-audit/scripts
plugins/kyberforge/skills/skill-audit/assets/vale
plugins/kyberforge/skills/agent-audit/scripts
plugins/kyberforge/skills/agent-audit/assets/vale
)
LAST_TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)"
if [[ -z "$LAST_TAG" ]]; then
echo "FAIL: no release tag exists yet, but .pre-commit-hooks.yaml already exposes hooks to external consumers." >&2
echo " Fix: cut the first release tag (e.g. v1.0.0) before this lands on main." >&2
exit 1
fi
EXISTING_PATHS=()
for p in "${RELEASE_PATHS[@]}"; do
[[ -e "$p" ]] && EXISTING_PATHS+=("$p")
done
if [[ ${#EXISTING_PATHS[@]} -eq 0 ]]; then
exit 0
fi
CHANGED="$(git diff --name-only "$LAST_TAG"..HEAD -- "${EXISTING_PATHS[@]}" 2>/dev/null || true)"
if [[ -n "$CHANGED" ]]; then
echo "FAIL: files covered by .pre-commit-hooks.yaml changed since $LAST_TAG:" >&2
echo "$CHANGED" | sed 's/^/ /' >&2
echo " Fix: cut a new release tag — external consumers pinning rev: $LAST_TAG would miss this change." >&2
exit 1
fi

View File

@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SCRIPT="$REPO_ROOT/scripts/check-release-needed.sh"
PASS=0
FAIL=0
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
# Helper: a fixture repo with one release-relevant file, committed and tagged.
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)
mkdir -p "$dir/scripts"
echo "v1" > "$dir/scripts/skill-size-check.sh"
(cd "$dir" && git add -A && git commit -q -m "initial" && git tag v1.0.0)
echo "$dir"
}
run_check() {
local dir="$1" branch="$2"
(cd "$dir" && PRE_COMMIT_REMOTE_BRANCH="$branch" bash "$SCRIPT" 2>&1)
}
# --- 1. Not targeting main: silent no-op regardless of state ---
echo ""
echo "--- exits 0 when not pushing to main, even with no tags ---"
FIXTURE1="$(mktemp -d)"
trap 'rm -rf "$FIXTURE1"' EXIT
(cd "$FIXTURE1" && git init -q)
if run_check "$FIXTURE1" "refs/heads/feature-branch" > /dev/null; then
pass "exits 0 when target branch isn't main"
else
fail "exited non-zero on a non-main target branch"
fi
# --- 2. Targeting main, no tag exists at all: hard fail ---
echo ""
echo "--- exits 1 when targeting main and no tag exists ---"
FIXTURE2="$(mktemp -d)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2"' EXIT
(cd "$FIXTURE2" && git init -q && git config user.email t@t.t && git config user.name t)
mkdir -p "$FIXTURE2/scripts"
echo "v1" > "$FIXTURE2/scripts/skill-size-check.sh"
(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"
else
pass "exits non-zero when targeting main and no tag exists yet"
fi
# --- 3. Targeting main, tag exists, no release-relevant changes since: passes ---
echo ""
echo "--- exits 0 when targeting main and nothing release-relevant changed since the tag ---"
FIXTURE3="$(make_tagged_fixture)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3"' EXIT
echo "unrelated" > "$FIXTURE3/README.md"
(cd "$FIXTURE3" && git add -A && git commit -q -m "unrelated change")
if run_check "$FIXTURE3" "refs/heads/main" > /dev/null; then
pass "exits 0 when only unrelated files changed since the tag"
else
fail "exited non-zero despite no release-relevant changes since the tag"
fi
# --- 4. Targeting main, tag exists, a release-relevant file changed since: hard fail ---
echo ""
echo "--- exits 1 when a release-relevant file changed since the tag ---"
FIXTURE4="$(make_tagged_fixture)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4"' EXIT
echo "v2" > "$FIXTURE4/scripts/skill-size-check.sh"
(cd "$FIXTURE4" && git add -A && git commit -q -m "update release-relevant script")
OUT4=$(run_check "$FIXTURE4" "refs/heads/main" || true)
if echo "$OUT4" | grep -q "skill-size-check.sh"; then
pass "exits non-zero and names the changed file when a release-relevant path changed since the tag"
else
fail "did not flag the release-relevant file that changed since the tag"
fi
# --- 5. Not targeting main even with release-relevant changes and a tag: still a no-op ---
echo ""
echo "--- exits 0 on a feature branch even with release-relevant changes since the tag ---"
FIXTURE5="$(make_tagged_fixture)"
trap 'rm -rf "$FIXTURE1" "$FIXTURE2" "$FIXTURE3" "$FIXTURE4" "$FIXTURE5"' EXIT
echo "v2" > "$FIXTURE5/scripts/skill-size-check.sh"
(cd "$FIXTURE5" && git add -A && git commit -q -m "update release-relevant script")
if run_check "$FIXTURE5" "refs/heads/some-feature" > /dev/null; then
pass "exits 0 on a feature branch regardless of un-tagged release-relevant changes"
else
fail "hard-failed on a feature branch — should only ever fail when targeting main"
fi
echo ""
echo "Results: $PASS passed, $FAIL failed"
[[ $FAIL -eq 0 ]]