🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

场景测试工坊 supengpeng/dsh-testkit

DeepSeek Harness
supengpeng19 天前有提交已提供中文文档扫描:无法判定

场景驱动的 DSH 测试插件:把测试场景变成可声明、可复跑、可进 CI 的数据

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/14 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add supengpeng/dsh-testkit
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · dev
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 19 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/10/10) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包@supengpeng/dsh-testkit(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22.19 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 08:17:25

安全扫描自动 · 静态分析源码
v2.7.0
⚪ 扫描:无法判定尚无人工评估
展开查看扫描说明与证据 ↓

已尝试扫描但取不到可分析的源码:仓库或包不存在、已删除,或为非 JS/TS 实现、未随包发布源码。无法判定,不代表安全。

  • · 取源不可用(Error: github tarball HTTP 404 @ https:
  • · api.github.com
  • · repos
  • · supengpeng
  • · dsh-testkit
  • · tarball)
扫描于 2026/11/1(0 天前)

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-llm@deepseek-ai/dsh-session@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-tools@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 声明式测试场景
  • 场景可复跑
  • 接入CI

README

由 DeepSeek 最新模型翻译生成
dsh-testkit DSH(DeepSeek Harness)测试插件。把 issue 提炼成可复现的测试场景,再让插件去造出那些场景。 提炼是逐批、由人决定的:人开批 → 模型只能提交提案 → 人批准才进 cases/(见提炼闸门)。 它的输入是 issue,输出是可复现、可断言、可回归的场景资产: issue ──提炼──▶ cases/TK-XXXX.yaml ──驱动──▶ src/kinds/.ts ──执行──▶ runs/<RUN-ID>/report.md 插件的测试对象不锁定:DSH 宿主能力、第三方插件、端到端行为,都由「场景 kind」决定。 包名已改为 @supengpeng/dsh-testkit(npm 上的 dsh-testkit 已被 iiwish/dsh-testkit 占据,latest 0.4.4,所以旧名字既不能用也不能发布)。改名依据、模块 id 耦合点与仍需在活宿主验证的最后一步见 docs/PUBLISHING.md。 插件身份(profile roster 里的 id、client 半模块名、locale 命名空间)仍是产品名 dsh-testkit。 重构:从纯 TypeScript 到「TS 接口层 + Rust 核心」 本仓正在按 RFC 0001(设计 / 指标)做一次完全重构。
展开完整文档(README 剩余 1 节)
dsh-testkit

DSH(DeepSeek Harness)测试插件。把 issue 提炼成可复现的测试场景,再让插件去造出那些场景。
提炼是逐批、由人决定的:人开批 → 模型只能提交提案 → 人批准才进 cases/(见提炼闸门)。

它的输入是 issue,输出是可复现、可断言、可回归的场景资产:

issue ──提炼──▶ cases/TK-XXXX.yaml ──驱动──▶ src/kinds/.ts ──执行──▶ runs/<RUN-ID>/report.md

插件的测试对象不锁定:DSH 宿主能力、第三方插件、端到端行为,都由「场景 kind」决定。

包名已改为 @supengpeng/dsh-testkit(npm 上的 dsh-testkit 已被 iiwish/dsh-testkit 占据,latest 0.4.4,所以旧名字既不能用也不能发布)。改名依据、模块 id 耦合点与仍需在活宿主验证的最后一步见 docs/PUBLISHING.md。
插件身份(profile roster 里的 id、client 半模块名、locale 命名空间)仍是产品名 dsh-testkit。

重构:从纯 TypeScript 到「TS 接口层 + Rust 核心」

本仓正在按 RFC 0001(设计 / 指标)做一次完全重构。旧实现原地保留——下面的能力清单描述的仍是它,而重构是在它旁边长出来的。

为什么重构:判定逻辑、调度、对拍、签名链这些"必须确定、必须可复算"的部分,留在 TS 里靠约定守不住(HashMap 的迭代顺序、浮点、时钟、异常都是不确定源)。
把它们移进 Rust 之后,判定路径的不确定性变成编译期问题。

flowchart LR
subgraph TS["TS 接口层(人在这一侧)"]
DSL["src/dsl7 个组合器"]
COMP["src/compilernormalize→compose→validate→plan"]
RPC["src/rpcNDJSON 客户端"]
VER["src/attest零依赖验证器"]
end
subgraph CORE["Rust 核心(crates/,判定在这一侧)"]
P["protocol帧/握手/方法表"]
C["capability能力探测与门控"]
A["assertion16 断言词 + 四态"]
S["scheduler显式事件队列"]
E["executorplan 校验 + 执行"]
R["reconciler表驱动对拍"]
AT["attestJCS + Ed25519 + Merkle"]
end
COMP -->|"ExecutionPlan"| RPC
RPC <-->|"JSON-RPC 2.0 / stdio"| P
P --- C
P --- S
S --- E
E --- A
E --- R
AT --- R
VER -.->|"独立复算"| AT

四层防御(设计 §5.3):① TS 类型系统 → ② 编译器 validate → ③ plan 校验 + 执行器 → ④ 报告对拍。判定永不返回 Err:断言失败是结论(Failed),不是异常。

阶段 0 / 阶段 1 的读数(都能复跑)

阶段 0(行为提取与基线冻结)与阶段 1(架构地基)已完成。读数、门槛与复算命令集中在 spec/metrics/stage1-readings.md(一页看全;未测的写"未测")。
要点:

| 指标 | 门槛 | 读数 |
| F1 编译期拦截率 | ≥95% | 21/21 = 100% |
| F2 plan 校验拦截率 | ≥95% | 100%(11/11) |
| K1 层级可信度正确率 | 100% | 7/7 = 100%(边界见下) |
| J1 Rust 覆盖率 | 行 ≥85% / 分支 ≥80% | 行 89.84% / 分支 82.12% |
| J2 TS 覆盖率 | 行 ≥80% / 分支 ≥75% | 行 87.05% / 分支 77.74% |
| A2 对拍字段覆盖 | 100% | 78/78 = 100% |
| A3 "没比"必须可见 | 100% | 80/80 = 100% |
| E2 篡改检出率 | 100% | 77/77(+ 两侧逐字段一致) |
| D4a 签名次数上界 | 成立 | ceil(N/B)+1 ≤ ceil(N/B)+2 |

门槛的读法:cargo +nightly llvm-cov 与 stable 的读数不可比 (Lines 分母 8732 vs 10019),且必须用独立 CARGO_TARGET_DIR (默认目录会复用陈旧 profile,我因此报错过一次)。这些坑连同 13 条已知缺陷都记在 spec/metrics/known-defects.md。

如实标注:K1 的 7/7 是阶段 1 能给出的最强形式——「实际执行」那一半走的是 NodeRunner 注入替身,不是端到端真跑的 Rust 服务端(该可执行文件在阶段 1 不存在);「真实宿主端到端」留待阶段 2。这条边界写在测试文件的文件头里,不只在这里。

安装

本包有两种身份,装法不同:

① 作为 DSH 插件(host 半 + client 半) $DSH = 'D:\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd'

从本地路径装(开发时用这个) & $DSH plugin --profile desktop add <本仓绝对路径>

或从 git 装(注意:本包没有 bin,装的是插件本体) & $DSH plugin --profile desktop add git+ssh://git@github.com/supengpeng/dsh-testkit.git

② 作为库 / CI 用例的依赖
npm install -D @supengpeng/dsh-testkit

本包现在有 CLI:dsh-testkit <子命令>(bin/dsh-testkit.mjs)。
它与 /testkit 人类命令、testkit_ 工具共用同一套引擎(不重造 runner / 选择器)。

dsh-testkit list # 列场景 dsh-testkit run # 跑全部 active(默认拒绝真实模型调用) dsh-testkit run --changed # 只跑受工作区改动影响的场景 dsh-testkit run --smoke # 冒烟集(5 秒静态估算预算) dsh-testkit coverage # 覆盖矩阵 + 可行动的缺口 dsh-testkit trace --format chrome # 步骤级 trace(可直接喂 chrome://tracing / Perfetto) dsh-testkit trend # 历史趋势(通过率 / flaky 率 / 耗时)

退出码:0 全部通过(含 skipped)· 1 有 failed/errored · 2 用法错误或选中 0 条 · 3 基础设施错误 · 6 协议不兼容(握手失败)· 7 必需能力缺失(6/7 是 1.0.0 新增)· 8 层级不允许(2026-10-11 阶段 2 裁决,协议码 -32008;见设计 §4.5)。
4/5 刻意保留不使用——它们是同名第三方包的发布语义,同名不同义是最贵的坑。
CLI 走 headless 宿主,因此缺 fs / sessions / goals / compaction 能力的场景会如实 skip(它不假装自己跑过);subprocess 自阶段 2 收口起已由 headless 提供,shell 类场景会真跑进程。

当前状态

| 部分 | 状态 |
| 架构与规范文档 | ✅ 五份已完成 |
| host 半(引擎 / 断言 / 夹具 / 装载 / 工具面 / 命令面 / HTTP 通道) | ✅ 可编译,且在真实 cordis 容器里装配通过 |
| client 半(会话内「测试」标签页 + 控制台) | ✅ 已构建,且在真实 web profile 里验证通过 |
| tool driver | ✅ 注册临时工具 / guard 拦截 / 真实管道调用取证 |
| prompt driver | ✅ section / context / variable 注入 + 主动 assemble() 取证 |
| llm driver | ✅ 接管 llm/stream:零上游请求、四种失败注入、用量伪造 |
| interaction driver | ✅ 接管提问与审批:按声明应答、超时路径可断言 |
| session driver | ✅ 人类命令:注册 / 驱动 / 两条失败路径(返回值 vs 抛异常) |
| resource driver | ✅ 假 web provider:接管 providerId、截断语义、降级路径 |
| agent driver | ✅ 派生真实子 agent 跑任务并断言轨迹——两条通道:one-shot(subagents.start)与 teammate(复用 Agent Teams,agentTeams.spawnTeammate)(⚠️ 会真的调模型) |
| ui driver | ✅ 在隔离 vm 里加载真实 client bundle,验证产物契约与 slot / 词典注册(纯离线) |
| shell driver | ✅ 跑外部命令(argv 数组)并取证退出码 / stdout / stderr(由真实 issue 数据驱动) |
| file driver | ✅ 读文件 / 列目录 / 搜内容(search,对应 grep);纯离线,任何宿主都能跑(由能力缺口分析驱动) |
| Phase 1 / 2 / 4 / 5 / 6 / 7 / 8 / 9 / 10 | ✅ 全部收口——12 个 driver 覆盖 12 类干预点,双轨执行可用 |
| 场景可跑通 | ✅ cases/ 下 40 条场景;TK-0001..0026 在真实 DSH 里 25 通过 / 1 失败(预期)/ 0 跳过;TK-0028…TK-0034(两条 waterfall / ctx.fs / session 三件套 / compaction 边界)在独立 headless 新进程 7/7 通过;TK-0035(真压缩)单跑通过。TK-0027 / TK-0033 / TK-0035 / TK-0037–TK-0040 是 draft(留痕或花 token;其中 TK-0040 是 D-1「步骤级释放真的生效」的端到端判据) |
| 组合场景(跨 kind) | ✅ setup 可含多个 kind,act 按动作形状分派;实测证明 root 的假 provider 会穿透到子 agent |
| issue 提炼闸门 | ✅ 要不要提炼、要不要落地都由人定:人开批次 → 模型只提交提案(质量预检不过不落盘)→ 人批准才进 cases/;未结案不允许开下一批(三个闸门都有回归测试,见 tests/pipeline-gate.test.mjs) |
| 成本闸门 | ✅ 场景可声明 cost(none/low/high)与 budget;high(真调模型)默认拒绝,被拒记为 skipped + 理由;预算超限判 failed 并归因 env。默认档位表在 src/kinds/index.ts 的 DRIVER_COST |
| 报告标准化 | ✅ runs/<RUN-ID>/junit.xml(CI 消费)+ schemas/run-report.schema.json(结构契约)+ 失败归因与最小复现(src/analysis/);md / json / junit 三种格式同源 |
| CI 与自举契约 | ✅ .github/workflows/ci.yml:Node 22/24 × ubuntu/windows/macos 共 6 组,唯一入口 pnpm run gate(不另拼一套,避免假绿);另有 rust job(Rust 测试 / clippy / fmt / H3 守卫)与 fixtures job("有外部夹具"这个环境,B4 的第 7 个组合) |
| 适配层守卫 | ✅ src/adapters/dsh/ 是全仓唯一允许依赖 @deepseek-ai/dsh- 的目录,由 scripts/check-adapter-boundary.mjs 机器守卫(注释里的包名不算) |
| 增量选择 / 夹具 / 契约 / 并发 | ✅ --changed/--since/--affected-by(git 不可用则退回全量)· fixtures/ 声明式夹具(CI 轨与插件面同一条链)· tests/contracts/ 65 条契约(含反安慰剂)· parallel: safe 并发隔离 + 残留检测 |
| 组合系统 | ✅ registry/steps/ 片段 + use:/with: 展开 + templates/ 参数化(一键展平成 flat 步骤,禁控制流与场景级 include) |
| 沙箱与隐私 | ✅ shell 默认只读(写命令与解释器默认拒)+ 禁止任意网络 + --redact 脱敏(findings 只记位置不记原文)+ check-secrets 门禁 |
| 可观测性 | ✅ 步骤级 trace(真实偏移;trace.json + 时间线 / Chrome Trace / OTLP 三种导出)· 结果趋势(kind/tag/owner/DSH 版本)· 覆盖矩阵与可行动缺口 · 全文搜索(0 条时解释为什么)· 失败原因分级(有据才说) |
| 独立 CLI | ✅ dsh-testkit <子命令>(bin/):17 个 CLI 子命令 + 全部选择/闸门开关;与 /testkit、testkit_ 共用同一套引擎 |
| 供应链与治理 | ✅ CI 硬化守卫(最小权限 / 禁止 pull_request_target / Action 钉 SHA)+ 锁文件守卫 + secret 扫描 + pnpm audit 独立步骤 + 带 provenance 的发布工作流;CODEOWNERS / 贡献指南 / 行为准则 / PR 与 issue 模板 / RFC 模板 / 迁移指南 |
| 自动 triage 与体检 | ✅ 生成 issue 草稿与 PR 评论(归因标签 + owner 路由,只出文本不发请求、不含取证原文);dsh-testkit doctor 报告能力矩阵 / 哪些场景会 skip / 残留(临时目录、端口、进程) |
| 验证(唯一入口) | ✅ pnpm run gate:主套件 809 项 + 契约轨 65 项(两个独立集合,后者不在前者里)+ 16 个守卫 + 导出的 26 条场景(gate 默认排除 7 条 fixture 场景——它们测的是外部被测对象)。Rust 侧另有 cargo test --workspace(439 通过)与 cargo clippy --workspace --all-targets -- -D warnings |
| 真实 DSH 验证(host 半) | ✅ 14 通过 / 0 失败 / 2 跳过 / 0 错误——独立 headless profile 实测,未改动 desktop profile |
| 真实 DSH 验证(client 半 + HTTP bridge) | ✅ 独立 web profile 实测:「测试」标签渲染、控制台显示 16 条场景 |

📋 完整功能清单见 docs/FEATURES.md(13 个模型工具 / 17 个 CLI 子命令 / 12 个 kind / 17 个断言词 / 约 241 个取证字段 / 16 个质量守卫),只列已实现并实测的能力。

风险台账:R1–R10 全部结案(0 项未决),含两次源码推导与四次真实宿主实测,见 docs/ARCHITECTURE.md §9。

真实验证怎么做的(不碰你的 desktop 配置档)

$DSH = 'D:\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd'
& $DSH tk --from-default-profile headless --help # 建独立 profile(一次) & $DSH plugin --profile tk add <本仓路径> # 装插件(可逆) & $DSH tk "调用 testkit_run 工具,报告通过/失败/跳过/错误" # 跑完即退出

headless 模式「答一个任务、打印结果、退出」,所以不占端口、不中断正在进行的会话。
完整说明与能力缺口见 DEVELOPMENT.md §5.3。

两条腿都已可用

腿 1:内置 runner(在活 DSH 里跑) /testkit run # 或让模型调 testkit_run

腿 2:导出的 CI 用例(自带 headless 宿主,无需 DSH)
npm run export:ci
node --test export/scenarios.test.mjs

| 腿 | 证明了 | 没证明 |
| 内置 runner | 真实宿主里的行为 | — |
| CI 导出 | 场景数据 + driver 逻辑 + 断言可判定 | 与真实 DSH 的交互一致(作用域 / 生命周期) |

两条腿不能互相替代:CI 轨是回归网,活宿主是真相来源。

已能跑通的场景

cases/ 下 40 条场景(其中 7 条带 source.issue 溯源):

| ID | kind | 测什么 |
| TK-0001 | tool | driver 自检:注册 → 调用 → 取证链路可用(smoke) |
| TK-0002 | tool | 边界:200KB 返回值不失败、值未被截断(精确长度断言) |
| TK-0003 | tool | tools.guard 拦截:拒绝理由可断言,且工具本体确实没跑 |
| TK-0004 | prompt | 提示词注入:不跑模型,靠 assemble() 直接断言 |
| TK-0005 | llm | 接管模型流:零上游请求,输出完全由声明决定 |
| TK-0006 | llm | 失败注入:流中途出错时保留已产出内容、以 error finish 收尾 |
| TK-0007 | interaction | 提问被应答为声明选择的选项 |
| TK-0008 | interaction | 审批返回声明决策,而不是兜底的 unavailable |
| TK-0009 | interaction | 失败路径:无人应答时以超时错误收尾,不静默挂起 |
| TK-0010 | session | 人类命令可被驱动,输出与入参都可断言 |
| TK-0011 | session | 命令以 error 结果收尾时被如实反映 |
| TK-0012 | resource | 假搜索 provider 被接管 + maxResults 截断语义 |
| TK-0013 | resource | 降级路径:provider 不可用必须报错,不能静默返回空 |
| TK-0014 | agent | 派生真实子 agent 跑最小任务(⚠️ 会真的调模型;CI 轨里自动跳过) |
| TK-0015 | ui | client 半产物契约:bundle 可加载、注册了标签与词典、给了渲染函数 |
| TK-0016 | agent | 组合场景:假 web provider(resource)+ 真实子 agent,验证替身会穿透到子 agent |
| TK-0017 | tool | 多个 guard 的短路语义:第一个拒绝后不再询问后续 guard |
| TK-0018 | shell | shell driver 自检:跑 git --version 与一条必失败命令 |
| TK-0019 | shell | 发包面自检:files 白名单必须覆盖入口文件(源自 dsh-memory #48 的形态) |
| TK-0020 | file | file driver 自检:读文件、列目录、验证"文件不存在"被如实取证 |
| TK-0021 | file | dsh-memory 发包面回归:files 必须含 utf8_boot.py(#48 的直接对应) |
| TK-0022 | shell | 隐式顶层导入检查:自动找出"装机后才会炸"的包内依赖(#12/#48 一类) |
| TK-0023 | file | 结构性检查(search 动作):目标符号必须有调用方(#37「产物无消费方」回归) |
| TK-0024 | shell | #48 的运行时验证:用 DSH 自带 Python 真跑 import utf8_boot 与 find_spec('md_cg.mcp_server') |
| TK-0025 | shell | #22 回归:_writer_session 的三条取值口径(显式优先 / 否则取 cg.session / 都没有为 None) |
| TK-0026 | shell | #75(⚠️ 0.8.1 仍存在):csre.build_index() 因 md_conn_or_none 拼写笔误抛 ImportError |
| TK-0027 | agent | team 通道:agentTeams.spawnTeammate 派生真实 teammate,roster 出现成员、回落 inactive、产出被取证(draft:成员留痕不可逆) |
| TK-0028 | tool | tools/pre-execute:deny / cancel / ask 三条 dispatch 前决策,且工具本体确实没跑 |
| TK-0029 | tool | tools/post-execute:结果被 block(反馈进入交付内容)与 replace(改写) |
| TK-0030 | fs | ctx.fs 写意图:createIfAbsent 与陈旧版本(FS_STALE_VERSION)都必须被拒,且被拒的写不落地 |
| TK-0031 | fs | ctx.fs 沙箱:read-only 下写入被拒(FS_SANDBOX_DENIED);后端不实施沙箱策略时诚实跳过 |
| TK-0032 | session | session/flush 检查点:经唯一入口派发,且契约「等每个 listener 结算」有实测耗时佐证 |
| TK-0033 | session | ctx.goals 状态机:create 会 arm 自动续轮(driver 默认立刻收回授权);并实测出「一半目标是 @Remote 方法,不能本地直调」(draft:留痕) |
| TK-0034 | compaction | 压缩边界:没有安全范围时不压、非法范围被拒、compactNow 缺 agent 上下文时如实不可用(只作用于隔离会话) |
| TK-0035 | compaction | 压缩正向路径:隔离会话上真的压出摘要;实测出「收缩校验」——成败取决于模型摘要长度,故按 compactionOutcome 断言 + soft 形状(draft:花 token) |
| TK-0036 | shell | #57 防回退:保留设备名守卫改「末段直判」后,谓词表两侧(真值须拦 / 假值须放行)与 _abs_host_path 的接线都必须在位(fixture:需 dsh-memory 0.8.1) |

快速上手

cd <本仓路径>

依赖(node/pnpm 用绝对路径,本机不在 PATH 上) $NODE = '<node 可执行文件路径>' $PNPM = '<pnpm.mjs 路径>' & $NODE $PNPM install

构建(host 半 tsc + client 半 esbuild) & $NODE $PNPM run build:all

装进 desktop profile(本机实测:装完无需重启) $DSH = 'D:\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd' & $DSH plugin --profile desktop add .

自检 & $NODE $PNPM run gate

Rust 核心(阶段 1 起)
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings

装完即刻可用(本机实测):dsh plugin add 会触发 profile 热重载,本次会话里 testkit_list / testkit_run / testkit_report / testkit_export / testkit_propose / testkit_pipeline 当场出现,不必重启 DSH。
客户端那半(会话视图环里的「测试」标签页)如未出现,刷新一次页面即可—— client bundle 由浏览器加载,不受 host 热重载影响。

装好后在 DSH 里:

- 模型侧工具:testkit_list / testkit_run / testkit_report / testkit_export / testkit_propose / testkit_pipeline
- 人类命令:/testkit list | run | report | export | reload | issue <open|show|approve|reject|close>
- 界面:会话视图环里的「测试」标签页

目录

dsh-testkit/
├── docs/ ① 架构 / 开发 / 场景规范 / issue 流程 / 迭代计划 / 发布 / 安全 │ └── rfc/ ①′ RFC 0001(Rust 核心全量重构)+ 设计 / 指标 ├── crates/ ⑲ Rust 核心(阶段 1):protocol / capability / assertion / │ scheduler / executor / reconciler / attest ├── spec/ ⑳ 行为规格(阶段 0,重构期唯一历史):behaviors/(12 kind + │ engine)· contracts/(能力 / 版本 / 注册面 / 对拍字段)· vectors/ │ (JCS + 篡改语料)· metrics/(读数 / 工具链 / 已知缺陷台账) ├── baseline/ ㉑ 冻结的对拍基线(D1–D3 的性能与环境读数) ├── src/ │ ├── cases/ ② 数据层:YAML 类型、校验、装载、注册表 │ ├── kinds/ ③ 驱动层:kind → driver(每类干预点一个) │ ├── runtime/ ④ 引擎层:夹具、执行、断言、取值、记录 │ ├── report/ ⑤ 报告层:Markdown / JSON / JUnit │ ├── headless/ ⑥ headless 宿主:契约一致的最小服务集(CI 轨的载体) │ ├── export/ ⑦ 导出层:生成自包含的 node:test 文件 │ ├── client/ ⑧ client 半:浏览器侧控制台 │ ├── dsl/ ⑳′ TS 侧 DSL:7 个组合器(seq/parallel/when/retry/matrix/ │ setup/dependsOn)+ 场景构造与构造期校验 │ ├── compiler/ ⑳″ 场景编译器:normalize → compose → validate → plan │ │ (产 ExecutionPlan;六项校验含依赖环 / 资源冲突 / 嵌套深度 / │ │ 层级-可信度自洽) │ ├── rpc/ ⑳‴ JSON-RPC 客户端:NDJSON 帧 / 握手 / 九方法 / 按 id 配对 │ ├── attest/ ⑳⁗ 零依赖验证器(免构建直接跑:独立验证的前提) │ ├── contracts/ ⑳⁵ 跨语言类型:generated/ 由 crates/protocol 的 ts-rs 导出 │ │ (禁止手改;scripts/check-generated-types.mjs 守它) │ ├── adapters/dsh/ ⑨ 适配层:全仓唯一允许依赖 @deepseek-ai/dsh- 的目录(机器守卫) │ ├── host-facade.ts ⑩ 窄接口 HostFacade 的装配(只经适配层取 DSH 原语) │ ├── http.ts ⑪ client 通道(webServer 路由) │ ├── tools.ts ⑫ 模型工具面 │ └── commands.ts ⑬ 人类命令面 ├── cases/ ⑭ 场景数据:一案一 YAML(真源) ├── pipeline/ ⑮ 提炼闸门:批次台账 ledger.json + 提案 proposals/(批准后才进 cases/) ├── schemas/ ⑯ 报告结构契约(run-report.schema.json) ├── scripts/ ⑰ 构建 client 半 / 校验场景 / 校验文档 / 适配层守卫 / 导出 CI 用例 │ 另含通用检查器(见下)、gate 并发守卫、H3 类型守卫 ├── tests/ ⑱ 插件自身的单元测试(含 headless 宿主与自举契约) ├── .github/ ⑲ CI 工作流:gate(2 档 Node × 3 平台)+ rust + fixtures ├── runs/ 运行产物(git 忽略) ├── export/ 导出的 CI 用例(git 忽略,gate 会重新生成并跑) ├── .fixtures/ 外部被测对象(git 忽略;scripts/fetch-fixtures.mjs 准备) └── cases-draft/ 从 issue 数据提炼的草稿(git 忽略;scripts/from-issue-data.mjs 生成)

可复用的检查器

下面几个脚本是从真实 issue 形态提炼出来的通用判据,任何 npm 包都能用;第四个是本仓自己的结构守卫(它守的是「适配层是唯一入口」这条架构承诺):

| 脚本 | 抓什么 | 源自 |
| check-pack-files.mjs | files 白名单是否覆盖入口声明的文件 | dsh-memory#48 |
| check-python-topimports.mjs | 包内非相对顶层导入是否被打进包(「装机后才炸」) | dsh-memory#12/#48 |
| check-git-installable.mjs | 从 git 安装会不会得到没有入口文件的空壳 | dsh-memory#2 |
| check-adapter-boundary.mjs | @deepseek-ai/dsh- 的 import / import() / require() 是否只出现在 src/adapters/dsh/ 下(注释里的包名不算) | 本仓架构承诺 |
| check-generated-types.mjs | src/contracts/generated/ 是否被手改(重新生成后与仓库内容逐字节比对) | 重构(H3) |

用法:node scripts/<name>.mjs <包目录>,退出码 0/1(check-generated-types.mjs 无需参数、必须从仓库根跑)。
适配层守卫也接进了 gate 链(pnpm run verify:adapter),所以它不会只躺在脚本目录里。

三条设计要点

1. 场景即数据。 新增一条测试场景,理想情况下只加一个 YAML 文件、不改代码——只有新类型的场景才需要写 driver。

2. 一切干预可回滚。 DSH 的扩展点天然是「注册即返回 disposer」,所以「制造测试条件」=「安装可回滚的注册」。所有 driver 的注册都必须经 Fixture.add() 登记,场景结束逆序释放——这是活宿主测试不互相污染的前提。
3. 双半分离、单点适配。 driver 不直接依赖 cordis Context,而是依赖窄接口 HostFacade;对 DSH 的真实调用收敛在 src/host-facade.ts,而 @deepseek-ai/dsh- 这个包级别的依赖进一步收敛在 src/adapters/dsh/。好处是核心逻辑(断言、夹具、校验、执行)可以脱离宿主单测,DSH 升级时改动集中在一层——而且这层边界由 scripts/check-adapter-boundary.mjs 机器守住,不靠 review 记忆。

4. (重构新增)判定移出 TS,不进 TS 的判定路径。 断言、调度、对拍、签名链都在 crates/ 里;TS 侧只做类型、组合与展示。判定路径禁用 HashMap/HashSet (Rust 的默认哈希种子会让迭代顺序每次运行都变),由 clippy disallowed_types + 每个 crate 的文本守卫(带负向证明)双保险。「没比必须可见」:对拍器不比任何字段时,那个字段必须出现在 uncompared_fields 里——否则对拍器的盲区就是重构的盲区。

文档

| 文档 | 内容 |
| 重构设计 | Rust 核心 + TS 接口层的完整设计(协议 / 编译器 / 层级 / 签名链) |
| 重构指标 | 阶段 0–3 的退出条件、每个指标的门槛与测量方式 |
| RFC 0001 | 重构提案:12 个决定、未决项的裁决、替代方案 |
| 阶段读数 | 阶段 0/1 的全部读数(含未测与不可达项的如实标注) |
| 已知缺陷台账 | 重构期间发现的 13 条缺陷(含复现、根因、修法、归属) |
| 行为规格 | 阶段 0 的产出格式:一个模块一个文件、逐条 atomic 可判定条目 |
| 架构设计 | 双半架构、概念模型、kind 分类学、设计决策、风险清单 |
| 开发文档 | 环境、构建、安装、调试、HMR、真实验证流程、排障、代码约定 |
| 场景数据规范 | cases/.yaml 的完整字段规范(含组合场景、cost / budget) |
| issue 提炼流程 | 从一个 issue 到一条可复现场景的五步法 + 提炼闸门 |
| 发布与改名清单 | 发布清单、scoped rename 的全量引用与耦合点、活宿主验证步骤 |
| 迁移指南 | 0.1.0 → 0.2.0:默认值变化、新增字段、包名与形态变化 |
| 供应链与合规 | 依赖锁定与审计、场景禁网、secret 扫描、发布 provenance,以及我们做不到的 |
| 治理与 RFC | 版本与弃用策略、RFC 流程、release cadence |
| 安全策略与数据隐私 | 漏洞报告渠道与范围、响应承诺;report / fixture 的数据边界与保留策略 |
| 变更日志 | 每个版本改了什么、怎么迁移、明确推迟了什么以及为什么 |
| 贡献指南 | 环境、怎么加一条场景 / 一个 driver、质量门与不要做的事 |

验证过的事实(非推测)

架构文档里的这些结论来自本机实测或 DSH 发行体源码核对,不是猜的。

- 插件 = Cordis 插件;dsh.bundle.patch 声明 host 半,dsh.client 声明 client 半
- host 半工具:defineTool({...}) + ctx.tools.register(def) → disposer
- host 半命令:ctx.commands.register({ name, description, input?, handler(invocation) }); handler 收到 { rawInput, signal, agent, attachments },须返回 {kind:'success'|'error'}
- host 半路由:ctx.webServer.register({ kind: 'exact'|'prefix', path, handler(req, res) })
- client 半产物:window.__ModuleLoader__.load({ id, factory }),require("react") 由宿主模块表注入
- 双半通道:静态插件包没有 host.call(harness.handle 属动态包沙箱,定义在 dsh-cordis-host-runner 的 guard 里)→ 本插件用自建 HTTP bridge
- 能力探测:只能用 ctx.get(name);ctx.someService 在未 inject 时会抛错 (这个坑被集成测试抓到过,现已回归覆盖)
- 能力探测不能拍快照:cordis 激活是异步的,apply() 时排在后面的插件可能还没注册(详见 ARCHITECTURE 教训三)
- 依赖的服务要等:apply() 里要用 ctx.inject([...], cb) 等 webServer 就绪,否则 bridge 路由会静默不注册(详见教训四)
- headless 宿主可行:new Context() + ctx.provide(...) 就能装载本插件并打端点,不需要跑完整 DSH
- client 半 revision 由入口 mtimeMs/ctimeMs/大小派生 → 重建 + 刷新即生效,无需重建 Web 产物
- slot 注册:ctx.slots.inject(name, () => ctx.slots.register(meta, Component))
- 可用落点:conversation.view、tool.call.toolview、settings.section、sidebar.right.
- 子代理不能人工交互:ask_user_question 在子代理里直接返回 human interaction is unavailable while the calling agent is owned by another live agent
- 热重载工作:长期运行的配置档下改 cases/.yaml,无需重启即生效
- 改 host 半代码必须重启 DSH:plugin_manager 的 disable → enable 会重新 apply(),但不打破 Node 模块缓存(实测:重新 apply 后 testkit_run 仍是旧代码行为);最省事的验证是另起一个 headless 新进程
- team 通道真的通:TK-0027 经 agentTeams.spawnTeammate 创建 teammate,观察到 running → inactive 回落、child 会话产出 TESTKIT_OK,且该 teammate 会用 send_message 把结果回传给 Lead(实测于 2026-10-10,独立 headless 新进程)
- 当前 DSH 版本 0.2.0-rc.2,配置档 desktop

重构期新增的实测结论(详见读数与台账):

- 跨语言契约里不能出现 u64:ts-rs 会把它映射成 bigint,而 JSON.stringify(1n) 抛 TypeError —— 一个 u64 字段足以让整条协议链在运行期失效。已全部改 u32。
- cargo llvm-cov 的读数必须绑定「一跑完整产生的 profile 集」:默认 target 目录会复用陈旧 profraw(实测分母虚高到 10019,真实值 8732);且 stable 与 nightly 的读数不可比 (分支覆盖率只有 nightly 能测)。
- pnpm run gate 不是并发安全的:两个 gate 会互删 lib/,导致半个测试树报 ERR_MODULE_NOT_FOUND,而错误会指向别人的测试文件。已加并发守卫(检测到并发就 fail loud,而不是排队——排队会把「两人同时在验收」这件事藏起来)。
- 「分开跑都绿」≠「一起跑绿」,而且这一条有两层:跨 crate(-p vs --workspace)与同一 crate 内的编译单元(lib / lib test / 每个集成目标——clippy 按单元 fail-fast)。

风险台账(R1–R10 全部结案)见 ARCHITECTURE.md §9。

许可

MIT
预览分享卡片长什么样 ↓supengpeng/dsh-testkit
分享 · DeepSeek Harness Hub
supengpeng/dsh-testkit

场景驱动的 DSH 测试插件:把测试场景变成可声明、可复跑、可进 CI 的数据

回到顶部