本教程带你以"触发准确性"为第一目标,从零构建一个敏感数据(PII)脱敏 Skill。你将掌握 SKILL.md frontmatter 的写法、description 触发工程的四条原则、以及一套用回归测试量化触发命中率的迭代方法。适合已经能写出能跑的 Skill、但总遇到"该触发不触发、不该触发乱触发"困扰的开发者。载体场景选了 PII 脱敏——它天然与"日志分析""安全审查""数据库查询"等 Skill 边界模糊,是最能体现触发工程的练手题材。
/help 或 /skillsSKILL.md 是一个文件夹里的入口文件这一基本概念(不需要写过复杂 Skill)pii-skill-demo/)很多人写 Skill 的顺序是:先想功能 → 写逻辑 → 最后随便填一句 description。结果就是 Skill 能力很强,却几乎从不被调用,或者被错误地塞进毫不相干的对话里。
Claude Code 的 Skill 系统基于渐进式披露(progressive disclosure):所有 SKILL.md 的 frontmatter 常驻在上下文里,但正文和脚本只在被判定为"相关"时才加载。换句话说——name 和 description 是你的 Skill 唯一长期暴露在模型决策视野里的信息。它写得好不好,直接决定模型是否把你的 Skill 纳入候选;正文写得再精彩,触发不了就等于不存在。
所以本教程提出一个反直觉的构建顺序——触发优先:先画边界、再写描述、然后才是逻辑,最后用回归测试反复校准描述。
写任何代码之前,拿出一张表,把"该触发"和"不该触发"的场景列清楚。这是后续 description 工程的依据。对 PII 脱敏 Skill,矩阵大致如下:
| 类型 | 场景示例 | 期望 |
|---|---|---|
| 正例 | "帮我把这份日志里的手机号打码" | ✅ 触发 |
| 正例 | "脱敏一下这个 CSV 里的身份证和邮箱" | ✅ 触发 |
| 正例 | "这串数据要去标识化再贴到 issue 里" | ✅ 触发 |
| 反例 | "日志里这个错误怎么排查" | ❌ 不触发(排查不是脱敏) |
| 反例 | "帮我查数据库里这张表的结构" | ❌ 不触发(查询不是脱敏) |
| 反例 | "审计这段代码有没有安全漏洞" | ❌ 不触发(审查不是脱敏) |
| 边界 | "把密钥从配置里删掉" | ❌ 不触发(这是删除机密,不是 PII 脱敏) |
注意右下角那几条反例——它们正是这个 Skill 最容易被误触发的场景。反例的价值远大于正例,因为它们定义了 Skill 的"围墙"。
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。先写一个能跑的最小版本,下一节再精修:
---
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:一句话写清"做什么 + 何时用"。这是模型做相关性判断的主要依据。这是本教程的核心。一条合格的 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"这类词,否则两者会争抢同一个请求。
按四原则重写后:
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.
frontmatter 还有一批可选字段,其中两个对触发控制尤其重要:
allowed-tools:白名单限制这个 Skill 能调用的工具,比如脱敏 Skill 只需要 Bash(跑脚本)和 Read(读文件),就显式列出,防止它越权改文件。disable-model-invocation:true 表示禁止模型自动触发,只能由用户显式调用。当你发现某个 Skill 反复误触发、怎么调 description 都压不住时,这是一个"核选项"——把它降级为纯手动 Skill。---
name: pii-masking
description: <上面精修后的描述>
allowed-tools:
- Bash
- Read
---
说明:本文聚焦触发工程,未展开
metadata(分类/版本等元信息)、license、version等字段,它们影响分发与发现,但不直接影响运行时触发决策。
把"重活"放进 scripts/mask.py,让 SKILL.md 正文只负责说明"何时调用脚本、怎么传参"——这正是渐进式披露的精髓:正文薄、脚本厚,避免把一堆正则塞进常驻上下文。
scripts/mask.py(可直接运行的正则脱敏器):
#!/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 正文写明调用方式(保持简短):
# 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——它只在模型需要解释规则时才被加载。
描述写完后,不要凭感觉说"应该能触发"。把第 1 步的触发矩阵转成一组测试提示词,逐条手测,记录结果:
✅ 正例(应触发)
[T1] "把这行日志里手机号打码"
[T2] "脱敏一下这段 CSV 里的邮箱和身份证"
[T3] "去标识化后再贴进工单"
❌ 反例(不应触发)
[F1] "这条日志报错啥原因"
[F2] "查一下 users 表有哪些字段"
[F3] "审计这段 SQL 有没有注入风险"
[F4] "把 .env 里的密钥删掉"
跑一遍,统计两个核心指标:
如果 skill-creator 这类工具可用,还能把这套提示词喂给它的 eval 机制做批量打分和方差分析,把触发质量量化成可追踪的数字,而不是"祈祷它工作"。
假设回归测试发现 F4(删密钥)被误触发了——模型看到"删掉数据"就联想到"脱敏"。这时候只改 description,强化边界:
# 调整前
... 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.
改完再跑一次回归,看误触发是否消失。触发优先构建法本质上是一个闭环:画边界 → 写描述 → 跑回归 → 改描述,直到两个指标都达标。
下面这张流程图把"触发优先构建法"的迭代闭环串起来:
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 就是这三件事:
.claude/skills/pii-masking/SKILL.md:精修后的 frontmatter + 简短正文(见第 3、5 步).claude/skills/pii-masking/scripts/mask.py:第 5 步的脱敏脚本.claude/skills/pii-masking/references/regex-rules.md:每条正则的说明文档验证它工作:
# 直接验证脚本逻辑
echo "联系人 13800138000, 邮箱 [email protected], 身份证 110101199001011234" | python3 .claude/skills/pii-masking/scripts/mask.py
# 期望: 联系人 138****8000, 邮箱 a***@test.com, 身份证 110101********1234
# 在 Claude Code 里验证触发
# 粘贴: "帮我把这份日志里的手机号和邮箱打码"
# 期望: 自动加载 pii-masking 并调用脚本
SKILL.md frontmatter 全字段参考(含 allowed-tools、disable-model-invocation 等):https://claudskills.com/learn/skill-md-frontmatter-reference/disable-model-invocation 等触发控制字段解析[!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 等场景全覆盖。
