signal-bot/.pre-commit-config.yaml
Krzysztof kuhy Rudnicki c9061b27a8 fix: make shellcheck agree between local and CI, and fix the group picker
CI failed on shellcheck where the local hook passed: the runner's shellcheck
reports SC2015 (`A && B || C` is not if-then-else) and mine did not. Both now
run with --severity=style so a version skew cannot pass locally and fail in
CI. The cleanup traps are plain ifs, which is what the finding was about --
`kill` on an already-dead pid makes B fail and C run while A is true.

live_smoke.sh choose_group had two bugs that only surface when it is actually
run, neither reachable from the offline suite:
- it fetched /v1/groups twice, once to display and once to resolve the picked
  index, so an unstable order would hand back a different group than the one
  shown. Fetched once into a variable now.
- `read -r -p` wrote its prompt to stdout, which the caller captures with
  $(...), so the prompt text would have been swallowed into the group id.
  Prompt and listing both go to stderr; stdout carries only the id.
Also validates that the pick is a number and in range.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yaBT45KhHqxTKVCTA87iy
2026-08-29 13:07:28 +02:00

94 lines
3.1 KiB
YAML

# Pre-commit hooks for signal-bot.
#
# Install with: pre-commit install
# Run manually: pre-commit run --all-files
#
# Every hook is `language: system` because the tools already live in .venv;
# pre-commit must not build isolated environments for them. fail_fast so a
# later check cannot run after an earlier one has already rejected the commit.
fail_fast: true
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-merge-conflict
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: detect-private-key
- id: debug-statements
- id: check-ast
- id: mixed-line-ending
args: [--fix=lf]
- repo: local
hooks:
# The shared 250-line cap. Checks only the files in the commit, so an
# unrelated commit never breaks. No baseline, no allowlist -- those are
# suppressions. The exemption list lives with the checker in ~/utils so
# this repo's idea of the rule cannot drift from every other repo's.
- id: file-length
name: file length <= 250 lines
entry: scripts/check_file_length.sh
language: system
# Four markdown namespaces (README / CLAUDE* / DOCS* / TODO*), plus the
# rule that a TODO carries "REMOVE ME AFTER FINISH" and nothing else may.
- id: md-naming
name: markdown naming convention
entry: scripts/check_md_naming.sh
language: system
files: \.md$
# Exact pins at newest stable. Runs repo-wide: a pin goes stale because
# upstream released, not because this commit touched the manifest.
- id: dependency-freshness
name: dependencies are newest stable
entry: scripts/check_dependency_freshness.sh --all
language: system
pass_filenames: false
always_run: true
- id: ruff-format
name: ruff format
entry: .venv/bin/ruff format --check
language: system
types_or: [python, pyi]
- id: ruff
name: ruff (ALL rules)
entry: .venv/bin/ruff check --force-exclude
language: system
types_or: [python, pyi]
- id: mypy
name: mypy --strict
entry: .venv/bin/mypy
language: system
pass_filenames: false
args: [signal_bot, fake_signal]
- id: shellcheck
name: shellcheck
# Same severity floor as CI: a stricter runner version must not be
# the thing that discovers a finding this hook let through.
entry: shellcheck --severity=style
language: system
types: [shell]
# 100% branch coverage, no omits. Runs on every commit: coverage is the
# whole point of the suite, and a partial run cannot prove it.
- id: pytest
name: pytest (100% branch coverage)
entry: .venv/bin/python -m pytest -q
language: system
pass_filenames: false
always_run: true