hk Setup
Configure hk git hooks with appropriate linters for any project.
When to Use This Skill
- •User asks to set up pre-commit hooks
- •User wants to add linting/formatting to a project
- •User mentions hk, git hooks, or pre-commit
- •User wants to enforce code quality on commits
Prerequisites
# Install hk (one of these) brew install hk mise use -g hk
Workflow
1. Detect Project Type
Look for these files to identify the project:
| File | Project Type | Recommended Linters |
|---|---|---|
pyproject.toml, *.py | Python | ruff, ruff_format |
package.json, *.ts | JavaScript/TypeScript | biome (or eslint + prettier) |
go.mod, *.go | Go | go_fmt, golangci_lint |
Cargo.toml, *.rs | Rust | rustfmt, cargo_clippy |
Package.swift, *.swift | Swift | swiftlint, swiftformat |
*.sh, *.bash | Shell | shellcheck, shfmt |
Dockerfile | Docker | hadolint |
*.pkl | Pkl configs | pkl |
2. Check Available Tools
hk builtins # List all built-in linters
See references/builtins.md for full catalog. Key builtins:
| Language | Recommended Builtins |
|---|---|
| Python | ruff, ruff_format |
| JS/TS | biome or eslint + prettier |
| Go | go_fmt, go_imports, golangci_lint |
| Rust | rustfmt, cargo_clippy |
| Shell | shellcheck, shfmt |
3. Generate hk.pkl
Create hk.pkl in project root. Always use version-pinned imports:
amends "package://github.com/jdx/hk/releases/download/v1.28.0/hk@1.28.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.28.0/hk@1.28.0#/Builtins.pkl"
local linters = new Mapping<String, Step> {
// Add linters here based on project type
}
hooks {
["pre-commit"] {
fix = true
stash = "git"
steps = linters
}
["pre-push"] {
steps = linters
}
["fix"] {
fix = true
steps = linters
}
["check"] {
steps = linters
}
}
4. Install & Test
hk validate # Check config syntax hk install # Install git hooks hk check --all # Run all checks hk fix --all # Auto-fix issues
Project Templates
Python (ruff + ty)
local linters = new Mapping<String, Step> {
["ruff"] = Builtins.ruff
["ruff-format"] = Builtins.ruff_format
["ty"] {
glob = "**/*.py"
check = "ty check"
}
["pkl"] = Builtins.pkl
}
JavaScript/TypeScript (eslint + prettier)
local linters = new Mapping<String, Step> {
["eslint"] = Builtins.eslint
["prettier"] = Builtins.prettier
["pkl"] = Builtins.pkl
}
JavaScript/TypeScript (biome)
Biome is a fast all-in-one linter+formatter. Use instead of eslint+prettier for new projects:
local linters = new Mapping<String, Step> {
["biome"] = Builtins.biome
["pkl"] = Builtins.pkl
}
JavaScript/TypeScript (oxc)
Oxlint + oxfmt from the oxc project. Oxfmt is alpha but very fast:
local linters = new Mapping<String, Step> {
["oxlint"] = Builtins.ox_lint
["oxfmt"] {
glob = "**/*.{ts,tsx,js,jsx,json,md}"
check = "oxfmt --check {{files}}"
fix = "oxfmt {{files}}"
}
["pkl"] = Builtins.pkl
}
Install: npm install -g oxfmt
Go
local linters = new Mapping<String, Step> {
["gofmt"] = Builtins.gofmt
["goimports"] = Builtins.goimports
["golangci-lint"] = Builtins.golangci_lint
["pkl"] = Builtins.pkl
}
Rust
local linters = new Mapping<String, Step> {
["rustfmt"] = Builtins.rustfmt
["clippy"] = Builtins.clippy
["pkl"] = Builtins.pkl
}
Swift (swiftlint + swiftformat)
local linters = new Mapping<String, Step> {
["swiftlint"] = Builtins.swiftlint
["swiftformat"] {
glob = "**/*.swift"
check = "swiftformat --lint {{files}}"
fix = "swiftformat {{files}}"
}
["pkl"] = Builtins.pkl
}
Shell
local linters = new Mapping<String, Step> {
["shellcheck"] = Builtins.shellcheck
["shfmt"] = Builtins.shfmt
["pkl"] = Builtins.pkl
}
Docker
local linters = new Mapping<String, Step> {
["hadolint"] = Builtins.hadolint
["pkl"] = Builtins.pkl
}
Universal Linters
These work for any project. Add them directly to your linters mapping:
local linters = new Mapping<String, Step> {
// Language-specific linters...
["ruff"] = Builtins.ruff
// Universal linters (add to any project)
["typos"] = Builtins.typos // Spell checker
["trailing-whitespace"] = Builtins.trailing_whitespace // Remove trailing spaces
["newlines"] = Builtins.newlines // Ensure final newline
}
Note: Pkl Mappings don't support + concatenation. Define all linters in a single mapping.
Other useful universal builtins:
- •
check_merge_conflict- Prevent committing merge conflict markers - •
detect_private_key- Prevent committing private keys - •
check_added_large_files- Warn about large files
Custom Steps
For tools without builtins, define custom steps:
["my-linter"] {
glob = "**/*.ext" // Files to match
check = "my-tool check {{files}}" // Check command
fix = "my-tool fix {{files}}" // Optional fix command
}
Step Options
| Option | Description |
|---|---|
glob | File patterns to match |
check | Command to run for checking |
fix | Command to run for fixing (optional) |
exclusive | Run in isolation (no parallel) |
batch | Process files in batches |
stomp | Allow file modifications during check |
Environment Variables
If tools are in a venv or non-standard location:
env {
["PATH"] = ".venv/bin:\(read("env:PATH"))"
}
Or better: install tools globally via brew/mise.
Troubleshooting
Tool not found
# Check if tool is in PATH which ruff # Install globally brew install ruff # or mise use -g ruff
Config validation failed
hk validate # Check Pkl syntax errors in output
Hooks not running
# Reinstall hooks hk install # Check hook files exist ls -la .git/hooks/pre-commit
Examples
User asks to add pre-commit hooks
- •Check project type (look for pyproject.toml, package.json, etc.)
- •Check what tools are available (
hk builtins,which ruff) - •Create appropriate hk.pkl
- •Run
hk validate && hk install - •Test with
hk check --all - •Fix issues with
hk fix --all
User has existing linter config
- •Read existing config (ruff.toml, .eslintrc, etc.)
- •Use matching hk builtins
- •Add any custom tools as custom steps