本教程带你从零亲手打造一个名为 api-contract-doc 的 Claude Code Skill——它能扫描项目里的路由/控制器代码,自动反向工程出一份符合 OpenAPI 3.0 规范的接口契约文档。通过这个实战案例,你将掌握 Skill 的底层机制(目录结构、SKILL.md、渐进式披露、脚本挂载)、skill-creator 加速器、frontmatter 的触发调优,以及调试发布全流程。适合已跑通 Claude Code、想让 AI 按团队规范重复执行复杂任务的后端开发者与工程效率工程师。
claude 进入会话)很多人把 Skill 想得很神秘,其实它就是一个带约定结构的文件夹。Claude Code 启动时会把三个位置的 Skill 注册进会话:
| 级别 | 路径 | 适用场景 |
|---|---|---|
| 个人级 | ~/.claude/skills/<name>/SKILL.md |
跨项目复用,比如你的 commit 规范 |
| 项目级 | <project>/.claude/skills/<name>/SKILL.md |
团队共享,随仓库走 |
| 插件级 | 通过 plugin marketplace 安装 | 第三方能力 |
一个 Skill 的标准目录长这样:
api-contract-doc/
├── SKILL.md # 唯一必需:入口文件
├── scripts/ # 可选:可执行脚本
│ └── extract-endpoints.js
└── reference/ # 可选:按需加载的详细资料
├── openapi-conventions.md
└── team-api-style.md
核心机制是"渐进式披露"(Progressive Disclosure):Claude 启动时只读取每个 Skill 的 SKILL.md(理想控制在 500 行以内),把 token 成本压到最低;只有当判断出某个 Skill 与当前任务相关、且需要更深细节时,才会去 Read reference 子目录里的文件。这就是为什么你能塞进几千行规范文档而不爆上下文。
flowchart TD
A[Claude Code 启动] --> B[扫描三个位置的 skills 目录]
B --> C[只读每个 SKILL.md 的<br/>frontmatter + 正文摘要]
C --> D{当前任务是否匹配某 Skill?}
D -->|否| E[跳过该 Skill 不占上下文]
D -->|是| F[加载完整 SKILL.md 正文]
F --> G{需要更深细节?}
G -->|是| H[按需 Read reference 文件]
G -->|需要执行确定性操作| I[Bash 调用 scripts 脚本]
H --> J[按规范产出结果]
I --> J
Claude Code 自带一个 skill-creator 官方 Skill,专门帮你生产新 Skill。两种起步方式:
方式 A:加速器(推荐新手)
直接在会话里说:
/skill-creator 帮我创建一个叫 api-contract-doc 的 skill,
功能是扫描项目路由代码生成 OpenAPI 3.0 接口文档
skill-creator 会问你几个澄清问题,然后自动生成目录结构、frontmatter 草稿和正文框架。它的价值在于保证文件结构、字段名符合规范,免去查文档。
方式 B:纯手工(本教程采用,便于讲透每一处)
mkdir -p ~/.claude/skills/api-contract-doc/scripts
mkdir -p ~/.claude/skills/api-contract-doc/reference
touch ~/.claude/skills/api-contract-doc/SKILL.md
手工的好处是你完全掌控每一行。下面的步骤都基于手工方式推进。
SKILL.md 顶部是一段 YAML frontmatter,用 --- 包裹。其中只有 description 决定 Skill 会不会被触发,这是新手最容易踩的坑。
---
name: api-contract-doc
description: 扫描项目路由或控制器源码,反向工程生成符合 OpenAPI 3.0 规范的接口契约文档(YAML/JSON)。当用户要"生成接口文档""出 API 文档""补接口说明""把代码转成 OpenAPI/Swagger""整理接口契约"时使用。支持 Express/Koa/Fastify 风格路由。
---
编写 description 的三条铁律:
常见问题:Skill 写好却从不自动触发?99% 是
description没写出用户嘴里的词。改 description,不需要重启,下一次会话即生效。
可选字段还有 allowed-tools(限制该 Skill 只能用哪些工具,如 Read, Bash)、version。本案例暂不限制,让 Claude 自由组合工具。
frontmatter 负责"何时用",正文负责"怎么用"。正文要写得像给一个聪明但不了解你项目的新同事的操作手册:明确步骤、明确分支、明确产出格式。
# 接口契约文档生成
## 何时使用
当用户希望从现有路由/控制器代码反向生成接口文档时激活本 Skill。
## 工作流程
1. **定位路由**:先用 Grep/Glob 找到项目的路由定义文件。
- Express:搜索 `app\.(get|post|put|delete|patch)\(` 或 `router\.(get|post)`
- Koa:搜索 `router\.(get|post)` 或 `\.ctx\.`
- 若用户指定了目录,优先扫该目录。
2. **粗扫出端点清单**:调用本 Skill 的脚本生成机器可读草稿:
```bash
node ~/.claude/skills/api-contract-doc/scripts/extract-endpoints.js <路由文件或目录>
```
脚本会输出 JSON 数组,每项含 method、path、handlerName。
3. **补全语义信息**:脚本只能给出骨架,请求/响应 schema、描述、示例需要你阅读
handler 函数体与相关 model 定义后补全。
4. **套用团队规范**:补全时务必遵循 reference/team-api-style.md 的命名与错误码约定;
OpenAPI 字段写法遵循 reference/openapi-conventions.md。
5. **产出**:默认输出 openapi.yaml 到项目根目录,并打印一段摘要给用户确认。
## 注意
- 路径参数(如 /users/:id)要转成 OpenAPI 的 {id} 写法。
- 遇到无法静态推断的字段,标注 TODO 而非编造。
注意第 2 步和第 4 步:把"机械活"交给脚本,把"判断活"留给模型。这是高质量 Skill 的设计哲学——确定性的事情用代码保证一致,需要语义理解的事情交给 LLM。
把几百行的 OpenAPI 规范要点和团队接口规范塞进 SKILL.md 会撑爆日常上下文。把它们拆到 reference 子目录,正文只在用到时点名引用。
reference/openapi-conventions.md(节选):
# OpenAPI 3.0 字段约定
- paths 下每个操作必填:summary、operationId、responses
- operationId 用 camelCase,全局唯一
- 响应体统一走 components/schemas 复用,避免内联
- 错误响应统一引用 ProblemDetails(RFC 7807)结构
- 字段 description 必填,用中文,一句话讲清用途
reference/team-api-style.md(节选):
# 团队接口风格
- URL 一律小写,单词用连字符分隔:/user-profiles 而非 /userProfiles
- 列表接口必须支持 page、pageSize 分页参数
- 时间字段用 ISO 8601 字符串,字段名以 At 结尾:createdAt、updatedAt
- 业务错误码 5 位数字,前两位标识模块
为什么这样组织?因为这两份资料只在"补全语义信息"那一步才需要。Claude 平时根本不读它们,只有在执行该 Skill 且走到第 4 步时,才会按正文指引 Read 这两个文件。一个 Skill 配 2000 行资料,日常启动成本几乎为零。
这是本 Skill 区别于"纯提示词 Skill"的关键。脚本用 Node 写,扫描 Express 路由生成端点骨架。
scripts/extract-endpoints.js(完整可运行):
#!/usr/bin/env node
// 从 Express/Koa 路由文件中静态提取端点骨架,输出 JSON
const fs = require('fs');
const path = require('path');
const target = process.argv[2];
if (!target) {
console.error('用法: extract-endpoints.js <文件或目录>');
process.exit(1);
}
const files = [];
function collect(p) {
const stat = fs.statSync(p);
if (stat.isFile() && /\.(js|ts)$/.test(p)) files.push(p);
else if (stat.isDirectory()) {
for (const f of fs.readdirSync(p)) collect(path.join(p, f));
}
}
collect(path.resolve(target));
// 匹配 app.get('/x', ...) / router.post('/y', ...)
const routeRe = /\.(get|post|put|delete|patch)\(\s*['"`]([^'"`]+)['"`]/g;
const paramRe = /:([A-Za-z_]\w*)/g;
const endpoints = [];
for (const file of files) {
const src = fs.readFileSync(file, 'utf8');
let m;
while ((m = routeRe.exec(src)) !== null) {
const method = m[1].toUpperCase();
const rawPath = m[2];
// Express :id -> OpenAPI {id}
const openapiPath = rawPath.replace(paramRe, '{$1}');
endpoints.push({
method,
path: openapiPath,
sourceFile: path.relative(process.cwd(), file),
handlerName: null, // 由模型阅读函数体补全
});
}
}
console.log(JSON.stringify({ count: endpoints.length, endpoints }, null, 2));
赋可执行权限:
chmod +x ~/.claude/skills/api-contract-doc/scripts/extract-endpoints.js
预期结果:对一个含 router.get('/users/:id', getUser) 和 router.post('/orders', createOrder) 的文件运行,输出:
{
"count": 2,
"endpoints": [
{ "method": "GET", "path": "/users/{id}", "sourceFile": "routes/users.js", "handlerName": null },
{ "method": "POST", "path": "/orders", "sourceFile": "routes/orders.js", "handlerName": null }
]
}
常见问题:脚本在会话里报"permission denied"?记得
chmod +x;Windows 下用node scripts/extract-endpoints.js显式调用解释器即可。
安装:因为是个人级路径(~/.claude/skills/),无需额外安装命令,新开会话即自动注册。项目级 Skill 同理,把目录放进 <project>/.claude/skills/ 即可。
触发有两种方式:
/api-contract-doc(斜杠 + Skill 名)强制调用,用于验证 Skill 是否注册成功。调试三连:
/ 触发斜杠菜单,找 api-contract-doc 是否在列。准备一个最小 Express 项目 mini-shop/routes/orders.js:
const router = require('express').Router();
// 创建订单
router.post('/orders', (req, res) => {
const { userId, amount } = req.body;
res.json({ id: 1001, userId, amount, createdAt: '2026-07-12T08:00:00Z' });
});
// 查询订单详情
router.get('/orders/:id', (req, res) => {
res.json({ id: req.params.id, status: 'paid' });
});
module.exports = router;
在项目根目录启动 Claude Code,只说一句话:
帮我把 mini-shop 的接口整理成 OpenAPI 文档,放到根目录 openapi.yaml。
预期发生的事:
api-contract-doc,Grep 定位到 routes/orders.js。extract-endpoints.js mini-shop/routes 拿到端点骨架。Read 两份 reference 文件套用规范。openapi.yaml:openapi: 3.0.3
info:
title: Mini Shop API
version: 1.0.0
paths:
/orders:
post:
summary: 创建订单
operationId: createOrder
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
userId: { type: integer, description: 下单用户ID }
amount: { type: number, description: 订单金额(元) }
responses:
'200':
description: 创建成功
content:
application/json:
schema:
type: object
properties:
id: { type: integer }
userId: { type: integer }
amount: { type: number }
createdAt: { type: string, format: date-time, description: 创建时间 }
/orders/{id}:
get:
summary: 查询订单详情
operationId: getOrderById
parameters:
- in: path
name: id
required: true
schema: { type: integer }
responses:
'200':
description: 查询成功
content:
application/json:
schema:
type: object
properties:
id: { type: integer }
status: { type: string, description: 订单状态 }
整个过程你只说了一句话,规范、命名、错误码全部对齐团队风格——这就是 Skill 把"重复教 AI"的成本一次性摊销掉的威力。
评估:用 skill-creator 的 eval 能力,准备 5-10 条真实 prompt(不同说法请求接口文档),跑一遍看触发率与产出质量。description 改一轮、脚本正则补一轮,指标就能稳定上升。
发布:项目级 Skill 直接提交进仓库 .claude/skills/,团队 clone 即用;要跨组织共享,可封装成 plugin 发布到 marketplace。
与 CLAUDE.md / Hooks 协同:把"接口变更必须同步更新 openapi.yaml"写进项目 CLAUDE.md,再用一个 PostToolUse Hook 在检测到 routes/ 改动时提示运行本 Skill,就形成了"改接口→自动催更文档"的闭环。
https://code.claude.com/docs/zh-CN/skillshttps://swagger.io/specification/[!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 等场景全覆盖。
