bahaospanov/skills/tree/main/git-gates
git-gates
一个基于 function hooks 的 Claude Code mod,用来给 git 工作设门禁:没有用户在当前工作阶段授权就阻止 commit、push 或 merge;拒绝推送已经落地的分支;审查 commit 消息(Conventional Commits 格式、issue 引用,以及对正文的 Haiku 检查)和 MR 描述;检查推送的一组提交是否在每个提交上都保持项目可用;并在工作合并且干净后清理工作树和分支。
关于这个 mod
Bakhtiyar Ospanov 的 agent skills 和 Claude Code 外挂:基于 function hooks 构建的外挂。
Skills
对于 skills CLI 支持的任意 agent:
npx skills add bahaospanov/skills --skill <skill>
或者在 Claude Code 中把它们作为一个外挂全部使用,通过 /bahaospanov-skills:<skill> 调用:
/plugin marketplace add bahaospanov/skills
/plugin install bahaospanov-skills@bahaospanov
| Skill | 用途 |
| --- | --- |
| prototype-stages | mattpocock/skills prototype(MIT)的 UI 分支重制版:完整流程,按阶段归组的选项,以及一键面板 |
| scheme | 用 ASCII 图表示代码变更:控制流、数据流、前后对比或分层 |
Mods
早期体验版;API 会随版本变化。
每个用途使用一个 mod。
| Mod | 用途 | | --- | --- | | git-gates | git 工作经过授权并保持整洁 | | lean-docs | 值得保留的文档 | | lean-comments | 值得保留的注释 | | lean-scripts | 值得保留的脚本 |
Haiku 审查先在代码中设门禁,因此无法让审查失败的调用不会产生模型调用。每个工作阶段结束时,检查会读取该阶段触及的仓库中的 git diff(也包括 Bash 编辑),每个工作阶段最多发送两次后续提示。
git-gates
推送属于部署,因此 agent 需要用户在当前工作阶段明确表示同意:开启它的提示,或运行期间输入的消息都算;后台任务的报告不会撤回同意。若同意检查本身失败,该调用会被阻止。
| Check | 运行于 | 需要 | 然后 | | --- | --- | --- | --- | | consent | git commit、push;PR/MR merge | 当前工作阶段输入了 commit、push、ship、deploy、pr、mr、tag 或 release,并且受保护分支必须点名 | 阻止调用 | | grants | 工作阶段后续的 commit | 当前工作阶段有每个任务一次的 commit 请求,或在授权消息后使用 grant 工具 | commit 消耗一次授权 | | messages | git commit | Conventional Commits 标题、没有预先写好的审查结论,且最后一行包含 issue 或 ticket(#87、#BLK-23);如果有消息或分支名,就使用其中一个 | 阻止 commit | | descriptions | 设置 MR/PR 描述 | 第 0 列的固定标签区块 | 阻止调用 | | landed branch | git push | 分支已推送的 head 已经位于受保护分支中,而且消息没有提到新 MR | 阻止 push | | every commit works | 推送 2 到 15 个没有远端的提交 | Sonnet:不能有某个提交删除之后提交才会恢复使用的内容,也不能引用之后提交才会加入的内容;消息说明顺序没问题时跳过 | 阻止 push | | stale work | 工作阶段结束时 | 该工作阶段曾 commit 或 push 的分支,其 head 位于整合分支中,且工作树干净 | 检查后提示删除本地及 origin 上的工作树和分支 |
受保护分支来自仓库自己的 push policy 文件。整合分支是受保护的分支;没有策略时,则使用远端默认分支,以及存在的 dev、develop、main、master。
只要 origin 上的分支 head 已经位于整合分支中,删除该远端分支就不需要关键词。
只有在有远端的仓库中,单独的 #87 才算有效;没有 issue tracker 时不会提出要求。你输入的 issue 会绑定到该工作阶段仓库及其工作树中的 commit;以 issue 编号结尾的分支(perf/mobile-lcp-89)可以让消息用该编号结尾。
lean-docs
| Check | 运行于 | 标记 | 然后 | | --- | --- | --- | --- | | docs-review | git checkout 中新增或扩充的文档 | Haiku:任务结束后无人再读的文档(runbook、设置页、旁白) | Claude 会收到原因 | | docs-no-repeat-code | 正在写入文档的行 | 已在某个代码文件中一起出现的标识符 | 拒绝写入 | | limit-docs | 工作阶段结束时 | 新增或扩充的文档、正文多于代码、文档重复代码 | 后续提示 |
lean-comments
默认不写注释:保留记录测量值、陷阱或不变量的注释,删除复述代码或叙述变更的注释。
| Check | 运行于 | 标记 | 然后 | | --- | --- | --- | --- | | limit-edits | Write 或 Edit | 新增超过 3 行注释,或编辑周围区域注释过多 | Claude 会收到指导 | | limit-turns | 工作阶段结束时 | 该工作阶段每个文件 diff 中新增超过 3 行注释 | 后续提示 |
lean-scripts
| Check | 运行于 | 标记 | 然后 | | --- | --- | --- | --- | | scripts-review | 在 git checkout 中编写或扩充的脚本 | Haiku:其实需要时重新输入即可的脚本 | Claude 会收到原因 |
安装 mods
只有启用 function hooks 后 mods 才会加载,所以先在 shell profile 中导出:
export CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1
没有这个设置,Claude Code 会静默跳过 mods。然后在 Claude Code 中:
/plugin marketplace add bahaospanov/skills
/plugin install <mod>@bahaospanov
开发
每个 mod 使用一个资料夹。共享 tsconfig.json 和 types/。已安装的 mod 只携带自己的资料夹,因此两个 mod 共享的代码会复制到各自的 hooks/shared/。
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir ./<mod> --debug
保存 <mod>/hooks/ 下的文件会重新加载 mod。重复 --plugin-dir 即可加载多个 mod。
检查
npm run typecheck # 对每个 mod 及其测试运行 tsc
npm run check:shared # 检查 hooks/shared/ 副本在各 mod 中是否一致
claude plugin validate ./<mod> # 引擎看到的模块 hook 和调用
claude plugin test ./<mod> # 该 mod 的测试
Types
types/ 由 /plugin-types types 写入,该命令要在按上述方式启动的工作阶段中运行。以下情况需要重新生成,不能手动编辑:
- Claude Code 更新(比较
head -1 types/claude-code.d.ts与claude --version) - 启用或停用会向
$增加内容的外挂 - 连接或断开 MCP 服务器
提交生成的结果;git diff types/ 会显示有哪些变更。
安装
请先查看作者 README 确认 marketplace 和插件名称;命令可能随仓库结构改变。
claude plugin marketplace add bahaospanov/skills claude plugin install git-gates
原文 / README
bahaospanov
Bakhtiyar Ospanov's agent skills, and Claude Code mods: plugins built on function hooks.
Skills
For any agent the skills CLI supports:
npx skills add bahaospanov/skills --skill <skill>
Or in Claude Code, all of them as one plugin, invoked as /bahaospanov-skills:<skill>:
/plugin marketplace add bahaospanov/skills
/plugin install bahaospanov-skills@bahaospanov
| Skill | Purpose |
| --- | --- |
| prototype-stages | mattpocock/skills prototype (MIT), its UI branch reworked: whole flows, options grouped by stage in a one-click panel |
| scheme | ASCII schematic of a code change: control flow, data flow, before/after, or layers |
Mods
Early access; the API changes between releases.
One mod per purpose.
| Mod | Purpose | | --- | --- | | git-gates | Git work is authorized and tidy | | lean-docs | Docs worth keeping | | lean-comments | Comments worth keeping | | lean-scripts | Scripts worth keeping |
Haiku reviews are gated in code first, so a call that cannot fail the review costs no model call. End-of-turn checks read the git diff of repos the turn touched, Bash edits included, and send at most two follow-up prompts a session.
git-gates
Pushing is a deploy, so the agent needs the user's word in the current turn: the prompt that opened it or one typed while it ran. If a consent check itself fails, the call is blocked.
| Check | Runs on | Needs | Then | | --- | --- | --- | --- | | consent | git commit, push; PR/MR merge | commit, push, ship, deploy, pr, mr, tag or release typed in the current turn (a message typed while it runs or a background task's report does not withdraw it); merge needs "merge"; a protected branch must be named | Call denied | | grants | Later commits in the session | A message asking for a commit per task, or the grant tool after an authorizing message | Commits spend the grant; pushes never | | messages | A git commit | Conventional Commits subject, no reviewer pre-answers, a last line with the issue or ticket (#87, #BLK-23) when your messages or the branch name one; then Haiku: a body only when the cause is subtle | Commit denied | | descriptions | Setting an MR/PR description | Fixed-label blocks at column 0 | Call denied | | landed branch | A git push | The branch's pushed head already sits in a protected branch, and the message names no new MR | Push denied | | every commit works | A git push of 2 to 15 commits no remote has | Sonnet: no commit removes something a later one stops using, or uses something a later one adds; skipped when the message says the order is fine | Push denied | | stale work | The end of a turn | A branch the session committed to or pushed whose head sits in an integration branch, its worktree clean | Follow-up prompt to remove the worktree and the branch, local and on origin, once checked |
Protected branches come from a repo's own push policy file.
Integration branches are the protected ones; with no policy, the remote's default branch and any of dev, develop, main, master that exist.
Deleting a branch on origin needs no keyword when origin's head of it already sits in an integration branch.
A bare #87 counts only in a repo with a remote; with no issue tracker, nothing is asked.
Issues you typed bind only commits in the session's repo and its worktrees; a branch ending in its issue number (perf/mobile-lcp-89) lets the message end with that one instead.
lean-docs
| Check | Runs on | Flags | Then | | --- | --- | --- | --- | | docs-review | A doc grown in a git checkout | Haiku: text nobody reads after the task (runbooks, setup pages, narration) | Claude gets the reason | | docs-no-repeat-code | A doc line being written | Identifiers that already appear together in one code file | Write denied | | limit-docs | The end of a turn | New or grown docs, prose outweighing code, doc lines repeating code | Follow-up prompt |
lean-comments
No comments by default: keep the ones that record a measured number, a trap or an invariant, cut the ones that restate the code or narrate the change.
| Check | Runs on | Flags | Then | | --- | --- | --- | --- | | limit-edits | A Write or Edit | More than 3 added comment lines, or a comment-heavy region around the edit | Claude gets the guidance | | limit-turns | The end of a turn | More than 3 new comment lines per file in the turn's diff | Follow-up prompt |
lean-scripts
| Check | Runs on | Flags | Then | | --- | --- | --- | --- | | scripts-review | A script written or grown in a git checkout | Haiku: scripts you could just type again when needed | Claude gets the reason |
Install mods
Mods load only with function hooks enabled, so export this in your shell profile first:
export CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1
Without it Claude Code skips the mods silently. Then, in Claude Code:
/plugin marketplace add bahaospanov/skills
/plugin install <mod>@bahaospanov
Develop
One folder per mod. tsconfig.json and types/ are shared. An installed mod
carries only its own folder, so code two mods share is copied into each one's
hooks/shared/.
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir ./<mod> --debug
Saving a file under <mod>/hooks/ reloads the mod. Repeat --plugin-dir to
load several.
Check
npm run typecheck # tsc over every mod and its tests
npm run check:shared # hooks/shared/ copies are identical across mods
claude plugin validate ./<mod> # what the engine sees the module hook and call
claude plugin test ./<mod> # the mod's tests/
Types
types/ is written by /plugin-types types, run inside a session started as
above. Regenerate, never edit, when:
- Claude Code updates (
head -1 types/claude-code.d.tsvsclaude --version) - a plugin that adds to
$is enabled or disabled - an MCP server is connected or disconnected
Commit the result; git diff types/ shows what the update changed.
