关于 hk
这是 hk(一个运行代码检查器和格式化工具的 Git 钩子管理器)的配置与设置技能。它指导检查仓库、选择钩子、编写 Pkl 配置、接入和验证,并保留项目现有约定。
主要功能
- 01检查仓库版本与钩子约定
- 02按语言和项目文件选择检查项
- 03使用 Pkl 配置钩子
- 04接入受版本控制的 Git 钩子包装器
- 05验证钩子计划与执行结果
适用场景
- 01为新仓库添加钩子
- 02维护现有 hk 配置
- 03为项目语言和工具选择检查项
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ connorads/ dotfiles --skill hk安装后在 Claude Code 中输入/hk确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/pnpm-build-scripts-check.mjssoft-protected-branch-pre-push.shevals/prompts.mdreferences/builtins-by-language.mdcomplete-examples.mdoutput-noise.mdtesting-steps.mdversions-and-migration.mdtests/pnpm-build-scripts-check.batssoft-protected-branch-pre-push.bats详细介绍
hk - Git 钩子管理器
hk 将代码检查器和格式化工具作为 Git 钩子运行。启用暂存时,它会协调文件访问并隔离暂存的更改。配置采用 Pkl。命令仍需准确选择文件并声明其影响。
心智模型
在编排步骤或接入钩子前,先检测仓库的版本和钩子契约。除非用户要求更改,否则维护任务应保留该契约。
设置工作流
1. 检测
hk --version # compare with the repo pin and schema URLs
ls package.json go.mod Cargo.toml pyproject.toml flake.nix Makefile
cat mise.toml package.json # existing tools, package manager, scripts
新建设置时使用 hk v2。对于现有设置,请检查工具版本固定和锁文件、hk.pkl 导入、本地覆盖项以及 git config --show-origin core.hooksPath。编辑前先解决版本不匹配;不要默默升级仓库。维护 v1 或进行任何主版本升级时,请阅读 references/versions-and-migration.md。保留钩子成员、暂存预期和机密扫描范围。
识别:
- 语言和框架
- 包管理器(JS 使用 pnpm/bun/npm/yarn,或 cargo、go、pip 等)
- 已配置的格式化工具(prettier、biome、ruff、gofmt 等)
- 已配置的代码检查器(eslint、golangci-lint、ruff、clippy 等)
- 测试运行器(vitest、jest、go test、cargo test、pytest 等)
- 是否为团队/共享仓库,以及分支保护应采用严格的服务端保护还是建议性的本地钩子保护
2. 选择步骤(分层)
第 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 | 类型检查(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(格式化+代码检查)、mypy |
flake.nix/*.nix | nix_fmt(nixfmt)、deadnix |
*.sh/*.zsh | shfmt、shellcheck |
第 4 层 - 项目特定(根据配置文件检测):
| 信号 | 步骤 |
|---|---|
存在 commitlint.config.* | 使用 commitlint 的 commit-msg 钩子 |
存在 .dependency-cruiser.* 或 check:deps | 全图架构检查 |
存在 .yamllint* | yamllint |
| 团队/共享仓库 | no-commit-to-branch(pre-commit)、分支防护(pre-push)。对于允许所有者选择退出的建议性私有仓库保护,请使用下方的软保护 pre-push 资源。 |
存在 pnpm-lock.yaml | pnpm 构建脚本决策检查 - 复制 assets/pnpm-build-scripts-check.mjs(见下文) |
| 检测到测试运行器 | 测试步骤 - vitest/jest/go test/cargo test/pytest |
3. 接入钩子
需要创建或更新 3 个文件,另有可选项:
mise.toml- 添加 hk 和工具二进制文件;v2 内置其 Pkl 评估器hk.pkl- 配置.hk-hooks/pre-commit- 纳入版本控制的钩子包装脚本(运行hk run pre-commit -q;成功时,-q会让每个步骤都保持安静 - 参见references/output-noise.md).hk-hooks/pre-push- 可选,用于推送时检查或分支保护。对于仅供参考的私有仓库分支保护,可复制assets/soft-protected-branch-pre-push.sh。
然后:
chmod +x .hk-hooks/*
git config --local core.hooksPath .hk-hooks
将以下内容合并到现有的 package.json prepare 脚本中(JS 项目):
"prepare": "[ -n \"$CI\" ] && exit 0 || git config --local core.hooksPath .hk-hooks"
直接配置 core.hooksPath - 不要在此处使用 hk install。在新版 hk/Git 上,hk install
会成功并接入 hk 自己生成的钩子,从而与本技能设置的纳入版本控制的 .hk-hooks/ 包装脚本悄然产生偏差(该包装脚本添加了
HK=0 绕过机制、mise 查找和 -q)。只保留一种接入方式,即纳入版本控制的这一种。准备阶段不必安装 hk - 包装脚本会在提交时查找 hk,如果找不到则会明确报错。
对于非 JS 项目,可手动设置 core.hooksPath,或通过 Makefile 的 setup 目标设置。
保留原生的包脚本作为任务运行器。不要在 JS 项目中使用 hk init --mise 来生成替代的 mise 任务。现有安装机制需要有计划地迁移,而不是在其上再叠加第二个启动器。
4. 验证
hk validate # check schema and configuration
hk run pre-commit --plan # inspect steps and selected files
hk check --all # execute the check hook, if defined
首选模式
新建 v2 配置时使用共享步骤
当 check、fix 和 pre-commit 使用相同检查时,使用顶层步骤。 以下 URL 以 2.0.1 为例;两处 URL 都应与选定的已安装版本一致。
amends "package://github.com/jdx/hk/releases/download/v2.0.1/[email protected]#/Config.pkl"
import "package://github.com/jdx/hk/releases/download/v2.0.1/[email protected]#/Builtins.pkl"
exclude = List("node_modules", "dist", ".next", ".git")
display_skip_reasons = List()
terminal_progress = false
steps {
["trailing-whitespace"] = Builtins.trailing_whitespace
["newlines"] = Builtins.newlines
["check-merge-conflict"] = Builtins.check_merge_conflict
}
这会创建 check、fix 和 pre-commit 钩子。Pre-commit 会修复、暂存并暂存收纳;check 和 fix 不会改动索引。不会创建 pre-push 钩子。 将分支保护、提交消息检查及其他特定事件的步骤保留在各自的显式钩子中。钩子覆盖会完全替换同名共享步骤;其他共享步骤仍会生效。
仅显式配置钩子的配置仍然有效。维护时保留其布局;在显式的 pre-commit 钩子中声明 fix = true 和 stash = "git"。只有在确实需要暂存时,才在其他钩子上使用 stage = true。
带有不同钩子的配置示例见 references/complete-examples.md。
排除二进制文件
对于尾随空格、换行符和拼写错误检查,务必排除二进制文件和字体文件:
local binary_excludes = List(
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.ico",
"*.woff", "*.woff2", "*.ttf", "*.eot", "*.pdf", "*.zip"
)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = binary_excludes
}
让步骤保持安静 - 在钩子包装脚本中使用一个标志
使用 hk run <hook> -q 并保留常规输出摘要。不要将其与
output_summary = "hide" 搭配使用,也不要使用 --silent:两者都会隐藏失败诊断信息。
有关支持的版本和输出验证,请参见 references/output-noise.md。
全图检查
有些工具会检查整个仓库图谱,不应接收 {{files}}:
dependency-cruiser、knip、供应链扫描器、链接检查器、完整类型检查
以及覆盖率门禁。若检查必须始终查看完整图谱,请将它们配置为普通步骤且不设置 glob。(应运行哪种供应链扫描,以及阻断与报告的严重性如何划分,由供应链加固技能决定 - 本技能负责接入。)
不设置 glob 不只是为了适配工具的接受范围。如果暂存的变更只有删除操作,带 glob 的步骤根本不会运行:hk 会将 glob 解析为零个文件,步骤就不会出现在执行计划中,而不设置 glob 的步骤仍会运行(显示“0 个文件”)。因此,任何可能因删除文件而被破坏的不变量 - 例如链接检查器、无效引用检查或清单与文件树一致性门禁 - 都必须不设置 glob,否则恰恰会在其需要发挥作用的情况下不执行。
对于 dependency-cruiser:
local depcruise_step = new Step {
check = "pnpm --silent check:deps"
}
使用包脚本,让较长的命令和配置路径保存在 JS 项目中:
"check:deps": "depcruise src --config .dependency-cruiser.cjs --output-type err-long --no-progress --no-cache"
最好将全图检查放在完整的 quality、check、CI 或 pre-push
钩子中。只有在测量步骤耗时并确认新增延迟对于日常提交可以接受后,才将其提升到暂存的 pre-commit 中。
.hk-hooks/pre-commit 包装脚本
这是 Git 实际执行的文件。它受 Git 跟踪(与 .git/hooks/ 不同)。
使用 exec,让退出状态和诊断信息传递给 Git。包装脚本提供 HK=0
绕过选项,并在 hk 不在 PATH 中时通过 mise 查找 hk。其输出约定见
references/output-noise.md。
#!/bin/sh
# hk pre-commit hook - tracked wrapper. Streams hk output directly.
# HK=0 bypasses all hooks (mirrors `HK=0 git commit`).
if [ "${HK:-1}" = "0" ]; then
exit 0
fi
# Find hk: on PATH, else via mise (covers shells without mise activated).
HK_BIN=""
if command -v hk >/dev/null 2>&1; then
HK_BIN="$(command -v hk)"
elif command -v mise >/dev/null 2>&1; then
HK_BIN="$(mise which hk 2>/dev/null || true)"
fi
if [ -z "$HK_BIN" ]; then
echo "hk not found. Install tools with: mise install" >&2
exit 1
fi
exec "$HK_BIN" run pre-commit -q "$@"
对于只委托给 hk 的钩子,请使用更简单的包装脚本:
#!/bin/sh
exec hk run commit-msg "$@"
#!/bin/sh
exec hk run pre-push "$@"
软保护分支 pre-push
请谨慎使用:适用于小型/私有/共享仓库,在这些仓库中,服务端分支保护不可用或仅作为建议,但希望引导协作者不要直接推送到 main/master。如果可以使用服务端分支规则,请优先采用。这不是安全边界:钩子按克隆分别设置,需要 core.hooksPath,并且可以通过 --no-verify 绕过。
将 assets/soft-protected-branch-pre-push.sh 复制到 .hk-hooks/pre-push,并设置为可执行:
cp /path/to/skill/assets/soft-protected-branch-pre-push.sh .hk-hooks/pre-push
chmod +x .hk-hooks/pre-push
git config --local core.hooksPath .hk-hooks
模式:
- 解析 Git 的 pre-push 标准输入,并根据
remote_ref阻止推送,而不是检查当前分支。 检查当前分支会漏掉git push origin feature:main这类推送。 - 默认阻止直接推送到
refs/heads/main和refs/heads/master。 - 允许仓库所有者通过仓库本地配置选择退出:
git config --local hooks.allowMainPush true。 - 明确保留一次性自动化的例外方式:
HK_ALLOW_MAIN_PUSH=1 git push。 - 在仓库文档/Agent 说明中说明其建议性质及选择退出的方法。
pnpm 构建脚本决策检查
包含生命周期脚本(preinstall/install/postinstall,或
binding.gyp)的依赖,需要在 pnpm-workspace.yaml 的
allowBuilds 中记录决策。如果没有记录,pnpm 11 会以失败方式阻止安装
(ERR_PNPM_IGNORED_BUILDS)——但仅限没有任何设置掩盖其检查的情况。在全局设置了 ignoreScripts 的机器上,安装会成功,全新安装也会成功,而失败则会出现在 CI 或平台构建中。
将 assets/pnpm-build-scripts-check.mjs 复制到 .hk-hooks/,并针对可能改变依赖树的文件 glob 此步骤:
["pnpm-build-scripts"] {
glob = List("package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml")
check = "node .hk-hooks/pnpm-build-scripts-check.mjs"
}
模式:
- **读取配置,不运行任何内容。**检查器会解析已安装的清单和
pnpm-workspace.yaml。pnpm 没有只检测而不执行的模式,而其自身的报告(pnpm ignored-builds、.modules.yaml)是在掩盖检查的设置下生成的,因此报告的是掩盖设置,而非缺失的决策。唯一能在本地复现 CI 的命令——pnpm install --ignore-scripts=false——会重新启用该设置所禁止的脚本,因此不能用作检查。 - 绝不因无法评估而阻断:如果没有
node_modules,或没有 pnpm 项目,则发出警告并以 0 退出(与在全新克隆上执行类型检查步骤的处理方式一致)。 - 提供
--json以供机器读取;人类可读的输出会限制列表长度,并报告实际总数。 - 软件包应设置为
true还是false,属于用户的安全决策——由供应链加固技能负责作出决定。此步骤只要求决策已记录。
已知限制:它读取的是已安装的依赖树,因此只能看到为当前平台解析出的可选依赖。在 linux-x64 软件包中才包含的 postinstall 脚本,对任何本地检查都不可见;只有在目标平台上运行的 CI 作业才能弥补这一缺口。
Pkl 语法参考
使用上文所示的版本化 amends 和 import 配对。内置项是否可用,取决于该架构包,而不仅仅是 PATH 中的可执行文件。
内置步骤(按原样使用)
["trailing-whitespace"] = Builtins.trailing_whitespace
内置步骤(带覆盖项)
["trailing-whitespace"] = (Builtins.trailing_whitespace) {
exclude = List("*.png", "*.jpg")
batch = true
}
在 v2 中覆盖内置命令
仅修改命令时,请保留内置项的 CommandSpec 效果:
["hk-test"] = (Builtins.hk_test) {
check {
command = "env -u GIT_DIR -u GIT_WORK_TREE hk test --quiet"
}
}
简单的字符串赋值会替换对象,并丢失其声明的效果。修改之前,请先检查所选内置项的定义;并非每个命令都是对象。对于结构化的 argv 命令,启动器前缀应是 argv 列表,例如 prefix = List("pnpm", "exec"),而不是 shell 字符串。
自定义步骤
["typecheck"] {
glob = List("*.ts", "*.tsx") // optional: only run when these files staged
check = "pnpm exec tsc --noEmit" // silent on success - no wrapper needed
// fix = "command to auto-fix" // optional
}
模板变量
| 变量 | 值 |
|---|---|
{{files}} | 经过步骤筛选器后选中的文件参数;具体选择取决于钩子和标志 |
{{commit_msg_file}} | 提交消息文件的路径(仅限 commit-msg 钩子) |
{{workspace}} | 包含 workspace_indicator 文件的目录 |
{{workspace_files}} | 相对于工作区目录的文件 |
{{root}} | 仓库根目录。在 tests {} 块中,它仍指向实际根目录,而非沙盒目录——这样 before 才能复制检查器 |
{{tmp}} | 每项测试专用的沙盒目录。仅限 tests {};使用它会自动启用 tmpdir |
分别设置钩子成员
当不同钩子需要执行不同检查时,请使用显式钩子和本地 Mapping<String, Step> 值。请参阅 references/complete-examples.md;v1 的等效内容见 references/versions-and-migration.md。
步骤排序
使用 depends = List("prettier"),让普通步骤排在 prettier 之后。组会在并行步骤集合之间创建顺序边界;语法请参阅钩子排序。在组中依赖测试之前,请先使用 hk test --list 检查测试用例是否符合预期。组发现范围的限制请参阅 references/testing-steps.md。
mise.toml 补充配置
[tools]
hk = "2"
# 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)
维护
添加新步骤
在 hk.pkl 的相应部分中插入。使用 hk builtins 查看可用内置步骤,或编写自定义步骤。
更新 hk 版本
如需升级,请先按照 references/versions-and-migration.md 操作,再更改工具版本固定值和两个架构 URL。使用所选二进制文件验证配置并检查钩子计划,然后运行相关检查。版本号一致本身并不能验证暂存或钩子成员配置。
临时绕过钩子
HK=0 git commit -m "wip" # skip all hk hooks
HK_SKIP_STEPS=vitest git commit # skip specific step
调试失败的步骤
hk check -v # verbose output
hk check -v --step typecheck # single step only
hk run pre-commit -v # simulate hook run
本地开发者覆盖配置
使用被 git 忽略且以 amends "./hk.pkl" 开头的 hk.local.pkl。仅修改目标钩子或步骤。替换某个钩子的步骤映射不会移除继承的顶层步骤;如需移除所有共享步骤,也请替换顶层映射。请参阅本地覆盖配置参考。
注意事项
| 问题 | 解决方法 |
|---|---|
pkl: command not found | V2 内置了其求值器。添加独立的 Pkl 依赖前,先检查失败的命令或 v1 后端 |
amends 版本不匹配 | 使 amends/import URL 版本与 hk --version 的输出一致 |
| 内置项使用 snake_case,步骤名称使用 kebab-case | Builtins.trailing_whitespace → ["trailing-whitespace"] |
| Hook 运行了,但没有匹配任何内容 | 检查 glob 模式;使用 hk check -v 查看文件匹配情况 |
当 {{files}} 中没有该工具能处理的文件时,步骤失败 | Glob 决定步骤在哪些文件上运行,而非工具接受哪些文件:有些工具在目标集合为空时会以非零状态退出,而不是空操作。当传入的每个路径都位于其自身的 ignorePatterns 中时,oxfmt 会报错“Expected at least one target file”;没有可 lint 的文件时,oxlint 也会如此。每个步骤的 glob 应匹配该工具实际处理的文件,并将 lint 和 format 分为独立步骤——如果将它们合并为一个对 *.json 使用 glob 的步骤,那么仅修改 JSON 文件的提交就会失败 |
| 二进制文件导致拼写检查失败 | 在 typos/trailing-whitespace/newlines 步骤中排除二进制文件 |
Builtins.typos 改写非英语单词,且提交通过 | Pre-commit 会运行该内置项的 fix(typos --write-changes),它将歌词和数据中的法语 sur 改为 sure,把更改暂存后放行了提交(2.0.1)。在 amend 中设置 fix = null 也无法阻止这一行为。对于包含非英语内容的仓库,请将该内置项替换为自定义步骤,且只包含 check = "typos --force-exclude {{files}}",并在 _typos.toml 中允许真实词语 |
Git worktree:hk install 失败 | v1.35.0 起会自动处理;如果使用较旧版本,请使用 .hk-hooks/ + core.hooksPath |
| 修复时的暂存行为与预期不同 | V2 默认仅在 pre-commit 中暂存。Hook 的 stage 会启用暂存;步骤的 stage 会筛选路径。参见 references/versions-and-migration.md |
| 成功时输出过多 | 使用 references/output-noise.md 中的包装器和摘要设置 |
| Hook 在 CI 中进行了不必要的运行 | 将 [ -n "$CI" ] && exit 0 添加到 prepare 脚本 |
未遵循 hk.local.pkl 中的 amends | 第一行必须是 amends "./hk.pkl" |
| 文档中提到的内置项无法解析 | 内置项集合取决于 amends/import URL 中的版本,而非已安装的 hk 版本。尝试使用之前,请检查该标签下的 pkl/builtins/——例如,1.56.1 中没有 statix,但有 deadnix、lychee、check_symlinks、check_case_conflict 和 hk_test |
hk check --all 似乎漏掉了文件 | 它会选择已跟踪且符合条件的未跟踪文件。暂存会排除未跟踪文件;HK_STASH_UNTRACKED=0 会禁用对它们的发现。更改排除规则前,请先检查执行计划和生效的设置 |
vale 因刻意构造的格式错误 frontmatter fixture 而失败 | Vale 遇到无法解析的 frontmatter 时会直接报错(E201),而非跳过该文件,因此故意无效的测试 fixture 必须从该步骤中排除,方式与 lint fixture 相同 |
机器离线时 pinact 总是失败 | 它会通过 GitHub API(/repos/<owner>/<repo>/commits/<ref>)解析每个 action 引用,且没有离线模式,因此 API 无法访问时会直接失败(3.10.1 中退出码为 1),这与它针对未固定的 action 报告的错误相同。请在 CI 中运行它,而非 pre-commit——这也是 hook 中的 zizmor 使用 --offline 的原因 |
| 步骤测试将 fixture 写入工作树 | 不带 tmpdir = true 的裸相对路径会写入仓库并留下文件。请使用 {{tmp}}/...。参见 references/testing-steps.md |
参考资料
references/versions-and-migration.md- 适用于 v1 维护、版本不匹配或请求升级到 v2 的情况references/builtins-by-language.md- 按生态系统选择步骤references/complete-examples.md- 适用于不同技术栈的完整 hk.pkl 配置references/output-noise.md- 如何正确保持步骤静默(包装器级别的-q、hk 的原生控制项、失败摘要注意事项)references/testing-steps.md-tests {}和hk test:相关字段、{{tmp}}沙盒规则、使用before+{{root}}测试整个仓库的检查器,以及Builtins.hk_test会引入哪些内容assets/soft-protected-branch-pre-push.sh- 复制到.hk-hooks/pre-push,以启用建议性的本地分支保护,并允许仅对当前克隆生效的所有者选择退出tests/soft-protected-branch-pre-push.bats- 针对建议性分支保护资源的行为测试assets/pnpm-build-scripts-check.mjs- 复制到.hk-hooks/,当依赖项的构建脚本没有allowBuilds决策时,阻止提交tests/pnpm-build-scripts-check.bats- 针对构建脚本决策检查器的行为测试evals/prompts.md- 包含验收标准的设置、维护和迁移提示词- hk 文档 - 官方文档
hk builtins- 列出所有可用的内置代码检查器