# Lefthook git hooks configuration # Install: lefthook install (or make install-hooks) # Docs: https://github.com/evilmartians/lefthook pre-commit: parallel: true commands: # Este repo es PUBLICO. docs/ son notas y planes internos (infra, nombres de # secretos, decisiones sin publicar) y esta en .gitignore — pero .gitignore no # frena un `git add -f`, que es exactamente como se colaron la primera vez. no-internal-docs: run: | staged=$(git diff --cached --name-only --diff-filter=ACMR | grep -iE '^docs/' || true) if [ -n "$staged" ]; then echo "Rutas de docs/ internas en el commit (repo publico):" echo "$staged" echo "Sacalas con: git restore --staged " exit 1 fi # Windows PowerShell 5.1 reads a script through the ANSI code page unless it # has a BOM, and the harness deploys them with one — but a curly quote or a # mojibake example in a COMMENT is still enough to break a string literal # on a machine whose code page differs (test/windows/README.md). ASCII only; # a leading UTF-8 BOM is allowed, it is what the README asks for. Python, not # grep -P: whether PCRE flags the BOM's bytes depends on the grep build. ps1-ascii: glob: "test/windows/*.ps1" run: | python3 -c "import sys; bad=[f for f in sys.argv[1:] if not open(f,'rb').read().removeprefix(b'\xef\xbb\xbf').isascii()]; bad and print('Non-ASCII bytes in harness scripts (keep test/windows/*.ps1 ASCII):', *bad, sep='\n'); sys.exit(1 if bad else 0)" {staged_files} gofmt-check: glob: "*.go" run: test -z "$(gofmt -l .)" || { echo "Files not formatted:"; gofmt -l .; exit 1; } go-vet: glob: "*.go" run: go vet ./... go-build: glob: "*.go" run: go build ./... # --allow-parallel-runners on EVERY golangci invocation in this hook: # `parallel: true` starts this command and arch-gate at the same moment, and # without the flag the second one to start dies on golangci's global flock # ("parallel golangci-lint is running", exit 3) and fails the commit. It # reproduced 3/3 with cold caches; warm caches only hid it by finishing first. golangci-lint: glob: "*.go" run: | if command -v golangci-lint &> /dev/null; then golangci-lint run --allow-parallel-runners ./... else echo "golangci-lint not installed, skipping (install: https://golangci-lint.run/welcome/install/)" fi # docker-entrypoint.sh is PID 1 of the published image (privilege drop, # mount ownership) and no Go test executes it — shellcheck is the only gate # it has. Mirrors the "shell" job in .github/workflows/ci.yml. shellcheck: glob: "*.sh" run: | if command -v shellcheck &> /dev/null; then shellcheck docker-entrypoint.sh scripts/check-arch.sh scripts/download-ffmpeg-static.sh scripts/release.sh else echo "shellcheck not installed, skipping (install: https://www.shellcheck.net/)" fi # Architectural / SOLID gate — blocks spaghetti & god-files in NEW/CHANGED code only # (legacy grandfathered vs origin/main). File size <500, func length, cyclo + cognitive # complexity 15, nesting, dup, max-params 5. See .golangci.arch.yml + scripts/check-arch.sh. arch-gate: glob: "*.go" run: | bash scripts/check-arch.sh || exit 1 if command -v golangci-lint &> /dev/null; then # Once per GOOS, exactly like `make arch`: a single pass never loads # //go:build windows|darwin files, so the ~35 platform-constrained # non-test files would commit unanalysed while golangci prints # "0 issues". Skipping the loop here is the half-fix that lets a # commit through and fails `make arch` afterwards. for goos in linux windows darwin; do GOOS=$goos golangci-lint run --allow-parallel-runners -c .golangci.arch.yml ./... || exit 1 done else echo "golangci-lint not installed, skipping arch lint (file-length gate still ran)" fi pre-push: commands: go-test: glob: "*.go" run: go test -race -count=1 -timeout=120s ./... commit-msg: scripts: validate.sh: runner: bash