
Hk
- 54 installs
- 15 repo stars
- Updated August 1, 2026
- connorads/dotfiles
Sets up the hk git hook manager to run linters and formatters as parallel, staged-file-only pre-commit hooks configured in hk.pkl.
About
Sets up and maintains the hk git hook manager, which runs linters and formatters as pre-commit and commit-msg hooks with parallelism and staged-file-only operation. A developer uses it to add code-quality automation to a repository via hk.pkl.
- Runs linters and formatters as git hooks with built-in parallelism and file locking
- Config is in Pkl; composes tiered steps and wires mise.toml plus .hk-hooks
Hk by the numbers
- 54 all-time installs (skills.sh)
- Ranked #692 of 1,438 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/connorads/dotfiles --skill hkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 54 |
|---|---|
| repo stars | ★ 15 |
| Last updated | August 1, 2026 |
| Repository | connorads/dotfiles ↗ |
What it does
Sets up the hk git hook manager to run linters and formatters as parallel, staged-file-only pre-commit hooks configured in hk.pkl.
Files
hk — Git Hook Manager
hk by jdx runs linters and formatters as git hooks with built-in parallelism, file locking (no race conditions), and staged-file-only operation (no separate lint-staged needed). Config is in Pkl — Apple's typed configuration language.
Mental Model
Every hk setup is three steps: detect what the project has → compose steps from tiers → wire the hooks in.
detect project type + tools
↓
compose hk.pkl (tiered steps)
↓
wire: mise.toml + .hk-hooks/ + prepare scriptSetup Workflow
1. Detect
hk --version # get current version for amends URL
ls package.json go.mod Cargo.toml pyproject.toml flake.nix Makefile
cat mise.toml package.json # existing tools, package manager, scriptsIdentify:
- Language(s) and framework
- Package manager (pnpm/bun/npm/yarn for JS, cargo, go, pip, etc.)
- Formatter already configured (prettier, biome, ruff, gofmt…)
- Linter already configured (eslint, golangci-lint, ruff, clippy…)
- Test runner (vitest, jest, go test, cargo test, pytest…)
- Whether it's a team/shared repo (needs no-commit-to-branch)
2. Choose steps (tiered)
Tier 1 — Universal (always add):
| Step | Builtin |
|---|---|
| trailing-whitespace | Builtins.trailing_whitespace |
| newlines | Builtins.newlines |
| check-merge-conflict | Builtins.check_merge_conflict |
Tier 2 — Common tools (add if relevant):
| Step | Builtin | When |
|---|---|---|
| typos | Builtins.typos | Always (fast spell check) |
| gitleaks | custom | Always (secret detection) |
| rumdl | Builtins.rumdl | If *.md files exist |
Tier 3 — Language-specific (see references/builtins-by-language.md):
| Signal file | Steps to add |
|---|---|
package.json + biome.json/biome.jsonc | biome (or ultracite), eslint |
package.json (no biome) | prettier, eslint |
tsconfig.json | typecheck (tsc/tsgo/astro check/svelte-check) |
go.mod | go_fmt, go_vet, golangci_lint, gomod_tidy |
Cargo.toml | cargo_fmt, cargo_clippy |
pyproject.toml/requirements.txt | ruff (format+lint), mypy |
flake.nix/*.nix | nix_fmt (nixfmt), deadnix |
*.sh/*.zsh | shfmt, shellcheck |
Tier 4 — Project-specific (detect from config files):
| Signal | Step |
|---|---|
commitlint.config.* exists | commit-msg hook with commitlint |
.yamllint* exists | yamllint |
| Team/shared repo | no-commit-to-branch (pre-commit), no-push-to-branch (pre-push) |
| Test runner detected | test step(s) — vitest/jest/go test/cargo test/pytest |
3. Wire the hooks
Four files to create/update:
1. mise.toml — add hk, pkl, tool binaries 2. hk.pkl — configuration 3. scripts/quiet-on-success.sh — noise suppressor (copy from assets/quiet-on-success.sh in this skill) 4. .hk-hooks/pre-commit — tracked hook wrapper
Then:
chmod +x scripts/quiet-on-success.sh .hk-hooks/*
git config --local core.hooksPath .hk-hooksAnd add to package.json prepare script (JS projects):
"prepare": "[ -n \"$CI\" ] && exit 0 || command -v hk >/dev/null && (hk install 2>/dev/null || git config --local core.hooksPath .hk-hooks) || echo 'Note: hk not found, skipping git hooks. Install mise to enable.'"For non-JS projects, set core.hooksPath manually or via a Makefile setup target.
4. Validate
hk check --all # verify all steps pass on existing files
hk validate # verify hk.pkl is valid Pkl---
Preferred Patterns
hk.pkl global settings
Always use these at the top (after the amends/import lines):
exclude = List("node_modules", "dist", ".next", ".git") // add project-specific dirs
display_skip_reasons = List() // suppress skip noise
terminal_progress = false // cleaner outputAlways use these on the pre-commit hook:
["pre-commit"] {
fix = true // auto-fix and re-stage
stash = "git" // isolate staged changes
steps { ... }
}Binary file excludes
Always exclude binary/font files from trailing-whitespace, newlines, and typos:
local binary_excludes = List(
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico",
"*.woff", "*.woff2", "*.ttf", "*.eot", "*.pdf", "*.zip"
)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = binary_excludes
}The quiet-on-success wrapper
Wrap noisy commands so output only appears on failure:
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec tsc --noEmit"
}Copy assets/quiet-on-success.sh from this skill directory into scripts/ in the target repo.
The .hk-hooks/pre-commit wrapper
This is the file git actually executes. It's tracked in git (unlike .git/hooks/):
#!/bin/sh
# hk pre-commit hook — silent on success, minimal on failure
if [ -n "$CI" ]; then
exec hk run pre-commit "$@"
fi
output=$(hk run pre-commit "$@" 2>&1)
code=$?
[ $code -ne 0 ] && printf '%s\n' "$output"
exit $codeFor other hooks (commit-msg, pre-push), use simpler wrappers:
#!/bin/sh
exec hk run commit-msg "$@"#!/bin/sh
exec hk run pre-push "$@"---
Pkl Syntax Reference
Required first lines
amends "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Builtins.pkl"Always match the version in `amends` and `import` to the installed hk version (hk --version).
Builtin step (use as-is)
["trailing-whitespace"] = Builtins.trailing_whitespaceBuiltin step (with overrides)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = List("*.png", "*.jpg")
batch = true
}Custom step
["typecheck"] {
glob = List("*.ts", "*.tsx") // optional: only run when these files staged
check = "scripts/quiet-on-success.sh pnpm exec tsc --noEmit"
// fix = "command to auto-fix" // optional
}Template variables
| Variable | Value |
|---|---|
{{files}} | Space-separated list of staged files matching the step's glob |
{{commit_msg_file}} | Path to commit message file (commit-msg hook only) |
{{workspace}} | Directory containing workspace_indicator file |
{{workspace_files}} | Files relative to workspace directory |
Multi-line inline script
["no-commit-to-branch"] {
check = """
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
echo "Direct commits to '$branch' are not allowed."
exit 1
fi
"""
}Local variable (share steps across hooks)
local fast_steps = new Mapping<String, Step> {
["trailing-whitespace"] = Builtins.trailing_whitespace
["shfmt"] = (Builtins.shfmt) { batch = true }
}
hooks {
["pre-commit"] { fix = true; stash = "git"; steps = fast_steps }
["check"] { steps = fast_steps }
["fix"] { fix = true; stash = "git"; steps = fast_steps }
}Sequential ordering with Groups
Steps within a group run in parallel; groups run sequentially:
steps {
["format"] = new Group {
steps = new Mapping<String, Step> {
["prettier"] { ... }
["eslint"] { ... }
}
}
["validate"] = new Group { // runs after format completes
steps = new Mapping<String, Step> {
["typecheck"] { ... }
["test"] { ... }
}
}
}Or use depends for fine-grained ordering:
["eslint"] {
depends = List("prettier") // waits for prettier to finish
...
}---
mise.toml Additions
[tools]
hk = "latest"
pkl = "latest" # required for hk.pkl parsing
# Add as needed based on detected steps:
typos = "latest" # Tier 2: spell check
gitleaks = "latest" # Tier 2: secret detection
rumdl = "latest" # Tier 2: markdown lint (if .md files present)
yamllint = "latest" # Tier 4: YAML lint (if .yamllint* present)---
Maintenance
Add a new step
Insert into hk.pkl under the appropriate section. Check hk builtins for available built-ins, or write a custom step.
Update hk version
hk --version # check currentBump both URLs in hk.pkl:
amends "package://github.com/jdx/hk/releases/download/v1.37.0/hk@1.37.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.37.0/hk@1.37.0#/Builtins.pkl"Bypass hooks temporarily
HK=0 git commit -m "wip" # skip all hk hooks
HK_SKIP_STEPS=vitest git commit # skip specific stepDebug a failing step
hk check -v # verbose output
hk check -v --step typecheck # single step only
hk run pre-commit -v # simulate hook runLocal developer overrides
Create hk.local.pkl (gitignored) to override settings locally:
amends "./hk.pkl"
hooks {
["pre-commit"] {
steps {
["vitest"] {
check = "scripts/quiet-on-success.sh pnpm exec vitest run --testPathPattern=fast"
}
}
}
}---
Gotchas
| Issue | Fix |
|---|---|
pkl: command not found | Add pkl = "latest" to mise.toml, run mise install |
amends version mismatch | Match amends/import URL version to hk --version output |
| Builtins snake_case vs step names kebab-case | Builtins.trailing_whitespace → ["trailing-whitespace"] |
| Hook runs but matches nothing | Check glob patterns; use hk check -v to see file matching |
| Binary files fail spell check | Add binary excludes to typos/trailing-whitespace/newlines steps |
Git worktrees: hk install fails | Automatic since v1.35.0; if using older version use .hk-hooks/ + core.hooksPath |
| Fix auto-stages wrong files | Use explicit stage glob on the step, or ensure step glob covers fixed files |
| Noisy output on success | Wrap commands in scripts/quiet-on-success.sh |
| Hook runs in CI unnecessarily | Add [ -n "$CI" ] && exit 0 to prepare script |
hk.local.pkl uses amends not being honoured | First line must be amends "./hk.pkl" |
---
References
references/builtins-by-language.md— step selection by ecosystemreferences/complete-examples.md— full hk.pkl configs for different stacksassets/quiet-on-success.sh— copy intoscripts/in target repo- hk docs — official documentation
hk builtins— list all 90+ available built-in linters
#!/bin/sh
# Runs a command silently; only shows output on failure
output=$("$@" 2>&1)
code=$?
[ $code -ne 0 ] && printf '%s\n' "$output"
exit $code
hk Builtins by Language/Ecosystem
Reference for choosing steps when setting up hk in a new repo. Run hk builtins for the full list.
Universal (always add)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico",
"*.woff", "*.woff2", "*.ttf", "*.eot", "*.pdf", "*.zip")
}
["newlines"] = (Builtins.newlines) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico",
"*.woff", "*.woff2", "*.ttf", "*.eot", "*.pdf", "*.zip")
}
["check-merge-conflict"] = (Builtins.check_merge_conflict) {}Common Tools (add if relevant)
Spell checking (typos)
["typos"] = (Builtins.typos) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico",
"*.woff", "*.woff2", "*.ttf", "*.eot", "pnpm-lock.yaml",
"package-lock.json", "yarn.lock", "Cargo.lock", "go.sum")
}Configure locale in _typos.toml:
[default]
locale = "en-gb" # or "en-us"Secret detection (gitleaks)
["gitleaks"] {
check = "scripts/quiet-on-success.sh gitleaks detect --no-banner --redact --log-level=error"
}Requires gitleaks = "latest" in mise.toml.
Markdown linting (rumdl)
["rumdl"] = (Builtins.rumdl) {}Requires rumdl = "latest" in mise.toml. Configure in .rumdl.toml:
[default]
extend_rule_off = ["MD013", "MD033"] # disable line-length and inline HTML rules---
JavaScript / TypeScript
Formatter: Biome (signal: biome.json or biome.jsonc)
["biome"] {
glob = List("*.ts", "*.tsx", "*.js", "*.jsx", "*.json", "*.css")
check = "scripts/quiet-on-success.sh pnpm exec biome check {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec biome check --write {{files}}"
}Or via ultracite wrapper:
["biome"] {
glob = List("*.ts", "*.tsx", "*.js", "*.jsx", "*.json", "*.css")
check = "scripts/quiet-on-success.sh pnpm exec ultracite check --error-on-warnings=true {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec ultracite fix {{files}}"
}Formatter: Prettier (signal: .prettierrc* or no biome)
["prettier"] {
glob = List("*.ts", "*.tsx", "*.js", "*.mjs", "*.json", "*.css", "*.md", "*.mdx")
check = "scripts/quiet-on-success.sh pnpm exec prettier --check {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec prettier --write {{files}}"
}Add framework-specific globs as needed: "*.astro", "*.svelte", "*.vue".
Linter: ESLint (signal: eslint.config.*)
["eslint"] {
glob = List("*.ts", "*.tsx", "*.js", "*.mjs")
check = "scripts/quiet-on-success.sh pnpm exec eslint {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec eslint --fix {{files}}"
}Add "*.astro", "*.svelte", "*.vue" to glob if using those frameworks.
Type checking
Plain TypeScript (`tsconfig.json`):
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec tsc --noEmit"
}Native TS compiler preview (tsgo — faster):
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec tsgo --noEmit"
}Astro:
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec astro check"
}SvelteKit:
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec svelte-kit sync && pnpm exec svelte-check"
}Next.js / Vite: standard tsc usually sufficient.
Test runners
Vitest:
["vitest"] {
check = "scripts/quiet-on-success.sh pnpm exec vitest run"
}Jest:
["jest"] {
check = "scripts/quiet-on-success.sh pnpm exec jest --passWithNoTests"
}Note: E2E tests (Playwright, Cypress) should NOT be in pre-commit — they're too slow. Run them in CI only.
Commit message validation (signal: commitlint.config.*)
Add a commit-msg hook:
["commit-msg"] {
steps {
["commitlint"] {
check = "pnpm exec commitlint --edit {{commit_msg_file}}"
}
}
}Package manager detection
| Signal file | Package manager | Command prefix |
|---|---|---|
pnpm-lock.yaml | pnpm | pnpm exec |
bun.lock / bun.lockb | bun | bun x / bunx |
yarn.lock | yarn | yarn |
package-lock.json | npm | npx |
---
Go
["go-fmt"] = (Builtins.go_fmt) {}
["go-vet"] = (Builtins.go_vet) {}
["golangci-lint"] = (Builtins.golangci_lint) {}
["gomod-tidy"] = (Builtins.gomod_tidy) {}Tests:
["go-test"] {
check = "scripts/quiet-on-success.sh go test ./..."
}---
Rust
["cargo-fmt"] = (Builtins.cargo_fmt) {}
["cargo-clippy"] = (Builtins.cargo_clippy) {}Tests:
["cargo-test"] {
check = "scripts/quiet-on-success.sh cargo test"
}---
Python
Formatter + linter: Ruff (preferred, signal: ruff.toml or [tool.ruff] in pyproject.toml)
["ruff-format"] = (Builtins.ruff_format) {}
["ruff"] = (Builtins.ruff) {}Legacy: Black + Flake8
["black"] = (Builtins.black) {}
["flake8"] = (Builtins.flake8) {}Type checking
["mypy"] = (Builtins.mypy) { stomp = true }Tests:
["pytest"] {
check = "scripts/quiet-on-success.sh pytest"
}---
Nix
["nixfmt"] = (Builtins.nix_fmt) { batch = true }
["deadnix"] = (Builtins.deadnix) {}---
Shell
["shfmt"] = (Builtins.shfmt) { batch = true }
["shellcheck"] = (Builtins.shellcheck) { batch = true }Custom glob for specific shell file patterns:
["zsh-syntax"] {
glob = List(".zshrc", ".zprofile", ".config/zsh/functions/**")
check = "zsh -n {{files}}"
}---
YAML (signal: .yamllint*)
["yamllint"] = (Builtins.yamllint) {}Requires yamllint = "latest" in mise.toml. Configure in .yamllint.yaml:
extends: default
rules:
line-length:
max: 200
document-start: disable---
CSS
With Prettier (usually sufficient), or:
["stylelint"] = (Builtins.stylelint) {}---
Dockerfile (signal: Dockerfile*)
["hadolint"] = (Builtins.hadolint) {}---
Terraform / OpenTofu (signal: *.tf)
["hclfmt"] = (Builtins.hclfmt) {}
["tflint"] = (Builtins.tf_lint) {}---
GitHub Actions (signal: .github/workflows/*.yml)
["actionlint"] = (Builtins.actionlint) {}---
Team/Shared Repo Guards
Block direct commits/pushes to protected branches:
// In pre-commit steps:
["no-commit-to-branch"] {
check = """
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
echo "Direct commits to '$branch' are not allowed."
echo "Create a feature branch: git checkout -b feature/my-change"
exit 1
fi
"""
}// In pre-push hook:
["pre-push"] {
steps {
["no-push-to-branch"] {
check = """
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
echo "Direct pushes to '$branch' are not allowed."
exit 1
fi
"""
}
}
}Complete hk.pkl Examples
Real configurations for different tech stacks. Bump the version in the amends/import URLs to match hk --version.
---
Astro + Preact + Tailwind + pnpm
9 pre-commit steps. Simple setup — no commit-msg or pre-push hooks needed.
// hk configuration - https://hk.jdx.dev/
amends "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Builtins.pkl"
exclude = List("node_modules", "dist", ".wrangler")
display_skip_reasons = List()
terminal_progress = false
hooks {
["pre-commit"] {
fix = true
stash = "git"
steps {
// Formatting (auto-fixed and staged)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico", "*.woff", "*.woff2", "*.ttf", "*.eot")
}
["newlines"] = (Builtins.newlines) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico", "*.woff", "*.woff2", "*.ttf", "*.eot")
}
["typos"] = (Builtins.typos) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico", "*.woff", "*.woff2", "*.ttf", "*.eot", "pnpm-lock.yaml")
}
["prettier"] {
glob = List("*.ts", "*.tsx", "*.js", "*.mjs", "*.json", "*.css", "*.astro", "*.md", "*.mdx")
check = "scripts/quiet-on-success.sh pnpm exec prettier --check {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec prettier --write {{files}}"
}
["eslint"] {
glob = List("*.ts", "*.tsx", "*.js", "*.mjs", "*.astro")
check = "scripts/quiet-on-success.sh pnpm exec eslint {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec eslint --fix {{files}}"
}
// Validation
["check-merge-conflict"] = (Builtins.check_merge_conflict) {}
["gitleaks"] {
check = "scripts/quiet-on-success.sh gitleaks detect --no-banner --redact --log-level=error"
}
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec astro check"
}
["vitest"] {
check = "scripts/quiet-on-success.sh pnpm exec vitest run"
}
}
}
}mise.toml additions:
[tools]
hk = "latest"
pkl = "latest"
typos = "latest"
gitleaks = "latest"---
Payload CMS + Next.js 15 + Biome + pnpm
16 pre-commit steps + commit-msg + pre-push hooks. Comprehensive setup for a team repo.
// hk configuration - https://hk.jdx.dev/
amends "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Builtins.pkl"
exclude = List("node_modules", "dist", ".next", ".open-next", "storybook-static")
display_skip_reasons = List()
terminal_progress = false
hooks {
["pre-commit"] {
fix = true
stash = "git"
steps {
// Formatting (auto-fixed and staged)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico", "*.woff", "*.woff2", "*.ttf", "*.eot")
}
["newlines"] = (Builtins.newlines) {
exclude = List("src/payload-types.ts", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico", "*.woff", "*.woff2", "*.ttf", "*.eot")
}
["rumdl"] = (Builtins.rumdl) {}
["typos"] = (Builtins.typos) {
exclude = List("*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico", "*.woff", "*.woff2", "*.ttf", "*.eot", "pnpm-lock.yaml")
}
["biome"] {
glob = List("*.ts", "*.tsx", "*.js", "*.jsx", "*.json", "*.css")
exclude = List(".vscode/*")
check = "scripts/quiet-on-success.sh pnpm exec ultracite check --error-on-warnings=true {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec ultracite fix {{files}}"
}
["eslint"] {
glob = List("*.ts", "*.tsx", "*.js", "*.jsx")
check = "scripts/quiet-on-success.sh pnpm exec eslint {{files}}"
fix = "scripts/quiet-on-success.sh pnpm exec eslint --fix {{files}}"
}
// Validation
["yamllint"] = (Builtins.yamllint) {}
["check-merge-conflict"] = (Builtins.check_merge_conflict) {}
["no-commit-to-branch"] {
check = """
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
echo "Direct commits to '$branch' are not allowed."
echo ""
echo "Please create a feature branch and open a pull request:"
echo " git checkout -b feature/my-change"
echo " git commit"
echo " git push -u origin feature/my-change"
echo " gh pr create"
exit 1
fi
"""
}
["gitleaks"] {
check = "scripts/quiet-on-success.sh gitleaks detect --no-banner --redact --log-level=error"
}
["check-migrations"] {
glob = List("src/collections/*", "src/blocks/*", "src/globals/*", "payload.config.ts")
check = "scripts/quiet-on-success.sh pnpm check:migrations"
}
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm typecheck:fast"
}
// Tests
["test-unit"] {
check = "scripts/quiet-on-success.sh pnpm test:unit:coverage"
}
["test-int"] {
check = "scripts/quiet-on-success.sh pnpm test:int:coverage"
}
["test-components"] {
check = "scripts/quiet-on-success.sh pnpm test:components"
}
["lint-stories"] {
check = "scripts/quiet-on-success.sh pnpm lint:stories"
}
["test-storybook"] {
check = "scripts/quiet-on-success.sh pnpm test:storybook:ci"
}
}
}
["commit-msg"] {
steps {
["commitlint"] {
check = "pnpm exec commitlint --edit {{commit_msg_file}}"
}
}
}
["pre-push"] {
steps {
["no-push-to-branch"] {
check = """
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
echo "Direct pushes to '$branch' are not allowed."
echo ""
echo "Please create a feature branch and open a pull request:"
echo " git checkout -b feature/my-change"
echo " git push -u origin feature/my-change"
echo " gh pr create"
exit 1
fi
"""
}
}
}
}mise.toml additions:
[tools]
hk = "latest"
pkl = "latest"
typos = "latest"
gitleaks = "latest"
rumdl = "latest"
yamllint = "latest"---
Dotfiles (Shell + Nix)
No package.json. Uses local variable to share steps across pre-commit/fix/check hooks. No JS tools — focused on shell, nix, and markdown.
// Dotfiles hk configuration - fast pre-commit checks for staged files.
amends "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Builtins.pkl"
exclude = List(".git", "git", "node_modules", ".cache", ".local", ".npm", ".cargo", ".rustup", ".vscode-server")
display_skip_reasons = List()
terminal_progress = false
local fast_steps = new Mapping<String, Step> {
["trailing-whitespace"] = (Builtins.trailing_whitespace) {}
["newlines"] = (Builtins.newlines) {}
["check-merge-conflict"] = (Builtins.check_merge_conflict) {}
["shfmt"] = (Builtins.shfmt) {
batch = true
}
["shellcheck"] = (Builtins.shellcheck) {
batch = true
}
["zsh-syntax"] {
glob = List(".zshrc", ".zprofile", ".zshenv", ".config/zsh/functions/**")
check = "zsh -n {{files}}"
}
["nixfmt"] = (Builtins.nix_fmt) {
batch = true
}
["rumdl"] = (Builtins.rumdl) {
batch = true
}
["mise"] = Builtins.mise
}
hooks {
["pre-commit"] {
fix = true
stash = "git"
steps = fast_steps
}
["fix"] {
fix = true
stash = "git"
steps = fast_steps
}
["check"] {
steps = fast_steps
}
}mise.toml additions:
[tools]
hk = "latest"
pkl = "latest"
rumdl = "latest"Installation (no prepare script — set manually once):
git config --local core.hooksPath .hk-hooks---
Go Service
amends "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Builtins.pkl"
display_skip_reasons = List()
terminal_progress = false
hooks {
["pre-commit"] {
fix = true
stash = "git"
steps {
["trailing-whitespace"] = (Builtins.trailing_whitespace) {}
["newlines"] = (Builtins.newlines) {}
["check-merge-conflict"] = (Builtins.check_merge_conflict) {}
["typos"] = (Builtins.typos) {
exclude = List("go.sum")
}
["gitleaks"] {
check = "scripts/quiet-on-success.sh gitleaks detect --no-banner --redact --log-level=error"
}
["go-fmt"] = (Builtins.go_fmt) {}
["go-vet"] = (Builtins.go_vet) {}
["golangci-lint"] = (Builtins.golangci_lint) {}
["go-test"] {
check = "scripts/quiet-on-success.sh go test ./..."
}
}
}
}mise.toml additions:
[tools]
hk = "latest"
pkl = "latest"
typos = "latest"
gitleaks = "latest"---
Python (ruff + mypy)
amends "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v1.36.0/hk@1.36.0#/Builtins.pkl"
exclude = List(".venv", "__pycache__", ".mypy_cache", ".ruff_cache", "dist")
display_skip_reasons = List()
terminal_progress = false
hooks {
["pre-commit"] {
fix = true
stash = "git"
steps {
["trailing-whitespace"] = (Builtins.trailing_whitespace) {}
["newlines"] = (Builtins.newlines) {}
["check-merge-conflict"] = (Builtins.check_merge_conflict) {}
["typos"] = (Builtins.typos) {}
["gitleaks"] {
check = "scripts/quiet-on-success.sh gitleaks detect --no-banner --redact --log-level=error"
}
["ruff-format"] = (Builtins.ruff_format) {}
["ruff"] = (Builtins.ruff) {}
["mypy"] = (Builtins.mypy) { stomp = true }
["pytest"] {
check = "scripts/quiet-on-success.sh pytest"
}
}
}
}mise.toml additions:
[tools]
hk = "latest"
pkl = "latest"
typos = "latest"
gitleaks = "latest"