chore: install refactoring guardrails (Phase 0) [guardrail-change]
- scripts/check-loc.sh: LOC budget checker (500 LOC hard cap) - .claude/rules/architecture.md: split triggers, patterns per language - .claude/rules/loc-exceptions.txt: documented escape hatches - AGENTS.python.md: FastAPI conventions (routes thin, service layer) - AGENTS.go.md: Go/Gin conventions (handler ≤40 LOC) - AGENTS.typescript.md: Next.js conventions (page.tsx ≤250 LOC, colocation) - CLAUDE.md extended with guardrail section + commit markers 273 files currently exceed 500 LOC — to be addressed phase by phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
scripts/check-loc.sh
Executable file
70
scripts/check-loc.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MAX_LOC="${MAX_LOC:-500}"
|
||||
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
EXCEPTIONS_FILE="${ROOT_DIR}/.claude/rules/loc-exceptions.txt"
|
||||
|
||||
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
||||
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
||||
|
||||
is_exempt() {
|
||||
local file="$1"
|
||||
[[ -f "$EXCEPTIONS_FILE" ]] || return 1
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
[[ "$line" =~ ^# ]] && continue
|
||||
local pattern="${line%%|*}"
|
||||
pattern="$(echo "$pattern" | xargs)"
|
||||
if [[ -n "$pattern" ]] && [[ "$file" == $pattern ]]; then
|
||||
return 0
|
||||
fi
|
||||
done < "$EXCEPTIONS_FILE"
|
||||
return 1
|
||||
}
|
||||
|
||||
count_loc() {
|
||||
[[ -f "$1" ]] && awk 'END { print NR }' "$1" || echo 0
|
||||
}
|
||||
|
||||
collect_changed_files() {
|
||||
{ git diff --name-only --cached; git diff --name-only; git ls-files --others --exclude-standard; } | awk 'NF' | sort -u
|
||||
}
|
||||
|
||||
main() {
|
||||
local mode="changed"
|
||||
[[ "${1:-}" == "--changed" ]] && mode="changed"
|
||||
[[ "${1:-}" == "--all" ]] && mode="all"
|
||||
|
||||
local files=()
|
||||
if [[ "$mode" == "changed" ]]; then
|
||||
IFS=$'\n' read -d '' -r -a files < <(collect_changed_files)
|
||||
else
|
||||
IFS=$'\n' read -d '' -r -a files < <(find "$ROOT_DIR" \( -name '*.py' -o -name '*.go' -o -name '*.ts' -o -name '*.tsx' \) -not -path '*/node_modules/*' -not -path '*/.next/*' -not -path '*/__pycache__/*' -not -path '*/venv/*')
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
local violations=0
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
[[ -f "$file" ]] || continue
|
||||
is_exempt "$file" && continue
|
||||
local loc
|
||||
loc="$(count_loc "$file")"
|
||||
if (( loc > MAX_LOC )); then
|
||||
red "VIOLATION: $file ($loc LOC > $MAX_LOC)"
|
||||
violations=$((violations + 1))
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
if (( failed )); then
|
||||
red ""
|
||||
red "$violations file(s) exceed ${MAX_LOC} LOC budget."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
green "LOC budget check passed."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user