<!-- title: 从零构建迁移安全守卫Skill -->

# Claude Code Skills 开发实战：从零构建「数据库迁移安全守卫」

## 概述

本教程面向已经会用 Claude Code 跑日常编码任务、但还没写过自定义 Skill 的开发者。我们将从一张空白 SKILL.md 开始，完整走通「写描述 → 编写工作流 → 拆分参考文件 → 触发测试 → 评估打磨」的全流程，最终产出一个**数据库迁移安全守卫 Skill**：它能在你提交 `ALTER TABLE`、`DROP COLUMN`、批量回填等高风险 SQL 时自动介入，按破坏性等级给出审查意见与锁表风险评估。学完后你将掌握 Skill 的三层渐进式披露结构、自动触发描述的写法，以及如何用 Evals 闭环验证一个 Skill 是否真的「该出手时才出手」。

## 前置条件

- 已安装 Claude Code（CLI 或 IDE 插件均可），能正常对话
- 一个真实的代码仓库（含数据库迁移文件即可，技术栈不限）
- 大致了解 Markdown 与 YAML 语法
- 不需要任何 MCP、SDK、Node 经验——Skill 本质是「带说明书的 Markdown」

---

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

很多人把 Skill 想得太重。一句话定义：**Skill 是一个带 YAML 头的 Markdown 文件（`SKILL.md`），里面写着「什么时候用我、按什么流程做」，Claude 会按需加载它。**

它和另外两个常被混淆的概念的区别：

| 概念 | 加载时机 | 适合放什么 |
|------|----------|-----------|
| `CLAUDE.md` | **每个会话都全量加载** | 项目级常驻规则、技术栈约定 |
| Skill（`SKILL.md`） | **需要时才加载**（描述命中用户意图） | 可复用的专项工作流 |
| Subagent / MCP | 显式调用或工具发现 | 独立上下文 / 外部数据源 |

关键认知：**Skill 的正文不会一直占用上下文**。Claude Code 启动时只读取每个 Skill 的 frontmatter（描述部分），只有当判断「这次任务和某个 Skill 相关」时，才会把该 Skill 的完整正文加载进来。这就是官方文档反复强调的 **渐进式披露（Progressive Disclosure）**。

### 渐进式披露的三层结构

```
第一层：frontmatter 的 description      ← 永远在上下文里（要短、要准）
第二层：SKILL.md 正文                   ← 命中后才加载（决策流程、关键规则）
第三层：同目录的参考文件（references/）  ← 用到才读取（完整清单、模板、样例）
```

理解这三层，是写好 Skill 的全部秘密。下面我们直接动手。

---

## 二、动手前：给 Skill 选一个存放位置

Skill 可以放在三个层级，加载优先级从高到低：

1. **项目级**：`<项目>/.claude/skills/<skill-name>/SKILL.md`——只在当前项目生效
2. **用户级**：`~/.claude/skills/<skill-name>/SKILL.md`——对你所有项目生效
3. **插件级**：随 Plugin 分发——适合跨团队共享

我们的「迁移安全守卫」和具体项目耦合（要识别项目里的迁移文件），放**项目级**最合适。

先建好目录骨架：

```bash
mkdir -p .claude/skills/migration-guardian/references
cd .claude/skills/migration-guardian
```

最终的目录结构会是：

```
.claude/skills/migration-guardian/
├── SKILL.md                      # 第二层：决策流程 + 关键规则
└── references/
    ├── destructive-patterns.md   # 第三层：完整破坏性模式清单
    ├── lock-risk-matrix.md       # 第三层：锁表风险矩阵
    └── rollback-template.md      # 第三层：回滚脚本模板
```

---

## 三、第一层：写好 frontmatter（决定 Skill 能否被触发）

`SKILL.md` 的开头是 YAML frontmatter，用三连横线包裹。**这是整个 Skill 最该反复打磨的部分**——因为它是唯一永远在上下文里的内容，写不好 Skill 就永远不被触发（或被错误触发）。

### 字段说明

| 字段 | 必填 | 作用 |
|------|------|------|
| `name` | 是 | Skill 唯一标识，全小写连字符，同时也是斜杠命令 `/migration-guardian` |
| `description` | 是 | **触发依据**。写明「这个 Skill 做什么、什么场景该用它」 |
| `allowed-tools`（或 `allowed_tools`） | 否 | 限制该 Skill 可调用的工具白名单，收敛权限 |
| `metadata` | 否 | 任意键值，便于组织/检索 |

### 写 description 的三条铁律

来自社区对 650 次会话的实测：**很多 Skill 不触发，根因就是 description 写成了「名词介绍」而非「触发条件」**。

1. **用第三人称、陈述「做什么 + 何时用」**，不要写「这是一个用于……的强大工具」
2. **显式铺开关键词**：把用户真实会打的词（迁移、migration、ALTER、DROP、回填、DDL）都融进去
3. **划清边界**：用「不要用于……」明确反面场景，防止误触发

### 我们的 frontmatter

```yaml
---
name: migration-guardian
description: Reviews database migration files for destructive or lock-heavy DDL before they are applied. Use when the user edits, stages, or asks about migration files (*.sql, Alembic, Rails migrations, Flyway, Liquibase, Prisma migrate, Knex) — especially operations like DROP TABLE/COLUMN, ALTER TABLE, TRUNCATE, renaming columns, NOT NULL without default, large backfills, or creating indexes without CONCURRENTLY. Do NOT use for seed data, fixture generation, or application-level SQL queries in code.
allowed-tools: Read, Grep, Glob, Bash
---
```

注意这 30 行字里塞进了多少触发信号：文件后缀（`*.sql`、Alembic、Rails、Prisma……）、具体危险操作（DROP、ALTER、TRUNCATE……）、边界排除（seed、fixture、应用层查询）。这一段写得越具体，自动触发越准。

> **常见问题**：我的 Skill 写完了但从不自动触发？
> **排查**：90% 是 description 缺少用户会实际输入的词。把你的描述读给同事听，问他「你会这样描述需求吗」。

---

## 四、第二层：编写正文（决策流程 + 关键规则）

正文只在 Skill 被触发后加载。这里要给 Claude 一个**清晰、可执行的决策流程**，而不是泛泛而谈。一个好的 Skill 正文像一份「带判断分支的检查清单」。

### 正文写作要点

- **直接以动词开头**：`Scan ...`、`Classify ...`、`Report ...`，不要寒暄
- **用编号步骤**呈现主流程，让 Claude 能逐步执行
- **关键规则就地放**：判断标准（什么算「破坏性」）必须明示
- **把长清单外链到 references/**：正文别堆 200 行模式表，否则违背渐进式披露

### 我们的 SKILL.md 正文

```markdown
# 数据库迁移安全守卫

你是一个数据库迁移审查助手。目标是：在迁移被应用到生产前，识别破坏性操作和高锁表风险，并给出可执行的缓解建议。

## 工作流程

1. **定位迁移文件**
   - 优先审查用户当前正在编辑 / 已 `git add` 的迁移文件。
   - 若用户未指定，用 Glob 找到最近的迁移目录（`db/migrate/`、`alembic/versions/`、`prisma/migrations/`、`migrations/`、`src/main/resources/db/changelog/` 等）。

2. **识别 DDL 类型**（用 Grep 扫描关键字，忽略大小写）：
   `DROP`, `TRUNCATE`, `ALTER`, `RENAME`, `MODIFY`, `ADD COLUMN`, `CREATE INDEX`, `NOT NULL`

3. **按破坏性等级分类**，参考 `references/destructive-patterns.md` 获取完整清单：
   - 🟥 **高危**：DROP TABLE/COLUMN、TRUNCATE、重命名列/表、删约束
   - 🟧 **中危**：NOT NULL 无默认值、加唯一索引、改列类型
   - 🟨 **低危但有锁风险**：普通 CREATE INDEX（无 CONCURRENTLY）、大表 ADD COLUMN + 回填

4. **评估锁表风险**
   - 读取 `references/lock-risk-matrix.md`，结合表行数（如能从代码/文档推断）给出 `低/中/高` 评级。

5. **输出审查报告**，格式固定：
   - 风险摘要表（文件、行号、等级、一句话原因）
   - 每个高危/中危项的**缓解建议**（如：先加可空列 → 分批回填 → 后续再设 NOT NULL）
   - 是否需要**停机窗口**的明确结论

6. **回滚预案**：对每个高危变更，参考 `references/rollback-template.md` 生成对应的正向 + 回滚 SQL。

## 关键规则（不可违反）

- **永不自动修改迁移文件**。只读、只建议。`allowed-tools` 已限定为只读工具。
- **不确定时按更严重的等级判定**，并标注「需人工确认」。
- **Postgres / MySQL / SQLite 差异**要识别：例如 `CREATE INDEX CONCURRENTLY` 仅 Postgres 支持，不要给 MySQL 项目提这个建议。
- 若迁移文件用了 ORM DSL（如 Rails 的 `remove_column`、Alembic 的 `op.drop_column`），按等价 DDL 判定。

## 何时停止

- 报告交付后，等待用户决定是否按建议修改。不要主动重写迁移。
```

到这里，`SKILL.md` 第二层就完整了。注意我们把**完整模式清单**和**锁表矩阵**都外链到了 `references/`——这就是渐进式披露的精髓：正文只留决策骨架，细节按需加载。

> **常见问题**：正文该写多长？
> **答**：以「能照着执行」为准，社区经验是正文控制在 **5000 词以内**为佳。超出的部分就该拆到 references。

---

## 五、第三层：编写参考文件（按需加载的细节库）

参考文件是普通 Markdown，**只有当正文明确引用、且 Claude 判断需要细节时**才会被读取。这意味着你可以放心地把长清单、大表格、模板塞进来，而不会污染主上下文。

### references/destructive-patterns.md

```markdown
# 破坏性 DDL 模式完整清单

## 🟥 高危（需停机或灰度）
| 模式 | 原因 | 典型缓解 |
|------|------|---------|
| DROP TABLE | 数据丢失，不可逆 | 先重命名（`_archived`）→ 观察期 → 真删 |
| DROP COLUMN | 旧代码可能仍读该列 | 先停写引用 → 再删 |
| TRUNCATE | 清空数据 | 同 DROP TABLE |
| RENAME COLUMN/TABLE | 旧代码/查询断裂 | 视图别名过渡 / 双写期 |
| 删除索引/约束 | 查询计划突变、写入报错 | 评估查询依赖后再删 |

## 🟧 中危（需灰度或分批）
| 模式 | 原因 | 缓解 |
|------|------|------|
| ADD COLUMN ... NOT NULL 无 default | 锁全表回填 | 先加可空列 → 回填 → 改 NOT NULL |
| ADD UNIQUE INDEX | 大表长锁 + 冲突失败 | 用 `CREATE UNIQUE INDEX CONCURRENTLY`（PG）|
| MODIFY 列类型 | 重写表、长锁 | 新列 + 双写 + 切换 |
| 收紧 CHECK 约束 | 已有数据可能违规 | 先扫描存量数据 |

## 🟨 低危但需注意
| 模式 | 注意点 |
|------|--------|
| CREATE INDEX（无 CONCURRENTLY）| 大表写锁，建议 `CONCURRENTLY` |
| ADD COLUMN（nullable，有 default）| PG 11+ 不重写表，MySQL 视版本而定 |
| ADD 默认值为非常量（如函数）| 部分方言行为不一致 |
```

### references/lock-risk-matrix.md

```markdown
# 锁表风险矩阵

风险 = f(操作类型, 表行数量级)。结合项目实际行数评级。

| 操作 | < 10万行 | 10万–1千万 | > 1千万 |
|------|---------|-----------|---------|
| DROP TABLE | 低（瞬时元数据锁）| 低 | 低 |
| ALTER ADD COLUMN（PG 11+）| 低 | 低 | 低 |
| ALTER ADD COLUMN w/ default 重写 | 低 | 🟧 中 | 🟥 高 |
| CREATE INDEX 普通 | 低 | 🟧 中 | 🟥 高 |
| CREATE INDEX CONCURRENTLY | 低 | 低 | 低（注意失败遗留 invalid index）|
| ADD UNIQUE INDEX | 🟧 中（冲突检查）| 🟥 高 | 🟥 高 |
| NOT NULL + 回填 | 低 | 🟧 中 | 🟥 高 |
| MODIFY 列类型 | 🟧 中 | 🟥 高 | 🟥 高（重写表）|

> 行数无法确定时：默认按「> 1千万」保守评级，并提示用户确认。
```

### references/rollback-template.md

```markdown
# 回滚脚本模板

每个迁移应提供 forward（上卷）与 rollback（回滚）两段。

## 加列（安全模式）
```sql
-- forward
ALTER TABLE orders ADD COLUMN status_new VARCHAR(20);

-- rollback
ALTER TABLE orders DROP COLUMN status_new;
```

## 改 NOT NULL（分两步，禁止合并）
```sql
-- Step 1 forward: 加可空列
ALTER TABLE orders ADD COLUMN email_verified BOOLEAN;
-- Step 1 rollback
ALTER TABLE orders DROP COLUMN email_verified;

-- Step 2 forward: 回填 + 收紧（必须在 Step 1 全量上线后）
UPDATE orders SET email_verified = false WHERE email_verified IS NULL;
ALTER TABLE orders ALTER COLUMN email_verified SET NOT NULL;
-- Step 2 rollback
ALTER TABLE orders ALTER COLUMN email_verified DROP NOT NULL;
```
```

到这一步，三层结构全部就位：**frontmatter 永驻、正文按需、references 深挖**。

---

## 六、验证：手动触发与自动触发

### 手动触发（最直接）

在项目里运行 Claude Code，输入：

```
/migration-guardian
```

应看到 Claude 开始按正文流程执行：先用 Glob 找迁移文件、再 Grep 关键字、再调取 references 分类。

### 自动触发（才是 Skill 的价值）

新建一个故意带坑的迁移文件 `db/migrate/20260622_drop_users_avatar.sql`：

```sql
-- 删掉头像列，反正没人用了
ALTER TABLE users DROP COLUMN avatar_url;
TRUNCATE TABLE user_avatar_history;
CREATE INDEX idx_users_email ON users(email);  -- 大表无 CONCURRENTLY
```

然后对 Claude 说一句**完全不提 skill 名**的自然话术：

```
我准备把这周的迁移上生产，帮我看一眼有没有问题。
```

**预期结果**：Claude 不需要你点名，会自动识别这是「审查迁移文件」场景，加载 `migration-guardian`，产出类似下面的报告：

```
🟥 高危
- db/migrate/20260622_drop_users_avatar.sql:3  DROP COLUMN avatar_url
  → 旧代码可能仍在读取，建议先双写/停引用，本轮不要删
- db/migrate/...:4  TRUNCATE user_avatar_history
  → 数据不可逆，建议先 RENAME 为 _archived 观察

🟨 低危但锁风险
- db/migrate/...:5  CREATE INDEX 无 CONCURRENTLY
  → users 为大表时写锁显著，建议改 CREATE INDEX CONCURRENTLY

结论：本批次不建议直接上线，需停机窗口或拆分为多步灰度迁移。
回滚预案见已生成的 forward/rollback 脚本。
```

> **常见问题**：自动触发没成功？
> **排查清单**：
> 1. description 里有没有出现用户原话里的关键词？（如「迁移」「上线」「生产」）
> 2. 是否把 Skill 放对了 `.claude/skills/<name>/SKILL.md` 路径？
> 3. 启动时是否重新加载？（改动 frontmatter 后建议重启会话）
> 4. 是否与另一个 Skill 的 description 重叠导致歧义？给本 Skill 加更尖锐的边界。

---

## 七、用 Evals 闭环：验证「该出手才出手」

Skill 写完不算完，要验证两件事：**该触发时必触发（召回）、不该触发时不乱入（精确）**。Claude Code 的 skill-creator / Evals 能力支持你跑一组带预期结果的用例。

设计两类用例：

```yaml
# evals 用例示例
should_activate:
  - input: "帮我看看 alembic/versions/ 下新加的迁移安不安全"
    expect: migration-guardian 被加载，输出风险报告
  - input: "prisma migrate 这次的 down 迁移会不会锁库"
    expect: 加载并评估锁表风险

should_not_activate:
  - input: "帮我写一个查最近订单的 SQL 查询"
    expect: 不加载（这是应用层查询，非迁移）
  - input: "生成一批测试用的 seed 数据"
    expect: 不加载（属 seed，已在 description 排除）
```

跑完看通过率。**召回不足**就去补 description 关键词；**误触发多**就去收紧边界词。这是把 Skill 从「能用」打磨到「好用」的标准动作。

---

## 八、完整工作流程图

```mermaid
flowchart TD
    A[用户发消息] --> B{Claude 扫描所有 Skill 的 frontmatter}
    B -->|描述命中意图| C[加载该 Skill 正文<br/>第二层]
    B -->|未命中| Z[按普通对话处理]
    C --> D[正文流程引用 references?]
    D -->|是| E[按需读取参考文件<br/>第三层]
    D -->|否| F[直接执行正文步骤]
    E --> F
    F --> G{是否破坏性/锁风险?}
    G -->|有| H[输出分级报告+缓解建议+回滚]
    G -->|无| I[给出低风险确认]
    H --> J[用户决定是否修改]
    I --> J
    J --> K[结束<br/>不自动改文件]

    style B fill:#e8f0fe,stroke:#1a73e8
    style C fill:#fef7e0,stroke:#f9ab00
    style E fill:#e6f4ea,stroke:#34a853
```

---

## 九、实战案例：把它接到 CI 里做提交门禁

Skill 在交互式会话里好用，但「上线前审查」这种事更适合卡在 CI。借助 Claude Code 的无头模式（`-p` / `--print`），可以把这个 Skill 变成 PR 检查：

```bash
# 在 CI 的迁移文件变更时调用
changed_migrations=$(git diff --name-only origin/main...HEAD -- \
  'db/migrate/*.sql' 'alembic/versions/*.py' 'prisma/migrations/**/migration.sql')

if [ -n "$changed_migrations" ]; then
  claude -p "审查以下迁移变更的安全性与锁表风险，只输出 JSON：{risk: high|medium|low, items:[...], block: bool}：$changed_migrations" \
    --allowed-tools "Read,Grep,Glob"
fi
```

`block: true` 时 CI 直接失败，挡住破坏性迁移进主干。这样 Skill 的知识资产就从「个人助手」升级为「团队门禁」，且逻辑只有一处，维护成本极低。

---

## 延伸阅读

- **Claude Code 官方 Skills 文档**：`code.claude.com/docs/en/skills`——结构与最佳实践权威来源
- **SKILL.md 渐进式披露深度篇**：AgentUpdate Chapter 29，对三层加载机制的工程化讨论
- **Skill 触发实测（650 次会话）**：社区对「为什么我的 Skill 不触发」的系统性排查
- **skill-creator 能力**：用 `/skill-creator` 直接脚手架 + 跑 Evals，省去手搓骨架
- **打包成 Plugin 分发**：当一个 Skill 在多个项目复用时，进一步封装为 Plugin

---

## 训练营适用性评估

- **适合训练营类型**：中后端工程师训练营、DevOps/SRE 训练营、AI 编程实战训练营（进阶阶段）
- **前置基础**：学员需先完成「Claude Code 入门 + 首个有效会话」（约 0.5 天）
- **预计教学时长**：讲授 1.5 小时 + 动手实作 2.5 小时（含自己选一个业务领域重写 Skill）+ Evals 打磨 1 小时，合计约半天
- **可拓展练习**：让学员把 Skill 改造到自己技术栈（如从 Postgres 改 MongoDB / 从 Rails 改 Django），体会 description 与 references 的可迁移性
- **评估标准**：自动触发通过率 ≥ 80%、误触发率 ≤ 10%、报告格式符合正文规范

---

## 参考来源

1. [Creating custom skills — Claude 官方文档](https://claude.com/docs/skills/how-to) — Skill 创建、结构化与测试的权威指南，涵盖 frontmatter 与多文件包
2. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — Claude Code 内 Skill 的管理、共享与自定义命令机制
3. [Chapter 29｜Deep Dive into SKILL.md: Progressive Disclosure — AgentUpdate](https://www.agentupdate.ai/tutorial/claude-code-tutorial/lesson-29/) — SKILL.md 模块化分层设计的工程化深度讲解
4. [How Claude Code Automatically Triggers Skills vs Manual Invocation — BSWen](https://docs.bswen.com/blog/2026-03-24-skill-triggering/) — 自动触发 vs 手动 `/skill` 调用的边界与排查
5. [How to Create Custom Claude Skills: Complete Tutorial (2026) — SkillsMD](https://skillsmd.pro/blog/create-custom-claude-skills) — 2026 版从零创建 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)
