DeepSeek Harness 为什么要七份 vitest 配置:测试分层怎么切的
把 deepseek-harness clone 下来,还没进 packages/,根目录先给你一排配置文件:五个 tsconfig*.json,七个 vitest*.ts。第一反应通常是「至于吗」。但如果你打算在这个仓库里改点东西,这排文件恰恰是你最先会撞上的东西——pnpm test 到底跑了哪一份、为什么你新写的 .stress.ts 一个都没被收进去、为什么 CI 说覆盖率不过而你本地绿的。
先把限定说在前面:截至我们采集的 2026-08-16,这个仓库建立于 2026-08-13,根 package.json 里 version 是 0.1.0-rc.5,GitHub 上一个 Release 都没有,README 的 Developer preview 一节原文写着未来会出现破坏兼容性的变更。下面提到的文件名、脚本名、常量值都是这个快照(47f9438)里的实读结果,随时可能变。我们没有安装、没有构建、也没有跑过其中任何一条测试通道,所有关于「gate 会怎么判」的说法都是文档与脚本源码的转述。
先把数目数清楚
标题里说的「四份 tsconfig」需要一句校准:仓库根目录实际有五份。tsconfig.json 是 solution 文件,它自己不参与编译——文件头注释(tsconfig.json:2-8)明写 files: [] 要保持 program-less,并且 NEVER add include/files entries,它只 reference 另外两份聚合配置。真正承担编译形状的是四份:tsconfig.base.json(280 行)、tsconfig.base.client.json(11 行)、tsconfig.host.json(299 行)、tsconfig.client.json(99 行)。
vitest 侧七个文件同理,其中 vitest.shared.ts(43 行)不是 lane,而是被其它配置引用的共享件,它导出 vitestExecArgv(vitest.shared.ts:9,按 --webstorage 是否可用决定要不要传 --no-webstorage)与 standardDecoratorPlugin()(:15)。所以准确说法是六条 lane 加一份共享件。
tsconfig:为什么必须切成 host 和 client 两半
这是整排配置里唯一一处「不这么切就不行」的地方,而且理由写在文件头。tsconfig.host.json:2-4 的注释说明 host 与 client 两侧会把 cordis 的 Context 合并到同一批 key 上,一个 program 没法同时看见两边。于是聚合被拆成 tsconfig.host.json(noEmit: true,见 :7)与 tsconfig.client.json(types: ["node"],见 :14;收 packages/client/*/tests 下的 .ts 与 .tsx,见 :20-22)两份,各自建各自的 program,再由 solution 文件把两者 reference 起来。
两份 base 是形状来源。tsconfig.base.json 装的是全局严格开关:strict(:19)、noUncheckedIndexedAccess(:20)、exactOptionalPropertyTypes(:21)、noImplicitOverride(:22)、noFallthroughCasesInSwitch(:23)、noUnusedLocals(:24)、noUnusedParameters(:25),加上 composite: true(:12)、moduleResolution: bundler(:8)、target: es2024(:6)。:30 起是 paths,把每个 workspace 名映射到它的 src。tsconfig.base.client.json 只负责客户端那点差异:jsx: react-jsx、lib: ["ES2024","DOM","DOM.Iterable"]、types: [](:6-10)。
规模感可以用 project references 的条目数看:我们用 Python 数 "path" 出现的行数,tsconfig.host.json 有 187 行、tsconfig.client.json 有 49 行。注意这是引用条目行数,不是包数——这个仓库 packages/*/* 下有 219 个 workspace 成员,别把两个数字混着用。
这里有个容易被忽略的耦合:tsconfig.base.json 的文件头注释(:2-4)说它同时兼任 vite-tsconfig-paths 的解析门面,vitest 配置就指向它。也就是说 paths 改一次,类型检查和测试解析是一起动的,不是两件事。
vitest:六条 lane 是按「花什么代价」切的
把六条 lane 排一遍,各自的定位取自它们自己的文件头注释:
| 配置文件 | 行数 | 文件头自述的定位 |
|---|---|---|
vitest.config.ts | 286 | 默认单测 lane,自带 scripts/coverage-uncovered-locations.cjs 报告器,打印每条未覆盖语句的 path:line:col(:9-13) |
vitest.e2e.config.ts | 59 | 真实 API lane,注释写它单列是因为「spends tokens」;无凭据自跳过(:5-8) |
vitest.snapshot.config.ts | 69 | 快照 lane |
vitest.web.config.ts | 36 | Web 浏览器 lane |
vitest.web.perf.config.ts | 16 | 手动高基数诊断,注释写它在 CI web gate 之外(:4-5) |
vitest.web-stress.config.ts | 16 | JSDoc 原文:Opt-in browser performance lane; no default Vitest config includes *.stress.ts.(:5) |
切分的轴不是「测什么」,而是「跑一次要付出什么」。e2e 花的是 token,所以它必须能在没凭据时自己跳过;web lane 需要浏览器;perf 与 stress 需要长时间且结论不稳定,所以干脆从默认 include 里拿掉——vitest.web-stress.config.ts 那句 JSDoc 说得很直白,没有任何默认配置会收 *.stress.ts,它的 include 只有 apps/web/stress-tests/**/*.stress.ts,并且 fileParallelism: false(:13)。这也解释了开头那个现象:你新建一个 .stress.ts 然后 pnpm test,它当然一个都不跑,因为设计上就不该被默认收进去。
几个写死在配置里的常量顺带记一下:vitest.e2e.config.ts:16 的 DEFAULT_E2E_MAX_WORKERS = 4,vitest.snapshot.config.ts:6 的 DEFAULT_SNAPSHOT_MAX_CONCURRENCY = 5,vitest.web.perf.config.ts:10-13 的 hookTimeout: 180_000 与 testTimeout: 600_000(include 是 apps/web/tests/**/*.perf.ts)。这些是配置里的默认值,不是「你跑起来会花多久」的承诺,也不能拿来推算任何执行时间或资源开销——我们没跑过其中任何一条。
vitest.web.config.ts:6-8 还记了一条环境约定:Linux 上的 PR CI 把 DSH_SNAPSHOT 钉成 replay,比对已提交的 golden 文件,record 与 refresh 只作为本地显式工作流。这意味着「本地重录一遍快照」和「CI 判定」是两个动作,别指望在 CI 上顺手刷新。
lane 的实际重量:按文件后缀数一遍
配置文件本身看不出各条 lane 有多重。按文件名后缀在这份快照里数:*.spec.ts 692 个、*.e2e.ts 129 个、*.snapshot.ts 19 个、*.stress.ts 1 个、*.perf.ts 1 个。
也就是说这套七文件的分层里,绝大部分重量压在默认 lane 上,末两条 lane 各自只有一个文件。为一个文件单开一份配置看着奢侈,但反过来想,它们各自的 include 与超时设定跟默认 lane 完全不兼容,混在一起才是麻烦。
入口在 npm scripts,判据在 AGENTS.md
这套分层对外的唯一入口是根 package.json 的 scripts:test(:34)、test:coverage(:35)、test:e2e(:36)、test:snapshot(:38)、test:snapshot:record(:39,DSH_SNAPSHOT=record)、test:snapshot:refresh(:40)、test:web(:42)、test:web:built(:44)、test:web:perf(:45)、test:web:stress(:47)、test:gui(:48)。整个根 package.json 有 123 条 scripts,其中 test 开头的 14 条、verify- 开头的 37 条、check 开头的 14 条——测试 lane 只是这套 gate 体系的一部分。
有一条最容易踩:AGENTS.md:92 明写 CI 的覆盖率 gate 是 test:coverage 而不是 test,AGENTS.md:65 进一步写这条 gate 的口径是对 packages/*/*/src 的 per-file 100%。本地跑 pnpm test 全绿、CI 仍然拦下,这两句就是原因所在。默认 lane 那个自带报告器会打印每条未覆盖语句的 path:line:col,正是为这条 gate 服务的。
顺带提醒,pre-push 钩子里只挂了一件事:lefthook.yml:52-55 的 pnpm run typecheck。测试不在 push 前跑。
想自己核一遍,动作是这样的
- 想知道某个测试文件归哪条 lane:直接看它的后缀,再回对应
vitest.*.config.ts的include。后缀对不上任何一条,就是没有 lane 会收它。 - 想知道类型错误来自哪一侧:看报错文件在
packages/client/*还是别处,分别对应tsconfig.client.json与tsconfig.host.json两个 program。 - 想改
paths:记住tsconfig.base.json同时被 vitest 侧引用,改完两边都要过。 - 在新克隆的仓库里跑之前:
apps/cli/reference/README.md:84写明pnpm run build必须在新克隆后单独跑一次,缺 Typert host 产物会以模块解析错误让 profile boot 失败且不给构建提示;同一处还写 launcher 不检查产物新鲜度,陈旧的 bundle 会照跑不误。
Windows 侧只能说到这里
本站读者用 Windows 的多,所以这条要写清楚:我们在仓库里能核到的 Windows 相关线索只有几处——根 package.json:58 的 check:windows-wine、:59-61 的三条 check:ci:windows-*、.github/workflows/ci.yml:661 的 runs-on: [self-hosted, dsh-win-ci, windows]、以及 apps/cli/tests/windows-shell.spec.ts 这个文件的存在。至于「六条 lane 里哪几条能在 Windows 上跑通」,我们没能在仓库里找到一处完整清单,所以这里不给结论。
另外值得记一笔的是这个仓库的 Node 门槛只在根 package.json:8-10 声明了一处("node": "^22.19.0 || >=24.0.0"),包管理器钉在 package.json:7 的 pnpm@11.7.0;而我们扫过的 230 份 packages/*/*、apps/*、vendor/* manifest 里,没有任何一份带 engines 字段,根包自己又是 private: true(package.json:5)。这两处只是并列陈述的实读结果,我们不推断原因。
值得抄走的是切分轴
七个 vitest 文件、五个 tsconfig 文件,摊开看是十二份配置,但真正的信息量只有两句:类型侧按 program 能不能共存来切,测试侧按每条 lane 花什么代价来切。前者是硬约束(cordis Context 的 key 合并),后者是取舍(token、浏览器、时长、稳定性各自单列)。
至于这种切法在别的项目里合不合适,我们没有依据评价——这份仓库的规模是 packages/*/* 下 219 个 workspace 成员、.ts 与 .tsx 合计 2578 个文件 566,700 行(截至 2026-08-16 的实读值),换一个体量结论未必成立。能确定的只有一点:在这个仓库里,配置文件的数量不是随手加出来的,每一份的文件头都写了它为什么单独存在,读之前先读注释比猜要快得多。
延伸阅读
- 从头读起:DeepSeek Harness 是什么:建仓三天、13 万 star 的 Agent 框架
- 本专题共 45 篇,完整分组目录见专题页
- DeepSeek Harness 中英文档怎么防漂:1078 组配对与 blob hash 校验
- DeepSeek Harness 安装与运行:npx dsh web 与从源码构建两条路
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
相关阅读
- npx @deepseek-ai/dsh web 是做什么的?默认端口 3080 与 Node 版本门槛
- DeepSeek Harness 的 terminal 工具:README 说 pty,代码是 terminals
- DeepSeek Harness 里的 TurnTrigger:文档还留着,packages 里搜不到
- Claude Agent SDK 与 OpenRouter Agent SDK 对照:循环放在哪一层、谁管工具谁管路由
- Cursor 里多个 Agent 同时跑:Agents Window、side chats 与多代理各解决什么
- 微软生成式 AI 入门课的 api_utils.py:它替你挡了哪些麻烦