Skip to content

kreuzberg-dev/pre-commit-hooks

Repository files navigation

kreuzberg-dev/pre-commit-hooks

CI Release Dry-Run License: MIT

Polyglot pre-commit hooks for the languages alef supports — Python, TypeScript, WebAssembly, Ruby, PHP, Go, Java, C#, Elixir, R, Kotlin, Gleam, Zig, C, Swift, Dart — plus shell, Helm, Dockerfile, and markdown. Compatible with both pre-commit and prek.

Why this repo exists

  • Replace Docker-based upstream hooks. koalaman/shellcheck-precommit, pocc/pre-commit-hooks (clang-format / clang-tidy / cppcheck), and hadolint/hadolint-precommit all require Docker. We ship native binaries with sha256 verification instead, which is faster and works in environments without Docker (e.g. GitHub Actions Linux runners under default permissions, or air-gapped CI).
  • Eliminate repo: local duplication across the kreuzberg-dev polyrepo. Twenty-plus configs were duplicating identical Ruby/PHP/C#/Elixir/Go/Java blocks. They live here once now.
  • Verified artifacts. Every binary download is sha256-checked against hooks/<id>/checksums.txt. Placeholder checksums refuse to run rather than silently producing a green build.
  • prek-friendly. All hooks use language: script (or python/node manifest hooks) and avoid Docker, so prek runs them at full native speed.

Quickstart

Add the repo to your .pre-commit-config.yaml and pin a tag:

repos:
  - repo: https://github.com/kreuzberg-dev/pre-commit-hooks
    rev: v0.1.0
    hooks:
      - id: shfmt
      - id: shellcheck
      - id: mypy

Then install hooks and run them:

prek install            # or: pre-commit install
prek run --all-files    # or: pre-commit run --all-files

Three concrete examples

Python-only repo (mypy + textlint for docs):

repos:
  - repo: https://github.com/kreuzberg-dev/pre-commit-hooks
    rev: v0.1.0
    hooks:
      - id: mypy
      - id: textlint

Polyglot repo (Go + Ruby + TypeScript + Helm + shell):

repos:
  - repo: https://github.com/kreuzberg-dev/pre-commit-hooks
    rev: v0.1.0
    hooks:
      - id: shfmt
      - id: shellcheck
      - id: go-fmt
      - id: golangci-lint
      - id: govulncheck
      - id: rubocop
      - id: rubocop-lint
      - id: tsc-typecheck
      - id: helm-lint
      - id: kubeconform

JVM-only repo (Java + Kotlin formatters and linters):

repos:
  - repo: https://github.com/kreuzberg-dev/pre-commit-hooks
    rev: v0.1.0
    hooks:
      - id: palantir-java-format
      - id: checkstyle
      - id: pmd
      - id: java-verify     # heavy: runs `mvn verify`; opt-in
      - id: ktfmt
      - id: detekt

Hook categories

Each hook falls into one of four implementation flavors:

Category Mechanism Guarantees Consumer expectation
Binary download Pinned upstream release binary, sha256-verified, cached under ~/.cache/kreuzberg-pre-commit-hooks/ First run downloads + verifies; subsequent runs are cache-only and offline-safe None — purely additive
Toolchain wrapper Calls a tool the consumer already has installed (swift, dart, mvn, …); warns and skips if missing Zero downloads; honest about what it depends on Tool must be installed locally; otherwise hook prints a skip warning and exits 0
JVM JAR Downloads a JVM JAR (sha256-verified) and execs java -jar. JDK 17+ discovered via JAVA_HOME/usr/libexec/java_home -v 17 (macOS) → SDKMAN → java on PATH Reproducible across JDK distributions JDK 17+ must be installed locally; otherwise hook skips
Manifest Standard language: python / language: node with pinned additional_dependencies (e.g. clang-format from PyPI, textlint from npm) Fully managed by pre-commit / prek Network access on first install

Hooks

Hook id Language / scope Category
shfmt Shell formatter binary download
shellcheck Shell linter binary download
gleam-format Gleam formatter toolchain wrapper
gleam-check Gleam type check toolchain wrapper
ktfmt Kotlin formatter JVM JAR
detekt Kotlin linter JVM JAR
clang-format C/C++ formatter manifest (PyPI)
clang-tidy C/C++ linter manifest (PyPI)
go-fmt Go formatter (gofmt + goimports) toolchain wrapper
golangci-lint Go linter binary download
govulncheck Go vulnerability scanner toolchain wrapper
palantir-java-format Java formatter JVM JAR
checkstyle Java linter JVM JAR
pmd Java linter JVM JAR
java-verify mvn verify (Java module) toolchain wrapper
mypy Python type checker toolchain wrapper
rubocop Ruby formatter (autocorrect) toolchain wrapper
rubocop-lint Ruby linter (no autocorrect) toolchain wrapper
steep Ruby type checker toolchain wrapper
dotnet-format C# formatter toolchain wrapper
dotnet-format-check C# verify-no-changes toolchain wrapper
php-cs-fixer PHP formatter (PHAR) binary download
phpstan PHP static analysis (PHAR) binary download
mix-format Elixir formatter toolchain wrapper
mix-credo Elixir linter toolchain wrapper
air-format R formatter (Posit air) binary download
air-check R formatter check-only (Posit air) binary download
lintr R linter toolchain wrapper
zig-fmt Zig formatter toolchain wrapper
zig-build-check Zig type check toolchain wrapper
swift-format Swift formatter toolchain wrapper
swiftlint Swift linter toolchain wrapper
dart-format Dart formatter toolchain wrapper
dart-analyze Dart linter toolchain wrapper
hadolint Dockerfile linter binary download
helm-lint Helm chart linter binary download
kubeconform Kubernetes schema validator binary download
textlint Markdown prose linter (docs/) manifest (Node)
tsc-typecheck TypeScript type check toolchain wrapper

Per-hook documentation lives in docs/hooks/.

Per-language usage snippets

Real-world configurations lifted from the kreuzberg polyrepo. Override args:, files:, or exclude: per your repo layout.

# Shell
- id: shfmt
  args: ["-w", "-i", "2"]
- id: shellcheck
  args: ["-x"]

# Python
- id: mypy

# Go
- id: go-fmt
- id: golangci-lint
- id: govulncheck

# Java
- id: palantir-java-format
- id: checkstyle
  args: ["-c", "/google_checks.xml"]
- id: pmd
  args: ["check", "-R", "rulesets/java/quickstart.xml", "-f", "text"]
- id: java-verify       # heavy; runs `mvn verify`

# Kotlin
- id: ktfmt
  args: ["--kotlinlang-style"]
- id: detekt
  args: ["--build-upon-default-config"]

# Ruby
- id: rubocop
- id: rubocop-lint
- id: steep

# C#
- id: dotnet-format
- id: dotnet-format-check

# PHP
- id: php-cs-fixer
  args: ["fix"]
- id: phpstan
  args: ["analyse", "--no-progress"]

# Elixir (assumes packages/elixir/)
- id: mix-format
- id: mix-credo

# R
- id: air-format
- id: air-check
- id: lintr

# Gleam
- id: gleam-format
- id: gleam-check

# Zig
- id: zig-fmt
- id: zig-build-check

# Swift
- id: swift-format
- id: swiftlint

# Dart
- id: dart-format
- id: dart-analyze

# C / C++
- id: clang-format
- id: clang-tidy

# Dockerfile
- id: hadolint

# Helm / Kubernetes
- id: helm-lint
- id: kubeconform
  args: ["-strict", "-summary"]

# TypeScript (project-context)
- id: tsc-typecheck

# Markdown prose (docs/)
- id: textlint

Configuration

Hooks honor a small set of environment variables:

Variable Default Purpose
KREUZBERG_HOOKS_CACHE $HOME/.cache/kreuzberg-pre-commit-hooks Cache root for downloaded binaries and JARs
KREUZBERG_HELM_CHART_DIRS charts/* Globs helm-lint walks for charts
KREUZBERG_JAVA_POM packages/java/pom.xml POM path for java-verify
JAVA_HOME unset If set and points at JDK 17+, used by JVM JAR hooks

Supported platforms

OS Arch Status
Linux x86_64 first-class
Linux aarch64 first-class
macOS x86_64 first-class
macOS aarch64 (Apple silicon) first-class
Windows x86_64 binary-download hooks only; toolchain wrappers depend on local install

CI runs the unit-test matrix on ubuntu-latest, macos-latest, and windows-latest against Python 3.10 and 3.14.

Cache layout

${KREUZBERG_HOOKS_CACHE:-$HOME/.cache/kreuzberg-pre-commit-hooks}/<tool>/<version>/<arch>/

Cache entries are immutable per (tool, version, arch) triple — bumping hooks/<id>/version.txt creates a new entry; old ones are kept until you GC manually. Inside each entry: the resolved binary or extracted tree, plus a .verified sentinel written after the sha256 check.

Troubleshooting

  • "refusing to download X: checksum is a placeholder"hooks/<id>/checksums.txt still has the all-zeros placeholder. Either bump version.txt to a real release and run task fetch-checksums, or wait for the Renovate bot to do it.
  • "skipping — <tool> not found on PATH" — toolchain wrapper found no local install. Install the toolchain (e.g. brew install swift, sdk install java 17-tem) or remove the hook from your config.
  • "skipping JVM hook — no JDK 17+ found" — the JDK locator tried JAVA_HOME, /usr/libexec/java_home -v 17, SDKMAN, and java on PATH. Set JAVA_HOME explicitly or install a JDK 17+.
  • Windows toolchain wrappers don't run — toolchain wrappers expect Bash; the binary-download and manifest hooks work via prek on Windows but the wrappers require WSL or Git Bash.
  • Helm chart template fails check-yaml — exclude helm-lint's test fixtures from check-yaml since chart templates use Go template syntax. Example: exclude: '^.*/templates/.*\.ya?ml$'.

Versioning policy

  • Hook semantics follow semver via git tags (v0.1.0, v0.1.1, v0.2.0, …).
  • Pin consumers to rev: vX.Y.Z (immutable). main is not a stable target.
  • Each binary-download hook tracks an upstream tool version in hooks/<id>/version.txt (Renovate-managed) and a sha256 in hooks/<id>/checksums.txt (regenerated by task fetch-checksums whenever version.txt bumps).
  • The weekly release-dry-run.yml workflow re-fetches every checksum and fails if upstream has re-tagged or moved an asset.

Local development

Built on Task and uv.

task setup                  # install dev deps, install pre-commit hooks
task lint                   # ruff + mypy + bash -n
task format                 # ruff format
task test                   # pytest + per-hook smoke tests
task test:unit              # pytest only
task test:hooks             # bash scripts/smoke_hooks.sh
task cov                    # coverage report (gate set in pyproject.toml)
task audit                  # pip-audit + bandit
task update                 # bump every hook + dev deps + pre-commit revs
task update:versions        # bump hooks/*/version.txt only
task update:versions:check  # show pending bumps without writing
task fetch-checksums        # populate sha256s in hooks/*/checksums.txt
task ci                     # full CI: lint + cov + audit + test:hooks

Adding a new hook

See docs/adding-a-hook.md. In short:

  1. cp -r hooks/_template hooks/<new-id> and fill in run.sh, assets.toml (binary or JVM only), version.txt, checksums.txt.
  2. Add a tests/ok.<ext> and tests/bad.<ext> fixture.
  3. Add tests/test_<new_id>.py mirroring an existing same-category hook test.
  4. Append a manifest entry to .pre-commit-hooks.yaml.
  5. Run task ci and prek run --all-files.
  6. Document in docs/hooks/<new-id>.md.

Architecture rationale, sha256 contract, cache layout, and prek nuances are documented in docs/design.md.

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors