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.
- Replace Docker-based upstream hooks.
koalaman/shellcheck-precommit,pocc/pre-commit-hooks(clang-format / clang-tidy / cppcheck), andhadolint/hadolint-precommitall 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: localduplication 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 uselanguage: script(orpython/nodemanifest hooks) and avoid Docker, soprekruns them at full native speed.
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: mypyThen install hooks and run them:
prek install # or: pre-commit install
prek run --all-files # or: pre-commit run --all-filesPython-only repo (mypy + textlint for docs):
repos:
- repo: https://github.com/kreuzberg-dev/pre-commit-hooks
rev: v0.1.0
hooks:
- id: mypy
- id: textlintPolyglot 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: kubeconformJVM-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: detektEach 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 |
| 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/.
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: textlintHooks 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 |
| 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.
${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.
- "refusing to download X: checksum is a placeholder" —
hooks/<id>/checksums.txtstill has the all-zeros placeholder. Either bumpversion.txtto a real release and runtask 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, andjavaon PATH. SetJAVA_HOMEexplicitly or install a JDK 17+. - Windows toolchain wrappers don't run — toolchain wrappers expect Bash; the
binary-download and manifest hooks work via
prekon Windows but the wrappers require WSL or Git Bash. - Helm chart template fails
check-yaml— excludehelm-lint's test fixtures fromcheck-yamlsince chart templates use Go template syntax. Example:exclude: '^.*/templates/.*\.ya?ml$'.
- Hook semantics follow semver via git tags (
v0.1.0,v0.1.1,v0.2.0, …). - Pin consumers to
rev: vX.Y.Z(immutable).mainis not a stable target. - Each binary-download hook tracks an upstream tool version in
hooks/<id>/version.txt(Renovate-managed) and a sha256 inhooks/<id>/checksums.txt(regenerated bytask fetch-checksumswheneverversion.txtbumps). - The weekly
release-dry-run.ymlworkflow re-fetches every checksum and fails if upstream has re-tagged or moved an asset.
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:hooksSee docs/adding-a-hook.md. In short:
cp -r hooks/_template hooks/<new-id>and fill inrun.sh,assets.toml(binary or JVM only),version.txt,checksums.txt.- Add a
tests/ok.<ext>andtests/bad.<ext>fixture. - Add
tests/test_<new_id>.pymirroring an existing same-category hook test. - Append a manifest entry to
.pre-commit-hooks.yaml. - Run
task ciandprek run --all-files. - Document in
docs/hooks/<new-id>.md.
Architecture rationale, sha256 contract, cache layout, and prek nuances are
documented in docs/design.md.