dsh-plugin-compat-check 0.1.0 → 0.1.2

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.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * ## 为什么必须有这个文件
5
5
  *
6
- * Plan.md §2 第 9 题锁的是「单仓库、**单版本**、一份 Node 内核、三个入口」。
6
+ * docs/Plan.md §1 约定的是「单仓库、**单版本**、一份 Node 内核、三个入口」。
7
7
  * 而实测下来这条承诺**从来没兑现过**:
8
8
  *
9
9
  * | 产物 | 版本 |
@@ -29,7 +29,7 @@
29
29
  */
30
30
 
31
31
  /** 语义化版本。改动它会同时影响三个入口的 `--version` 与报告 JSON 的 `toolVersion`。 */
32
- export const TOOL_VERSION = '0.1.0';
32
+ export const TOOL_VERSION = '0.1.2';
33
33
 
34
34
  /** 人类可读的一行,给 `--version` 用 */
35
35
  export function versionLine() {
@@ -0,0 +1,82 @@
1
+ import { Worker } from "node:worker_threads";
2
+
3
+ /** Supervise trusted jobs; settle only after reclaiming their worker thread. */
4
+ export function runWorkerTask(
5
+ url,
6
+ data,
7
+ { timeoutMs = 10000, resourceLimits = {} } = {},
8
+ ) {
9
+ return new Promise((resolve, reject) => {
10
+ const limits = {
11
+ maxOldGenerationSizeMb: 512,
12
+ maxYoungGenerationSizeMb: 64,
13
+ ...resourceLimits,
14
+ };
15
+ if (
16
+ !Number.isInteger(timeoutMs) ||
17
+ timeoutMs <= 0 ||
18
+ timeoutMs > 2147483647 ||
19
+ Object.entries(limits).some(
20
+ ([key, value]) =>
21
+ !["maxOldGenerationSizeMb", "maxYoungGenerationSizeMb"].includes(
22
+ key,
23
+ ) ||
24
+ !Number.isInteger(value) ||
25
+ value < 4 ||
26
+ value > (key === "maxOldGenerationSizeMb" ? 512 : 64),
27
+ )
28
+ )
29
+ return reject(new Error("Invalid worker resource budget / 预算"));
30
+ let worker;
31
+ try {
32
+ worker = new Worker(url, {
33
+ workerData: data,
34
+ execArgv: [],
35
+ resourceLimits: limits,
36
+ });
37
+ } catch (error) {
38
+ reject(error);
39
+ return;
40
+ }
41
+ let finishing = false;
42
+ const finish = async (error, value) => {
43
+ if (finishing) return;
44
+ finishing = true;
45
+ clearTimeout(timer);
46
+ try {
47
+ await worker.terminate();
48
+ } catch (cleanupError) {
49
+ error ||= cleanupError;
50
+ }
51
+ error ? reject(error) : resolve(value);
52
+ };
53
+ const timer = setTimeout(() => {
54
+ const error = new Error("静态分析超过时间预算,已终止工作线程");
55
+ error.code = "DSC_WORKER_TIMEOUT";
56
+ void finish(error);
57
+ }, timeoutMs);
58
+ worker.once("message", (message) => {
59
+ if (
60
+ !message ||
61
+ typeof message.ok !== "boolean" ||
62
+ (message.ok && !Object.hasOwn(message, "value")) ||
63
+ (!message.ok && typeof message.error !== "string")
64
+ )
65
+ return void finish(new Error("Worker protocol / 协议无效"));
66
+ void finish(message.ok ? null : new Error(message.error), message.value);
67
+ });
68
+ worker.once("error", (error) => void finish(error));
69
+ worker.once("exit", (code) => {
70
+ if (!finishing)
71
+ void finish(new Error(`分析线程未返回结果(exit ${code})`));
72
+ });
73
+ });
74
+ }
75
+
76
+ export function extractInWorker(nm, options = {}, timeoutMs = 10000) {
77
+ return runWorkerTask(
78
+ new URL("./extract-worker.mjs", import.meta.url),
79
+ { nm, options },
80
+ { timeoutMs },
81
+ );
82
+ }
@@ -1,64 +1,53 @@
1
- # 严重度规则表
1
+ # 严重度规则表与维护
2
2
 
3
- `severity.json` 是判定「一条发现有多严重」的唯一依据。判定**不由 LLM 决定**——LLM 只能润色措辞。这样做的理由很直接:这个工具的价值全在结论可信,而"某处 API 缺失意味着设置页会消失"这种判断必须可复核、可被反驳、可被社区提 PR 修正。
3
+ 适用于0.1.2,判定算法与0.1.1一致。当前契约见 [Plan现行契约](../docs/Plan.md),验证见 [自动统计](../docs/current-state.json)。本页后半保留2026-10-11旧提取器的规则依据;其中表格不是当前覆盖率,也不是当前运行时真值。
4
4
 
5
- ## 两份文件
5
+ ## 文件与运行时消费者
6
6
 
7
- | 文件 | 作用 |
7
+ | 文件 | 用途 |
8
8
  |---|---|
9
- | `severity.json` | 规则本体:`服务.方法 → 严重度 + 影响面`,外加四条**分层默认档** |
10
- | `usage-inventory.json` | 覆盖依据:从作者本机 21 个社区插件静态提取的真实调用点快照,用来说明"为什么是这些服务" |
9
+ | severity.json | 方法严重度/影响文案;引擎读取services下的方法规则,并与代码内默认档合用 |
10
+ | dsh-api-catalog.json | 指定DSH版本生成的宿主/客户端契约目录;不证明当次运行时提供某方法 |
11
+ | usage-inventory.json | 带日期/版本/profile及完整性标记的AST库存快照;由coverage显式刷新,不保证与后续提取器重扫同数 |
11
12
 
12
- 判定引擎实际读的只有一样东西:`services[服务].methods[方法].severity` 与 `.impact`(见 `lib/verdict.mjs` 的 `lookupSeverity`)。**其余字段是给人看的、给 PR 用的**,不影响输出:
13
+ 严重度由 `lib/verdict.mjs` 的确定性逻辑、缺失证据、方法规则及默认档共同决定,不由LLM决定。方法规则的severity/impact参与判定与输出,layer控制端别:方法自身优先、服务layer回退。summary/basis/evidence/knownGoneIn/needsReview等用于解释与维护;knownExtractionArtifacts是历史反例登记,不是自动过滤黑名单。
13
14
 
14
- | 字段 | 含义 |
15
- |---|---|
16
- | `layer` | `host` = Node 宿主侧;`client` = 浏览器客户端侧;`both` = 同名服务两侧都有但语义/签名不同 |
17
- | `summary` | 这个服务是干什么的(人话,取自官方 API 目录) |
18
- | `basis` | 这条判定的证据等级:`catalog` / `catalog-description` / `callsite`(含义见下) |
19
- | `evidence` | `basis: callsite` 时必须给的具体文件:行 |
20
- | `knownGoneIn` | 已知从哪个 DSH 版本起不再提供(沿用既有写法) |
21
-
22
- `basis` 的三档不是形式主义,它决定了这条规则**可以被怎么反驳**:
15
+ 0.2.1-rc.1宿主改为优先按engines.dsh判断;本版尚未同步该逻辑,已复现声明误报与漏报,规则/API目录也未更新为新版。新版接入验证不代表这些判定已适配,实施顺序见Plan后续。
23
16
 
24
- - `catalog`:官方 API 目录的 `summary`/`description` 直接支持。最硬。
25
- - `catalog-description`:目录**没有声明任何方法**(抽象服务),但官方 `description` 里明确写了这个方法存在。`credentials.resolve`、`sessionPersistence.open/list` 属于这一档——这正是"目录 ≠ 运行时 API 面"的活例。
26
- - `callsite`:目录里查不到,依据是真实插件在那个调用点上的代码。**这是最弱的一档**,跟着 `evidence` 给的 `文件:行` 去读一眼就能推翻它。
17
+ ## 当前证据与严重度
27
18
 
28
- ## 三档严重度
29
-
30
- | 档 | 含义 | 典型来源 |
31
- |---|---|---|
32
- | `fatal` | 插件条目没激活,整个插件是死的 | 运行时事实(`ctx.loader.entries()` 的 fiber 状态) |
33
- | `feature-dead` | 某个特性确定不可用 | 表里有明确语义的 API 缺失 |
34
- | `degraded` | 仍可用但能力缩水 | 有回退路径的 API 缺失(如事件变轮询) |
19
+ | 情形 | 处理 |
20
+ |---|---|
21
+ | 可映射loader条目FAILED/PENDING | fatal,影响依据measured;禁用、LOADING、无法映射不冒充同一事实 |
22
+ | 宿主硬依赖且明确absent | 可判fatal;仅catalog缺项不冒充运行时缺失 |
23
+ | 非硬依赖服务明确absent,或有足够方法证据 | 按规则/默认档给feature-dead或degraded;用户影响通常是inferred |
24
+ | 官方peer范围冲突 | declared-incompatible,degraded及推断影响;不是实测加载失败;当前内核沿用0.2.0-rc.2,将engines.dsh作建议处理 |
25
+ | 未采样、读取失败、访问器未知、源码不完整 | 保留覆盖限制;不以省略键认定不存在 |
35
26
 
36
- **`degraded` 的硬门槛:必须能说出回退路径是什么。** 说不出回退路径的一律 `feature-dead`。本表里 `degraded` 的每一条都写了回退到哪(`jobs.onJobsChanged` → 轮询;`timer.timeout` → 原生 `setTimeout`;`sessions.using` → `sessions.binding`;`layout.selectPanel` → 侧栏入口),这不是措辞,是判据。
27
+ 方法规则中的degraded应有具体降级/回退依据;peer声明冲突的degraded是引擎针对声明风险的独立默认,不代表观察到回退。方法缺失但没有语义规则时会明确“影响面未知”。声明provide只是候选提供方,不证明已激活,不能遮住完整的present方法观测。
37
28
 
38
- ## 分层默认档(不查表也能判的部分)
29
+ 宿主、客户端及shared调用点必须分开。客户端方法不能拿宿主probe判缺失;同名服务也不保证同一语义。对源码归属不确定的Context/别名保留suspected。certainty与impactBasis分别表达证据强度和影响是否实测。
39
30
 
40
- 判定顺序是:**先看运行时事实,再查表,最后落默认档**。
31
+ ## 如何更新
41
32
 
42
- 1. 条目 `FAILED`/`PENDING` → `fatal`,这是运行时事实,不需要静态分析。
43
- 2. 插件在 `inject` 里声明了宿主没有的服务 → `fatal`(条目会停在 pending)。
44
- 3. 用 `ctx.get('x')` 取不到服务 → `feature-dead`(不会导致未激活,但功能静默失效)。
45
- 4. 方法不存在且不在表中 → `feature-dead` + **影响面未知**。
33
+ 先读调用点和目标版本的真实描述符/官方源码,再修改规则,添加行为回归并审查快照差异。原needsReview已在2026-10-15逐条复核,决定与证据保留于severity.json的needsReview.reviewed/evidence及 [Plan](../docs/Plan.md)。
46
34
 
47
- 第 4 条必须显眼:它是在承认规则表没覆盖到,而不是在假装判断过。
35
+ `tools/coverage.mjs` 从AST调用点按host/client/shared分端计数,规则命中与默认影响、类别发现分列。**knownExtractionArtifacts只保留历史反例,不过滤现在的真实调用**;纯客户端规则不用于宿主同名方法。probe须为现代格式并绑定本目标/版本/时间,不能借别的profile的面。写库存要求显式或观测到的合法semver版本,非法版本退出3且不覆盖旧文件。
48
36
 
49
- ## 客户端服务与宿主服务不是一回事
37
+ ```bash
38
+ node tools/coverage.mjs --nm <node_modules目录> --dsh-version <已核验版本> --json
39
+ node tools/coverage.mjs --nm <node_modules目录> --probe <同目标的现代probe> --write --json
40
+ # 默认拒绝将不完整扫描写成库存;审查diagnostics后方可显式接受部分样本
41
+ node tools/coverage.mjs --nm <node_modules目录> --dsh-version <已核验版本> --write --allow-partial --json
42
+ ```
50
43
 
51
- `locale` / `slots` / `theme` / `layout` / `uiWorkspace` / `workspaces` 活在浏览器里,宿主探针必然看不到;而 `sessions`、`timer`、`jobs` **两侧都有同名服务却语义不同**:
44
+ 仓库保存的库存是2026-10-15的desktop21插件样本,DSH0.2.0-rc.2,静态catalogue模式,不借用规则复核的私有web探针。扫描受默认字节预算和缺失CSS入口限制,明确为partial:50个有调用服务、125个分端方法对,其中88个有对应规则(host69/79、client19/46)。这些是本次读到的部分集合,不是完整覆盖率或准确率;同名方法不同端分别计数,不能直接和下面旧口径61%比较。sourceDigest记录规范化AST分析摘要,不承诺所有包字节的完整性。历史Python参考不是AST真值。
52
45
 
53
- | 服务 | 宿主侧 | 客户端侧 |
54
- |---|---|---|
55
- | `sessions` | `create`/`enter`/`flush`/`fork`/`get`/`list`/`announce`(内存 session store) | `binding`/`scope`/`search`/`retain`/`using`(会话引用与作用域的面) |
56
- | `timer` | `timeout(delay): Promise<void>` | `timeout(callback, delay): () => void` |
57
- | `jobs` | 后台任务注册表(`list`/`observe`/`kill`) | 由 dsh-better-sidebar 软取的客户端任务面(`watchRows`/`state`) |
46
+ 2026-10-15规则复核:attachments.readImage由官方实现和真实描述符互证,保留仅该读取路径的影响;sessions.open保留明确的客户端旧回退语义;删除sessions.getSnapshot、settings.get及sessions.map的无依据影响条目;sessions.refresh有守卫且无足够语义证据,不新增规则。客户端运行时未采样,不宣称对应API已被删除。
58
47
 
59
- 所以方法级判定**必须按调用点所在的那一侧去比目录**。本表已给这些服务的方法逐条标 `layer`。拿宿主方法集去比客户端调用(或反过来)会造出成片假阳性——`sessions.scope`(客户端方法)被判成"宿主缺方法"就是这种错法。
48
+ ## 历史依据:覆盖率、方法与反例(2026-10-11,旧提取器)
60
49
 
61
- ## 覆盖率
50
+ > 以下表格、旧命令、完整性判断和“故意没填”均是2026-10-11旧阶段记录,不是现行操作规程;当前证据与更新命令以上方为准。
62
51
 
63
52
  > ⚠️ **这张表由 `node tools/coverage.mjs` 生成,是一台机器的一个 profile 的快照,不是生态普查。**
64
53
  > 它以前是**手工算的**,于是必然过期。**一张会过期却没人知道的覆盖率表比没有表更糟:它看起来像证据。**
@@ -79,7 +68,7 @@ node tools/coverage.mjs --probe <报告> --write # 顺带刷新 rule
79
68
  于是"落到默认档"这个数字既不是真实的缺口、也不是用户会看到的东西。
80
69
  实测差别:同一个 profile,不给面 7、给面 **3**。
81
70
 
82
- ### 当前值(2026-10-11,desktop profile,DSH 0.2.0-rc.2,**带运行时面**)
71
+ ### 当时值(2026-10-11,desktop profile,DSH 0.2.0-rc.2,**带运行时面**)
83
72
 
84
73
  | 口径 | 值 |
85
74
  |---|---|
@@ -112,7 +101,7 @@ node tools/coverage.mjs --probe <报告> --write # 顺带刷新 rule
112
101
  **所以规则表在"DSH API"这个范围内是完整的**:凡是真实存在、且被真实调用的宿主 API,
113
102
  要么有规则,要么已确证是提取产物。**剩下没规则的,都不是 DSH 的 API。**
114
103
 
115
- ## 新增条目的证据分列
104
+ ## 历史补充:新增条目的证据分列
116
105
 
117
106
  ### 一、有官方 API 目录直接依据(27 条)
118
107
 
@@ -155,7 +144,7 @@ node tools/coverage.mjs --probe <报告> --write # 顺带刷新 rule
155
144
  | `sessions.using` | `@michengai/dsh-archive-manager/lib/client.js`(`using` 不可用即退回 `binding`) |
156
145
  | `timer.timeout` | `dsh-recall-plugin/lib/client.js:1430`(不可用即用原生 `setTimeout`) |
157
146
 
158
- ## 故意没填的服务与原因
147
+ ## 历史补充:故意没填的服务与原因
159
148
 
160
149
  未进入本表的 18 个实际被用到的服务。**这一节和填了的那些一样重要**——乱填一个影响面,等于毁掉本工具唯一的资产。
161
150
 
@@ -194,39 +183,38 @@ node tools/coverage.mjs --probe <报告> --write # 顺带刷新 rule
194
183
 
195
184
  ### F. 提取器已正确处理,无需规则(1 个)
196
185
 
197
- `betterSidebar` —— 它是插件自己 `ctx.provide()` 出来的服务,**提取器已正确记入 `providedServices`**(同类的还有 dshmarket 的 `market`),因此不会产生假缺口。`pet` / `agentTeams` / `companionQq` / `betterLocale` 这些**没有**被 `ctx.provide()` 捕获的(见 `Plan.md` §5 A5),就落进了上面 A 组。
186
+ `betterSidebar` —— 它是插件自己 `ctx.provide()` 出来的服务,**提取器已正确记入 `providedServices`**(同类的还有 dshmarket 的 `market`),因此不会产生假缺口。`pet` / `agentTeams` / `companionQq` / `betterLocale` 这些**没有**被 `ctx.provide()` 捕获的(见 `docs/Plan.md` §2.1 的提供方证据约束),就落进了上面 A 组。
198
187
 
199
- ## 已知提取误报:不要为它们补规则
188
+ ## 历史补充:已知提取误报
200
189
 
201
190
  `severity.json` 里有一个 `knownExtractionArtifacts` 段,逐条登记了"看起来是 API 缺口、其实是正则产物"的 `服务.方法`,并给出证据。**这一节存在的意义是防止后来者(包括未来的我)出于好心把假阳性升格成"有影响面的确定结论"。**
202
191
 
203
- 最值得记住的两个(都是本轮新查出来的,不只是继承 `usage-inventory.json` 的已知缺陷):
192
+ 最值得记住的两个(当时新查出来的,不只是继承 `usage-inventory.json` 的已知缺陷):
204
193
 
205
194
  1. **alias 正则漏了词边界**:`lib/extract.mjs` 里 `ctx.get("x")` 的别名规则构造的正则是 `别名\.方法(`,**没有 `\b`**。于是源码里的 `subagents.listDescendants(` 会同时命中 `agents.listDescendants`——因为 `subagents` 里含子串 `agents.`。desktop profile 上正好 2 处。改前它一直以"影响面未知"的形式出现在结论里。
206
195
  2. **同文件内别名撞名**:dsh-better-sidebar 的 `lib/client.js` 里同时有 `const service = ctx.get("jobs")`(1 处)和 `const service = ctx.get("betterSidebar")`(6 处)。别名规则对文件内**每一次**匹配都按每个别名目标各记一次,于是文件里所有 `service.X()`——**包括 JSDoc 注释里的**——都被算到 `jobs` 头上。`jobs.getTabs`/`closeTab`/`registerTab` 等 13 个"方法"就是这么来的(`lib/client.js:977` 那处原文是注释里的一句 `service.getTabs()`)。
207
196
 
208
197
  其余登记项:`settings.isTrusted`/`reportNonstrict`/`useStrictBehavior`(KaTeX 的 `parser.settings`)、`settings.settingsGet`/`externalDisable`(better-sidebar 自有的 `SidebarSettingsFace`)、`locale.startsWith`(字符串方法)、`tools.filter/join/push` 与 `sessions.map/filter/slice/...`(数组方法)。
209
198
 
210
- ## 待复核的既有条目(本轮**没有改**)
199
+ ## 历史待复核登记(2026-10-11;现已按上文处理)
211
200
 
212
- 改这些条目会改变输出,按 `Plan.md` §4 第 3 条应先摊开差异再决定,所以只登记证据,列在 `severity.json` 的 `needsReview` 段:
201
+ 改这些条目会改变输出,按 `docs/Plan.md` §3 第 2 条应先摊开差异再决定,当时仅登记证据。以下保留当时的疑问;当前处理决定在needsReview.reviewed中:
213
202
 
214
203
  - `sessions.open` / `sessions.getSnapshot`:两个目录里都没有;客户端真正的跳转入口是 `uiWorkspace.openSession`。
215
204
  - `sessions.refresh`:两个目录里都没有(客户端目录里的是 `refreshProjections`)。
216
205
  - `settings.get`:0.2.0-rc.2 的宿主目录里 `settings` 没有 `get`,可能和 `settingsGet` 一样来自插件自有的设置面。
217
206
  - `attachments.readImage`:目录里是 `readImageRequest`,方法名对不上,需要一次运行时探针定音。
218
207
 
219
- ## 已知的提取缺陷(会影响本表的解读)
208
+ ## 当前提取边界
220
209
 
221
- `usage-inventory.json` 用的是与内核同源的正则启发式,里面有肉眼可见的误报。这带来两个后果,都已经写进设计:
210
+ 旧inventory来源于正则提取器;当前JSON已由有版本和完整性标记的AST样本替换,旧数值只保留在本页历史表。knownExtractionArtifacts也保留历史反例用途,并非当前引擎的黑名单。新的规则刷新必须记录DSH版本、目标profile、probe身份与时间,并逐条复核源码归属。
222
211
 
223
- - 每条发现必须带**确定性标注**(确定/疑似),提取不可靠时降级标注,而不是升格成确定;
224
- - 换真解析器(跟踪 `inject` 列表、`const { tools } = ctx` 解构、变量别名、作用域)是**必须完成的最终状态**,不是可选项。验收指标之一就是"疑似"比例明显下降。
212
+ 真解析器已经落地;“疑似比例明显下降”不作为验收指标,因为该比例还受运行时采样覆盖影响。当前已支持有界跨函数/包内模块值流,未知动态行为仍有诊断;不声称完整分析,边界见 [现行契约](../docs/Plan.md)。
225
213
 
226
- ## 怎么改
214
+ ## 当前维护原则
227
215
 
228
216
  直接改 `severity.json` 并说明依据。特别注意三类容易判错的:
229
217
 
230
218
  - **同名不同义**:`settings.register` 在客户端设置页和宿主设置文档里不是一回事;`sessions`/`jobs`/`timer` 两侧同名但 API 不同。别混。
231
- - **降级 ≠ 无害**:`logger` 缺失判 `degraded` 是因为功能不受影响;但 `jobs.onJobsChanged` 判 `degraded` 是因为它**有明确的轮询回退**,而不是因为它不重要。
232
- - **判不准就不要填**:落到默认档 + 明确的"影响面未知",比编一个看起来合理的影响面好得多。犹豫的写进上一节"故意没填",那是有价值的产出,不是欠账。
219
+ - **降级 ≠ 无害**:`logger`相关规则按日志/诊断影响描述,不外推全部业务功能均不受影响;但 `jobs.onJobsChanged` 判 `degraded` 是因为它**有明确的轮询回退**,而不是因为它不重要。
220
+ - **判不准就不要填**:落到默认档 + 明确的"影响面未知",比编一个看起来合理的影响面好得多。新疑问写入needsReview并保留来源和待核验范围,不把历史“故意没填”当永久结论。
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "description": "服务/方法 → 严重度 + 影响面。判定由这张表驱动,不由 LLM 决定;表未覆盖的 API 落到 defaults。",
4
4
  "_coverageBasis": {
5
- "usedServices": "rules/usage-inventory.json(快照)与 `node lib/extract.mjs ~/.dsh/profiles/desktop/node_modules <out>` 的真实输出(实测 58 个服务)",
5
+ "usedServices": "rules/usage-inventory.json:有版本、目标身份、AST来源及扫描完整性的新快照;旧数值仅在rules/README.md历史段保留",
6
6
  "serviceSemantics": "DSH 桌面端 app.asar 内官方生成的 API 目录(宿主 dsh-tool-cordis/lib/types/api-catalog.js、客户端 dsh-cordis-client-runner/lib/client.js)。每条 summary 与每个方法含义都取自它,不自行发明。",
7
7
  "coverageReport": "覆盖率的前后对比、\"有官方目录依据\"与\"从调用点推断\"的分列、以及**故意没填**的清单见 rules/README.md"
8
8
  },
@@ -11,7 +11,7 @@
11
11
  "summary": "这个服务是干什么的(人话,来自官方目录的 summary/description)",
12
12
  "severity": "只能是 fatal / feature-dead / degraded 三档之一,语义见 severityLevels",
13
13
  "impact": "一句人话:什么东西坏了,或者怎么个降级法。禁止\"可能有问题\"这类空话",
14
- "basis": "该条判定的证据等级:catalog = 官方 API 目录的 summary/description 直接支持;catalog-description = 目录没声明方法,但官方 description 里明确写了这个方法;callsite = 目录里没有,依据是真实插件在该调用点上的代码(会在 evidence 里给出文件:行)",
14
+ "basis": "该条判定的证据等级:catalog = 官方 API 目录的 summary/description 直接支持;catalog-description = 目录没声明方法,但官方 description 里明确写了这个方法;callsite = 目录里没有,依据是真实插件在该调用点上的代码(会在 evidence 里给出文件:行);official-source-runtime = 指定DSH版本官方实现与私有profile描述符观测互证",
15
15
  "evidence": "basis=callsite 时必须给出具体文件:行",
16
16
  "knownGoneIn": "已知在某个 DSH 版本起不再提供(沿用既有条目的写法)",
17
17
  "note": "补充说明;不影响判定"
@@ -120,7 +120,10 @@
120
120
  "methods": {
121
121
  "readImage": {
122
122
  "severity": "feature-dead",
123
- "impact": "无法读取图片附件,视觉相关功能失效"
123
+ "impact": "使用readImage读取图片附件的操作无法完成",
124
+ "basis": "official-source-runtime",
125
+ "evidence": "@deepseek-ai/dsh-attachment-local/lib/index.js:1035 (DSH0.2.0-rc.2); private official-web descriptor probe",
126
+ "note": "官方目录未列出但运行时确实存在;缺失判断以当次完整方法观测为准"
124
127
  }
125
128
  }
126
129
  },
@@ -483,15 +486,13 @@
483
486
  "impact": "会话列表为空",
484
487
  "layer": "host"
485
488
  },
486
- "map": {
487
- "severity": "feature-dead",
488
- "impact": "同上",
489
- "layer": "host"
490
- },
491
489
  "open": {
492
490
  "severity": "feature-dead",
493
- "impact": "点击后无法跳转到会话",
494
- "layer": "client"
491
+ "impact": "插件选择旧sessions.open回退路径时,会话跳转无法完成",
492
+ "layer": "client",
493
+ "basis": "callsite",
494
+ "evidence": "@michengai/dsh-archive-manager@1.0.19/lib/client.js:306",
495
+ "note": "仅客户端旧回退语义;主路径为uiWorkspace.openSession,不可用于宿主同名方法判定"
495
496
  },
496
497
  "fork": {
497
498
  "severity": "feature-dead",
@@ -503,11 +504,6 @@
503
504
  "impact": "会话绑定关系读不到",
504
505
  "layer": "client"
505
506
  },
506
- "getSnapshot": {
507
- "severity": "degraded",
508
- "impact": "拿不到会话快照,相关 UI 显示不全",
509
- "layer": "client"
510
- },
511
507
  "liveEntryFor": {
512
508
  "severity": "feature-dead",
513
509
  "impact": "拿不到会话在内存 store 里的条目 → “先定位条目再动手”的操作(归档/摘除)直接失败",
@@ -545,22 +541,26 @@
545
541
  "announce": {
546
542
  "severity": "degraded",
547
543
  "impact": "宿主不再广播会话变化,插件的会话列表不会自动刷新",
548
- "basis": "catalog"
544
+ "basis": "catalog",
545
+ "layer": "host"
549
546
  },
550
547
  "create": {
551
548
  "severity": "feature-dead",
552
549
  "impact": "建不了会话,插件无法开启新会话",
553
- "basis": "catalog"
550
+ "basis": "catalog",
551
+ "layer": "host"
554
552
  },
555
553
  "enter": {
556
554
  "severity": "feature-dead",
557
555
  "impact": "进不去会话,插件的跳转/激活失效",
558
- "basis": "catalog"
556
+ "basis": "catalog",
557
+ "layer": "host"
559
558
  },
560
559
  "flush": {
561
560
  "severity": "feature-dead",
562
561
  "impact": "会话变更写不下去,重启后这部分内容丢失",
563
- "basis": "catalog"
562
+ "basis": "catalog",
563
+ "layer": "host"
564
564
  },
565
565
  "openSubagent": {
566
566
  "severity": "degraded",
@@ -593,10 +593,6 @@
593
593
  "severity": "feature-dead",
594
594
  "impact": "设置改不动"
595
595
  },
596
- "get": {
597
- "severity": "feature-dead",
598
- "impact": "读不到设置值,插件按默认值运行"
599
- },
600
596
  "installSection": {
601
597
  "severity": "feature-dead",
602
598
  "impact": "走旧版 settings.installSection 通道注册的设置项不会出现(该通道在 0.2.0-rc.2 已被移除,插件需改用 Config 投影)",
@@ -1029,28 +1025,158 @@
1029
1025
  ]
1030
1026
  },
1031
1027
  "needsReview": {
1032
- "_note": "既有条目里**看起来可疑、但本轮没有改**的项。改它们会改变输出,按 Plan.md §4 第 3 条应先摊开差异再由人决定,所以这里只登记证据。",
1033
- "items": [
1028
+ "_note": "2026-10-15复核已完成;未验证的客户端运行时保持未知,不再列作未处理任务。",
1029
+ "items": [],
1030
+ "reviewed": [
1031
+ {
1032
+ "entry": "sessions.open",
1033
+ "decision": "retain-client-legacy",
1034
+ "reason": "客户端源码仅在uiWorkspace.openSession不可用时调用旧回退;宿主观测无open,客户端运行时未测,不能宣称已移除客户端API"
1035
+ },
1034
1036
  {
1035
- "entry": "sessions.open / sessions.getSnapshot",
1036
- "problem": "宿主与客户端两个官方目录里都没有这两个方法。客户端侧真正的跳转入口是 uiWorkspace.openSession,archive-manager 也是把它当回退用的。",
1037
- "evidence": "@michengai/dsh-archive-manager/lib/client.js(typeof openSession === 'function' ? openSession : N.sessions.open)"
1037
+ "entry": "sessions.getSnapshot",
1038
+ "decision": "remove-unsupported-impact",
1039
+ "reason": "宿主描述符无此方法,当前调用来自observable store等对象;无证据把store快照解释为sessions服务方法"
1038
1040
  },
1039
1041
  {
1040
1042
  "entry": "sessions.refresh",
1041
- "problem": "两个官方目录都没有它(客户端目录里有的是 refreshProjections)。无法判断是旧版 API 还是误报,故不补规则。",
1042
- "evidence": "@michengai/dsh-archive-manager/lib/client.js(typeof 守卫后调用)"
1043
+ "decision": "no-rule",
1044
+ "reason": "客户端源码使用typeof/可选调用守卫;宿主无此方法,不能为可选旧seam发明失效影响"
1043
1045
  },
1044
1046
  {
1045
1047
  "entry": "settings.get",
1046
- "problem": "0.2.0-rc.2 的宿主目录里 settings 没有 get(只有 configure/describe/mutate/prepareDocument/replace/update)。它可能和 settingsGet/externalDisable 一样来自插件自有的设置面。",
1047
- "evidence": "rules/dsh-api-catalog.json 的 host.settings.methods"
1048
+ "decision": "remove-local-object-impact",
1049
+ "reason": "宿主无get;better-sidebar buildApi中的getSettings返回局部settingsFace,非ctx.settings;没有证据支持插件必然按默认值运行"
1048
1050
  },
1049
1051
  {
1050
1052
  "entry": "attachments.readImage",
1051
- "problem": "宿主目录里是 readImageRequest,没有 readImage。条目本身写得通顺(“无法读取图片附件”),但方法名对不上,需要一次运行时探针来确认到底哪个是真的。",
1052
- "evidence": "rules/dsh-api-catalog.json 的 host.attachments.methods"
1053
+ "decision": "retain-runtime-confirmed",
1054
+ "reason": "官方目录省略此方法;运行时完整描述符与官方实现均确认存在,仅约束该读取路径影响"
1055
+ },
1056
+ {
1057
+ "entry": "sessions.map",
1058
+ "decision": "remove-historical-artifact-rule",
1059
+ "reason": "旧集合误报留下的“同上”没有服务语义依据;真实Context上的同名调用不应被黑名单删去,也不应套用伪造影响"
1053
1060
  }
1054
- ]
1061
+ ],
1062
+ "evidence": {
1063
+ "dshVersion": "0.2.0-rc.2",
1064
+ "probedAt": "2026-10-15T12:50:23.833Z",
1065
+ "profile": "private official web template (removed after sampling)",
1066
+ "profileIdDigest": "09a867e8c1a29a9f5ebe23b5d30efe92ae1218429f297c2bed92ba1bffae9c5a",
1067
+ "sampleId": "a45631f6-4603-4a57-b663-0b8fece40f8f",
1068
+ "runtimeMethods": {
1069
+ "attachments": {
1070
+ "status": "present",
1071
+ "namesComplete": true,
1072
+ "methods": [
1073
+ "__defineGetter__",
1074
+ "__defineSetter__",
1075
+ "__lookupGetter__",
1076
+ "__lookupSetter__",
1077
+ "admitEncodedFile",
1078
+ "admitPromptContent",
1079
+ "constructor",
1080
+ "fileHostPath",
1081
+ "hasOwnProperty",
1082
+ "imageHostPath",
1083
+ "isAttachmentError",
1084
+ "isPrototypeOf",
1085
+ "propertyIsEnumerable",
1086
+ "readFileStream",
1087
+ "readImage",
1088
+ "readImageRequest",
1089
+ "requestVersion",
1090
+ "saveFile",
1091
+ "saveFileStream",
1092
+ "saveImage",
1093
+ "saveImages",
1094
+ "toLocaleString",
1095
+ "toString",
1096
+ "validateImage",
1097
+ "validateImageBatch",
1098
+ "valueOf"
1099
+ ]
1100
+ },
1101
+ "sessions": {
1102
+ "status": "present",
1103
+ "namesComplete": true,
1104
+ "methods": [
1105
+ "__defineGetter__",
1106
+ "__defineSetter__",
1107
+ "__lookupGetter__",
1108
+ "__lookupSetter__",
1109
+ "_forkBoundary",
1110
+ "_resolveForkSource",
1111
+ "announce",
1112
+ "constructor",
1113
+ "create",
1114
+ "detachEntered",
1115
+ "emitDisposed",
1116
+ "enter",
1117
+ "flush",
1118
+ "fork",
1119
+ "get",
1120
+ "hasOwnProperty",
1121
+ "isPrototypeOf",
1122
+ "list",
1123
+ "liveEntryFor",
1124
+ "prepare",
1125
+ "propertyIsEnumerable",
1126
+ "registerMessageProjection",
1127
+ "toLocaleString",
1128
+ "toString",
1129
+ "valueOf"
1130
+ ]
1131
+ },
1132
+ "settings": {
1133
+ "status": "present",
1134
+ "namesComplete": true,
1135
+ "methods": [
1136
+ "__defineGetter__",
1137
+ "__defineSetter__",
1138
+ "__lookupGetter__",
1139
+ "__lookupSetter__",
1140
+ "configure",
1141
+ "constructor",
1142
+ "describe",
1143
+ "hasOwnProperty",
1144
+ "importLegacyDocument",
1145
+ "invalidate",
1146
+ "isPrototypeOf",
1147
+ "mutate",
1148
+ "prepareDocument",
1149
+ "propertyIsEnumerable",
1150
+ "replace",
1151
+ "schema",
1152
+ "toLocaleString",
1153
+ "toString",
1154
+ "update",
1155
+ "valueOf",
1156
+ "write"
1157
+ ]
1158
+ }
1159
+ },
1160
+ "sources": [
1161
+ {
1162
+ "package": "@michengai/dsh-archive-manager",
1163
+ "version": "1.0.19",
1164
+ "file": "lib/client.js",
1165
+ "sha256": "e427fb55c15c3e7b09404d17a35b6a81369434b5eacd9107388b87517bbcf5d5"
1166
+ },
1167
+ {
1168
+ "package": "dsh-better-sidebar",
1169
+ "version": "0.26.0",
1170
+ "file": "lib/index.js",
1171
+ "sha256": "9b2855f2e3d5b8c94432af6c7e353b4266211e9cb87fd6419e37d69b8e93389e"
1172
+ },
1173
+ {
1174
+ "package": "@deepseek-ai/dsh-attachment-local",
1175
+ "dshVersion": "0.2.0-rc.2",
1176
+ "file": "lib/index.js",
1177
+ "sha256": "dfe6e517a8aef173a0eb00753344dc91f2abf49d3bf7614dcc0198e426a1e94e"
1178
+ }
1179
+ ]
1180
+ }
1055
1181
  }
1056
1182
  }