本教程面向已经装好 Claude Code、能跑通基本对话与文件操作的读者,目标是用一个真实可运行的项目——重复代码检测 Skill(duplicate-code-detector)——带你完整走完自定义 Skill 的开发全流程。读完你将掌握:SKILL.md 的标准结构与 frontmatter 写法、渐进式披露(Progressive Disclosure)三层加载机制、scripts/ + references/ + assets/ 三类捆绑资源的取舍、动态上下文注入(!command``)、用 skill-creator 做 eval 验证触发准确率,以及把 Skill 装到个人级 / 项目级 / 插件级三个位置的最佳实践。适用人群:想把自己的重复劳动沉淀成"可复用能力"的后端 / 前端 / 全栈工程师,以及希望给团队制定统一工程规范的 Tech Lead。
claude --version 能输出版本号,建议 v2.1.145+,以使用 /run、/verify 等较新捆绑技能)node_modules 或 vendor 也无所谓,脚本会自动忽略)Skill 的官方定义非常直白:一个文件夹,里面放一份 SKILL.md 加可选的脚本与资源。它和你在 CLAUDE.md 里写一大段"项目记忆"的区别在于——CLAUDE.md 每次会话都全量加载,而 Skill 的正文只在被触发时才进入上下文。
这一点是整个 Skill 体系的灵魂,官方给了它一个正式名字:Progressive Disclosure(渐进式披露)。它分三层:
| 层级 | 内容 | 何时进入上下文 | 典型体积 |
|---|---|---|---|
| Tier 1 | name + description 元数据 |
始终在(系统提示里) | ~100 词 / 个 |
| Tier 2 | SKILL.md 正文 |
命中触发时 | 目标 1500–2000 词 |
| Tier 3 | scripts/ references/ assets/ |
执行中按需读取 | 无上限(脚本可不读入直接执行) |
也就是说,你装 50 个 Skill,常驻上下文成本只有"50 条 description",几十 KB 而已。这正是 Skill 能让你"既拥有大量能力、又不撑爆上下文窗口"的根本原因。
判断"该不该做成 Skill"的一句话标准:当你反复把同一段指令、检查清单、多步流程粘进对话,或者 CLAUDE.md 里某一节已经从"事实陈述"长成了"操作步骤"时——它就该被抽成 Skill。检测重复代码这件事完美命中:每次重构前你都要跑扫描、读结果、生成报告、给重构建议,流程固定、可脚本化、适合"只在重构时加载"。
官方的 skill-development 指南给出 6 步法,前两步是"用具体例子理解 Skill"和"规划可复用内容"。我们直接落地。
1.1 列出触发场景(这些会写进 description)
1.2 拆解可复用资源
| 重复出现的动作 | 沉淀成什么 | 为什么 |
|---|---|---|
| 扫描源码、归一化、哈希、聚类 | scripts/find_duplicates.py |
确定性强、每次都重写、可不读入直接执行(省 token) |
| 窗口大小、忽略目录、语言扩展等调参说明 | references/heuristics.md |
长、查阅型,不该污染 SKILL.md 正文 |
| 报告长什么样 | examples/sample-report.md |
给 Claude 一个输出格式锚点 |
1.3 确定目录结构
duplicate-code-detector/
├── SKILL.md # 入口(必需)
├── scripts/
│ └── find_duplicates.py # 核心扫描脚本
├── references/
│ └── heuristics.md # 调参/CI 集成说明
└── examples/
└── sample-report.md # 输出示例
注意:只建你真正需要的子目录。assets/(用于产出的模板、图片、字体)这次用不到,就不要建——官方明确强调"删除用不到的示例目录,只建实际需要的"。
Skill 设计有一条黄金法则:能交给脚本的就别让模型即兴写。脚本省 token、可复用、结果确定。我们先把这个 Skill 最重的部分——扫描脚本——写出来。
在 duplicate-code-detector/scripts/find_duplicates.py 写入:
#!/usr/bin/env python3
"""按归一化哈希定位重复 / 近似重复的代码块。"""
import argparse, hashlib, os, re, json
from collections import defaultdict
DEFAULT_EXT = (".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".java", ".c", ".h", ".cs", ".rb")
def normalize(lines):
"""把变量名/数字/字符串/注释抹平,让"换个名字的复制粘贴"也能命中。"""
out = []
strip_str = re.compile(r'"[^"]*"|\'[^\']*\'')
strip_cmt = re.compile(r'#.*$|//.*$')
for ln in lines:
ln = strip_str.sub('"S"', ln) # 字符串 -> S
ln = strip_cmt.sub('', ln) # 去注释
ln = re.sub(r'\b\d+\b', 'N', ln) # 数字 -> N
ln = re.sub(r'\b[A-Za-z_]\w*\b', 'I', ln) # 标识符 -> I
ln = re.sub(r'\s+', '', ln) # 去空白
if ln:
out.append(ln)
return out
def iter_files(root, exts, ignore):
for dp, dn, fn in os.walk(root):
dn[:] = [d for d in dn if d not in ignore]
for f in fn:
if f.endswith(exts):
yield os.path.join(dp, f)
def windows(norm, size):
for i in range(len(norm) - size + 1):
yield hashlib.md5("\n".join(norm[i:i+size]).encode()).hexdigest(), i + 1
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--root", default=".")
ap.add_argument("--window", type=int, default=6, help="每个块多少行")
ap.add_argument("--min-cluster", type=int, default=2, help="至少重复几处才报告")
ap.add_argument("--ext", default=",".join(DEFAULT_EXT))
ap.add_argument("--json", action="store_true")
a = ap.parse_args()
exts = tuple(a.ext.split(","))
ignore = {"node_modules", ".git", "vendor", "dist", "build", ".venv"}
clusters = defaultdict(list)
for path in iter_files(a.root, exts, ignore):
raw = open(path, encoding="utf-8", errors="ignore").read().splitlines()
norm = normalize(raw)
seen = set()
for h, line in windows(norm, a.window):
if h in seen: # 同一文件内的滑动重叠只记一次
continue
seen.add(h)
clusters[h].append((path, line))
dups = {h: l for h, l in clusters.items() if len(l) >= a.min_cluster}
if a.json:
print(json.dumps(dups, indent=2)); return
print(f"# 重复代码报告\n\n扫描目录: `{a.root}` | 窗口: {a.window} 行 | 命中聚类: {len(dups)}\n")
for rank, (h, locs) in enumerate(sorted(dups.items(), key=lambda x: -len(x[1])), 1):
print(f"## #{rank} 重复 {len(locs)} 处")
for p, line in locs:
print(f"- `{p}:{line}`")
print()
print(f"---\n共 {len(dups)} 个重复聚类,建议优先重构重复 ≥3 处的块。")
if __name__ == "__main__":
main()
操作说明:给脚本可执行权限并自测。
chmod +x duplicate-code-detector/scripts/find_duplicates.py
python3 duplicate-code-detector/scripts/find_duplicates.py --root . --window 6
预期结果:
# 重复代码报告
扫描目录: `.` | 窗口: 6 行 | 命中聚类: 4
## #1 重复 5 处
- ./src/auth/login.py:42
- ./src/auth/signup.py:38
- ./src/api/user.py:120
...
---
共 4 个重复聚类,建议优先重构重复 ≥3 处的块。
常见问题:
for i in range(n): 这类样板会被全员命中。解法是调大 --window(如 8–10),或对热点目录单独跑。references/heuristics.md,不塞进 SKILL.md。SKILL.md 决定 Skill 会不会被触发以及触发后怎么执行。它有两部分:YAML frontmatter(决定触发)+ Markdown 正文(决定执行)。先看完整文件,再逐段拆解。
在 duplicate-code-detector/SKILL.md 写入:
---
name: duplicate-code-detector
description: Detects duplicated and near-duplicated code blocks across a codebase and produces a refactor report. This skill should be used when the user asks to "find duplicate code", "检测重复代码", "代码重复检测", "找出抄来的代码", "refactor duplication", or wants a duplication audit before refactoring.
allowed-tools: Bash, Read, Glob, Grep
---
# 重复代码检测
## 用途
扫描指定目录,按归一化哈希定位"几乎相同"的代码块,输出按重复次数排序的报告,用于重构前锁定最值得抽取公共方法的高频重复。
## 何时使用
- 用户问"哪些代码重复了 / 能不能抽公共方法 / 这段是不是抄过来的"
- 重构、合并、技术债清理前需要一份重复清单
## 执行步骤
1. 运行内置扫描脚本(默认 6 行窗口、跨文件去重):
```bash
python3 scripts/find_duplicates.py --root . --window 6 --min-cluster 2
```
2. 对重复 ≥3 处的聚类,优先建议抽取为公共函数 / 组件 / Hook。
3. 调参与多语言支持见 `references/heuristics.md`:缩小窗口抓短片段、放大窗口只留长克隆;`--ext` 限定语言;`--json` 便于接入 CI。
## 报告规范
- 先给总数与最严重的 3 个聚类
- 每个聚类列出 `文件:行号`
- 末尾给一句重构建议;不要直接改代码,除非用户明确要求
## 当前仓库规模
!`find . -type f \( -name '*.py' -o -name '*.js' -o -name '*.ts' \) -not -path '*/node_modules/*' | wc -l`
name:必填,必须与目录名一致,也是 /duplicate-code-detector 这个斜杠命令的名字。description:必填,这是触发准确率的命脉。官方建议三条规则——① 用第三人称("This skill should be used when…");② 塞入用户真实会说的触发短语(中英文都给);③ 控制在 100 词以内。坏例子 vs 好例子:yaml
# ❌ 坏:人称错、没触发词、太泛
description: Use this skill when working with code duplication.
# ✅ 好:第三人称 + 具体触发短语
description: This skill should be used when the user asks to "find duplicate code", "检测重复代码", ...
allowed-tools:可选,预先批准这个 Skill 能用的工具,免去每次执行都要你按 y 放行。这里给 Bash, Read, Glob, Grep 已经够用。
disable-model-invocation:可选,设为 true 时模型不能自动触发,只能你手敲 /duplicate-code-detector。本例不需要,但做"危险操作"类 Skill 时强烈建议开启。
正文最后一行 !`find ... | wc -l` 是 Skill 的杀手锏:Claude Code 在把正文交给模型之前,会先执行反引号里的命令,把输出原位替换进去。于是模型每次看到 SKILL.md 时,里面已经是"当前仓库有 1342 个源文件"这样的实时数字,而不是一句干巴巴的指令。这和官方第一个示例 Skill 用 !`git diff HEAD` 把实时 diff 注入是同一个机制。
官方对正文风格的措辞非常明确:全程用祈使句/动词原形开头,不要用第二人称"你应该"。
# ✅ 推荐
运行扫描脚本,得到聚类结果。
对重复 ≥3 处的聚类,优先抽取为公共函数。
# ❌ 避免
你应该运行扫描脚本。
如果你需要调参,可以看 heuristics.md。
正文目标 1500–2000 词。任何超过这个量的内容(详细调参、CI 集成、多语言 AST 策略)一律下沉到 references/。原则是:信息要么在 SKILL.md,要么在 references,绝不在两边各放一份。
在 references/heuristics.md 写调参与集成说明(节选):
# 调参与进阶
## 窗口大小(--window)
- 4–6 行:抓短克隆(重复的工具函数、相似的校验逻辑),噪声偏高
- 8–12 行:只留结构完整的克隆块,适合做"抽取公共方法"的决策
- >12 行:仅命中大段复制粘贴,几乎无误报
## 多语言
默认覆盖 .py/.js/.ts/.go/.java 等。仅扫描 Go:--ext .go
新增语言只需把扩展名加进 --ext,归一化是语言无关的。
## CI 集成
--json 输出机器可读结果,在 CI 里判定:
dups=$(python3 scripts/find_duplicates.py --json --min-cluster 3)
test "$(echo "$dups" | jq 'length')" -lt 5 || exit 1 # 重复块超过 5 个则失败
在 examples/sample-report.md 放一份理想输出,给模型一个格式锚点(内容即第 2 步的"预期结果")。这两类文件默认不进上下文——只有当 SKILL.md 正文里指向它们、且模型判断当前需要时,才会被读入。这就是 Tier 3 的价值。
Skill 的存放位置决定"谁能用"。官方定义了四个层级,优先级从高到低:
| 层级 | 路径 | 作用范围 |
|---|---|---|
| Enterprise | 受管设置 | 全组织所有人 |
| Personal | ~/.claude/skills/duplicate-code-detector/SKILL.md |
你的所有项目 |
| Project | <repo>/.claude/skills/duplicate-code-detector/SKILL.md |
仅本项目(可随 git 分享给团队) |
| Plugin | <plugin>/skills/duplicate-code-detector/SKILL.md |
启用该插件处 |
把整个 duplicate-code-detector/ 目录拷到目标位置即可:
# 装成个人级:所有项目可用
cp -r duplicate-code-detector ~/.claude/skills/
# 装成项目级:随仓库分享给同事
cp -r duplicate-code-detector your-repo/.claude/skills/
关键特性:实时变更检测。你修改 ~/.claude/skills/ 或项目 .claude/skills/ 下的 SKILL.md 后,无需重启 Claude Code,当前会话立刻生效——非常适合边改边测。
进入任意仓库,启动 claude,分别用两种方式触发:
# 方式 A:让模型自动触发
> 重构前帮我查一下哪些代码重复了,给个清单
# 方式 B:直接喊命令
> /duplicate-code-detector
两者都应输出一份带文件:行号的报告。若方式 A 没触发——大概率是 description 触发词不够,回第 3 步加短语。
手工测一两条不够,真正可靠的做法是用捆绑的 /skill-creator 跑 eval 基准。流程:
yaml
# eval_cases.yaml
- input: "帮我把这几段相似的校验逻辑抽成一个函数"
expect_trigger: true
- input: "解释一下 Python 的 GIL 是什么"
expect_trigger: false
- input: "find duplicate code in src/"
expect_trigger: true
- input: "把这个函数改名叫 validateEmail"
expect_trigger: false/skill-creator,选择"评估现有 Skill" → 指向 duplicate-code-detector 与用例文件。description,把误报场景用更精确的措辞排除——直到命中率稳定在 90% 以上。常见问题:
description 太宽(如"代码分析")。改成更窄的动词与名词组合。skills 拼成 skill)、name 与目录名是否一致、description 是否被截断(超长会被截)。~/,应改为相对 Skill 目录的相对路径 scripts/find_duplicates.py。假设你的团队有个 Python + TypeScript 混合仓库 monorepo/,后端 3 个微服务都在各自复制了一套"参数校验 + 错误码返回"的逻辑。目标:重构前生成一份优先级清单。
操作:
cd monorepo
claude
# 在交互里输入:
> /duplicate-code-detector
模型会执行 SKILL.md 的步骤:调用 scripts/find_duplicates.py --root . --window 8 --min-cluster 2(窗口放大到 8,避免短样板噪声),读取 references/heuristics.md 确认参数合理,最后按"报告规范"输出:
## 概览
扫描 1247 个源文件,命中 7 个重复聚类,其中 3 个重复 ≥4 处。
## 优先级 1(重复 6 处,跨 services/auth、services/order、services/pay)
- services/auth/handlers.py:88
- services/order/handlers.py:51
- services/pay/handlers.py:73
- ...
建议:抽取 validate_and_respond(payload, schema) 到 common/http.py,6 处统一替换。
## 优先级 2(重复 4 处,TS 侧用户校验)
- ...
---
建议本周先处理优先级 1,预计减少 ~180 行重复代码。需要我直接动手重构吗?
整个过程从"打开仓库"到"拿到可执行的重构清单"不到 1 分钟,且报告基于脚本的真实扫描结果,而非模型的猜测。把 SKILL.md 放进项目 .claude/skills/ 并提交到 git,全团队任何人 claude 一开就能复用同一套审计标准——这就是 Skill 把"个人经验"变成"团队标准库"的方式。
flowchart TD
U([用户提问: 哪些代码重复了?]) --> T1{"Tier 1 元数据\n始终在上下文"}
T1 --> DEC{description 命中?}
DEC -- 否 --> N[正常对话回复]
DEC -- 是 --> T2["Tier 2\n加载 SKILL.md 正文"]
T2 --> INJ["动态注入\n!find...wc -l\n实时文件数"]
INJ --> RUN["执行脚本\nscripts/find_duplicates.py"]
RUN --> NEED{需要调参说明?}
NEED -- 是 --> T3["Tier 3\n按需读 references/heuristics.md"]
NEED -- 否 --> RPT[按规范生成报告]
T3 --> RPT
RPT --> OUT([输出 文件:行号 + 重构建议])
flowchart LR
subgraph 开发循环
A[1 列触发场景] --> B[2 写脚本 find_duplicates.py]
B --> C[3 写 SKILL.md frontmatter+正文+动态注入]
C --> D[4 补 references/examples]
D --> E[5 装到 personal/project 位置]
E --> F[6 skill-creator 跑 eval]
F -->|命中率不足| C
F -->|达标| G([发布])
end
当你的重复检测 Skill 长出更多配套能力(比如一个 PreToolUse hook 在每次提交前自动跑检测、一个子代理做深度 AST 克隆分析),就可以把它打包成 Plugin。官方机制:在 Skill 文件夹里加一个 .claude-plugin/plugin.json,它就以 <name>@skills-dir 的形式作为插件加载,可以同时捆绑 hooks、agents、MCP 服务器。Plugin 还支持 plugin-name:skill-name 的命名空间,不会和其他层级的同名 Skill 冲突,适合在组织内分发。
另一个值得记住的能力是 把 Skill 放进子代理执行:对于大型仓库的全面扫描,可以在 SKILL.md 里指示用 Explore 子代理并行收集文件清单,主代理只负责汇总——这是控制上下文膨胀的常用招数。
code.claude.com/docs/en/skills ——frontmatter 字段、动态注入、子代理执行、skill-creator eval 的权威来源platform.claude.com/docs/en/agents-and-tools/agent-skills/overview):Skill 是跨工具的开放标准,理解它有助于把同一份 Skill 复用到 Claude Desktop、Agent SDKCLAUDE.md / Sub-Agents / Hooks / Skills 四件套何时取用的对比~/.claude/skills/ 的真实 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 等场景全覆盖。
