关于 polyglot-git-hooks
polyglot-git-hooks 是用于在多语言仓库中配置 Lefthook 的技能,可为暂存文件和推送设置本地 Git 检查,并与 CI 配合而非取代 CI。
主要功能
- 01在仓库根目录配置 lefthook.yml
- 02提交前检查暂存文件
- 03推送前运行类型检查和测试
- 04按子目录配置语言专属命令
- 05并行执行并暂存自动修复
适用场景
- 01为仓库添加本地格式与 lint 检查
- 02配置 TypeScript 和 Python 子目录的钩子
- 03整合多语言单体仓库中的钩子配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bm629/ agent-skills --skill polyglot-git-hooks安装后在 Claude Code 中输入/polyglot-git-hooks确认已加载。
附带文件references/lefthook-reference.mdreferences/sources.md
详细介绍
polyglot-git-hooks — SKILL.md
变体: 标准 · 使用时机: 调用此技能后,你会生成一个
lefthook.yml并完成接入,然后将控制权返回给调用方。此技能介绍的工具是 Lefthook(Git 钩子管理器);技能以其差异化优势命名——通过一个配置驱动多种语言的检查。
概述
此技能使用 Lefthook 为多语言代码库配置 Git 钩子。Lefthook 是一款快速、基于 Go、与语言无关的钩子管理器。目标是在代码库根目录放置一个 lefthook.yml,针对混合语言代码库(例如包含 TypeScript 子目录和 Python 子目录),并行运行合适的快速检查:在 pre-commit 阶段检查已暂存文件(格式化 + lint),在 pre-push 阶段运行较慢的检查(类型检查 + 测试)。这是本地“在提交/推送前快速失败”这一层,用于补充 CI,而非取代 CI。此技能负责钩子接入;各工具的具体用法(例如 Biome/Ruff/tsc/ty 的工作方式)属于各自的技能,此技能会引用这些技能。
何时启用
- ✅ 为代码库配置 Git 钩子(新建
lefthook.yml),或添加pre-commit/pre-push门禁。 - ✅ 接入 lint/格式化/类型检查,使其在提交前针对已暂存文件运行,或在推送前针对待推送文件运行。
- ✅ 为多语言代码库或 monorepo配置钩子,其中不同子目录使用不同的工具链。
- ✅ 从 husky+lint-staged 或 Python
pre-commit框架迁移到单配置的多语言方案。
以下情况不要启用:
- 需要了解或配置某个具体的 linter/formatter/type-checker——请使用该工具对应的技能(
biome、ruff、ty、typescript-typecheck)。 - 正在设计 CI 流水线(在服务器上执行完整、权威的检查)——钩子是本地门禁;CI 另行处理(参见步骤 5 和
turborepo技能)。 - 任务是将禁止使用
--no-verify设为组织政策——这是针对狭义政策钩子的需求,而非钩子配置(参见注意事项)。
工作流
步骤 1:选择 Lefthook,并了解原因
Lefthook 是一个无依赖的单二进制程序(使用 Go 编写),可管理任意语言项目的 Git 钩子。对于多语言代码库,应优先选择它而非其他方案,原因如下:
- 与 husky + lint-staged 相比——husky 仅适用于 JS/Node,需要额外接入
lint-staged来筛选已暂存文件;配置最终会分散在.husky/pre-commit、package.json中的lint-staged键,以及package.json脚本中,并会向node_modules引入庞大的依赖树。Lefthook 只需一个lefthook.yml、一个二进制文件,原生支持筛选已暂存文件,且无需额外的运行时依赖。在前端/后端混合团队中,使用 husky+lint-staged 往往意味着需要配置两套方案。 - 与 Python
pre-commit框架相比——pre-commit以 Python 为中心(会为每个钩子管理自己的工具环境),对 TS/混合语言代码库的支持较弱。Lefthook 可以并行运行命令,是单个编译后的二进制文件,而非 Python 脚本,并且同样适用于各种语言和大型 monorepo。(pre-commit仍有更丰富的即用型钩子目录,也更适合小型/Python 单语言项目——这是值得承认的合理取舍,但此处不选它。)
步骤 2:安装并激活
将 Lefthook 安装为开发依赖或二进制文件,然后运行 lefthook install 将其接入 .git/hooks。
- npm:
npm install lefthook --save-dev(也以@evilmartians/lefthook发布)。lefthooknpm 包会运行自己的postinstall,调用lefthook install,因此全新运行npm install时会自动接入钩子,npm 用户开箱即用,无需担心克隆后激活的问题。(参见下方的全新克隆说明。) - **二进制文件/非 npm:**通过 Homebrew、
go install、pipx install lefthook、gem install lefthook、apt、snap 或 winget 安装,然后运行一次lefthook install。
在仓库根目录放置一个 lefthook.yml。运行 lefthook install 验证接入是否成功(该命令具有幂等性,可安全地重复运行)。
**全新克隆后的激活(关键)。**钩子位于每位开发者本地的 .git/hooks 中,不会提交到仓库,因此刚克隆仓库的队友在运行 lefthook install 之前,不会有任何钩子。有两种方法可以让克隆后的仓库自动完成接入:
- **npm 项目:**依赖
lefthook包的postinstall(在运行npm install时自动执行),或在package.json中添加显式的"prepare": "lefthook install"脚本,使其在安装后运行。(对于pnpm,将lefthook添加到pnpm.onlyBuiltDependencies/pnpm-workspace.yaml的onlyBuiltDependencies中,否则会跳过postinstall,钩子也就不会接入。) - **非 npm 项目:**在 README 中将
lefthook install说明为必需的设置步骤,或通过引导/设置脚本运行该命令。
在 CI 中,通常不希望通过 postinstall 安装钩子,因此请设置 CI=true(大多数 CI 环境已设置该变量)以跳过安装;需要强制启用时,使用 LEFTHOOK=1。
步骤 3:编写 lefthook.yml 架构
顶层键是 Git 钩子名称——pre-commit、pre-push(任何钩子名称均可:commit-msg、post-merge 等)。在每个钩子下,将工作定义为以下任一形式:
commands:——由命名命令组成的映射(经典形式),或jobs:——由任务组成的列表(灵活形式;支持命令、脚本,以及可设置自身parallel/piped流程的嵌套组)。在组上设置的glob/root/exclude会应用于所有嵌套任务。
选择更清晰易读的形式即可;此处示例使用 commands:。你会用到的钩子级和命令级选项:
| 选项 | 位置 | 作用 |
|---|---|---|
run: | 命令 | Shell 命令(通过 sh 运行)。必填。 |
glob: | 命令 | 筛选匹配的文件,例如 "*.{ts,tsx}"。允许使用 glob 列表(Lefthook ≥ 1.10.10)。如果 run 中省略了文件模板,Lefthook 仍会按 glob 筛选 {staged_files}(pre-commit)/{push_files}(pre-push),并在没有匹配项时跳过该命令。 |
root: | 命令 | 将命令的工作目录更改为某个子目录(例如 apps/web/)。当 package.json/配置文件位于子目录中时很有用。Glob 仍基于 git 仓库根目录计算——root 不会改变 glob 匹配方式——但 Lefthook 为该命令代入 {staged_files} 的文件路径会改为相对于 root,因此工具会收到相对于子目录的路径。 |
exclude: | 命令 | 排除与正则表达式(或列表)匹配的文件,并与 glob 组合使用。 |
stage_fixed: | 命令 | 命令运行后,对(筛选后的)文件执行 git add,以重新暂存自动修复的内容。仅适用于 pre-commit。 |
parallel: | 钩子 | true → 并发运行该钩子的命令。 |
piped: | 钩子/组 | true → 按顺序运行,遇到首个失败时停止。 |
skip: / only: | 钩子或命令 | 条件:true(始终)、merge / rebase / merge-commit、ref: main(如 ref: dev/* 这样的 glob 引用),或 run: <shell>(命令退出码为 0 时跳过)。 |
tags: / priority: | 命令 | 对命令分组/排序。 |
run: 的文件模板变量——在执行时替换:
{staged_files}——为提交暂存的文件(在pre-commit时填充)。{push_files}——已提交但尚未推送的文件(在pre-push时填充)。{all_files}——所有由 git 跟踪的文件。{files}——自定义files:命令的结果。{cmd}——来自lefthook.yml的命令(用于包装命令,例如在 Docker 中运行)。
如果文件列表超过操作系统的命令行长度限制,Lefthook 会拆分列表,并依次对各批文件运行命令。
步骤 4:多语言完整示例
这是一个完整的根目录 lefthook.yml,适用于同时包含 TypeScript 子树和 Python 子树的仓库。pre-commit 会并行运行快速、自动修复且仅针对暂存文件的检查,并分别限定在各自的语言范围内;pre-push 则会在代码离开本机前运行耗时较长的类型检查和测试关卡。
# lefthook.yml — at the repo root
min_version: 2.0.0 # pin the major version; warn on older Lefthook
pre-commit:
parallel: true
commands:
# --- TypeScript / JavaScript: format + lint staged files, re-stage fixes ---
biome:
glob: "*.{js,jsx,ts,tsx}"
run: biome check --write {staged_files}
stage_fixed: true
# --- Python: lint-fix + format staged files in the python subtree ---
ruff:
root: "services/api/" # CWD for the command (a python subtree)
glob: "*.py" # computed from the repo root, not from `root`
run: ruff check --fix {staged_files} && ruff format {staged_files}
stage_fixed: true
pre-push:
parallel: true
commands:
# --- TypeScript: whole-project type-check (no file template; checks the project) ---
tsc:
glob: "*.{ts,tsx}"
run: tsc --noEmit
# --- Python: type-check the subtree ---
ty:
root: "services/api/"
glob: "*.py"
run: ty check
# --- Tests: run the suites (heavier; pre-push, not pre-commit) ---
test-ts:
glob: "*.{ts,tsx}"
run: npm test
test-py:
root: "services/api/"
glob: "*.py"
run: pytest
采用这种结构的原因:
pre-commit= 快速 + 仅检查暂存文件。biome check --write和ruff check --fix/ruff format只会处理{staged_files},因此钩子运行迅速,且只会修改你准备提交的内容。stage_fixed: true会重新暂存自动修复的结果,确保提交包含格式化后的内容(而且stage_fixed只能用于pre-commit)。- 按子树限定范围。 每条命令都由
glob(语言)限定范围;对于 Python 部分,还会通过root(子树目录)限定范围,以确保相应工具处理正确的文件。请记住,即使设置了root,glob仍会根据相对于仓库根目录的路径进行匹配。 pre-push= 慢速 + 全面。 类型检查(tsc --noEmit、ty check)和测试覆盖整个项目,运行速度太慢,不适合每次提交都运行,因此改为在 推送 时执行。这里的glob只决定命令是否运行(如果没有对应语言的文件发生变更,就跳过);随后类型检查器和测试运行器会检查整个项目,而不只是变更文件。如果希望推送时的命令仅针对变更文件运行,可以使用{push_files}。- 并行运行。
parallel: true会让不同语言的检查同时执行,因此 TS 和 Python 检查互不等待。
各工具的具体调用方式(biome check、ruff check / ruff format、tsc --noEmit、ty check)由各自的技能负责,详见 ## Related。此文件只负责接线配置。
步骤 5:让钩子配合 CI(不要重复配置)
钩子和 CI 是不同层次,应当合理分工,而不是相互照搬:
- 钩子 = 本地、针对暂存内容、快速。 在提交或推送离开本机之前,钩子会捕获明显的问题:格式化、Lint,以及推送时的快速类型检查和测试关卡。由于每次提交都会运行钩子,速度很重要;
pre-commit应仅运行快速的暂存文件检查,耗时较长的任务则放到pre-push。 - CI = 全面、权威。 CI 会在服务端针对整个仓库运行完整套件,并作为强制执行的关卡(合并要求)。由于钩子在本地运行且可以绕过(步骤 6),真正能保证检查已运行的是 CI。
不要在 lefthook.yml 中复述 CI 流水线。 如果仓库使用任务运行器,钩子命令可以调用某项任务(例如 turbo run lint),而不是重新列出流水线步骤;但流水线定义本身应放在任务运行器中,而非钩子配置中。任务图请参阅 turborepo 技能;本技能不会重复定义它。
尚无 CI 时的注意事项(重要)。 如果仓库尚无 CI 流水线,钩子就是主要的自动化关卡——它们背后没有服务端保障。在这种情况下,应提高 pre-push 的检查标准:推送时运行完整的类型检查和测试套件,确保未经验证的代码不会离开本机;在 CI 上线之前,应将整套钩子视为关键保障。CI 建立后,可以将 pre-push 放宽为快速冒烟检查,并由 CI 负责全面运行。
步骤 6:记录 --no-verify 绕过方式
Hook 是本地的,可以跳过——确保团队知道什么情况下可以合理跳过,以及这并非悄无声息的漏洞:
git commit --no-verify(-n)和git push --no-verify会完全跳过 Git Hook。- 在运行
git命令前设置LEFTHOOK=0(或LEFTHOOK=false),会在该次调用中禁用 Lefthook;LEFTHOOK_EXCLUDE会覆盖exclude_tags配置,以排除指定标签/命令。
合理用途:向个人分支提交已知尚未完成的 WIP,或在紧急修复中 Hook 阻止了提交,而 CI 无论如何都会重新检查。由于存在绕过方式,强制执行的关卡是 CI,而不是 Hook(步骤 5)。有些团队会添加阻止 --no-verify 的策略 Hook,让绕过操作变得有意为之;一个已发布的示例是 wshobson/agents@block-no-verify-hook。这是独立的强制执行问题——此处仅作为参考提及,不由此技能配置。
规则
硬性规则(绝不违反):
- 在仓库根目录放置一个
lefthook.yml。 单个与语言无关的配置正是此方案的核心;不要按语言拆分配置。 pre-commit必须快速运行,且仅处理已暂存文件。 在{staged_files}上运行格式化/Lint,并设置stage_fixed: true;绝不要在pre-commit中运行全仓库类型检查或完整测试套件。pre-push承担耗时较长的检查。 类型检查和测试应放在这里,而不是pre-commit。- 为每个命令限定范围。 使用
glob(子目录还需使用root)限定每个命令的范围,确保正确的工具处理正确的文件。不要让 Python 工具处理 TS 文件,反之亦然。 - 此技能负责接线,而非工具本身。 工具标志请参考
biome/ruff/ty/typescript-typecheck;CI/任务图请参考turborepo。不要重复讲解或复制它们的内容。 - 使用
min_version:锁定主版本(当前主版本:Lefthook 2.x),以便在队友使用较旧二进制版本时发出警告,而不是悄无声息地出现异常行为。
偏好(可覆盖):
- 简单的按语言分流优先使用
commands:;需要嵌套的piped/parallel流程控制时,再使用jobs:和分组。 - 使用
parallel: true并发运行互不依赖的语言分流。 - 为不应在合并/变基期间运行的命令添加
skip: [merge, rebase]。
易踩坑
- 全新克隆,不含钩子。 钩子位于
.git/hooks,不会被提交——队友克隆后什么也没有,直到运行lefthook install。表现为:“他们那边从未运行过钩子。”解决方法:依赖 npm 的postinstall(npm 项目),或添加prepare脚本/记录lefthook install(步骤 2)。对于pnpm,除非lefthook位于onlyBuiltDependencies中,否则会跳过postinstall。 root不会改变 glob 匹配方式。 设置root: "services/api/"会更改命令的 CWD,但glob模式仍会按相对于仓库根目录的路径进行匹配。使用"services/api/**/*.py"(完整路径)或"*.py"(匹配任意深度)这样的 glob 都可以;不要把 glob 写成相对于root的路径。stage_fixed仅适用于pre-commit。 在pre-push和其他钩子中会被忽略。如果格式化工具的修复结果没有被提交,请确认命令位于pre-commit下,并且在该命令上设置了stage_fixed: true。- 没有匹配项 = 跳过命令(通常正合所需)。 设置了
glob但没有匹配的暂存文件时,Lefthook 会跳过该命令,而不是在零个文件上运行它——因此,仅包含 TS 文件的提交不会触发 Python 流程。不要添加手动的“无文件时”判断;glob 已经处理了这一点。 - 旧版本配置键已过时。 当前版本是 Lefthook 2.x;非常旧的配置/博客文章可能会使用已弃用的键(例如,用
runner:代替run:)。请依据当前 schema 编写配置,并通过min_version:标记过旧的二进制文件。v2 的 Go 模块路径也为.../lefthook/v2。 lefthook install与工作树。lefthook install可能会将安装时的绝对路径写入钩子脚本,这已知会导致 git worktrees 出现问题——如果钩子在工作树中异常运行,请在那里重新运行lefthook install。- 钩子不是强制措施。 任何人都可以使用
--no-verify。请将钩子视为快速的本地便利工具,并让 CI 充当真正阻止合并的关卡(步骤 5/6)。
反模式
- “把完整测试套件放到
pre-commit,这样就不会漏掉任何问题。” 不要这样做——这会让每次提交都很慢,还会让团队习惯使用--no-verify。pre-commit运行暂存文件的快速检查;pre-push设置较慢的关卡;全面运行交给 CI。 - “每次提交都检查整个仓库的代码风格。” 不要这样做——使用
{staged_files},让钩子只处理正在提交的内容;整个仓库的检查应交给 CI。 - “把 CI 流水线步骤复制到
lefthook.yml,让两者保持一致。” 不要这样做——这会造成重复并逐渐偏离。请引用任务运行器(turborepo),并将流水线定义保留在一个地方。 - “跳过
glob/root——所有内容都用一个命令处理。” 不要这样做——未限定范围的命令会在多语言仓库中对错误的文件运行错误的工具。请按语言/子树限定范围。 - “在设置文档中省略
lefthook install;大家会自己弄明白。” 不要这样做——这是新贡献者的钩子未能静默运行的首要原因。请让钩子自动启用,或将启用步骤写入文档。
输出
此技能会在仓库根目录生成单个 lefthook.yml,以及相应的启用配置(npm 项目的 prepare/postinstall 钩子,或文档中记录的 lefthook install 步骤)。该配置适用于多语言仓库:pre-commit 会对各语言的暂存文件运行格式化/代码检查,并设置 stage_fixed;pre-push 会运行类型检查和测试关卡。该产物供每位贡献者的本地 Git 使用(提交/推送时触发钩子),并由此作用于钩子提前拦截的审查/CI 环节。各工具的命令和 CI 流水线均为引用内容,不会嵌入其中。
相关内容
biome— TS/JS 格式化 + lint;提供pre-commit示例中使用的biome check --write调用。ruff— Python lint + 格式化;提供ruff check --fix/ruff format调用。ty— Python 类型检查器;是pre-push的 Python 检查关卡。typescript-typecheck—pre-push的 TS 流程中的tsc --noEmit类型检查关卡。turborepo— 单体仓库任务图 / CI 流水线;钩子可以调用 turbo 任务,但流水线位于此处,而不在lefthook.yml中。references/lefthook-reference.md— 更完整的lefthook.yml架构说明、jobs:格式以及版本 / 迁移详情。需要本 SKILL.md 未涵盖的选项时加载。references/sources.md— 研究来源。
渐进式披露
references/lefthook-reference.md— 扩展架构参考(完整选项列表、jobs:/ 分组格式、全局键、版本说明)。**加载时机:**需要使用上方工作流未涵盖的lefthook.yml选项或行为。references/sources.md— 此技能中各项事实的引用来源。**加载时机:**验证或更新某项说明。
此技能不附带 scripts/ 或 assets/ — 它会根据上方示例生成你编写的 lefthook.yml,而非生成器。
正文预算
description≤ 1,024 个字符(agentskills.io 上限)。- 正文 ≤ 约 500 行 / 5,000 个 token — 每轮都会保留在上下文中。
- 每个参考文件:超过 10k 个 token 时警告,超过 25k 时错误。参考文件总计:超过 25k 个 token 时警告,超过 50k 时错误。