name: hk description: Configure, migrate, debug, or write hk git-hook manager configs with Pkl, especially when replacing pre-commit or mise hook tasks. Use when working with hk.pkl, hk install/run/check/fix/validate, Pkl hook config, staged-file linting, autofix hooks, or hk + mise integration.
hk
Use hk as the git-hook/file-selection layer and keep project task definitions as the command source of truth when they already exist.
Core Rules
- Prefer
hk.pklfor hook orchestration; do not keep old.git/hooks/pre-commitshims or duplicate pre-commit/mise wrappers once hk owns the hook. - Prefer global mise tools/config (
~/.config/mise/config.toml) over adding project-local[tools], unless the project needs pinned versions. - When existing mise tasks already define lint/fix commands, call those tasks from
hk.pklinstead of duplicating command lines. - If no project task exists for a tool, prefer hk
Builtinsbefore inventing custom shell snippets; builtins are transparent Pkl configs, not downloaded hook repos. - Keep Pkl readable: inline values used once; only introduce
localvariables for reused mappings or genuinely shared values. Name reusable linter mappingslinters. - Keep
checkcommands read-only and put mutations infix; hk parallelizes checks and uses locks around fixes. - Prefer autofix in
pre-commitwhen requested:fix = true,stash = "git", and let hk stage fixed files by default.
Workflow
Gather current hook/task context:
find . -maxdepth 3 \( -name 'mise.toml' -o -name '.mise.toml' -o -name 'hk.pkl' -o -name 'hk.local.pkl' -o -name '.pre-commit-config.yaml' \) -print git config --show-origin --get-regexp '^hook\.|^hk\.' || true ls -la .git/hooks 2>/dev/null || trueIf using mise integration, ensure global mise has hk support:
[env] HK_MISE = "1" [tools] hk = "latest" pkl = "latest"pklis needed for hk's default Pkl evaluator.HK_PKL_BACKEND=pklrcan avoid the CLI, but the Pkl CLI is the stable default.Install hk hooks globally unless the user explicitly wants repo-local hooks:
hk install --global --miseGlobal hooks no-op in repos without
hk.pkl. Avoid installing both global and local hk hooks because Git can run both.Write
hk.pklwith one conciselintersmapping and hook blocks:amends "package://github.com/jdx/hk/releases/download/v1.46.0/hk@1.46.0#/Config.pkl" local linters = new Mapping<String, Step> { ["ruby"] { glob = "**/*.rb" check = "mise run rb:check -- {{ files }}" fix = "mise run rb:fix -- {{ files }}" } ["javascript"] { glob = List( "app/javascript/**/*.js", "app/javascript/**/*.ts", "app/javascript/**/*.svelte", ) check = "mise run js:check -- {{ files }}" fix = "mise run js:fix -- {{ files }}" batch = true } } hooks { ["pre-commit"] { fix = true stash = "git" steps = linters } ["check"] { steps = linters } ["fix"] { fix = true steps = linters } }If no existing task owns a command, import and customize builtins instead:
import "package://github.com/jdx/hk/releases/download/v1.46.0/hk@1.46.0#/Builtins.pkl" local linters = new Mapping<String, Step> { ["prettier"] = (Builtins.prettier) { glob = List("app/javascript/**/*.js", "app/javascript/**/*.ts") batch = true } }If hk calls mise tasks with file args, make those tasks file-aware using mise's
usagefield:[tasks."js:check"] usage = 'arg "[files]" var=#true' run = "pnpm exec eslint --quiet ${usage_files:-app/javascript}"arg "[files]" var=#truemeans optional variadic positional args. Mise exposes them as$usage_files.Remove old hook entry points after hk is installed:
rm -f .git/hooks/pre-commit .git/hooks/pre-commit.oldAlso remove obsolete
mise run pre-committasks/wrappers when the user does not want them.Validate and inspect the plan:
hk validate --no-progress hk run pre-commit --plan --no-progress hk check --all --plan --no-progressUse targeted plan checks to confirm globs:
hk check --plan --no-progress path/to/file.rb hk fix --plan --no-progress path/to/file.js
Pkl Style Preferences
Good: inline one-use globs and use linters for reused steps.
local linters = new Mapping<String, Step> {
["erb"] {
glob = "**/*.erb"
check = "mise run erb:check -- {{ files }}"
}
}
Bad: create one-use variables and overly specific names.
local erb_files = List("**/*.erb")
local pre_commit_steps = new Mapping<String, Step> {
["erb"] { glob = erb_files }
}
Use local for reused maps (linters) or values referenced by multiple steps. Otherwise inline for scanability.
Useful Docs
- hk getting started:
https://hk.jdx.dev/getting_started.html - hk configuration:
https://hk.jdx.dev/configuration.html - hk examples:
https://hk.jdx.dev/reference/examples/ - hk mise integration:
https://hk.jdx.dev/mise_integration.html - hk Pkl intro:
https://hk.jdx.dev/pkl_introduction.html - mise task arguments:
https://mise.jdx.dev/tasks/task-arguments.html