- 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>
71 lines
1.8 KiB
Bash
Executable File
71 lines
1.8 KiB
Bash
Executable File
#!/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 "$@"
|