#!/usr/bin/env bash # Global pre-commit hook # Runs checks before allowing a commit # # Scans the staged DIFF (added lines only), not whole files, so that # pre-existing content in modified files does not trigger false positives. set -euo pipefail # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' echo "Running pre-commit checks..." # Collect added lines from the staged diff, once, for subsequent content checks. # -U0: zero context lines (only the +/- lines) # ^\+ : lines that start with + (added) # ^\+\+\+ : excluded (these are file headers like "+++ b/foo.txt") added_lines="$(git diff --cached -U0 | grep -E '^\+' | grep -vE '^\+\+\+' || true)" # Check for debugging keywords or TODOs in ADDED lines only if echo "$added_lines" | grep -qE "(console\.log|debugger|binding\.pry|TODO:|FIXME:|XXX:)"; then echo -e "${YELLOW}Warning: Found debugging keywords or TODOs in added lines:${NC}" echo "$added_lines" | grep -nE "(console\.log|debugger|binding\.pry|TODO:|FIXME:|XXX:)" || true echo # Probe by OPENING /dev/tty, not testing existence — on macOS /dev/tty always # exists ([ -e ] passes) but open() fails with ENXIO when there is no controlling # terminal (agent harnesses, CI), which used to kill the read and block the commit. if ( : < /dev/tty ) 2>/dev/null; then printf "Continue anyway? (y/n) " read -n 1 -r REPLY < /dev/tty echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi else echo -e "${YELLOW}(non-interactive; proceeding — rerun in a TTY to enforce)${NC}" fi fi # Check for large files (>5MB) — operates on file size, not diff content for file in $(git diff --cached --name-only); do if [ -f "$file" ]; then size=$(wc -c < "$file") if [ $size -gt 5242880 ]; then echo -e "${RED}Error: $file is larger than 5MB${NC}" echo "Consider using Git LFS for large files" exit 1 fi fi done # Check for secrets in ADDED lines only (basic check) if echo "$added_lines" | grep -qE "(password|secret|token|api_key)[[:space:]]*=[[:space:]]*[\"'][^\"']+[\"']"; then echo -e "${RED}Warning: Possible secrets detected in added lines:${NC}" echo "$added_lines" | grep -nE "(password|secret|token|api_key)[[:space:]]*=[[:space:]]*[\"'][^\"']+[\"']" || true echo "Please review your changes carefully." echo # Same open()-probe as above. Note the deliberate asymmetry: secrets BLOCK when # non-interactive (fail closed); debug keywords proceed with a warning. if ( : < /dev/tty ) 2>/dev/null; then printf "Continue anyway? (y/n) " read -n 1 -r REPLY < /dev/tty echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi else echo -e "${RED}(non-interactive; blocking — rerun in a TTY to confirm or use --no-verify with explicit justification)${NC}" exit 1 fi fi echo -e "${GREEN}Pre-commit checks passed!${NC}"