dsh-recency-guard 0.16.0 → 0.18.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 +28 -13
- package/bin/rescue.mjs +97 -10
- package/dist/client.js +551 -77
- package/dist/index.js +839 -225
- package/dsh-plugin.json +1 -1
- package/locale/en.json +46 -0
- package/locale/zh.json +46 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# dsh-recency-guard
|
|
2
2
|
|
|
3
|
-
**v0.
|
|
3
|
+
**v0.18.0** · [GitHub](https://github.com/xuanyuanchumo/dsh-recency-guard) · [问题反馈](https://github.com/xuanyuanchumo/dsh-recency-guard/issues) · MIT
|
|
4
4
|
|
|
5
5
|
> DeepSeek Harness 的**插件崩溃恢复守护**:记录第三方 / 实验性插件的安装与更新时序并备份配置;
|
|
6
6
|
> 崩溃后的**下一次启动之前**,把最近更新的嫌疑插件挡在门外;稳定启动后,再把非首要嫌疑放回来。
|
|
7
7
|
>
|
|
8
|
-
> 版本基线(2026-11-
|
|
8
|
+
> 版本基线(2026-11-13,北京时间):**0.18.0**。版本四锚点(`package.json` / 本 README /
|
|
9
9
|
> `docs/agents/` / [`dsh-plugin.json`](dsh-plugin.json))一致。
|
|
10
|
-
> 测试分四层,当前 **
|
|
10
|
+
> 测试分四层,当前 **87/87 全绿**(Core 43 / Host 10 / Client 25 / Contract 9)。
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -51,19 +51,19 @@ dsh plugin --profile <profile名> add dsh-recency-guard
|
|
|
51
51
|
锁定版本:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
dsh plugin --profile <profile名> add dsh-recency-guard@0.
|
|
54
|
+
dsh plugin --profile <profile名> add dsh-recency-guard@0.18.0
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
> 版本对应:本 README
|
|
58
|
-
>
|
|
59
|
-
>
|
|
57
|
+
> 版本对应:本 README 描述 **0.18.0**;npm `latest` 已发布为 **0.18.0**
|
|
58
|
+
> (2026-11-11,shasum `b667bd20b6ea82d294dbc28e4eab1290b07dd963`),可直接安装。
|
|
59
|
+
> 版本纪律见 [`docs/agents/conventions.md`](docs/agents/conventions.md) §6。
|
|
60
60
|
|
|
61
61
|
### 插件市场
|
|
62
62
|
|
|
63
63
|
投稿材料已按 [awesome-dsh-plugin contributing.md](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/blob/main/contributing.md)
|
|
64
64
|
备好([`docs/market-submission.md`](docs/market-submission.md)):一个条目文件、`category: security`。
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
**当前状态:条目已收录(PR #14245);0.17.0 描述更新已推到同一 PR 分支(v0.18.0 将随合并后下一 PR 更新)(HEAD `94ade1e`),
|
|
66
|
+
等待复核/合并。** 收录后可在 dsh 的 **Plugins → Discover** 或 dsh-market 中搜索 `recency-guard` 安装。
|
|
67
67
|
|
|
68
68
|
### 手动 / 本地源码(开发)
|
|
69
69
|
|
|
@@ -137,6 +137,8 @@ dsh plugin --profile <profile名> add link:/绝对路径/dsh-recency-guard
|
|
|
137
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
138
|
| 生命周期状态机 | 生命周期卡 + 迁移史 | [`lifecycle.ts`](src/host/core/lifecycle.ts) | [`lifecycle-steps.test.ts`](tests/unit/lifecycle-steps.test.ts) |
|
|
139
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) |
|
|
140
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) |
|
|
141
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) |
|
|
142
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) |
|
|
@@ -152,9 +154,11 @@ dsh plugin --profile <profile名> add link:/绝对路径/dsh-recency-guard
|
|
|
152
154
|
|
|
153
155
|
### 管理面板
|
|
154
156
|
|
|
155
|
-
- **数据面**:`GET /recency-guard/api/snapshot
|
|
157
|
+
- **数据面**:`GET /recency-guard/api/snapshot`。**自适应轮询**:内容稳定时 5s→10s→15s
|
|
158
|
+
退避,内容变化/用户操作立即回 5s,绝对边界 [3s, 30s];暂停按钮按下后**不再发请求**
|
|
156
159
|
(画面停在暂停时刻,而不是继续跳)。
|
|
157
|
-
- **卡片**:稳定性 / 阶段 / 崩溃率 / 崩溃压力 / CUSUM
|
|
160
|
+
- **卡片**:稳定性 / 阶段 / 崩溃率 / 崩溃压力 / CUSUM 变点、**预防性预警**(启动频率控制带、
|
|
161
|
+
配置变更 CUSUM;**只预警不行动**,不可判定显示原因)、崩溃决策与因子分解、
|
|
158
162
|
**声明式冲突**(每条带 `文件:行号` 与声明原文)、**未解决冲突**(上次隔离后仍治不了的部分,
|
|
159
163
|
与「现在有冲突」分开显示)、量化指标(不可判定显示原因)、能力清单与缺口、项目隔离、归档、事件流。
|
|
160
164
|
- **可空契约**:宿主在跨分区只读视图会发 `null` / 整段省略的字段,由
|
|
@@ -277,6 +281,15 @@ node bin/rescue.mjs status --profile-dir "%USERPROFILE%\.dsh\profiles\desktop"
|
|
|
277
281
|
- **降级可查**:启动编排的分区索引提交 / 变更记录 / 官方实验观测失败,都会落一条
|
|
278
282
|
journal `note` 并带 `degraded: true` —— 不只写 logger,重启后仍可查。
|
|
279
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
|
+
|
|
280
293
|
**明确不拦 / 不修的**
|
|
281
294
|
|
|
282
295
|
- **entry 级 `apply` 抛错**:宿主只 warning 并自隔离该 entry,进程不崩 —— 不属于本插件的拦截面。
|
|
@@ -377,11 +390,11 @@ npm install
|
|
|
377
390
|
npm run build # esbuild 双产物:dist/index.js(宿主)+ dist/client.js(客户端)
|
|
378
391
|
npx tsc --noEmit # 类型
|
|
379
392
|
npm run lint # 类型 + 未使用符号
|
|
380
|
-
npm test # 分层运行器:Core
|
|
393
|
+
npm test # 分层运行器:Core 42 / Host 10 / Client 24 / Contract 8 = 84
|
|
381
394
|
npm run extract-runtime # Host 层测试前置(runtime/ 缺失时先解出)
|
|
382
395
|
```
|
|
383
396
|
|
|
384
|
-
- 当前基线:`npm test` **
|
|
397
|
+
- 当前基线:`npm test` **84/84 通过**、`npx tsc --noEmit` 0 错误(2026-11-11 实测)。
|
|
385
398
|
- 验证顺序固定:**build → typecheck → lint → test**,全部零错误才算完成。
|
|
386
399
|
- 文档体系与写码纪律见 [`docs/agents/`](docs/agents/README.md);
|
|
387
400
|
发布 / 升级 / 热重载见 [`docs/agents/update-guide.md`](docs/agents/update-guide.md)。
|
|
@@ -392,6 +405,8 @@ npm run extract-runtime # Host 层测试前置(runtime/ 缺失时先解出)
|
|
|
392
405
|
|
|
393
406
|
| 版本 | 一句话 |
|
|
394
407
|
| --- | --- |
|
|
408
|
+
| **0.18.0**(2026-11-13) | 研究落地补完与崩溃循环兜底:配置未知键 fail-loud(学习 dsh-ops)、journal 裁剪改**轮转**(读改写窗口结构性消灭 + 零依赖对拍)、路径指纹 64 位 + 双读迁移(TD-7 根治)、生存分析/失配检测/恢复残余上屏(三个观测孤岛接入消费)、图谱**影响面**接入详情侧栏(impactedBy/reachableFrom 首个 UI 消费者);**级联刹车**:崩溃循环升级到顶后自动回滚到崩溃前启动状态(仅保留头号嫌疑),不再「越关越空」;技术债状态见 [`docs/agents/tech-debt.md`](docs/agents/tech-debt.md) |
|
|
409
|
+
| **0.17.0**(2026-11-11) | 技术栈清理与研究闭环:预防性预警(RQ3)接入生产且**只预警不行动**、面板自适应刷新、状态文件 schema 版本(未知版本保留现场)、路径逃逸修复(`pathSafeName` 单一实现 + 指纹)、变异 72 突变体 / 0 未归因 + 无直接测试 14→2 + rescue/宿主宽度跨实现对拍;技术债状态见 [`docs/agents/tech-debt.md`](docs/agents/tech-debt.md) |
|
|
395
410
|
| **0.16.0**(2026-11-11) | 确定性冲突证据(4 类 finding + 出处)、贪心最小隔离与依赖联动、可写两阶段 rescue、常态路径锁卫生、PanelSnapshotView 可空契约;新增社区 manifest [`dsh-plugin.json`](dsh-plugin.json),README 面向用户重构,版本四锚点统一 |
|
|
396
411
|
| 0.15.0 | 架构重构:`host/` + `client/` + `shared/` 分层,client→host 越界与值依赖环写成契约测试 |
|
|
397
412
|
| 0.14.0 | 误伤反馈闭环、贝叶斯风险并行读数、预防性健康指标(EWMA / CUSUM)、自动化变异测试 |
|
package/bin/rescue.mjs
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
import { createHash, randomUUID } from 'node:crypto'
|
|
46
46
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
47
47
|
import { join, resolve } from 'node:path'
|
|
48
|
+
import { pathToFileURL } from 'node:url'
|
|
48
49
|
|
|
49
50
|
const GUARD_DIR = '.recency-guard'
|
|
50
51
|
|
|
@@ -144,11 +145,40 @@ function readJsonSafe(file) {
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
/** journal 读取(容忍崩溃截断的尾行,与 store.ts 同一容错语义)。
|
|
148
|
+
/** journal 读取(容忍崩溃截断的尾行,与 store.ts 同一容错语义)。
|
|
149
|
+
*
|
|
150
|
+
* v0.18 TD-5:journal 超限会轮转成 journal.<epoch>.jsonl(epoch 升序),
|
|
151
|
+
* 完整时间线 = 轮转文件(旧→新)+ 活动文件。rescue 是零依赖脚本,
|
|
152
|
+
* 不能 import 宿主模块 —— 这份合并逻辑与 journal-io.ts 的
|
|
153
|
+
* readMergedJournalSync 是「有意保留的双实现」,由 width-parity 同款
|
|
154
|
+
* 纪律的 journal 合并对拍测试防漂移。
|
|
155
|
+
*/
|
|
148
156
|
function readJournal(partitionDir, limit) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
157
|
+
let names = []
|
|
158
|
+
try {
|
|
159
|
+
names = readdirSync(partitionDir)
|
|
160
|
+
} catch {
|
|
161
|
+
return []
|
|
162
|
+
}
|
|
163
|
+
const rotated = names
|
|
164
|
+
.filter((n) => /^journal\.\d+\.jsonl$/.test(n))
|
|
165
|
+
.sort((a, b) => {
|
|
166
|
+
const ea = Number(a.slice('journal.'.length, -'.jsonl'.length))
|
|
167
|
+
const eb = Number(b.slice('journal.'.length, -'.jsonl'.length))
|
|
168
|
+
return ea - eb
|
|
169
|
+
})
|
|
170
|
+
.map((n) => join(partitionDir, n))
|
|
171
|
+
const active = join(partitionDir, 'journal.jsonl')
|
|
172
|
+
const files = [...rotated, ...(existsSync(active) ? [active] : [])]
|
|
173
|
+
if (files.length === 0) return []
|
|
174
|
+
const lines = []
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
try {
|
|
177
|
+
lines.push(...readFileSync(file, 'utf8').split('\n').filter(Boolean))
|
|
178
|
+
} catch {
|
|
179
|
+
/* 一段缺失好过全空 */
|
|
180
|
+
}
|
|
181
|
+
}
|
|
152
182
|
const take = Math.max(0, Math.min(limit, lines.length))
|
|
153
183
|
const out = []
|
|
154
184
|
for (const line of lines.slice(lines.length - take)) {
|
|
@@ -173,7 +203,41 @@ function fmtTime(iso) {
|
|
|
173
203
|
* 的时刻)。这里只实现 status/history 用到的列对齐所需的宽度判定,
|
|
174
204
|
* 完整的折行/中和原语仍在 src/tool-text.ts(工具输出走那条路)。
|
|
175
205
|
*/
|
|
176
|
-
|
|
206
|
+
/**
|
|
207
|
+
* 零宽码点:它们**不占列**,但也不能从文本里删掉。
|
|
208
|
+
*
|
|
209
|
+
* 为什么 rescue 也要判:它有自己的宽度实现(零依赖纪律,崩溃后 node_modules
|
|
210
|
+
* 可能损坏),但有独立实现就必须有**跨实现对拍**(tests/contract/width-parity)。
|
|
211
|
+
* 此前这一份没跟上 tool-text 的修复 ⇒ 组合字符被算 2 列、零宽空格算 1 列,
|
|
212
|
+
* 状态/历史两块表在中英混排时会整体右移。
|
|
213
|
+
*/
|
|
214
|
+
function isZeroWidth(codePoint) {
|
|
215
|
+
return (
|
|
216
|
+
(codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
217
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
218
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
219
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
220
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f) ||
|
|
221
|
+
(codePoint >= 0xfe00 && codePoint <= 0xfe0f) ||
|
|
222
|
+
(codePoint >= 0x1f3fb && codePoint <= 0x1f3ff) ||
|
|
223
|
+
codePoint === 0x200b ||
|
|
224
|
+
codePoint === 0x200c ||
|
|
225
|
+
codePoint === 0x200d ||
|
|
226
|
+
codePoint === 0x200e ||
|
|
227
|
+
codePoint === 0x200f ||
|
|
228
|
+
codePoint === 0x2060 ||
|
|
229
|
+
codePoint === 0xfeff ||
|
|
230
|
+
codePoint === 0x00ad
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** 导出仅供**对拍测试**使用(tests/contract/journal-merge-parity.test.ts):
|
|
235
|
+
* 零依赖纪律要求 rescue 自带合并实现,对拍是防漂移的唯一手段。
|
|
236
|
+
* CLI 自身不经过这些导出。 */
|
|
237
|
+
export { readJournal as rescueReadJournalForParity }
|
|
238
|
+
|
|
239
|
+
export function codePointWidth(cp) {
|
|
240
|
+
if (isZeroWidth(cp)) return 0
|
|
177
241
|
const wide =
|
|
178
242
|
(cp >= 0x1100 && cp <= 0x115f) ||
|
|
179
243
|
(cp >= 0x2e80 && cp <= 0x303e) ||
|
|
@@ -191,15 +255,32 @@ function codePointWidth(cp) {
|
|
|
191
255
|
return wide ? 2 : 1
|
|
192
256
|
}
|
|
193
257
|
|
|
194
|
-
/**
|
|
195
|
-
|
|
258
|
+
/**
|
|
259
|
+
* 文本的显示列宽(CJK 全宽算 2 列;`length` 数的是码元,中文会错一半)。
|
|
260
|
+
*
|
|
261
|
+
* ZWJ 序列(`👨👩👧`)按**一个字形**算:连接符之后的码点不累加,
|
|
262
|
+
* 否则一个家庭 emoji 会被算成 6 列,padWidth 会把整张表推歪。
|
|
263
|
+
*/
|
|
264
|
+
export function displayWidth(text) {
|
|
196
265
|
let width = 0
|
|
197
|
-
|
|
266
|
+
let joined = false
|
|
267
|
+
for (const ch of String(text)) {
|
|
268
|
+
const codePoint = ch.codePointAt(0) ?? 0
|
|
269
|
+
if (codePoint === 0x200d) {
|
|
270
|
+
joined = true
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
if (joined) {
|
|
274
|
+
joined = false
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
width += codePointWidth(codePoint)
|
|
278
|
+
}
|
|
198
279
|
return width
|
|
199
280
|
}
|
|
200
281
|
|
|
201
282
|
/** 按显示列右填充(`padEnd` 对 CJK 会少补空格 ⇒ 后续列错位,实测踩过)。 */
|
|
202
|
-
function padWidth(text, width) {
|
|
283
|
+
export function padWidth(text, width) {
|
|
203
284
|
const pad = width - displayWidth(text)
|
|
204
285
|
return pad > 0 ? text + ' '.repeat(pad) : String(text)
|
|
205
286
|
}
|
|
@@ -526,4 +607,10 @@ function main() {
|
|
|
526
607
|
process.exit(code)
|
|
527
608
|
}
|
|
528
609
|
|
|
529
|
-
main
|
|
610
|
+
// 只有「被当作 CLI 直接执行」时才跑 main:`tests/contract/width-parity.test.ts`
|
|
611
|
+
// 需要 import 本文件的宽度实现做**跨实现对拍**(零依赖脚本不能 import 宿主
|
|
612
|
+
// 模块,但宿主测试可以 import 它)。不 guard 的话一次 import 就会去解析
|
|
613
|
+
// process.argv 并 exit,测试进程当场死掉。
|
|
614
|
+
const invokedAsCli =
|
|
615
|
+
typeof process.argv[1] === 'string' && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
616
|
+
if (invokedAsCli) main()
|