mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:07:04 +01:00
The last unit over the cap is now github.com/kuhyx/digital-wellbeing, cloned at ~/digital-wellbeing, and `--warn` is gone from the directory-depth-cap hook. `check_directory_depth.sh --all` exits 0 with zero violations, down from 46 at the start of the session. That was the point of the campaign. History was filtered across all three paths the unit has lived at (63 commits); the current path alone holds 2. The six suites that test this code lived in linux_configuration/tests/ rather than beside it, so they were filtered separately (24 commits) and merged in as a second root. A path filter of the unit alone would have left them here, testing code that had left the repo -- and their names would still have been in shell-tests.yml's hardcoded arrays, failing CI on missing files. linux_configuration/lib/common.sh is vendored into vendor/ rather than trimmed to the four functions actually used: set_actual_user_vars calls two of the others, so a trim breaks silently. It is in vendor/ and not lib/ because test_music_parallelism.sh copies the unit's own lib/ into a scratch worktree while stubbing the shared lib; in one directory the real common.sh overwrote the stub, and the suite silently measured the wrong library. Running the callers caught what shellcheck and grep both missed: check_and_enable_services.sh died with "DW_REPO: unbound variable" because the definition sat below its first use. It now runs clean and reports ok for every extracted-unit check. Two long-standing live defects fixed as a consequence. The pacman-wrapper drift manifest embeds ABSOLUTE paths and had named deleted scripts/ paths since the flatten, so the check reported a permanent false "stale or tampered"; it now verifies, with unchanged hashes proving the extracted bytes are identical. And the last two stale live references are gone: 8 -> 0. The two pre-commit excludes for setup_midnight_shutdown.sh are dropped rather than moved. lsattr shows nothing in the unit is chattr +i any more, and the fixer hooks leave the file alone when allowed to run. Removing 80 shell files also shrank the jscpd denominator enough that the virtualenv/tqdm clones inside the GENERATED .ci-mirror-venv/ tipped the repo to 2.09% and blocked this commit, which adds no duplication. That directory is the same vendored third-party shell the ignore list already excludes as .venv/, just under another name; jscpd walks the working tree rather than the index, so being untracked did not spare it. Excluded by name: 1.74%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
206 lines
6.1 KiB
Bash
Executable File
206 lines
6.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# ============================================================================
|
|
# Enforce the repo-wide directory-depth cap.
|
|
#
|
|
# A tracked file may sit at most MAX_DEPTH directories below the repo root.
|
|
# The cap exists so that this repo stays a home for small, findable scripts:
|
|
# once a subtree needs its own taxonomy several levels deep, it has outgrown
|
|
# "misc" and belongs in its own repository.
|
|
#
|
|
# Exempt, by path prefix:
|
|
# third_party/ vendored - its shape is upstream's business
|
|
# docs/superpowers/ hook-generated bookkeeping, not hand-placed
|
|
# .github/skills/ layout fixed by the agent harness
|
|
# C/ a C project: its own build layout, not taxonomy
|
|
# artgate/ a Flask app; routes/ and styles/ are framework shape
|
|
# misc/tools/ one tool split across modules to satisfy the line cap
|
|
# docs/ prose trees; chapter folders are not code taxonomy
|
|
# dwm/ an upstream-shaped suckless tree
|
|
# utils/data/ data files beside the script that reads them
|
|
# zsh/scratchpad/ a single scratch file
|
|
# run_all/ the aggregate runner's own fixtures
|
|
#
|
|
# Exempt, by path segment: `lib/` and `tests/` are structural, not taxonomic
|
|
# - a library split out to satisfy the 250-line cap, and the test suite the
|
|
# shell-coverage ratchet requires beside it. Capping them would force one
|
|
# gate to fight another. The same argument covers the directories whose name
|
|
# is dictated by an external format rather than chosen by us: systemd unit
|
|
# dirs, pacman/git hook dirs, GitHub workflow dirs, patch sets and test
|
|
# fixtures. Each is matched as a whole segment, so `libvirt/` or `binutils/`
|
|
# is unaffected.
|
|
#
|
|
# check_directory_depth.sh --all # every tracked file
|
|
# check_directory_depth.sh <file>... # only the named files
|
|
# check_directory_depth.sh --all --warn # report but always exit 0
|
|
#
|
|
# Exits 0 when everything is within the cap, 1 when anything is over.
|
|
# With --warn it reports violations and still exits 0; the hook runs in that
|
|
# mode while 46 paths under `periodic_background/{hosts,digital_wellbeing}`
|
|
# are over the cap and can only be fixed by extracting those two units, which
|
|
# is blocked on a manual guard repoint. Drop --warn from the hook once they
|
|
# are out; the cap is then enforced for real.
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
readonly MAX_DEPTH=2
|
|
|
|
readonly EXCLUDED_PREFIXES=(
|
|
third_party/
|
|
docs/superpowers/
|
|
.github/skills/
|
|
# Whole projects that carry their own conventional layout.
|
|
linux_configuration/C/
|
|
linux_configuration/dwm/
|
|
python_pkg/artgate/
|
|
# A single tool split into modules to satisfy the 250-line cap.
|
|
linux_configuration/misc/tools/
|
|
# Prose trees: a chapter folder is not code taxonomy.
|
|
linux_configuration/docs/
|
|
# One-off leaves, listed explicitly rather than exempting the segment
|
|
# name everywhere: `data` and `scratchpad` are too generic for that.
|
|
linux_configuration/utils/data/
|
|
linux_configuration/zsh/scratchpad/
|
|
meta/scripts/run_all/
|
|
)
|
|
|
|
# Directory names that never count as taxonomy. Matched as whole path
|
|
# segments, so a file called `lib.sh` or a directory `libvirt/` is unaffected.
|
|
readonly EXEMPT_SEGMENTS=(
|
|
lib
|
|
tests
|
|
# Names dictated by an external format, not chosen as a category.
|
|
systemd
|
|
hooks
|
|
workflows
|
|
patches
|
|
fixtures
|
|
)
|
|
|
|
WARN_ONLY=0
|
|
|
|
usage() {
|
|
echo "Usage: $SCRIPT_NAME [--warn] [--all | <file>...]"
|
|
echo "Options:"
|
|
echo " --all Check every tracked file"
|
|
echo " --warn Report violations but always exit 0"
|
|
echo " -h, --help Show this help"
|
|
echo
|
|
echo "Files must sit at most $MAX_DEPTH directories below the repo root."
|
|
exit 0
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# True when any DIRECTORY component of the path is an exempt segment. The
|
|
# final component is the filename and is deliberately not considered.
|
|
has_exempt_segment() {
|
|
local path="$1" dir="${1%/*}"
|
|
# No slash means the file is at the root: no directory components at all.
|
|
[[ "$path" == */* ]] || return 1
|
|
local IFS=/
|
|
local -a parts
|
|
read -r -a parts <<<"$dir"
|
|
local part segment
|
|
for part in "${parts[@]}"; do
|
|
for segment in "${EXEMPT_SEGMENTS[@]}"; do
|
|
[[ "$part" == "$segment" ]] && return 0
|
|
done
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Number of directories above the file: a/b/c.sh -> 2.
|
|
depth_of() {
|
|
local path="$1"
|
|
[[ "$path" == */* ]] || {
|
|
echo 0
|
|
return
|
|
}
|
|
local dir="${path%/*}"
|
|
echo $(($(tr -cd '/' <<<"$dir" | wc -c) + 1))
|
|
}
|
|
|
|
# Print "<depth>\t<path>" for each in-scope file that exceeds the cap.
|
|
report_violations() {
|
|
local path depth
|
|
for path in "$@"; do
|
|
[[ -n "$path" ]] || continue
|
|
is_excluded "$path" && continue
|
|
has_exempt_segment "$path" && continue
|
|
depth="$(depth_of "$path")"
|
|
if ((depth > MAX_DEPTH)); then
|
|
printf '%s\t%s\n' "$depth" "$path"
|
|
fi
|
|
done
|
|
}
|
|
|
|
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
|
|
if [[ $# -gt 1 ]]; then
|
|
echo "Error: --all takes no further arguments (got: ${*:2})" >&2
|
|
echo "Note: options must precede --all, e.g. --warn --all" >&2
|
|
exit 1
|
|
fi
|
|
mapfile -t targets < <(git ls-files)
|
|
else
|
|
targets=("$@")
|
|
fi
|
|
|
|
local violations
|
|
violations="$(report_violations "${targets[@]}" | sort -rn)"
|
|
|
|
if [[ -z "$violations" ]]; then
|
|
echo "check_directory_depth: all checked paths are within $MAX_DEPTH directories"
|
|
return 0
|
|
fi
|
|
|
|
local count
|
|
count="$(printf '%s\n' "$violations" | wc -l)"
|
|
|
|
echo "check_directory_depth: $count path(s) over the $MAX_DEPTH-directory cap:" >&2
|
|
printf '%s\n' "$violations" | while IFS=$'\t' read -r depth path; do
|
|
printf ' %3d %s (+%d)\n' "$depth" "$path" "$((depth - MAX_DEPTH))" >&2
|
|
done
|
|
|
|
if ((WARN_ONLY)); then
|
|
echo "check_directory_depth: --warn given, not failing" >&2
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h | --help)
|
|
usage
|
|
;;
|
|
--warn)
|
|
WARN_ONLY=1
|
|
shift
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
|
|
main "$@"
|