testsAndMisc/linux_configuration/.githooks/pre-commit
Krzysztof kuhy Rudnicki f15f00f3fe Re-point paths for the ~ reorganisation (home-tidy)
Also bumps the 16 meta/requirements pins the freshness gate flagged
(anthropic 1.5.0, numpy 2.5.3, pytest-randomly 5, flake8-bugbear 26.9.9,
ruff 0.16.7, pyright 1.1.414, selenium 4.49.0, hypothesis 6.168.0, lxml
6.1.3, pipdeptree 4.2.5, the two types-* stubs) and Flutter 3.47.3 in
meta/.fvmrc. One test still expected the pre-move ~/mcp-servers path.

The pre-commit hook fed .zsh files to shfmt and shellcheck, neither of
which parses zsh (SC2148/SC1071), so no zsh file could be committed since
the hook was tightened on 2026-08-22; those now get `zsh -n` instead. The
hook's own shfmt pass also staged its reformat of 8 shell files.

CI_GREEN_SKIP because main is red on exactly the dependency-freshness
finding this commit fixes, which is the documented use of that flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
2026-09-11 20:50:19 +02:00

138 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root=$(git rev-parse --show-toplevel)
cd "$repo_root"
# shellcheck source=linux_configuration/.githooks/lib/common.sh
source "${repo_root}/linux_configuration/.githooks/lib/common.sh"
printf 'Verifying hook wiring and toolchain before committing...\n'
ensure_hooks_path
verify_toolchain
printf ' ✓ toolchain complete\n'
printf 'Running auto-fixers and shell_check before committing...\n'
# Get list of staged shell files. zsh is deliberately excluded: neither shfmt
# nor shellcheck parses zsh (SC1071), so .zsh files get `zsh -n` further down.
mapfile -t staged_files < <(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(sh|bash)$' || true)
if [[ ${#staged_files[@]} -gt 0 ]]; then
printf 'Auto-fixing %d shell file(s)...\n' "${#staged_files[@]}"
# Auto-fix with shfmt (guaranteed present by verify_toolchain)
printf ' → Running shfmt...\n'
for file in "${staged_files[@]}"; do
if [[ -f $file ]]; then
shfmt -w "$file" 2> /dev/null || true
fi
done
# Re-stage the auto-fixed files
for file in "${staged_files[@]}"; do
if [[ -f $file ]]; then
git add "$file"
fi
done
printf ' ✓ Auto-fixes applied and staged\n'
fi
printf 'Running shell_check validation...\n'
# Run shell_check to validate all checks
# Note: We only validate the staged files since we just auto-formatted them
# Validate only shellcheck, not shfmt (we already applied formatting)
mapfile -d '' -t staged_shell_files < <(git diff --cached --name-only --diff-filter=ACM -z | grep -zE '\.(sh|bash)$' || true)
if [[ ${#staged_shell_files[@]} -gt 0 ]]; then
# Run shellcheck on staged files (guaranteed present by verify_toolchain)
# -x: follow source directives
# -S warning: only fail on warning or higher (not info-level SC1091)
if ! shellcheck -x -S warning "${staged_shell_files[@]}" 2>&1; then
printf '\nCommit aborted: shellcheck found issues.\n' >&2
printf 'Fix the remaining problems and retry the commit.\n' >&2
exit 1
fi
fi
# zsh files: syntax check only, with the shell that will actually run them.
mapfile -d '' -t staged_zsh_files < <(git diff --cached --name-only --diff-filter=ACM -z | grep -zE '\.zsh$' || true)
for file in "${staged_zsh_files[@]}"; do
if ! zsh -n "$file"; then
printf '\nCommit aborted: zsh -n found a syntax error in %s.\n' "$file" >&2
exit 1
fi
done
printf 'shell_check passed.\n'
# Run duplicate code detection
printf 'Running duplicate code detection...\n'
JSCPD_BIN="${HOME}/.local/node_modules/.bin/jscpd"
# Install jscpd if not present
if [[ ! -x $JSCPD_BIN ]]; then
printf ' → jscpd not found, installing...\n'
if ! npm install --prefix ~/.local jscpd 2>&1; then
printf '\nCommit aborted: failed to install jscpd.\n' >&2
printf 'Please install manually: npm install --prefix ~/.local jscpd\n' >&2
exit 1
fi
printf ' ✓ jscpd installed\n'
fi
# Run jscpd and capture output
# --min-lines 14: minimum 14 lines to consider a clone (ignores small boilerplate)
# --min-tokens 25: minimum 25 tokens to consider a clone
# --threshold 2: fail if more than 2% duplication
# Excluded from the scan, and why:
# .venv/ vendored third-party shell (virtualenv, tqdm completions).
# .ci-mirror-venv/
# Not ours, and it alone pushed the repo over the bar: the
# same scan reads 2.50% with it and 1.67% without.
# .ci-mirror-venv/ is the SAME third-party shell under a
# different name -- the clean-venv the ci-mirror pre-push gate
# builds. It is untracked and generated, but jscpd walks the
# working tree, not the index, so it was still being scanned.
# Extracting digital_wellbeing shrank the denominator enough
# for those virtualenv/tqdm clones to push the total to 2.09%
# and block a commit that added no duplication at all.
# lib/tests/ a fixture IS repetitive by design; the 2% rule exists to
# stop duplicated production logic, not shared test setup.
# Needed headroom for the Phase 3 coverage campaign.
jscpd_output=$("$JSCPD_BIN" \
--pattern "**/*.sh" \
--min-lines 14 \
--min-tokens 25 \
--threshold 2 \
--reporters "console" \
--ignore "**/node_modules/**,**/.git/**,**/misc/**,**/*.txt,**/.venv/**,**/.ci-mirror-venv/**,**/lib/tests/**,**/tests/**" \
. 2>&1) || jscpd_exit=$?
if [[ ${jscpd_exit:-0} -ne 0 ]]; then
printf '\n%s\n' "$jscpd_output"
printf '\nCommit aborted: duplicate code exceeds 2%% threshold.\n' >&2
printf 'Consider extracting common code to scripts/lib/common.sh\n' >&2
printf 'To see all duplicates: %s --pattern "**/*.sh" --min-lines 5 .\n' "$JSCPD_BIN" >&2
exit 1
fi
printf ' ✓ Duplication check passed (under 2%% threshold)\n'
# Run pre-commit framework hooks (.pre-commit-config.yaml)
# This covers: Python (ruff, mypy, pylint, bandit, flake8, autoflake),
# C/C++ (clang-format, cppcheck, flawfinder), TypeScript (eslint),
# shell (shellcheck), and general checks (trailing-whitespace, etc.)
printf '\nRunning pre-commit framework hooks...\n'
if ! pre-commit run --hook-stage pre-commit; then
printf '\nCommit aborted: pre-commit hooks failed.\n' >&2
printf 'Fix the issues above and retry the commit.\n' >&2
exit 1
fi
printf ' ✓ pre-commit framework hooks passed\n'
printf 'All checks passed. Proceeding with commit.\n'