<!-- title: 造正则Skill：描述直转正则并自验证 -->

# Claude Code Skills 开发实战：从零造一个会自我验证的正则生成 Skill

## 概述

本教程面向已装好 Claude Code、想从「会用别人写的 Skill」升级到「自己造 Skill」的开发者。你将掌握 2026 版 Skill 的完整骨架——三个存放目录的区别、`SKILL.md` 的 15 个 frontmatter 字段、进度披露（progressive disclosure）的拆分原则，并最终亲手造出一个 `regex-forge` 技能：输入一句自然语言，输出一条**已被脚本验证过、可直接复制**的正则表达式。适合做过一轮 Claude Code 入门、能看懂 Markdown 与少量 JavaScript 的读者。

## 前置条件

- 已安装并跑通 Claude Code（CLI 或 IDE 插件均可），能正常对话与执行 `/` 命令。
- 机器上有 `node`（执行验证脚本用，v18+ 即可）；没有也行，可用 Python/Shell 改写，思路一致。
- 一个用来练手的空目录，用于承载 `.claude/skills/`。
- 对正则有基本概念（会写 `\d+`、`.`、`*` 这一级别足够），不需要精通。

---

## 一、先搞清楚：Skill 到底是什么、放哪儿

一个 Skill 在磁盘上就是**一个文件夹**，里面至少有一个带 YAML frontmatter 的 `SKILL.md`。Claude Code 启动时扫描这些文件，把它们的 `name` 和 `description` 注册进自己的「技能目录」。当对话内容命中某条描述，或你手动敲 `/技能名`，对应的指令才会被加载进上下文执行。

存放位置有三档，优先级从高到低：

| 目录 | 作用域 | 是否进 git | 典型用途 |
|---|---|---|---|
| `~/.claude/skills/` | 用户全局，所有项目可用 | — | 个人跨项目复用的小工具 |
| `<项目>/.claude/skills/` | 项目级，团队共享 | 是（建议） | 团队规范、项目专属流程 |
| `<项目>/.claude/skills.local/` | 项目级，仅自己 | 否（gitignore） | 个人临时实验 |

**命名规则**：技能名只能小写字母、数字、连字符，最长 64 字符，目录内唯一。我们这次造的叫 `regex-forge`，放在项目级 `.claude/skills/regex-forge/`。

**操作说明**：

```bash
mkdir -p .claude/skills/regex-forge/scripts
mkdir -p .claude/skills/regex-forge/references
```

**预期结果**：得到如下骨架——

```
.claude/skills/regex-forge/
├── SKILL.md          # 入口，唯一会被默认读进上下文的文件
├── scripts/          # 进度披露：可执行脚本，按需调用
└── references/       # 进度披露：参考资料，按需加载
```

**常见问题**：

- *三档同名会怎样？* 高优先级（全局）会「遮蔽」低优先级的同名技能，不会合并。别指望用 `skills.local` 去覆盖团队版本——它优先级最低。
- *目录名和技能名不一致行不行？* `name` 字段缺省时回退到目录名，但官方明确建议**始终显式写 `name`**，否则重命名目录会悄悄改变 `/` 命令。

---

## 二、写 frontmatter：15 个字段里，真正决定成败的只有两个

frontmatter 是 `SKILL.md` 顶部被 `---` 包住的 YAML 块。2026 版规范里它有多达 15 个字段，但对绝大多数 Skill 来说，**`description` 决定「能不能被触发」，`allowed-tools` 决定「跑得顺不顺」**，其余按需。

### 2.1 `description`——整个 Skill 最该花心思的一行

它驱动**自动触发**：模型是靠读这段话来判断「现在该不该把这个技能拽进来」。官方给的有效公式是：

> **[动词短语：它做什么]。Use when [触发短语]。**

对比：

```
# ❌ 模糊，模型不知何时用
description: 正则助手

# ✅ 具体，触发词密集
description: Build and test a regular expression from a natural-language description of what should match or be rejected. Use when the user asks for a regex, a pattern, to match/validate/extract emails, phone numbers, hashtags, or any text pattern.
```

注意：`description` 加上可选的 `when_to_use`，在技能目录里被截断到 **1536 字符**。宁可具体不可啰嗦。

### 2.2 `allowed-tools`——给脚本开绿色通道

我们的技能要跑 `node` 做验证。如果不声明，每次执行都会弹权限确认，打断节奏。声明后，技能激活期间这些工具免确认：

```yaml
allowed-tools:
  - Bash(node:*)
  - Read
```

**铁律：尽量收窄。** 写 `Bash(node:*)` 而不是 `Bash`，写 `Read(./src/**)` 而不是 `Read`。松散的权限等于在每个加载该技能的项目里预授权，是安全隐患。而且全局 `deny` 永远优先，`allowed-tools` 只能「加」，不能「绕」。

### 2.3 完整的 frontmatter

```yaml
---
name: regex-forge
description: Build and test a regular expression from a natural-language description of what should match or be rejected. Use when the user asks for a regex, pattern, or validation rule (match emails, validate phone numbers, extract hashtags).
argument-hint: <what-to-match> [--tests="mustMatch;mustNotMatch"]
allowed-tools:
  - Bash(node:*)
  - Read
---
```

**字段小结**：

- `name`：技能标识，也是 `/regex-forge` 的命令名。
- `argument-hint`：纯 UX，用户敲 `/regex-forge` 时补全菜单里显示的提示，`<必填>` 与 `[可选]` 约定同 man 手册。
- `description`：自动触发的核心，第三人称，含触发词。
- `allowed-tools`：技能激活期间免确认的工具白名单。

**常见问题**：

- *要不要加 `disable-model-invocation: true`？* 凡是有副作用（部署、提交、删分支、发邮件、开 PR）的技能，**默认加**，防止模型自作主张触发。`regex-forge` 是只读+本地验证，无副作用，故**不加**，让模型在合适时机自动调用。
- *字段大小写敏感吗？* 敏感。布尔/字符串字段用连字符（`disable-model-invocation`），只有 `when_to_use` 用下划线。写错一个字符整段失效。

---

## 三、写 body：把「聪明的指令」拆成「笨但可靠的步骤」

body 是 frontmatter 之后的正文。原则是**可执行、可校验**——把模糊的「帮我写正则」拆成模型能照做的清单，并**强制走验证脚本**。

```markdown
# Regex Forge

把一句自然语言的匹配需求，变成一条**已被测试通过**的正则。

## 步骤

1. 读懂 `$ARGUMENTS` 或对话，明确：什么必须匹配？什么必须**不**匹配？
2. 写出**一条** JavaScript 风格正则。优先选满足需求的最窄模式。仅当需求隐含整串校验时才加 `^`/`$` 锚点或 `\b`。
3. **返回前必须先验证**，运行内置测试器：

   ```bash
   node scripts/regex-tester.js "<PATTERN>" "<必须匹配,samples>" "<必须不匹配,samples>"
   ```

   脚本输出 `PASS` 才算数，否则逐条列出失败样本。未得 `PASS` 前不要向用户展示正则。失败就改模式重跑。

4. 通过后，输出：正则代码块 + 每段（锚点/分组/量词）的一句话解释 + 测试证据。
5. 涉及进阶构造（后行断言、Unicode、反向引用）时，查阅 `references/regex-cookbook.md` 并注明引用的小节。
```

**预期结果**：模型拿到这段 body 后，行为从「直接甩一个可能错的正则」变成「生成→自测→必要时改→再给」。这正是 Skill 相比纯 prompt 的价值：把流程固化下来。

**常见问题**：

- *body 要写多详细？* 够让一个认真但没读过你脑子的执行者照做即可。细节塞进 `references/`，别把 `SKILL.md` 写成论文——它默认全量进上下文。

---

## 四、加 bundled 脚本：进度披露的关键一环

`SKILL.md` 默认加载，但 `scripts/`、`references/` 不会——它们是**按需**才被读进来。这就是「进度披露」：入口保持精简，重资源用到才取，省 token、保聚焦。

新建 `scripts/regex-tester.js`（注意把 `g` 标志剥掉，避免 `test()` 的有状态陷阱）：

```javascript
#!/usr/bin/env node
// 校验一条正则：必须匹配的全匹配，必须不匹配的全不匹配。
// 用法: node regex-tester.js "<pattern>" "<must,match>" "<must,not,match>"
const [, , patternStr, matchStr = "", nomatchStr = ""] = process.argv;
if (!patternStr) { console.error("FAIL: no pattern"); process.exit(2); }

// 支持 /pattern/flags 形式，统一拆出 source，并去掉 g 以保证 test() 无状态
let source = patternStr, flags = "";
const m = patternStr.match(/^\/(.+)\/([gimsuy]*)$/);
if (m) { source = m[1]; flags = m[2].replace(/g/g, ""); }

let re;
try { re = new RegExp(source, flags); }
catch (e) { console.error("FAIL: invalid regex -> " + e.message); process.exit(2); }

const must    = matchStr  ? matchStr.split(",")  : [];
const mustNot = nomatchStr? nomatchStr.split(","): [];
const fails = [];
for (const s of must)    if (!re.test(s)) fails.push(`should MATCH:    "${s}"`);
for (const s of mustNot) if ( re.test(s)) fails.push(`should NOT match: "${s}"`);

if (!fails.length) {
  console.log(`PASS — /${re.source}/${flags} | ${must.length} positive, ${mustNot.length} negative`);
  process.exit(0);
}
console.log("FAIL:");
fails.forEach(f => console.log("  - " + f));
process.exit(1);
```

**预期结果**——在项目根目录手测一次：

```bash
node .claude/skills/regex-forge/scripts/regex-tester.js \
  '^\d{4}-\d{2}-\d{2}$' \
  '2026-07-09,1999-12-31' \
  '26-07-09,2026/07/09'
# PASS — /^\d{4}-\d{2}-\d{2}$/ | 2 positive, 2 negative
```

脚本返回 `PASS`，正则可信；改个故意错的模式则会列出哪条样本挂了。

**常见问题**：

- *为什么要单独拎成脚本，而不是让模型自己脑补测试结果？* 模型对「这条正则到底匹配不匹配字符串」会算错，而 `node` 不会。把**可机械判定**的部分交给脚本，把**需要判断**的部分（怎么设计模式）留给模型，这是高质量 Skill 的通用分工。
- *`g` 标志为什么剥掉？* `RegExp.test()` 在带 `g` 时会记住 `lastIndex`，连续调用会跳着匹配，是经典的「同一个串第一次 true 第二次 false」坑。测试场景不需要全局，剥掉最稳。

---

## 五、加 references：把「偶尔才用」的知识按需加载

进阶构造不必塞进 `SKILL.md`，放进 `references/regex-cookbook.md`，body 第 5 步提示模型「需要时去查」即可：

```markdown
# Regex Cookbook（按需查阅，仅在用到进阶构造时加载）

- 后行断言：匹配前面是 `$` 的 `foo` → `(?<=\$)foo`
- 命名分组：`(?<year>\d{4})-(?<month>\d{2})` → `match.groups.year`
- Unicode 属性：任意文字 `\p{L}+`（需 `u` 标志）
- 反向引用：连续重复字符 `(\w)\1`
- 锚点取舍：`^...$` 用于表单整串校验；`\b` 用于从长文本里定位词
```

这样 `SKILL.md` 始终轻量，复杂知识按需出现，上下文不爆。

---

## 六、发现、触发与自测：确认它真的能用

保存好三份文件后，**新开**一个 Claude Code 会话（老会话不会热重载技能目录）。两种触发方式：

1. **手动**：敲 `/regex-forge 匹配中国大陆手机号`，补全菜单应出现你写的 `argument-hint`。
2. **自动**：直接说「帮我写个匹配邮箱的正则」，模型读到 `description` 里的触发词，应自行激活该技能。

验证「是否被加载」的窍门：在对话里问「你现在激活了哪些技能」，或观察它有没有去调用 `node .../regex-tester.js`——调用了，就说明 body 生效了。

**常见问题**：

- *敲 `/` 看不到这个技能？* 八成是 frontmatter 解析失败（YAML 缩进错、字段名拼错、`---` 漏了第二根）。frontmatter 全文大小写敏感，逐字核对。
- *自动触发不灵？* 多半是 `description` 没写好触发词。把「Use when …」那段写具体，列出用户真实会说的话术（"写个正则""匹配 xxx""校验"）。
- *想让它只在某些文件上自动出现？* 加 `paths: ["**/*.log"]` 这类 glob，按文件类型门控自动激活（注意 `**/*.log` 与 `*.log` 不是一回事，前者跨目录）。手动 `/` 调用不受 `paths` 限制。

---

## 实战案例：一句话产出可上线的邮箱校验正则

完整跑通这个 Skill。用户输入：

> /regex-forge 校验公司邮箱：用户名是字母数字下划线，域名是 xxx.com，要整串匹配，并给我两组测试

技能被激活后，模型按 body 执行：

1. 理解需求：必须匹配 `名@xxx.com`、整串、用户名限 `[A-Za-z0-9_]`。
2. 生成候选：`^[A-Za-z0-9_]+@xxx\.com$`
3. 调脚本自测：

```bash
node scripts/regex-tester.js '^[A-Za-z0-9_]+@xxx\.com$' 'alice_01@xxx.com,Bob@xxx.com' 'alice@yyy.com,a..b@xxx.com,空格 @xxx.com'
# PASS — /^[A-Za-z0-9_]+@xxx\.com$/ | 2 positive, 3 negative
```

4. 通过，输出最终答复：

```
✅ 正则：/^[A-Za-z0-9_]+@xxx\.com$/

逐段解释：
- ^ ... $   整串校验，前后不允许多余字符
- [A-Za-z0-9_]+  用户名：字母/数字/下划线，至少 1 位
- @xxx\.com  字面域名（. 被转义，只匹配点号）

测试证据（脚本 PASS）：通过 2 条正例、3 条反例。
```

整个链路里，模型**没把未经测试的正则交给你**——脚本兜住了正确性。这就是把 Skill 做成「自带质检」的好处。

---

## 工作流程图

```mermaid
flowchart TD
    A["用户：帮我写个匹配邮箱的正则"] --> B{"description 命中触发词？"}
    B -- 否 --> Z["走常规对话"]
    B -- 是 --> C["加载 SKILL.md 进上下文（默认全量）"]
    C --> D["解析 $ARGUMENTS / 参数"]
    D --> E["按 body 步骤生成候选正则"]
    E --> F["Bash: node scripts/regex-tester.js"]
    F --> G{"脚本输出 PASS？"}
    G -- 否 --> E
    G -- 是 --> H["返回正则 + 解释 + 测试证据"]
    C -. 用到进阶构造时 .-> R["按需加载 references/regex-cookbook.md"]
    style F fill:#e8f5e9
    style G fill:#fff8e1
```

---

## 进阶：再往前走两步

1. **换模型省成本**：正则这类「确定性变换」可加 `model: haiku` + `effort: low`，便宜又快；若改造成需要权衡架构的技能，再 `model: opus`。
2. **大读取量技能用 fork**：若把技能扩成「扫全仓所有日志再总结」，加 `context: fork` + `agent: Explore`，让它跑在隔离子代理里，不污染主对话、还省主上下文。
3. **`skill-creator` 自动脚手架**：不想手敲骨架，可直接调用官方的 `skill-creator` 技能，描述需求即可生成目录与初版 `SKILL.md`，再把 frontmatter 按本教程精修。

---

## 延伸阅读

- 官方《Extend Claude with skills》——权威的技能创建、管理与共享文档。
- frontmatter 全字段速查——15 个字段的默认值与取舍建议，写复杂技能时当字典查。
- 进度披露设计原则——为什么 `SKILL.md` 要薄、脚本与参考资料为什么要按需。

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方技能总览，定义了 SKILL.md、三个存放目录与触发机制。
2. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — 逐字段拆解 15 个 frontmatter 字段的默认值、用法与最佳实践（2026-04 更新）。
3. [SKILL.md Format Explained](https://skillhq.dev/blog/skill-md-format-explained) — frontmatter 各字段、body 结构与 allowed-tools 写法的实操详解。
4. [Claude Code Skills Structure and Usage Guide (gist)](https://gist.github.com/mellanon/50816550ecb5f3b239aa77eef7b8ed8d) — 技能如何被格式化为 Skill 工具提示并参与自动触发。
5. [Claude Skills Tutorial: Build Your First Skill (TECHSY)](https://techsy.io/en/blog/claude-skills-tutorial) — 从零创建首个 Skill 的入门范例，可作为本教程的前置热身。

## 训练营适用性评估

- **适配类型**：适合「Claude Code 进阶/二次开发」类训练营的第 2–3 天课程，承接「入门装通」之后、MCP 与 Agent 自动化之前。也可作为企业内部「团队规范固化」工作坊的核心实操。
- **教学时长**：讲授 45 分钟 + 动手 60 分钟。前 45 分钟讲透 frontmatter 两关键字段与进度披露；后 60 分钟学员每人造一个属于自己的「输入一句话→产出带验证产物」技能（如 SQL 校验、日志提取、配置模板生成），当场跑通 `PASS`。
- **考核点**：①技能能被自动触发（描述写得到位）；②脚本确实验证了产物（看到 `PASS`/`FAIL`）；③`allowed-tools` 收窄合理，无过度授权。
- **延伸作业**：给该技能补 `paths` 门控或 `context: fork`，对比改造前后上下文占用与触发行为差异。

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