hk

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

hk — 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 script

Setup 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, scripts
Identify:
  • 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):
StepBuiltin
trailing-whitespace
Builtins.trailing_whitespace
newlines
Builtins.newlines
check-merge-conflict
Builtins.check_merge_conflict
Tier 2 — Common tools (add if relevant):
StepBuiltinWhen
typos
Builtins.typos
Always (fast spell check)
gitleakscustomAlways (secret detection)
rumdl
Builtins.rumdl
If
*.md
files exist
Tier 3 — Language-specific (see
references/builtins-by-language.md
):
Signal fileSteps 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):
SignalStep
commitlint.config.*
exists
commit-msg hook with commitlint
.yamllint*
exists
yamllint
Team/shared repono-commit-to-branch (pre-commit), no-push-to-branch (pre-push)
Test runner detectedtest step(s) — vitest/jest/go test/cargo test/pytest
层级1 — 通用规则(始终添加):
步骤内置方法
trailing-whitespace
Builtins.trailing_whitespace
newlines
Builtins.newlines
check-merge-conflict
Builtins.check_merge_conflict
层级2 — 通用工具(符合场景则添加):
步骤内置方法适用场景
typos
Builtins.typos
所有场景(快速拼写检查)
gitleaks自定义所有场景(密钥检测)
rumdl
Builtins.rumdl
存在
*.md
文件的项目
层级3 — 语言专属规则(参考
references/builtins-by-language.md
):
标识文件需添加的步骤
package.json
+
biome.json
/
biome.jsonc
biome(或ultracite)、eslint
package.json
(无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(格式化+lint)、mypy
flake.nix
/
*.nix
nix_fmt(nixfmt)、deadnix
*.sh
/
*.zsh
shfmt、shellcheck
层级4 — 项目专属规则(从配置文件检测):
标识步骤
存在
commitlint.config.*
搭配commitlint的commit-msg钩子
存在
.yamllint*
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:
  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:
bash
chmod +x scripts/quiet-on-success.sh .hk-hooks/*
git config --local core.hooksPath .hk-hooks
And add to
package.json
prepare script (JS projects):
json
"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.
需要创建/更新四个文件:
  1. mise.toml
    — 添加hk、pkl和工具二进制文件
  2. hk.pkl
    — 配置文件
  3. scripts/quiet-on-success.sh
    — 输出降噪脚本(从本技能的
    assets/quiet-on-success.sh
    复制)
  4. .hk-hooks/pre-commit
    — 受git追踪的钩子包装器
然后执行:
bash
chmod +x scripts/quiet-on-success.sh .hk-hooks/*
git config --local core.hooksPath .hk-hooks
JS项目需要在
package.json
中添加prepare脚本:
json
"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项目可以手动设置
core.hooksPath
,或者在Makefile的
setup
目标中添加配置。

4. Validate

4. 校验配置

bash
hk check --all      # verify all steps pass on existing files
hk validate         # verify hk.pkl is valid Pkl

bash
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 output
Always 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
assets/quiet-on-success.sh
from this skill directory into
scripts/
in the target repo.
包装有冗余输出的命令,仅在执行失败时输出日志:
pkl
["typecheck"] {
    check = "scripts/quiet-on-success.sh pnpm exec tsc --noEmit"
}
将本技能目录下的
assets/quiet-on-success.sh
复制到目标仓库的
scripts/
目录下即可使用。

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/sh

hk 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
amends
and
import
to the installed hk version
(
hk --version
).
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"
始终保证
amends
import
中的版本和安装的hk版本一致
(可通过
hk --version
查看)。

Builtin step (use as-is)

内置步骤(直接使用)

pkl
["trailing-whitespace"] = Builtins.trailing_whitespace
pkl
["trailing-whitespace"] = Builtins.trailing_whitespace

Builtin 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

模板变量

VariableValue
{{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
变量取值
{{files}}
匹配步骤glob规则的暂存文件,以空格分隔
{{commit_msg_file}}
提交信息文件路径(仅commit-msg钩子可用)
{{workspace}}
包含
workspace_indicator
文件的目录
{{workspace_files}}
相对于工作区目录的文件路径

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
depends
for fine-grained ordering:
pkl
["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"] { ... }
        }
    }
}
也可以使用
depends
实现更精细的顺序控制:
pkl
["eslint"] {
    depends = List("prettier")   // 等待prettier执行完成
    ...
}

mise.toml Additions

mise.toml配置补充

toml
[tools]
hk = "latest"
pkl = "latest"        # required for hk.pkl parsing
toml
[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
hk.pkl
under the appropriate section. Check
hk builtins
for available built-ins, or write a custom step.
插入到
hk.pkl
的对应分区下即可。可通过
hk builtins
查看可用内置步骤,也可以编写自定义步骤。

Update hk version

更新hk版本

bash
hk --version   # check current
Bump both URLs in
hk.pkl
:
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"
bash
hk --version   # 查看当前版本
更新
hk.pkl
中的两个URL:
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

临时绕过钩子

bash
HK=0 git commit -m "wip"             # skip all hk hooks
HK_SKIP_STEPS=vitest git commit      # skip specific step
bash
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 run
bash
hk check -v                          # 输出详细日志
hk check -v --step typecheck         # 仅运行单个步骤
hk run pre-commit -v                 # 模拟钩子执行

Local developer overrides

本地开发者覆盖配置

Create
hk.local.pkl
(gitignored) to override settings locally:
pkl
amends "./hk.pkl"
hooks {
    ["pre-commit"] {
        steps {
            ["vitest"] {
                check = "scripts/quiet-on-success.sh pnpm exec vitest run --testPathPattern=fast"
            }
        }
    }
}

创建
hk.local.pkl
(加入.gitignore)即可在本地覆盖配置:
pkl
amends "./hk.pkl"
hooks {
    ["pre-commit"] {
        steps {
            ["vitest"] {
                check = "scripts/quiet-on-success.sh pnpm exec vitest run --testPathPattern=fast"
            }
        }
    }
}

Gotchas

常见问题

IssueFix
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 nothingCheck glob patterns; use
hk check -v
to see file matching
Binary files fail spell checkAdd 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 filesUse explicit
stage
glob on the step, or ensure step
glob
covers fixed files
Noisy output on successWrap commands in
scripts/quiet-on-success.sh
Hook runs in CI unnecessarilyAdd
[ -n "$CI" ] && exit 0
to
prepare
script
hk.local.pkl
uses amends not being honoured
First line must be
amends "./hk.pkl"

问题解决方案
pkl: command not found
mise.toml
中添加
pkl = "latest"
,运行
mise install
amends
版本不匹配
将amends/import URL的版本和
hk --version
的输出对齐
内置步骤命名是snake_case,步骤名是kebab-case对应关系为
Builtins.trailing_whitespace
["trailing-whitespace"]
钩子执行但未匹配任何文件检查glob规则;使用
hk check -v
查看文件匹配逻辑
二进制文件未通过拼写检查为typos/trailing-whitespace/newlines步骤添加二进制文件排除规则
Git worktree环境下
hk install
失败
v1.35.0之后版本已自动支持;旧版本请使用
.hk-hooks/
+
core.hooksPath
方案
自动修复暂存了错误的文件为步骤添加明确的
stage
glob规则,或保证步骤的
glob
覆盖修复后的文件
执行成功时仍有冗余输出
scripts/quiet-on-success.sh
包装命令
CI环境下钩子不必要执行
prepare
脚本中添加
[ -n "$CI" ] && exit 0
逻辑
hk.local.pkl
的amends配置不生效
首行必须为
amends "./hk.pkl"

References

参考资料

  • references/builtins-by-language.md
    — step selection by ecosystem
  • references/complete-examples.md
    — full hk.pkl configs for different stacks
  • assets/quiet-on-success.sh
    — copy into
    scripts/
    in target repo
  • hk docs — official documentation
  • hk builtins
    — list all 90+ available built-in linters
  • references/builtins-by-language.md
    — 按技术栈分类的步骤选择指南
  • references/complete-examples.md
    — 不同技术栈的完整hk.pkl配置示例
  • assets/quiet-on-success.sh
    — 可复制到目标仓库
    scripts/
    目录使用的静默脚本
  • hk官方文档 — 官方文档
  • hk builtins
    — 查看所有90+可用的内置linter