glove/scripts/hooks/monolith-slop.sh
0xkite 60be71c908
chore(hooks): sync glove hook with sage refinements
Align glove's monolith-slop.sh with the sage copy: monolith check scoped to newly-added files; slop detection switched to jscpd-only; in-file #[cfg(test)] exemption.
2026-08-13 07:01:38 -06:00

93 lines
2.8 KiB
Bash
Executable file

#!/bin/bash
# Monolith + slop detection pre-commit hook.
#
# Monolith: fails when a newly-added staged source file exceeds MAX_FILE_LINES
# (pre-existing monoliths are tracked separately, so modifying them is allowed).
# Slop: copy-paste detection via jscpd when available (block-level, accurate).
# There is deliberately no single-line duplicate heuristic — it was
# too noisy (flagging legitimate logging targets and test setup).
#
# Overrides: a repo may set monolith-slop.allow (space-separated globs) via
# `git config monolith-slop.allow "*.gen.rs **/vendor/**"` to exempt generated or
# vendored files. This hook never blocks when there are no staged source files.
set -u
MAX_FILE_LINES="${MONOLITH_MAX_LINES:-800}"
# Source file extensions we police.
SOURCE_RE='\.(rs|cpp|hpp|cc|hh|c|h|py|ts|js|go|sh)$'
all_files=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E "$SOURCE_RE" || true)
added_files=$(git diff --cached --name-only --diff-filter=A 2>/dev/null | grep -E "$SOURCE_RE" || true)
if [ -z "$all_files" ]; then
exit 0
fi
allow=$(git config --get monolith-slop.allow 2>/dev/null || true)
is_allowed() {
local f="$1"
local g
if [ -n "$allow" ]; then
for g in $allow; do
case "$f" in
$g) return 0 ;;
esac
done
fi
return 1
}
# Test files legitimately repeat setup; the duplicate-line heuristic is too
# aggressive for them, so the slop check is skipped (monolith still applies).
is_test() {
case "$1" in
tests/* | test/* | */tests/* | */test/* | test_* | *_test.* | *_tests.* | *Test.* | *Tests.*) return 0 ;;
esac
# In-file test modules (Rust `#[cfg(test)]`) legitimately repeat setup; skip
# the duplicate-line heuristic for them (the monolith limit still applies).
if [ -f "$1" ] && grep -q '#\[cfg(test)\]' "$1" 2>/dev/null; then
return 0
fi
return 1
}
monoliths=""
for f in $added_files; do
[ -f "$f" ] || continue
is_allowed "$f" && continue
lines=$(wc -l <"$f" | tr -d ' ')
if [ "$lines" -gt "$MAX_FILE_LINES" ]; then
monoliths="${monoliths}\n $f (${lines} lines, limit ${MAX_FILE_LINES})"
fi
done
slop=""
if command -v jscpd >/dev/null 2>&1; then
for f in $all_files; do
[ -f "$f" ] || continue
is_allowed "$f" && continue
is_test "$f" && continue
out=$(jscpd --silent --min-lines 5 --min-tokens 40 "$f" 2>/dev/null || true)
if [ -n "$out" ]; then
slop="${slop}\n $f (jscpd: copy-paste detected)"
fi
done
fi
status=0
if [ -n "$monoliths" ]; then
echo "❌ monolith detection: staged file(s) exceed ${MAX_FILE_LINES} lines:"
printf "%b\n" "$monoliths"
echo " Split these into focused modules before committing."
status=1
fi
if [ -n "$slop" ]; then
echo "⚠️ slop detection: copy-paste / duplicated code in staged file(s):"
printf "%b\n" "$slop"
echo " Deduplicate or factor the repeated code before committing."
status=1
fi
exit "$status"