Claude Code 插件进阶(下):给插件写评测,看它占多少上下文,管好依赖、版本和市场
插件给团队用起来之后,改一句 skill 的描述都可能让它不再被触发。本站实测了 claude plugin eval:给示例插件写两个用例,带插件和不带插件各跑三遍,12 次运行 27 秒、0.28 美元,直接看出插件贡献了多少。后半篇整理怎么看插件占多少上下文、怎么声明依赖、怎么发版本和托管自己的市场。
本站已实测,测试日期:2026-10-12。
本页目录(8 节)
这篇教程解决一个问题:插件做出来、团队也用上了,然后呢?改了一句 skill 的描述,它还会不会被触发?它到底有没有让结果变好?它给每个会话加了多少负担?新版本怎么发,才不会把别人弄坏?
上一篇站在用插件的人这边,这一篇站在维护插件的人这边。用的还是做一个插件分享给团队里那个示例插件 express-kit:一个写提交说明的 skill,一个评审测试的子代理,一个记改动的 hook。
评测那一节是本站实测的。后面三节(上下文占用里的用量部分、依赖、托管市场)整理自官方文档,没有实测,每节会再标一次。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-12 |
| 版本 | Claude Code 2.1.296 |
| 系统 | Ubuntu 24.04 |
| 模型 | Sonnet 5.5 |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
一、给插件写评测
评测是什么
claude plugin eval 是终端里的一条命令。它拿一组用例去跑你的插件,然后打分。
- 一个用例 = 一句用户可能会说的话,加上一个或几个评分器。
- 评分器是对结果的一项通过或不通过的检查:回答里有没有匹配某个正则、某个工具有没有被调用、另一个模型按你写的标准判它合不合格。
- 每个用例默认跑三遍。 模型的输出不稳定,跑一遍说明不了什么。
- 每个用例还会在不加载插件的情况下再跑三遍。 两边的得分分别叫 WITH 和 W/OUT,差值 Δ 就是插件的贡献。
最后这一条是关键。得分高不代表插件有用,Claude 不靠它可能也做得一样好。有了对照才知道。
每次运行都是一个全新的、隔离的非交互会话,只加载被测的插件,工作目录是空的。
写用例:两种办法
让 Claude 写。 在插件根目录运行 claude plugin eval init,它会打开一个会话,读你的插件,问你什么算好结果,提出该触发和不该触发的提示,设计评分器,试跑一遍,然后把文件写好。文档推荐这种。本站没有试。
自己写。 本站走的是这条,为了看清楚文件里到底有什么。先要一个空白模板:
claude plugin eval init --bare commit-format
Created evals/commit-format/prompt.md and evals/commit-format/graders/criteria.md
模板里是两个待填的文件。prompt.md 上半是运行的限制,下半是发给 Claude 的话:
---
max_turns: 10
allowed_tools: [Read, Glob, Grep, Skill]
---
TODO: describe what the agent should do
本站写的两个用例
用例一:该触发的。 示例插件的 skill 要求提交说明是英文的一行、以 fix:、docs:、test:、refactor: 之一开头、不带解释。
evals/commit-format/prompt.md:
---
max_turns: 6
allowed_tools: [Skill]
---
帮我写一条提交说明:我把 getUser 改名成了 fetchUser,三处调用的地方也一起改了。
提示要像用户会说的话,不要点 skill 的名字。点了名就测不出“它会不会自己被选中”。
三个评分器,每个一个文件,放在 graders/ 下。
prefix.md,检查开头的类型:
---
type: regex
pattern: '^(fix|docs|test|refactor): [A-Za-z]'
---
one-line.md,检查只有一行(不能出现第二个非空行):
---
type: regex
pattern: '\n\s*\S'
match: not_contains
---
skill-fired.md,检查答案确实是这个 skill 产生的:
---
type: tool_used
tool: Skill
input_match: '"skill"\s*:\s*"(?:[\w-]+:)?commit-message"'
---
用例二:不该触发的。 一个和提交说明毫无关系的问题,确认插件不会乱插手。
evals/ignores-unrelated/prompt.md 的正文是“用一句话解释 HTTP 状态码 404 是什么意思。”,两个评分器:一个正则检查回答里说到了“找不到”,一个 tool_used 把 min 和 max 都设成 0,要求 skill 一次都没被调用。
跑起来
claude plugin eval . --trust-plugin --no-publish --model sonnet -j 3
.是插件目录。--trust-plugin:第一次对一个目录运行时它会问你是否信任,这个参数替你回答,给脚本和 CI 用的。--no-publish:报告只留在本地。默认情况下,账号支持的话它会把报告发布到 claude.ai。--model sonnet:被测的会话用哪个模型。-j 3:同时跑三个。
每跑完一次打一行,带着每个评分器的结论。不带插件的那几次是这样的:
✗ one-line (weight 1): pattern found (expected absent)
✗ prefix (weight 1): pattern not found in last_message
最后是汇总表:
CASE WITH W/OUT Δ RUNS COST NOTES
commit-format 1.00 0.00 +1.00 6 $0.15
ignores-unrelated 1.00 1.00 0.00 6 $0.13
2 case(s) · mean Δ +0.50 · 27s · $0.28
Report: …/express-kit/evals/results/2026-10-12T15-33-15-933Z/report.html
怎么读这张表
commit-format:带插件 1.00,不带 0.00,Δ +1.00。 三遍带插件的运行全部符合格式;三遍不带的全部不符合,Claude 写了好几行,也没有类型前缀。这个 skill 的作用是实打实的。
ignores-unrelated:两边都是 1.00,Δ 是 0。 这正是想要的结果。这个用例测的不是“插件有帮助”,而是“插件没添乱”:带着插件,Claude 照样正常回答了无关的问题。
再看每次运行的明细,有一行值得解释:
✓ skill-not-fired [with-only, not scored]: Skill called 0x (expected 0..0)
“skill 有没有被调用”这类检查不计入得分。 文档的解释:这种检查在不带插件时永远不可能通过,算进去会把对照那一边的分数压低,Δ 就虚高了。所以它在两边都不计分,只在带插件那边当一个指示灯。
指示灯的用处在排查。文档说最常见的第一个发现是:Δ 接近 0,同时 tool_used: Skill 那个评分器不通过。意思是 Claude 在自然的说法下根本没选你的 skill。这时该改的是 skill 的 description。
六种评分器
| 类型 | 通过的条件 | 要不要花钱 |
|---|---|---|
regex |
正则在目标里找到了(或者用 match: not_contains 要求找不到) |
不要 |
tool_used |
某个工具被调用的次数在 min 和 max 之间 |
不要 |
tool_order |
两个工具都被调用了,并且先后顺序对 | 不要 |
file_exists |
Claude 创建了匹配某个路径的文件 | 不要 |
llm |
一个评审模型按你写的标准投票,三票里至少两票通过 | 要,每次三个短的评审调用 |
baseline |
评审认为这次的结果不比一份参考记录差 | 要 |
没有“自己写代码来评分”这一种。
正则默认看的是 Claude 最后一条回答。也可以看整个会话的记录、Claude 创建了哪些文件、某一个文件的内容。
文档给的几条经验:
- 长的输出用正则评。 评审模型读的东西越长,前后两次的结论越容易不一样。
llm评分器留给短的输出,标准写成明确的“什么情况通过、什么情况不通过”。 - 每个用例配两个评分器:一个看结果,一个看过程。 合起来才知道答案对不对、是不是你的插件做出来的。
- skill 被调用了但 Δ 是负的,先怀疑评审。 默认的评审模型是 Haiku,它可能因为格式和你描述的不一样就判错。换成
--judge-model sonnet再跑,并把标准写得不依赖格式。
常用的几个参数
| 参数 | 用途 |
|---|---|
--case <名字> |
只跑名字匹配的用例 |
--runs 1 --ablation none |
只跑一遍、不跑对照。调评分器的时候用,省钱。一遍的结果有噪声,定稿前要用默认的三遍确认 |
--threshold 0.8 |
有用例得分低于这个值,命令的退出码是 1。默认是 1.0。放进 CI 就靠它 |
--max-cost-usd 2 |
花费到了就中止,给出部分结果 |
--json 文件 |
把每次运行的完整结果写成 JSON |
每次运行的结果写在 evals/results/<时间>/ 下,里面有 report.html 和 aggregate-result.json。把 evals/results/ 加进 .gitignore。
两点提醒
- 评测会真的运行插件里的代码,用的是你的身份。 命令的帮助里写着:只评测你信任的插件;它的隔离限制了恶意插件能够到的范围,但不是保证。
- 每次运行都是真的模型调用。 文档的算法:一个评测集大约是“用例数 × 运行次数”次带插件的运行,对照再来同样多次,每个
llm评分器每次运行再加三个评审调用。
二、它占多少上下文,有没有人在用
占多少(实测)
上一篇用过的命令,对还没安装的插件也能用:
$ claude --plugin-dir ./express-kit plugin details express-kit
Projected token cost
Always-on: ~134 tok added to every session
Per-component (rounded)
component always-on on-invoke
commit-message ~70 ~270
test-reviewer ~60 ~160
- Always-on 是每个启用了这个插件的会话都要背的:各个 skill、子代理、命令的名字和描述。不管用没用到都在。这是最该压的数字。
- on-invoke 是某个组件真的被用到时才加载的正文。
- hook 不占上下文。MCP 服务器的工具说明不在这个估算里,要在会话里用
/context看。
压低 Always-on 的办法只有两个:把描述写短,或者把大插件拆开让人按需安装。
这里有一个和评测直接相关的风险,文档特意提了:skill 的描述同时也是 Claude 判断“该不该用它”的依据,写得太短它可能就不触发了。 所以改完描述,跑一遍上面那个 skill-fired 评分器。这两节是配着用的。
有没有人在用(整理自文档,没有实测)
Claude Code 不会把插件的使用情况回报给作者。用量只记在每个安装者自己的机器上。你能做的是请同事在自己的会话里看:
/plugin的 Installed 页签:一个插件连续 14 天、10 个会话没被用到,会被归到“Not used recently”下面,详情里有Last used一行。/skill-doctor:看每个 skill 占多少上下文、用得多不多。/doctor和/usage里也各有一处。
/skill-doctor 本站在讲 advisor 的那篇里试过:用令牌接中转站时它是“未知命令”,和 advisor 用不了是同一个原因。
三、依赖和版本(整理自文档,没有实测)
声明依赖
写在插件的 .claude-plugin/plugin.json 里:
{
"name": "deploy-kit",
"version": "3.1.0",
"dependencies": [
"audit-logger",
{ "name": "secrets-vault", "version": "~2.1.0" }
]
}
两种写法:
- 只写名字:依赖跟着它所在的市场走,市场发了新版本,用户下次更新就换上。那个版本要是改了你用到的东西,你的插件就对所有更新了的人坏掉。
- 带版本范围:
~2.1.0、^2.0、>=1.4这样的语义化版本范围。用户会一直收到范围内的补丁,不会越界。它是按依赖的 git 标签来解析的,所以依赖的维护者要给发布打标签。
用依赖做“全家桶”
一个插件的清单只要有 name 就合法。所以可以发一个只有名字和依赖列表的插件,装它就等于装了一组:
{
"name": "backend-standard",
"version": "1.0.0",
"description": "后端工程师的标准插件组合",
"dependencies": ["secrets-vault", "deploy-kit", { "name": "db-migrate", "version": "^3.0" }]
}
新同事一条命令装齐。
跨市场的依赖默认不装
依赖在另一个市场里时,Claude Code 默认不会替用户装它,除非用户已经装了。这是为了防止一个市场悄悄从用户没看过的来源装东西。要放行,在你自己市场的 marketplace.json 里用 allowCrossMarketplaceDependenciesOn 列出允许的市场名。
四、发版本和托管市场(整理自文档,没有实测)
市场放在哪
| 放在哪 | 用户怎么添加 | 用户要有什么 |
|---|---|---|
| GitHub | /plugin marketplace add your-org/your-marketplace |
git;私有仓库要有访问权限 |
| 别的 git 托管(GitLab 等) | 给完整的地址,/plugin marketplace add https://…/plugins.git |
git,能访问那台主机 |
一个放着 marketplace.json 的网址 |
/plugin marketplace add https://…/marketplace.json |
能访问那个地址 |
| 共享盘上的目录 | /plugin marketplace add /Volumes/shared/claude-plugins |
能读那个路径 |
想让一个仓库里的所有人都自动有这个市场:在仓库里运行一次 claude plugin marketplace add your-org/your-marketplace --scope project,把它写出来的 .claude/settings.json 提交。
私有市场靠的是机器上已有的凭据
Claude Code 自己没有 git 令牌,marketplace.json 里也没有放令牌的地方。它在用户机器上运行 git,关掉了交互提示,能不能拉下来全看那台机器上已经有的凭据。
- 走 SSH:密钥要能不输密码就用(比如已经加进
ssh-agent),主机要已经在known_hosts里。 - 走 HTTPS:凭据助手里已经存着的能用,需要现场问的不行。
同事说“添加市场失败”,多半是这里。让他先在终端里手动 git clone 一次那个仓库。
用户什么时候拿到新版本
两条规则,都容易踩:
一、你的市场默认不自动更新。 marketplace.json 里没有开关,要每个用户自己在 /plugin 的 Marketplaces 里打开,或者由管理员统一设置。没开的话,用户要手动 /plugin marketplace update <名字> 才会看到新东西。
二、只有版本号变了,用户才会拿到新的一份。
| 你的做法 | 结果 |
|---|---|
每次发布都改 version |
用户在版本号变化时更新 |
写了 "version": "1.0.0",之后只推提交、不改这个字符串 |
用户收不到你的改动,一直用缓存的那份 |
不写 version |
用户跟着你的提交走 |
plugin.json 和市场条目里都写了 version |
以 plugin.json 为准,没有警告。claude plugin validate 会报两边不一致 |
第二行是最常见的“我明明推了,他们怎么没变”。
稳定版和尝鲜版
Claude Code 没有发布通道的概念,一个市场里一个插件同一时间只有一个版本。要分两条线,就开两个名字不同的市场,条目分别指向同一个插件仓库的不同分支,让用户自己选加哪个。
另外两页
这两页本站没有展开,放个指引:
- 让你的命令行工具推荐你的插件。 如果你维护一个 CLI,它可以在检测到自己运行在 Claude Code 里的时候,往标准错误输出写一行
<claude-code-hint />标签,Claude Code 会给用户弹一次安装提示。只对列在官方市场里的插件有效。 - 给 mod 写测试、mod 不工作怎么查。 mod 有自己的两页文档。写第一个 mod那篇讲了怎么做,测试和排错这两页还没有写到。
通过中转站用,有什么不一样
评测通过中转站正常跑完了。它用的就是你平时的认证和模型,没有额外的要求。
有两处和接入方式有关:
- 报告发布不了。 默认它会把报告发布到 claude.ai,这要 claude.ai 的账号。本站带了
--no-publish,报告留在本地。 /skill-doctor用不了,见上文。
这一趟花了多少
评测跑了两遍,每遍 12 次运行、约半分钟,分别是 0.31 和 0.28 美元,合计约 0.59 美元。每次运行 0.01 到 0.03 美元,用的是 Sonnet 5.5,没有用到要花钱的评审类评分器。这是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。
这次实测没有覆盖到的
claude plugin eval init的访谈式流程没有试。llm和baseline两种要评审模型的评分器没有试,“评审不稳定”是文档的说法。- 没有接进 CI。
--threshold的退出码、--max-cost-usd的中止都没有验证。 - 用例里没有用到文件、脚本和 MCP 的模拟。 示例插件的 skill 只输出一行文字,用不上。
- 子代理和 hook 没有写用例,只测了 skill。
- 依赖、托管市场、版本更新三节全部是文档的内容,没有搭一个真的市场来验证。
- 只跑了两遍。 这两个用例很简单,两遍的分数完全一样;复杂的用例波动有多大,没有数据。