首页 › tutorials › 动态注入+脚本校验打造提交守卫

动态注入 + 脚本校验:从零打造提交守卫 Skill

概述

本教程手把手带你构建一个 提交守卫 Skill(/commit-guard):它自动读取暂存区 diff,按 Conventional Commets 规范生成提交信息,并用自带脚本在「落笔」前做确定性校验。你将吃透 Claude Code Skill 的三个核心机制——动态上下文注入、${CLAUDE_SKILL_DIR} 脚本工程化、frontmatter 调用控制与工具预批准。适合已会基本使用 Claude Code、想把重复工作流沉淀为可复用资产的开发者。

前置条件

第 1 步:理解 Skill 的「三段式」骨架

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 让它开始监听。

第 2 步:写好 description,决定它「会不会被想起」

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 *)
---

这里藏着三个关键决策,逐一拆解。

2.1 disable-model-invocation: true:谁来按「扳机」

默认情况下,你和 Claude 都能触发任何 Skill。但「提交」是有副作用、需要你掌控时机的动作——你绝不想 Claude 因为「代码看起来 ready 了」就擅自 git commit。设为 true 后:

调用控制三态对照(务必记牢):

frontmatter 你能调用 Claude 能调用 何时进上下文
默认 是 是 description 常驻,触发时加载全文
disable-model-invocation: true 是 否 description 不进上下文,你调用时才加载
user-invocable: false 否 是 description 常驻,触发时加载全文

2.2 allowed-tools:免确认预批准

列在此处的工具,在 Skill 激活期间无需逐次确认即可调用。注意它不限制可用工具集(其他工具仍在,仍受权限设置约束),只是给这几把「钥匙」开了绿灯。格式支持空格/逗号分隔的字符串,或 YAML 列表,且支持带参数的模式匹配,例如 Bash(git commit *)。

常见问题:项目级 .claude/skills/ 里的 allowed-tools,需先接受工作区信任对话框才生效——审查陌生仓库的 Skill 时要警惕,因为 Skill 可以给自己授予很宽的工具权限。

2.3 其余可选字段速览(按需取用)

字段 作用
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

第 3 步:动态注入,把「真实 diff」钉进提示词

这是本 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。

第 6 步:本地测试与调优触发

实测路径:

  1. 在任一 Git 仓库改一个文件并 git add 暂存。
  2. 启动 Claude Code,输入 /commit-guard。
  3. 预期:Claude 读到已渲染的 diff,按规则生成首行,主动调用校验脚本,PASS 后执行 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 一次即可恢复全文。

实战案例:完整可运行的 SKILL.md

把前几步合并,最终 ~/.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

图中蓝色为动态注入(预处理、确定性取数),橙色为脚本校验(确定性护栏),红色为分支判空——三道关卡共同把「提交信息」这件模糊的事钉死在规范里。

延伸阅读

训练营适用性评估

参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方权威文档,本文 frontmatter 字段表、动态注入语法、${CLAUDE_SKILL_DIR}、生命周期机制均出自此处
  2. Lessons from building Claude Code: How we use skills — Claude Blog — Anthropic 内部 Skill 工程化实践经验(2026-06-03)
  3. Claude Code Skill Frontmatter: Every YAML Option Explained — 15 个 frontmatter 字段的逐项实例讲解
  4. anthropics/skills — GitHub — 官方公开 Skill 仓库,含指令、脚本、资源的组织范式参考
  5. Conventional Commits 1.0.0 — 校验脚本所依据的提交信息规范

[!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 等场景全覆盖。

51学通信知识星球海报-扫码加入