mcpField
P
polyglot-git-hooks

polyglot-git-hooks

github.com/bm629开发593 次安装1 天前更新
代码仓库

关于 polyglot-git-hooks

polyglot-git-hooks 是用于在多语言仓库中配置 Lefthook 的技能,可为暂存文件和推送设置本地 Git 检查,并与 CI 配合而非取代 CI。

主要功能

  1. 01在仓库根目录配置 lefthook.yml
  2. 02提交前检查暂存文件
  3. 03推送前运行类型检查和测试
  4. 04按子目录配置语言专属命令
  5. 05并行执行并暂存自动修复

适用场景

  1. 01为仓库添加本地格式与 lint 检查
  2. 02配置 TypeScript 和 Python 子目录的钩子
  3. 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 发布)。lefthook npm 包会运行自己的 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 时错误。

评论

支持 Markdown