dsh-recency-guard 0.12.1 → 0.17.0

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/README.md CHANGED
@@ -1,426 +1,316 @@
1
- ## v0.10 新增(可观测性数学增强 + 洞察卡 + 插件信息/反馈入口)
2
-
3
- - **转移矩阵卡(马尔可夫链)**:阶段行为的「去向分布」——monitoring 是健康值守
4
- (80% 留在自己)还是高危巡逻(40% 掉进 crashed)一眼可见;幂法迭代求平稳分布
5
- (ε=1e-9,L∞ 判敛),回答「长期来看守护停在哪个阶段」。样本不足时诚实显示
6
- 「不可判定」,白名单外迁移被丢弃且如实计数。
7
- - **控制带(休哈特单值-MR)**:崩溃间隔的 x̄±3σ̂(σ̂=MR̄/1.128)控制带,
8
- 区分「偶然波动」与「特殊原因」——哪一次间隔越界(崩溃变密)被点名到序号;
9
- 与 CUSUM 变点、回归斜率、生存分析互为视角(趋势看方向,控制带看单点越界)。
10
- - **数据流卡**:journal 原文 → 事件投影 → 语义流动 → 面板渲染的四环链路白盒,
11
- 每环带说明与计数 —— 「画面上的事实从哪来、经过什么变换」不再靠猜。
12
- - **系统诊断卡 + 插件信息面板 + 反馈入口(R18)**:插件版本 / 宿主版本 / 运行环境 /
13
- **分区键** / 数据面状态 / 快照耗时一卡尽览;仓库链接来自 package.json 的
14
- `repository`/`bugs`,「问题反馈」按钮直达 GitHub Issues(未登记时隐藏按钮,
15
- 绝不放死链);工具 `status` 的命令窗口输出同步携带这两条。
16
- - **命令窗口显示修复(R10)**:`rescue.mjs` 的 status/history 输出按 CJK 显示宽
17
- (全角 2 列)对齐 —— 中文标签与英文值混排不再错位(rescue 零依赖纪律不变,
18
- 宽度判定内联实现)。
19
-
20
- ## v0.9.1 新增(判定可视化)
21
-
22
- - **恢复判定条**:崩溃后的分级判定现在到达用户可见面 —— 面板生命周期卡片顶部
23
- 显示「观察中 / 已行动」标签 + 完整判定理由 + 进度(第 N/M 轮,act=false 虚线中性色,
24
- 「观察中」不是错误);工具 `status` 的 facts 同步携带。undefined(从未崩溃过)
25
- 不渲染假判定。
26
- - **事件流高亮两阶段**:观察轮/恢复判定/两阶段对账的 journal note 在事件流卡片
27
- 用 warn/success 徽标区分(识别 detail 前缀派生语义,存储词汇不变)。
28
-
29
- ## v0.9 新增(恢复置信分级 + 两阶段提交 + CLI 逃生通道 + 卡片限高)
30
-
31
- - **恢复置信分级**:唯一嫌疑是确定性证据(第 1 轮即关闭);多嫌疑是概率性证据,
32
- 同一头号嫌疑连续 N 轮(默认 2,可配 `confirmRounds`)复现才自动关闭。
33
- 偶发崩溃不再误伤无辜插件 —— 观察轮的崩溃证据链完整保留,供下次判定。
34
- - **两阶段提交 + 重启对账**:关闭动作分 prepared(记录待执行)→ quarantined
35
- (已生效)两阶段;崩溃打断时下次启动按磁盘现状对账(confirmed/reverted),
36
- 不留半成品状态。
37
- - **CLI 逃生通道**:`node bin/rescue.mjs status|history|decision|archives
38
- --profile-dir <dir>` —— 宿主起不来时离线读取守护产物(零依赖、只读、
39
- 语义化退出码 2 = 有未恢复崩溃,供脚本联动)。
40
- - **面板备份策略卡片**:时间线备份开关/保留份数/备份根/配置编辑服务可用性
41
- 一目了然(只读投影;改配置走宿主设置页)。
42
- - **卡片固定高度 + 扩大**:每张展开卡片内容区限高 240px 内部滚动(窄侧栏
43
- 180px),视口密度均匀;卡片头部 ⤢ 按钮临时解除限高摊开阅读,与折叠态独立。
44
-
45
- ## v0.8 新增(备份增强 + 官方实验插件观测 + 事件驱动)
46
-
47
- - **时间线备份**:插件更新时自动把「更新前」的配置快照存为 `<版本>@<时刻>.json`,
48
- 每插件默认保留 5 份(可配),更新前后的配置可逐版对账 —— 回答「升级后行为变了,
49
- 是配置变了还是 bug」。工具 `action=backups` 同时报告最近备份与每插件时间线。
50
- - **自定义备份目录**:配置 `backupRoot` 或环境变量 `RECENCY_GUARD_BACKUP_ROOT`;
51
- 自定义根下仍按分区键分层,多项目隔离不因集中存放而失效。
52
- - **官方实验插件观测**:`@deepseek-ai/dsh-experimental-*` 只记录不关闭
53
- (journal `official-observe` 事件 + 整组状态的时间线备份)。它们是官方的
54
- 实验场,崩溃概率最高,但自动关闭官方组件的风险不可接受 —— 现在崩溃归因时
55
- 能回答「当时装了哪些实验组件、什么版本、什么状态」。
56
- - **事件驱动变更检测**:订阅宿主 `plugin-manager/changed` 事件(500ms 防抖),
57
- GUI/工具路径的安装更新**立即**进时间线与嫌疑面,不用等重启。CLI/手工编辑
58
- 不发此事件,仍由每次启动的同步 diff 兑底 —— 事件是加速器,不是唯一真相。
59
- - **嫌疑排名显示修复**:数据层不再截断(曾 slice(0,10) 且渲染又 limit 5,
60
- 「报 N 只列 5」);命令窗口列 15 条带省略计数,面板默认 15 条 + 「展开其余 N 个」。
61
-
62
1
  # dsh-recency-guard
63
2
 
64
- **插件崩溃恢复守护** —— 解决"添加或更新第三方/实验性插件导致 DSH 崩溃、必须禁用所有插件再逐个添加"的问题。
65
-
66
- 当某个插件把 DSH 拖垮时,本插件会在崩溃的那一刻记录下"谁最可疑",并在**下一次启动时、在崩溃插件的代码被加载之前**把它挡在门外。反复崩溃时,关闭的插件会逐个增加(第 1 次关最近更新的 1 个,仍崩溃则关最近更新的 2 个)。稳定之后,除首要嫌疑外,其余被关闭的配置会自动恢复。
67
-
68
- ## v0.12.1 修复(两条均由真机用户旅程发现)
69
-
70
- - **崩溃信号补第二条通道(P0 检测缺口)**:崩溃信号此前**只有哨兵文件**,而哨兵写在
71
- `process.on('exit')` 里 —— SIGKILL / OOM killer / 断电 / 原生段错误**根本不运行
72
- exit 处理器**。真机实测:强杀后 `bootMarker` 残留(pid 已死)而 `crashSeq` 仍是 0,
73
- 下一次启动报「启动时未发现上次崩溃」。
74
- 危险的不是少记一次,而是**崩溃循环失去升级依据**:插件以原生方式撞死宿主时,
75
- 每一轮都只看到残留标记、每一轮都被当成干净启动清零计数 ⇒ 永远停在「关 0 个」。
76
- 新增 `classifyAbnormalExit` 四种结局:
77
- - `clean`(无残留 / 有哨兵)⇒ 归零;有哨兵时走既有通道、**不重复计数**;
78
- - `self-reload`(残留 pid == 本进程,热重载/HMR)⇒ 归零 + 留痕;
79
- - `concurrent`(残留 pid 仍活着且不是自己)⇒ 归零 + 留痕「另一个实例在运行」;
80
- - `abnormal`(残留 pid 已死)⇒ 计数 +1,累计到 `confirmRounds` 才动手。
81
- 异常终止按**概率性证据**处理(残留标记也可由人为杀进程/关机产生):第 1 轮只观察、
82
- 第 2 轮才行动,达阈值后复用既有 `recover()` 通道关闭(不另写一套关闭逻辑,
83
- 避免两条通道的隔离语义各自漂移)。真机两次强杀验证:
84
- `1/2 轮 act=false` → `2/2 轮 act=true 关闭 1 个`。
85
- 顺带修了引出它的二阶缺陷:原 `recovering && !crashedLastRun` 把「没有哨兵」
86
- 当「活过了启动」的代理,新通道下异常终止同样没有哨兵 ⇒ 会把「关了嫌疑 → 又死一次」
87
- 读成「崩溃已过」而恢复全部插件,亲手拆掉刚建立的隔离。现改为要求
88
- `exitClass === 'clean'`,并拆成纯函数 `shouldRestoreAfterBoot` 用 5 条单测钉住。
89
- - **小地图点击无反应(真机 GUI 实测)**:`setPointerCapture?.(id)` 排在
90
- `onNavigate(...)` **之前**,而可选链只保护「方法不存在」、**不保护方法抛异常**。
91
- 浏览器在 pointerId 失效时按规范抛 `NotFoundError` ⇒ 整条 handler 中断 ⇒
92
- 点小地图毫无反应(只有控制台有痕迹)。一个只为「拖拽连续性」存在的可选优化,
93
- 否决了「定位到那一块」这个主功能。
94
- 修法:捕获/释放各自 `try` 包裹并抽成 `capturePointer`/`releasePointer`;
95
- 新增 3 条**源码门**(`minimap-gesture.test.ts`)钉住「不得出现裸调用」,
96
- 并经**变异自证**会响(写回裸调用 ⇒ 恰 1 条断言 FAIL)。真机复验:
97
- 导航生效且 0 未捕获异常。
98
-
99
- ## v0.12 新增(命令窗口显示接线 · 乐观并发 · L4 审计线 · 依赖代际对齐)
100
-
101
- - **命令窗口显示修复(根因级)**:`presentCall` / `presentResult` /
102
- `output.presentationMeta` 三个展示投影**此前写好了却没接进 `defineTool`** ——
103
- 宿主因此落回通用回退卡片:标题是裸工具名 `recency_guard`、正文是整坨参数 JSON。
104
- 接线后待执行卡片显示人话摘要(`事件时间线 · 最多 40 条`),完成卡片显示
105
- 带标题与可折叠分区。同时把本地视图类型换成宿主词汇(`ToolCallView` /
106
- `ToolResultView`,`content` 是 `ContentBlock[]` 而非 `string`)—— 类型漂移
107
- 正是「写了不消费」的恶果。反漂移双门:契约源码门 + 宿主行为门(走宿主同一条
108
- 数据流 `execute → presentationMeta → presentResult`,含旧记录回放的诚实降级)。
109
- - **乐观并发 revision(防「过期按钮」)**:面板快照带一枚从
110
- state+decision+journal 尾派生的 12 hex 哈希;动作请求携带
111
- `expectedRevision`,凭证过期 ⇒ **409 且动作不执行**(不是执行了再报错),
112
- 并回传当前凭证供重读。用哈希而非计数器:计数器需要单调持久位点,崩溃时
113
- 会落后于状态(假冲突/假放行),哈希幂等 ⇒ 崩溃重启后同一事实必得同一枚。
114
- 缺参数 400 优先于 409;不带凭证放行(agent 主动修复路径);跨分区只读视图
115
- 恒 `null`(只读无可改事实,带凭证必然假冲突)。面板动作已带凭证 + 冲突提示条。
116
- **同源性是结构保证**:两处装配唯一收敛到 `Guard.revisionFactsFrom`,
117
- 并由端到端门断言「面板凭证 == 动作路径凭证」,该门经变异实验自证会响。
118
- - **L4 审计线**:写动作(recover/restore/reenable/archive)在**执行之前**
119
- 落一条审计行 `{actor, action, justification}`(崩溃发生在动作中间时理由已在盘上);
120
- 读动作零噪声(否则面板每 5s 轮询会把崩溃因果链冲掉)。复用 journal 的 `note`
121
- 通道而不是新开 kind —— kind 是跨层词汇表,漏改任一处审计行就在事件流里静默消失。
122
- - **依赖代际对齐宿主(阻断级修复)**:`cordis` peer 从 `~4.0.5` 改为
123
- `>=4.0.5 <5.0.0`。宿主实际携带 cordis **4.1.0** ⇒ 旧范围下 npm 安装会被
124
- 兼容闸门**整体拒绝并回滚**;桌面端用 `link:` 直连源码从不走闸门,所以这个洞
125
- 在本机完全不可见。devDependencies 同时从 0.2.1-rc.5 全量对齐装机代际
126
- **0.2.2-rc.1**(此前测试跑在一个从未有人装过机器的代际上:绿灯不代表装机绿)。
127
- 新增「以宿主真实事实为见证」的契约门 11 条:逐 peer 用 `runtime/`(从 app.asar
128
- 解出)的装机版本做 `semver.satisfies` 判定 + devDep == 装机版本断言。
129
- - **dsh.compatibility.dshReleases**:逐发布线声明 compatible(借鉴 dsh-context),
130
- 市场索引与兼容闸门可逐版本判定,而不是只看 engines 区间。
131
- - **图谱交互补齐**:k 跳邻域聚焦(原「只看关联」是传递闭包 ⇒ 连通图上等于
132
- 不过滤,即「点了没反应」)、小地图 Overview(可点可拖的缩略导航,往返误差
133
- < 1e-9)、边宽 = 连接度对数刻度、键盘方向键二维导航 + `aria-keyshortcuts`。
134
- - **技术债清理**:数据层显示截断 4 处(「报 N 只列 5」同族,含搜索指示器
135
- 对 20 个命中说「第 1/8 个」的假话)改为数据给全量 + 渲染层显式省略计数;
136
- i18n parity 测试补上(回退链会掩盖缺键:只在 zh 加键则英文露中文原文而 CI 全绿);
137
- 路由日志面桥回宿主 logger(console.error 不进宿主日志采集面);
138
- 防抖计时器持有 + `unref()` + dispose 清理(热重载不留幽灵定时器)。
139
- - **复杂度治理**:工具注册抽出 `tool-registration.ts`;小地图几何抽出
140
- `minimap-model.ts`(graph-model 117 → 101;且刻意从 `LAYOUT` 各自直读避免
141
- graph-model ⇄ minimap-model 循环依赖 —— 循环里的常量 TDZ 会让
142
- 「小地图矩形尺寸随机为 0」只在一台机器上复现)。
143
-
144
- ## v0.11.4 触屏双指缩放(图谱交互补齐)
145
-
146
- - **双指捏合缩放**:两指同时按下即进入捏合 —— 中点下的世界点在缩放中保持
147
- 不动(与单指缩放同一不变式),同时中点位移会带动画布平移;捏合期间取消
148
- 画布平移拖拽,抬指后清空取样。退化输入(两指重合/非有限)返回原视口,
149
- 绝不产生 Infinity。
150
- - 回归:viewport 新增 4 条捏合断言(翻倍缩放/锚点不动/跟随平移/退化与上界)。
151
-
152
- ## v0.11.3 图谱交互补齐(边可交互 + 布局过渡)
153
-
154
- - **边可交互(审计 P0 收尾)**:此前边完全不可点击、`edge.label` 是死数据。
155
- 现在每条边叠加透明宽描边热区:悬停高亮 + 指针变化、点击显示**边详情侧栏**
156
- (生产方 → 消费方 · 语义 · 边自带 label);选中边的语义标签与数据标签
157
- 分别绘制。选中态与节点详情**互斥**(单一真相),Esc / 空白处点击 /
158
- 「重置」都会清空。
159
- - **布局切换过渡**:节点坐标改用 CSS transform,布局切换与拖拽回弹有
160
- 过渡动画;拖动中的节点禁用过渡(否则「追不上指针」);
161
- `prefers-reduced-motion` 由全局规则降级。
162
- - 回归:`edgeDetailOf` 纯函数新增 3 条断言(端点解析/字段带出/幽灵端点 null)。
163
-
164
- ## v0.11.2 紧急修复(恢复诚实性 + 启动恢复时序)
165
-
166
- - **恢复结果白盒化(P0 假绿修复)**:`reenable` 此前**无条件**返回
167
- `reenabled: true` 并从 `autoDisabled` 移除 —— 实测管理器返回
168
- `application: 'failed'`(HMR 嵌套事务)时,state 仍记「已恢复」,面板/工具
169
- 显示成功而插件实际仍关闭。现在只有**真的 applied** 才算成功;失败者保留在
170
- 自动关闭名单待下轮重试,journal/note 如实列出「未启用(管理器拒绝)」。
171
- - **启动恢复延后到 HMR 事务之外**:boot 阶段 Loader 自身可能正处在事务中,
172
- 恢复调用会报 `HMR transactions cannot be nested`。自动恢复改为 boot 后
173
- 1.5s 执行(定时器 unref,不拖住宿主退出),失败由诚实结果兜底。
174
- - 回归:host 工具测试新增「管理器拒绝 ⇒ reenable 报失败」断言;14/14 全绿。
175
-
176
- ## v0.11.1 紧急修复(宿主兼容 + 启动崩溃 · 2026-11-08)
177
-
178
- **事故背景**:桌面端 DSH 自动升级到 `dsh 0.2.2-rc.1` 后连续崩溃(5 次/3 分钟),
179
- 守护插件被兼容闸门移出 profile、不再保护;重新挂载后又因遗留写锁把宿主拖进
180
- `fatal load failure`。本版本修复三处,全部是「守护不能拖垮宿主」的底线问题。
181
-
182
- - **宿主预发布版本兼容(P0)**:`engines.dsh` 与全部 `@deepseek-ai/dsh-*`
183
- peer 范围改为显式预发布分支
184
- `>=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0 || >=0.2.3-rc.1 <0.3.0-0`。
185
- 原因:node-semver 只放行「与范围中某个比较符 **major.minor.patch 元组相同**
186
- 且自身带预发布标签」的预发布版本 —— 旧范围 `>=0.2.1-rc.5 <0.3.0-0` 会
187
- 静默排除 `0.2.2-rc.1`,插件被兼容闸门判为不兼容并从 profile 移除。
188
- 已在 `dsh 0.2.2-rc.1` 干净沙箱(sb-rg-v0111)实测安装 + 启动 + 守护激活通过。
189
- - **启动绝不抛出(P0)**:`boot()` 失败改为**降级留痕**(步骤记失败 + 警告
190
- 日志),不再 rethrow。此前一个可恢复的写锁超时会把宿主变成
191
- `dsh: fatal load failure`(退出码 1)—— 观测/恢复设施不能拖垮主流程。
192
- - **失效写锁自愈(P0)**:新增 `cleanStaleLocks`(Store.init 启动时清理 +
193
- `updateState` 超时后清理重试一次)。判据:锁内容为空/非正整数 PID(不完整
194
- 记录,atomic-write 永远无法接管)/ PID 对应进程已死,且文件年龄 > 5s
195
- (避开锁创建与写 PID 之间的零字节窗口,绝不抢活锁)。
196
- 真机事故:崩溃恰好发生在锁创建与写 PID 之间,遗留 0 字节
197
- `state.json.lock`,此后每次启动都等满超时并致命。
198
- - **连续崩溃兜底升级(P0)**:`assessConfidence` 新增安全网 —— 累计崩溃
199
- `crashSeq ≥ 3` 时即使概率计数未达 `confirmRounds` 也强制行动;同时
200
- `readCrashHistory` 跳过启动时的「无嫌疑 crash-detected」记录。实测该记录
201
- 每轮打断连续计数,导致 crashSeq 涨到 3 仍停在「观察中 1/2 轮」,崩溃循环
202
- 永远无法自愈。
203
- - 测试:新增 `tests/unit/store-lock.test.ts`(锁自愈 11 断言)与契约层
204
- 预发布范围守卫;14/14 全绿(Core 7 / Host 2 / Client 4 / Contract 1)。
205
-
206
- ## v0.11 新增(数据面安全 + 图谱图例一致性 + 马尔可夫去向可见 + 市场准备)
207
-
208
- - **动作路由方法守卫(P0 安全修复)**:`/action/archive|reenable|restore|recover`
209
- 此前不检查 HTTP 方法 —— `GET /recency-guard/api/action/archive` 也会执行归档重置
210
- (浏览器预取/爬虫/误点链接都可能触发)。现在非 POST 一律 405 + `allow: POST`。
211
- - **`?partition=` 真正生效(P0)**:客户端一直拼分区查询参数,但数据面此前从不读它
212
- —— 跨分区查看永远静默返回当前分区。现在快照路由在宿主侧实现分区感知:
213
- 请求其它分区返回**只读磁盘投影**(状态/日志/决策/备份),运行时字段如实标注
214
- 归属,未知分区返回 `available:false`(绝不假绿)。
215
- - **面板动作面补齐**:新增 `/action/restore` 路由 + 面板「恢复非嫌疑插件」按钮
216
- (工具早有此动作,路由此前缺失);暂停轮询时 footer 显示「数据已停留 N 分钟」。
217
- - **图谱图例一致性(P0)**:图例颜色此前固定语义色调色板,切到「按类型」口径时
218
- 与节点实际颜色不一致;现在图例色**跟随当前着色口径**(legendColorOf),并直接
219
- 绘制节点形状(形状=第二编码通道,图例不展示它就只对知道映射的人可读)。
220
- - **搜索循环跳转**:多匹配时回车依次遍历(第 n/m 指示可见),不再永远停在第一个;
221
- 「重置」按钮现在同时清掉拖拽偏移(此前节点拖飞后只能刷新页面)。
222
- - **马尔可夫跨阶段去向(P0)**:矩阵卡此前只渲染自环概率条 —— `monitoring → 崩溃
223
- 40%` 这类核心去向分布(README 宣称的)在界面上根本看不到。现在每行带 top-2
224
- 跨阶段去向徽标。
225
- - **事件流展开**:默认 12 条 + 「展开其余 N 条」显式计数(与嫌疑排名同款纪律,
226
- 数据层给全量、显示层管截断)。
227
- - **测试盲区补齐**:新增 `tests/client/graph-model.test.ts`(过滤共源/布局确定性/
228
- 图例同源/平行边曲率)与 `tests/host/routes.test.ts`(方法守卫/partition/参数
229
- 校验/请假语义)—— 文件头「已由测试钉死」的注释如今成真。13/13 全绿。
230
- - **参考插件取长补短**:系统研究 dsh-ops(输出 notice 三要素)、dsh-compat-guardian
231
- (reconcile 自愈/原子写/乐观锁)、dsh-context(工作预算/资源自适应)、
232
- dsh-cookie-guard(独立救援 CLI)后按审计清单采纳落地;研究报告见
233
- `.cycles/cycles/cycle-6/参考插件研究报告.md`。
234
- - **插件市场准备**:仓库 topics(dsh-plugin 等)补齐、256px 图标、screenshots.json
235
- 声明、投稿 YAML 按 awesome-dsh-plugin contributing.md 形态定稿。
236
-
237
- ## v0.7 新增(崩溃级修复 + 可交互图谱 + 可观测性白盒化)
238
-
239
- **先说最要紧的一条:v0.6 会把 profile 写崩。**
240
-
241
- - **崩溃级修复:守护行 YAML 注入**。v0.6 写入守护行时 `name:` 没有加引号,
242
- 而 YAML 把 `@` 列为保留指示符 —— 于是任意 scoped 包(`@linxin666/...`)
243
- 都会让**整份 `cordis.patch.yml` 不可解析**,DSH 起不来。真机桌面 profile
244
- 已中招(第 185/189/193 行)。v0.7 改为「永远引号 + 写前校验 + 原子回滚 +
245
- pristine 备份」三层防线,并提供 `npm run repair-patch` 修复既有污染。
246
- - **静默失效修复**:`apply` 里直接读未 `inject` 的 `ctx.configEditor` 会抛错,
247
- 表现为「端口在听、UI 能开,但插件根本没激活」(日志只有一行
248
- `warning: 1 entry did not activate`)。改走 `tryReadService` 后消失。
249
- - **可交互图谱**:hover 高亮 + tooltip、点击选中联动详情、搜索过滤、
250
- 布局切换、缩放平移钳制;**fit 只做一次**,数据轮询不重置用户缩放
251
- (实测 7 秒轮询零变化)。滚轮改用原生非 passive 监听,
252
- 修掉「缩放时页面跟着滚」。
253
- - **面板自己滚动(v0.7.1)**:宿主槽位是 `overflow:hidden` 且整页不滚,
254
- 面板内容约 4000px 被裁到一屏 —— 下方指标/能力清单/归档根本看不到。
255
- 面板改为自带滚动容器(`height:100%` + `min-height:0` + `overflow-y:auto`
256
- + `overscroll-behavior:contain`),实测可滚到 `scrollTop=3361` 并看到底部归档区。
257
- 滚轮分工同步调整:**普通滚轮滚动面板,Ctrl/⌘ + 滚轮缩放图谱**
258
- (缩放入口仍在工具栏三个按钮上)。
259
- - **可观测性白盒化**:每个指标都带 `formula` / `inputs` / `sampleSize` /
260
- `confidence`;不可判定时 `defined=false` 且**给原因**("没有崩溃样本"),
261
- 绝不把无数据当 0。CUSUM 变点按**相邻崩溃间隔**回归(不是时间戳、不是累计计数)。
262
- - **能力闭环**:能力清单与注册处双向比对;`consumes`/`consumedBy` 方向修正;
263
- 21 项能力、对外暴露 12、连通率 100%、孤立 0(面板可见)。
264
- - **8 条数据面路由全部可用**(曾出现 `isolation` / `lifecycle` 500,
265
- 根因是 RouteContext 缺 provider)。
266
- - **模块拆分降复杂度**:`patch.ts` 142 → `patch-scalar.ts`(87) + `patch-yaml.ts`
267
- + `patch.ts` 门面,band-detector `Status: OK`。
268
-
269
- ## v0.6 新增
270
-
271
- - **崩溃压力(EWMA)**:新增 KPI「崩溃压力」——EWMA 平滑的近期崩溃密度
272
- (半衰期 5 次启动)。同样 1/3 的崩溃率,崩溃集中在最近时压力更高,
273
- 这是整体崩溃率给不出的时间局部性。无观测样本时诚实显示「不可判定」。
274
- - **归档摘要**:归档清单(工具 `archives` / 面板归档卡片)现在带 manifest
275
- 摘要——那次归档时的崩溃次数、事件数、被关插件。「举他对照」时能看到
276
- 基线事实,而不只是「归过档」。
277
- - **隔离可见性**:工具 `status` 输出新增「工作区 · 分区」行,命令窗口可
278
- 对账「这次守护是哪个项目的事实」;面板 header 显示可读项目名 +
279
- 完整工作区路径(悬停全文),哈希前缀退居兜底。
280
- - **图谱邻居导航**:节点详情侧栏新增上游(生产方)/下游(消费方)邻居
281
- 列表,点击即跳转定位——度数回答「有几条边」,邻居回答「连到谁」。
282
-
283
- ## v0.5 新增
284
-
285
- - **图谱交互二次增强**:节点拖拽重排(布局偏移跨轮询保持)、搜索定位(id/标签/引用三级优先匹配,回车跳第一个)、节点详情侧栏(完整 id + 度数 + 定位按钮)、双击节点视口居中、贝塞尔曲线边(同一对端点的平行边奇偶交替弯曲,方向可辨)、高亮边带生产/消费语义标签。
286
- - **面板现代化**:KPI 摘要条置顶(稳定性 SVG 圆环、当前阶段、崩溃率、趋势判定、CUSUM 变点一览);崩溃间隔统计(中位/CV)与 CUSUM 变点接入指标卡片;轮询暂停/恢复开关(调试截图时画面不再跳动);卡片可折叠(折叠态记入 localStorage,跨会话保持)。
287
- - **性能债清理**:图谱 id→node 索引由「每条边重建一次」改为「每个节点数组建一次」(O(E·V) → O(V)),节点上百时轮询不再卡顿。
288
-
289
- ## v0.3 新增
290
-
291
- - **能力闭环可证**:能力清单(注册/生产/消费)与实际注册面双向比对(契约测试 100% 层),三类缺口(消费断链/过度生产/孤立)可检出。
292
- - **生命周期状态机**:六阶段白名单迁移(idle/monitoring/crashed/deciding/stabilizing/recovered),非法迁移被拒且不改状态,迁移历史带原因与时刻。
293
- - **可量化可观测**:崩溃率/平均恢复耗时/检出率/隔离纯度/稳定性评分/风险评分/崩溃趋势七个读数,**不可判定时为 null 而非 0**。
294
- - **管理面板 + 可交互图谱**:`main` 与侧栏双席位,滚轮缩放以指针为锚、拖拽平移、点击高亮追溯(BFS)、Esc 一次退出;响应式(窄屏单列)、主题变量驱动。
295
- - **多项目隔离**:状态按**项目目录**分区(哈希键),A 项目崩溃不影响 B;守护行经指针文件搭桥。
296
- - **归档重置 + 举他**:归档 = 搬走 + 留档(manifest 先读后搬),重置后不继承旧结论、按最新 node_modules 事实重推嫌疑;另一工作区可只读清单作对照。
297
- - **质量评审体系**:Core/Host/Client/Contract 四层测试 + 类型/lint/构建/依赖审计全零门禁(npm audit high:0 critical:0)。
298
-
299
- ---
3
+ **v0.17.0** · [GitHub](https://github.com/xuanyuanchumo/dsh-recency-guard) · [问题反馈](https://github.com/xuanyuanchumo/dsh-recency-guard/issues) · MIT
300
4
 
301
- ## 目录
302
-
303
- - [它解决什么问题](#它解决什么问题)
304
- - [工作原理](#工作原理)
305
- - [安装](#安装)
306
- - [配置](#配置)
307
- - [工具](#工具recency_guard)
308
- - [数据文件](#数据文件)
309
- - [恢复流程](#恢复流程)
310
- - [安全边界](#安全边界)
311
- - [开发与测试](#开发与测试)
5
+ > DeepSeek Harness 的**插件崩溃恢复守护**:记录第三方 / 实验性插件的安装与更新时序并备份配置;
6
+ > 崩溃后的**下一次启动之前**,把最近更新的嫌疑插件挡在门外;稳定启动后,再把非首要嫌疑放回来。
7
+ >
8
+ > 版本基线(2026-11-11,北京时间):**0.17.0**。版本四锚点(`package.json` / 本 README /
9
+ > `docs/agents/` / [`dsh-plugin.json`](dsh-plugin.json))一致。
10
+ > 测试分四层,当前 **84/84 全绿**(Core 42 / Host 10 / Client 24 / Contract 8)。
312
11
 
313
12
  ---
314
13
 
315
14
  ## 它解决什么问题
316
15
 
317
- 在 DSH 里添加或更新一个第三方 / 实验性插件后,DSH 可能直接崩溃退出:
16
+ 在 DSH 里安装或更新一个第三方 / 实验性插件后,宿主可能直接退出:
318
17
 
319
18
  ```
320
19
  dsh: fatal uncaught exception: Error: ...
321
20
  ```
322
21
 
323
- 崩溃发生在插件启动阶段,所以此时**插件本身已经无法运行**,也就无法自己写任何恢复逻辑。传统做法是:手动禁用所有插件 → 重启 → 逐个加回 → 每次重启验证。这既慢又容易再次踩坑。
22
+ 崩溃发生在插件加载阶段,**插件自己已经来不及运行** —— 它的恢复逻辑、工具、面板都不可达。
23
+ 手动流程是:禁用所有插件 → 重启 → 逐个加回 → 每次重启验证。慢,而且容易再踩一次。
324
24
 
325
- 本插件把这件事自动化:
25
+ 本插件把这件事自动化成三步:
326
26
 
327
- | 步骤 | 传统做法 | 本插件 |
27
+ | 步骤 | 手动做法 | 本插件 |
328
28
  | --- | --- | --- |
329
- | 记录安装/更新日期 | 靠记忆 | 自动写入日志(崩溃也不丢) |
330
- | 备份插件配置 | 靠手动复制 | 自动备份到 `.recency-guard/backups/` |
331
- | 崩溃后定位嫌疑 | 靠猜 | 按"最近更新"排序 |
332
- | 禁用插件 | 手动改配置 | 自动写入决策文件,重启即生效 |
333
- | 反复崩溃 | 重复手工操作 | 第 2 次崩溃自动多关一个 |
334
- | 恢复配置 | 手动逐个加回 | 非首要嫌疑自动恢复 |
29
+ | 记录 | 靠记忆 | 安装 / 更新事件写进 `journal.jsonl`(崩溃也不丢) |
30
+ | 拦截 | 手动改配置 | 崩溃瞬间**同步**写决策文件;Loader 在 import 嫌疑插件**之前**求值守护行 |
31
+ | 恢复 | 逐个加回 | 稳定启动后恢复非首要嫌疑;首要嫌疑留给你确认 |
32
+
33
+ **它不做什么**:不修复宿主自身缺陷、不处理依赖版本不兼容、不阻止 entry 级 `apply` 抛错
34
+ (那是宿主的自隔离 warning,不是进程崩溃)。见下文「诚实边界」。
335
35
 
336
36
  ---
337
37
 
338
- ## 工作原理
38
+ ## 安装
339
39
 
340
- ### 1. 崩溃是怎么发生的
40
+ ### npm(推荐)
341
41
 
342
- DSH 的 Loader 会捕获插件 `apply` 中抛出的错误,让该插件失效但不影响其它部分。真正致命的是**未被捕获的 rejection / 未捕获的异常**:Harness 的 fail-loud 处理器会把它们变成一次 `exit(1)`,整个 DSH 随之退出。
42
+ ```bash
43
+ dsh plugin --profile <profile名> add dsh-recency-guard
44
+ ```
343
45
 
344
- 这意味着恢复**必须发生在崩溃插件被加载之前**,而不是之后。
46
+ 安装后**重启 dsh**。启动时本插件会自动:
345
47
 
346
- ### 2. 崩溃检测:进程级哨兵
48
+ - 在 profile 的 `cordis.patch.yml` 中为每个第三方插件写入守护行;
49
+ - 开始记录安装 / 更新事件与配置备份。
347
50
 
348
- Harness 在**正常退出和崩溃时都会**释放插件的 fiber,所以插件的 `dispose` 回调无法区分二者。能区分的是**退出码**:正常退出是 0,崩溃是 1。
51
+ 锁定版本:
349
52
 
350
- 因此本插件在**进程级**注册 `process.on('exit')`(不是 fiber 级 —— cordis 的 disposer 逆序执行,fiber 级的监听器会在进程真正退出前被移除):
53
+ ```bash
54
+ dsh plugin --profile <profile名> add dsh-recency-guard@0.17.0
55
+ ```
351
56
 
352
- - 退出码非 0 → 写入 `.recency-guard/crashed` 哨兵文件
353
- - 同时**同步**写出 `.recency-guard/decision.json` 恢复决策
57
+ > 版本对应:本 README 描述本仓库 **0.17.0** 源码。npm 上实际可安装的版本以
58
+ > `npm view dsh-recency-guard version` 为准;若 `latest` 落后于本文版本,请按下面的源码方式安装,
59
+ > 或等待对应版本发布。发布顺序见 [`docs/agents/conventions.md`](docs/agents/conventions.md) §6。
354
60
 
355
- ### 3. 恢复决策:在插件被加载之前生效
61
+ ### 插件市场
356
62
 
357
- Loader 支持给 patch 行写一个 `disabled: !!js "<表达式>"`。关键在于:**Loader 在导入该行的模块之前就会求值这个表达式**。
63
+ 投稿材料已按 [awesome-dsh-plugin contributing.md](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md)
64
+ 备好([`docs/market-submission.md`](docs/market-submission.md)):一个条目文件、`category: security`。
65
+ 上架后可在 dsh 的 **Plugins → Discover** 或 dsh-market 中搜索 `recency-guard` 安装。
66
+ **当前状态:材料已备、PR 未提交** —— 本文不声称已在市场中可搜到。
358
67
 
359
- 本插件为每个第三方插件在 profile 的 `cordis.patch.yml` 中写入这样一行(安装时自动完成):
68
+ ### 手动 / 本地源码(开发)
360
69
 
361
- ```yaml
362
- - id: some-third-party-plugin
363
- name: dsh-some-third-party-plugin
364
- disabled: !!js "(() => { try { const fs = process.getBuiltinModule('fs'); const profile = ctx.get('profileContext'); if (!profile || !profile.dir) return false; const file = profile.dir + '/.recency-guard/decision.json'; if (!fs.existsSync(file)) return false; const decision = JSON.parse(fs.readFileSync(file, 'utf8')); return Array.isArray(decision.disable) && decision.disable.includes('dsh-some-third-party-plugin'); } catch { return false } })()"
70
+ ```bash
71
+ git clone https://github.com/xuanyuanchumo/dsh-recency-guard.git
72
+ cd dsh-recency-guard
73
+ npm install
74
+ npm run build # 必跑:dsh plugin add file: 只拷贝 files 声明的产物,不执行构建
75
+ dsh plugin --profile <profile名> add file:/绝对路径/dsh-recency-guard
76
+ ```
77
+
78
+ 开发期反复改代码时用 `link:`(Windows 落 Junction),改完 `npm run build` 重启即生效:
79
+
80
+ ```bash
81
+ dsh plugin --profile <profile名> add link:/绝对路径/dsh-recency-guard
365
82
  ```
366
83
 
367
- 表达式读取上一轮崩溃时写下的决策文件,命中就把该插件挡在门外 —— **崩溃插件的代码根本不会被加载**,崩溃自然不会重演。
84
+ ### 安装顺序
85
+
86
+ 本插件必须排在被守护插件**之前**,才能在启动阶段完成守护行安装。确认 profile 的 `package.json`:
368
87
 
369
- > 表达式运行在 Loader 的 `with (ctx) { eval(expr) }` 作用域里:有 `ctx` 和 `process`,没有 `require`。`process.getBuiltinModule('fs')` 在该作用域可用,因此表达式能同步读到决策文件。
88
+ ```json
89
+ "dsh": {
90
+ "profile": {
91
+ "bundles": [
92
+ "@deepseek-ai/dsh-base",
93
+ "dsh-recency-guard",
94
+ "dsh-你的插件"
95
+ ]
96
+ }
97
+ }
98
+ ```
370
99
 
371
- ### 4. 为什么要"崩溃时"而不是"下次启动时"写决策
100
+ ---
372
101
 
373
- Loader 并发创建各个行。如果等到下次启动、守护插件自己加载时才写决策,决策可能**晚于**嫌疑插件那一行的求值,竞态会让嫌疑插件照常加载。
102
+ ## 三分钟上手
374
103
 
375
- 所以决策在**崩溃退出的那一刻**就写好:那时决策文件已经在磁盘上,下一次启动的第一个被求值的就是它。
104
+ 1. **重启 dsh 后打开面板**:侧栏图标「插件守护」→ 中央面板。首屏三块:生命周期、崩溃决策、能力清单。
105
+ 2. **用工具看状态**(会话里让模型调用,或直接看命令窗口):
376
106
 
377
- ### 5. 嫌疑排序
107
+ ```json
108
+ { "action": "status" }
109
+ ```
378
110
 
379
- 嫌疑 = profile 里声明的、**非官方**(`@deepseek-ai/*` 除外)、且**不是本插件自己**的依赖。排序依据:
111
+ 输出包含崩溃次数、嫌疑排名(全量)、自动关闭名单、声明式冲突(每条带
112
+ `文件:行号` 与声明原文)、上次隔离后仍治不了的 `unresolved`、隔离分区与量化指标;
113
+ 不可判定的指标显示**原因**,不显示 0。
114
+ 3. **看磁盘事实**:`<profile>/.recency-guard/projects/<分区键>/` 下的
115
+ `journal.jsonl` / `state.json` / `decision.json` 是全部真相,面板只是它们的投影。
116
+ 4. **想验证拦截**:不要在主 profile 做。用独立沙箱 profile 装一个**故意抛异常**的插件,
117
+ 启动 → 崩溃 → 再启动,观察决策文件与日志。步骤见
118
+ [`docs/agents/verification.md`](docs/agents/verification.md) §5.3。
380
119
 
381
- 1. 日志中的安装/更新事件(权威)
382
- 2. `node_modules` 的 mtime(首次运行时兜底)
120
+ ---
383
121
 
384
- 排除官方包是为了保证恢复机制永远不会误伤 harness 自身;排除本插件自己,是因为关掉它就等于关掉恢复机制。
122
+ ## 能力表
123
+
124
+ 每条能力的代码位置与验证入口都可点击核对。能力清单本身是**投影**,真相在注册处
125
+ (工具注册、路由注册、事件发射),由契约测试双向比对。
126
+
127
+ | 能力 | 你能看到什么 | 代码 | 验证 |
128
+ | --- | --- | --- | --- |
129
+ | 安装 / 更新时间线 | `history`、`journal.jsonl` | [`store.ts`](src/host/data/store.ts) | [`core.test.ts`](tests/unit/core.test.ts) |
130
+ | 嫌疑排序(最近更新 + 实验性标记 + 变更频率) | `status.suspectRanking`(全量) | [`recency.ts`](src/host/risk/recency.ts) | [`risk-frequency.test.ts`](tests/unit/risk-frequency.test.ts)、[`official-experimental.test.ts`](tests/unit/official-experimental.test.ts) |
131
+ | 风险评分与误伤校准(并行读数) | 面板「决策因子」卡 | [`risk-bayes.ts`](src/host/risk/risk-bayes.ts)、[`risk-calibration.ts`](src/host/risk/risk-calibration.ts) | [`risk-bayes.test.ts`](tests/unit/risk-bayes.test.ts)、[`risk-calibration.test.ts`](tests/unit/risk-calibration.test.ts) |
132
+ | **确定性冲突证据** | status / 面板「声明式冲突」(带 文件:行号) | [`conflict-declarations.ts`](src/host/guard/conflict-declarations.ts)、[`conflict-findings.ts`](src/host/guard/conflict-findings.ts)([`conflict-metadata.ts`](src/host/guard/conflict-metadata.ts) 转发) | [`conflict-findings.test.ts`](tests/unit/conflict-findings.test.ts)、[`deterministic-evidence.test.ts`](tests/unit/deterministic-evidence.test.ts) |
133
+ | **贪心最小隔离 + 依赖联动** | `recover` 关闭整组、残余 `unresolved` 上屏 | [`quarantine-plan.ts`](src/host/guard/quarantine-plan.ts)、[`crash-decision.ts`](src/host/guard/crash-decision.ts) | [`quarantine-plan.test.ts`](tests/unit/quarantine-plan.test.ts)、[`crash-decision.test.ts`](tests/unit/crash-decision.test.ts) |
134
+ | 崩溃决策 + 守护行 | 下次启动前拦截 | [`crash-decision.ts`](src/host/guard/crash-decision.ts)、[`decision.ts`](src/host/guard/decision.ts)、[`guarded-rows.ts`](src/host/guard/guarded-rows.ts)、[`patch.ts`](src/host/profile/patch.ts) | [`patch-safety.test.ts`](tests/unit/patch-safety.test.ts)、[`core.test.ts`](tests/unit/core.test.ts)、[`revision-source.test.ts`](tests/host/revision-source.test.ts) |
135
+ | 双通道崩溃检测(哨兵 + 残留标记) | 强杀 / 断电也能被发现 | [`sentinel.ts`](src/host/guard/sentinel.ts)、[`abnormal-boot.ts`](src/host/guard/abnormal-boot.ts) | [`fault-injection.test.ts`](tests/fault-injection/fault-injection.test.ts)、[`reload-journey.test.ts`](tests/host/reload-journey.test.ts) |
136
+ | 降级留痕与状态对账 | 重启后仍可查;「关了但 crashSeq=0」会被补账 | [`boot-orchestration.ts`](src/host/guard/boot-orchestration.ts)、[`state-reconcile.ts`](src/host/guard/state-reconcile.ts)、[`store.ts`](src/host/data/store.ts) | [`boot-degradation.test.ts`](tests/unit/boot-degradation.test.ts)、[`state-reconcile.test.ts`](tests/unit/state-reconcile.test.ts)、[`journal-append-race.test.ts`](tests/unit/journal-append-race.test.ts)、[`boot-crashseq-race.test.ts`](tests/unit/boot-crashseq-race.test.ts) |
137
+ | 恢复与配置备份 | `restore` / `backups` | [`recovery-actions.ts`](src/host/guard/recovery-actions.ts)、[`recovery-policy.ts`](src/host/guard/recovery-policy.ts) | [`core.test.ts`](tests/unit/core.test.ts)、[`versioned-backup.test.ts`](tests/unit/versioned-backup.test.ts) |
138
+ | 生命周期状态机 | 生命周期卡 + 迁移史 | [`lifecycle.ts`](src/host/core/lifecycle.ts) | [`lifecycle-steps.test.ts`](tests/unit/lifecycle-steps.test.ts) |
139
+ | 量化指标与数学模型 | 指标卡 / 控制带 / 转移矩阵 | [`observability.ts`](src/host/observe/observability.ts)、[`markov.ts`](src/host/observe/markov.ts)、[`control-band.ts`](src/host/observe/control-band.ts) | [`observability-math.test.ts`](tests/unit/observability-math.test.ts)、[`hazard-threshold.test.ts`](tests/unit/hazard-threshold.test.ts) |
140
+ | **预防性预警(只预警不行动)** | 面板/`status` 的启动频率控制带与配置变更 CUSUM;不可判定显示原因 | [`preventive.ts`](src/host/observe/preventive.ts)、[`preventive-view.ts`](src/host/surface/preventive-view.ts) | [`observability-math.test.ts`](tests/unit/observability-math.test.ts)、[`preventive-card.test.ts`](tests/client/preventive-card.test.ts) |
141
+ | **自适应刷新** | 稳定时 5s→10s→15s 退避,变化立即回 5s;暂停即真停 | [`poll-adaptive.ts`](src/client/panel/poll-adaptive.ts)、[`GuardPanel.tsx`](src/client/panel/GuardPanel.tsx) | [`poll-adaptive.test.ts`](tests/client/poll-adaptive.test.ts) |
142
+ | 管理面板 | 中央面板 + 侧栏图标 | [`GuardPanel.tsx`](src/client/panel/GuardPanel.tsx)、[`snapshot-view.ts`](src/client/panel/snapshot-view.ts)、[`PanelShell.tsx`](src/client/panel/PanelShell.tsx) | [`panel-v014.test.ts`](tests/client/panel-v014.test.ts)、[`panel-scroll.test.ts`](tests/client/panel-scroll.test.ts)、[`panel-null-snapshot.test.ts`](tests/client/panel-null-snapshot.test.ts) |
143
+ | 可交互能力图谱 | 节点 / 边 / 缺口 / 小地图 | [`GuardGraph.tsx`](src/client/graph/GuardGraph.tsx)、[`graph-interaction.ts`](src/client/graph/graph-interaction.ts) | [`graph-model.test.ts`](tests/client/graph-model.test.ts)、[`graph-focus.test.ts`](tests/client/graph-focus.test.ts)、[`graph-keyboard.test.ts`](tests/client/graph-keyboard.test.ts) |
144
+ | 项目隔离 | 隔离卡 + `?partition=<key>` | [`isolation.ts`](src/host/core/isolation.ts) | [`isolation-journey.test.ts`](tests/host/isolation-journey.test.ts)、[`isolation-parallel.test.ts`](tests/host/isolation-parallel.test.ts) |
145
+ | 归档与重置 | `archive` / `archives` | [`archive.ts`](src/host/data/archive.ts) | [`core.test.ts`](tests/unit/core.test.ts)、[`resilience-fallback.test.ts`](tests/unit/resilience-fallback.test.ts) |
146
+ | 工具 `recency_guard`(8 个 action) | 命令窗口卡片与文本 | [`tool-registration.ts`](src/host/surface/tool-registration.ts)、[`tool-dispatch.ts`](src/host/surface/tool-dispatch.ts)、[`tool-render.ts`](src/host/surface/tool-render.ts) | [`tool.test.ts`](tests/host/tool.test.ts)、[`display-truncation.test.ts`](tests/host/display-truncation.test.ts) |
147
+ | 写动作乐观并发(409 不执行) | 面板冲突提示 | [`change-guard.ts`](src/host/core/change-guard.ts)、[`routes.ts`](src/host/surface/routes.ts) | [`change-guard.test.ts`](tests/unit/change-guard.test.ts)、[`routes.test.ts`](tests/host/routes.test.ts)、[`api-conflict.test.ts`](tests/client/api-conflict.test.ts) |
148
+ | 能力清单双向比对 | 能力缺口卡 | [`capability-specs.ts`](src/host/capability/capability-specs.ts) | [`capability.test.ts`](tests/contract/capability.test.ts) |
149
+ | 离线逃生 CLI | 宿主起不来时查现场 / 止血 / 回插 | [`rescue.mjs`](bin/rescue.mjs) | [`rescue-cli.test.ts`](tests/unit/rescue-cli.test.ts) |
385
150
 
386
151
  ---
387
152
 
388
- ## 安装
153
+ ## 面板与图谱
154
+
155
+ ### 管理面板
156
+
157
+ - **数据面**:`GET /recency-guard/api/snapshot`。**自适应轮询**:内容稳定时 5s→10s→15s
158
+ 退避,内容变化/用户操作立即回 5s,绝对边界 [3s, 30s];暂停按钮按下后**不再发请求**
159
+ (画面停在暂停时刻,而不是继续跳)。
160
+ - **卡片**:稳定性 / 阶段 / 崩溃率 / 崩溃压力 / CUSUM 变点、**预防性预警**(启动频率控制带、
161
+ 配置变更 CUSUM;**只预警不行动**,不可判定显示原因)、崩溃决策与因子分解、
162
+ **声明式冲突**(每条带 `文件:行号` 与声明原文)、**未解决冲突**(上次隔离后仍治不了的部分,
163
+ 与「现在有冲突」分开显示)、量化指标(不可判定显示原因)、能力清单与缺口、项目隔离、归档、事件流。
164
+ - **可空契约**:宿主在跨分区只读视图会发 `null` / 整段省略的字段,由
165
+ [`snapshot-view.ts`](src/client/panel/snapshot-view.ts) 的 `PanelSnapshotView` 显式放宽,
166
+ 渲染层诚实降级,不把面板整块打进错误边界(契约测试
167
+ [`panel-snapshot-nullability.test.ts`](tests/contract/panel-snapshot-nullability.test.ts) 守护)。
168
+ - **折叠记忆**:折叠态存 `localStorage` 的 `recency-guard-collapsed`,读取失败退化为全展开,不抛异常。
169
+ - **动作**:恢复非嫌疑插件、重新启用某个插件、归档重置 —— 都携带快照的 `revision`;
170
+ 过期返回 **409 且动作不执行**,面板提示重读后再决定。
171
+ - **滚动**:面板自己滚(`height:100% + min-height:0 + overflow-y:auto`),页面不被撑高。
172
+ - **真机视觉验收**:卡片标题单行;数据流 4 步完整;面板已无渲染崩溃 ——
173
+ 此前 `intervals.cv=null` 会让整块白屏,现由客户端可空视图 + 契约测试钉住(见上)。
174
+
175
+ ### 可交互图谱
176
+
177
+ - **缩放**:`Ctrl/⌘ + 滚轮`(指针为锚),或工具栏 `+` / `-`;普通滚轮滚面板,不吞手势。
178
+ - **默认 fit-all**:首帧优先铺满全图;内容多时文字会偏小,可点工具栏「放大视图」。
179
+ 22 节点规模下首帧会自动放大到可读比例。
180
+ - **放大视图**:高度按**像素**(不是 `vh`),窗口变化不挤压图形;收起后**保留缩放**,
181
+ 不把用户视角打回默认。
182
+ - **平移 / 拖拽**:拖画布平移;拖节点做**用户叠加层**位移,数据轮询不会重置它。
183
+ - **选择**:单击节点高亮上下游;双击节点居中;点击边看边详情(可与节点详情互斥收起)。
184
+ - **搜索**:输入定位;`Enter` 在匹配项间循环并显示第 n / m 个。
185
+ - **图例**:按着色口径自动跟色(改配色图例跟着变),形状通道同样入图例。
186
+ - **小地图**:显示全图范围与当前视口,可点击 / 拖拽跳转。
187
+ - **键盘**:方向键在节点/边之间二维导航,`+` / `-` / `0` 缩放与复位,`Esc` 一次清空全部观察态,
188
+ `Enter` 循环搜索结果(以 [`GuardGraph.tsx`](src/client/graph/GuardGraph.tsx) 的 `aria-keyshortcuts` 为准)。
189
+ - **轮询不重置视口**:fit 只跑一次(有守卫),数据刷新不会把用户缩放打回默认。
190
+
191
+ > 本文**不贴截图**:仓库的 [`screenshots.json`](screenshots.json) 目前只声明了市场图标,
192
+ > 没有面板 / 图谱截图。宁可写清交互,也不放一张不存在的图。
193
+
194
+ ---
195
+
196
+ ## 工具:`recency_guard`
197
+
198
+ `execute` 返回**规范 JSON**(业务数据摊平在顶层),人读文本由 `output.render` 投影,
199
+ 命令窗口另接 `presentCall` / `presentResult` / `presentationMeta` 三条卡片通道。
200
+
201
+ | action | 参数 | 行为 |
202
+ | --- | --- | --- |
203
+ | `status` | — | 崩溃状态、嫌疑排名(全量)、自动关闭、**声明式冲突(带出处)**、**未解决冲突**、指标、工作区 / 分区归因 |
204
+ | `history` | `limit?=40` | 安装 / 更新 / 崩溃 / 恢复事件时间线 |
205
+ | `recover` | — | 按确定性冲突与依赖联动做**贪心最小隔离**;未命中声明时退回「按最近更新关闭」;残余 `unresolved` 上屏 |
206
+ | `restore` | — | 重新启用非首要嫌疑,首要嫌疑保持关闭 |
207
+ | `reenable` | `bundle` | 手动重新启用指定插件 |
208
+ | `backups` | — | 配置备份清单(含版本时间线) |
209
+ | `archive` | `reason?` | 归档当前产物并重置(先写审计行,再执行) |
210
+ | `archives` | — | 历史归档清单(带 manifest 摘要) |
211
+
212
+ 所有写 action 在执行**之前**先落一条审计行(`kind: note` + 结构化 `audit{actor,action,justification}`);
213
+ 审计写失败不阻塞动作。读 action 不写日志。
214
+
215
+ ---
216
+
217
+ ## CLI 逃生通道
218
+
219
+ 宿主起不来时,面板、工具、路由全都不可达 —— 这时用零依赖的 rescue CLI 直接读磁盘。
220
+ **默认只读**;写操作必须显式 `--yes`,且是两阶段、可精确回插的。
389
221
 
390
- ### 在主力 profile 中安装
222
+ ### 只读命令(默认绝不写盘)
391
223
 
392
224
  ```bash
393
- dsh plugin --profile <profile名> add dsh-recency-guard
225
+ node bin/rescue.mjs status --profile-dir "<profile目录>" [--project <key|label>]
226
+ node bin/rescue.mjs history --profile-dir "<profile目录>" [--limit 40]
227
+ node bin/rescue.mjs decision --profile-dir "<profile目录>"
228
+ node bin/rescue.mjs archives --profile-dir "<profile目录>"
229
+ node bin/rescue.mjs rescues --profile-dir "<profile目录>"
394
230
  ```
395
231
 
396
- 安装后重启 DSH。本插件会在启动时自动:
232
+ ### 可写命令(崩溃救火,默认 dry-run)
397
233
 
398
- - 在 profile 的 `cordis.patch.yml` 中为每个第三方插件写入守护行
399
- - 开始记录安装 / 更新日志
234
+ ```bash
235
+ # 1) 先看计划:把 decision.json 的 disable 名单(或 --bundle)从 profile bundles 摘掉
236
+ node bin/rescue.mjs apply --profile-dir "<profile目录>" [--bundle a,b] [--reason "事故说明"]
400
237
 
401
- > **安装顺序**:`dsh-recency-guard` 必须排在被守护插件**之前**,这样它才能在启动阶段完成守护行的安装。请在 profile 的 `package.json` 中确认:
402
- >
403
- > ```json
404
- > "dsh": { "profile": { "bundles": [
405
- > "@deepseek-ai/dsh-base",
406
- > "dsh-recency-guard",
407
- > "dsh-你的插件"
408
- > ] } }
409
- > ```
238
+ # 2) 确认后真正落盘
239
+ node bin/rescue.mjs apply --profile-dir "<profile目录>" --yes
240
+
241
+ # 3) 回插:默认取最新一条可回插记录;中断后用 --resume
242
+ node bin/rescue.mjs restore --profile-dir "<profile目录>" [--id <uuid>] [--resume] --yes
243
+ ```
410
244
 
411
- ### 从本地源码安装(开发)
245
+ - **两阶段**:先写 `prepared` 记录(含 `beforeHash` / `afterHash` / 每个 bundle 的原 index),
246
+ 再原子改 profile 的 `package.json`,最后标 `applied`;记录在
247
+ `<profile>/.recency-guard/rescue/<uuid>.json`。
248
+ - **中断恢复**:`restore --resume` 读 `prepared` 记录,用哈希判定第二步是否已生效 ——
249
+ `afterHash` 相符 ⇒ 按已生效回插;`beforeHash` 相符 ⇒ 第 2 步没发生,记录作废、不动 profile。
250
+ 回插按**原 index 升序**执行,不会互相挤位。
251
+ - **拒绝而不是硬改**:现场哈希不符 / 记录被篡改 ⇒ 退出码 `3`,不覆盖。
252
+ - **退出码**:`0` 正常;`1` 参数错误;`2` 发现未恢复的崩溃状态;`3` 拒绝(现场已变)。
253
+ - npm 安装后也可用 bin 名:`recency-guard-rescue status --profile-dir ...`。
254
+
255
+ Windows 示例:
412
256
 
413
257
  ```bash
414
- dsh plugin --profile <profile名> add file:/绝对路径/dsh-recency-guard
258
+ node bin/rescue.mjs status --profile-dir "%USERPROFILE%\.dsh\profiles\desktop"
415
259
  ```
416
260
 
417
- ### 插件市场
261
+ ---
418
262
 
419
- 本插件已提交 [awesome-dsh-plugin](https://github.com/xuanyuanchumo/awesome-dsh-plugin) 收录(`security` 分类),
420
- 收录后即可在 dsh 0.2.1+ 的 **Plugins → Discover** 中搜索到(索引源
421
- `https://cdn.jsdelivr.net/npm/dsh-plugin-catalog/index.json`),也可通过
422
- [dsh-market](https://github.com/dsh-market/dsh-market#readme) 一键安装。
423
- npm 发布后市场索引会自动携带 npm 版本;GitHub-only 期间用上面的命令安装。
263
+ ## 诚实边界
264
+
265
+ **概率性判定(会观察、可能不立刻行动)**
266
+
267
+ - 第二条崩溃通道(残留 bootMarker + 该 pid 已不在)是**概率性证据**:
268
+ 人为强杀也会产生同样形状。因此第 1 轮只观察,同一头号嫌疑累计到 `confirmRounds`(默认 2)才动手;
269
+ **唯一嫌疑**走确定性证据,第 1 轮即行动。
270
+ - 幻影「另一个实例」已单独归类为 `self-reload`(同 pid 热重载),不按并发实例处理。
271
+
272
+ **写安全与对账(真机竞态修复)**
273
+
274
+ - **journal 是 append-only**:崩溃路径同步追加、绝不整文件回写;行数裁剪只在异步、
275
+ 加锁的安全时机做。并发写不会再用旧快照覆盖 `crash-detected`(此前真机约 1/9 复现)。
276
+ - **以决策文件为真相补账**:哨兵在场且决策文件有非空 `disable` 时,state 缺失的
277
+ `autoDisabled` / 被清零的 `crashSeq` 会在下次启动补回;无哨兵 / 非崩溃决策 / 空名单
278
+ 一律不补(不猜)。
279
+ - **清零是 CAS**:干净启动只归零「本轮启动后没被崩溃改过」的计数,晚到的异步写
280
+ 不会抹掉崩溃路径刚写下的 `crashSeq`。
281
+ - **降级可查**:启动编排的分区索引提交 / 变更记录 / 官方实验观测失败,都会落一条
282
+ journal `note` 并带 `degraded: true` —— 不只写 logger,重启后仍可查。
283
+
284
+ **研究边界(避免误读)**
285
+
286
+ - **预防性预警只预警、不行动**:启动频率 EWMA 与配置变更 CUSUM 只进观测面
287
+ (面板/`status`),**不被任何关闭决策读取**;样本不足 ⇒ `defined:false` + 原因,不填 0。
288
+ `loadTime` 是「启动频率(次/分钟)」的**代理**,不是真实加载耗时 —— 面板会原样显示代理说明。
289
+ - **共现信号只判定、不加权**:`mayWeightByCooccurrence` 只在满足研究
290
+ [09 §4.3](docs/research/09-整合研究-四方向与既有研究的合流.md) 的条件时给出「可加权」判定,
291
+ 默认决策权重仍为 0;面板显示判定与理由,不把共现当成已生效权重。
292
+
293
+ **明确不拦 / 不修的**
294
+
295
+ - **entry 级 `apply` 抛错**:宿主只 warning 并自隔离该 entry,进程不崩 —— 不属于本插件的拦截面。
296
+ 真崩溃来自进程级未捕获异常 / rejection。评估守护能力时别用 entry 级失败当夹具。
297
+ - **官方组件**:`@deepseek-ai/*` 永远不是嫌疑;官方实验包(`@deepseek-ai/dsh-experimental-*`)
298
+ 只观测、**绝不自动关闭**(关官方组件等于卸宿主半边)。
299
+ - **宿主自身缺陷 / 依赖版本不兼容**:本插件不修,也不假装能修。按
300
+ [`docs/agents/update-guide.md`](docs/agents/update-guide.md) 的紧急流程处理。
301
+ - **指标不可判定时为 `null`,不是 0**:没有真实崩溃样本时,贝叶斯后验、预防性预警不编数字;
302
+ 研究文档里的「Top-1 命中率提升 ≥5%」「预警提前量 ≥1 轮」两条效果判据**尚未声称达成**
303
+ (机制已就位,效果待真机样本)。
304
+ - **跨分区只读视图**:`GET /snapshot?partition=<key>` 的运行时字段(stage / metrics / markov)
305
+ 如实降级为 `null` / `idle`,不替别的分区编造状态;`revision` 恒为 `null`。
306
+ - **声明式冲突只在有声明时产生**:插件没写 `dshCompat` / `dsh-plugin.json`、版本范围不可判定、
307
+ 对手不在场 ⇒ **不产生 finding**(不猜)。`conflicts` 空数组 = 扫过且没有,不是没扫。
308
+ - **`unresolved` 不静默**:受保护组件(官方 / 本插件)/ 无候选 / 配额不足导致治不了的冲突会单独上屏;
309
+ 不会用 recency 嫌疑人顶替确定性目标来「装作解决」。
310
+ - **`maxAutoDisable` 是整组上限**:依赖联动可能一次关一组;整组放不进配额则**一个都不动**
311
+ (不让依赖者半关着在下一次启动时再崩一次)。
312
+ - **rescue 写模式不是自动的**:默认 dry-run,不带 `--yes` 绝不写;两阶段 + 哈希校验,
313
+ 现场已变时退出码 `3` 拒绝而不是硬改 profile。
424
314
 
425
315
  ---
426
316
 
@@ -433,127 +323,107 @@ npm 发布后市场索引会自动携带 npm 版本;GitHub-only 期间用上
433
323
  name: dsh-recency-guard
434
324
  config:
435
325
  enabled: true # 总开关;false 时只记录,绝不自动关闭
436
- includeExperimental: true # 是否把 experimental/beta 命名的包算作嫌疑
437
- maxAutoDisable: 2 # 单次恢复最多关闭几个嫌疑插件
438
- autoRestore: true # 稳定后自动恢复非首要嫌疑的配置
326
+ includeExperimental: true # 实验性 / beta 命名是否算嫌疑
327
+ maxAutoDisable: 2 # 单次恢复最多停用几个(整组上限,含依赖联动)
328
+ autoRestore: true # 稳定后自动恢复非首要嫌疑
439
329
  confirmRounds: 2 # 概率性证据的行动阈值(唯一嫌疑不受限)
440
- versionedBackups: true # 插件更新时写带版本的时间线备份
441
- backupRetain: 5 # 每插件保留几份时间线备份
442
- backupRoot: '' # 备份自定义目录(留空=profile 内分区目录)
330
+ versionedBackups: true # 更新时写带版本的时间线备份
331
+ backupRetain: 5 # 每插件保留几份
332
+ backupRoot: '' # 备份自定义目录(环境变量 RECENCY_GUARD_BACKUP_ROOT 优先)
443
333
  ```
444
334
 
445
335
  | 配置 | 默认 | 说明 |
446
336
  | --- | --- | --- |
447
- | `enabled` | `true` | 总开关。关闭后插件只记录日志,永不自动禁用任何插件 |
448
- | `includeExperimental` | `true` | 实验性/beta 命名的包是否算嫌疑 |
449
- | `maxAutoDisable` | `2` | 单次恢复最多关闭几个。崩溃次数递增,但封顶于此值 |
450
- | `autoRestore` | `true` | 稳定启动后自动重新启用非首要嫌疑 |
451
- | `confirmRounds` | `2` | 多嫌疑时同一头号嫌疑连续 N 轮崩溃才自动关闭(唯一嫌疑第 1 轮即行动) |
452
- | `versionedBackups` | `true` | 插件更新时写「带版本的时间线备份」,可逐版对账 |
337
+ | `enabled` | `true` | 总开关。关闭后只记录,永不自动禁用 |
338
+ | `includeExperimental` | `true` | 实验性 / beta 命名的包是否算嫌疑 |
339
+ | `maxAutoDisable` | `2` | 单次恢复最多停用数,**整组上限**(依赖联动可能一次关多个;整组放不进则不动) |
340
+ | `autoRestore` | `true` | 稳定启动后自动恢复非首要嫌疑 |
341
+ | `confirmRounds` | `2` | 多嫌疑时同一头号嫌疑连续 N 轮才自动关闭(唯一嫌疑第 1 轮行动) |
342
+ | `versionedBackups` | `true` | 写「带版本的时间线备份」,可逐版对账 |
453
343
  | `backupRetain` | `5` | 每个插件保留的备份份数(超出删最旧) |
454
- | `backupRoot` | `''` | 备份自定义目录;环境变量 `RECENCY_GUARD_BACKUP_ROOT` 优先 |
344
+ | `backupRoot` | `''` | 自定义备份目录;`RECENCY_GUARD_BACKUP_ROOT` 优先 |
455
345
 
456
346
  ---
457
347
 
458
- ## 工具:`recency_guard`
348
+ ## 兼容范围
459
349
 
460
- | action | 作用 |
461
- | --- | --- |
462
- | `status` | 查看崩溃状态、嫌疑排名、被自动关闭的插件、备份列表;输出带「工作区 · 分区」行(多项目时对账归因) |
463
- | `history` | 查看安装 / 更新 / 崩溃 / 关闭 / 恢复的完整日志 |
464
- | `recover` | 立即按"最近更新"执行一次恢复关闭(无需等崩溃) |
465
- | `restore` | 重新启用被自动关闭的非首要嫌疑插件 |
466
- | `reenable` | 手动重新启用指定插件(`bundle` 参数) |
467
- | `backups` | 列出配置备份 |
468
- | `archive` | 归档当前产物并重置(按最新事实重新开始) |
469
- | `archives` | 列出历史归档,带 manifest 摘要(崩溃次数 / 事件数 / 曾关插件) |
470
-
471
- 示例:
472
-
473
- ```json
474
- { "action": "status" }
475
- ```
476
-
477
- ---
350
+ | 项 | 范围 | 依据 |
351
+ | --- | --- | --- |
352
+ | dsh 宿主 | `>=0.2.1-rc.5 <0.2.2-0 \|\| >=0.2.2-rc.1 <0.2.3-0 \|\| >=0.2.3-rc.1 <0.3.0-0` | [`package.json`](package.json) 的 `engines.dsh` |
353
+ | 开发 / 测试代际 | `0.2.2-rc.1` | `devDependencies` 与 `peerDependencies` 对齐 |
354
+ | `@deepseek-ai/cordis` | `>=4.0.5 <5.0.0` | 宿主实际 `4.1.0`;旧 `~4.0.5` 会被兼容闸门整体拒绝 |
355
+ | 官方包 | 全部 `peerDependencies` + `optional: true` | 缺 `dsh-tools` / `schemastery` 时降级加载,不 require 崩 |
356
+ | 社区 manifest | draft **v0.15**([`dsh-plugin.json`](dsh-plugin.json)) | 与 dsh 安装契约(`package.json`)并存,不是替换 |
357
+ | Node | 开发与测试环境 `v24.14.0`;`package.json` 未声明 `engines.node` | 不声称未验证的 Node 版本 |
358
+ | 平台 | Windows 真机实测(桌面端 dsh) | Linux / macOS 未做真机崩溃注入验证 —— 未声称 |
359
+
360
+ > 预发布分支为什么要写显式 `||`:node-semver 只允许「范围里某个比较符与该版本的
361
+ > `major.minor.patch` 元组一致、且自身带预发布标签」时才放行预发布版本。
362
+ > 写成 `>=0.2.1-rc.5 <0.3.0-0` 会**静默排除** `0.2.2-rc.1`,用户安装直接撞 `ERESOLVE`。
478
363
 
479
364
  ## 数据文件
480
365
 
481
- 全部位于 profile 目录下:
366
+ 全部在 profile 目录下,按工作区分区(项目 A 的崩溃不影响项目 B):
482
367
 
483
368
  ```
484
369
  <profile>/.recency-guard/
485
- ├── current.json # 指针文件:当前分区键(守护行的 disabled 表达式经它搭桥)
486
- ├── partitions.json # 分区索引:项目 → 分区键(多项目隔离的登记处)
487
- ├── projects/<分区键>/ # 每个项目的分区目录(状态/日志/决策完全独立)
488
- │ ├── journal.jsonl # 只追加的事件日志:install / update / crash-detected / disable / restore …
489
- │ ├── snapshot.json # 上次观测到的 { 插件名: 版本 },用于识别更新
490
- │ ├── state.json # 崩溃计数、被自动关闭的插件、已恢复的插件
491
- │ ├── decision.json # 本轮要拦截的插件(守护行的表达式读它)
492
- │ ├── crashed # 崩溃哨兵:非零退出码时写下
493
- │ ├── observation-*.json # 观测快照(启动事实,量化指标的数据源)
494
- │ ├── archive/ # 本分区的历史归档(搬走+留档)
495
- │ └── backups/ # 关闭前备份的配置(versionedBackups 时带版本)
496
- └── …
370
+ ├── current.json # 指针:当前分区键(守护行表达式经它搭桥)
371
+ ├── partitions.json # 分区索引:项目 → 分区键
372
+ ├── rescue/<uuid>.json # 两阶段 rescue 记录(profile 级;apply/restore 用)
373
+ └── projects/<分区键>/
374
+ ├── journal.jsonl # 只追加:install / update / crash-detected / disable / restore / note
375
+ ├── snapshot.json # 上次观测到的 { 插件名: 版本 }
376
+ ├── state.json # 崩溃计数、自动关闭 / 已恢复名单
377
+ ├── decision.json # 本轮要拦截的插件 + unresolved 残余(守护行表达式读 disable)
378
+ ├── crashed # 崩溃哨兵(非零退出码时写下)
379
+ ├── observation-*.json # 观测快照(指标数据源)
380
+ ├── archive/ # 历史归档(搬走 + 留档)
381
+ └── backups/ # 关闭前备份的配置(versionedBackups 时带版本)
497
382
  ```
498
383
 
499
384
  ---
500
385
 
501
- ## 恢复流程
502
-
503
- ```
504
- 第 1 次崩溃
505
- └─ 退出码非 0 → 写哨兵 + 决策(关闭最近更新的 1 个嫌疑)
506
- 第 2 次启动
507
- └─ 决策文件已就位 → 嫌疑插件的模块不被加载
508
- ├─ 仍然崩溃 → 关闭数 +1(最近更新的 2 个)
509
- └─ 启动成功 → 视为恢复
510
- 第 3 次启动(稳定)
511
- └─ 自动重新启用除首要嫌疑外的插件
512
- └─ 首要嫌疑保持关闭,等你确认后用 reenable 手动开启
513
- ```
514
-
515
- **首要嫌疑**(最后被关闭的那个)会一直保持关闭,直到你明确用 `reenable` 开启它 —— 避免刚恢复就再次崩溃。
516
-
517
- ---
518
-
519
- ## 安全边界
520
-
521
- 本插件在设计上刻意保守:
522
-
523
- - **只碰第三方插件**:`@deepseek-ai/*` 官方包永远不是嫌疑,harness 核心不会被关掉。
524
- - **不碰自己**:`dsh-recency-guard` 永远不在嫌疑名单里。
525
- - **决策文件缺失 / 损坏时一律放行**:表达式在任何异常下都返回 `false`,插件照常加载。守护机制**永远不会**把 profile 锁死在插件之外。
526
- - **无法读取时不影响启动**:所有文件操作都包在 `try/catch` 中,失败只记录日志。
527
- - **官方包与 harness 组件不受影响**:崩溃检测只影响"用户自己加的插件"这一层。
528
-
529
- ---
530
-
531
386
  ## 开发与测试
532
387
 
533
388
  ```bash
534
389
  npm install
535
- npm run build # esbuild 双产物:dist/index.js(宿主)+ dist/client.js(客户端)
536
- npm run typecheck # tsc --noEmit
537
- npm test # 分层测试(Core/Host/Client/Contract)
390
+ npm run build # esbuild 双产物:dist/index.js(宿主)+ dist/client.js(客户端)
391
+ npx tsc --noEmit # 类型
392
+ npm run lint # 类型 + 未使用符号
393
+ npm test # 分层运行器:Core 42 / Host 10 / Client 24 / Contract 8 = 84
394
+ npm run extract-runtime # Host 层测试前置(runtime/ 缺失时先解出)
538
395
  ```
539
396
 
540
- 工具行为测试需要 DSH 运行时包(harness 由应用提供,不在 profile 的 node_modules 中):
541
-
542
- ```bash
543
- npm run extract-runtime # 从 DSH 安装目录解出运行时包到 .tmp/runtime
544
- ```
397
+ - 当前基线:`npm test` **84/84 通过**、`npx tsc --noEmit` 0 错误(2026-11-11 实测)。
398
+ - 验证顺序固定:**build → typecheck → lint → test**,全部零错误才算完成。
399
+ - 文档体系与写码纪律见 [`docs/agents/`](docs/agents/README.md);
400
+ 发布 / 升级 / 热重载见 [`docs/agents/update-guide.md`](docs/agents/update-guide.md)。
545
401
 
546
- ### 沙箱验证
402
+ ---
547
403
 
548
- 开发时务必在独立沙箱 profile 中验证,不要动主力 profile:
404
+ ## 版本记录
549
405
 
550
- ```bash
551
- dsh plugin --profile sb-recency-guard add file:/绝对路径/dsh-recency-guard
552
- dsh --profile sb-recency-guard --help
553
- ```
406
+ | 版本 | 一句话 |
407
+ | --- | --- |
408
+ | **0.17.0**(2026-11-11) | 技术栈清理与研究闭环:预防性预警(RQ3)接入生产且**只预警不行动**、面板自适应刷新、状态文件 schema 版本(未知版本保留现场)、变异 72 突变体 / 0 未归因 + 无直接测试 14→2 + rescue/宿主宽度跨实现对拍;技术债状态见 [`docs/agents/tech-debt.md`](docs/agents/tech-debt.md) |
409
+ | **0.16.0**(2026-11-11) | 确定性冲突证据(4 类 finding + 出处)、贪心最小隔离与依赖联动、可写两阶段 rescue、常态路径锁卫生、PanelSnapshotView 可空契约;新增社区 manifest [`dsh-plugin.json`](dsh-plugin.json),README 面向用户重构,版本四锚点统一 |
410
+ | 0.15.0 | 架构重构:`host/` + `client/` + `shared/` 分层,client→host 越界与值依赖环写成契约测试 |
411
+ | 0.14.0 | 误伤反馈闭环、贝叶斯风险并行读数、预防性健康指标(EWMA / CUSUM)、自动化变异测试 |
412
+ | 0.13.0 | 决策因子快照、决策与展示排序口径统一、可选 peer 惰性解析(修安装即崩)、图谱结构洞察 |
413
+ | 0.12.1 | 崩溃信号第二条通道:哨兵盲区(SIGKILL / 断电)与 self-reload 判别 |
414
+ | 0.12.0 | 命令窗口显示接线、动作路由乐观并发(409)、L4 审计线、依赖代际对齐 |
415
+ | 0.11.x | 数据面安全(动作路由方法守卫)、跨分区只读快照、图谱交互补齐(边 / 小地图 / 触屏) |
416
+ | 0.10.0 | 马尔可夫转移矩阵、休哈特控制带、诊断投影与反馈入口 |
417
+ | 0.9.0 | 恢复置信分级(`confirmRounds`)、两阶段提交、离线 rescue CLI |
418
+ | 0.8.0 | 备份增强(版本时间线)、官方实验插件只观测、事件驱动变更检测 |
419
+ | 0.7.0 | 崩溃级修复(守护行 YAML 写入管线)、可交互图谱、可观测性白盒化 |
420
+ | 0.3–0.6 | 能力闭环双向比对、生命周期状态机、量化指标、多项目隔离、面板与图谱基础 |
421
+
422
+ 长设计论证(为什么这样决策、数学模型、踩过的坑)已迁到
423
+ [`docs/agents/design.md`](docs/agents/design.md),不再堆在 README。
554
424
 
555
425
  ---
556
426
 
557
- ## 许可证
427
+ ## 许可
558
428
 
559
- MIT
429
+ MIT。见 [`LICENSE`](LICENSE)。