关于 dry-consolidation
该开发技能通过克隆检测和结构搜索识别重复代码,并指导提取为经过测试的共享抽象。适用于需要跨文件整理工具函数、组件或钩子的开发者。
主要功能
- 01使用 jscpd 检测代码克隆
- 02用 ast-grep 确认结构相似性
- 03按工具函数、组件和钩子分类
- 04规划抽象接口、位置与使用方
- 05编写测试并运行项目检查
适用场景
- 01提取多个文件中重复的工具函数
- 02合并重复的界面片段或状态逻辑
- 03先以 dry-run 检查重复项而不修改
- 04按指定范围整理代码重复
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ laurigates/ claude-plugins --skill dry-consolidation安装后在 Claude Code 中输入/dry-consolidation确认已加载。
附带文件scripts/tests/test-dry-consolidation.sh
详细介绍
DRY 整合
系统性地将重复代码提取为共享且经过测试的抽象。
何时使用此技能
| 在以下情况下使用此技能…… | 在以下情况下改用这些工具…… |
|---|---|
| 多个文件包含完全相同或几乎相同的代码块 | 只需清理单个文件 → /code:refactor |
| 组件之间存在复制粘贴的工具函数 | 只想查找反模式而不进行修复 → /code:antipatterns |
| 重复的 UI 模式(对话框、分页、错误状态) | 对文件或目录进行功能性重构 → /code:refactor |
| 重复的钩子或状态管理样板代码 | 仅进行结构化代码搜索 → ast-grep-search |
| 需要枚举变量名已更改的近似重复代码(jscpd 可在此找到这些代码簇) | 匹配一个已知的结构模式 → ast-grep-search |
| 重复的内联模式导致导入块臃肿 | 检查 lint/格式问题 → /lint:check |
上下文
- 目标路径:!
echo "$1" - 项目类型:!
find . -maxdepth 1 \( -name "package.json" -o -name "Cargo.toml" -o -name "pyproject.toml" -o -name "go.mod" \) - 源目录:!
find . -maxdepth 1 -type d \( -name "src" -o -name "lib" -o -name "app" -o -name "components" -o -name "packages" \) - 测试框架:!
find . -maxdepth 2 \( -name "vitest.config.*" -o -name "jest.config.*" -o -name "pytest.ini" -o -name "conftest.py" \) - 现有共享工具:!
find . \( -path "*/lib/*" -o -path "*/utils/*" -o -path "*/shared/*" -o -path "*/common/*" -o -path "*/hooks/*" \) -type f -print -quit
参数
$1:要扫描的路径或目录(默认为src/)--scope:指定提取类型:utilities、components、hooks或all(默认:all)--dry-run:分析并报告重复内容,不做任何更改
执行
执行此 7 步整合工作流。会话中有任务工具时,使用 TodoWrite 将每项提取作为单独任务跟踪(参见 .claude/rules/agentic-permissions.md § Task-tool availability);否则在回复中使用清单。
步骤 1:发现重复代码簇(确定性克隆检测)
使用确定性克隆检测器枚举重复范围,然后只读取报告中列出的范围,而不是整个候选文件。基于令牌的检测(jscpd)可忽略空白和格式差异以及外层符号名称,找到复制粘贴代码;如果包装函数已重命名,按名称搜索的 Grep 就会漏掉这些克隆对。随后,ast-grep(1b)还能识别代码块内部变量名已更改的情况。
1a. 使用 jscpd 检测基于令牌的近似重复代码
jscpd 是基于令牌的复制粘贴检测器,尽管名称中有 “js”,但它支持 150 多种语言;使用 npx 运行,无需全局安装。在目标路径上运行:
npx jscpd --reporters json --min-tokens 50 --output /tmp/jscpd-dry --silent <path>
它会将报告写入 /tmp/jscpd-dry/jscpd-report.json。读取该报告并解析其 duplicates 数组——每个条目都包含一对克隆代码的准确文件和行范围,以及其令牌数/行数:
{
"duplicates": [
{
"format": "tsx",
"lines": 12,
"tokens": 84,
"firstFile": { "name": "src/UserList.tsx", "start": 20, "end": 32 },
"secondFile": { "name": "src/OrderList.tsx", "start": 15, "end": 27 }
}
],
"statistics": { "total": { "clones": 3, "duplicatedLines": 40, "duplicatedTokens": 252, "percentage": 5.1 } }
}
对于报告中的每个克隆,只读取对应行范围(使用 Read,通过 offset/limit 指定 start/end 附近的范围),以确认重复并进行分类——不要读取整个候选文件。jscpd 报告的克隆相似度本身就很高(符合 --min-tokens 匹配条件);请在提取计划中记录令牌数/行数。
1b. 使用 ast-grep 进行结构确认
jscpd 找到代码簇后,使用 ast-grep 元变量确认它们具有相同的形状——调用形状/代码块相同,允许捕获的变量不同。$VAR / $INIT 可匹配任何标识符/表达式,因此即使代码块中的捕获变量名称已更改,也仍能匹配:
ast-grep -p 'const $VAR = useState($INIT)' --lang tsx <path>
使用此方法在规划提取前区分真正可提取的重复代码与偶然的令牌重叠。(若要单独进行结构搜索而不执行提取,请使用 ast-grep-search 技能。)
1c. 检测器不可用时的优雅回退(Grep)
当 npx/jscpd 不可用,或者生态系统的 PATH 中没有 npx 时,回退到由 Agent 驱动的文本搜索:
- 使用 Grep 查找重复的函数名、变量模式和导入集群
- 使用 Glob 找出结构相似的文件(例如,所有
*List.tsx、所有*Detail.tsx) - 阅读候选文件以确认重复并评估范围
对于近似重复(变量重命名、参数重新排序),这种回退方式的召回率较低——只要 jscpd 可用,就优先使用 jscpd 路径;仅在 jscpd 不可用时使用 Grep。
需要归类的重复信号(jscpd 和 Grep 路径都会将结果归入第 2 步中的相同类别):
- 在多个文件中以相同方式定义的工具函数(字符串截断、日期格式化、验证)
- 相同的错误处理代码块(try/catch 模式、错误状态 JSX)
- 复制粘贴的 UI 片段(分页控件、确认对话框、加载状态)
- 重复的钩子/状态管理模式(删除确认 + mutation + 处理函数)
- 重复的导入代码块,表明存在重复的内联实现
第 2 步:归类重复项
将发现的重复项归入提取类别:
| 类别 | 提取为 | 位置惯例 |
|---|---|---|
| 工具函数 | 纯函数 | src/lib/utils/ 或 src/utils/ |
| 组件 | 共享 UI 组件 | src/components/ui/ 或 src/components/shared/ |
| 钩子 | 自定义 React/Vue 钩子 | src/hooks/ 或 src/composables/ |
| 类型 | 共享类型定义 | src/types/ 或与抽象放在一起 |
遵循项目现有的共享代码位置惯例。如果没有惯例,则根据框架提出一种方案。
第 3 步:规划提取
针对每个重复项集群,规划提取:
- 为抽象命名——使用清晰且能体现共享行为的描述性名称
- 定义接口——确定能够涵盖所有用法差异所需的参数
- 选择位置——遵循项目的共享代码放置惯例
- 列出所有使用方——确定需要更新的每个文件
- 评估风险——记录重复实例之间需要通过参数化处理的细微差异
在继续操作前向用户展示计划(除非未指定 --dry-run 且范围明确)。
计划格式:
## Extraction Plan
### 1. [Abstraction Name] → [target file path]
- Type: utility | component | hook
- Replaces: [N] identical blocks across [M] files
- Consumers: [list of files]
- Parameters: [any variations that need to be parameterized]
- Duplicated: [N] tokens / [N] lines (from jscpd; blank when the Grep fallback was used)
- Similarity: [N]% (from jscpd; "exact" when ast-grep-confirmed as the same shape)
- Estimated lines saved: [N]
Duplicated 和 Similarity 字段来自 jscpd 报告(每个克隆的 token/行数以及集群占比)——这是量化的 --dry-run 报告,而非尽力而为的描述性总结。如果集群由 Grep 回退路径(1c)提供,则将这些字段留空或注明“grep 估算”。
第 4 步:提取共享抽象
执行计划中的每项提取:
- 创建共享抽象,并添加恰当的类型和文档
- 替换使用方文件中的每个实例,改为导入并使用新的抽象
- 处理差异——对实例间的差异进行参数化,而不是创建多个抽象
- 更新导入——添加新的导入,并移除仅供内联版本使用的导入
**提取顺序:**先处理工具函数(无依赖),再处理组件,最后处理钩子(可能依赖工具函数/组件)。
在继续下一项之前,先在跟踪表中将当前提取标记为已完成。
第 5 步:编写测试
为每个提取的抽象编写测试:
| 抽象类型 | 测试方法 |
|---|---|
| 工具函数 | 编写单元测试,覆盖所有输入差异和边缘情况 |
| UI 组件 | 渲染测试、属性差异、无障碍性 |
| 自定义钩子 | 使用模拟依赖进行钩子测试,覆盖状态转换 |
| 类型定义 | 视情况编写类型级测试(tsd、expect-type) |
根据现有惯例,将测试文件放在抽象旁边或项目的测试目录中。
步骤 6:清理无用代码
完成所有提取后:
- 移除未使用的导入:从所有已更新的使用方文件中移除
- 移除无用代码——删除已被替代的内联辅助函数
- 确认没有孤立引用——搜索是否仍有对已移除代码的引用
步骤 7:确认所有检查均通过
运行完整的验证套件:
TypeScript/JavaScript 项目:
npx tsc --noEmit # Type checking
npm run lint # Linting (or biome/eslint directly)
npm run test # Full test suite
Python 项目:
ty check . # Type checking
ruff check . # Linting
pytest # Test suite
Rust 项目:
cargo check # Type checking
cargo clippy # Linting
cargo test # Test suite
三项检查都必须通过。若有任何检查未通过,请先修复问题,再报告完成情况。
输出摘要
完成所有阶段后,报告:
## DRY Consolidation Summary
### Extractions
- [Abstraction Name] (type) — replaced N blocks in M files
- ...
### New Files Created
- path/to/new/file.ts — [description]
- ...
### Tests Added
- N tests across M test files
### Net Effect
- ~N lines of duplicated code consolidated
- N reusable abstractions created
- All verified: typecheck + lint + N passing tests
Agent 优化
| 上下文 | 方法 |
|---|---|
| 确定性克隆扫描 | 运行 npx jscpd --reporters json --min-tokens 50 --output /tmp/jscpd-dry --silent <path>,然后解析 duplicates[] 中的精确范围 |
| 确认结构形态 | 运行 ast-grep -p '<pattern with $METAVARS>' --lang <lang> <path> |
| 快速扫描 | 使用 --dry-run 查看重复报告,不做任何更改 |
| 定向提取 | 使用 --scope utilities,仅提取工具函数 |
| 大型代码库 | 将范围限定到特定目录:/code:dry-consolidation src/components/ |
| 提取后验证 | 运行 `npx tsc --noEmit 2>&1 |
| 运行测试(快速) | 运行 npm test -- --bail=1 --reporter=dot,快速确认通过或失败 |
另请参阅
/code:refactor— 对文件或目录进行函数式重构(纯函数、不可变性、组合)/code:antipatterns— 仅检测代码异味的分析ast-grep-search— 用于查找代码模式的结构化代码搜索
相关技能
- 如果在整合期间检测到无用代码 →
/code:dead-code - 如果整合后复杂度较高 →
/code:complexity