本教程手把手带你构建一个 提交守卫 Skill(/commit-guard):它自动读取暂存区 diff,按 Conventional Commets 规范生成提交信息,并用自带脚本在「落笔」前做确定性校验。你将吃透 Claude Code Skill 的三个核心机制——动态上下文注入、${CLAUDE_SKILL_DIR} 脚本工程化、frontmatter 调用控制与工具预批准。适合已会基本使用 Claude Code、想把重复工作流沉淀为可复用资产的开发者。
feat:、fix(scope): 等)Claude Code 的 Skill 已与旧版 custom commands 合并。一个 Skill 就是一个目录,入口是 SKILL.md,由两部分组成:顶部的 YAML frontmatter(告诉 Claude 何时用),其后的 Markdown 正文(告诉 Claude 怎么做)。官方约定的目录结构如下:
commit-guard/
├── SKILL.md # 主指令(必需)
├── reference.md # 规范细则(按需加载)
└── scripts/
└── validate-commit-msg.sh # 确定性校验脚本(执行,不读入上下文)
关键认知:一旦 Skill 被触发,它渲染后的正文会作为一条消息进入会话,整个 session 都留在上下文里。因此正文要「说做什么」而非「讲为什么」,并尽量把厚重内容挪到按需加载的 reference.md。官方建议 SKILL.md 控制在 500 行以内。
先建目录(个人级,跨你所有项目可用):
mkdir -p ~/.claude/skills/commit-guard/scripts
存放位置与作用范围对照:
| 层级 | 路径 | 作用范围 |
|---|---|---|
| 个人 | ~/.claude/skills/commit-guard/SKILL.md |
你的所有项目 |
| 项目 | .claude/skills/commit-guard/SKILL.md |
仅当前项目 |
| 插件 | <plugin>/skills/commit-guard/SKILL.md |
启用该插件处 |
| 企业 | managed settings | 全组织 |
同名时优先级:企业 > 个人 > 项目。提交守卫这种通用工具放个人级最合适。
常见问题:目录建好后,Claude Code 会实时监听 skill 目录变化,新增/编辑无需重启即生效——但前提是 ~/.claude/skills/ 这个顶层目录在会话启动时就已存在;如果是首次创建,需重启一次 Claude Code 让它开始监听。
SKILL.md 的 frontmatter 中,唯一推荐字段是 description。Claude 正是靠它判断何时自动加载本 Skill。description 与可选的 when_to_use 拼接后,在 skill 列表里会被截断到 1536 个字符——所以要把最关键的触发场景写在最前面。
写入 ~/.claude/skills/commit-guard/SKILL.md:
---
description: Generate a Conventional Commits message from staged changes and validate it before committing. Use when the user wants to commit, write a commit message, or says "提交"/"commit"/"帮我提交". disable-model-invocation is set, so invoke manually via /commit-guard.
disable-model-invocation: true
allowed-tools:
- Bash(git diff *)
- Bash(git commit *)
- Bash(git status *)
---
这里藏着三个关键决策,逐一拆解。
disable-model-invocation: true:谁来按「扳机」默认情况下,你和 Claude 都能触发任何 Skill。但「提交」是有副作用、需要你掌控时机的动作——你绝不想 Claude 因为「代码看起来 ready 了」就擅自 git commit。设为 true 后:
/commit-guard 才会触发调用控制三态对照(务必记牢):
| frontmatter | 你能调用 | Claude 能调用 | 何时进上下文 |
|---|---|---|---|
| 默认 | 是 | 是 | description 常驻,触发时加载全文 |
disable-model-invocation: true |
是 | 否 | description 不进上下文,你调用时才加载 |
user-invocable: false |
否 | 是 | description 常驻,触发时加载全文 |
allowed-tools:免确认预批准列在此处的工具,在 Skill 激活期间无需逐次确认即可调用。注意它不限制可用工具集(其他工具仍在,仍受权限设置约束),只是给这几把「钥匙」开了绿灯。格式支持空格/逗号分隔的字符串,或 YAML 列表,且支持带参数的模式匹配,例如 Bash(git commit *)。
常见问题:项目级 .claude/skills/ 里的 allowed-tools,需先接受工作区信任对话框才生效——审查陌生仓库的 Skill 时要警惕,因为 Skill 可以给自己授予很宽的工具权限。
| 字段 | 作用 |
|---|---|
name |
列表中的显示名,默认取目录名;不影响你输入的 /命令(命令名来自目录/路径) |
when_to_use |
追加到 description,计入 1536 字符上限,用于写触发短语 |
argument-hint |
自动补全时的参数提示,如 [issue-number] |
arguments |
声明命名位置参数,配合 $name 替换 |
user-invocable |
设 false 则从 / 菜单隐藏(纯背景知识用) |
disallowed-tools |
激活期间从工具池移除某些工具(如自动循环里禁掉 AskUserQuestion) |
model |
本 Skill 激活时切换的模型,如 haiku 降本;下一条消息恢复会话模型 |
effort |
推理强度:low/medium/high/xhigh/max |
context: fork |
在 forked subagent 上下文中运行(隔离主会话) |
agent |
配合 context: fork 指定 subagent 类型 |
paths |
glob 模式,仅操作匹配文件时才自动激活 |
shell |
动态注入命令用 bash(默认)还是 powershell |
这是本 Skill 的第一处「独门绝技」。传统做法是让 Claude 自己调用工具去读 diff——它得猜、得调工具、还可能读错文件范围。动态上下文注入用 !`命令` 语法,在 Skill 正文发给 Claude 之前就执行命令,把输出原地替换进正文。Claude 看到的永远是已经渲染好的真实数据。
在 SKILL.md 正文区追加:
## Staged changes
Current branch: !`git rev-parse --abbrev-ref HEAD`
Staged diff:
```!git diff --staged --stat
git diff --staged
Unstaged files (for awareness, do NOT include unless told):
!git status --short
**执行机制要点**(容易踩坑):
1. 每个 `` !`cmd` `` 在 Claude 看到任何内容前**立即执行一次**,输出替换占位符——这是**预处理**,不是 Claude 执行的。
2. 替换**只跑一遍**:命令输出以纯文本插入,**不会被再次扫描**占位符,因此一个命令无法「吐出」一个供下一轮展开的占位符。
3. 行内形式 `` !`cmd` `` 仅在 `!` 出现在**行首**或**紧跟空白**时才被识别。写成 `KEY=!`cmd`` 这种紧贴形式,会被当作字面文本、**不执行**。
4. 多行命令用 ``` ```! ``` 围栏代码块(如上例),比行内形式更适合 `git diff` 这类多行输出。
**常见问题**:命令报错(比如暂存区为空)时,输出区会显示错误信息。可在第 5 步的校验脚本里前置判空,或直接让 Claude 读到空 diff 后提示用户先 `git add`。
> 如需在受控环境禁用此特性,可在 settings 设 `"disableSkillShellExecution": true`,每条命令会被替换成 `[shell command execution disabled by policy]`(bundled/managed skill 不受影响)。
## 第 4 步:`${CLAUDE_SKILL_DIR}` 让脚本工程化
LLM 生成的提交信息天生「不靠谱」——它会拼错类型名、超长、漏冒号。**确定性校验必须交给脚本**,而不是寄望于提示词约束。Skill 支持用 `${CLAUDE_SKILL_DIR}` 引用自身 `SKILL.md` 所在目录,从而在动态注入里调用自带的脚本,**无论当前工作目录在哪**都能找到。
先写校验脚本 `scripts/validate-commit-msg.sh`:
```bash
#!/usr/bin/env bash
# 校验 Conventional Commits 提交信息。用法: validate-commit-msg.sh "<message>"
set -euo pipefail
msg="$1"
# 1) 类型白名单 + 必须有冒号
regex='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9_-]+\))?(!)?: .{1,72}$'
header=$(printf '%s' "$msg" | head -n1)
if ! printf '%s' "$header" | grep -Eq "$regex"; then
echo "FAIL: 首行不符合 Conventional Commits 规范。"
echo " 期望: type(scope)!: subject(subject ≤72 字符)"
echo " 实际: $header"
exit 1
fi
# 2) subject 长度(中文按字符计)
subj_len=${#header}
if [ "$subj_len" -gt 80 ]; then
echo "FAIL: 首行过长($subj_len 字符 > 80)。"
exit 1
fi
echo "PASS: $header"
赋予执行权限:
chmod +x ~/.claude/skills/commit-guard/scripts/validate-commit-msg.sh
接着在 SKILL.md 末尾,用动态注入调用它,并把校验规则作为生成指令写入正文:
## 提交信息生成规则
严格遵循 Conventional Commits:`type(scope)!: subject`。
- type ∈ feat/fix/docs/style/refactor/perf/test/build/ci/chore/revert
- scope 可选,小写;`!` 表示 breaking change
- subject ≤ 72 字符,祈使句、句末不加句号
- 仅基于上方 **Staged changes**,不得包含未暂存改动
## 落笔前必做:确定性校验
在 `git commit` 之前,用下方脚本校验你草拟的**首行**。若返回 FAIL,按提示修正后重试,**严禁**跳过校验直接提交:
```!msg=$(printf 'feat(api): add /health endpoint')
"${CLAUDE_SKILL_DIR}/scripts/validate-commit-msg.sh" "$msg" || echo "(替换为真实首行后再次运行)"
注意:上方围栏内的
$msg仅为示例占位,实际请把你生成的真实首行作为参数传入。校验通过后再执行git commit -m "$(cat)"的多行提交。
**为什么这样设计**:`${CLAUDE_SKILL_DIR}` 是 Skill 的「自指」锚点,让脚本随 Skill 一起分发、路径无关。校验脚本返回非零退出码时,配合「严禁跳过」的指令,构成一道**确定性护栏**——这是提示词无法保证的。这也是为什么官方把 scripts 定义为「**执行而不读入上下文**」的文件:它消耗的是 CPU 不是 token。
**常见问题**:Windows 用户若未启用 PowerShell 工具,`${CLAUDE_SKILL_DIR}` 下的 `.sh` 无法直接跑。两种解法:① 在 WSL 里用 Claude Code;② 把脚本改写为 `.ps1` 并在 frontmatter 设 `shell: powershell`(需 `CLAUDE_CODE_USE_POWERSHELL_TOOL=1`)。
## 第 5 步:参数化与多行提交(可选增强)
若你想 `/commit-guard` 时附加「关联的 issue 号」等参数,Skill 支持位置与命名参数替换。在 frontmatter 加:
```yaml
arguments: [ticket]
正文里即可用 $ticket(等价于 $0 / $ARGUMENTS[0])。可用替换变量一览:
| 变量 | 含义 |
|---|---|
$ARGUMENTS |
全部参数原文 |
$ARGUMENTS[N] / $N |
第 N 个参数(0 起) |
$name |
arguments 声明的命名参数 |
${CLAUDE_SESSION_ID} |
当前会话 ID |
${CLAUDE_EFFORT} |
当前推理强度 |
${CLAUDE_SKILL_DIR} |
本 Skill 目录 |
例如正文写 提交信息末尾追加 footer:Refs #$ticket,执行 /commit-guard 4823 即会展开成 Refs #4823。多词参数用引号包裹:/commit-guard "hello world" second 使 $0=hello world、$1=second。正文中要写字面 $1.00,用反斜杠转义 \$1.00。
实测路径:
git add 暂存。/commit-guard。git commit。验证脚本单独可用:
~/.claude/skills/commit-guard/scripts/validate-commit-msg.sh "feat: ok"
# 输出: PASS: feat: ok
~/.claude/skills/commit-guard/scripts/validate-commit-msg.sh "updated stuff"
# 输出: FAIL: 首行不符合 Conventional Commits 规范。
调优触发率(若自动触发不灵敏):
- 把用户高频口语(「提交」「commit」「帮我提交」)写进 description 前部。
- 注意本 Skill 因 disable-model-invocation: true 不会自动触发,这是设计预期;若你希望它也能被自动想起,去掉该字段即可。
理解 Skill 的「生命周期」:被触发后,渲染正文会整条留在会话直到结束,Claude Code 不会逐轮重读文件——所以把「全程适用的常驻规则」写进正文,而非「一次性步骤」。当上下文触发 auto-compaction 时,已触发的 Skill 会按「最近优先」重新挂回,每个保留前 5000 token,合计 25000 token 预算;调了太多 Skill 时,较早的可能被整体丢弃,必要时重新 /commit-guard 一次即可恢复全文。
把前几步合并,最终 ~/.claude/skills/commit-guard/SKILL.md 全貌如下:
---
description: Generate a Conventional Commits message from staged changes and validate it before committing. Use when the user wants to commit, write a commit message, or says "提交"/"commit"/"帮我提交". Invoked manually via /commit-guard.
disable-model-invocation: true
allowed-tools:
- Bash(git diff *)
- Bash(git commit *)
- Bash(git status *)
arguments: [ticket]
---
## Staged changes
Branch: !`git rev-parse --abbrev-ref HEAD`
Diff stat:
```!git diff --staged --stat
Full staged diff: ```!git diff --staged
Unstaged (context only, do NOT commit): !`git status --short`
## 生成规则
遵循 Conventional Commits `type(scope)!: subject`:
- type ∈ feat/fix/docs/style/refactor/perf/test/build/ci/chore/revert
- subject ≤ 72 字符,祈使句,句末无句号
- 仅基于上方 staged diff;若 diff 为空,提示用户先 `git add` 后停止
- 若提供了 $ticket,在正文后追加空行 + `Refs #$ticket`
## 落笔前确定性校验(严禁跳过)
把生成的真实首行传入脚本校验,FAIL 则修正重试:
```!"${CLAUDE_SKILL_DIR}/scripts/validate-commit-msg.sh "<你的真实首行>"```
PASS 后再用多行 `git commit` 提交(首行 + 空行 + 正文 + footer)。完成。
配套 reference.md(按需加载的规范细则,被 SKILL.md 引用即按需读取,省 token):
# Conventional Commits 完整规范摘要
- breaking change: type 后加 `!`,footer 写 `BREAKING CHANGE: <说明>`
- revert: `revert: <原首行>`,footer 写 `This reverts commit <hash>.`
- 多 scope 用 `/` 分隔,如 `feat(api/parser): ...`
跑一遍 /commit-guard 4823,你会得到一条经过脚本确定性校验、基于真实 diff、带 ticket footer 的规范提交。
flowchart TD
A[输入 /commit-guard ticket] --> B[动态注入执行<br/>git diff --staged]
B --> C{暂存区为空?}
C -- 是 --> Z[提示先 git add, 停止]
C -- 否 --> D[Claude 读到真实 diff<br/>按规则生成首行+正文]
D --> E[调用 SKILL_DIR 校验脚本]
E --> F{PASS?}
F -- 否 --> G[按报错修正首行] --> E
F -- 是 --> H[git commit 多行提交]
H --> I[完成规范提交]
style B fill:#e1f5ff
style E fill:#fff4e1
style F fill:#ffe1e1
图中蓝色为动态注入(预处理、确定性取数),橙色为脚本校验(确定性护栏),红色为分支判空——三道关卡共同把「提交信息」这件模糊的事钉死在规范里。
${CLAUDE_SKILL_DIR} 脚本工程化、frontmatter 调用控制。掌握后可迁移到任何「需要真实数据 + 确定性护栏」的 Skill 场景。context: fork 让生成在隔离 subagent 跑,主上下文不被 diff 污染;③ 用 Evals 给提交信息质量打分,闭环优化 description。${CLAUDE_SKILL_DIR}、生命周期机制均出自此处[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
