一份从零到发布的 Claude Code Skill 实战教程,带完整脚本、渐进式披露设计与可复现案例。
本教程带你从零创建一个名为 comment-doctor 的自定义 Skill:它扫描项目源代码,精确定位缺失文档注释的函数、方法与类,再按目标语言规范(Python docstring、JSDoc、GoDoc、JavaDoc)生成准确的中文注释并写回文件。学完你将掌握 Skill 的目录结构、SKILL.md 元数据写法、渐进式披露(Progressive Disclosure)三层架构,以及如何用挂载脚本把"凭感觉分析"升级为"可量化、可复核"的工程化能力。适用人群:已跑通 Claude Code 并能创建项目的开发者,希望把重复性文档工作沉淀为团队可复用资产的人。
claude --version 可正常输出版本号)ast 标准库做静态解析,无需第三方依赖)在动手之前,必须先吃透 Skill 区别于"一段提示词"的核心设计:渐进式披露(Progressive Disclosure)。它的工程动机很朴素——上下文窗口是稀缺资源,不该把所有指令一股脑塞进去。
Skill 把信息分成三个加载层级:
flowchart TD
A[用户提问/命令] --> B{Tier 1: 仅读 frontmatter description}
B -->|不相关| C[不加载, 零成本]
B -->|相关| D[Tier 2: 加载 SKILL.md 正文]
D --> E[执行任务]
E -->|需要细规范| F[Tier 3: 按需加载 references/ 脚本]
F --> E
style B fill:#fef3c7
style D fill:#dbeafe
style F fill:#dcfce7
| 层级 | 内容 | 何时加载 | 成本 |
|---|---|---|---|
| Tier 1 | frontmatter 的 description |
每次对话都读 | 极低 |
| Tier 2 | SKILL.md 正文指令 |
仅当 Skill 被判定相关并触发 | 中 |
| Tier 3 | references/、scripts/ 等附属文件 |
正文指令显式要求时才读 | 按需 |
这条铁律直接影响成败:description 写不好,Skill 永远不会被触发;正文写太长,每次触发都浪费上下文。后面每一步都围绕这条铁律展开。
Claude Code 在两个位置查找 Skill:个人级 ~/.claude/skills/ 与项目级 <repo>/.claude/skills/。本教程选个人级,这样 comment-doctor 在任何项目里都能用。
Skill 的目录名必须与 name 字段一致,且用小写连字符(kebab-case)。一个自包含的 Skill 推荐结构如下:
~/.claude/skills/comment-doctor/
├── SKILL.md # 入口:元数据 + 正文指令
├── scripts/
│ └── find_undocumented.py # Tier 3:静态扫描脚本
└── references/
└── docstring-styles.md # Tier 3:各语言注释风格规范
操作命令(在终端执行):
mkdir -p ~/.claude/skills/comment-doctor/scripts
mkdir -p ~/.claude/skills/comment-doctor/references
预期结果:目录创建后,Claude Code 启动时会自动发现该 Skill 并把它的 description 注入可触发列表。你可以在会话里输入 / 或直接问"有没有补全注释的技能"来确认它已被识别。
常见问题:
~/.claude/skills/ 下而非误建到 ~/.claude/skill/(少了个 s);确认 SKILL.md 大小写正确,Windows 上尤其易错。<repo>/.claude/skills/comment-doctor/,并提交进 Git 供团队共享。SKILL.md 由两部分组成:顶部的 YAML frontmatter 与下方的正文指令。先看 frontmatter 的最小可用版本:
---
name: comment-doctor
description: 扫描项目源代码,定位缺失文档注释的函数、方法与类,按目标语言规范(Python docstring、JSDoc、GoDoc、JavaDoc)生成准确的中文注释并写回文件。当用户要求"补全注释""给函数加文档""生成 docstring""让代码自解释",或代码缺少文档时触发。
---
frontmatter 只有两个必填字段,但每一个都有硬约束:
| 字段 | 约束 | 写法要点 |
|---|---|---|
name |
最长 64 字符,须与目录名一致 | 全小写连字符,避免缩写歧义 |
description |
最长 1024 字符 | 第三人称,先说"做什么",再列"何时触发"的关键短语 |
description 是整个 Skill 的命脉。Claude 仅凭它判断是否调用本 Skill,因此要"埋"进用户真实会说的高频短语("补全注释""生成 docstring""让代码自解释")。注意要用第三人称("扫描…定位…"),不要写"我可以帮你"这类第一人称表述——那是反模式,会降低匹配准确率。
接着写正文指令。正文应聚焦"做什么 + 用什么工具做 + 边界在哪",避免长篇大论:
# Comment Doctor
## 何时使用
当用户希望为无注释的函数、方法、类补全文档注释时使用。语言涵盖 Python、JavaScript/TypeScript、Go、Java。
## 工作流程(务必按序)
1. **先扫描**:运行 `python3 ~/.claude/skills/comment-doctor/scripts/find_undocumented.py <目标路径>`,得到缺失注释的函数清单(JSON)。
2. **读上下文**:对清单中的每一项,读取该函数及其调用处的代码,理解真实行为,而非按函数名臆测。
3. **查规范**:按目标语言,参考 `references/docstring-styles.md` 中的格式样板生成注释。内容要准确反映实际逻辑(参数含义、返回值、副作用、抛出的异常)。
4. **写回文件**:用 Edit 工具把注释插入到函数定义正下方。每改完一个文件,向用户报告本次新增注释的函数数量。
5. **输出总结**:在会话末尾给出 Markdown 表格,列出已补全注释的文件与函数数。
## 边界(禁止行为)
- 不要修改函数体逻辑,只加注释。
- 不要生成显而易见的废话注释(如 `# 返回 a + b` 给 `def add(a, b): return a+b`)。如果函数确实简单,写一句总结即可,或主动跳过并说明。
- 不要为已经存在注释的函数重复添加。
正文刻意短小——把"各语言具体长什么样"这种细节挪到 Tier 3 的 references/,需要时才加载。
常见问题:
Tier 2 正文只能让 Claude 读文件、靠理解来判断"哪些函数缺注释",这在大型项目里既慢又不可靠。挂载脚本(Tier 3)的引入正是为了解决这个痛点:用确定性算法给出精确清单,Claude 据此逐个处理,可复核、可重跑。
创建 ~/.claude/skills/comment-doctor/scripts/find_undocumented.py:
#!/usr/bin/env python3
"""扫描源文件,输出缺失文档注释的函数/类清单(JSON)。
用法: python3 find_undocumented.py <path>
"""
import ast, json, sys, os
# 支持的扩展名 → 解析器(本脚本聚焦 Python;JS/TS 可用 tree-sitter 扩展)
SUPPORTED = {".py": "python"}
SKIP_DIRS = {"node_modules", ".git", "venv", ".venv", "__pycache__", "dist", "build"}
def find_py(path):
"""解析单个 Python 文件,返回缺 docstring 的函数/类。"""
src = open(path, encoding="utf-8").read()
tree = ast.parse(src)
out = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
if not ast.get_docstring(node):
kind = "class" if isinstance(node, ast.ClassDef) else "function"
out.append({"type": kind, "name": node.name,
"line": node.lineno, "file": path})
return out
def walk(root):
results = []
for dp, dn, fn in os.walk(root):
dn[:] = [d for d in dn if d not in SKIP_DIRS] # 原地剪枝
for f in fn:
p = os.path.join(dp, f)
if os.path.splitext(f)[1] in SUPPORTED:
try:
results += find_py(p)
except SyntaxError:
pass # 跳过无法解析的文件
return results
if __name__ == "__main__":
root = sys.argv[1] if len(sys.argv) > 1 else "."
print(json.dumps(walk(root), ensure_ascii=False, indent=2))
运行预期结果(对某个含无注释函数的目录执行):
[
{"type": "function", "name": "calculate_tax", "line": 12, "file": "./billing/calc.py"},
{"type": "class", "name": "OrderRepository", "line": 40, "file": "./billing/repo.py"}
]
这段脚本体现了三层设计的精髓:它独立于 Skill 体系,可单独测试、单独改进;Skill 正文只需说"运行它并处理清单",把繁重的静态分析外包出去。
常见问题:
python3 xxx.py 调用,不依赖可执行位,无需 chmod +x。ast 换成 tree-sitter,或调用项目已有的 eslint/ts-doc 插件输出未文档化清单,结构保持不变。创建 ~/.claude/skills/comment-doctor/references/docstring-styles.md,这里集中存放每种语言的样板,正文只在需要时引导加载:
# 文档注释风格规范
## Python(Google 风格 docstring)
"""计算含税金额。
Args:
price: 不含税单价。
rate: 税率,0~1 之间的小数。
Returns:
含税后的总价。
Raises:
ValueError: rate 超出 [0, 1] 时抛出。
"""
## JavaScript / TypeScript(JSDoc)
/**
* 计算含税金额。
* @param {number} price - 不含税单价。
* @param {number} rate - 税率,0~1。
* @returns {number} 含税总价。
*/
## Go(GoDoc,注释紧贴声明、以名字开头)
// CalculateTax 计算含税金额。price 为不含税单价,rate 为 0~1 的税率。
func CalculateTax(price float64, rate float64) float64 { ... }
## Java(JavaDoc)
/** 计算含税金额。
* @param price 不含税单价
* @param rate 税率,0~1
* @return 含税总价
*/
把样板放进 Tier 3 而非正文,是因为它只在"生成注释"那一步才需要,提前加载纯属浪费上下文。
准备一个最小测试项目 ~/demo_shop/,内含故意无注释的代码:
# ~/demo_shop/billing.py
def apply_discount(price, pct):
if pct < 0 or pct > 1:
raise ValueError("pct 必须在 0~1")
return price * (1 - pct)
class Cart:
def add(self, sku, qty=1):
self.items[sku] = self.items.get(sku, 0) + qty
在 Claude Code 会话里触发 Skill(自然语言即可):
帮我把 ~/demo_shop/billing.py 里没注释的函数都补上文档。
预期结果:Claude 自动调用 comment-doctor,运行脚本得到清单,逐个生成注释并写回。最终 billing.py 变为:
def apply_discount(price, pct):
"""对原价施加折扣。
Args:
price: 原价。
pct: 折扣比例,0~1 之间(0.2 表示打 8 折)。
Returns:
折扣后的价格。
Raises:
ValueError: pct 不在 [0, 1] 区间时抛出。
"""
if pct < 0 or pct > 1:
raise ValueError("pct 必须在 0~1")
return price * (1 - pct)
class Cart:
"""购物车,维护 SKU 到数量的映射。"""
def add(self, sku, qty=1):
"""向购物车加入商品,已存在则累加数量。
Args:
sku: 商品唯一标识。
qty: 本次添加数量,默认 1。
"""
self.items[sku] = self.items.get(sku, 0) + qty
并给出类似总结表:
| 文件 | 新增注释函数/类 |
|---|---|
billing.py |
apply_discount、Cart、Cart.add |
常见问题:
# 返回 apply_discount 结果)? 在边界条款里加一句"对简单包装函数允许跳过并说明",并要求注释至少包含一个"非显而易见"的信息(如取值范围、副作用、异常)。把上述六步串成一个完整工作流,目标是一个 30+ 路由、文档缺失严重的 Flask 仓库。
cd ~/my_flask_appfind_undocumented.py app/,假设命中 47 处;
- 按文件分批,每批读上下文 → 查 docstring-styles.md → 用 Edit 写回 → 汇报;
- 全程不碰业务逻辑,只增注释。git diff --stat 查看改动行数;python -m compileall app/ 确认未引入语法错误;对抽样函数人工核对注释与逻辑是否一致。整个案例的价值在于可复现:同样的脚本、同样的指令,换一个项目再跑一次,结果同样可信。这正是 Skill 相比"一次性提示词"的根本优势。
flowchart LR
U[真实使用] --> T[触发记录]
T --> M{该触发时触发?}
M -->|漏触发| D1[在 description 补同义短语]
M -->|误触发| D2[收窄 description 场景]
M -->|准| OK[保持]
D1 --> U
D2 --> U
OK --> U
Skill 是"活"的资产,建议建立调优闭环:
~/.claude/skills/ 整体纳入一个 Git 仓库,团队成员克隆即用。https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview——理解 Skill 在整个 Agent 体系中的定位。https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices——frontmatter 字段约束与正文写作准则的权威来源。https://github.com/anthropics/skills——官方维护的 Skill 样例集,每个都是自包含文件夹,是抄结构范式的最佳参考。/skill-creator 可交互式生成脚手架,适合不想手写目录的人。| 维度 | 评估 |
|---|---|
| 适合的训练营类型 | AI 工程化实战营、研发效能提升营、Claude Code 专项训练营 |
| 学员基础要求 | 会用命令行、读过 YAML、写过任意一门后端语言 |
| 预计教学时长 | 讲解 + 跟练 90 分钟,独立完成实战案例另需 60 分钟 |
| 可交付成果 | 每位学员带走一个可运行、可复用的个人 Skill,并能套用到自己项目 |
| 进阶衔接 | 学完后可衔接"造量化基准测试体系""调优 Skill 描述"等专题,深化测试与触发优化 |
本教程把 Skill 的元数据、目录结构、渐进式披露三层与脚本挂载四个要点,全部压缩进一个能立刻用、能反复跑的真实技能里。掌握 comment-doctor 的搭建路径,你就掌握了造任何自定义 Skill 的通用方法。
name/description 字段约束与正文写作准则。[!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 等场景全覆盖。
