1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
9.2 KiB
93-兼容判定收口:伞包版本 + prerelease 语义(2026-09-14 落地)
- 日期:2026-09-14 | 状态:✅ 已上线(平台后端已构建重启;服务器
ci.sh转绿) - 触发:用户原话 ——「能都把这些问题都解决,同步项目到仓库」(接档案 92 结尾上报的两个问题)
TL;DR ① "单测长期红"根本不是代码问题 —— 本机
48 通过 / 0 失败,是服务器源码落后于 git (服务器test/db.test.mjs还是旧版断言)⇒ 同步该文件后服务器 CI 立即转绿。 ② 伞包版本收口:@deepseek-ai/dsh不在自己的node_modules里 ⇒ 原查询对它恒判null, 被当作「平台没这个包,不算不兼容」而放行;收口到dsh-install.platformPackageVersion(), 闸门与列表共用同一份版本表。 ③ ⚠️ 收口带出一个必须一并修的副作用:闸门判据 A 用 semver 默认语义,而平台版本是 prerelease ⇒ 声明*(任意版本)都会被判成不兼容。实测抽样 300:real-newer 3 | narrow-pin 22 |**prerelease-artifact 117** | match 98—— 即闸门原本约 39% 假阳性 (dshmarket、dsh-context这些在平台上跑得好好的都在其中)。 只收口不修语义 = 让假拦截变多,所以同轮加prerelease 兜底:先按默认语义判, 不满足时再看includePrerelease—— 容忍下能满足的只计数不阻断。
一、问题①:单测"长期红"的真相(服务器源码落后)
| 事实 | 判据 |
|---|---|
| 本机单测 | node --test 五个文件 → 49 tests / 48 pass / 0 fail(1 skip) |
本机 test/db.test.mjs |
断言已是新口径:档案 87:条目口径从「互斥单选」改为「各自开关、可同时启用」⇒ 老断言整体改写,期望 every entry stays enabled(5 个) |
| 服务器同文件 | 仍是旧断言 exactly one key enabled,实际 5 !== 1 ⇒ 失败 |
| 服务器是否落后 | 用 git ls-files src test(60 个文件)做 LF 归一化后的 sha256 逐文件比对:只有 2 个文件不同 —— test/db.test.mjs(服务器旧)与 src/supervisor/orchestrator.ts(别人在途,见 §四) |
⇒ 修法 = 同步该文件(不是改代码)。同步后服务器 bash scripts/ci.sh → CI OK ✅(48 pass / 0 fail)。
📌 教训(本库已在
CODEBUDDY.md写过,这次又踩):「服务器能跑」≠「服务器源码 == git」。 这次的具体形态更隐蔽:你没改的东西,服务器就是旧版 —— 所以"某个单测红"未必是代码坏了, 先做一次逐文件哈希比对再动手。方法见 §五。
二、问题②:伞包版本收口(含"共用版本表")
病根
plugin-compat.platformPkgDir(pkg) = <dsh 包根>/node_modules/@deepseek-ai/<短名>,
而伞包 @deepseek-ai/dsh 不在自己的 node_modules 里 ⇒ platformVersion('@deepseek-ai/dsh') 恒 null
⇒ 判据 A 那句 if (v === null) continue // 平台没这个包:不算不兼容(插件自带) 把它放行。
代价(实测):官方目录里那 3 条"要求更高版本"的声明全部只写在伞包上
(dsh-zotero ^0.1.5-rc.2、dsh-any-background >=0.1.5-rc.2、dsh-md-notes >=0.1.5-rc.2 <0.1.6)
⇒ 一条都认不出来。
修法(单一来源)
- 新增
src/web/dsh-install.ts→export function platformPackageVersion(pkg): 伞包读<dsh 包根>/package.json,子包读<scope>/<短名>/package.json;进程内缓存;resetInstallPathsCache()一并清缓存。 - 消费方两处改为共用:
plugin-compat.platformVersion()与plugin-dsh-compat.platformVersionOf()⇒ 两处不再各写一份版本表(原先plugin-dsh-compat有自己的versionTable、plugin-compat有自己的versionCache)。
三、问题③(收口带出):闸门判据 A 的 prerelease 假阳性
为什么要一起修
只把伞包接进判据 A,会让声明 * 的插件第一次被拦(satisfies('0.1.5-rc.1', '*') 在默认语义下为 false)
—— 即"修好一处,制造一批新假拦截"。所以必须把语义一并处理。
实测分布(抽样 TOP300,@deepseek-ai/* 全量声明逐条判)
| 分类 | 数量 | 例 | 处置 |
|---|---|---|---|
match |
98 | dsh-vision-router |
满足 |
none |
60 | @linxin666/* |
没声明平台依赖 |
real-newer |
3 | dsh-zotero dsh-any-background dsh-md-notes |
真不兼容,必须拦 |
narrow-pin |
22 | dsh-univer-office @nanmicoder/dsh-agent-teams |
声明里只列了更旧具名版本;仍拦(有 trust 逃生口) |
prerelease-artifact |
117 | dshmarket dsh-context dsh-find-plugin |
平台效应,不该拦 |
修法
判据 A 逐条改成两步(plugin-compat.ts):
ok = semver.satisfies(v, range) // ① 默认语义(与 pnpm 一致)—— 保持原判据
if (!ok && semver.satisfies(v, range, { includePrerelease: true })) {
ok = true; prereleaseOnly = true // ② prerelease 兜底:**只计数不阻断**
}
CompatResult 新增 prereleaseOnly?: number —— 不计入 findings、不影响 level,只留可观测计数。
⚠️ 这一改放宽了上传闸门(少拦 ~39%),是本轮唯一一处"安全网变松"的改动, 依据是实测数据(那 117 条里包含本平台正在运行的
dshmarket);判据 B(平台包导出符号缺失) 未动,真正的 API 破坏仍会被拦。若认为该保持严格,把第二节的兜底删掉即可回退(锚点在verify-dsh-compat.mjs)。
四、⚠️ 未动(属别人 lane,仅上报)
src/supervisor/orchestrator.ts(在途):BASE_MEM_MB 160→448、MIN 384→512、MAX 1024→1536、dsh-plugin-mcn-suite 128→256(注释标注 2026-09-14、为 0.1.5 调);而poc/business-plugins/lib/client.js的对应常量还是旧值 ⇒ 本机verify-mem-model.mjs4 项红(它正是为「两份事实漂了」建的断言)。 我没有动任何一边:这组数字直接决定实例 cgroup 配额,是产品决策。 ⚠️ 注意:提交后仓库树两边都是旧值 ⇒ 该断言在 HEAD 上自然一致(红只出现在当前工作树)。- 文档库
CODEBUDDY.md/BRIEF.md/DEPLOY-本部署.md/skills/dsh-*/04-调整方案/{76,82}等 仍是别人的在途改动,本轮提交未包含。 - 文档库
scripts/docs-archive-index.py在 Windows 上会把INDEX.md的行尾 CR 翻倍 (见档案 91 §八 的完整记录)—— 该脚本是别人新增的,未改。
五、方法沉淀:判断"服务器源码是否等于 git"
# 本机:列出仓库里的源码文件,逐个算「LF 归一化后的 sha256」
git ls-files src test > files.txt
# 服务器:同一份清单,逐个算(tr -d '\r' 抹平行尾差异)
cd /opt/dshs && while read f; do [ -f "$f" ] && printf "%s %s\n" "$(tr -d '\r' <"$f" | sha256sum | cut -c1-16)" "$f"; done < files.txt
# 两边 sort 后 diff ⇒ 差异**精确到文件**(本轮 60 个文件里只 2 个不同)
⚠️ 两个坑:① 两侧都要行尾归一化(服务器 LF / 本机可能是 CRLF); ②
diff <(a) <(b)的进程替换在本机 Git bash 里不可用(/dev/fd不存在)⇒ 落临时文件再 diff。
六、验证
| 层 | 手段 | 结果 |
|---|---|---|
| 本机测试 | node --test 五文件 |
✅ 48 pass / 0 fail |
| 本机 verify | npm run verify |
⚠️ 仅剩 §四 的 4 项(别人在途),其余全绿 |
| 本机断言 | verify-platform-admin-section.mjs(本轮更新:旧断言断的是档案 91 已替换掉的 UI) |
✅ 全绿(含模拟点击两个新增入口 + 选中目录厂家) |
| 本机断言 | verify-dsh-compat.mjs(新增 4 条锚点:共用版本表 / 伞包单独解析 / prerelease 兜底 / 只计数不阻断) |
✅ 全绿 |
| 服务器 CI | bash scripts/ci.sh |
✅ CI OK(48 pass / 0 fail)—— 修前 1 项红 |
| 服务器功能 | 造临时插件目录真跑闸门 | ✅ 13 项全绿:伞包/子包"要求更高"→incompatible;*/^0.1.0-rc.7 → 不拦截且 prereleaseOnly 计数正确;满足→ok;未声明→unknown |
| 服务器接口 | 重启后复验白名单 | ✅ dshVersion=0.1.5-rc.1、默认不含 older、newer 2 条、dshCompat=all 时 older 出现 |
铺发:tar(LF)→ /opt/dshs → npm run build / bash scripts/ci.sh → systemctl restart dshs(重启后门户 200)。
备份:/opt/dsh/backups/pre-93-20260914-210458/(含 test/db.test.mjs / src/web/{dsh-install,plugin-compat,routes/whitelist}.ts / package.json / poc/business-plugins/{lib/client.js,package.json})。
七、回滚
- 后端:
cp -a /opt/dsh/backups/pre-93-*/覆盖回/opt/dshs/→npm run build→systemctl restart dshs。 - 单点回退"闸门放宽":删
plugin-compat.ts判据 A 里的 prerelease 兜底分支(verify-dsh-compat.mjs会立刻报警)。 - 无数据迁移、无 DB 变更。