4 minutes
Skill 测试与演化
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-5 到 claude-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 沉淀。