fix(lint): hard-fail on main when a release tag is needed
.pre-commit-hooks.yaml now exposes hooks to external consumers pinning rev: <tag>, but nothing enforced that a tag actually gets cut when the files it references change — relying on memory is exactly what this repo's governance rules say to avoid for a repeatable, deterministic check. scripts/check-release-needed.sh hard-fails at pre-push, but only when PRE_COMMIT_REMOTE_BRANCH (set by pre-commit's hook-impl) is refs/heads/main: it diffs .pre-commit-hooks.yaml's referenced paths against the last tag reachable from HEAD, and fails if either no tag exists yet or something changed since. It's 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 risk the repo: local (vs. pinned self- reference) decision in ADR-0014 already avoids for this repo's own dev-time gate. Verified against the real git pre-push hook path (not just the script in isolation): simulated stdin matching git's pre-push protocol through .git/hooks/pre-push, confirmed it correctly fires and fails when targeting main with no tag, and is silent otherwise. ADR: 0014 Refs: #87
This commit is contained in:
56
scripts/check-release-needed.sh
Executable file
56
scripts/check-release-needed.sh
Executable 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
|
||||
Reference in New Issue
Block a user