<!-- title: 从零造正则回溯扫描Skill -->

## 概述

本教程以"正则灾难性回溯（ReDoS）风险扫描"为案例，带你从零构建一个完整的 Claude Code 自定义 Skill，覆盖目录规划、SKILL.md frontmatter 编写、渐进式披露三层结构、辅助脚本、参考子文档与调试验证全流程。学完后你能独立设计任意领域的 Skill，并掌握让 Skill"该出手时才出手"的触发词写作精髓。适合已会用 Claude Code 跑基本任务、想封装团队可复用工作流的开发者。

## 前置条件

- Claude Code 已安装并能正常对话（CLI 或 IDE 插件均可）
- 一台装了 Python 3.8+ 与 Git 的开发机
- 一个带正则表达式的真实代码仓库（用于实战演练）
- 了解 Markdown 与 YAML 基础语法

---

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

很多人把 Skill、自定义命令、子代理、MCP 服务器混为一谈。它们的本质区别在于**"加载时机"**与**"承载形态"**。

Skill 是一组放在文件系统里的指令包，核心是一份 `SKILL.md`。模型平时**不读**它，只在判断当前任务与某条 Skill 的描述匹配时，才把它加载进上下文。这正是"渐进式披露"的精髓——平时零成本，命中才付费。

| 形态 | 承载 | 触发 | 适合场景 |
|------|------|------|----------|
| Skill | 文件系统指令包 | 模型按 description 自动判断 | 领域工作流、专家知识封装 |
| 自定义命令 | slash 命令文件 | 用户输入 `/名字` | 用户主动调用的固定流程 |
| 子代理（subagent）| 独立上下文的代理 | 主代理委派 | 隔离上下文、并行拆分 |
| MCP 服务器 | 外部进程 + 工具协议 | 模型调用 tool | 接入外部系统、实时数据 |

一句话记忆：**命令是"我要做"，MCP 是"我需要数据"，子代理是"分身去做"，Skill 是"我会做，且你该知道我什么时候该做"。**

## 二、案例预览：regex-guard 要解决什么

正则表达式写不好，会埋下 ReDoS 这颗定时炸弹。一段看似无害的 `^(a+)+$` 在面对精心构造的输入时，匹配时间会随字符串长度**指数级膨胀**，几十字符就能让一个 Node 服务 CPU 打满、整条请求线卡死。历史上 Stack Overflow、Cloudflare 都因此发生过线上事故。

`regex-guard` 这个 Skill 的目标很简单：**扫描代码库里的正则字面量，找出有回溯爆炸风险的写法，给出回溯路径分析与安全重写建议。** 它足够小、足够具体，却能完整展示一个生产级 Skill 的全部结构。

## 三、第一步：规划目录与触发场景

Skill 可以放在两个位置：用户级 `~/.claude/skills/<name>/`（所有项目可用）或项目级 `.claude/skills/<name>/`（随仓库共享给团队）。我们选项目级，方便版本管理与团队复用。

```bash
mkdir -p .claude/skills/regex-guard/{scripts,reference}
cd .claude/skills/regex-guard
touch SKILL.md scripts/detect_regex_risks.py reference/evil-patterns.md reference/rewrite-patterns.md
```

最终目录长这样：

```
.claude/skills/regex-guard/
├── SKILL.md                       # 主入口：frontmatter + 精简工作流
├── scripts/
│   └── detect_regex_risks.py      # 启发式扫描脚本
└── reference/
    ├── evil-patterns.md           # 危险模式详解（第三层，按需读）
    └── rewrite-patterns.md        # 安全重写对照表（第三层，按需读）
```

**预期结果**：Claude Code 启动时会扫描该目录，发现 `SKILL.md` 即注册为一个候选 Skill，但此时还不会加载正文。

**常见问题**：目录名必须与 frontmatter 的 `name` 一致，否则部分版本会出现"注册成功但不触发"的幽灵状态。

## 四、第二步：写 frontmatter——description 是灵魂

整个 Skill 最关键的字段不是正文，而是 frontmatter 里的 `description`。模型**只看 description** 来决定要不要加载你的 Skill，写得含糊，Skill 就永远不会被触发。

```yaml
---
name: regex-guard
description: 扫描代码库中的正则表达式字面量，识别可能导致灾难性回溯（ReDoS）的性能风险模式（嵌套量词、重叠交替叠加量词、贪婪通配叠加等），输出风险位置、回溯路径分析与可落地的安全重写建议。当用户排查正则性能问题、审查用户输入校验逻辑、做安全审计、或抱怨"某个校验函数偶尔卡死"时使用。
---
```

写好 description 的四条铁律：

1. **第一句说能力**：它"做什么"，而不是"它是什么"。
2. **塞入触发关键词**：把用户可能脱口而出的词埋进去——"正则""regex""性能""卡死""ReDoS""输入校验""安全审计"。
3. **明确使用场景**：用"当……时使用"收尾，给模型一个判断锚点。
4. **长度克制**：通常 1–3 句、不超过 500 字符。描述越长越泛，反而越容易误触发或漏触发。

可选字段还包括 `disable-model-invocation: true`（禁止自动触发，仅 `/skill` 手动调用）、`version`、`author` 等。`allowed-tools` 可声明限制，但要注意它主要是**意图声明**，不要把它当成安全边界——真正要收紧权限应靠 `settings.json` 的权限配置。

**常见问题**：描述写成 `"一个检查正则的工具"` 这种废话，模型根本无从判断何时该用。务必具体到场景。

## 五、第三步：写 SKILL.md 正文——保持精简

正文是第二层披露。原则是**只放工作流骨架，细节下沉到 reference**。因为正文每次触发都会进入上下文，写得越长，每个会话的 token 成本越高。

```markdown
# 正则回溯风险扫描（regex-guard）

## 何时使用
用户提交了用户输入校验、遇到正则匹配卡顿、或要求做一次正则/安全审查时，主动扫描仓库。

## 工作流
1. 调用 `scripts/detect_regex_risks.py <目录>` 跑启发式初筛，拿到可疑清单。
2. 对每个可疑正则，结合上下文判断是否真有可控输入路径。
   - 若需要判断精确的回溯路径与星级，读取 `reference/evil-patterns.md`。
   - 若需要给出安全重写，读取 `reference/rewrite-patterns.md`。
3. 输出报告：文件:位置、风险等级（高/中/低）、一句话原因、重写建议。

## 输出格式
按风险等级排序的 Markdown 表格，高风险项附 2–3 行修复 diff 示例。

## 注意
脚本仅为启发式，会有误报。最终判断必须结合输入是否可控——只处理常量字符串的正则
即便模式危险也无需告警，请勿制造噪音。
```

注意最后那段"何时读取 reference"的指引——这是渐进式披露的**胶水**。正文告诉模型"危险模式详解在 reference/evil-patterns.md，需要时去读"，模型只在实际需要深度分析时才加载那份文档，平时不占上下文。

## 六、第四步：辅助脚本 detect_regex_risks.py

脚本负责把"重复且确定"的活儿从模型身上剥离——模型擅长判断，脚本擅长穷举。下面是一份精简但可运行的启发式扫描器：

```python
#!/usr/bin/env python3
"""Extract regex literals from source and heuristically flag ReDoS risks.
This is a heuristic first-pass; precise backtracking analysis is left to the model
after it reads reference/evil-patterns.md.
"""
import re, sys, pathlib

# Heuristic dangerous patterns
DANGEROUS = [
    (r'\([^)]*[+*?][^)]*\)[+*?]',      'nested quantifier, e.g. (a+)+'),
    (r'\((?:[^|)]+\|)+[^|)]+\)[+*?]',  'overlapping alternation + quantifier, e.g. (a|a)*'),
    (r'(?:\.\*|\.\+|\.\?){2,}',        'stacked greedy wildcards, e.g. .*.*'),
]

# Match /pattern/flags and new RegExp("pattern")
LITERAL = re.compile(r'/(?:[^/\\\n]|\\.)+/[gimsuy]*')
NEWRE   = re.compile(r'new\s+RegExp\(\s*[\'"]([^\'"]+)[\'"]')

def scan(text):
    found = [(m.start(), m.group(0).strip('/').split('/')[0]) for m in LITERAL.finditer(text)]
    found += [(m.start(), m.group(1)) for m in NEWRE.finditer(text)]
    risks = []
    for off, pat in found:
        for rule, reason in DANGEROUS:
            if re.search(rule, pat):
                risks.append((off, pat, reason))
    return risks

if __name__ == '__main__':
    root = pathlib.Path(sys.argv[1])
    exts = {'.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.java'}
    files = [p for p in root.rglob('*') if p.suffix in exts
             and 'node_modules' not in p.parts and '.git' not in p.parts]
    total = 0
    for f in files:
        text = f.read_text(encoding='utf-8', errors='ignore')
        for off, pat, reason in scan(text):
            line = text[:off].count('\n') + 1
            print(f'{f}:{line}  /{pat}/  -> {reason}')
            total += 1
    print(f'---- {total} suspicious risk(s) ----')
```

**预期结果**：在一个含 `const email = /^(a+)+$/` 的测试文件上运行，会输出该行与原因。

```bash
chmod +x scripts/detect_regex_risks.py
python3 scripts/detect_regex_risks.py ./src
# src/validators.js:42  /^(a+)+$/  -> nested quantifier, e.g. (a+)+
# ---- 1 suspicious risk(s) ----
```

**常见问题**：脚本误报是常态。比如 `(\d{4})+` 虽然形似嵌套量词，但 `\d{4}` 是定长，没有回溯。这正是要把"最终判断"留给模型、把"精确判据"放进 reference 的原因——脚本只负责把候选捞出来。

## 七、第五步：reference 子文档——第三层披露

`reference/evil-patterns.md` 存放需要专业知识才能写好的判据。这部分平时完全不加载，只有模型在分析某个可疑正则时，按正文指引"按需"读取。它可以是详尽的、篇幅不受限的。

```markdown
# ReDoS 危险模式判据

## 星级标准（基于回溯步数的理论分析）
- ⭐⭐⭐ 指数级：嵌套量词叠加，如 (a+)+、(a*)*，输入长度 n 时回溯 O(2^n)
- ⭐⭐   多项式级：重叠交替叠加量词，如 (a|a)*，回溯 O(n^2)
- ⭐     线性放大：贪婪通配叠加，如 .*.*，常数级恶化但通常可接受

## 关键判别：输入是否可控
模式的危险度 = 回溯复杂度 × 输入可控度。
- 若正则只匹配代码内常量 → 即便 ⭐⭐⭐ 也无需告警
- 若正则匹配 request.body / 用户上传文本 → ⭐ 也要修

## 常见误报
- (\d{4})+ ：定长子项，无回溯，安全
- (?:abc)+ ：定长子项，安全
- 字符类 [a-z]+ 后跟 + ：字符类不重叠时安全
```

`reference/rewrite-patterns.md` 则是修复对照表，例如把 `^(a+)+$` 改写成原子组 `^(?>a+)$` 或换用 `string-argv` 这类线性匹配。这两份文档共同构成了 Skill 的"专家大脑"。

## 八、第六步：安装、调试与验证

Skill 文件就位后，重启或新开一个 Claude Code 会话即可自动注册。验证触发是否正常，是最容易踩坑的一环。

```bash
# 1. 确认 Skill 被发现
claude            # 进入会话后输入
> /skills         # 列出所有已注册 Skill，应能看到 regex-guard
```

调试触发率的三板斧：

1. **不触发的诊断**：用一句贴近 description 关键词的话测试，如"帮我看看 src 里的正则有没有性能隐患"。若仍不触发，说明 description 缺关键词或写得太宽泛。
2. **强制调用**：在会话里输入 `/regex-guard` 可手动激活（前提是未设 `disable-model-invocation`）。
3. **观察加载日志**：开启 `ANTHROPIC_LOG=debug` 环境变量，能看到 Skill 是否进入候选集、为何被选中或排除。

**常见问题**：改了 SKILL.md 却不生效——Claude Code 对 Skill 有缓存，需新开会话或重载。脚本路径要用相对 Skill 根目录的写法，模型默认在 Skill 目录下执行。

## 九、实战案例：在真实仓库上跑通

以一个 Express 项目为例，模拟一次完整的扫描：

```bash
# 项目结构
my-api/
├── src/
│   ├── validators.js   # 含 const phone = /^(\\d+)+$/
│   └── routes/user.js
└── .claude/skills/regex-guard/   # 我们的 Skill
```

在仓库根目录打开 Claude Code，自然语言提问：

> 帮我审查一下 src 目录里的输入校验，看看有没有正则方面的隐患。

模型应自动触发 regex-guard，调用脚本初筛、读取 reference 做精确判断，最终产出类似报告：

| 文件 | 行 | 正则 | 风险 | 等级 | 建议 |
|------|----|----|------|----|------|
| src/validators.js | 18 | `/^(\d+)+$/` | 嵌套量词，指数级回溯 | ⭐⭐⭐ | 改写为 `/^\d+$/` 或 `^(?>\d+)$` |

修复建议附带最小 diff：

```diff
- const phone = /^(\d+)+$/;
+ const phone = /^\d+$/;
```

至此一个"会自己判断、会调脚本、会查专家文档、会输出规范报告"的 Skill 就闭环跑通了。

## 工作流全景

```mermaid
flowchart TD
    A[用户提问：正则有隐患吗？] --> B{模型扫描所有 Skill 的 description}
    B -->|命中 regex-guard| C[加载 SKILL.md 正文 第二层]
    B -->|未命中| Z[零成本，不加载]
    C --> D[按正文工作流调用 scripts 扫描]
    D --> E{需要精确分析？}
    E -->|是| F[读取 reference/evil-patterns.md 第三层]
    E -->|否| G[直接输出报告]
    F --> H[读取 reference/rewrite-patterns.md]
    H --> G
    G --> I[Markdown 报告 + 修复 diff]
```

图中三条虚线对应渐进式披露的三个层级：平时只有 description 在场，命中后才加载正文，深入分析才读 reference。每一层都意味着上下文成本，所以"能下沉就下沉"是 Skill 设计的第一原则。

## 排错速查

- **Skill 不触发** → 检查 description 是否含用户真实用语；删除空泛形容词；新开会话清缓存。
- **脚本路径找不到** → 在 SKILL.md 正文里明确"脚本位于本 Skill 目录的 scripts/ 下"，并示范调用命令。
- **误报泛滥** → 在正文加"必须结合输入可控性判断"的硬约束，把判据写进 reference 而非脚本。
- **团队同事用不上** → Skill 放在项目级 `.claude/skills/` 并提交到 Git；用户级 `~/.claude/skills/` 只对本机生效。

## 延伸阅读

- 把多个 Skill 与 hooks、MCP 组合，可构建"提交前自动正则体检"的闭环——参考 hooks 的 `PreToolUse` 事件。
- 复杂场景可让 Skill 内部再委派子代理并行扫描多个子目录，进一步提速。
- 对外分享时，把 Skill 打包成 plugin 发布到插件市场，实现一条命令安装。

## 训练营适用性评估

- **入门营（2 小时）**：略重。建议先跑通"装好→触发→看报告"三步即可，跳过脚本编写。
- **进阶营（半天，4 小时）**：高度适配。学员可现场替换自己的检测规则，做出领域定制版。
- **企业内训（1 天）**：最佳载体。可扩展为"团队规范 Skill 化"工作坊，让各组把内部代码规范都封装成 Skill。
- **预计教学时长**：讲授 45 分钟 + 动手 90 分钟 + 调试答疑 30 分钟，合计约 2.5–3 小时。

## 参考来源

1. [使用 skills 扩展 Claude - Claude Code Docs](https://code.claude.com/docs/zh-CN/skills) — 官方 Skill 创建、管理与共享权威指南
2. [Claude Code 的 SKILLS 技能渐进式披露实现原理解析 - 潘锦](https://www.phppan.com/2026/04/claude-code-ai-skills-source/) — 三层加载机制的源码级剖析
3. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — frontmatter 全字段逐一详解
4. [Claude Skill（技能）构建完全指南 - 知乎](https://zhuanlan.zhihu.com/p/2008328175697041144) — 中文社区完整的 Skill 构建教程
5. [continuous-claude-v3/dead-code/SKILL.md - GitHub](https://github.com/parcadei/continuous-claude-v3/blob/main/.claude/skills/dead-code/SKILL.md) — 真实生产级静态分析 Skill 的结构参考

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