hk
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesehk — Git Hook Manager
hk — Git Hook 管理器
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.
由jdx开发的hk可以作为git hooks运行linter和格式化工具,具备内置并行能力、文件锁(无竞态条件)和仅处理暂存区文件的特性(无需额外安装lint-staged)。配置文件采用Pkl编写——这是苹果推出的类型化配置语言。
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 script每个hk设置都分为三个步骤:检测项目现有环境 → 从不同层级组合执行步骤 → 绑定对应钩子。
detect project type + tools
↓
compose hk.pkl (tiered steps)
↓
wire: mise.toml + .hk-hooks/ + prepare scriptSetup Workflow
安装工作流
1. Detect
1. 检测
bash
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)
bash
hk --version # 获取当前版本用于更新amends地址
ls package.json go.mod Cargo.toml pyproject.toml flake.nix Makefile
cat mise.toml package.json # 查看现有工具、包管理器、脚本配置确认以下信息:
- 使用的编程语言和框架
- 包管理器(JS生态的pnpm/bun/npm/yarn,cargo、go、pip等)
- 已配置的格式化工具(prettier、biome、ruff、gofmt…)
- 已配置的linter(eslint、golangci-lint、ruff、clippy…)
- 测试运行器(vitest、jest、go test、cargo test、pytest…)
- 是否为团队/共享仓库(需要配置禁止直接提交到主干分支的规则)
2. Choose steps (tiered)
2. 选择执行步骤(分层级)
Tier 1 — Universal (always add):
| Step | Builtin |
|---|---|
| trailing-whitespace | |
| newlines | |
| check-merge-conflict | |
Tier 2 — Common tools (add if relevant):
| Step | Builtin | When |
|---|---|---|
| typos | | Always (fast spell check) |
| gitleaks | custom | Always (secret detection) |
| rumdl | | If |
Tier 3 — Language-specific (see ):
references/builtins-by-language.md| Signal file | Steps to add |
|---|---|
| biome (or ultracite), eslint |
| prettier, eslint |
| typecheck (tsc/tsgo/astro check/svelte-check) |
| go_fmt, go_vet, golangci_lint, gomod_tidy |
| cargo_fmt, cargo_clippy |
| ruff (format+lint), mypy |
| nix_fmt (nixfmt), deadnix |
| shfmt, shellcheck |
Tier 4 — Project-specific (detect from config files):
| Signal | Step |
|---|---|
| commit-msg hook with commitlint |
| 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 |
层级1 — 通用规则(始终添加):
| 步骤 | 内置方法 |
|---|---|
| trailing-whitespace | |
| newlines | |
| check-merge-conflict | |
层级2 — 通用工具(符合场景则添加):
| 步骤 | 内置方法 | 适用场景 |
|---|---|---|
| typos | | 所有场景(快速拼写检查) |
| gitleaks | 自定义 | 所有场景(密钥检测) |
| rumdl | | 存在 |
层级3 — 语言专属规则(参考):
references/builtins-by-language.md| 标识文件 | 需添加的步骤 |
|---|---|
| biome(或ultracite)、eslint |
| prettier、eslint |
| typecheck(tsc/tsgo/astro check/svelte-check) |
| go_fmt、go_vet、golangci_lint、gomod_tidy |
| cargo_fmt、cargo_clippy |
| ruff(格式化+lint)、mypy |
| nix_fmt(nixfmt)、deadnix |
| shfmt、shellcheck |
层级4 — 项目专属规则(从配置文件检测):
| 标识 | 步骤 |
|---|---|
存在 | 搭配commitlint的commit-msg钩子 |
存在 | yamllint |
| 团队/共享仓库 | no-commit-to-branch(pre-commit阶段)、no-push-to-branch(pre-push阶段) |
| 检测到测试运行器 | 测试步骤 — vitest/jest/go test/cargo test/pytest |
3. Wire the hooks
3. 绑定钩子
Four files to create/update:
- — add hk, pkl, tool binaries
mise.toml - — configuration
hk.pkl - — noise suppressor (copy from
scripts/quiet-on-success.shin this skill)assets/quiet-on-success.sh - — tracked hook wrapper
.hk-hooks/pre-commit
Then:
bash
chmod +x scripts/quiet-on-success.sh .hk-hooks/*
git config --local core.hooksPath .hk-hooksAnd add to prepare script (JS projects):
package.jsonjson
"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 manually or via a Makefile target.
core.hooksPathsetup需要创建/更新四个文件:
- — 添加hk、pkl和工具二进制文件
mise.toml - — 配置文件
hk.pkl - — 输出降噪脚本(从本技能的
scripts/quiet-on-success.sh复制)assets/quiet-on-success.sh - — 受git追踪的钩子包装器
.hk-hooks/pre-commit
然后执行:
bash
chmod +x scripts/quiet-on-success.sh .hk-hooks/*
git config --local core.hooksPath .hk-hooksJS项目需要在中添加prepare脚本:
package.jsonjson
"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.'"非JS项目可以手动设置,或者在Makefile的目标中添加配置。
core.hooksPathsetup4. Validate
4. 校验配置
bash
hk check --all # verify all steps pass on existing files
hk validate # verify hk.pkl is valid Pklbash
hk check --all # 验证所有步骤在现有文件上可正常执行
hk validate # 验证hk.pkl是合法的Pkl文件Preferred Patterns
推荐实践
hk.pkl global settings
hk.pkl全局配置
Always use these at the top (after the amends/import lines):
pkl
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:
pkl
["pre-commit"] {
fix = true // auto-fix and re-stage
stash = "git" // isolate staged changes
steps { ... }
}始终在文件顶部(amends/import行之后)添加以下配置:
pkl
exclude = List("node_modules", "dist", ".next", ".git") // 添加项目专属的排除目录
display_skip_reasons = List() // 屏蔽跳过提示
terminal_progress = false // 简化输出样式始终在pre-commit钩子中添加以下配置:
pkl
["pre-commit"] {
fix = true // 自动修复并重新暂存文件
stash = "git" // 隔离暂存区变更
steps { ... }
}Binary file excludes
二进制文件排除规则
Always exclude binary/font files from trailing-whitespace, newlines, and typos:
pkl
local binary_excludes = List(
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico",
"*.woff", "*.woff2", "*.ttf", "*.eot", "*.pdf", "*.zip"
)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = binary_excludes
}始终从trailing-whitespace、newlines和typos步骤中排除二进制/字体文件:
pkl
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:
pkl
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec tsc --noEmit"
}Copy from this skill directory into in the target repo.
assets/quiet-on-success.shscripts/包装有冗余输出的命令,仅在执行失败时输出日志:
pkl
["typecheck"] {
check = "scripts/quiet-on-success.sh pnpm exec tsc --noEmit"
}将本技能目录下的复制到目标仓库的目录下即可使用。
assets/quiet-on-success.shscripts/The .hk-hooks/pre-commit wrapper
.hk-hooks/pre-commit包装器
This is the file git actually executes. It's tracked in git (unlike ):
.git/hooks/sh
#!/bin/sh这是git实际执行的文件,会被git追踪(和不同):
.git/hooks/sh
#!/bin/shhk pre-commit hook — silent on success, minimal on failure
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 $code
For other hooks (commit-msg, pre-push), use simpler wrappers:
```sh
#!/bin/sh
exec hk run commit-msg "$@"sh
#!/bin/sh
exec hk run pre-push "$@"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 $code
其他钩子(commit-msg、pre-push)可以使用更简单的包装器:
```sh
#!/bin/sh
exec hk run commit-msg "$@"sh
#!/bin/sh
exec hk run pre-push "$@"Pkl Syntax Reference
Pkl语法参考
Required first lines
必备首行配置
pkl
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 and to the installed hk version ().
amendsimporthk --versionpkl
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"始终保证和中的版本和安装的hk版本一致(可通过查看)。
amendsimporthk --versionBuiltin step (use as-is)
内置步骤(直接使用)
pkl
["trailing-whitespace"] = Builtins.trailing_whitespacepkl
["trailing-whitespace"] = Builtins.trailing_whitespaceBuiltin step (with overrides)
带覆盖配置的内置步骤
pkl
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = List("*.png", "*.jpg")
batch = true
}pkl
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = List("*.png", "*.jpg")
batch = true
}Custom step
自定义步骤
pkl
["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
}pkl
["typecheck"] {
glob = List("*.ts", "*.tsx") // 可选:仅当暂存区存在匹配文件时执行
check = "scripts/quiet-on-success.sh pnpm exec tsc --noEmit"
// fix = "command to auto-fix" // 可选:自动修复命令
}Template variables
模板变量
| Variable | Value |
|---|---|
| Space-separated list of staged files matching the step's glob |
| Path to commit message file (commit-msg hook only) |
| Directory containing |
| Files relative to workspace directory |
| 变量 | 取值 |
|---|---|
| 匹配步骤glob规则的暂存文件,以空格分隔 |
| 提交信息文件路径(仅commit-msg钩子可用) |
| 包含 |
| 相对于工作区目录的文件路径 |
Multi-line inline script
多行内联脚本
pkl
["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
"""
}pkl
["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)
局部变量(跨钩子共享步骤)
pkl
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 }
}pkl
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:
pkl
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 for fine-grained ordering:
dependspkl
["eslint"] {
depends = List("prettier") // waits for prettier to finish
...
}同一分组内的步骤并行执行,分组之间按顺序执行:
pkl
steps {
["format"] = new Group {
steps = new Mapping<String, Step> {
["prettier"] { ... }
["eslint"] { ... }
}
}
["validate"] = new Group { // format执行完成后再运行
steps = new Mapping<String, Step> {
["typecheck"] { ... }
["test"] { ... }
}
}
}也可以使用实现更精细的顺序控制:
dependspkl
["eslint"] {
depends = List("prettier") // 等待prettier执行完成
...
}mise.toml Additions
mise.toml配置补充
toml
[tools]
hk = "latest"
pkl = "latest" # required for hk.pkl parsingtoml
[tools]
hk = "latest"
pkl = "latest" # 解析hk.pkl的必备依赖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)
---typos = "latest" # 层级2:拼写检查
gitleaks = "latest" # 层级2:密钥检测
rumdl = "latest" # 层级2:markdown lint(存在.md文件时添加)
yamllint = "latest" # 层级4:YAML lint(存在.yamllint*文件时添加)
---Maintenance
维护指南
Add a new step
添加新步骤
Insert into under the appropriate section. Check for available built-ins, or write a custom step.
hk.pklhk builtins插入到的对应分区下即可。可通过查看可用内置步骤,也可以编写自定义步骤。
hk.pklhk builtinsUpdate hk version
更新hk版本
bash
hk --version # check currentBump both URLs in :
hk.pklpkl
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"bash
hk --version # 查看当前版本更新中的两个URL:
hk.pklpkl
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
临时绕过钩子
bash
HK=0 git commit -m "wip" # skip all hk hooks
HK_SKIP_STEPS=vitest git commit # skip specific stepbash
HK=0 git commit -m "wip" # 跳过所有hk钩子
HK_SKIP_STEPS=vitest git commit # 跳过指定步骤Debug a failing step
调试执行失败的步骤
bash
hk check -v # verbose output
hk check -v --step typecheck # single step only
hk run pre-commit -v # simulate hook runbash
hk check -v # 输出详细日志
hk check -v --step typecheck # 仅运行单个步骤
hk run pre-commit -v # 模拟钩子执行Local developer overrides
本地开发者覆盖配置
Create (gitignored) to override settings locally:
hk.local.pklpkl
amends "./hk.pkl"
hooks {
["pre-commit"] {
steps {
["vitest"] {
check = "scripts/quiet-on-success.sh pnpm exec vitest run --testPathPattern=fast"
}
}
}
}创建(加入.gitignore)即可在本地覆盖配置:
hk.local.pklpkl
amends "./hk.pkl"
hooks {
["pre-commit"] {
steps {
["vitest"] {
check = "scripts/quiet-on-success.sh pnpm exec vitest run --testPathPattern=fast"
}
}
}
}Gotchas
常见问题
| Issue | Fix |
|---|---|
| Add |
| Match amends/import URL version to |
| Builtins snake_case vs step names kebab-case | |
| Hook runs but matches nothing | Check glob patterns; use |
| Binary files fail spell check | Add binary excludes to typos/trailing-whitespace/newlines steps |
Git worktrees: | Automatic since v1.35.0; if using older version use |
| Fix auto-stages wrong files | Use explicit |
| Noisy output on success | Wrap commands in |
| Hook runs in CI unnecessarily | Add |
| First line must be |
| 问题 | 解决方案 |
|---|---|
| 在 |
| 将amends/import URL的版本和 |
| 内置步骤命名是snake_case,步骤名是kebab-case | 对应关系为 |
| 钩子执行但未匹配任何文件 | 检查glob规则;使用 |
| 二进制文件未通过拼写检查 | 为typos/trailing-whitespace/newlines步骤添加二进制文件排除规则 |
Git worktree环境下 | v1.35.0之后版本已自动支持;旧版本请使用 |
| 自动修复暂存了错误的文件 | 为步骤添加明确的 |
| 执行成功时仍有冗余输出 | 用 |
| CI环境下钩子不必要执行 | 在 |
| 首行必须为 |
References
参考资料
- — step selection by ecosystem
references/builtins-by-language.md - — full hk.pkl configs for different stacks
references/complete-examples.md - — copy into
assets/quiet-on-success.shin target reposcripts/ - hk docs — official documentation
- — list all 90+ available built-in linters
hk builtins
- — 按技术栈分类的步骤选择指南
references/builtins-by-language.md - — 不同技术栈的完整hk.pkl配置示例
references/complete-examples.md - — 可复制到目标仓库
assets/quiet-on-success.sh目录使用的静默脚本scripts/ - hk官方文档 — 官方文档
- — 查看所有90+可用的内置linter
hk builtins