<!-- title: 触发优先：从零造精准Skill -->

# 触发优先：从零造一个"不被误触"的脱敏 Skill

## 概述

本教程带你以"触发准确性"为第一目标，从零构建一个**敏感数据（PII）脱敏 Skill**。你将掌握 `SKILL.md` frontmatter 的写法、`description` 触发工程的四条原则、以及一套用回归测试量化触发命中率的迭代方法。适合已经能写出能跑的 Skill、但总遇到"该触发不触发、不该触发乱触发"困扰的开发者。载体场景选了 PII 脱敏——它天然与"日志分析""安全审查""数据库查询"等 Skill 边界模糊，是最能体现触发工程的练手题材。

## 前置条件

- 已安装 Claude Code（CLI 或 IDE 插件均可），能在任意项目里跑通 `/help` 或 `/skills`
- 了解 `SKILL.md` 是一个文件夹里的入口文件这一基本概念（不需要写过复杂 Skill）
- 本机有 Python 3（脱敏脚本用 Python 跑正则替换）
- 一个用来测试的空目录（本文用 `pii-skill-demo/`）

## 为什么"触发"是 Skill 的第一道关

很多人写 Skill 的顺序是：先想功能 → 写逻辑 → 最后随便填一句 description。结果就是 Skill 能力很强，却几乎从不被调用，或者被错误地塞进毫不相干的对话里。

Claude Code 的 Skill 系统基于**渐进式披露（progressive disclosure）**：所有 `SKILL.md` 的 frontmatter 常驻在上下文里，但正文和脚本只在被判定为"相关"时才加载。换句话说——**`name` 和 `description` 是你的 Skill 唯一长期暴露在模型决策视野里的信息**。它写得好不好，直接决定模型是否把你的 Skill 纳入候选；正文写得再精彩，触发不了就等于不存在。

所以本教程提出一个反直觉的构建顺序——**触发优先**：先画边界、再写描述、然后才是逻辑，最后用回归测试反复校准描述。

## 第 1 步：先画"触发矩阵"，再动笔

写任何代码之前，拿出一张表，把"该触发"和"不该触发"的场景列清楚。这是后续 description 工程的依据。对 PII 脱敏 Skill，矩阵大致如下：

| 类型 | 场景示例 | 期望 |
|------|---------|------|
| 正例 | "帮我把这份日志里的手机号打码" | ✅ 触发 |
| 正例 | "脱敏一下这个 CSV 里的身份证和邮箱" | ✅ 触发 |
| 正例 | "这串数据要去标识化再贴到 issue 里" | ✅ 触发 |
| 反例 | "日志里这个错误怎么排查" | ❌ 不触发（排查不是脱敏） |
| 反例 | "帮我查数据库里这张表的结构" | ❌ 不触发（查询不是脱敏） |
| 反例 | "审计这段代码有没有安全漏洞" | ❌ 不触发（审查不是脱敏） |
| 边界 | "把密钥从配置里删掉" | ❌ 不触发（这是删除机密，不是 PII 脱敏） |

注意右下角那几条反例——它们正是这个 Skill 最容易被误触发的场景。**反例的价值远大于正例**，因为它们定义了 Skill 的"围墙"。

## 第 2 步：搭建 Skill 骨架与必填 frontmatter

Skill 是一个文件夹，入口文件必须叫 `SKILL.md`。在 `pii-skill-demo/` 下创建项目级 Skill：

```
pii-skill-demo/
└── .claude/
    └── skills/
        └── pii-masking/
            ├── SKILL.md
            ├── scripts/
            │   └── mask.py
            └── references/
                └── regex-rules.md
```

`SKILL.md` 的开头是 YAML frontmatter。frontmatter 只有**两个必填字段**：`name` 和 `description`。先写一个能跑的最小版本，下一节再精修：

```markdown
---
name: pii-masking
description: Mask PII in text or files. Use when the user asks to mask, redact, or de-identify phone numbers, emails, ID cards, or bank cards in logs, CSVs, or pasted text.
---

# PII Masking

When invoked, run `scripts/mask.py` on the target text or file path.
```

字段说明：

- `name`：用 kebab-case 小写连字符，简短、与文件夹同名，便于在调用时引用（如 `/skill pii-masking`）。
- `description`：一句话写清"做什么 + 何时用"。这是模型做相关性判断的主要依据。

## 第 3 步：description 触发工程四原则

这是本教程的核心。一条合格的 description 要同时满足四条：

**原则 1：主动语态，避免第三人称被动。** 不要写 "This skill handles PII masking"（含糊、像产品介绍），而是写 "Mask PII… Use when the user asks to mask…"（给出明确的触发动作和场景信号）。

**原则 2：正向触发词清单。** 把用户可能说出的关键词密集铺进描述：mask、redact、de-identify、anonymize、打码、脱敏、去标识化。模型做的是模式匹配，覆盖同义词能提高召回。

**原则 3：负向排除。** 显式列出"何时不触发"。这是把误触发率压下去的关键手段：`Do NOT trigger for log debugging, DB queries, secret deletion, or general code review.`

**原则 4：与相邻 Skill 差异化。** 如果你环境里已有"安全审查 Skill"，就别在脱敏描述里用"audit / review"这类词，否则两者会争抢同一个请求。

按四原则重写后：

```yaml
description: Mask or redact personally identifiable information (PII) — phone numbers, email addresses, ID-card numbers, bank card numbers — in files or pasted text. Use this skill when the user asks to mask, redact, anonymize, de-identify, "打码", or "脱敏" such data before sharing, pasting into issues, or committing. Do NOT trigger for log debugging, database queries, secret/key deletion, or general security code review — those belong to other skills.
```

## 第 4 步：用可选字段给触发加"保险"

frontmatter 还有一批可选字段，其中两个对触发控制尤其重要：

- `allowed-tools`：白名单限制这个 Skill 能调用的工具，比如脱敏 Skill 只需要 `Bash`（跑脚本）和 `Read`（读文件），就显式列出，防止它越权改文件。
- `disable-model-invocation`：**`true` 表示禁止模型自动触发，只能由用户显式调用**。当你发现某个 Skill 反复误触发、怎么调 description 都压不住时，这是一个"核选项"——把它降级为纯手动 Skill。

```yaml
---
name: pii-masking
description: <上面精修后的描述>
allowed-tools:
  - Bash
  - Read
---
```

> 说明：本文聚焦触发工程，未展开 `metadata`（分类/版本等元信息）、`license`、`version` 等字段，它们影响分发与发现，但不直接影响运行时触发决策。

## 第 5 步：写脱敏逻辑（scripts/ 工程化）

把"重活"放进 `scripts/mask.py`，让 `SKILL.md` 正文只负责说明"何时调用脚本、怎么传参"——这正是渐进式披露的精髓：正文薄、脚本厚，避免把一堆正则塞进常驻上下文。

`scripts/mask.py`（可直接运行的正则脱敏器）：

```python
#!/usr/bin/env python3
"""Mask common PII types in text from a file or stdin."""
import re, sys

RULES = [
    # 中国大陆手机号: 1[3-9]xxxxxxxxx
    (re.compile(r'(?<!\d)1[3-9]\d{9}(?!\d)'), lambda m: m.group(0)[:3] + '****' + m.group(0)[-4:]),
    # 邮箱
    (re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+'), lambda m: m.group(0)[0] + '***@' + m.group(0).split('@')[-1]),
    # 18位身份证号
    (re.compile(r'(?<!\d)\d{17}[\dXx](?!\d)'), lambda m: m.group(0)[:6] + '********' + m.group(0)[-4:]),
    # 银行卡号(16-19位连续数字)
    (re.compile(r'(?<!\d)\d{16,19}(?!\d)'), lambda m: m.group(0)[:4] + ' **** **** ' + m.group(0)[-4:]),
]

def mask(text: str) -> str:
    for pat, repl in RULES:
        text = pat.sub(repl, text)
    return text

def main():
    # 从文件或 stdin 读取
    if len(sys.argv) > 1:
        with open(sys.argv[1], encoding='utf-8') as f:
            src = f.read()
    else:
        src = sys.stdin.read()
    sys.stdout.write(mask(src))

if __name__ == '__main__':
    main()
```

`SKILL.md` 正文写明调用方式（保持简短）：

```markdown
# PII Masking

When invoked:

1. Determine the input: either a file path the user gave, or pasted text.
2. Run `python3 scripts/mask.py <path>` for a file, or pipe text in via stdin.
3. Return the masked result; never echo the original unmasked data back.

For the exact regex rules and how each PII type is masked, read `references/regex-rules.md`.
```

把每条正则的"为什么这么写"放进 `references/regex-rules.md`——它只在模型需要解释规则时才被加载。

## 第 6 步：触发回归测试——把"祈祷"变成"数据"

描述写完后，不要凭感觉说"应该能触发"。把第 1 步的触发矩阵转成一组测试提示词，逐条手测，记录结果：

```
✅ 正例（应触发）
  [T1] "把这行日志里手机号打码"
  [T2] "脱敏一下这段 CSV 里的邮箱和身份证"
  [T3] "去标识化后再贴进工单"

❌ 反例（不应触发）
  [F1] "这条日志报错啥原因"
  [F2] "查一下 users 表有哪些字段"
  [F3] "审计这段 SQL 有没有注入风险"
  [F4] "把 .env 里的密钥删掉"
```

跑一遍，统计两个核心指标：

- **召回率** = 正例中成功触发的比例（本例目标 3/3）
- **精确率** = 触发的请求中真正该触发的比例（本例目标 0 次误触发）

如果 `skill-creator` 这类工具可用，还能把这套提示词喂给它的 eval 机制做批量打分和方差分析，把触发质量量化成可追踪的数字，而不是"祈祷它工作"。

## 第 7 步：用回归数据反推描述调参

假设回归测试发现 `F4`（删密钥）被误触发了——模型看到"删掉数据"就联想到"脱敏"。这时候**只改 description**，强化边界：

```yaml
# 调整前
... Do NOT trigger for log debugging, database queries, secret/key deletion ...

# 调整后（增加显式区分：删除 vs 打码）
... Do NOT trigger for log debugging, database queries, or removing/deleting
secrets and keys — masking keeps the row but hides the value, deletion
removes it entirely. Those are different operations handled elsewhere.
```

改完再跑一次回归，看误触发是否消失。**触发优先构建法本质上是一个闭环**：画边界 → 写描述 → 跑回归 → 改描述，直到两个指标都达标。

## 整体工作流程

下面这张流程图把"触发优先构建法"的迭代闭环串起来：

```mermaid
flowchart TD
    A[画触发矩阵<br/>正例 + 反例 + 边界] --> B[写必填 frontmatter<br/>name + description 最小版]
    B --> C[description 四原则精修<br/>主动语态/正向词/负向排除/差异化]
    C --> D[可选字段加保险<br/>allowed-tools / disable-model-invocation]
    D --> E[写脱敏逻辑<br/>scripts/mask.py + references/]
    E --> F[触发回归测试<br/>召回率 + 精确率]
    F --> G{指标达标?}
    G -->|否| H[用数据反推<br/>只改 description]
    H --> C
    G -->|是| I[发布/共享 Skill]
```

## 常见问题与诊断清单

**症状：写了 Skill 但从不被触发。**
- 诊断：description 是否太短或太抽象？是否漏了用户真实会说出的同义词（含中文）？尝试把正向触发词补全。
- 诊断：`name` 是否和别的 Skill 重名导致冲突？

**症状：频繁被无关请求误触发。**
- 诊断：description 里有没有用到"review/audit/handle"这类与其他 Skill 撞车的宽泛词？
- 诊断：是否缺负向排除句？补上 `Do NOT trigger for ...`。
- 兜底：若仍压不住，设 `disable-model-invocation: true`，降级为纯手动 `/skill` 调用。

**症状：触发不稳定，时灵时不灵。**
- 诊断：边界场景是否定义清楚？把模糊反例加入回归集，固化边界。
- 诊断：脚本依赖是否齐全（Python 版本、文件路径）？触发对了但执行失败，也会让 Skill 看起来"时好时坏"。

## 实战案例完整代码

把前面所有片段拼起来，一个最小可运行的脱敏 Skill 就是这三件事：

1. `.claude/skills/pii-masking/SKILL.md`：精修后的 frontmatter + 简短正文（见第 3、5 步）
2. `.claude/skills/pii-masking/scripts/mask.py`：第 5 步的脱敏脚本
3. `.claude/skills/pii-masking/references/regex-rules.md`：每条正则的说明文档

验证它工作：

```bash
# 直接验证脚本逻辑
echo "联系人 13800138000, 邮箱 alice@test.com, 身份证 110101199001011234" | python3 .claude/skills/pii-masking/scripts/mask.py
# 期望: 联系人 138****8000, 邮箱 a***@test.com, 身份证 110101********1234

# 在 Claude Code 里验证触发
# 粘贴: "帮我把这份日志里的手机号和邮箱打码"
# 期望: 自动加载 pii-masking 并调用脚本
```

## 训练营适用性评估

- **适合的训练营类型**：Claude Code 进阶开发训练营、企业 AI 工程化训练营。学员需先掌握基础 Skill 创建（对应"从零打造首个自定义 Skill"那一课），本教程作为其进阶专题。
- **不适合**：零基础入门营——学员还没建立"Skill 是什么"的认知，直接讲触发工程会断档。
- **预计教学时长**：理论讲授 30 分钟 + 动手构建 60 分钟（含回归测试）+ 描述调参练习 30 分钟，共约 2 小时。
- **可交付成果**：每位学员带走一个可运行、触发指标达标的 PII 脱敏 Skill，以及一套可在其他 Skill 上复用的触发回归测试模板。
- **延伸练习建议**：让学员用同一套方法改造一个自己已有的"误触发"Skill，把召回率/精确率做成对比报告。

## 延伸阅读

- 官方 Skill 文档（含目录结构、bundled skills、共享机制）：<https://code.claude.com/docs/en/skills>
- `SKILL.md` frontmatter 全字段参考（含 `allowed-tools`、`disable-model-invocation` 等）：<https://claudskills.com/learn/skill-md-frontmatter-reference/>
- Frontmatter 与触发机制的深度解读：<https://deepwiki.com/kongyo2/markdown-skill-creator/8.1-frontmatter-and-skill-triggering>
- 从 YAML 到可工作 Skill 的实战教程：<https://sjramblings.io/building-skills-for-claude-part-2/>
- 5 个开箱即用 Skill 示例（含安全控制与分发）：<https://fp8.co/articles/Claude-Code-Skills-Complete-Developer-Guide>

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skill 创建、管理与共享权威文档
2. [SKILL.md frontmatter reference — ClaudSkills](https://claudskills.com/learn/skill-md-frontmatter-reference/) — 必填/可选字段与 `disable-model-invocation` 等触发控制字段解析
3. [Frontmatter and Skill Triggering — DeepWiki](https://deepwiki.com/kongyo2/markdown-skill-creator/8.1-frontmatter-and-skill-triggering) — frontmatter 如何影响 Skill 触发决策的机制说明
4. [Build a Claude Skill: YAML Frontmatter, SKILL.md Instructions, and Testing — sjramblings](https://sjramblings.io/building-skills-for-claude-part-2/) — 从 frontmatter 写到测试的实战教程
5. [Claude Code Skill Frontmatter: Every YAML Option Explained — Allahabani](https://allahabani.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — 全部 frontmatter 字段的逐项解释与示例

> [!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)
