Fix steps now run their fixer directly instead of checking first, and batch steps start fewer processes on small commits. Together these make hk run pre-commit about a third faster on a typical fixture. This release also adds JUnit XML output, hook arguments in conditions, and a hk migrate pre-commit that produces a working config right away. It fixes a staging race between concurrent fixers and a stale config cache when Pkl reads environment variables.
Added
-
--junit-xml <PATH>forhk check,hk fixandhk run <hook>(#1432, @sanga). Writes one JUnit<testcase>per step, with its status, duration and captured output, so CI systems such as Jenkins can show hk results. It works alongside--sarif, whatever output format you choose. If a run fails before any step is planned, the report still contains a failing test case, so it doesn't look like a passing build. -
Git hook arguments in
conditionandstep_condition(#1445, @jdx). Conditions can now use the hook variables that command templates already had:prev_head,new_headandis_branch_checkout(post-checkout),commit_msg_file(commit-msg,prepare-commit-msg),sourceandsha(prepare-commit-msg),hook_stdin(pre-push,post-rewrite), andhook_args(all hooks). Conditions are expr-lang expressions, so writeis_branch_checkout, not{{ is_branch_checkout }}.hooks { ["post-checkout"] { steps { ["install-deps"] { step_condition = "is_branch_checkout" check = "mise install" } } } }
-
hk migrate pre-commitproduces a working config (#1459, @jdx).- Hooks from 34 well-known repos (66 hook ids) become hk builtins, unless they set
args,files,typesor other options the builtin can't reproduce. - Local
system,scriptandfailhooks become native steps with their command, filters and stages kept. - Every other hook becomes a
precommit("id", "stage")step that runs through prek or pre-commit, with a comment explaining why it wasn't converted. Pick the tool with the new--runner prek|pre-commitflag. The default is prek, unless only pre-commit is on PATH. - Hooks go into the matching git stage.
manualhooks run only withhk checkandhk fix. - Pre-commit's top-level
excludeandfail_fastsettings are honored. - The legacy
commitstage now maps topre-commitinstead ofcommit-msg.
Keep
.pre-commit-config.yamluntil every delegated step is converted, because those steps still read it. Delegated steps use the same command for check and fix, sohk checkcan modify files for them, just aspre-commit rundoes. The migration no longer clones hook repos into.hk/vendors/. - Hooks from 34 well-known repos (66 hook ids) become hk builtins, unless they set
-
New builtins
Builtins.renovate_depsandBuiltins.lychee_extended(#1448, #1449, @zeitlinger). Both run Flint's checkers on the files hk selects.renovate_depschecks the Renovate dependency snapshot (.github/renovate-tracked-deps.json) and regenerates it in fix mode. It requiresflintandrenovate.lychee_extendedchecks links in text files and remaps links to the same repository in PRs. It requiresflintandlychee. -
svelte,vueandastrofile types (#1428, @nettlesh)..svelte,.vueand.astrofiles are now taggedtextplus their own type, so steps can select them withtypes = List("svelte", "vue")instead of globs. These tags don't implyhtml,javascriptortypescript. -
hk initfinds source files in subdirectories (#1403, @zeitlinger). Detection respects.gitignore(or.ignoreoutside git), includes hidden files and doesn't follow symlinks. For example, nested shell scripts andBUILD.bazelfiles now enable the matching builtins. Manifest and config indicators such asCargo.tomlstill count only at the repository root. -
hk initdetects native Biome and ESLint config files (#1402, @zeitlinger), includingbiome.jsonc, flateslint.config.*and legacy.eslintrc*. Projects no longer need matching package metadata to get these steps. -
cargo-fmtbuiltin watchesrustfmt.tomland.rustfmt.toml(#1446, @sanga), so changing rustfmt settings reruns the step.
Changed
-
check_firstnow defaults tofalse(#1461, @jdx). Before, when several fixers wrote the same files, each ran itscheckand then ranfixonly if the check failed. Files that needed fixing were processed twice (eslint, theneslint --fix). Steps now runfixdirectly. Setcheck_first = trueon a slow fixer whose files usually pass already. hk still checks first on its own in these cases:- steps whose
checkandfixare the same command (pre-commit-style fixers). A single fixer like this now passes after fixing, even when no other step touches its files. check_diffsteps in fix mode.check_difforcheck_list_filessteps in hooks that stage fixes. These fix and stage only the files that need it.
On a benchmark with about 6,000 files and ten fixers, together with the batching change below,
hk run pre-commitdropped from 1,442 ms to 965 ms andhk fix --allfrom 5,959 ms to 4,568 ms. - steps whose
-
Batch steps run at least 4 files per process (#1458, @jdx). hk used to split files into
files / jobschunks, which on small commits meant one process per file. It now makesmin(jobs, files / 4)batches and splits files evenly across them, like pre-commit and prek. For example, 5 files on 8 jobs run as a single process. Large file sets are split the same way as before. -
Faster
black,jqandyqbuiltins (#1454, @jdx).blackno longer passes--diff, which bypassed black's cache. On about 4,000 formatted files the step went from roughly 7–9 s to 0.8 s. Failures now show black'swould reformat <path>lines instead of a diff.jqandyqnow setbatch = true, so their files are split across jobs. -
Top-level
excludefrom user config and project config now combine (#1460, @jdx). If~/.config/hk/config.pklandhk.pklboth setexclude, the project value used to replace the user value, which contradicted the docs. Both now apply.
Fixed
- Staging no longer fails or stages half-written files when fixers overlap (#1453, @jdx). When several fixers rewrote the same files in parallel,
hk run pre-commitcould fail with errors likefailed to add files to index … file changed before we could read it,index.lock: File exists, orcould not find '…' to open, and could stage partial content. Staging now waits until every step writing those files is done. - Config cache follows environment variables read by Pkl (#1450, @christopher-buss). A config that branched on
read?("env:..."), such as settingprofileswhenCLAUDECODEis set, kept whatever result the first run cached. The cache is now keyed on the value of every environment variable the evaluation read, including unset ones. The first run after upgrading misses the cache. - A
Regexfor top-levelexcludenow loads (#1460, @jdx).exclude = Regex(#"^vendor/|\.gen\."#)used to fail withdata did not match any variant of untagged enum StringOrList. It now works like a step-level regex exclude. An invalid pattern failshk validate. Top-level excludes also now apply to file arguments written as absolute paths or with..segments. yqfix no longer merges several files into the first one (#1452, @jdx).yq -iP a.yml b.ymlwrites both documents intoa.ymland leavesb.ymlunchanged. The builtin now fixes each file separately.check_list_filesworks with a step'sdir(#1454, @jdx). Paths printed relative todirused to match nothing, and the fix aborted withcheck_list_files failed with no files in output. This affects prettier, gofmt, black and any other step usingcheck_list_files.- Templates and XML files with unknown extensions count as text (#1427, @nettlesh). Files starting with an HTML-like tag (such as
script.tmpl) or an XML declaration (such asApp.csproj) were taggedbinary, sotypes = List("text")steps skipped them. They are now taggedtext, and XML files also getxml. - Closed output pipes no longer crash hk (#1442, @jdx). Commands like
hk --version | true, a pager you quit early, or a cancelled shell completion used to panic with SIGABRT and leave a core dump. hk now exits like a program killed by SIGPIPE (status 141), with no panic output. hk init --misekeeps an existingmise.toml(#1404, @zeitlinger). It now adds only missinghkandpre-commitentries and keeps existing pins, comments, tools and task definitions. It doesn't add apre-committask when an included task file already defines one.--forcenow affects onlyhk.pkl.
New Contributors
- @sanga made their first contribution in #1432
- @christopher-buss made their first contribution in #1450
Full Changelog: v2.1.0...v2.2.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.