[PROPOSAL] Global pre-commit hook: repo-declared checks (REVIEWED-100)
core.hooksPath makes this hook global to every repo, which is why it is tracked and travels — and why it must hold no repo knowledge. A repo opts in by declaring `.precommit-triggers` at its root: staged pathspecs on the left, a command on the right. If the staged diff touches a declared pathspec the command runs, and a non-zero exit refuses the commit. Three decisions worth stating rather than leaving to be rediscovered: Path matching is delegated to `git diff --cached --name-only -- <pathspec>` rather than reimplemented, so declarations use the pathspec syntax the repo's users already know and globs behave as they do everywhere else in git. The declaration file is read on fd 3, so a declared check that reads stdin cannot swallow the remainder of the rules. It is dependency-free by design — no yq, no python. A global convention that needs a toolchain silently fails to travel to the next machine, and a check that silently does not run is worse than no check, because its absence reads as a pass. This is a deliberate departure from the YAML used by data that python tools consume. Scope: this is a tripwire, not an enforcement boundary. --no-verify steps over it, and the message says so. It is worth having because the failure mode it addresses is forgetting, not evading. First consumer: studium-engine, where a corpus edit invalidates engine fixtures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A35wiD55yRHj5U1ECZAX4t
This commit is contained in:
co-authored by
Claude Opus 5
parent
08fe08eed5
commit
c86b82512b
@@ -74,4 +74,42 @@ if echo "$added_lines" | grep -qE "(password|secret|token|api_key)[[:space:]]*=[
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Repo-declared checks ──────────────────────────────────────── (REVIEWED-100)
|
||||||
|
# This hook is global to every repo (core.hooksPath), so it must hold no repo
|
||||||
|
# knowledge. A repo opts in by declaring `.precommit-triggers` at its root:
|
||||||
|
#
|
||||||
|
# <git pathspec> [more pathspecs] | <command>
|
||||||
|
#
|
||||||
|
# If the staged diff touches a declared pathspec, the command runs and a non-zero
|
||||||
|
# exit refuses the commit. Path matching is delegated to git's own pathspec
|
||||||
|
# engine rather than reimplemented here.
|
||||||
|
#
|
||||||
|
# Deliberately dependency-free — no yq, no python. A global convention that needs
|
||||||
|
# a toolchain silently fails to travel to the next machine or repo, and a check
|
||||||
|
# that silently does not run is worse than no check, because its absence reads as
|
||||||
|
# a pass. That is why this departs from the YAML used by data python tools read.
|
||||||
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||||
|
triggers_file="${repo_root:-.}/.precommit-triggers"
|
||||||
|
if [ -n "$repo_root" ] && [ -f "$triggers_file" ]; then
|
||||||
|
# fd 3, so a check that reads stdin cannot swallow the rest of this file
|
||||||
|
while IFS='|' read -r paths cmd <&3 || [ -n "${paths:-}" ]; do
|
||||||
|
case "${paths%%[![:space:]]*}${paths#"${paths%%[![:space:]]*}"}" in \#*) continue ;; esac
|
||||||
|
cmd="$(printf '%s' "${cmd:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
||||||
|
[ -n "$cmd" ] || continue
|
||||||
|
# shellcheck disable=SC2086 — word splitting is intended: multiple pathspecs
|
||||||
|
if [ -z "$(git diff --cached --name-only -- $paths 2>/dev/null)" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo -e "${YELLOW}Staged change touches [${paths# }] — running declared check:${NC} $cmd"
|
||||||
|
if ! ( cd "$repo_root" && eval "$cmd" ) < /dev/null; then
|
||||||
|
echo -e "${RED}Declared check FAILED — commit refused.${NC}"
|
||||||
|
echo " declared in: .precommit-triggers"
|
||||||
|
echo " command: $cmd"
|
||||||
|
echo " --no-verify bypasses this; it is a tripwire, not a boundary."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}Declared check passed.${NC}"
|
||||||
|
done 3< "$triggers_file"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}Pre-commit checks passed!${NC}"
|
echo -e "${GREEN}Pre-commit checks passed!${NC}"
|
||||||
|
|||||||
Reference in New Issue
Block a user