| name | dsh-plugin-workflow |
| description | 改完 dsh 插件代码、准备提交或发布之前使用——上游到底要求什么、该跑哪些门禁、门禁缺失怎么识别、生成物哪些该入库、客户端半该用插槽还是 DOM 注入、提交前清单。流程属于这个工作区,不往插件仓库里塞 CI;跑门禁用 tools/check-plugin.py。 |
改动 → 门禁 → 提交
流程住在这个工作区。 插件仓库只声明它自己的门禁脚本;什么时候跑、跑哪些、
跑出什么算过关,是这里的事。不要往插件仓库里加 CI 或 hook。
根仓库只拥有共享流程;每个 dsh-*/ 都是独立插件仓库。先确认当前目录和目标仓库,
不要跨插件暂存,也不要把工作区外的 checkout 自动纳入任务。
python3 tools/check-plugin.py dsh-my-plugin
python3 tools/check-plugin.py dsh-my-plugin --conformance
python3 tools/check-plugin.py <repo> --skip build
先做版本门
python3 tools/harness_upstream.py status
python3 tools/check-harness-drift.py
插件锚定的 preview 与工作区锁不同时,先声明本次目标。升级不是改版本字符串:一次只迁移
一个插件,使用同版本文档/.d.ts,再运行完整门禁与 tarball 验证。
外部发布契约的三个核心面
权威是目标版本的
vendor/deepseek-harness/docs/user/develop/basic/publish.zh.md。一个外部插件必须有:
dsh.bundle manifest + cordis.patch.yml(否则装得上但不激活任何层)。
- npm/tarball 必须真的包含 manifest 指向的入口和运行资产;用
files、pnpm pack 与
publint 验证发布内容,而不是只验证工作区源码。
- 只有要支持 Git 安装时才必须提供自包含的
prepare:Git 拉的是源码,不会自动
运行名为 build 的脚本;同时用户必须显式 allowlist 该安装脚本。不能假设旁边有
monorepo checkout。
上游不要求 typecheck、不要求 lint、不要求覆盖率、不要求 CI。 文档举 turtle-ui 的
prepare 为范例时甚至明说它"直接转译 src/,不用项目引用,也不做类型检查"。
⚠️ docs/cookbook/adding-a-package.zh.md 那套 pnpm run constraints /
doc-sync / verify-type-equiv / private: true / 版本与根同步 —— 只适用于
harness 仓库内部的 @deepseek-ai/dsh-* 包(开头原文写明是"添加 workspace 包"),
它们靠的是上游仓库自己的 scripts/check-workspace-constraints.ts 之类。外部插件
既没有那些脚本也不该照抄那些不变量。别把内部清单当成外部义务。
oxlint / knip / jscpd / 100% 覆盖 / built-e2e 都是插件作者可选的更高标准,不是
上游对所有外部插件的一刀切义务。其中最贴近发布契约的是 publint(校验 files 与
exports 是否真能被消费)和基于打包产物的真实加载测试。
铁律一:不存在的门禁不等于通过的门禁
pnpm -r typecheck 遇到没有该脚本的包会静默跳过并整体报成功。同理,一个
runner 忽略了仓库声明的某个脚本,也是在制造同一种假象。
所以 check-plugin.py 把三件事分开报:通过 / 缺口(包没声明该门禁)/ 未纳入
(仓库声明了但没跑)。报告门禁结果时也照这么说,不要把跳过说成通过。
铁律二:先失败得便宜的
类型 → lint → 重复度 → 测试 → 构建 → 生成物校验(*:check)→ 打包卫生
构建必须排在生成物校验之前。 构建会重写产物,校验放前面就是在校验旧状态。
若仓库自己的 check 已经编码了顺序,直接执行它并审计实际包含哪些门禁,不再拆开
重排。
铁律三:生成物入库看"是不是交付物"
不是"是不是生成的"。
| 产物 | 入库? | 为什么 |
|---|
lib/ | 否 | 构建产物;prepare 在 install 时重建。入库只会让工作树永久脏 |
cordis.patch.yml(手写的) | 是 | 它就是组合包贡献的那一层,装的人要用 |
| 从 manifest 生成的 patch / 页面资源 | 是,但必须与源同步 | 是交付物,消费方直接吃它 |
~/.dsh 下的任何东西 | 不在仓库里 | 机器本地状态,见 dsh-local-verify |
配套:新建包时 .gitignore(node_modules/ + lib/)由脚手架带;漏了它,lib/
就会悄悄进版本库,而且因为 prepare 在每次 install 时重建,工作树会永久脏。
入库的生成物必须在构建之后重新生成再提交,否则它记录的是上一次构建的状态。
铁律四:客户端半优先用声明好的插槽,别直接改 DOM
优先注册进目标版本公开的 Client slot,并为对应 slot map 做声明合并;走插槽契约时,
上游调整内部 DOM 不会直接让插件静默失效。
只有在确实没有插槽可用时才往 shell 的 DOM 里注入。一旦这么做:
- 必须有 jsdom 测试,且同时覆盖新旧两种 DOM 形状。 上游改 DOM 不会通知你、也
不会报错,功能只是静默消失(rc.6 删掉
data-pane="sidebar" 并在侧边栏根节点
外包了 provider <div>,就这么让两个插件的侧边栏行无声失效)。
- 环境用文件头 docblock:
/** @vitest-environment jsdom */。
- 选择器写成跨版本的(
[data-pane="sidebar"], [class*="sidebarCol"]):CSS-module
类名带哈希后缀,但语义前缀稳定。
- 按类名子串全局找元素时先按容器收窄再全局兜底,否则可能命中别处的组件。
铁律五:修完 bug 要反向验证测试
新写的测试必须能抓到它声称抓的那个 bug。把修复打回原样,对应测试必须失败:
repair_tmp=$(mktemp -d)
cp src/x.ts "$repair_tmp/x.ts"
pnpm vitest run tests/x.test.ts
cp "$repair_tmp/x.ts" src/x.ts
pnpm vitest run tests/x.test.ts
在坏代码上也通过的测试比没有测试更糟 —— 它让人以为这里有保护。
提交前清单
harness_upstream.py status 和 check-harness-drift.py 已说明目标版本与漂移
python3 tools/check-plugin.py <repo> 全绿,且看清哪些是缺口、哪些未纳入
- 改了源码 → 重新构建,再重新生成任何入库的生成物
- 动了 DOM 依赖 → 有 jsdom 覆盖,且做过铁律五的反向验证
- 碰过
~/.dsh → 按 dsh-local-verify 准备过回滚,--dump-config 前后对比过
- 要发布 →
publint 过,且 pnpm pack 出来的 tarball 真装过一次
(见 dsh-bundle-and-profile)
git status 干净;只暂存这次改动涉及的文件(多份 WIP 并存时尤其重要)
- commit 按 conventional commits,正文说行为变化和为什么,不复述文件清单;
验证过什么就写出来
- 默认分支上先开分支再提交
跨引用
- 上游怎么规定的 →
dsh-docs-lookup
- 打包、发布、层序、
dsh.bundle / dsh.client → dsh-bundle-and-profile
- 改
~/.dsh、装机验证、回滚 → dsh-local-verify