hk 2.0 simplifies configuration around a single shared steps map, stages fixed files only in pre-commit by default, turns builtin variants into typed options, and removes the deprecated v1 configuration files and entry points. Upgrading requires reviewing your hk.pkl; see Breaking Changes below and the v2 migration guide.
Highlights
- Less repetition in
hk.pkl: a top-levelstepsblock now defines thecheck,fix, andpre-commithooks at once, and builtin variants such asgitleaks_stagedorpinact_v3are replaced by options on the primary builtin (#1255, #1253). - Predictable staging: only
pre-commitstages fixes automatically;hk fixand custom hooks leave the index alone unless you ask (#1256). - Pkl only:
hk.toml/yaml/json,.hkrc.pkl,UserConfig.pkl,hk generate, and the externalpklCLI fallback are gone; hk reports a targeted replacement for each removed input (#1257). - Nine new builtins for Terraform, Terragrunt, Kubernetes, Markdown, and repository hygiene, plus fixes to
rubocop,tf_lint, andzizmor.
Breaking Changes
Shared top-level steps and hook defaults (#1255, @jdx)
A top-level steps map materializes implicit check, fix, and pre-commit hooks, so the common "define linters once, wire them into three hooks" pattern collapses to:
amends "package://github.com/jdx/hk/releases/download/v2.0.0/hk@2.0.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v2.0.0/hk@2.0.0#/Builtins.pkl"
steps {
["prettier"] = Builtins.prettier
["eslint"] = Builtins.eslint
}Defaults: check runs checks without fixing or staging, fix applies fixes without staging, and pre-commit fixes, stages, and uses Git stashing. No implicit pre-push is created. An explicit hook with one of these names keeps its own hook-level settings and replaces same-named inherited steps; the remaining shared steps still apply. Hooks gain enabled = false to skip a hook at run time and omit it from hk install. hk init and hk migrate pre-commit now emit this shorter form. In monorepos, subproject configs contribute steps only; explicit hook-level settings in a subproject are ignored with a debug message.
Staging is contextual (#1256, @jdx)
Previously fixed files were staged for every hook. Now only pre-commit stages by default; hk fix, check, and custom hooks leave the index untouched unless stage = true is set on the hook or --stage / HK_STAGE / git config hk.stage is supplied. Precedence is CLI and settings, then the hook's stage, then the hook-name default. A step's stage patterns are now pure path filters: they narrow what gets git added once hook-level staging is on, and never enable staging by themselves. Scripts that relied on hk fix staging its changes should pass --stage.
Builtins are flat, configurable steps (#1253, @jdx)
Every builtin is a plain Config.Step; default references such as ["prettier"] = Builtins.prettier are unchanged. The separate variant exports are removed in favor of typed options amended on the primary builtin:
| Removed | Replacement |
|---|---|
Builtins.gitleaks_staged
| (Builtins.gitleaks) { scan = "staged" }
|
Builtins.knip_strict
| (Builtins.knip) { strict = true }
|
Builtins.pinact_v3, Builtins.pinact_update_v3
| (Builtins.pinact) { version = "3" }, (Builtins.pinact_update) { version = "3" }
|
Builtins.check_byte_order_marker, Builtins.fix_byte_order_marker
| Builtins.byte_order_marker
|
Generic step customization (batch, glob, depends, ...) goes in the same amended object. hk's embedded evaluator is upgraded to pklr 2.0.1.
Deprecated v1 interfaces removed (#1257, @jdx)
hk now loads Pkl configuration only. Removed inputs fail with an error that names the replacement and links to the migration guide:
| Removed in v2 | Replacement |
|---|---|
hk.toml, hk.yaml, hk.yml, hk.json
| hk.pkl amending Config.pkl
|
project .hkrc.pkl
| hk.local.pkl
|
home ~/.hkrc.pkl, --hkrc <PATH>
| ~/.config/hk/config.pkl
|
UserConfig.pkl (and its environment { }, defaults { })
| Config.pkl with env { } and top-level settings
|
Types.Regex(...), Config.Regex(...)
| Pkl's built-in Regex(...)
|
hk generate
| hk init
|
HK_PKL_BACKEND=pkl
| remove it; pklr remains an accepted no-op
|
.config/hk.pkl, .config/hk.local.pkl, and XDG config.pkl continue to work. Global XDG hooks merge additively into project hooks (adding env, a missing report, and non-conflicting steps) without overriding project execution settings. The hk runtime no longer invokes the pkl CLI, directly or through mise; the built-in pklr evaluator is always used.
Added
- Terraform and Terragrunt builtins (@sassdavid) #1362, #1363.
terraform_docs(checks with--output-check, writes and stages the README on fix) andterraform_validate(runsterraform init -backend=falseonly when validation fails) operate per module directory derived from the selected files.terragrunt_hcl_fmtandterragrunt_hcl_validaterun from the working directory sinceterragrunt hcl fmt --fileaccepts one path.hk migrate pre-commitmapsterraform_docs,terraform_validate, andterragrunt_fmt. kubeconformbuiltin (@sassdavid) #1363. Validates Kubernetes manifests; it globs all YAML, so narrowglobto your manifest paths if the repository holds unrelated YAML.madoMarkdown linter (@hituzi-no-sippo) #1349. Check-only builtin for the Rust CommonMark/GFM linter.- Repository hygiene checks (@sassdavid) #1360. New
destroyed_symlinks(symlink in HEAD staged as a regular file),forbid_submodules(gitlinks in the index), andcheck_shebang_scripts_are_executablebuiltins, each also available ashk util <name>.hk migrate pre-commitmaps the matching pre-commit ids, includingforbid-new-submodules. - Release archives ship agent skills and completions (@jdx) #1337, #1336. Every release archive includes
hk-configureandhk-debugskills underskills/, and native bash, zsh, fish, and PowerShell completion scripts are published as release assets. Both are declared in the signed packslip manifest so mise-compatible installers can use them withoutusageor a separate setup step.
Fixed
rubocopandrubocop_servernow fix and block offenses (@andyw8) #1355.check_list_filesused--list-target-files, which always exits 0, so hk skipped the check/fix commands. It now usesrubocop --format files.tf_lintlints every module directory (@sassdavid) #1361. tflint was invoked without arguments and only linted the current directory. It now runs--chdirfor each directory derived from the selected files, treats tflint's exit 2 after a successful fix as success, accepts all extensions tflint supports, and excludes.terraform.zizmorembedded tests pass with hash pinning (@nettlesh) #1366. Projects usingBuiltins.hk_testwithBuiltins.zizmorno longer see five fixture failures on zizmor 1.20+, which requires hash-pinned first-party actions.- Fail-fast status reporting (#1253). Steps are marked aborted before the run is cancelled, and structured output reports cancelled before failed when both apply.
Documentation
- Rewritten guides and a redesigned documentation site with a new landing page, reorganized navigation, a CI guide, downloadable example configurations, and richer generated CLI reference pages (@jdx) #1340.
- Documented
{% raw %}...{% endraw %}for step commands whose tool syntax uses{{, such as kubeconform's-schema-location(@sassdavid) #1359.
New Contributors
- @sassdavid made their first contribution in #1362
- @nettlesh made their first contribution in #1366
Full Changelog: v1.58.1...v2.0.0
💚 Sponsor hk
hk is built and maintained by @jdx, an open source developer at entire.io, the title sponsor of his open source work.
If hk speeds up your pre-commit loop or makes linting less painful, please consider becoming an individual or company sponsor. Your support funds ongoing development and helps keep hk fast, free, and independent.