dsh-scout 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -1
- package/README.en.md +140 -47
- package/README.md +140 -47
- package/dist/i18n.d.ts +157 -1
- package/dist/i18n.js +354 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +360 -88
- package/dist/model.d.ts +62 -9
- package/dist/model.js +655 -134
- package/docs/dsh-scout-product-contract.md +22 -1
- package/package.json +4 -1
- package/scripts/acceptance-report.mjs +622 -0
- package/scripts/check-release.mjs +228 -0
- package/scripts/profile-smoke.mjs +12 -15
- package/scripts/smoke-tool-call.mjs +43 -0
- package/tests/acceptance.test.mjs +118 -42
- package/tests/model.test.mjs +301 -69
- package/tests/package.test.mjs +572 -3
- package/tests/tools.test.mjs +382 -16
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,83 @@
|
|
|
4
4
|
|
|
5
5
|
本文件从 0.9.1 开始记录;0.9.0 及更早版本的变更见仓库的 git tag。
|
|
6
6
|
|
|
7
|
+
## [0.9.4] - 2026-11-07
|
|
8
|
+
|
|
9
|
+
**待发布**:本条目描述冻结为 0.9.4 的工作树;npm 元数据与发布 workflow 结果在发布后补记。
|
|
10
|
+
|
|
11
|
+
0.9.4 的主题是「在真实宿主里能用 + 报告让人读得下去」:修复桌面 App 下导出写不进会话工作区的 P0,补齐主体核验的字段级证据,并把报告从「按内部数据模型平铺」重构为「按决策阶段组织」的可读结构。
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **阶段式报告(v2)**:`report.md` 重排为 30 秒结论 → 公司与主体 → 已确认的事实(业务/资本/组织)→ 线索与待确认(面试确认/外部可查/说法冲突)→ 风险与红旗(按严重度)→ 面试问题(按主题,≤12)→ 距离下一步还差什么 → 附录 A 来源表 / B 技术台账 / C 调查覆盖度 / D 术语表。正文不再出现 E0–E3、source id 或内部枚举,改用人话标签与短来源名 + 采集日期;E 码只保留在附录术语表与台账。
|
|
16
|
+
- **主体核验分档与字段级 provenance**:`identityStatus` 增加 `corroborated`;`scout_verify_identity` 支持可选 `fieldSources`(逐字段来源,须为 `sourceIds` 子集)。E3 注册类来源支撑统一社会信用代码才算 `verified`,两个独立来源覆盖全部字段为 `corroborated`,报告与决策理由明确标注「非官方登记核验」。
|
|
17
|
+
- **来源元数据升级**:同一 URL 再次登记时,显式更强的 `evidenceLevel` / 不同 `sourceType` / 新标题会升级已有来源并记录 `source_updated` 事件(E3 升级仍受授权域约束)。
|
|
18
|
+
- **面试问题字段**:`scout_add_claim` / `scout_ingest` 支持可选 `interviewQuestion`(ingest 草稿还支持 `nextAction` / `confidenceNote`);只有显式问题与精选问题进入问题列表。
|
|
19
|
+
- **紧凑摘要与下一步**:搜索摘要显示新来源标题与 host,ingest 摘要区分「+N sources, +M claims」;空 case 的 `nextActions` 由状态合成(核验主体/登记来源/补必需维度)。
|
|
20
|
+
- **验收脚本**:新增 `scripts/acceptance-report.mjs` 与 `npm run test:acceptance`(受限文件系统 + 会话沙箱策略 + 完整 case + 工作区导出 + 冷启动回填,15 项断言)。
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- 面试问题不再由 `claim.nextAction` 生成(研究动作不再冒充面试问题);`corroborated` 的决策理由按「是否仍有未核验结论」分两种措辞。
|
|
25
|
+
- URL 去重归一化会剥掉 `utm_*` / `hmsr` 等追踪参数;来源类型推断新增 App Store → `company_official`、中文工商聚合站 → `public_database`。
|
|
26
|
+
- `scout_import` 校验来源/claim 的枚举与字段,非法等级不再被 `NaN` 比较放过;旧导出仍可导入。
|
|
27
|
+
- **zh-CN 报告字节基线随本轮报告重构更新**(0.9.3 起的「除 Coverage 外逐字节不变」在 0.9.4 明确升级为阶段式报告新基线;`tests/model.test.mjs` 的 golden 已同步)。
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **P0 · 桌面宿主导出失败**:导出 / `autoPersist` 未向受限 fs 后端传 per-call 沙箱策略,桌面 App 下写会话工作区被 `FS_SANDBOX_DENIED` 拒绝(只有临时目录可写)。现在从 `sandboxPolicy.resolve({session})` 取策略并作为 `writeText` 第 5 参数传入;失败时附可执行提示。
|
|
32
|
+
- `/scout export <caseId> [targetDir]` 冷启动不回填、`[targetDir]` 被静默忽略:现在与工具同一状态机并真正使用 targetDir。
|
|
33
|
+
- 冷启动后同名 `scout_start` 静默覆盖磁盘完成导出(数据丢失):现在默认拒绝,提示 `scout_import` / 换 id / `overwrite:true`。
|
|
34
|
+
- 同一 URL 二次登记静默丢弃显式元数据;重复 ingest 同一批 claim 导致台账翻倍。
|
|
35
|
+
- `caseId` 可含 `../`、公司名/岗位可为空;`fieldSources` 可引用 `sourceIds` 之外的来源。
|
|
36
|
+
|
|
37
|
+
### Known boundaries
|
|
38
|
+
|
|
39
|
+
- 兼容矩阵新增本轮实测宿主 **dsh 0.2.1-rc.5**;`engines.dsh` 范围通过不等于 fs 写语义通过,桌面安装后仍需人工复测一次默认 `scoutDir` 导出与冷启动回填。
|
|
40
|
+
- `corroborated` 只是决策可用的分档,不改变 E3 证据定义;PROCEED 仍要求所有非 informational claim 与三个必需岗位维度 verified(决策语义拆分留给 0.10.0)。
|
|
41
|
+
- 验收脚本是真实插件代码 + 真实 fs 沙箱契约 + 脚本驱动,不经过 LLM agent loop。
|
|
42
|
+
|
|
43
|
+
## [0.9.3] - 2026-10-13
|
|
44
|
+
|
|
45
|
+
**已发布**(2026-10-13):npm `dsh-scout@0.9.3`(`dist-tags.latest` = `0.9.3`),tarball `https://registry.npmjs.org/dsh-scout/-/dsh-scout-0.9.3.tgz`(**23 个文件**),shasum `45d3dc0d3b2e61840c8549bd8021798fae32b8c3`,integrity `sha512-x88Ar9Oi8RUK/PNtG/akox+MCJFBD2ajP95MwWGiodNtoDsz4Zsm4tx2BQU9KjChYq/YZnr1aWBl/uGYPWduqg==`,带 **Sigstore provenance**(透明日志 `https://search.sigstore.dev/?logIndex=3245268322`),由 GitHub Actions 以 `npm publish --provenance --access public` 发布;git tag `v0.9.3`(annotated,指向 `3634c2c`)与 GitHub Release `v0.9.3`(附件 `dsh-scout-0.9.3.tgz`,158,725 B)同批创建;发布 workflow run `38428072425` success(2026-10-13 16:08:46Z → 16:29:14Z,约 20 分 28 秒:四版本 release gate → `pnpm pack` → npm publish → GitHub Release)。发布前验收:`npm test` **134/134**、`test:profile` PASS(六步真实宿主链)、`pnpm pack` 23 个文件、`env -u DSH_BIN npm run check:release` **4/4 PASS**;独立验证 [verification-0.9.3.md](docs/verification-0.9.3.md)(仓库文档,不在 npm 包内)判定 **GO**。
|
|
46
|
+
|
|
47
|
+
**发布时移动过一次 tag,如实记录**:首次 tag `v0.9.3` 指向 `6b8b9ab`,release workflow run `38426795876` 在 14 秒内(job 15:59:47Z → 16:00:01Z)挂在 `Run the release gate`——验证套件 V4 派生一个子进程跑出厂 README 守卫,却把断言压在 spec reporter 的汇总行上;Node 的默认 test reporter 跨大版本不稳定(CI 的 Node 22 对管道子进程打印 TAP 的 `# fail 0`,本地 Node 26 打印 spec 的 `fail 0`),子进程两种情况下都 18/18 通过,断言只在 CI 用的 Node 22 上失败。**当时 `Pack the tarball` / `Publish to npm` / `Create GitHub Release with tarball` 全部 skipped,没有任何东西发出去。** 修复提交 `3634c2c`(子进程固定到 TAP reporter、断言改读 TAP 失败数与退出码、用 `process.execPath`、剥离 `NODE_OPTIONS` 里的 test-runner 旗标)后删除 tag 并重建,第二次 run `38428072425` 成功。这是本轮"本地绿、CI 红"的实例:0.9.3 的全部工作就是把静默漂移变成可见失败,而这次先被拦下的是发布流程自己。
|
|
48
|
+
|
|
49
|
+
0.9.3 的主题是**安装体验**,不加功能、不改运行时契约。缘起是一份在真实桌面 App 上完整走完安装的评审:README 的「三条已验证路径」对桌面 App 用户**全部不可用**——桌面版的 `desktop` profile 被 Electron 应用独占,CLI 对它的所有 `dsh plugin` 子命令都按 profile 名硬拒绝(`error: profile "desktop" is managed exclusively by the Electron application`,连兼容性逃生门 `allow-version` 一起拒绝),而桌面 App 恰恰是这类插件最主流的入口。本版把「App 内安装」补成主路径,并给安装补上可执行的判据。
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- **README 新增「路径 0:桌面 App」**(README.md / README.en.md 逐段同步):说明 `desktop` profile 的独占拒绝与「这是按 profile 名的硬拒绝、所有 `dsh plugin` 子命令都会命中」,给出**侧边栏 → 插件页**里的安装串 `dsh-scout@0.9.3`,并写明装完无需重启——实测 12 个 `scout_*` 工具即时注入当前会话。
|
|
54
|
+
- **README 新增「装上了吗(成功判据)」**:桌面**四条**判据,且**区分两个 GUI 面**——**侧边栏 → 插件页**的「已安装」里找到 `dsh-scout`、打开**详情页**看 `v0.9.3`(版本号只在这一页)→ **设置 → 内置插件 → 插件列表**看该行**已启用 + 运行中**(不是**已停用**/**启动失败**/**条件启用**;这一页不显示版本号)→ 新开一轮对话出现 12 个 `scout_*` 工具 → `/scout status <caseId>` 命中且输出末尾带 `· dsh-scout 0.9.3`;CLI 用 `dsh --profile <your-profile> --dump-config | grep -A2 'dsh-scout'`,并点明「组合进去 ≠ 运行时挂上了」,最可靠的判据始终是那 12 个工具与 `/scout`。
|
|
55
|
+
- **README 新增「卸载与升级」**:CLI 的 `add dsh-scout@<new-version>` / `remove dsh-scout` 与桌面 App 的**侧边栏 → 插件页**两条写法,并说明安装失败会回滚 profile 清单与 lockfile。
|
|
56
|
+
- **README 补 `compatibility.json` 手动豁免路径**(评审 P2-3):桌面用户拿不到 `allow-version`,文档给出 profile 目录下该文件的平铺 JSON 写法(精确 `package@version` → 精确 DSH 版本数组,示例 `{"dsh-scout@0.9.3": ["0.2.0-rc.2"]}`),并复述同一组边界、`--accept-risk` 风险确认与「写法不合规的记录被忽略并告警」的读取行为。
|
|
57
|
+
- **门禁守护 README 的版本引用**(评审 P2-4):README / README.en.md 里**活的**版本引用——`dsh-scout-<version>.tgz`、安装命令里的 `dsh-scout@<version>` 操作数、`compatibility.json` 的 `"dsh-scout@<version>"` 键、git ref `#v<version>`——必须等于 `package.json` 的 version。守卫同时落在 `tests/package.test.mjs`(每次 `npm test`、CI,毫秒级)与 `scripts/check-release.mjs` 最前面(独立第二实现,README 过期时不必先跑完整套单测与约两分钟的四版本矩阵)。历史提及(实测矩阵、路线图、发布叙述)按设计不在守卫范围。
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- **安装章节上移到「快速上手」之后**,三条 CLI 路径降级为「CLI / 自建 profile 用户适用」;`scout-demo` 这类**虚构** profile 名改为显式占位符 `<your-profile>`,附「怎么知道我的 profile 名」(没有 `dsh profile list` 子命令,直接 `ls ~/.dsh/profiles`)与「照抄示例名不会报错、只会把插件静默装进另一个 profile」的陷阱说明。
|
|
62
|
+
- **git 安装路径保留但降级**到「其他安装方式与取舍」表,标注**不推荐**:仓库不含 `dist/`,`prepare` 会跑 `tsc` 并安装 devDependencies,是三条路径里唯一会执行代码的;只保留「需要未发布提交时」的理由。示例 ref 由不可执行的 `#<commit>` 改为**真实 tag**(`github:MaxHou-infinity/dsh-scout#v0.9.3`;仓库自 `v0.3.0` 起共 10 个 tag,`v0.9.1` 是本地迭代版、未打 tag——评审「git tag 为空」的判断与事实不符)。
|
|
63
|
+
- **主安装命令显式锁版本**(`dsh-scout@0.9.3`,不再是裸包名):pnpm ≥ 11 的最小发布年龄(minimum release age)保护会让刚发布的版本被静默跳过、回退到旧版;该说明从「注意事项」降为路径 0 下的一句注解,现象 / 原因 / `minimumReleaseAgeExclude` 特征全部保留。
|
|
64
|
+
- **Node 要求的适用范围**:「Node ≥ 22.19」只对 CLI / 门禁路径成立;桌面 App 自带 node / pnpm 运行时(实测 node 24.21.0 + pnpm 11.7.0),无需安装 Node。
|
|
65
|
+
- **桌面场景建议绝对 `scoutDir`**:默认 `./dsh-scout` 依赖 cwd,桌面会话的 cwd 语义与 CLI 不同;文档建议在 `~/.dsh/profiles/desktop/cordis.patch.yml` 里给 `id: dsh-scout` 配绝对路径,并提示该文件由 App 维护、手改前先退出 App。
|
|
66
|
+
- **`/scout status <caseId>` 的输出尾部新增 `· dsh-scout 0.9.3`**:纯追加,原有前缀逐字节不变,`status` 之外的子动作不受影响。这是 0.9.3 唯一的**用户可见输出变化**,用途是让桌面用户自证「运行时真正加载的是哪个版本」——桌面既看不到 stderr、也没有运行日志。版本号取自源码常量 `PLUGIN_VERSION`(`src/index.ts`),随发布手动 bump,不从 `package.json` 运行时读取。
|
|
67
|
+
- **加载横幅(评审 §5.3)不做,改为让状态在两个 GUI 面里可读**:取证显示桌面 App 的 stderr 不可见——Electron 主进程的 fd0/fd1/fd2 全部指向 `/dev/null`(host 进程的 fd1/fd2 是通向主进程的管道,主进程再 pipe 到 `/dev/null`),`~/Library/Logs/DeepSeek Harness/` 为空目录、全盘没有 DSH 运行日志,`ctx.logger()` 也没有落盘出口(cordis 只有内存 ring buffer exporter,其 `startupLogs` 只在 `StartupError` 分支挂上)——加一行加载横幅**没有用户看得到**。落点改为文档与验收 checklist 写清 GUI 里**两个各答一个问题**的面:① **侧边栏 → 插件页**(`dsh-client-ui-plugin-manager`)是**管理面**,回答安装 / 启用 / 移除与**装的是哪版**(**详情页**标题旁的 `v{version}`,来自 `BundleInfo.version`;列表卡片只有「实验性 / 异常」状态标签);② **设置 → 内置插件 → 插件列表**(`dsh-client-ui-settings-plugin-inventory`)是**只读检查面**,回答**装没装 / 为什么没生效**——按「会话插件 / 全局插件」分组,用**已启用 / 已停用 / 启动失败 / 条件启用**标签与**运行中 / 启动失败 / 等待依赖 / 未运行**运行状态说话,**不显示版本号**(wire 行只有 `{ entryId, moduleName, meta?, enabled, fiberPhase }`)。关键依据:启动时被禁用的插件行(peer 版本不匹配是其中一种)已被投影到这一页的「已停用」——宿主侧就是 `enabled: !entry.disabled`(`dsh-host-plugin-inventory/lib/index.js:131`)——所以那行被丢进 `/dev/null` 的 stderr,答案早就在 GUI 里;**插件被 disabled 时它自己不可能输出任何东西**,这正是「没有任何报错」在桌面必须去 GUI 查的理由。
|
|
68
|
+
|
|
69
|
+
### Docs
|
|
70
|
+
|
|
71
|
+
- **新增 `docs/install-verification.md`**:把桌面 App 安装固化成可执行的手工验收 checklist(Lane D)——确认运行时版本 → 确认兼容性 → App 内安装 → 生效验证(**侧边栏 → 插件页**看「已安装 + `v0.9.3`」,**设置 → 内置插件 → 插件列表**看「已启用 + 运行中」,辅以 bundle installed+enabled / loader 行 fiber active / 插件总数变化)→ 功能验证(12 个 `scout_*` 工具即时注入、`scout_start` + `scout_report` 真实调用、`/scout status` 输出带版本)——并写明它与现有 CLI 门禁(Lane C:`npm run test:profile` 的六步链)的差异、自动化门禁为什么覆盖不到 Electron 独占 profile、每条 lane 的触发时机,以及一条**如实标注的残差**:GUI 两页只做过源码取证 + profile 组合实测,**没有 GUI 活体验证**,判据标为「待人工点一次确认」。仓库内文档,不在 npm 包内。
|
|
72
|
+
- **安装评审原件入库**:`docs/dsh-scout-install-ux-review.md`(桌面 App 上的完整实测记录),仓库内文档,不在 npm 包内。
|
|
73
|
+
- 产品契约的安装 / 分发段落与 README 的新安装模型对齐(桌面 App 为主路径),并把 `compatibility.json` 的手动豁免路径记进契约的兼容性段落。
|
|
74
|
+
|
|
75
|
+
### Known boundaries
|
|
76
|
+
|
|
77
|
+
- **桌面 App 的 `desktop` profile 只能由 App 管理**:CLI 对它的 `add` / `list` / `allow-version` 等所有子命令都按 profile 名拒绝。这是 DSH 参数解析层的硬编码判断(`rejectElectronProfile`),不是权限或配置问题;0.9.3 不改 DSH,只在文档里如实说明,并给出**侧边栏 → 插件页**安装与手动豁免两条替代路径。
|
|
78
|
+
- **README 版本守卫只覆盖「活的」引用**:历史版本串(实测矩阵里的 `dsh-scout@0.9.2`、路线图条目、发布叙述)按设计不参与断言,所以它防的是「忘改安装串」,不是「历史叙述过期」。
|
|
79
|
+
|
|
7
80
|
## [0.9.2] - 2026-10-13
|
|
8
81
|
|
|
82
|
+
**已发布**(2026-10-13):npm `dsh-scout@0.9.2`(`dist-tags.latest` = `0.9.2`),tarball `https://registry.npmjs.org/dsh-scout/-/dsh-scout-0.9.2.tgz`(**23 个文件**),shasum `bedb4a656709d5d0e77d5f4b410e2173c0b8eb31`,integrity `sha512-UIJOP8KalLqiEBssFB3+sw+cP1+R78nNZzwA/Wg5gT1o/NLOBgWccURdgbqk4fNdJwGKYVyNvbRuy4RZYrusDw==`,带 **Sigstore provenance**(透明日志 `https://search.sigstore.dev/?logIndex=3244828180`),由 GitHub Actions 以 `npm publish --provenance --access public` 发布;git tag `v0.9.2`(annotated,指向 `64138a0`)与 GitHub Release `v0.9.2`(附件 `dsh-scout-0.9.2.tgz`,130,691 B)同批创建;发布 workflow run `38406696598` success(16m59s:四版本 release gate → `pnpm pack` → npm publish → GitHub Release)。发布前验收:`npm test` **118/118**、`test:profile` PASS(六步真实宿主链)、`pnpm pack --dry-run` 干净、`env -u DSH_BIN npm run check:release` **4/4 PASS**;独立验证 [verification-0.9.2.md](docs/verification-0.9.2.md)(仓库文档,不在 npm 包内)判定 **GO**,无新缺陷。
|
|
83
|
+
|
|
9
84
|
0.9.2 是 0.9.1 的收口版本:把 0.9.1 验证报告点名的最大盲区(**U11**)补成真实宿主门禁,清掉三轮独立验证留下的文档税,并关闭 0.9.1 的两个已知边界(**D10**、**O10**)。
|
|
10
85
|
|
|
11
86
|
### Added
|
|
@@ -19,7 +94,7 @@
|
|
|
19
94
|
|
|
20
95
|
### Fixed
|
|
21
96
|
|
|
22
|
-
- **render 整串上限改为由省略号长度推导**(原 O10):0.9.1 的裁切预算是固定余量 `MAX_RENDER_BYTES - 8
|
|
97
|
+
- **render 整串上限改为由省略号长度推导**(原 O10):0.9.1 的裁切预算是固定余量 `MAX_RENDER_BYTES - 8`(1016 B)——裁到 ≤1015 B 再追加 3 B 省略号,含省略号的**精确上界是 1018 B**(实测最坏 1018 B),距 1024 B 预算余 **6 B**;余量只剩 1 B(调用点写 `MAX_RENDER_BYTES - 1`)时上界变成 1025 B,就会越界。现在省略号自身的 UTF-8 长度先从预算里扣掉、再按码元裁切,整串(含省略号)**不超过 1024 B**;对抗性测试把 ASCII 输入推到整整 1024 B,并对 CJK 与 emoji(代理对、UTF-8 变长)断言不超过预算。
|
|
23
98
|
|
|
24
99
|
### Docs
|
|
25
100
|
|
package/README.en.md
CHANGED
|
@@ -103,81 +103,118 @@ The generated report contains:
|
|
|
103
103
|
- **Sources**: URL-linked source list
|
|
104
104
|
- **Interview questions**: suggested questions (untouched dimensions come first)
|
|
105
105
|
|
|
106
|
-
##
|
|
106
|
+
## Install
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|---|---|
|
|
110
|
-
| `scout_start` | Create a due-diligence case |
|
|
111
|
-
| `scout_add_source` | Register a source (with evidence level) |
|
|
112
|
-
| `scout_ingest` | Batch-register collected results (infers source type/evidence level; can draft one claim per item) |
|
|
113
|
-
| `scout_search` | Search through the DSH Web Provider and auto-register result sources (type/evidence inferred per URL) |
|
|
114
|
-
| `scout_add_claim` | Add an evidence-bounded claim |
|
|
115
|
-
| `scout_verify_identity` | Verify the legal entity from an E3 official source |
|
|
116
|
-
| `scout_verify_claim` | Promote a claim with stronger evidence |
|
|
117
|
-
| `scout_report` | Render the current Markdown report |
|
|
118
|
-
| `scout_questions` | Derive a deduplicated, prioritized interview question list (up to 12; case-curated questions keep their quota) |
|
|
119
|
-
| `scout_compare` | Side-by-side comparison of 2–5 cases (decisions/identity/claims/risks/questions) |
|
|
120
|
-
| `scout_export` | Persist the five-file export + completion marker |
|
|
121
|
-
| `scout_import` | Restore a case from an export directory and recompute its decision (tolerates legacy exports, reports `complete`) |
|
|
108
|
+
First decide where you run it — the two entry points install differently, and they must not be mixed:
|
|
122
109
|
|
|
123
|
-
|
|
110
|
+
| Where you run | Target profile | How to install |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| **Desktop app (DeepSeek Harness)** | `desktop` | **In-app install** (the only path — see below) |
|
|
113
|
+
| CLI / headless / your own profile | your profile name | `dsh plugin --profile <your-profile> add …` |
|
|
124
114
|
|
|
125
|
-
|
|
115
|
+
### Path 0: desktop app (recommended)
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
- `render` is a **compact summary that never exceeds 1 KB as a whole**: decision + this call's delta + unresolved blockers + next steps. The cap is the render budget itself (`MAX_RENDER_BYTES = 1024` B): the ellipsis's own UTF-8 length is subtracted from the budget before cutting, so **the final string including the ellipsis never exceeds 1024 B**;
|
|
129
|
-
- `persist: { persisted, targetDir, error? }` appears **only on the `autoPersist` path of mutating tools** (the key is omitted when no persist was attempted). **A failed persist is no longer silent**: `persist.persisted === false` plus `persist.error`, and no `case_exported` event is recorded. `scout_export` carries **no `persist` key** — it expresses the same thing through **top-level** `persisted` / `complete` / `error`;
|
|
130
|
-
- **Full data still comes from `scout_report` (Markdown) and `scout_export` (files)** — the model sees a summary, not the whole ledger.
|
|
117
|
+
The desktop `desktop` profile is owned exclusively by the Electron application, so the CLI cannot manage it — `dsh plugin --profile desktop list` and `add` are both refused:
|
|
131
118
|
|
|
132
|
-
|
|
119
|
+
```text
|
|
120
|
+
error: profile "desktop" is managed exclusively by the Electron application
|
|
121
|
+
```
|
|
133
122
|
|
|
134
|
-
|
|
123
|
+
That is a hard refusal **by profile name**, so **every** `dsh plugin` subcommand against it is rejected — including `allow-version` in the compatibility section below. Install from the **sidebar → Plugins page** (install, enable, and remove all live there) instead:
|
|
135
124
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
| `scout` Skill | The due-diligence workflow registered by the plugin itself (`ctx.skills.register`, no files, no client bundle): start → search/ingest → claim → verify_identity → report → questions, plus the four evidence disciplines; it disappears on unload |
|
|
125
|
+
```sh
|
|
126
|
+
dsh-scout@0.9.4
|
|
127
|
+
```
|
|
140
128
|
|
|
141
|
-
|
|
129
|
+
> Always pin the exact version. pnpm ≥ 11 enables minimum-release-age supply-chain protection by default, so a bare package name silently skips a just-released version and falls back to an older one (installing right after 0.9.2 shipped resolved to 0.9.0); with an exact version pnpm records it under the profile's `minimumReleaseAgeExclude` (it prints `Added 1 entry to …`).
|
|
142
130
|
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
No restart is needed: the 12 `scout_*` tools are injected into the current session. A desktop session's cwd differs from the CLI's, so give `scoutDir` an absolute path in the profile's `cordis.patch.yml` (see "Configuration" below).
|
|
132
|
+
|
|
133
|
+
### Path 1: CLI (npm, recommended)
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
# Replace <your-profile> with your own profile name — do not copy the example verbatim
|
|
137
|
+
dsh plugin --profile <your-profile> add dsh-scout@0.9.4
|
|
145
138
|
```
|
|
146
139
|
|
|
147
|
-
|
|
140
|
+
<details>
|
|
141
|
+
<summary>How do I find my profile name?</summary>
|
|
148
142
|
|
|
149
|
-
|
|
143
|
+
There is no `dsh profile list` subcommand (`profile` is parsed as a profile name and errors), so read the directory:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
ls ~/.dsh/profiles # or $DSH_HOME/profiles
|
|
147
|
+
```
|
|
150
148
|
|
|
151
|
-
|
|
149
|
+
Every name it lists is a usable profile (e.g. `web`, or one you created). `desktop` is app-owned: any `dsh plugin` subcommand against it is refused.
|
|
150
|
+
</details>
|
|
151
|
+
|
|
152
|
+
**Do not copy an example name** (such as `scout-demo`) verbatim: the CLI creates and uses it without any error, so the plugin lands in a different profile and appears not to work.
|
|
153
|
+
|
|
154
|
+
### Path 2: local tarball (offline / reproducible)
|
|
152
155
|
|
|
153
156
|
```sh
|
|
154
|
-
#
|
|
155
|
-
dsh plugin --profile
|
|
157
|
+
pnpm pack # produces dsh-scout-0.9.4.tgz
|
|
158
|
+
dsh plugin --profile <your-profile> add "file:$PWD/dsh-scout-0.9.4.tgz"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Other install sources, and the trade-off
|
|
162
|
+
|
|
163
|
+
| Source | Ships build output | Runs install scripts | Recommendation |
|
|
164
|
+
|---|---|---|---|
|
|
165
|
+
| npm (path 1) | yes, ships `dist/` | no (`pendingBuilds: []` measured) | **recommended** |
|
|
166
|
+
| local tarball (path 2) | yes (the `pnpm pack` output) | no | offline / self-test |
|
|
167
|
+
| git: `github:MaxHou-infinity/dsh-scout#v0.9.4` | no (`dist/` is not committed) | yes — `prepare` runs `tsc` and installs devDependencies | **not recommended**; only for unreleased commits |
|
|
168
|
+
|
|
169
|
+
The git ref must be a **real tag or commit**: the repository has 11 tags since `v0.3.0` (`v0.3.0` … `v0.9.3`; `v0.9.1` was a local iteration and was never tagged), and this release creates `v0.9.4`. Git is also the only path that executes code: `dist/` is not committed, so it always builds locally, and pnpm may require an explicit `allowBuilds` entry for `dsh-scout` (only allow a pinned version you have reviewed).
|
|
156
170
|
|
|
157
|
-
|
|
158
|
-
pnpm pack # produces dsh-scout-0.9.2.tgz
|
|
159
|
-
dsh plugin --profile scout-demo add "file:$PWD/dsh-scout-0.9.2.tgz"
|
|
171
|
+
> The desktop app bundles its own node / pnpm runtime (measured: node 24.21.0 + pnpm 11.7.0), so you do **not** need Node installed; the "Node ≥ 22.19" requirement applies to the CLI / gate paths only.
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
### Did it install? (success criteria)
|
|
174
|
+
|
|
175
|
+
**Desktop app**
|
|
176
|
+
|
|
177
|
+
1. **Sidebar → Plugins page**: `dsh-scout` appears under Installed; **open its detail page** — the title carries a `v0.9.4` tag, and the version lives only on this page (list cards carry only the Experimental / Problem status tags);
|
|
178
|
+
2. **Settings → Built-in plugins → Plugin list**: the `dsh-scout` row carries the **Enabled** tag and the **Running** status — not **Disabled** / **Failed** / **Conditional** (this page shows **no version**);
|
|
179
|
+
3. start a new turn — 12 `scout_*` tools appear in the tool list;
|
|
180
|
+
4. `/scout status <caseId>` resolves and its output ends with the plugin version (`· dsh-scout 0.9.4`) — with no stderr or logs on desktop, this is the surface that proves which version actually loaded.
|
|
181
|
+
|
|
182
|
+
**CLI**
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
# the composed tree should contain the dsh-scout layer
|
|
186
|
+
dsh --profile <your-profile> --dump-config | grep -A2 'dsh-scout'
|
|
163
187
|
```
|
|
164
188
|
|
|
165
|
-
|
|
189
|
+
Being composed is **not** the same as being mounted at runtime: the reliable signal is always whether the 12 `scout_*` tools show up and whether `/scout` resolves.
|
|
190
|
+
|
|
191
|
+
On a runtime mismatch the CLI disables the plugin row and writes exactly one stderr line, `dsh: disabling profile plugin <row>: <reason>` — what you see is "the plugin isn't working", not a loud red error. **The desktop app has no visible stderr** (measured: the Electron main process points fd0/fd1/fd2 at `/dev/null`, `~/Library/Logs/DeepSeek Harness/` is empty, and there is no DSH runtime log anywhere), so on desktop the only place to look is the GUI: **Settings → Built-in plugins → Plugin list** groups rows into Session plugins / Global plugins and answers "is it installed, and why isn't it working?" with the **Enabled / Disabled / Failed / Conditional** tags and the **Running / Failed to start / Waiting for dependencies / Not running** statuses (this page shows **no version**) — a row disabled at startup (a peer mismatch is one cause) shows up there as **disabled**, because the host projects it as `enabled: !entry.disabled`; the stderr line thrown into `/dev/null` already has its answer in the GUI. For which version is installed, open the plugin's detail page on the **sidebar → Plugins page** and read the `v{version}` tag.
|
|
192
|
+
|
|
193
|
+
### Upgrade and uninstall
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
dsh plugin --profile <your-profile> add dsh-scout@<new-version> # upgrade: target version, also pinned
|
|
197
|
+
dsh plugin --profile <your-profile> remove dsh-scout # uninstall
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Use the **sidebar → Plugins page** on desktop to upgrade / remove. A failed install rolls back the profile manifest and lockfile.
|
|
166
201
|
|
|
167
202
|
### DSH compatibility
|
|
168
203
|
|
|
169
|
-
|
|
204
|
+
The following table records **historical DSH 0.2.0-rc.2 behavior**. As of 2026-10-29, the [current official rules](https://deepseek-harness.github.io/deepseek-harness/develop/basic/publish) prioritize `engines.dsh`, falling back to DSH peer ranges only when it is absent. This plugin declares the same range in both places.
|
|
205
|
+
|
|
206
|
+
That older runtime has three compatibility layers:
|
|
170
207
|
|
|
171
208
|
| Layer | Where | Who enforces it | What happens on mismatch |
|
|
172
209
|
|---|---|---|---|
|
|
173
|
-
| **Declaration** | `package.json` → `engines.dsh` (`>=0.1.0-rc.6 <0.3.0-0`) | **
|
|
210
|
+
| **Declaration** | `package.json` → `engines.dsh` (`>=0.1.0-rc.6 <0.3.0-0`) | **That version does not read it** (the DSH 0.2.0-rc.2 runtime never reads `engines.dsh`; the official manifest comment says *"DSH compatibility is declarative until a reader enforces it"*) | Expresses author intent only; blocks nothing |
|
|
174
211
|
| **Enforced** | The ranges in `peerDependencies` whose names are `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` (this repo has exactly one: `@deepseek-ai/dsh-tools: ">=0.1.0-rc.6 <0.3.0-0"`) | DSH install checks + the profile startup preflight `evaluatePluginCompatibility()` | Install is refused, or the plugin row is disabled at startup |
|
|
175
212
|
| **Not enforced** | `@deepseek-ai/cordis: ">=4.0.2 <5"` (and `@deepseek-ai/schemastery`) | DSH's peer name filter only accepts `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` | Resolved by pnpm; DSH makes no compatibility judgement |
|
|
176
213
|
|
|
177
214
|
Three details that matter:
|
|
178
215
|
|
|
179
216
|
1. **Prereleases participate in matching.** The check is `semver.satisfies(runtime, range, { includePrerelease: true })`. The `<0.3.0-0` upper bound exists precisely to exclude `0.3.0-alpha` and later *while* prereleases are included; conversely, **the range depends on that flag** — without it, `0.2.0-rc.2` would be judged incompatible. **When checking this range with other tooling, pass `includePrerelease`**: under default semver semantics (node-semver's prerelease rule accepts a prerelease only through a comparator with the same major.minor.patch tuple), `0.1.2-rc.1` / `0.2.0-rc.2` / `0.2.1-alpha.2` are all judged **unsatisfied** (measured with semver 7.8.5: `0.1.0-rc.6` is true under both semantics; the other three are plain=false / incl=true; `0.3.0-alpha.1` is false under both). **DSH's verdict is the one that counts.**
|
|
180
|
-
2. **The failure mode is "silently not loaded", not an error.** Specs that can be judged up front (adding a registry package) are refused before pnpm runs, with the exemption command printed. Git / tarball specs are judged after installation, and a failure rolls back the profile manifest and lockfile. But a plugin **already in the profile whose mismatch is only discovered at startup** has its row disabled: the process writes exactly one stderr line, `dsh: disabling profile plugin <row>: <reason>`. What the user sees is "the plugin isn't working" (no tools, no `/scout`) — not a loud red error.
|
|
217
|
+
2. **The failure mode is "silently not loaded", not an error.** Specs that can be judged up front (adding a registry package) are refused before pnpm runs, with the exemption command printed. Git / tarball specs are judged after installation, and a failure rolls back the profile manifest and lockfile. But a plugin **already in the profile whose mismatch is only discovered at startup** has its row disabled: the process writes exactly one stderr line, `dsh: disabling profile plugin <row>: <reason>`. What the user sees is "the plugin isn't working" (no tools, no `/scout`) — not a loud red error. **That line is visible to CLI users only**; the desktop app's stderr goes to `/dev/null` and there is no runtime log (see "Did it install?" above), so on desktop the state is only visible in **Settings → Built-in plugins → Plugin list** (the installed version, in turn, only on the **sidebar → Plugins page**).
|
|
181
218
|
3. **That single `@deepseek-ai/dsh-tools` peer carries the entire gate.** Changing the peer range changes the compatibility promise.
|
|
182
219
|
|
|
183
220
|
#### Escape hatch: profile-level exact-version exemptions
|
|
@@ -199,27 +236,82 @@ The exemption lives in the profile's `compatibility.json`, beside `package.json`
|
|
|
199
236
|
|
|
200
237
|
Granting requires explicit risk acceptance (CLI `--accept-risk`, plugin manager `acceptRisk: true`); DSH prints a risk warning before writing — an incompatible plugin may crash or corrupt data.
|
|
201
238
|
|
|
239
|
+
**Desktop app users**: `allow-version` is refused by profile name like every other `dsh plugin` subcommand, so this file must be written by hand. It is a flat JSON object — exact `package@version` → the exact DSH versions it is allowed on; with a desktop runtime of `0.2.0-rc.2`:
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"dsh-scout@0.9.4": ["0.2.1-rc.5"]
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
A hand-written grant is bound by the same four boundaries above (exact versions only, one package version paired with one DSH version, no authorization of install scripts, effective on the next composition) and it **bypasses the CLI's `--accept-risk` acknowledgement** — an incompatible plugin may crash or corrupt data. Read the exact runtime version from `desktopVersion` in `~/.dsh/dsh-runtimes/*/runtime.json`; a malformed record is ignored with a warning and never makes the profile unusable.
|
|
248
|
+
|
|
202
249
|
#### Verified version matrix
|
|
203
250
|
|
|
204
|
-
|
|
251
|
+
**`dsh-scout@0.9.4` passed on four exact DSH versions** (before the release, 2026-11-07, isolated profiles, all four binaries fetched from npm rather than the local DSH): covering installation, cold startup, tool mounting, the **six-step real-host gate** (`scout_start` → export → cold restart → lazy rehydration → cross-language import → uninstall, plus the "marker missing / `complete:false`" negative controls), and unload. The full gate runs through `npm run check:release`; measured output:
|
|
205
252
|
|
|
206
253
|
```text
|
|
207
254
|
dsh-scout release gate: PASS (4 DSH versions, >=0.1.0-rc.6 <0.3.0-0)
|
|
208
255
|
```
|
|
209
256
|
|
|
210
|
-
| DSH version | npm channel (checked 2026-10-12) | 0.9.
|
|
257
|
+
| DSH version | npm channel (checked 2026-10-12) | 0.9.4 (candidate tarball) |
|
|
211
258
|
| --- | --- | --- |
|
|
212
259
|
| `0.1.0-rc.6` | Declared lower bound | Pass |
|
|
213
260
|
| `0.1.2-rc.1` | Older release regression | Pass |
|
|
214
261
|
| `0.2.0-rc.2` | `latest` / `next` | Pass |
|
|
215
262
|
| `0.2.1-alpha.2` | `alpha` | Pass |
|
|
216
263
|
|
|
217
|
-
This table previously recorded the **0.9.0** and **0.9.
|
|
264
|
+
This table previously recorded the **0.9.0**, **0.9.1**, and **0.9.3** candidate/released results; 0.9.4 keeps the identical `peerDependencies` and `engines.dsh` ranges (**unchanged**) — this version changes the runtime fs sandbox seam, the stage-based report, and the evidence tiers — and it passed the same gate again on the same four exact versions (4/4), so the table is updated to this version. The previous release, **0.9.3, is published on npm** (`dist-tags.latest` = `0.9.3`): the release workflow (run `38428072425`) ran the same four-version gate before `pnpm pack`, so the four-version pass this table recorded at the time is the exact artifact that shipped. **0.9.2 is published on npm** (`dist-tags.latest` = `0.9.2`): the release workflow (run `38406696598`) ran the same four-version gate before `pnpm pack`. The npm-channel column is still the 2026-10-12 check; this re-run did not re-check it. The `/scout` command and Skill added in 0.9.1 are optional bindings, skipped silently on hosts without the corresponding services, so they do not affect loading.
|
|
265
|
+
|
|
266
|
+
2026-10-29 verification-script repair: nightly checks use Node 24 for the Node ≥24.9 requirement of DSH `0.2.2-alpha.2`. The verifier selects single-call or batch execution via the public `setExecutor` capability, checks result cardinality and call IDs, and never retries failures. This repair is not yet published to npm; see the [verification record](docs/verification-dsh-2026-10-29.md).
|
|
218
267
|
|
|
219
268
|
Future `0.2.x` versions still require runtime verification. The [nightly compatibility workflow](https://github.com/MaxHou-infinity/dsh-scout/actions/workflows/compatibility-nightly.yml) resolves npm's `latest`, `next`, and `alpha` tags to exact versions before running tests and the isolated profile check. The badge shows the latest workflow result; this table records versions already verified.
|
|
220
269
|
|
|
221
270
|
> **A red nightly does not mean the plugin is broken**: it may simply mean the declared range expired (e.g. DSH published `0.3.0-alpha.1`). `check:release` reports "out of declared range (expected)" separately from "in range but failing (regression)" — the former calls for updating the range or using the exact-version exemption above, not for a regression fix.
|
|
222
271
|
|
|
272
|
+
## Tools (12)
|
|
273
|
+
|
|
274
|
+
| Tool | Purpose |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `scout_start` | Create a due-diligence case |
|
|
277
|
+
| `scout_add_source` | Register a source (with evidence level) |
|
|
278
|
+
| `scout_ingest` | Batch-register collected results (infers source type/evidence level; can draft one claim per item) |
|
|
279
|
+
| `scout_search` | Search through the DSH Web Provider and auto-register result sources (type/evidence inferred per URL) |
|
|
280
|
+
| `scout_add_claim` | Add an evidence-bounded claim |
|
|
281
|
+
| `scout_verify_identity` | Verify the legal entity from an E3 official source |
|
|
282
|
+
| `scout_verify_claim` | Promote a claim with stronger evidence |
|
|
283
|
+
| `scout_report` | Render the current Markdown report |
|
|
284
|
+
| `scout_questions` | Derive a deduplicated, prioritized interview question list (up to 12; case-curated questions keep their quota) |
|
|
285
|
+
| `scout_compare` | Side-by-side comparison of 2–5 cases (decisions/identity/claims/risks/questions) |
|
|
286
|
+
| `scout_export` | Persist the five-file export + completion marker |
|
|
287
|
+
| `scout_import` | Restore a case from an export directory and recompute its decision (tolerates legacy exports, reports `complete`) |
|
|
288
|
+
|
|
289
|
+
### Output contract (since 0.9.1)
|
|
290
|
+
|
|
291
|
+
Mutating tools (`scout_start` / `scout_add_source` / `scout_ingest` / `scout_search` / `scout_add_claim` / `scout_verify_identity` / `scout_verify_claim` / `scout_import`, 8 in total) **no longer echo the whole case JSON back to the model**:
|
|
292
|
+
|
|
293
|
+
- `value` is a **structured object**: `{ ok, caseId, decision, identity, counts: { sources, claims, verified, open }, changed: { kind, ids }, nextActions, persist? }`;
|
|
294
|
+
- `render` is a **compact summary that never exceeds 1 KB as a whole**: decision + this call's delta + unresolved blockers + next steps. The cap is the render budget itself (`MAX_RENDER_BYTES = 1024` B): the ellipsis's own UTF-8 length is subtracted from the budget before cutting, so **the final string including the ellipsis never exceeds 1024 B**;
|
|
295
|
+
- `persist: { persisted, targetDir, error? }` appears **only on the `autoPersist` path of mutating tools** (the key is omitted when no persist was attempted). **A failed persist is no longer silent**: `persist.persisted === false` plus `persist.error`, and no `case_exported` event is recorded. `scout_export` carries **no `persist` key** — it expresses the same thing through **top-level** `persisted` / `complete` / `error`;
|
|
296
|
+
- **Full data still comes from `scout_report` (Markdown) and `scout_export` (files)** — the model sees a summary, not the whole ledger.
|
|
297
|
+
|
|
298
|
+
Read-class tools keep their own shapes: `scout_report` → `{ caseId, decision, report, coverage }`; `scout_questions` → `{ caseId, questions }`; `scout_compare` → `{ caseIds, report }`; `scout_export` → `{ caseId, targetDir, files, persisted, complete, error? }`, where `targetDir` is the **resolved absolute path**. `scout_import` reuses the mutating envelope and adds `sourceDir` (the directory it restored from) and `complete` (whether the export was complete).
|
|
299
|
+
|
|
300
|
+
### Commands & Skill (new in 0.9.1)
|
|
301
|
+
|
|
302
|
+
| Entry point | Purpose |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `/scout` command | `status` / `questions` / `report` / `export` sub-actions (reusing the tool logic). The host automatically logs `command/run` + `command/done` around the handler — both names are in the host's known event vocabulary, so the session log stays reconstructable |
|
|
305
|
+
| `scout` Skill | The due-diligence workflow registered by the plugin itself (`ctx.skills.register`, no files, no client bundle): start → search/ingest → claim → verify_identity → report → questions, plus the four evidence disciplines; it disappears on unload |
|
|
306
|
+
|
|
307
|
+
Usage (identical to the implementation):
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
/scout status <caseId> | /scout questions <caseId> | /scout report <caseId> | /scout export <caseId> [targetDir]
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Both are **optional bindings**: when the host exposes no `commands` / `skills` service they are skipped silently, and plugin loading plus tools are unaffected.
|
|
314
|
+
|
|
223
315
|
## Configuration (optional)
|
|
224
316
|
|
|
225
317
|
Configure `dsh-scout` in the DSH profile's `cordis.patch.yml`:
|
|
@@ -235,6 +327,7 @@ Configure `dsh-scout` in the DSH profile's `cordis.patch.yml`:
|
|
|
235
327
|
```
|
|
236
328
|
|
|
237
329
|
- A relative `scoutDir` resolves against the **session cwd**, not the DSH process cwd;
|
|
330
|
+
- **Desktop app**: the default `scoutDir` (`./dsh-scout`) depends on cwd, and a desktop session's cwd differs from the CLI's, so give `id: dsh-scout` an absolute path in `~/.dsh/profiles/desktop/cordis.patch.yml`; the app maintains that file, so quit the app before editing it, or the write may be overwritten;
|
|
238
331
|
- With `autoPersist: true`, every mutation persists automatically; **failures are visible**: the return value carries `persist.persisted=false` plus `persist.error`, and no `case_exported` event is written;
|
|
239
332
|
- `language` affects the report, interview questions, the comparison report, and the explanatory text that `scout_ingest` writes into auto-drafted claims (`confidenceNote` / `nextAction`); it also aligns case-owned text that is **still at its factory default**, at two different granularities: `decisionObjective` / `role.fitQuestion` are decided **per field** (a field still at its factory default is aligned); curated interview questions are decided **entry by entry, by index** — when the set has the factory length, an entry still equal to its factory slot is replaced by this deployment's default at the same index and **an entry the user edited is kept verbatim**; a set whose length differs from the factory array is taken as fully user-authored and preserved as a whole. The test is "does this entry equal its factory slot", not edit history (editing an entry back to the factory text makes it align). It is a no-op when it matches the case's own language, so the `zh-CN` default changes nothing for existing users. Separately, `language` in `case.json` is the **rendering language of this deployment**, not the language of the file's contents: alignment reads only that label and **never checks whether the label is consistent with the content**, so a hand-edited file whose `language` disagrees with its prose is taken at its word and skipped by the "same language" early return (the normal export / import / upgrade paths are unaffected: a 0.9.0 export always says `zh-CN`, matching its content).
|
|
240
333
|
|