From 0b89294af79ebb6518ecf56c4c4a7ab03fe9708b Mon Sep 17 00:00:00 2001 From: kami Date: Sun, 2 Aug 2026 03:22:46 +0400 Subject: [PATCH] hooks: refuse master, cap a code commit at 300 lines, require the task ref (V-445) Two git hooks, tracked in .githooks and wired with core.hooksPath so a fresh clone gets them with one config line. pre-commit refuses master and refuses more than 300 changed lines in non-markdown files. Markdown is exempt because docs land as one batch. This is a commit-time guard, which diff-budget.sh is not: that hook blocks the agent's edits and says nothing when either of us commits. commit-msg requires (V-), not (#). Gitea autolinks # to its own issues, and Vikunja is the tracker. Co-Authored-By: Claude Opus 5 --- .githooks/commit-msg | 30 ++++++++++++++++++++++++++++++ .githooks/pre-commit | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100755 .githooks/commit-msg create mode 100755 .githooks/pre-commit diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..b69caf1 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,30 @@ +#!/bin/sh +# Every commit names the Vikunja task it belongs to. +# +# router: narrow the single-token rule (V-359) +# +# V- and not #, because Gitea autolinks #359 to a Gitea issue, which is a +# different tracker and a wrong link. +# +# Exempt: merges, reverts, fixup/squash, and the initial commit. + +msg_file=$1 +subject=$(sed -n '1p' "$msg_file") + +case "$subject" in + Merge\ *|Revert\ *|fixup!\ *|squash!\ *|amend!\ *) exit 0 ;; +esac + +if [ -f "$(git rev-parse --git-dir)/MERGE_HEAD" ]; then + exit 0 +fi + +if printf '%s' "$subject" | grep -qE '\(V-[0-9]+\)$'; then + exit 0 +fi + +echo "commit-msg: subject must end with a Vikunja task ref." >&2 +echo " got: $subject" >&2 +echo " want: router: narrow the single-token rule (V-359)" >&2 +echo " No task yet? Create one. Work without a task is work nobody can resume." >&2 +exit 1 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..73756df --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,30 @@ +#!/bin/sh +# Two guards, both bypassable with --no-verify when you mean it. +# 1. master is not a working branch. +# 2. a code commit stays under 300 changed lines. +# Markdown is exempt from the size cap on purpose: docs land as one batch. + +branch=$(git symbolic-ref --short HEAD 2>/dev/null) + +case "$branch" in + master|main) + echo "pre-commit: refusing to commit on $branch." >&2 + echo " task start # branch off origin/master, write TASK.md" >&2 + exit 1 + ;; +esac + +# Added + deleted lines across staged files that are not markdown. +# numstat prints "-\t-\t" for binaries; those count 0 and that is fine, +# a binary blob is not the kind of diff this cap exists to stop. +loc=$(git diff --cached --numstat -- . ':(exclude)*.md' | + awk '$1 ~ /^[0-9]+$/ { a += $1 } $2 ~ /^[0-9]+$/ { d += $2 } END { print a + d + 0 }') + +if [ "$loc" -gt 300 ]; then + echo "pre-commit: $loc changed lines in non-markdown files, cap is 300." >&2 + echo " Split it. Each commit should be one reviewable idea." >&2 + echo " git reset to unstage, or --no-verify if this genuinely cannot split." >&2 + exit 1 +fi + +exit 0