<!-- title: 动态注入+脚本校验打造提交守卫 -->

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

## 概述

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

## 前置条件

- 已安装 Claude Code（v2.1.145 及以上，以支持完整的 Skill 特性）
- 本机有 Git，且当前在一个 Git 仓库中工作
- 了解 Conventional Commits 基本语法（`feat:`、`fix(scope):` 等）
- 一个可执行的 Bash 环境（Windows 用户需启用 PowerShell 或 WSL）

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

Claude Code 的 Skill 已与旧版 custom commands 合并。一个 Skill 就是一个**目录**，入口是 `SKILL.md`，由两部分组成：顶部的 YAML frontmatter（告诉 Claude **何时**用），其后的 Markdown 正文（告诉 Claude **怎么做**）。官方约定的目录结构如下：

```text
commit-guard/
├── SKILL.md                 # 主指令（必需）
├── reference.md             # 规范细则（按需加载）
└── scripts/
    └── validate-commit-msg.sh   # 确定性校验脚本（执行，不读入上下文）
```

**关键认知**：一旦 Skill 被触发，它渲染后的正文会作为一条消息进入会话，**整个 session 都留在上下文里**。因此正文要「说做什么」而非「讲为什么」，并尽量把厚重内容挪到按需加载的 `reference.md`。官方建议 `SKILL.md` 控制在 **500 行以内**。

先建目录（个人级，跨你所有项目可用）：

```bash
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`：

```yaml
---
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` 后：

- 只有**你**手动输入 `/commit-guard` 才会触发
- Claude **不会**自动加载它，连它的 description 都不会进上下文

调用控制三态对照（务必记牢）：

| 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` 正文区追加：

```markdown
## 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"
```

赋予执行权限：

```bash
chmod +x ~/.claude/skills/commit-guard/scripts/validate-commit-msg.sh
```

接着在 `SKILL.md` 末尾，用动态注入调用它，并把**校验规则**作为生成指令写入正文：

```markdown
## 提交信息生成规则

严格遵循 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`。

**验证脚本单独可用**：

```bash
~/.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` 全貌如下：

```markdown
---
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）：

```markdown
# 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** 的规范提交。

## 工作流程图

```mermaid
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 Code 官方文档 · Skills**：<https://code.claude.com/docs/en/skills> —— frontmatter 全字段、动态注入、subagent 执行的权威来源
- **Anthropic 工程博客 · Lessons from building Claude Code: How we use skills**（2026-06-03）：<https://claude.com/blog/lessons-from-building-claude-code-how-we-use-skills> —— Anthropic 内部数百个 Skill 的规模化管理经验
- **Agent Skills 开放标准**：<https://agentskills.io> —— Skill 跨工具互通的规范基础
- **Conventional Commits 1.0.0**：<https://www.conventionalcommits.org> —— 本教程校验脚本所依据的规范
- **Claude Code Skill Frontmatter 全字段指南**：<https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide> —— 15 个 frontmatter 字段的逐项释义

## 训练营适用性评估

- **适合类型**：中高级开发者训练营、DevOps 工程效率训练营、AI 辅助研发实战营。不适合零基础（需先掌握 Claude Code 基本使用与 Git）。
- **技术锚点价值**：本教程刻意聚焦三个常被忽略的「确定性」机制——动态注入取数、`${CLAUDE_SKILL_DIR}` 脚本工程化、frontmatter 调用控制。掌握后可迁移到任何「需要真实数据 + 确定性护栏」的 Skill 场景。
- **预计教学时长**：讲解 + 跟练约 **90 分钟**（含 Bash 脚本编写与触发调试）；若作为课后项目独立完成，约 **2–3 小时**。
- **延伸练习建议**：① 把校验脚本升级为支持 breaking change 与多 scope；② 加 `context: fork` 让生成在隔离 subagent 跑，主上下文不被 diff 污染；③ 用 Evals 给提交信息质量打分，闭环优化 description。

## 参考来源

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

> [!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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
