第 18 章

从零编写 Skills:5种模式完整示例与最佳实践

第18章:从零编写 Skills——5种模式完整示例与最佳实践

本章概览

理论已经足够,本章进入纯实战模式。我们将完整展示 Skills 系统的 5 种典型写法模式,每种模式都附有可直接使用的完整 SKILL.md 代码、适用场景分析和关键设计决策说明。最后,我们总结 Degree of Freedom 匹配原则和"Concise is Key"哲学,以及最容易踩的常见错误。


18.1 模式一:最简工具型(Hello World 级别)

适用场景

设计原则

最简工具型 Skill 的核心是:最少字段,最清晰指导。不要为了"看起来专业"而添加不必要的复杂度。

完整 SKILL.md 示例

---
name: word-count
description: |
  当用户需要统计文件或文本的字数、行数、字符数时使用。
  提供快速的 wc 命令用法指导。
metadata:
  openclaw:
    emoji: "🔢"
    requires:
      bins:
        - wc
---

# Word Count Helper

统计文件内容的快速工具。

## 基本用法

```bash
# 统计字数、行数、字符数
wc -lwc filename.txt

# 仅统计行数
wc -l filename.txt

# 统计多个文件
wc -l *.md

输出格式说明

wc 输出三列:行数 / 字数 / 字节数

如果用户只说"统计一下",默认使用 wc -lwc


### 为什么这样写

- `description` 精确描述了触发场景(字数/行数/字符数统计)
- `requires.bins: [wc]` 确保在没有 wc 的环境不会加载(Windows 需要 WSL)
- 正文简洁:只有必要信息,没有废话
- 包含了边界情况处理("用户只说统计一下")

---

## 18.2 模式二:信息型(多步骤 GitHub PR 工作流)

### 适用场景

- 需要引导模型完成多个有序步骤
- 工作流中有判断分支
- 需要强制执行团队规范

### 设计原则

信息型 Skill 的价值在于**将团队的隐性知识显性化**。它把"老员工知道但新人不知道"的流程固化为可重复的步骤。

### 完整 SKILL.md 示例

```yaml
---
name: github-pr
description: |
  当用户需要创建 Pull Request、进行 PR 审查、管理 PR 标签和审查者,
  或者询问团队 PR 规范时使用。包含完整的从分支到合并的 PR 工作流。
user-invocable: true
metadata:
  openclaw:
    emoji: "🔀"
    requires:
      bins:
        - git
        - gh
      env:
        - GITHUB_TOKEN
    install:
      - type: brew
        pkg: gh
---

# GitHub PR 完整工作流

## 第一步:准备分支

```bash
# 确认当前分支状态
git status
git log --oneline -5

# 如果有未提交的变更,先提交
git add -p  # 交互式暂存,避免提交多余文件
git commit -m "feat: <简短描述>"

提交信息规范(强制执行):

前缀 场景
feat: 新功能
fix: Bug 修复
refactor: 代码重构(无新功能)
docs: 文档变更
test: 测试相关
chore: 构建/工具链变更

第二步:推送并创建 PR

# 推送当前分支
git push -u origin HEAD

# 使用 GitHub CLI 创建 PR
gh pr create \
  --title "feat: 添加用户认证模块" \
  --body "$(cat <<'EOF'
## 变更说明
- 实现 JWT 认证
- 添加刷新 token 机制

## 测试方案
- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 手动测试认证流程
EOF
)"

第三步:分配审查者和标签

# 添加审查者
gh pr edit --add-reviewer username1,username2

# 添加标签
gh pr edit --add-label "needs-review,feature"

# 设置里程碑
gh pr edit --milestone "v2.0"

第四步:处理审查反馈

当审查者留下评论时:

  1. 在本地 checkout 到对应分支
  2. 修改代码并提交(使用 fix:refactor: 前缀)
  3. git push(会自动更新 PR)
  4. 在 PR 评论中回复"已处理,见 commit xyz"

第五步:合并

# 检查是否可以合并(CI 通过?审查批准?)
gh pr checks
gh pr view --json reviewDecision

# 合并(使用 squash 保持主分支历史整洁)
gh pr merge --squash --delete-branch

常见问题处理

合并冲突:

git fetch origin main
git rebase origin/main
# 解决冲突后
git add .
git rebase --continue
git push --force-with-lease

PR 卡在 CI:

gh pr checks --watch  # 实时监控 CI 状态
gh run view <run-id>  # 查看具体失败原因

---

## 18.3 模式三:工具集成型(带 requires.bins + install 的 image-lab)

### 适用场景

- 依赖特定外部工具(ffmpeg、imagemagick 等)
- 需要自动安装依赖
- 涉及多种工具的协调使用

### 完整 SKILL.md 示例

```yaml
---
name: image-lab
description: |
  当用户需要处理图片或视频时使用:格式转换、裁剪、调整尺寸、
  批量处理、添加水印、视频截图、GIF 制作、图片压缩优化。
  支持 PNG/JPG/WebP/AVIF/GIF/MP4 等格式。
metadata:
  openclaw:
    emoji: "🖼️"
    os: darwin
    requires:
      bins:
        - ffmpeg
        - convert       # ImageMagick
      anyBins:
        - cwebp
        - avifenc
    primaryEnv: shell
    homepage: https://imagemagick.org
    install:
      - type: brew
        pkg: ffmpeg
      - type: brew
        pkg: imagemagick
      - type: brew
        pkg: webp
---

# Image Lab — 图像与视频处理工具集

## 图片格式转换

```bash
# JPG → WebP(质量 85)
cwebp -q 85 input.jpg -o output.webp

# PNG → AVIF(高压缩率)
avifenc --min 0 --max 63 --speed 6 input.png output.avif

# 批量转换目录下所有 JPG 为 WebP
for f in *.jpg; do cwebp -q 85 "$f" -o "${f%.jpg}.webp"; done

图片尺寸调整

# 调整为指定宽度(高度自动等比缩放)
convert input.jpg -resize 800x output.jpg

# 调整为指定尺寸(可能变形)
convert input.jpg -resize 800x600! output.jpg

# 调整为指定尺寸(不超出边界,填充白色)
convert input.jpg -resize 800x600 -background white -gravity center -extent 800x600 output.jpg

图片裁剪

# 从坐标 (x=100, y=50) 裁剪 400x300 区域
convert input.jpg -crop 400x300+100+50 output.jpg

# 中心裁剪
convert input.jpg -gravity center -crop 400x400+0+0 output.jpg

视频操作(FFmpeg)

# 提取视频第 10 秒的帧为 PNG
ffmpeg -i video.mp4 -ss 00:00:10 -vframes 1 screenshot.png

# 视频转 GIF(优化质量)
ffmpeg -i video.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" output.gif

# 压缩视频(H.264,CRF 23)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium output.mp4

批量水印

# 在所有图片右下角添加文字水印
for f in *.jpg; do
  convert "$f" \
    -gravity southeast \
    -fill "rgba(255,255,255,0.7)" \
    -pointsize 24 \
    -annotate +10+10 "© MyBrand" \
    "watermarked_$f"
done

选择工具的优先级

  1. WebP 输出 → 优先 cwebp(质量更好)
  2. AVIF 输出 → 使用 avifenc
  3. 复杂合成/滤镜 → 使用 convert(ImageMagick)
  4. 视频相关 → 始终使用 ffmpeg

---

## 18.4 模式四:command-dispatch 型(零推理成本的 browser Skill)

### 适用场景

- 斜杠命令应该立即触发特定工具,不经过模型推理
- 需要极低延迟(毫秒级响应)
- 命令行为完全确定,不需要模型判断

### 关键设计

`command-dispatch: tool` 是这种模式的核心。它告诉 OpenClaw:当用户输入 `/browser` 时,直接将参数传递给 `browser_action` 工具,**跳过模型的 token 消耗**。

### 完整 SKILL.md 示例

```yaml
---
name: browser
description: |
  当用户需要控制浏览器、访问网页、点击元素、填写表单、
  截图,或者执行任何网页自动化任务时使用。
  支持 /browser <url> 直接打开网页。
user-invocable: true
command-dispatch: tool
command-tool: "browser_action"
command-arg-mode: raw
metadata:
  openclaw:
    emoji: "🌐"
    requires:
      anyBins:
        - chromium
        - google-chrome
        - firefox
    install:
      - type: brew
        pkg: chromium
---

# Browser Automation Skill

## 快速启动

/browser https://example.com


等价于直接调用 `browser_action` 工具,无需模型推理。

## 常用操作

### 打开网页并截图

导航到指定 URL,等待加载完成后截图: browser_action("navigate", url="https://target.com") browser_action("screenshot")


### 填写表单

找到表单元素,填入内容: browser_action("find", selector="#email-input") browser_action("type", text="[email protected]") browser_action("find", selector="#submit-btn") browser_action("click")


### 提取页面内容

获取页面文本内容: browser_action("extract_text", selector=".article-body")

获取页面 HTML: browser_action("get_html")


## command-dispatch 工作机制说明

当用户输入 `/browser https://news.ycombinator.com` 时:

用户输入 → OpenClaw 路由 → browser_action(url="https://news.ycombinator.com") (不经过模型,0 token 消耗)


对比普通 Skill 调用:

用户输入 → 模型读取 description → 模型决策 → 模型调用工具 (需要消耗 ~500-2000 tokens)


## 注意事项

- 动态内容丰富的页面请等待 `networkIdle` 状态
- 登录态页面需要先处理 cookies
- 如需持久化浏览器状态,使用 `--profile` 参数

18.5 模式五:TDD 专业型(完整的测试驱动开发 Skill)

适用场景

完整 SKILL.md 示例

---
name: tdd-pro
description: |
  当用户需要按照测试驱动开发(TDD)方法编写代码时使用:
  先写测试、再写实现、然后重构。适用于新功能开发、Bug 修复、
  遗留代码改造。包含 Red-Green-Refactor 循环和反模式检测。
user-invocable: true
metadata:
  openclaw:
    emoji: "🧪"
    requires:
      anyBins:
        - jest
        - vitest
        - pytest
        - go
    primaryEnv: node
    homepage: https://martinfowler.com/bliki/TestDrivenDevelopment.html
---

# TDD Pro — 专业测试驱动开发指南

## 核心循环:Red → Green → Refactor

┌─────────────────────────────────────┐ │ 1. RED: 写一个失败的测试 │ │ → 运行测试,确认它确实失败 │ │ → 确认失败原因是"功能未实现" │ ├─────────────────────────────────────┤ │ 2. GREEN: 写最少量的代码使测试通过 │ │ → 不要过度设计 │ │ → 先让测试绿,再考虑优雅 │ ├─────────────────────────────────────┤ │ 3. REFACTOR: 重构,保持测试绿 │ │ → 消除重复 │ │ → 改善命名和结构 │ │ → 每次改动后运行测试 │ └─────────────────────────────────────┘


## 第一步:模式发现(开始前必做)

在开始写任何代码之前,先分析需求:

```markdown
## 需求分析模板
- **输入是什么?** (类型、格式、边界值)
- **输出是什么?** (类型、格式、异常情况)
- **核心行为是什么?** (业务规则、转换逻辑)
- **边界情况有哪些?** (空值、溢出、并发)
- **与外部系统的集成点?** (需要 Mock 的依赖)

第二步:测试用例设计

按照以下顺序设计测试(从简单到复杂):

// 示例:为 calculateTax(amount, rate) 设计测试

describe('calculateTax', () => {
  // 1. 基础正常情况
  it('should calculate tax correctly for positive amount', () => {
    expect(calculateTax(100, 0.1)).toBe(10)
  })

  // 2. 边界值:零值
  it('should return 0 for zero amount', () => {
    expect(calculateTax(0, 0.1)).toBe(0)
  })

  // 3. 边界值:零税率
  it('should return 0 for zero rate', () => {
    expect(calculateTax(100, 0)).toBe(0)
  })

  // 4. 异常情况:负数
  it('should throw for negative amount', () => {
    expect(() => calculateTax(-100, 0.1)).toThrow('Amount cannot be negative')
  })

  // 5. 精度问题
  it('should handle floating point correctly', () => {
    expect(calculateTax(0.1, 0.1)).toBeCloseTo(0.01, 10)
  })
})

第三步:RED 阶段

# 运行测试,确认失败
npm test -- --testNamePattern="calculateTax"

# 预期输出:FAIL(因为函数还不存在)
# 如果意外通过,说明测试写错了

第四步:GREEN 阶段

关键原则:只写让当前失败测试通过的最少量代码。

// 第一次让测试通过的"丑陋但有效"实现
function calculateTax(amount, rate) {
  if (amount < 0) throw new Error('Amount cannot be negative')
  return amount * rate
}

这就够了。不要在 GREEN 阶段引入复杂性。

第五步:REFACTOR 阶段

测试全绿后,开始改善代码质量:

// 重构:更清晰的错误处理、更好的命名
/**
 * 计算税额
 * @param {number} amount - 税前金额(必须 >= 0)
 * @param {number} rate - 税率(0-1 之间的小数)
 * @returns {number} 税额
 */
function calculateTax(amount, rate) {
  if (amount < 0) {
    throw new RangeError(`Amount must be non-negative, received: ${amount}`)
  }
  if (rate < 0 || rate > 1) {
    throw new RangeError(`Rate must be between 0 and 1, received: ${rate}`)
  }
  return Number((amount * rate).toFixed(10))
}

重构后再次运行测试,确认仍然全绿。

反模式识别与避免

反模式 症状 修复方式
先写实现后写测试 测试刚好覆盖了实现的逻辑 删除实现重来
测试太大 单个 it() 包含多个断言 拆分为独立测试
过度 Mock Mock 了 80%+ 的依赖 考虑重新设计接口
测试实现细节 测试内部变量而非行为 改为测试公共 API
跳过 RED 阶段 "我知道它会失败" 强制运行一次,看到 FAIL

不同语言的测试运行命令

# JavaScript/TypeScript
npm test -- --watch

# Python
pytest -xvs tests/

# Go
go test ./... -v -run TestCalculateTax

# Rust
cargo test calculate_tax -- --nocapture

参考资料

详细的 Mock 策略见:{baseDir}/references/mocking-strategies.md 常用断言模式见:{baseDir}/references/assertion-patterns.md


---

## 18.6 Degree of Freedom 匹配原则

选择哪种模式,应该与任务的"自由度"(Degree of Freedom)匹配:

| 任务自由度 | 描述 | 推荐模式 |
|-----------|------|----------|
| 极低 | 完全确定的单步命令 | command-dispatch 型 |
| 低 | 固定流程,无分支 | 工具型或信息型 |
| 中 | 有分支,需要判断 | 信息型或工具集成型 |
| 高 | 复杂工作流,有循环和反模式 | TDD 专业型 |
| 极高 | 任务高度定制化 | 信息型 + references/ 扩展 |

**错误案例:** 用 TDD 专业型来包装一个简单的 `wc` 命令——过度设计,浪费 context。

**正确做法:** 根据任务复杂度选择最轻量的足够模式。

---

## 18.7 "Concise is Key"——context 预算哲学

在 Skill 写作中,**简洁是最重要的美德**,原因如下:

### 过长 Skill 的代价

一个 8000 token 的 Skill,即使只用到其中 20% 的内容,也会全量消耗 context 预算(懒加载只保护了"是否加载"这一决策,不保护加载后的大小)。

### 实用写作规则

1. **每条指令一行**:不要用段落写可以用列表表达的内容
2. **代码示例代替文字解释**:`wc -l file.txt` 比"使用 wc 命令的 -l 参数统计指定文件的行数"少 80% 的 tokens
3. **把详细参考移到 references/**:SKILL.md 正文只留操作核心,细节放 references/
4. **避免重复**:不要在正文里重复 description 里已经说过的话

### 目标长度参考

| 模式 | 推荐 token 范围 |
|------|----------------|
| 工具型 | 100-300 tokens |
| 信息型 | 300-800 tokens |
| 工具集成型 | 400-1000 tokens |
| command-dispatch 型 | 50-200 tokens |
| TDD 专业型 | 800-2000 tokens |

---

## 18.8 常见错误模式

### 错误一:把 shell 命令写进 skill body 当作"执行代码"

```yaml
# 错误写法:好像 Skill 会"执行"这段脚本
---
name: setup-env
description: 设置开发环境
---

#!/bin/bash
npm install
cp .env.example .env
docker-compose up -d

问题: Skill 正文是给模型读的指导,不是可执行脚本。模型读到这里会把它当成"应该告诉用户执行的命令",而不是自动执行。

正确写法:

---
name: setup-env
description: 当用户需要初始化本项目开发环境时使用
---

## 初始化步骤

让用户按顺序执行以下命令:

```bash
npm install
cp .env.example .env
docker-compose up -d

完成后验证:docker ps 查看容器是否正常运行。


### 错误二:description 过于宽泛导致误触发

```yaml
# 危险:会与几乎所有任务匹配
description: 帮助处理各种代码和开发相关的任务

错误三:在 command-dispatch Skill 中添加复杂正文

command-dispatch Skill 的正文永远不会被模型读取(因为绕过了模型),所以在正文里写详细说明是无效的。

错误四:忘记 {baseDir} 导致路径引用失效

# 错误:硬编码路径
参考文档见 ~/.openclaw/skills/my-skill/references/guide.md

# 正确:使用占位符
参考文档见 {baseDir}/references/guide.md

18.9 本章小结

下一章探索 ClawHub 生态:13,000+ Skills 的平台架构、Top 10 精选,以及如何用向量搜索找到真正有价值的 Skill。

本章评分
4.7  / 5  (13 评分)

💬 留言讨论