Template
feat(enforcement): global house rules + tier-wide mechanical gates
Machine-optimal global AGENTS.md (symlinked at ~/.config/AGENTS.md, picked up by every crush lane) covering the alpha/beta/uat/prod doctrine (prod = human release only), strict SDLC (red/green TDD, adversarial review, code/tests/docs/ticket sync), gitea-redmine-discourse cross-linking, and the 2026-08-31 rulings (no JOURNAL.md, sparse STATUS.md, rolling table HUD). Global enforcement layer: repo-scoped ticket-gate (crush PreToolUse, all lanes) + universal git pre-commit/pre-push (core.hooksPath) that delegate to repo-local check-rules.sh and chain installed .git/hooks.
This commit is contained in:
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# hooks/global/pre-commit — GLOBAL git hook (core.hooksPath for every
|
||||
# repo on this host). Runs universal checks always, delegates to the
|
||||
# repo's own enforcement when present, and chains the repo's installed
|
||||
# .git/hooks/<name> so copy-installed hooks keep working.
|
||||
#
|
||||
# Layers, in order:
|
||||
# 1. Universal: staged conflict markers, secret-looking material,
|
||||
# :latest image tags in staged compose/Dockerfiles.
|
||||
# 2. Repo-local: scripts/check-rules.sh --fast (if the repo ships it)
|
||||
# 3. Repo chain: .git/hooks/pre-commit (if present)
|
||||
# Bypass: --no-verify in genuine emergencies ONLY, and note it in the
|
||||
# repo JOURNAL the same day.
|
||||
set -uo pipefail
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || exit 0)"
|
||||
FAIL=0
|
||||
|
||||
# --- 1. Universal checks on staged content -------------------------------
|
||||
while IFS= read -r -d '' f; do
|
||||
# skip binary-looking blobs
|
||||
case "$f" in
|
||||
*.png|*.jpg|*.jpeg|*.gif|*.ico|*.woff|*.woff2|*.ttf|*.db|*.zip|*.gz) continue ;;
|
||||
esac
|
||||
[ -f "$REPO_ROOT/$f" ] || continue
|
||||
|
||||
if grep -nE '^(<<<<<<<|=======|>>>>>>>)' "$REPO_ROOT/$f" >/dev/null 2>&1; then
|
||||
echo "FAIL conflict markers staged in $f" >&2; FAIL=1
|
||||
fi
|
||||
case "$f" in
|
||||
*Dockerfile*|*docker-compose*|*compose*.y*ml)
|
||||
if grep -nE 'image:[[:space:]]*[A-Za-z0-9._/-]+:latest' "$REPO_ROOT/$f" >/dev/null 2>&1; then
|
||||
echo "FAIL :latest image tag in $f (pin digests or versions)" >&2; FAIL=1
|
||||
fi ;;
|
||||
esac
|
||||
if grep -nE '(sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----)' "$REPO_ROOT/$f" >/dev/null 2>&1; then
|
||||
echo "FAIL secret-looking material staged in $f (secrets live ONLY in ~/.creds)" >&2; FAIL=1
|
||||
fi
|
||||
done < <(git diff --cached --name-only -z 2>/dev/null)
|
||||
|
||||
# --- 2. Repo-local rule engine, when adopted -----------------------------
|
||||
if [ -x "$REPO_ROOT/scripts/check-rules.sh" ]; then
|
||||
if ! (cd "$REPO_ROOT" && bash scripts/check-rules.sh --fast --quiet); then
|
||||
echo "FAIL repo-local check-rules.sh (fast)" >&2; FAIL=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 3. Chain the repo's own installed hook ------------------------------
|
||||
if [ -x "$REPO_ROOT/.git/hooks/pre-commit" ]; then
|
||||
if ! "$REPO_ROOT/.git/hooks/pre-commit"; then
|
||||
echo "FAIL repo .git/hooks/pre-commit" >&2; FAIL=1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit "$FAIL"
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# hooks/global/pre-push — GLOBAL git hook (core.hooksPath for every
|
||||
# repo on this host). Runs universal checks always, delegates to the
|
||||
# repo's own enforcement when present, and chains the repo's installed
|
||||
# .git/hooks/<name> so copy-installed hooks keep working.
|
||||
#
|
||||
# Layers, in order:
|
||||
# 1. Universal: staged conflict markers, secret-looking material,
|
||||
# :latest image tags in staged compose/Dockerfiles.
|
||||
# 2. Repo-local: scripts/check-rules.sh --fast (if the repo ships it)
|
||||
# 3. Repo chain: .git/hooks/pre-push (if present)
|
||||
# Bypass: --no-verify in genuine emergencies ONLY, and note it in the
|
||||
# repo JOURNAL the same day.
|
||||
set -uo pipefail
|
||||
# pre-push receives refs on stdin; drain it so delegating hooks inherit a clean fd
|
||||
cat >/dev/null 2>&1 || true
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || exit 0)"
|
||||
FAIL=0
|
||||
|
||||
# --- 1. Universal checks on staged content -------------------------------
|
||||
while IFS= read -r -d '' f; do
|
||||
# skip binary-looking blobs
|
||||
case "$f" in
|
||||
*.png|*.jpg|*.jpeg|*.gif|*.ico|*.woff|*.woff2|*.ttf|*.db|*.zip|*.gz) continue ;;
|
||||
esac
|
||||
[ -f "$REPO_ROOT/$f" ] || continue
|
||||
|
||||
if grep -nE '^(<<<<<<<|=======|>>>>>>>)' "$REPO_ROOT/$f" >/dev/null 2>&1; then
|
||||
echo "FAIL conflict markers staged in $f" >&2; FAIL=1
|
||||
fi
|
||||
case "$f" in
|
||||
*Dockerfile*|*docker-compose*|*compose*.y*ml)
|
||||
if grep -nE 'image:[[:space:]]*[A-Za-z0-9._/-]+:latest' "$REPO_ROOT/$f" >/dev/null 2>&1; then
|
||||
echo "FAIL :latest image tag in $f (pin digests or versions)" >&2; FAIL=1
|
||||
fi ;;
|
||||
esac
|
||||
if grep -nE '(sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----)' "$REPO_ROOT/$f" >/dev/null 2>&1; then
|
||||
echo "FAIL secret-looking material staged in $f (secrets live ONLY in ~/.creds)" >&2; FAIL=1
|
||||
fi
|
||||
done < <(git diff --cached --name-only -z 2>/dev/null)
|
||||
|
||||
# --- 2. Repo-local rule engine, when adopted -----------------------------
|
||||
if [ -x "$REPO_ROOT/scripts/check-rules.sh" ]; then
|
||||
if ! (cd "$REPO_ROOT" && bash scripts/check-rules.sh --quiet); then
|
||||
echo "FAIL repo-local check-rules.sh (fast)" >&2; FAIL=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 3. Chain the repo's own installed hook ------------------------------
|
||||
if [ -x "$REPO_ROOT/.git/hooks/pre-push" ]; then
|
||||
if ! "$REPO_ROOT/.git/hooks/pre-push"; then
|
||||
echo "FAIL repo .git/hooks/pre-push" >&2; FAIL=1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit "$FAIL"
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# hooks/global/ticket-gate.sh — GLOBAL crush PreToolUse gate (all lanes).
|
||||
#
|
||||
# Ticket-first policy, repo-scoped: inside a git repository, modifying
|
||||
# operations are blocked until the repo's .crush/active-ticket is set
|
||||
# echo '#NNN' > .crush/active-ticket (set)
|
||||
# > .crush/active-ticket (clear)
|
||||
# Outside git repos (scratch space) everything is allowed: the gate
|
||||
# governs governed work, not throwaway experiments.
|
||||
#
|
||||
# Env provided by crush: CRUSH_TOOL_NAME, CRUSH_TOOL_INPUT_COMMAND,
|
||||
# CRUSH_TOOL_INPUT_FILE_PATH, CRUSH_PROJECT_DIR, PWD.
|
||||
set -u
|
||||
|
||||
TOOL="${CRUSH_TOOL_NAME:-}"
|
||||
CMD="${CRUSH_TOOL_INPUT_COMMAND:-}"
|
||||
FILE_PATH="${CRUSH_TOOL_INPUT_FILE_PATH:-}"
|
||||
|
||||
# Read-only tools — always allowed, everywhere.
|
||||
case "$TOOL" in
|
||||
view|ls|grep|glob|agent|sourcegraph|fetch|agentic_fetch|download|lsp_diagnostics|lsp_symbols|lsp_definition|lsp_references|lsp_call_hierarchy|crush_info|crush_logs|question|todos)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Not inside a git repo? Scratch space — allow.
|
||||
REPO_ROOT="$(git -C "${PWD:-.}" rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -z "$REPO_ROOT" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# bash tool: exempt read-only + management commands.
|
||||
if [ "$TOOL" = "bash" ]; then
|
||||
case "$CMD" in
|
||||
*"redmine-cli"*|*"discourse-cli"*|*"dns-cli"*|*"technitium"*) exit 0 ;;
|
||||
*"git status"*|*"git log"*|*"git diff"*|*"git show"*|*"git branch"*) exit 0 ;;
|
||||
*"check-rules"*|*"setup-hooks"*|*"shellcheck"*|*"run-tests"*|*"promote.sh"*) exit 0 ;;
|
||||
*"tailscale status"*|*"access-matrix"*|*"docker ps"*|*"docker logs"*|*"docker inspect"*) exit 0 ;;
|
||||
*active-ticket*) exit 0 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# edit/write: policy and wiring files ARE the policy — exempt them.
|
||||
case "$FILE_PATH" in
|
||||
*/AGENTS.md|*/questions-v*.md|*/check-rules.sh|*/crush.json|*/crushrc|*/hooks/*|*/.crush/*)
|
||||
case "$TOOL" in write|edit|multiedit) exit 0 ;; esac
|
||||
;;
|
||||
esac
|
||||
|
||||
# Enforce: repo root's .crush/active-ticket must exist and be non-empty.
|
||||
TICKET_FILE="$REPO_ROOT/.crush/active-ticket"
|
||||
if [ -f "$TICKET_FILE" ] && [ -s "$TICKET_FILE" ]; then
|
||||
TICKET="$(tr -d '\n' < "$TICKET_FILE")"
|
||||
printf '{"context":"Active ticket: %s"}\n' "$TICKET"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat >&2 <<'EOF'
|
||||
{"error":{"message":"TICKET GATE: no active ticket in this repo. Set one first: echo '#NNN' > .crush/active-ticket (create the ticket in Redmine first if none exists). Clear with: > .crush/active-ticket"}}
|
||||
EOF
|
||||
exit 2
|
||||
Reference in New Issue
Block a user