# 更新日志

[English](CHANGELOG.en.md) | 中文

`dsh-jev-tools` 的重要变更，采用 [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) 格式，
版本号遵循[语义化版本](https://semver.org/spec/v2.0.0.html)。本项目处于 1.0 之前：次版本号
可能包含破坏性变更，真发生时下面的 `Removed` / `Changed` 小节会写明。

## 版本现状

| 版本 | 日期 | 状态 | 摘要 |
|---|---|---|---|
| `0.1.12` | 2026-09-30 | **已发布** | 设置卡片终于能读能写：挂载行 id、`.volatile()` 标记、`configForms` 通道、幂等解析，四处一起修好。 |
| `0.1.11` | 2026-09-30 | **已发布** | 判定端点与模型终于能在配置卡片里改：这两个设置 0.1.6 就有了，卡片一直没画出来。 |
| `0.1.10` | 2026-09-25 | **已发布** | 台账记录基线覆盖率；没有实测到基线时不再报净增量。 |
| `0.1.9` | 2026-09-25 | **已发布** | 注入的 notice 曾让整轮对话失败；来源 kind 已改为格式 v4 要求的生产者自有形式。 |
| `0.1.8` | 2026-09-22 | **已发布** | 技能推荐此前**从未触发过**，已修复；两个工具的额度上限与拒绝原因不再静默或失效。 |
| `0.1.7` | 2026-09-22 | **已发布** | 结构性失败不再静默；成本可见；标定增加 AUC 与阈值扫掠；CI 与 tag 驱动的发布。 |
| `0.1.6` | 2026-09-22 | **已发布** | 判定端点可配置（`baseUrl`）；`/jev-status` 报告该端点。 |
| `0.1.5` | 2026-09-21 | **已发布** | 修正 Workshop 清单的适配器字段；能力与 `0.1.4` 相同。 |
| `0.1.4` | 2026-09-21 | **已发布** | 增加 OMDSH Workshop 清单；能力与 `0.1.3` 相同。 |
| `0.1.3` | 2026-09-21 | **已发布** | README 重写；能力与 `0.1.2` 相同。 |
| `0.1.2` | 2026-09-21 | **已发布** | 源码安装可用；能力与 `0.1.1` 相同。 |
| `0.1.1` | 2026-09-21 | **已发布** | 文档修正；能力与 `0.1.0` 相同。 |
| `0.1.0` | 2026-09-20 | **已发布** | 首个版本，包含下面描述的全部内容。 |

已发布到 npm：`npm i dsh-jev-tools`。也可以直接从仓库检出目录安装。

## [0.1.13] — 2026-09-30

### Added

- **插件图标：清单判定 → 决策路径。** `package.json` 增加 `icon: "icon.svg"`——一行红叉、一行绿勾，
  绿勾之后直接延伸成青→紫的箭头：被否掉的停在原地，通过的那条成为路径。DSH 插件页的包卡片、
  详情页与插件行会显示它：Host 的 `readPluginMeta()`（`@deepseek-ai/dsh-app-boot`）读
  `package.json.icon`，按扩展名映射 MIME 后编码成 `image/svg+xml;base64` 的 data URL 交给
  `<img src>`，只有未声明或解码失败时才回落到默认风车插画。`files` 里同时加上 `icon.svg`：
  `files` 是 allowlist，不带上就是"字段在、文件不在"，装了插件照样没有图标。颜色是固定值，
  因为 `<img>` 里拿不到 `currentColor`，一份文件同时服务浅色与深色两套主题。

## [0.1.12] — 2026-09-30

### Fixed

- **设置卡片读不到自己的设置，三个开关全是禁用且空白、两个端点框只读。** 两个独立原因叠在一起：
  (1) DSH 按**挂载行的 id** 发布一个 bundle 的设置命名空间（`SettingsForms.describe()` 公布的是
  `entry.options.id`，写入也按它解析），而卡片（占 `plugins.bundle.config` 座位）与插件页的配置座位都按
  **包名**索引；(2) 设置页只发布标了 `.volatile()` 的字段——`volatileForm()` 对一个 volatile 字段都没有的
  schema 返回空，于是连命名空间都不会出现在 `describe()` 里。行 id 叫 `jev-tools`、schema 又一处
  `.volatile()` 都没有时，卡片两边都取不到东西，而且**任何地方都不报错**——这就是 0.1.6→0.1.11
  「设置页改不了」的全部原因（issue #3）。现在挂载行 id 就是包名，卡片要读要写的六个字段都标了
  `.volatile()`，并由 `test/bundle-contract.test.ts` 把包名、行 id、卡片请求的命名空间，以及这六个字段的
  可编辑性钉在一起。
- **删除了 `settings.installSection` 调用。** DSH 0.2.0-rc.2 的 `settings` 服务没有这个方法，每次挂载都会抛
  `TypeError` 再被自己的 catch 吞成一条警告——一条把「注册失败」说成「暂时不可用」的假象。设置页本来就由条目
  导出的 Config schema 自动生成，写入会让该条目的 fiber 重启、`apply` 重新解析配置，所以「改完立即生效」
  不依赖任何注册回调。
- **命名空间对不上时，卡片现在会说出来。** 过去它画一个看起来像「什么都没配置」的空表单；现在它直接点名
  找不到的命名空间，以及决定它的那份文件。

### Changed

- **配置解析改走 standard-schema 路径并解包 volatile 标记。** `.volatile()` 会把被标记字段的解析结果包成一个
  标记对象（写回时用它判断哪些键要落盘），所以配置经 `Config['~standard'].validate(value)`（Loader 自己走
  的就是这条）解析后再递归解包，`model`、`baseUrl` 这类字段拿到的仍是字符串而不是对象。
  `@deepseek-ai/schemastery` 升到 `^3.18.4`（`.volatile()` 自该版本起提供，也正是宿主自带的版本），
  并为 `isVolatile` 新增依赖 `@deepseek-ai/cosmokit`。
- **卡片改走浏览器唯一的设置通道。** 现在读写都经 `ctx.configForms.get(命名空间)`（插件页上四个官方设置页
  用的就是这一条）：读取取共享设置镜像的快照，写入按 `{ op, path, value }` 操作列表带 revision 提交。此前
  直接调 `remote.settings`，而浏览器里 `settings.describe()` 只有一个读取方——设置 UI 自己；第三方卡片拿
  不到它，于是**宿主已经把命名空间发布出来，卡片仍然读不到**。读不到时的提示现在会连宿主实际提供的命名
  空间一起列出。
- **解析必须幂等。** 宿主交给 `apply` 的是**它已经解析过的**配置（`.volatile()` 的标记对象都在里面），
  所以要先解包输入再校验。少了这一步，`enabled` 会被当成对象校验失败并抛错，而 `apply` 里任何抛错都会让
  整条 fiber 挂掉：插件在界面上显示「异常」、设置页一个命名空间都不提供，看起来又像"卡片读不到自己的
  设置"。`test/config.test.ts` 现在钉住这一条（Loader 形状的输入必须与原始输入解析出同一份值）。

## [0.1.11] — 2026-09-30

### Fixed

- **判定端点终于能在界面上改。** `baseUrl` 与 `model` 自 0.1.6 起就是宿主侧设置，后端每次判定都读它们的
  实时值，但配置卡片只画了 key 输入框和三个开关——而卡片占用的 `plugins.bundle.config` 座位是**按包名
  单占用**的，卡片没画的字段，界面上就够不到。于是这两个设置虽然声明了、接线了、README 也写着「设置页
  可改」，实际只有手工编辑组合配置行才能改，还要重启宿主。依据：issue #3 的报告人描述的正是这件事——
  想接 OpenRouter 的 Jev，却发现只能在启动时设置。现在卡片里有「判定端点」区：服务地址与模型，暂存后
  显式保存；地址必须以 `http(s)://` 开头且非空，否则拦住保存并说明原因；只写真正变化的字段，避免
  「打开看一眼」变成一次覆写。
- **两处会撒谎的文案跟着端点走。** 隐私说明原本硬写 `api.typesafe.ai`，端点在 0.1.6 可配置之后这句话
  就是错的；现在印的是实际生效的主机。申请 key 的链接同理：端点指向 OpenRouter 时给
  `openrouter.ai/settings/keys`，其余情况仍去 TypeSafe 控制台。

## [0.1.10] — 2026-09-25

### Added

- **基线覆盖率进台账。** 每次 prune 判定现在记录确定性基线是否真的测得：返回内容记为
  `baselineKeptTokens`；返回 `null`（内容在 DSH 自身预算内）按**原始 token 数**记录——这是一次
  实测"基线本就不削"，而不是缺数据；服务缺席或抛错记为 `baselineUnavailable: no-service | error`。
  累计值随之新增 `baselineMeasured` 与 `baselineUnmeasured`。依据：2026-09-25 的两次 prune 判定在
  旧逻辑下写法完全相同——headless 组合实测到 `baselineKeptTokens: 4441.5`，web 组合记的是
  `no-service`（Web 面把确定性剪枝器挂在 agent preset 之后），当时台账看不出这两者的区别。

### Changed

- **没有基线就不再报净增量。** `/jev-status` 把覆盖率与净增量一起印出；`baselineMeasured` 为 0 时
  只报削減量，并明说净增量目前无数据。旧逻辑用 `baselineKept = entry.baselineKeptTokens ?? original`
  把"从未测到"读成"基线不削"，于是净增量等于本插件的全部削减——那是假设，不是结果。
- README 两处措辞改准：剪枝保留的是**散点的相关片段**，不是"从中间砍掉一段"；净增量则连同它的
  覆盖前提一起表述。

## [0.1.9] — 2026-09-25

### Fixed

- **注入的 notice 让整轮对话失败。** Harness 自 0.1.7 起把会话写成格式 v4，而 v4 **没有共享的
  `plugin` 来源 kind**：每条消息要说明自己**是谁**产出的，旧的 `{ kind: 'plugin', plugin: … }`
  包装被写入器直接拒绝——它不会丢掉那一条消息，而是让整轮以
  `format v4 message requires a producer-owned source kind` 结束（界面上就是「本轮运行失败」）。
  四条能力线的 notice 全部命中：`prune`、`screen`、`suggest`，以及降级提示。依据：2026-09-24
  两个会话都在 notice 注入的那一刻断在 step 中间——`f87f8e58` 的 prune 判定
  （23:55:53，5376 → 3187 tokens）与 `5f45b2e2` 的 screen 判定（23:56:51）——日志停在
  `tool/call`，没有 `turn/end`；再用 Harness 0.1.7-rc.1 的 `assertV4MessageSources` 复现：
  `kind: 'plugin'` 抛错，`kind: 'plugin:dsh-jev-tools'` 通过。

### Changed

- **notice 的来源改为单一模块产出。** 现在只有 `src/source.ts` 构造它；写入的 kind
  `plugin:dsh-jev-tools` 正是 Harness 自己的 v3→v4 迁移为历史 notice 记录的身份，因此升级前
  写入的 notice 与今天写入的归到同一个 kind，而不是分裂成两个。

## [0.1.8] — 2026-09-22

### Fixed

- **技能推荐从未触发过。** 这是本次唯一一个「整个能力没在工作、而外部完全看不出来」的缺陷。
  `skills.list()` 没有传 `scope`，而注册表在省略 `scope` 时**只读 global 层**——agent preset 挂载的
  插件注册在 **preset 层**，于是一个会话真实持有的二十多个技能，插件一个都看不见，目录恒低于 15 的
  门槛，每一次都静默返回。依据：修复前 **6 个会话、227 条台账记录里 `suggest` 一条都没有**，
  同期 `prune` 218 条、`screen` 9 条；修复后首次出现 `judged`，并向会话注入了建议。
- **`jev_ask` / `jev_gate` 每会话只能调用 3 次。** 两者都调 `budget.tryConsume(agentId, -1)`：`-1` 是一个
  **永不前进的伪 turn**，而传入的是 `prune.perTurnLimit`（一个 per-turn 值），于是 `turns.get(-1)`
  在整个会话里只增不减，第 4 次起**永久**被拒。实测：连续 3 次成功、随后 3 次全部 `skip: "budget-turn"`。
  现在显式调用走单独的 `tryConsumeSession()`，只受 `sessionCallLimit` 约束——per-turn 上限的理由是
  限制**自动能力**的串行延迟，对一次主动索取答案的调用并不成立。
- **拒绝在会话里只剩一个占位符。** 渲染器只读 `report`，而 `declined()` 返回
  `{ problem, message, detail }`——没有 `report`。于是没配 key、请求非法、额度耗尽、网络失败
  四种完全不同的失败在会话里长得一模一样：`(no judgment)`（闸门为 `(no gate verdict)`）。
  这违背了 README 的约定——fail-open 的代价是这些失败**只能**在会话里被看见。
- **拒绝记录丢失会话标识。** 失败路径硬编码 `agentId: ''`，成功路径写真实 agentId，
  同一个工具一半的台账因此无法归因。
- **拒绝文案张冠李戴。** 提示语声称「本会话至多 200 次」，实际执行的却是 per-turn 检查。
  现在文案只描述真正触发它的那个上限。
- **弃权后同一 turn 内重复判定。** turn 只在**建议真正产出后**才上锁，于是弃权——含糊 turn 的常态
  ——什么都不锁，后续每个 step 都重发一次判定。实测 **3 个用户 turn 产生 4 条判定记录**，
  且每次都在消耗 `prune` 共享的每 turn 额度。现在请求发出前就锁，且锁本身不写记录：
  **一个 turn 一条 `suggest` 记录**成为不变量。

### Added

- **技能推荐的每一次放弃都进台账。** 新增 `no-skills`、`catalog-unavailable`、`catalog-too-small`
  三个跳过原因。在此之前它的 8 条提前返回全部静默，于是「从未运行」与「运行了但每次放弃」
  在外部**无法区分**——而台账存在的唯一理由就是回答这个问题。记录按 (turn, 原因) 去重：
  `agent/pre-step` 每个 step 都触发，不去重会拿同一条记录冲掉 1000 条的台账窗口。
- **技能推荐改为按最新一条用户消息判定**（过短时回退到最近三条）。这是**语义**选择而非准确性改进：
  建议回答「此刻问什么」，三消息窗口回答「这段会话在做什么」——后者是剪枝的问题。受控复现
  （同 criteria、只换任务文本）显示窗口只**稀释**分布：冠军 0.510 → 0.470，top2 边际 0.24 → 0.16，
  **不改变冠军**。

### Changed

- README 的额度说明改准：`sessionCallLimit` 明确含两个工具，`prune.perTurnLimit` 明确是
  prune / screen / suggest **三者共享**。数据边界表补上 `jev_ask` 与 `jev_gate`——两个工具都会把
  调用者给的正文送出本机，此前那一节漏了它们。
- 海报与渲染脚本移出 `docs/`（改到仓库根的 `poster/`）：`files` 白名单包含整个 `docs/`，
  3 MB 的营销素材没有理由进入每个用户的 `npm i`。

## [0.1.7] — 2026-09-22

### Added

- **结构性失败不再静默**。没配 key、或端点/密钥不匹配导致 401 时，插件会**在会话里说一次**（每个会话、
  每种原因各一次，靠会话历史去重）。fail-open 的意义是判定失败不影响任务，代价是这两类失败在别处
  都不报错——一个填错的 `baseUrl` 或一把属于别的主机的 key，表现就是"插件什么都没做"。提示同时
  到达用户与模型，后者因此不会继续相信一个已经瞎了的剪枝层。
- **成本可见**。台账新增累计 input token 计数器（输入计费、输出免费），`/jev-status` 与
  `npm run measure -- --ledger` 都按 `$0.042`/百万折成美元显示。此前 `estimateCostUsd()` 除了单元
  测试没有任何调用者，而度量脚本自己另写了一份价格常量。
- **标定报告增加 AUC 与阈值扫掠**。`npm run measure -- samples.jsonl` 现在回答两个此前没回答的问题：
  ROC AUC（模型到底能不能区分两类，与"标定得好不好"是两回事），以及 0.05–0.95 的阈值表
  （精确率/召回/F1）与最佳操作点——这是闸门类能力需要的数字，与已有的 ECE/Brier 互补。
- **CI 与发布自动化**。`.github/workflows/ci.yml`（ubuntu + windows × node 24：`npm ci` →
  `npm test` → tarball 检查），`.github/workflows/release.yml`（**只有 tag 能发布**：先校验 tag 与
  `package.json` 版本一致，再跑同一套门禁，最后经 npm trusted publishing 发布，无长期 token）。
  配套 `scripts/check-tarball.mjs`：断言发布包里既没有本机状态（`.npm-cache/`、`trigger-rate.json`
  等），也不缺用户需要的文件——`files` 白名单只有在没人放宽它时才是一道控制。

### Changed

- 判定价格只声明一处（`src/request.ts` 的 `USD_PER_MTOK`），度量脚本不再自带副本。

## [0.1.6] — 2026-09-22

### Added

- **判定端点可配置**（`baseUrl`，默认 `https://api.typesafe.ai`）。改动很小，修的是一个
  **"声明了却没接线"**：`JevBackendOptions.baseUrl` 早就存在、`createJevBackend` 也早就照它取地址，
  但 `JevSettings` 里没有这个字段，设置页里也就没有这一项，`apply()` 里三个构造点更无从传起——
  这条管线因此永远只可能走默认主机。写死端点挡住的是**自建的 Jev 兼容服务**，以及**在前面挡了
  一层网关**的部署。现在这个值从设置（或 bundle 行的 `config:`）读到三个后端构造点，填裸主机名即可，
  `/v1/systemone` 仍由插件追加；端点与 key 一样按操作实时读取，改完不用重启。
- **`/jev-status` 增加「判定端点」一行**。端点可配之后，"内容发去哪台主机"不再是常量，而填错的表现
  与"插件什么都没做"完全相同——这个报告本来就是回答这个问题的唯一地方。

### Changed

- 判定端点的默认值改为只声明一处（`src/config.ts`），后端从那里 import 而不再自己持有一份：
  设置项的默认值与后端的兜底不会再漂成两台不同的主机。

## [0.1.5] — 2026-09-21

### Fixed

- `dshWorkshop.install.adapter` 由 `harness-cordis` 改为 `third-party`：OMDSH Workshop 的协议映射要求 `harness-cordis` 搭配 `third-party` 适配器，用协议名当适配器会被它的校验器拒绝。

## [0.1.4] — 2026-09-21

### Added

- `package.json` 增加 `dshWorkshop` 清单（`omdsh-workshop-package/v1`），声明集成协议、安装模式、生命周期、结构化权限、兼容版本与一项可观测能力，供 [OMDSH Hub](https://github.com/omdsh-dev/dsh-hub-workshop) 的上架流程读取。这是给收录方看的元数据，不改变插件在 DSH 里的行为。

## [0.1.3] — 2026-09-21

### Changed

- README 改为更精简的结构：能力、安装、配置、数据边界、设置、排查、已知局限、台账、开发。
  删掉了 Jev 教程式的长篇介绍与路线图部分，关键事实一条没少。

### Fixed

- `cordis.patch.yml` 的头部注释此前只列了部分能力，现在按实际能力列全。

## [0.1.2] — 2026-09-21

### Fixed

- 从 GitHub 源码安装现在会自动构建：`package.json` 增加了 `prepare` 脚本。在此之前源码安装能装上，
  但 `lib/` 是构建产物、不在仓库里，装完没有入口文件可加载。从 npm 安装不受影响。

## [0.1.1] — 2026-09-21

### Fixed

- README 与 CHANGELOG 里与发布状态有关的表述已改为与实际情况一致（此前写的是"尚未发布"），
  README 顶部补上 npm 版本与许可证徽章。
- README 中"只有两项能力"的旧描述改为按当前实际能力陈述。

## [0.1.0] — 2026-09-20

### Added

- **超长工具结果的语义剪枝**（`tools/post-execute`），作用于
  `read` / `grep` / `glob` / `web_fetch` / `web_search`。Jev 逐段判断与当前任务的相关性，
  丢掉不相关的段落，并留下一条可见提示。它**只排序、不卡阈值**，保留确定性的首尾保底，
  且**纯 fail-open**。真实 API 实测：`read: 4613 → 2624 tokens`。
- **试运行模式**（`prune.shadow`）：判定与记账完全照常，但一个字都不改——每个 payload
  会报告它**本来会**削掉什么。它存在的意义是让取舍在**被信任之前**先被看清；它的记录
  不计入削减总量，因为什么都没被削减。
- **注入筛查**（抓取内容，`web_fetch` / `web_search`）：判定一个页面里"存在针对 AI 的指令"
  的概率，并附加一条提醒。**仅提醒**——它绝不拦截调用，也绝不改写内容。它跑在剪枝阈值
  **之下**（最危险的页面是短的那个），并且在有剪枝请求可以搭车时搭同一次请求。
- **技能推荐**（`agent/pre-step`）：每轮至多一条建议，低于置信度下限时保持沉默，且是严格
  意义上的建议——它从不拒绝任何 step。
- **`jev_ask`** —— 面向模型的工具，可提任意带类型的问题（`noul` / `choice` / `score`）；
  在内容离开本机之前，逐条校验官方文档写明的每一项限制。
- **`jev_gate`** —— 面向模型的工具，在宣布做完之前核对交付：用**实际提供的证据**逐条判定
  完成声明（`verified` / `contradicted` / `not_addressed`），并给出整个交付的一个动作
  （`auto` / `review` / `escalate`）。这是本插件唯一一处**把自己那条 fail-open 规矩倒过来**
  的能力：每一条含糊的路径都落到 `escalate`，因为闸门 fail-open 就等于 fail 到「通过」。
- **判定台账**，只含元数据；累计计数器与有上限的记录集**分开存放**，所以淘汰不会让累计
  数字变小。它同时记录「确定性基线会保留多少」与「语义选择实际保留多少」，使净增量成为
  一个**被测出来的数字**而不是一种说法。profile 提供 `storageDomain` 时持久化，否则留在内存。
- **`/jev-status`** —— 启用状态、key 来源、判定次数、台账存放位置，以及**每一次跳过的原因**。
- **双语输出**，覆盖一切给人看的东西：设置卡片跟随 DSH 界面语言，会话提示、`/jev-status`
  与工具报告跟随对话语言。
- **度量工具**：`scripts/trigger-rate.ts`（从本地会话日志统计触发率；无需 key、无网络）与
  `scripts/measure.ts`（标注集标定，或台账自身的增量报告）。
- **一套覆盖每个接缝的测试**：剪枝管线与每一条跳过守卫、重试/退避矩阵、请求校验、凭据策略、
  台账持久化与重启连续性、注入筛查、闸门的决策矩阵，以及架构分层（分层由测试强制，
  而非靠约定）。

### 默认值及其依据

下面每一个都来自对 72 个真实会话、4653 条真实工具结果的测量，而不是偏好。
数字在 `docs/s0-trigger-rate.md`。

| 配置项 | 默认值 | 依据 |
|---|---|---|
| `prune.minTokens` | `2000` | 延迟由配额支配而非阈值；在超长结果中，此值保留 31.9% 的可节省量 |
| `prune.perTurnLimit` | `3` | 不设上限时最坏一个 turn 增加 8.1 秒；限 3 后 0.9 秒 |
| `prune.toolAllowlist` | `read` `grep` `glob` `web_fetch` `web_search` | 覆盖 76% 的超长结果；**刻意不含 `pwsh`** |
| `prune.headLines` / `tailLines` | `40` / `40` | 确定性保底，不可协商 |
| `prune.keepHigh` | `0.5` | 达到此相关度的段落无条件保留 |
| `prune.minKeepRatio` | `0.2` | 绝不把结果削成空壳 |
| `prune.minSaving` | `0.15` | 低于此值，失真不值得那点 token |
| `prune.minTaskChars` | `12` | 拿"继续吧"去判相关性只会得到噪声 |
| `screen.minTokens` | `300` | 低于此长度，文本承载不了注入指令 |
| `screen.threshold` | `0.75` | 附加提醒的注入概率阈值 |
| `suggest.minCatalogSize` | `15` | 实测目录：最小 27、中位 29 |
| `suggest.minConfidence` | `0.3` | 低于此值不注入任何建议 |
| `sessionCallLimit` | `200` | 成本与内容出境的硬上限 |

### 已知局限

- **不存在准确率数字。** 唯一的质量证据是 8 条自造中文三分类样本上的 8/8，而且
  **从未与宿主模型做过对照**。项目自己的计划要求过这项对照，它没有被完成。
- **概率不是标定概率。** 实测：在简单输入上饱和到 `1.000`，在困难输入上又系统偏低。
  可以当排序用，绝不能当"正确率"用，也不要在简单区间上按固定置信度设闸门。
- **标定是一条就绪但未接线的回路。** `src/calibrate.ts` 没有任何运行时代码调用它：拟合曲线
  需要每条判定的概率与后来可观察的对错，而 `noul` 答案不含 confidence 字段，同时也没有
  任何机制报告"被剪掉的那段后来是否真的需要"。
- **`/jev-status` 与技能推荐从未在真实会话中被观察到。** 两者都有逻辑层测试，但都没有在
  harness 里被亲眼看见工作。
- **完成度闸门是判断，不是证明。** 它不跑测试、不应用补丁；它对输入有上限（截断即禁止
  `auto`）；它的字段隔离只是指令层面的——diff 里藏着的提示注入靠措辞缓解，而不是靠硬边界。
- **两个 harness 进程共用一个存储根**时，累计计数器是最后写入者胜；域设施的"单次打开"
  保证只在进程内生效。

## 兼容性

| 对象 | 要求 |
|---|---|
| DSH 版本 | `0.1.6-alpha.2`，声明在 `package.json` 的 `dsh.compatibility` 里 |
| Node.js | `>= 20`（`engines`）；开发与测试使用 24 |
| 必需的 DSH 服务 | 无。所有服务都是软注入的，缺少任何一个插件仍会挂载并可见地降级 |
| 可选的 DSH 服务 | `credentials`、`settings`、`tools`、`commands`、`skills`、`storageDomain`、`toolResultPruner` |
| 包依赖 | 只有 `@deepseek-ai/schemastery`。DSH 各包是**可选** peer 依赖：npm 上发布的那些严重滞后于部署，因此刻意不 import 它们的类型 |
