Use when the user hands over a dsh plugin repository (or a real migration commit / version corridor) and wants its upgrade experience extracted into one auto-graded Harbor benchmark exam task — produce tasks/<id>/ (fixture + instruction.md + task.toml + judge.mjs + solution) that harbor run can grade 0–1 and that folds into the dsh-plugin-upgrade-skill benchmark suite; also applies to turning existing version cards (references/v*.md) into executable exam tasks. Not for writing upgrade cards; not for running the benchmark.
Resources
2Install
npx skillscat add oh-my-dsh/dsh-plugin-upgrade-skill/dsh-benchmark-case Install via the SkillsCat registry.
从插件仓库提取 dsh 升级考题
把一个真实发生的插件迁移(git 历史里的破坏性变更 commit)变成一道
Harbor 考题:旧形态插件为 fixture(装上必坏,或有隐藏坑),新形态为
oracle 参考解,judge 在容器内真实冷启动判活。产出跟随./benchmark/(上文:oh-my-dsh/dsh-plugin-upgrade-skill 的 benchmark 目录)。
上下文锚点:目标套件是 dsh-plugin-upgrade-skill 的 benchmark v2.4(Harbor
格式),host 镜像固定为 node:24-bookworm + 全局 dsh 0.1.2-alpha.2。每条考题
遵循 execution-contract(BENCHMARK-AUTH-v1)与固定评分模型(100 分/题)。
何时使用
- 用户给一个插件仓库,说"提成考题 / 做成 case / 转 benchmark task"。
- 用户拿着一条版本走廊或一个迁移 commit,要可执行的评测而不是文档。
- 现有升级卡(references/v*.md)需要测"AI 会不会照卡修"。
工作原理(一句话)
fixture = 迁移前形态(偶藏误导注释/预置失败),judge = 静态检查 +
容器内真实冷启动信号(pending / plugin tree failed / MISSING_CREDENTIAL /__DSH_BOOT__ 条目),oracle = 参考迁移,判分天然 0~1。
Pipeline
Stage 0 · 选材与走廊定位
先回答三个问题,答不出就退回升级卡形态,不做题:
- 迁移方向:仓库 git 历史里哪一个 commit / 哪一段走廊是"host 升级导致的
必需改动"?(例:426e86dwhale-girl bundle 转换,走廊 rc.8→rc.1)。
纯自研重构、无 host 侧断裂的 commit 不成题。 - 可测性:迁移后形态能否在容器内"装得上 + 冷启动得到确定信号"?
判分信号必须是宿主侧信号(见 Stage 3),不是"输出文本长什么样"。
无浏览器容器 ⇒ client 运行时不可判,只能判 boot 图条目 / HTTP 状态。 - 对应卡:这条走廊在 references/ 里有没有卡(或需要先补卡)?
考题与卡是一对多:一道题可以覆盖多张卡,但卡必须已存在或随题新增
(validate.mjs 会校验卡引用存在且链接可解析)。
选材标准(×成立越多越好):迁移真实发生过(有 commit 可考);
断裂面小(1~4 个 touchpoint);装旧形态必有可观察故障(pending / 静默缺行为);
新形态在 alpha.2 上可冷启动验证;最好带隐藏坑(诱导注释 / 看着无辜的面上
藏着致命伤——坑要来自真实迁移的"第二个 commit",不是编的)。
产出:选定的 task id(见 Stage 1 命名)+ 走廊 + 覆盖的卡 ID 清单 +
fixture 应模拟的第 N 个 commit 形态(迁移前)。Checkpoint:三者对齐才继续。
Stage 1 · 骨架与命名
任务目录 tasks/<id>/,id 命名:前缀字母 S(静态只读)/ M(迁移可改)/
H(hands-on 陷阱),编号接该系列现有最大值。编号不要凭记忆硬编码——
动手前先 ls benchmark/tasks/ 并对照 benchmark/README.md 任务表与注册表
校验脚本确认当前集合(当前 52 题:S1–S16 / M1–M14 / H1–H22,下一可用
S17 / M15 / H23),勿跳号。一个任务一个目录:
tasks/<id>/
├── instruction.md # 给 agent 的 prompt(含 BENCHMARK-AUTH-v1 契约)
├── task.toml # Harbor 配置(见 assets/task.toml.example)
├── environment/
│ ├── Dockerfile # node:24-bookworm + git + pnpm + dsh alpha.2 + git 基线
│ └── fixture/ # 旧形态插件(private:true,README 声明 exam material)
├── tests/
│ ├── test.sh # verifier 入口:跑 judge,score→reward 写 /logs/verifier/
│ ├── judge.mjs # 判分逻辑(全部在这)
│ └── judge-utils.mjs # 共享判分库:profile 生命周期、冷启动信号、清理
└── solution/
├── solve.sh # oracle:把参考解拷进 fixture
├── SOLUTION.md # 参考解说明 + The Point + Grading Boundary
└── plugin/ # 参考迁移后的完整文件模板在 $_S/assets/:task.toml.example、instruction.md.example、
judge-utils.mjs(整库直接复制)、test.sh.example、Dockerfile.example、
solve.sh.example、judge.mjs.example(骨架)。
装 dsh 的 Dockerfile 必须(与现有任务一致,勿改版本):FROM node:24-bookworm;装 git;npm install -g pnpm@11.24.0 @deepseek-ai/dsh@0.1.2-alpha.2;COPY fixture → /app/fixture;git init + 基线
commit(judge 靠 git status --porcelain -- fixture 判"fixture 是否被改",
只读题 0 分门禁,可变题 0 分门禁的相同实现)。Checkpoint:目录 6 块齐全。
Stage 2 · fixture 构造
按"迁移前第 N 个 commit"的真实形态写 fixture,三个纪律:
- 必须私有:package.json
"private": true;README 首行
"exam material only, do not publish"。源头切片除外(见 4)。 - 坑要用真坑:误导注释要像真话——来自真实迁移决策记录里"当时为什么
这样做/为什么别改"的措辞;预置失败测试要确实预先失败且与升级无关
(测诚实报告,不测静默修复)。 - 坏必须可观察:旧形态要么装上 pending / plugin tree failed,要么
静默缺行为但 judge 有对应锚(如 boot 图没有该插件的 client 条目)。
判分锚必须能在容器里拿到,拿不到的 feature 不出现在 judge 里。 - 上游切片保留原元数据(Apache-2.0 真实仓库切片如 dsh-web):保留
license/original package.json,读 provenance/ 惯例(见基准tasks/H9-dsh-web-alpha2/provenance/)。
fixture 放哪、可以留什么:Node half 可留在 .dsh-plugin/ 子目录等
旧布局位置——但这类布局有深坑可挖:host 的 client-modules 从 Node entry
文件向上找最近 package.json 作为包身份(机制见$_S/references/host-archaeology.md 的"manifest 遮蔽"条目)。设计 fixture
时可利用这类机制布坑:让旧布局残留文件影响包识别,浏览器半静默消失;
判分侧的 boot 图条目检查会自然抓到。具体某道在线题怎么布、参考解怎么收
尾,不在此展开——方法级不讲答案。
Checkpoint:dsh plugin add 旧 fixture 在容器里真的坏 / 有静默缺陷;
新 fixture(参考解)全绿。两态都必须先本地 docker 验证,不许只靠想象。
Stage 3 · judge 设计(判分心法)
固定评分模型:100 分/题,emit(score, reasons) 最后一行输出{"score":0-100,"max":100,"reasons":[...]},test.sh 归一化到 0~1 写/logs/verifier/reward.txt。线性叠加 checkpoints,禁止加权魔法。
通用三段式:
| 段 | 内容 | 分数惯例 |
|---|---|---|
| Gate | fixtureChanges():fixture 必须被改(静态题 0 分;可变题 0 分) |
0 或整体继续 |
| 静态 | manifest 字段 / exports / patch insert 行 / 源码模式(grep) | 40~70 |
| 运行时 | dsh plugin add + 冷启动信号 + boot 图条目/HTTP |
30~60 |
判分锚必须是宿主侧信号(从 judge-utils.mjs 输出获取):
NEGATIVE_SIGNAL:plugin tree failed/did not activate/pending (waiting for service: …)/FAILED fiber/ClientPackageCompositionError= 失败带(通常 40 或该 check 0 分)。- headless 冷启动:无 API key 时到达
MISSING_CREDENTIAL/no API key/dsh: AUTH= 插件树整体激活,通过。出口码不算——无 key 成功也 exit 1。 - web 冷启动 + GET
/:__DSH_BOOT__.entries包含<pkg>/client.js
(注意:boot 图 URL 按 exports 的 key 拼${pkg}/client.js,与文件
真实路径无关)或该插件的 HTTP 通道 401/200 冒烟。
边界声明:judge 头注释与 SOLUTION.md 的 Grading Boundary 必须写明"容器
内没有浏览器 → client 运行时行为不判,只判宿主宣告的 boot 图条目"这类边界;
未覆盖面同样写进 scoring.md。
分数带设计提醒:给"半对"留带(关键声明完整给满分、缺字段给半分、
完全缺失 0 分);陷阱跟随者给封顶(如误导注释说别改 X,留着 X = 该
check 上限)。判分必须能区分"真迁移"与"看起来绿但绕开了迁移"——绕道
路径 boot 也能绿,要用静态检查(grep 特定字段/模式)封顶才抓得住。
运行纪律:judge 只建 bench-<task> profile 与 /tmp/bench-<task>,
finally 里清理(pkill 用 [x] 自逃逸模式防止自杀)。timeout 充足(add 180s /
boot 60s / web 150s,真实 workspace 题 verifier 900s)。judge 永远 exit 0,
test.sh 解析不了 JSON 时按 0 计。
Stage 4 · oracle(参考解)
solution/plugin/ 是完整迁移后的 fixture;solve.sh 逐文件覆盖进/app/fixture/,并显式删除该题迁移要求删除的残留文件。
SOLUTION.md 三节必备:
- Reference Changes:每条改动对应卡 ID 全名(如 DSH-0.1.1-R1-01,
不要写 R1-01 简写——validate.mjs 校验全 ID 存在)。 - The Point (one sentence):这道题真正测的一招。
- Grading Boundary:与 Stage 3 的边界一致。
- Warning:如果 judge 是 grep 源码模式判"已删除",参考解源码注释里
不得出现被 grep 的字面 token(实战教训:solution 注释复述了被判
"已删除"的旧标识符字面,judge grep 命中注释导致自伤扣分)。
Checkpoint:oracle 在容器里跑出 100/100(reward 1.0,下一步做)。
Stage 5 · 契约与注册表同步(四连)
instruction.md 契约(validate-execution-contract.mjs 用正则逐条匹配,
字面必须命中,双语任选其一):
- 恰好一处
BENCHMARK-AUTH-v1;契约五子句:
no-follow-up("there will be no follow-up user messages")、
proceed-after-plan("continue executing immediately once the plan is formed")、
no-pause("do not pause to wait for confirmation")、
no-modify("must not modify the skill")、
no-stop("do not stop merely because another round of confirmation is missing")。 - 边界授权句按 mode:可变题 "you may modify
/app/fixture/directly";
只读题 "/app/fixture/must remain completely unchanged";
build-artifacts 题 src 零改动 + lib 可清理(照抄现有任务的措辞最稳)。 - task.toml:
execution_contract = "BENCHMARK-AUTH-v1"恰好一次、version = "1.1.0"。
四连注册表(全部必须同步,否则校验失败):
benchmark/README.md:任务表加行(Task/Type/What it tests);顶部
"The N plugin-upgrade tasks measure";"The first N are written exams …
the last M are hands-on"(按 Type 列计数);# all N tasks注释;
"All Ninstruction.mdfiles carry";"existing N tasks" 维护者注记。benchmark/docs/scoring.md:Total <N×100> (N tasks × 100; …);
任务表加行(Checkpoint 卡引用 + Score breakdown 精确到每条)。benchmark/scripts/validate-execution-contract.mjs:expectedModes
Map 加['<task-id>', '<mode>'](mode ∈ readonly | mutable |
build-artifacts-only)。- benchmark 的卡引用:全 ID + 链接可解析(validate.mjs 会查)。
Stage 6 · 校验与 oracle 自检(门禁)
cd dsh-plugin-upgrade-skill
node scripts/validate.mjs # 卡 + 链接 + 引用全绿
node benchmark/scripts/validate-task-registry.mjs # 任务清单/计数注册表一致
node benchmark/scripts/validate-execution-contract.mjs # 契约五子句 + mode
# oracle 自检(must be 1.0):
harbor run -p benchmark/tasks/<id> -a oracle
# harbor 不可用时(本机 pip/uv 受限),用 docker 手动走通等价值:
# docker build -t bench-<id> environment/ && docker run ... 复制 oracle →
# fixture → node tests/judge.mjs → 断言 {"score":100}
node --check tests/judge.mjs && node --check tests/judge-utils.mjsCheckpoint:三个校验全绿 + oracle 100/100 + 文件权限(solve.sh / test.sh
755,与现有任务一致)。不绿不交付。
交付
tasks/<id>/完整任务 + 四连注册表改动,与升级卡(如有)同一 PR 主题。- PR 描述声明验证命令与结果(validate × 3 + oracle 1.0)、覆盖的卡、
未覆盖边界、致谢(本次实战致谢脚本$_S/无,按仓库惯例)。
Gotchas(实战教训,新增题前先扫)
- 注释会自伤:solution 源码注释不得含 judge 要 grep 的"已删除"字面 token。
- manifest 遮蔽:client-modules 从 Node entry 向上找最近 package.json;
残留旧清单(无 dsh.client)让浏览器半静默消失——参考解要删,
这是合法题点不是 bug。 - boot URL ≠ 文件路径:
__DSH_BOOT__条目按 exports key 拼${pkg}/client.js;判 boot 图别去匹配真实路径。 - 服务名随走廊变:
httpServer→webServer(R1-09)、tasks→jobs;
fixture 用旧名、参考解用新名之前,先在容器冷启动验证哪个名能解析。 - profile 组合:web 冷启动 profile 用
['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'];headless 用
headless 预设。别复用共享 ~/.dsh。 - 版本钉死:Dockerfile 的 pnpm/dsh 版本与现有全部任务完全一致
(pnpm@11.24.0 / dsh@0.1.2-alpha.2);改了等于换题。 - pkill 自杀:judge 清理命令 pidpattern 用
[x]括号技巧,否则匹配到
自己执行的 sh -c 会杀自己(本次实测 exit 143)。注意[x]只防"模式
文本自身":同一 sh -c 命令行的其他位置若还有明文 profile 名(如 add/boot
命令),pkill 依然自杀——把 pkill 拆成独立 exec。 - sh 引号吞内嵌脚本:judge 里用
node --input-type=module -e 'script'
做路由冒烟时,内嵌脚本中的单引号会在 sh 单引号内提前闭合 → node 收残缺
程序 → SyntaxError 伪装成"路由失败"(stderr 只有 node 栈尾)。修法:
内嵌脚本零单引号(断言用JSON.parse(text).ok === true而非text.includes('"ok":true'))。已写进 M14 judge 头注释。 - harbor 可用性:本机 harbor CLI 因 uv/pip 沙箱装不上;oracle 验证可用
docker 手动等价路径,README 说明跑法(harbor run -a oracle 为标准式)。 - 一个走廊多题:强 case(如 bundle 转换、服务改名)可出多道独立题,
各自 fixture 聚焦不同 touchpoint;不要一道题塞所有。 - 判别力验证:交付前不只要 oracle=100,还要跑负例矩阵——未动
fixture 必须 0(gate)、陷阱跟随为该 check 上限、半迁移(如只改服务名
不改事件名)落在预期带。judge 要是"什么都得高分"就没有评测价值。
references(按需读)
$_S/references/case-selection.md— 选材判据细节与反例。$_S/references/harbor-task-spec.md— 目录规范、task.toml 字段、
环境镜像、判分信号表(详细版)。$_S/references/contract-clauses.md— 契约五子句与授权句的全量字面
(照抄最稳)。$_S/references/grading-recipe.md— judge 三段式、分数带、caps 设计。$_S/references/host-archaeology.md— 宿主源码考古出的坑与证据
(client-modules 解析、boot URL、服务改名)。$_S/references/registry-sync.md— 四连同步全量清单 + 校验命令。$_S/assets/— task.toml.example / instruction.md.example /
judge-utils.mjs(完整库)/ judge.mjs.example / test.sh.example /
Dockerfile.example / solve.sh.example。
校验清单(交付前逐项自查)
- Stage 0 三问:真实迁移 / 容器可测 / 卡存在或随题新增
- fixture
private: true+ "exam material only";旧形态容器实测会坏 - judge 全部锚是宿主侧信号;gate/静态/运行时三段齐全;边界声明齐全
- oracle 100/100(harbor 或 docker 手动)
- solution 注释无 judge grep token;残留清单已删
- 四连注册表同步(README × 6 计数、scoring Total+行、expectedModes)
- validate.mjs + validate-task-registry + validate-execution-contract 全绿
- solve.sh / test.sh 755