testsAndMisc/meta/scripts/check_file_length.sh
Krzysztof kuhy Rudnicki c4bb3e93f6 meta: add the check_file_length.sh gate the splits assumed existed
The shell-splitting work has been referring to check_file_length.sh --all
as its per-commit gate. No such script was ever committed: it is absent
from the working tree and from every commit in history, so each split so
far was checked by eye.

Cap is 250 lines, source extensions only (sh/bash/zsh, py, kt/kts, dart,
ts/tsx/js/jsx). Markdown, HTML, JSON, YAML and lockfiles are out of
scope, as are third_party/ and docs/superpowers/.

--all reports exactly 24 files over the cap, independently reproducing
the count the brief carried. That agreement is what confirms the
extension scope is the intended one.

Deliberately NOT wired into .pre-commit-config.yaml: 24 files are still
over, so a hook would block every commit. It becomes a hook once --all
exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 14:52:24 +02:00

136 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================================
# Enforce the repo-wide source-file length cap.
#
# Every source file must be at most MAX_LINES lines. The cap exists so that a
# file stays readable in one sitting and so that a split is forced while the
# seams are still obvious, rather than after a file has grown past rescue.
#
# Checks tracked source files only. Data, docs, config and vendored trees are
# out of scope: a 900-line lockfile or an HTML report is not something a
# human is expected to read top to bottom, so a cap there is noise.
#
# check_file_length.sh --all # every tracked source file
# check_file_length.sh <file>... # only the named files
#
# Exits 0 when everything is within the cap, 1 when anything is over.
# ============================================================================
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
readonly MAX_LINES=250
# Extensions that carry hand-written logic. Kept explicit rather than
# inverted: a new data format should not silently become subject to the cap.
readonly SOURCE_EXTENSIONS=(
sh bash zsh
py
kt kts
dart
ts tsx js jsx
)
# Vendored or generated trees. Their length is upstream's business, and a
# split would be reverted by the next sync.
readonly EXCLUDED_PREFIXES=(
third_party/
docs/superpowers/
)
usage() {
echo "Usage: $SCRIPT_NAME [--all | <file>...]"
echo "Options:"
echo " --all Check every tracked source file"
echo " -h, --help Show this help"
echo
echo "Source files must be at most $MAX_LINES lines."
exit 0
}
# True when the path carries a source extension we cap.
is_source_file() {
local path="$1" ext="${1##*.}"
# A path with no dot in its final component has no extension to match.
[[ "$path" == *.* ]] || return 1
local candidate
for candidate in "${SOURCE_EXTENSIONS[@]}"; do
[[ "$ext" == "$candidate" ]] && return 0
done
return 1
}
# True when the path sits under a tree we deliberately do not police.
is_excluded() {
local path="$1" prefix
for prefix in "${EXCLUDED_PREFIXES[@]}"; do
[[ "$path" == "$prefix"* ]] && return 0
done
return 1
}
# Print "<lines>\t<path>" for each in-scope file that exceeds the cap.
report_violations() {
local path lines
for path in "$@"; do
[[ -f "$path" ]] || continue
is_source_file "$path" || continue
is_excluded "$path" && continue
lines=$(wc -l <"$path")
if ((lines > MAX_LINES)); then
printf '%s\t%s\n' "$lines" "$path"
fi
done
}
collect_all_tracked() {
git ls-files
}
main() {
local -a targets=()
if [[ $# -eq 0 ]]; then
echo "Error: pass --all or one or more file paths" >&2
exit 1
fi
if [[ "$1" == "--all" ]]; then
mapfile -t targets < <(collect_all_tracked)
else
targets=("$@")
fi
local violations
violations="$(report_violations "${targets[@]}" | sort -rn)"
if [[ -z "$violations" ]]; then
echo "check_file_length: all checked files are within $MAX_LINES lines"
return 0
fi
local count
count="$(printf '%s\n' "$violations" | wc -l)"
echo "check_file_length: $count file(s) over the $MAX_LINES-line cap:" >&2
printf '%s\n' "$violations" | while IFS=$'\t' read -r lines path; do
printf ' %5d %s (+%d)\n' "$lines" "$path" "$((lines - MAX_LINES))" >&2
done
return 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h | --help)
usage
;;
*)
break
;;
esac
done
main "$@"