Custom steps
A step can invoke any shell command. Define which files it uses, how to check them without writes, and how to apply fixes.
This example manually defines a whitespace step using an hk utility. It needs only hk, and includes tests you can run before adding the step to your workflow.
Download custom-linters.pkl and save it as hk.pkl.
Configuration
/// A custom step using hk's whitespace utility, including check and fix tests.
amends "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.58.1/hk@1.58.1#/Builtins.pkl"
local linters = new Mapping<String, Step> {
["whitespace"] {
glob = List("*.txt", "*.md")
check = "hk util trailing-whitespace {{files}}"
fix = "hk util trailing-whitespace --fix {{files}}"
tests {
["accepts clean text"] {
run = "check"
files = List("{{tmp}}/clean.txt")
write {
["{{tmp}}/clean.txt"] = "hello\n"
}
expect { code = 0 }
}
["removes trailing spaces"] {
run = "fix"
files = List("{{tmp}}/dirty.txt")
write {
["{{tmp}}/dirty.txt"] = "hello \n"
}
expect {
files {
["{{tmp}}/dirty.txt"] = "hello\n"
}
}
}
}
}
["newlines"] = Builtins.newlines
}
hooks {
["pre-commit"] {
fix = true
stash = "git"
steps = linters
}
["check"] { steps = linters }
["fix"] {
fix = true
steps = linters
}
}Test the step
hk validate
hk test --step whitespace
hk check --all --planEach test writes a file in a temporary sandbox. One expects a clean check to succeed; the other checks the exact content after fixing. The files list explicitly selects the sandbox paths passed to each command.
Use this pattern when adding a custom linter or contributing a builtin.
Add a condition
Conditions use expression syntax. To invoke a shell test, wrap it in exec:
condition = "exec('test -f .lint-enabled')"This fragment assumes a POSIX shell. condition is evaluated for each job; use step_condition to evaluate once for the step.
Use platform-specific commands
For a project that provides both shell and PowerShell check scripts, define a Script:
check = new Script {
linux = "sh scripts/check.sh"
macos = "sh scripts/check.sh"
windows = "pwsh -NoProfile -File scripts/check.ps1"
}These scripts are project-owned placeholders: create them before using the fragment. They must leave files unchanged when used as a check.
Add optimizations when supported
check_list_filesreports only the files that need fixing.check_diffemits a unified diff that hk can apply.batch = truelets hk divide files among jobs when the tool supports independent subsets.workspace_indicatorruns commands for matching projects.
Keep the step’s selected files consistent with everything the command can modify. For broader effects, use dependencies or exclusive = true. See the configuration reference.
