Skill 写完不等于"做完"——它跟代码一样会被模型版本变化、用户工作流变化、新 harness 行为变化折腾。本章把 Skill 当成 software:有 unit test、有 regression、有 changelog,让它在 6 个月后还能用。

23.1 为什么 Skill 也需要测试

不要等你某天打开 OpenCode 发现某个 Skill 触发率从 70% 跌到 5%——Skill 的"准确度"会随模型版本而漂移,prompt 中某词语的 token 化变了,召回就崩。可追踪、可回归是关键。

23.2 三类测试

类型 名称 测试对象
1 Trigger test “在 X 输入下会被召回吗”
2 Behavior test “被召回后遵循 Skill 内的 flow 吗”
3 Regression test “上次行为跑得 OK,这次没变吗”

23.3 Trigger test:写 probe 集

Create tests/probes.toml

[[probe]]
text = "给 lib/validators.py 加一个 is_alpha 函数"
expect = "engaged"

[[probe]]
text = "为什么 Python 不需要类型?"
expect = "skipped"

[[probe]]
text = "把 src/foo.ts 第 12 行的 == 改成 ==="
expect = "skipped"

[[probe]]
text = "写一个 SQL 解析器"
expect = "engaged"

跑 trigger test 的脚本:

# tests/test_trigger.py
import toml, subprocess, json

def run_probe(probe_text: str) -> str:
    result = subprocess.run(
        ["opencode", "trigger-check", "--skill", "tdd-reminder",
         "--probe", probe_text],
        capture_output=True, text=True,
    )
    return json.loads(result.stdout)["triggered"]

def test_trigger_probes():
    probes = toml.load("tests/probes.toml")
    for p in probes["probe"]:
        actual = run_probe(p["text"])
        assert actual == p["expect"], (
            f"probe failed: expected={p['expect']}, actual={actual}, "
            f"text={p['text']}"
        )

tests/test_trigger.py 放进 CI。每次 Skill 更新 → trigger 稳定性回归测试。

23.4 Behavior test:模拟 skill 被激活后的响应

Trigger test 测 “是否召回”。Behavior test 测"召回之后模型有没有按 Skill 行动"。

# tests/test_behavior.py
import anthropic, os, toml
from mcp_client import launch_opencode_session

async def test_skill_active():
    proc = await launch_opencode_session(skill="tdd-reminder")
    await proc.send("给 lib/x.py 加 is_alpha(空字符串返 True)")
    first = await proc.first_response()
    assert "[tdd-reminder" in first, f"missing tag: {first[:200]}"
    assert "test" in first.lower()

async def test_skill_skip_path():
    proc = await launch_opencode_session(skill="tdd-reminder")
    await proc.send("给 lib/x.py 加 is_alpha")
    await proc.send("skip")
    second_response = await proc.last_response()
    assert "[tdd-reminder skipped]" in second_response

Behavior test 难点在于 harness 不可重置——你只测"输出 prefix 出现",不去断言"模型最终行为一致"。后者会让测试脆化。

23.5 Regression test:模型版本切换时

每次你换模型(比如从 claude-opus-4-5claude-opus-4-7)跑 trigger_probes + behavior_tests 一遍。

mkdir -p tests/regress/
cp tests/probes.toml tests/regress/probes-2026-07-15.toml
opencode with-model=anthropic/claude-opus-4-7 trigger-check --all
pytest tests/test_trigger.py

记录历史存档每次模型的命中数:

2026-07-15 - claude-opus-4-5 - 5 probes ✓ 0 mismatch |
2026-09-10 - claude-opus-4-7 - 5 probes ✓ 0 mismatch
2026-10-22 - gpt-5.5         - 5 probes ✓ 1 mismatch (probe 3 trigger)

注意:模型变 prompt 编码会变 → trigger keywords 前 3 个是 stability key,别动。

23.6 Skill Versioning

每个 skill 几个月就要演化。给 SKILL.md 加 version header:

---
name: tdd-reminder
version: 0.2.0
last_updated: 2026-07-15
changelog:
  - 0.2.0: Added 'failure modes' section
  - 0.1.0: Initial version
---

回视几个常见变更类型:

变更 大动作? 需 regression? 升版本?
加 failure mode minor y patch
改 trigger keywords major y minor
改 flow 步骤 major y minor
改 output prefix major y minor/major
加 reference 文件 minor 只加 references test patch
删掉 reference 文件 major 全量回归 minor

写 changelog 时给"为什么改"。3 个月后看到 changelog 想起来为何当初决定这么做,是 Skill 时间线的关键。

23.7 跨 harness 写一份,跑两遍

把 SKILL.md 同步两处目录:

# .opencode/skills/ 与 .claude/skills/ 同步
git mv .opencode/skills/tdd-reminder .skills-shared/
ln -s ../.skills-shared/tdd-reminder .opencode/skills/tdd-reminder
ln -s ../.skills-shared/tdd-reminder .claude/skills/tdd-reminder

CI 里跑 OpenCode 与 Claude Code 一遍,统计 engaged 是否同步——发现不用一回事就回视 trigger。

23.8 Skill audit 季度仪式

每 90 天做一遍:

1. 触发率统计:
   rg "[tdd-reminder" .opencode/log/ --since "3 months ago"

2. 找没人触发的 skill:
   - 长尾 30 commits 一次都未触发的 skill
   - 三个可能: trigger 写错 / 用户工作流变了 / skill 真没用了

3. Review 一次每个 active skill:
   - trigger 文字还跟得上一时代么? 加新词?
   - failure mode 列表够吗?回看 log 找新失败模式
   - references 还有用么?

4. 删除过时:
   - 3 月没触发的先标 deprecated
   - 1 季度后再删,保留 changelog 历史

仪式名 “skill audit”,可用 openCode 的 cron 模式自动 dispatch。

23.9 Skill 跟 Subagent 的协同

Skill 文本里派发 subagent

When triggered, dispatch exploratory probes in parallel:
  - task(subagent_type="explore", prompt="[CONTEXT]: ditto\n[GOAL]: 查 todos")
  - task(subagent_type="explore", prompt="[CONTEXT]: ditto\n[GOAL]: 查 fixture patterns")
然后基于 subagent 返回结果, 决定测试框架选择。

让 Skill 不仅给"行动指南",还能编写"协调多 Agent"指令。这是 21 章讲的 Tool Skill 形态——把 subagent dispatch 当作 skill 的一类行为。

23.10 一份 Skill 的 lifecycle

想法阶段
  ↓ (grep 自己 log 找 3 次重复)
Drafting
  ↓ (write SKILL.md, references/, 5 probe)
Prototyping
  ↓ (一周内 5-10 次使用, 调 trigger)
Stabilizing
  ↓ (写 probes.toml, behavior test, 加 version)
Production
  ↓ (季度 audit)
Evolving
  ↓ (按 changelog 修)
Sunsetting (deprecated 状态)
  ↓ (3 个月没触发)
Deletion

80% 的 Skill 卡在 Prototyping → Production 这一步——没规范化 trigger 测 / 没版本标签 → 一改就崩。把"上 production gate"当作里程碑 skill audit 做。

23.11 一个真实 Skill 演化的故事

这是我维护的 tdd-reminder 的演化史:

v0.1.0 (2026-04-01): 诞生,trigger 写"add function"
v0.2.0 (2026-05-10): 加 "skip failure mode" → 用户汇报大半卡在 "skip"
v0.3.0 (2026-06-01): trigger 改成 user-intent classification(task-based),
                     比关键字更准
v0.4.0 (2026-07-15): 加 references/which-test-framework.md (用户汇报
                     跨语言门槛高)
v0.4.1: hotfix references 路径笔误
v0.5.0 (2026-09-20): 改 BM25 trigger 写法 → 兼容 OpenCode new recall v3
v0.6.0 (2026-12-08): 加入 self-report `[tdd-reminder]` prefix, trace 可观察
v0.7.0 (2027-02-01): 跨 harness compatible — 同时跑 Claude Code 与 OpenCode

modest 几匝都来自用户日志的反馈——你写 Skill 不写日志等于做没回声的工作。

23.12 小结

  • Skill 三类测试:trigger / behavior / regression
  • 跨模型版本升级跑 regression 一遍
  • 季度 audit 是仪式,90 天自杀
  • 加版本号 + changelog 是合理演化基础
  • Skill 生命周期跟 software 一样:想法 → Draft → Proto → Production → Sunsetting

下一篇: 《24 Skill 库管理:团队协作与分发》——从单 skill 到团队 Skill 库:plugin、版本管理、share 与 install。

Summary: Skill = software,三测 trigger / behavior / regression;版本号 + changelog + 季度 audit + self-report = 可演化的 skill 沉淀。