dsh-gokifu-compact 1.8.1 → 1.9.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.
- package/CHANGELOG.md +326 -0
- package/QUICKSTART.md +4 -3
- package/README.md +39 -5
- package/docs//346/226/271/346/241/210-/346/214/201/347/273/255/344/274/230/345/214/226.md +1812 -0
- package/docs//347/216/260/347/212/266/345/256/241/350/256/241-2026-10-29.md +336 -0
- package/docs//350/215/211/347/250/277-/346/214/201/347/273/255/344/274/230/345/214/226.md +958 -0
- package/docs//350/256/241/345/210/222/350/241/250.md +5 -1
- package/docs//351/207/215/345/220/257/345/220/216/351/252/214/350/257/201/346/270/205/345/215/225.md +2 -2
- package/evidence/bench-2000.json +6 -6
- package/evidence//344/273/212/346/227/245/346/225/210/346/236/234/350/257/204/344/274/260-2026-10-29.md +86 -0
- package/evidence//350/257/204/344/274/260/346/212/245/345/221/212-2026-10-29-/344/270/273/344/272/272/346/211/223/345/210/206.md +250 -0
- package/lib/client.js +272 -0
- package/package.json +25 -7
- package/scripts/cli.mjs +4 -1
- package/scripts/doctor.mjs +340 -0
- package/scripts/install.mjs +243 -29
- package/scripts/link-path.mjs +333 -0
- package/src/engine.mjs +257 -35
- package/src/graph.mjs +544 -42
- package/src/keeper.mjs +3 -0
- package/src/ledger.mjs +566 -81
- package/src/ui.mjs +809 -0
- package/src/verifier.mjs +126 -0
- package/src/worldstate.mjs +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,331 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 1.9.2 — 2026-10-30
|
|
4
|
+
|
|
5
|
+
### ⚠ 破坏性契约变化:`graph.json` 不再是「最新状态」
|
|
6
|
+
|
|
7
|
+
**1.9.2 起,落盘拆成「上一回合基线 + 当前增量」:**
|
|
8
|
+
|
|
9
|
+
| 文件 | 内容 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `graph.json` | **上一个回合边界**的完整基线 |
|
|
12
|
+
| `graph.json.delta.jsonl` | 本回合的增量帧,一帧一行 `<baseHash16>\t<checksum31>\t<json>` |
|
|
13
|
+
|
|
14
|
+
`graph.json` 只在**回合边界**(`agent/status idle`)落一次;回合内每一步只追加 delta 帧。
|
|
15
|
+
⇒ **外部工具若只 `JSON.parse(graph.json)`,会少一个回合的数据。** 最新状态 = 基线 + 按序重放 delta
|
|
16
|
+
(`fromJSON` → `decodeDeltaFrames(text, baseHash)` → `applyDelta`,三个函数在 `src/graph.mjs`)。
|
|
17
|
+
README 新增「graph.json 契约(外部工具必读)」一节,含三条余量(短回合写放大 / dirty>100 flush / `appendFileSync` 跨平台)。
|
|
18
|
+
|
|
19
|
+
### P3 · flush 写放大 ×800–975 治理(增量 delta + index 追加 + dirty 节点)
|
|
20
|
+
|
|
21
|
+
- `graph.json.delta.jsonl`:每步只追加 dirty 节点/边(绝对 upsert)+ 标量 + order 追加;
|
|
22
|
+
合并阈值 `max(64KB, 基线 20%)`,超过则写新基线(`tmp` → `rename`)并清空 delta
|
|
23
|
+
- 崩溃安全:帧带 `baseHash`(generation 保护,基线重写后旧帧整体忽略)+ `checksum31`
|
|
24
|
+
(半写帧就地停止并截断到最后一个好帧)+ 幂等重放(同一批帧重放多次结果相同)
|
|
25
|
+
- `index.md` 稳态**追加**增量段(~200–800 B/次),fold / 首次 / 缺失 / >256KB 时全量重建;
|
|
26
|
+
`nodes/*.md` 稳态不写,只记 pending,fold 时只写 dirty
|
|
27
|
+
- 实测(三张真实图 × 60 步 + 1 回合边界):写放大 **×80.16 / ×70.04 / ×61.75 → ×37.41 / ×33.31 / ×30.75**;
|
|
28
|
+
稳态每步写 **10.5–11.8 KB → 591–713 B(−94%)**;fold 后 flush **429–836 ms → 9.4–22.9 ms**
|
|
29
|
+
(5–20 个 dirty 节点)/ **76.5–88.8 ms**(100 个)
|
|
30
|
+
- 测试:`test/persist-incremental.test.mjs` **8 项**(重放等价 / 幂等 / 半写帧 / generation 保护 /
|
|
31
|
+
index 增量 / engine 端到端 / nodes dirty)
|
|
32
|
+
- 已知边界:**极短回合(<10 步)** 每回合的全量基线摊销不足,写放大仍可能 >×50;
|
|
33
|
+
`appendFileSync` 跨平台原子性未验证
|
|
34
|
+
|
|
35
|
+
### P4 · 首屏 `state` 的 graph.json 解析缓存
|
|
36
|
+
|
|
37
|
+
- `src/ui.mjs` 新增 `(绝对路径, mtimeMs, size) → 聚合摘要` 缓存 + `pruneGraphCache()`:
|
|
38
|
+
改文件必失效(同 mtime 但 size 不同也失效)
|
|
39
|
+
- 133 图 / 33 MB 实测:热路径 p50 **140.5 ms → 20.9 ms**(另一轮 **242.5 → 51.1**);
|
|
40
|
+
冷路径也更快(**170.7 → 101.3** / **386.4 → 196.7**);`state` 响应 **3,648 B 逐字相同**
|
|
41
|
+
- 测试:`test/ui-perf.test.mjs` **10 项**
|
|
42
|
+
|
|
43
|
+
### P5 · 生产口径 token 预算与 `pickFallback`
|
|
44
|
+
|
|
45
|
+
- `renderLedger` 新增 `fallback` 三态(`none` / `over-budget` / `impossible`)+ `overBudgetBy`:
|
|
46
|
+
**不再静默返回超预算文本** —— 调用方拿到的每个结果都能区分「预算内 / 兜底超限 / 无解」
|
|
47
|
+
- `pickFallback` 择优顺序固定:预算内优先 → tokens 最小 → 平手列得多;全超选最省并标 `over-budget`;
|
|
48
|
+
无非空文本返回 `null`(`impossible`)
|
|
49
|
+
- 生产口径(`treeDir` = 真实会话目录、`maxTokens=500`,133 图):
|
|
50
|
+
**>500 = 24/133(与 1.9.1 HEAD 的 24 相同,新增 0)**、avg **444.80**(HEAD 430.85,+13.95 ≤ +20)、
|
|
51
|
+
max **905**(与 HEAD 完全相同)。24 张全部是 L4/L5 兜底档 —— 正文本身 >1500 字符,不是预算记账 bug
|
|
52
|
+
- 测试:`test/ledger-budget.test.mjs` **7 项**
|
|
53
|
+
|
|
54
|
+
### P6a · `TRACE:` 行绝对路径改相对,回收 avg +17 tok/图
|
|
55
|
+
|
|
56
|
+
- `TRACE:` 从 `D:\DeepSeek Harness\home\chess-context\sessions\<sid>/index.md …`(137.7 字符)
|
|
57
|
+
改成 `sessions/<sid>/index.md`(57.5 字符),`traceRoot` 可配置(默认无前缀,生产传 `sessions`)
|
|
58
|
+
- 生产口径 133 图:avg **445.08 → 429.64(−15.44 tok/图)**;over500 **24 → 18**;max **905 → 878**;
|
|
59
|
+
逐会话 85 便宜 / 7 变贵(5 个是 TRACE 变短后落进更低档位、列了更多内容)/ 41 不变;
|
|
60
|
+
`renderFacts` **133 图 0 差异**
|
|
61
|
+
- 测试:`test/trace-relative.test.mjs` **7 项**(Windows 盘符 / POSIX 用户名路径 / traceRoot 可配置 / 不泄漏绝对路径 …)
|
|
62
|
+
|
|
63
|
+
### P6-A · 悬空边只在分析中排除(不剪数据)
|
|
64
|
+
|
|
65
|
+
- 新增 `classifyDangling()`(`residue` / `live-unsettled` / `from-broken` / `none`)与 `danglingStats()`;
|
|
66
|
+
**只过滤显示,不改 `graph.json`**
|
|
67
|
+
- 判定:`residue := to==null && age>10min`;**`live_unsettled`(untested 且 ≤10 分钟)必须保留可见**
|
|
68
|
+
—— 这是 Lead 批准的**更保守**口径(keeper 先 settle 结果、`step/end` 才回填 `to`,
|
|
69
|
+
按字面公式会把刚结算的新鲜失败当场隐藏)
|
|
70
|
+
- `from` 悬空 **214 条**(全在 `5acafe42`,`mergeNodeInto` 老 bug)单独标记,不混入 `to=null` residue
|
|
71
|
+
- 实测(133 图):热清单**无终点失败边 158 → 0**;avg **−18.98**;over500 **17 → 17**;max **878 → 878**;
|
|
72
|
+
全库 `residue 5,566 / liveUnsettled 1 / fromBroken 214`
|
|
73
|
+
- `renderFacts` **仍列全部失败边**(失败记忆不丢),新增 `DANGLING_RESIDUE: N` / `FROM_DANGLING: 214` /
|
|
74
|
+
`LIVE_UNSETTLED: N` 显式计数
|
|
75
|
+
- 测试:`test/dangling-exclusion.test.mjs` **8 项**
|
|
76
|
+
|
|
77
|
+
### P7 · `fallback` 与 `traceRoot` 接进 engine / doctor(不留死开关)
|
|
78
|
+
|
|
79
|
+
- `src/engine.mjs`:新增配置 `hotLedgerTraceRoot`(schema 默认 `"sessions"`),生产 `renderLedger` 调用传
|
|
80
|
+
`traceRoot: "sessions"` ⇒ 生产热清单 `TRACE:` 为 `sessions/<sid>/index.md`,不泄漏盘符/用户名
|
|
81
|
+
- engine **消费** `fallback`:`over-budget` → warn 日志含 `overBudgetBy=` / `budget=`;
|
|
82
|
+
`impossible` → error;`ledgerBudgetMetrics` 累计并原子落盘 `<graphDir>/ledger-budget.json`;
|
|
83
|
+
提供 `getLedgerBudgetStats()` / `onLedgerBudget` 钩子(可测,不静默)
|
|
84
|
+
- `scripts/doctor.mjs`:新增「热清单预算(fallback)」项,显示 `none / over-budget / impossible` 分布与
|
|
85
|
+
`maxOverBudgetBy`;字段缺失写「未采集」;`impossible > 0` 记 error
|
|
86
|
+
- 测试:`test/engine-harness.test.mjs` 增至 **25 项**(+4 条 P7 接线用例)
|
|
87
|
+
|
|
88
|
+
### S2b · `no-exit` 弱成功按 N1 收窄
|
|
89
|
+
|
|
90
|
+
- `verifier.classify()` 的 shell 分支:`[exit code]` 存在时行为不变;**无退出码时默认给 `weak:"shell"`**,
|
|
91
|
+
命中 N1 任一规则则不给。`result` **永远是 `unverified`**(不进已验证事实)
|
|
92
|
+
- N1 八条 veto 规则集中在 `SHELL_WEAK_VETO_RULES` 一处(id + 说明 + 正则):末行自报非零 / 末行 `Node.js vX` /
|
|
93
|
+
尾部 node 栈迹 / `command not found`·`No such file`·`cannot stat` / 行首 `SyntaxError|AssertionError|…` /
|
|
94
|
+
`FAILED (failures=N)` 等;规则表可测试、可追溯
|
|
95
|
+
- 真实采样:全标 weak 的误报率 **2.94% → 收窄后约 0.49%**(≈48/9762 条假进展)
|
|
96
|
+
- 测试:`test/weak-shell.test.mjs` **13 项**(每条规则命中/不命中 + 旧图兼容 + renderFacts 不变 + 变异自检)
|
|
97
|
+
|
|
98
|
+
### 测试
|
|
99
|
+
|
|
100
|
+
- `node test/run.mjs`:**373 passed / 0 failed**(30 个测试文件,`EXIT 0`;2026-10-30 03:2x 实测)
|
|
101
|
+
- 新增测试文件:`persist-incremental`(8) · `ui-perf`(10) · `ledger-budget`(7) · `trace-relative`(7) ·
|
|
102
|
+
`dangling-exclusion`(8) · `weak-shell`(13);`engine-harness` 21 → **25**
|
|
103
|
+
|
|
104
|
+
## 1.9.1 — 2026-10-29
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
### 修图坍缩:全库 61.3% 的边是自环(三份独立评估共同指向)
|
|
108
|
+
|
|
109
|
+
事故:`worldstate.touch(paths)` 收到的是**单个路径字符串**(`engine.mjs` 的
|
|
110
|
+
`st.touchPath = (p) => world.touch(p)`),却按可迭代对象遍历 ⇒ 一个路径被拆成逐字符的
|
|
111
|
+
20 多个「路径」⇒ 触碰集合是十来个字符 ⇒ `hash()` 对所有会话、所有步骤返回**同一个哈希**
|
|
112
|
+
⇒ `addAgentNode` 每步命中同一 `stateIndex` 节点 ⇒ `step/end` 把边的 `to` 指回 `from`。
|
|
113
|
+
|
|
114
|
+
实测:全库 **6,172 / 10,059 = 61.3%** 自环,边/节点 3.36,极端会话 530 边 / 23 节点。
|
|
115
|
+
连锁后果:`tewariAnalysis` 的同终点判定恒真 ⇒ **`dead` 判定 109/109 全错**、
|
|
116
|
+
`redundant` 135 条全假 ⇒ 热清单告诉模型「这条路永远别试」,真相只是「同一步里另一条命令成功了」。
|
|
117
|
+
|
|
118
|
+
四处修复:
|
|
119
|
+
- `worldstate.touch()`:兼容 string / array 入参,单字符丢弃
|
|
120
|
+
- `addAgentNode()`:置换命中**本步起点**时强制新建节点(真正的置换——回到更早状态——照旧复用)
|
|
121
|
+
- `tewariAnalysis()`:自环边不入手割;自环成功边不算替代路径
|
|
122
|
+
- `failureClass()`:`edge.to !== edge.from` 才能判 `dead`
|
|
123
|
+
|
|
124
|
+
### 修两个真 bug(评估中发现)
|
|
125
|
+
|
|
126
|
+
- **fold 后 `nodes/*.md` 从不刷新**:`#flush(session, { md: true })` 的键名写错(签名是 `tree`)
|
|
127
|
+
- **摘要上限三处默认互相矛盾**:Config 4096 / resolveChess 2048 / 安装配置 8192 ⇒ 统一 8192
|
|
128
|
+
(3072~4096 在真实会话里会贴边触发 `MAX_TOKENS`,历史 82 次)
|
|
129
|
+
|
|
130
|
+
### 压缩失败不再静默
|
|
131
|
+
|
|
132
|
+
- 连续失败计数进热清单:`⚠ CONTEXT COMPRESSION IS FAILING (N consecutive folds failed; last error: …)`,
|
|
133
|
+
成功一次即清零。此前只写 `logger.warn`,实测 856 次失败 / 5.5 小时无人知晓。
|
|
134
|
+
|
|
135
|
+
### 诊断与清理工具
|
|
136
|
+
|
|
137
|
+
- `graphHealth(g)`(`src/graph.mjs`):自环数 / 自环率 / 边节点比
|
|
138
|
+
- `doctor`:新增「图结构(自环)」一项
|
|
139
|
+
- `verify-graphs`:报告图健康度,自环 ≥5% 标 ⚠
|
|
140
|
+
- `test/rewash-selfloop.mjs`:清洗历史自环边(保留边与结果,只把 `to` 置 null),dry-run 默认、带备份
|
|
141
|
+
- 已对全库执行:自环 **6,172 → 0**,坍缩图 **42 → 0**
|
|
142
|
+
|
|
143
|
+
### 只读工具的「成功」只进 WORKING,不进已验证事实(S2a)
|
|
144
|
+
|
|
145
|
+
根因:`read` / `grep` / `glob` 这类只读工具执行成功,只能说明「读到了」,不能说明「任务往前走了」。
|
|
146
|
+
旧实现把它一律计为 `unverified` ⇒ 纯只读会话的热清单里**没有 WORKING 路径**(实测 106 个会话里
|
|
147
|
+
只有 21 张图能渲染出 WORKING),模型看不到「我刚才走到哪」。
|
|
148
|
+
|
|
149
|
+
- `verifier.classify()`:只读类工具(`read` / `mcp-read`)+ 输出非空 + 无任何失败信号 ⇒ 额外给
|
|
150
|
+
`weak: "read"`,**`result` 仍是 `unverified`**(不改既有语义,避免 `failureEdges` / `openEdges` /
|
|
151
|
+
`tewari` 的判定漂移)
|
|
152
|
+
- keeper 落账 `edge.weak`;`renderLedger` **只在没有强 success 锚点时才**用弱边建 WORKING,
|
|
153
|
+
渲染时标 `[weak k/n]` + 图例;旧图按 settle 时已写的 `verify.kind === "read"` 识别
|
|
154
|
+
- `renderFacts` 的 `UNVERIFIED_CLAIMS` 段**继续列**这些边 —— 弱成功不会被当成已验证事实
|
|
155
|
+
- 实测(活库 106 会话):有 WORKING 的会话 **21 → 39**(+18,回退 0、既有行变化 0);
|
|
156
|
+
日志回放 106 份:**25 → 44**(+19,丢失 0);`dead` / `redundant` / `longer` 增量 **0**;
|
|
157
|
+
`renderFacts` 逐字不同 **0**;热清单仍 ≤500 tokens
|
|
158
|
+
|
|
159
|
+
### 可选严格不变量门禁 + 历史图豁免(S1a / S1c / C-13)
|
|
160
|
+
|
|
161
|
+
- `test/graph-invariants.mjs`(新):纯判定层,四条判据 —— `degenerate-graph`(节点 0 但有边)、
|
|
162
|
+
`self-loop`(`from === to`)、`edge-per-node`(默认 >8)、`collapsed-graph`(自环率 ≥5%)
|
|
163
|
+
- `test/verify-graphs.mjs` 增 `--strict` 与三个阈值参数(`--max-self-loops` / `--max-edge-per-node` /
|
|
164
|
+
`--max-self-loop-rate`)。**默认模式(无参 / `<目录>` / `--json`)一行代码都没改行为**:
|
|
165
|
+
实测用 HEAD 版本与本版对同一份冻结夹具(132 张 `graph.json`)跑文本与 JSON 两种默认模式,
|
|
166
|
+
**stdout / stderr 逐字节一致(`cmp` 无差异)、退出码一致**
|
|
167
|
+
- 为什么不设默认门禁:全库 `--strict` 下 **15 / 132 张图红** —— 2 张历史自环坍缩图
|
|
168
|
+
(`session-1331683e…` 509/628 = 81.1%、`session-69dd787e…` 115/153 = 75.2%)+ 13 张旧版节点
|
|
169
|
+
冻结造成的高 ratio(13 张全部 >8,最高 530 边 / 23 节点 = **23.04**)。这 13 张不可修(`to` 不可知、节点冻结不可逆),
|
|
170
|
+
设默认会永久红 ⇒ 保留阈值 8、`--strict` 保持可选
|
|
171
|
+
- `test/graph-exemptions.mjs`(新)+ `test/fixtures/history-exemptions.json`:**显式、可审计、可过期的
|
|
172
|
+
历史违规豁免清单**(C-13),只在 `--strict --exemptions=<文件>` 下生效。每条记录
|
|
173
|
+
`sessionId` / 判据代号 / `E,N,L` 数值 / 理由 / 登记人 / 登记日期 / 有效期 / 指纹(由该图
|
|
174
|
+
node+edge 全字段派生的 sha256)
|
|
175
|
+
- **精确而非宽松**:一条豁免只吸收「同一张图 + 同一判据 + 同一组数值 + 同一指纹」。
|
|
176
|
+
同一张图的 ratio 继续增长、或新加一条自环 ⇒ **照样红**
|
|
177
|
+
- **豁免 ≠ 通过**:被豁免的 17 条逐条打印(⊘ 带登记值与理由),并单独报告
|
|
178
|
+
「豁免前 N 张违规 → 豁免后 M 张」
|
|
179
|
+
- **实测**(同上冻结夹具):`--strict` 15 张红 →加 `--exemptions` 后 **0 张红、退出码 0**
|
|
180
|
+
(17 条违规被已登记条目吸收)
|
|
181
|
+
- 到期日(默认 2027-06-30)过后自动失效恢复判红,且不是静默的 —— 输出会打「已过期 N 条」
|
|
182
|
+
- 测试:`test/graph-invariants.test.mjs` 31 项(含 CLI 端到端退出码、逐图 ✓/✗ 与退出码同口径);
|
|
183
|
+
C-13 另有已登记图绿 / 未登记坏图红 / 数值变化仍红 / 部分豁免 / 空清单 / 清单格式错误 / 过期失效等用例
|
|
184
|
+
|
|
185
|
+
### 安装判据统一 + 打包后 `doctor` 真的能跑(S10a / B3)
|
|
186
|
+
|
|
187
|
+
- `scripts/link-path.mjs`(新):`link:` / `file:` 依赖规格的**唯一**判据来源 —— `parseSpec` /
|
|
188
|
+
`resolveSpecPath` / `samePath`(Windows 大小写不敏感)/ `statLink`(区分「没有 / 断链 / 指向别处」,
|
|
189
|
+
不用 `existsSync`)/ `inspectProfileInstall` / `createDirLink`(Node junction,免 shell 引号,
|
|
190
|
+
中文与空格安全)/ `removeDirLink`(只删链接,真目录拒删)
|
|
191
|
+
- `scripts/install.mjs` 改用它:断链与「指向别处」的 junction 会重建,不再「存在即跳过」;
|
|
192
|
+
`mklink` 兜底实测中文 + 空格路径可用
|
|
193
|
+
- `test/verify-install.mjs` 修掉「读不到 package.json」式假绿:目标目录不存在时说「目标目录不存在」,
|
|
194
|
+
不再给误导性措辞
|
|
195
|
+
- 🔴 **`doctor` 从 `test/doctor.mjs` 迁到 `scripts/doctor.mjs`**:`test/` 不在 `files` 里 ⇒
|
|
196
|
+
1.9.0 及更早从 npm 装的用户跑 `npx dsh-gokifu-compact doctor` 会 **ENOENT**(README 的安装第 3 步
|
|
197
|
+
就是这条命令)。1.9.1 起 doctor 与 `cli.mjs` 同在 `scripts/`,随包发布
|
|
198
|
+
- `test/install-link.test.mjs`(新)29 项,含 4 组变异自检(判据退回字符串比较 / 安装器退回
|
|
199
|
+
「键在就跳过」/ junction 退回「存在即跳过」/ 入口判据退回字符串比较,各自必须变红)
|
|
200
|
+
|
|
201
|
+
### 安装验证的诊断措辞、占位红与入口判据(S10c / S10e / C-12)
|
|
202
|
+
|
|
203
|
+
- `test/verify-install.mjs`:目标目录不存在 / `package.json` 缺失 / 坏 JSON / 没有 `name` **各给各的措辞**,
|
|
204
|
+
不再一律写「读不到 package.json」(实测:坏状态的根因是 `link:` 指向的目录不存在,
|
|
205
|
+
旧文案会把人引到错的排查方向)。`manifestError` 现在进 ✗ 详情
|
|
206
|
+
- 检查项总数**恒为 16**:`profile/package.json` 缺失时补 5 条占位红(首条带路径)、`preset` 缺失时补
|
|
207
|
+
4 条占位红 —— 缺文件不再让总数缩水(缩水等于悄悄少查)
|
|
208
|
+
- `test/ab-render.mjs` 的入口判据改成 `fileURLToPath` + `realpath` 等价判定:
|
|
209
|
+
旧判据「`import.meta.url` 结尾等于 basename」在**非 ASCII 文件名**下静默不执行(exit 0、零输出 = 假绿),
|
|
210
|
+
新判据对等价路径 / Windows 大小写 / junction / 非 ASCII 都判真,被 import 时零副作用
|
|
211
|
+
- `scripts/link-path.mjs` 与 `test/verify-install.mjs` 补末尾换行(`wc -l == awk NR`)
|
|
212
|
+
- 测试:`test/ws-install.test.mjs` **13 项**(S10c-1..5 / S10e-1..3 / C-12-1..5),16 个变异体自证不是假绿
|
|
213
|
+
|
|
214
|
+
### 版本号在中文路径下读成 `v?`(S10′)
|
|
215
|
+
|
|
216
|
+
`src/ui.mjs` 的 `HERE` 用 `new URL(import.meta.url).pathname` —— 得到的是**百分号编码串**
|
|
217
|
+
(`/D:/%E4%B8%BB%E5%B7%A5%E4%BD%9C%E5%8C%BA/…`),Windows 上交给 `readFileSync` 直接 ENOENT,
|
|
218
|
+
被 `readVersion()` 的 `catch` 吞成 `null` ⇒ 页面标题渲染成 `v?`。改用 `fileURLToPath`(Node 官方解码入口,
|
|
219
|
+
空格 `%20`、中文、`%7E` 一并还原)。测试用**派生值**而不是字面量:
|
|
220
|
+
① 当前真实中文路径读到的版本 === `package.json` 的 `version`;② 模块搬到「空格 + 非 ASCII」目录后读到的是那份
|
|
221
|
+
`package.json`;③ `fileURLToPath` 能还原、`pathname` 直读必失败(附 ASCII 对照组,证明断言不是常量);
|
|
222
|
+
④ `package.json` 缺失时仍返回 `null`(保留既有容错接口)。`test/version-path.test.mjs` 4 项
|
|
223
|
+
|
|
224
|
+
### `/gokifu/api` 同源校验与备份轮转(A1 / A2 · 安全)
|
|
225
|
+
|
|
226
|
+
审计实测的原始缺陷:`/gokifu/api` 接受**跨站** `text/plain` POST —— `Origin: http://evil.example`
|
|
227
|
+
+ `content-type: text/plain` 得到 **200 且 preset 被改写**(`text/plain` 属 CORS 简单请求,
|
|
228
|
+
不触发预检,`OPTIONS` 上不返 `Access-Control-Allow-Origin` 挡不住写)。M2 的密钥字段一落地,
|
|
229
|
+
这条就升级成「任意网页可换掉你的摘要模型密钥」。
|
|
230
|
+
|
|
231
|
+
三段校验(全部在 `readBody` **之前**):
|
|
232
|
+
|
|
233
|
+
| # | 规则 | 为什么是这条 |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| ① | `Host` 必须存在、合法、且为回环(`127/8` / `::1` / `localhost` / `*.localhost`) | 防 DNS rebinding —— 那种情况下 `Origin == Host == evil.example` 也自洽,只比同源挡不住。`webServer` 绑 `0.0.0.0` 时自动放开,其它非回环主机走 `DSH_GOKIFU_ALLOWED_HOSTS` 显式放行 |
|
|
236
|
+
| ② | 带 `Origin` ⇒ 必须与 `Host` 同协议 + 同主机 + 同端口 | 浏览器 POST 必带 `Origin` |
|
|
237
|
+
| ③ | 带 `Sec-Fetch-Site` ⇒ 只允许 `same-origin` / `none`,**`same-site` 也拒** | 实测本机 `127.0.0.1:19401 → 127.0.0.1:19400` 的来源报的是 `same-site` 而不是 `cross-site`,只拒 `cross-site` 会漏 |
|
|
238
|
+
| ④ | `content-type` 必须是 `application/json`(允许 `; charset=`) | HTML 表单发不出 JSON ⇒ 非 JSON 一律 415 |
|
|
239
|
+
| ⑤ | **缺 `Origin` ⇒ 允许,但 `Host` 必须回环** | 浏览器对 POST 一定带 `Origin`,缺它只可能是 curl / node / 本项目测试;这条是回归护栏,不能把本机工具一起拒掉 |
|
|
240
|
+
|
|
241
|
+
- 备份轮转:每个 preset 最多保留 **5** 份 `.bak-before-ui-<ms>`,超出的删最旧的;同毫秒多次保存用
|
|
242
|
+
`-2`/`-3` 后缀防覆盖
|
|
243
|
+
- 密钥守卫:新配置块里出现密钥(键名以 `key`/`secret`/`password`/`credential`/`token` 结尾,或值形如
|
|
244
|
+
`sk-…`/`ghp_…`/PEM 私钥头)**直接抛错不落盘**;源文件原本就有密钥时**跳过备份**并返回
|
|
245
|
+
`backupSkipped`(否则每个备份都是明文副本)。密钥识别只回**键名**,绝不回值
|
|
246
|
+
- 测试:`test/ui-security.test.mjs` 25 项 —— 跨站 `text/plain` / 外站 `Origin` / `Origin: null` /
|
|
247
|
+
同 IP 异端口(`same-site`)/ DNS rebinding 形状 / 缺 Host / 非 JSON content-type 全部 403 或 415 且
|
|
248
|
+
preset 一字不变;同源浏览器请求与无 Origin 的本机调用仍 200(防止把功能改坏)
|
|
249
|
+
- ⚠ **已上线的 DSH 进程没有重启,跑着的仍是旧代码** —— 本修复要在重启后才生效
|
|
250
|
+
|
|
251
|
+
### 性能:热清单渲染与三层树渲染的索引化(P1 / P2)
|
|
252
|
+
|
|
253
|
+
**P1 · `tewariAnalysis` 记忆化 + transposition 索引化**(`src/graph.mjs`、`src/ledger.mjs`)
|
|
254
|
+
|
|
255
|
+
热清单渲染的热点在 `tewariAnalysis`:一次 `renderLedger` 会在每个档位各算一遍手割,全库一轮调用
|
|
256
|
+
3,523 次而必要只有约 800 次;置换提示对每条路径做 `Object.values(g.edges).some(...)`,是 O(path×E)。
|
|
257
|
+
「无缓存」的代价实测是 **234ms/次**(`session-c328440a`,158 节点路径)。
|
|
258
|
+
|
|
259
|
+
- 每张图维护 `tewariIndex`(`byFrom` 邻接表 + 每节点可达集 + 失败边结论缓存),挂在 WeakMap / 非枚举
|
|
260
|
+
Symbol 上,**不进 `graph.json`**;缓存键 = 结构修订号 + `updatedAt` + 边/节点数 + `to`/`from` 数字和
|
|
261
|
+
(数字和专门兜住「同毫秒两次 settle」与「绕过 bump 直接改边」两种撞键)
|
|
262
|
+
- 旧实现把同一份「baseline 可达集 BFS」在两处各算一遍 → 合并成一次
|
|
263
|
+
- 置换提示改成预建 `Set`(`from>to`),`has()` 取代全表扫描
|
|
264
|
+
|
|
265
|
+
实测(133 张真实图):`renderLedger` / `renderFacts` 输出**逐字节相同**;全库一轮渲染
|
|
266
|
+
**487.4ms → 70.5ms(6.9×)**,最差单图 **150.82ms → 8.99ms(16.8×)**;tewari 调用
|
|
267
|
+
**3,523 → 804(−77%)**。新增 `test/tewari-perf.test.mjs` 7 项(含「关掉记忆化必须退化」变异自检)。
|
|
268
|
+
|
|
269
|
+
**P2 · `renderIndexMd` / `renderNodeMd` 的 O(N×E) 索引化**(`src/graph.mjs`)
|
|
270
|
+
|
|
271
|
+
三层树的 index/node md 每次 debounce flush 都要重写:`renderIndexMd` 每轮 BFS 都 `Object.values(g.edges)`
|
|
272
|
+
全表扫,`renderNodeMd` 每个节点各做一次入边/出边全表 filter —— 节点数一多就是 O(N×E)。
|
|
273
|
+
5000 边合成图上 `renderIndexMd` 要 **3,506.8ms**、`renderNodeMd` 要 **7,208ms**。
|
|
274
|
+
|
|
275
|
+
- `graphIndex(g)` 增加 `byTo`(入边表);`renderIndexMd` 的 BFS / 根判定 / 出边查询与 `renderNodeMd` 的
|
|
276
|
+
入出边查询全部走 `byFrom` / `byTo`;`renderNodeMd` 每次调用只取一次索引
|
|
277
|
+
- `renderIndex(g)` 用便宜键(`rev|updatedAt|nextEdge|order.length`)判缓存,不命中才算完整结构签名
|
|
278
|
+
(否则逐节点调用会把 O(E) 签名乘 N 次:5000 边实测会从 6.4ms 退化到 14.9s)
|
|
279
|
+
|
|
280
|
+
实测:133 张真实图输出**逐字节不变**(index **133/133**、node md **7,843/7,843**);真实最大图
|
|
281
|
+
**49.39ms → 0.54ms(91×)** / **51.46ms → 0.60ms(86×)**;5000 边合成图 **3,506.8ms → 4.26ms(823×)** /
|
|
282
|
+
**7,208ms → 6.37ms(1,132×)**。新增 `test/render-index-perf.test.mjs` 6 项。
|
|
283
|
+
|
|
284
|
+
> 两项都不改任何输出文本 —— 这是硬验收:性能优化若改变了模型看到的热清单,就不是优化。
|
|
285
|
+
|
|
286
|
+
### 测试
|
|
287
|
+
|
|
288
|
+
- 新增 `test/selfloop.test.mjs`(8 项)+ `engine-harness` 端到端 1 项(自环回归)
|
|
289
|
+
- 新增测试文件与项数:
|
|
290
|
+
`test/weak-success.test.mjs`(22 · S2a)、`test/graph-invariants.test.mjs`(31 · S1a)、
|
|
291
|
+
`test/install-link.test.mjs`(29 · S10a)、`test/ws-install.test.mjs`(13 · S10c/S10e/C-12)、
|
|
292
|
+
`test/version-path.test.mjs`(4 · S10′)、`test/c17-path-at-every-level.test.mjs`(15 · C17)、
|
|
293
|
+
`test/graph-exemptions.test.mjs`(12 · C-13)、`test/ui-security.test.mjs`(25 · A1/A2)、
|
|
294
|
+
`test/ui-config.test.mjs`(13 · 配置写入正确性)、`test/tewari-perf.test.mjs`(7 · P1)、
|
|
295
|
+
`test/render-index-perf.test.mjs`(6 · P2)
|
|
296
|
+
- `test/run.mjs`:**316 passed / 0 failed**(24 个测试文件,`EXIT 0`;2026-10-30 02:31 实测)
|
|
297
|
+
- 之前的 125 项无一覆盖 `from === to` —— 这正是这个回归能躲过所有自测的原因
|
|
298
|
+
|
|
299
|
+
## 1.9.0 — 2026-10-29
|
|
300
|
+
|
|
301
|
+
### 「上下文压缩」详情页(DSH 原生 GUI 里的第三方插件页)
|
|
302
|
+
|
|
303
|
+
左侧栏「插件 / 自动化任务」那一列新增一条 **上下文压缩**,点开是整页(与「自动化任务」同一套
|
|
304
|
+
`sidebar.panellist` + `main` 槽位,来自 asar 里的 `dsh-client-ui-schedule` 实现)。
|
|
305
|
+
|
|
306
|
+
**页面内容(MVP)**
|
|
307
|
+
- 状态:最近活跃会话(fold 后总量 / fold 次数 / 失败边 / 节点数 / 上次遮蔽)、今天汇总
|
|
308
|
+
(fold 次数 / 会话数 / 合计遮蔽 / 全库失败边)、上次摘要真实 usage(含 provider/model)
|
|
309
|
+
- 参数(15 项):自动压缩、触发阈值、增长闸、逐字保留、最小 fold 量、摘要方式、输出上限、
|
|
310
|
+
热清单开关/预算/标记档/就地替换、置换检测、checkpoint 合并阈值、摘要 provider/model
|
|
311
|
+
- 操作:保存(写 preset + 自动备份,提示重启生效)、恢复默认、刷新;未保存改动会列出键名
|
|
312
|
+
- 只读展示配置来源路径
|
|
313
|
+
|
|
314
|
+
**结构(为什么这么切)**
|
|
315
|
+
- 包根 `dsh-gokifu-compact` = UI host 半(`src/ui.mjs`,注册 `POST /gokifu/api`),
|
|
316
|
+
引擎挪到 `dsh-gokifu-compact/engine` —— 因为 `dsh-client-modules` 只对**包根说明符**做客户端发现,
|
|
317
|
+
子路径条目不会加载 `dsh.client`
|
|
318
|
+
- 浏览器半 `lib/client.js`:`window.__ModuleLoader__.load` 形状、只 `require("react")`、
|
|
319
|
+
数据全走 host 路由(不用 Typert —— 那是 DSH 构建期生成的,第三方拿不到)
|
|
320
|
+
- 安装器新增两步:preset 引擎名迁移(`dsh-gokifu-compact` → `/engine`)+ profile 的
|
|
321
|
+
`cordis.patch.yml` 挂 `gokifu-ui`(都带备份、幂等)
|
|
322
|
+
|
|
323
|
+
### 测试
|
|
324
|
+
|
|
325
|
+
125 项全绿(新增 6 项 UI 测试:块解析/渲染回环、sanitizePatch 拦非法值、saveConfig 写盘+备份+
|
|
326
|
+
同值不重写、客户端 bundle 的 `__ModuleLoader__` 形状与两个 slot 注册、`POST /gokifu/api` 的
|
|
327
|
+
state/save/未知 action)
|
|
328
|
+
|
|
3
329
|
## 1.8.1 — 2026-10-29
|
|
4
330
|
|
|
5
331
|
### doctor:一天的 fold 收益一眼可见
|
package/QUICKSTART.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# 快速开始 · dsh-GOkifu-compact
|
|
2
2
|
|
|
3
|
-
> 给「已经装好,现在要用」的人。安装/回滚细节见 [README](
|
|
3
|
+
> 给「已经装好,现在要用」的人。安装/回滚细节见 [README](README.md)。
|
|
4
4
|
|
|
5
5
|
## 一 · 重启后先做这三步(3 分钟)
|
|
6
6
|
|
|
7
7
|
### 1. 确认插件已加载
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
cd D:/主工作区/projects/dsh-
|
|
10
|
+
cd D:/主工作区/projects/dsh-chess-context
|
|
11
11
|
node test/verify-install.mjs
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
期望:**
|
|
14
|
+
期望:**16 项全过**,最后一行 `== 全部通过 ==`。
|
|
15
|
+
(S10a 起「依赖声明的 link 目标」是真检查:解析后的目录必须存在、包名必须是本包、node_modules 实际指向要一致 —— 以前只查字符串前缀,目标不存在也报 ✓)
|
|
15
16
|
其中「运行期已产出棋谱」那条的 mtime 应该是**刚刚**(说明 keeper 在工作)。
|
|
16
17
|
|
|
17
18
|
### 2. 看一次真实会话的棋谱
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-GOkifu-compact
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 当前版本:**`1.9.1`** · npm [`dsh-gokifu-compact`](https://www.npmjs.com/package/dsh-gokifu-compact) · [GitHub Releases](https://github.com/MYCF711/dsh-GOkifu-compact/releases) · 更新记录见 [CHANGELOG.md](CHANGELOG.md)
|
|
4
4
|
|
|
5
5
|
**DSH 的棋谱式上下文压缩后端 —— 把「历史 → 一段摘要」换成「历史 → 可复盘的棋谱 DAG + 每步只有 323 tokens 的热清单」。**
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
热清单 323 tokens (2000 边压力测试;旧版全量渲染 28,800)
|
|
9
9
|
失败保留 8 / 8 (真实模型对照;官方 8 段模板 4.67 / 8)
|
|
10
10
|
清单开销 0.2% of prompt (v1.3.1 起;事故会话的旧版是 21.3%)
|
|
11
|
-
验证
|
|
11
|
+
验证 373 项单测 · 真实包 8/8 · 真实进程 fold 3~6/6
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
GO = 围棋,kifu = 棋谱,compact = 压缩。一局棋下完,棋谱上不只有落子顺序,还有每一手的评语。
|
|
@@ -50,6 +50,38 @@ GO = 围棋,kifu = 棋谱,compact = 压缩。一局棋下完,棋谱上不
|
|
|
50
50
|
| 失败记录 | 融进散文摘要,模型可以丢 | 规则抽取 + 逐字注入,模型无权丢 |
|
|
51
51
|
| 热上下文 | 无 | 323 tokens / 步,带语义标记与报错原文 |
|
|
52
52
|
|
|
53
|
+
### ⚠ 棋谱落盘:`graph.json` 的语义(1.9.2 起)
|
|
54
|
+
|
|
55
|
+
**`graph.json` 不再代表「最新状态」,而是「上一回合基线 + 当前增量」。**
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
每次都重写整图(≤1.9.1) 增量落盘(1.9.2 起)
|
|
59
|
+
graph.json = 最新 graph.json = 上一个回合边界的完整基线
|
|
60
|
+
graph.json.delta.jsonl = 本回合的增量帧(带 baseHash + checksum)
|
|
61
|
+
(两者合并 = 最新状态)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**⇒ 它在回合内落后一回合。** 任何直接 `JSON.parse(graph.json)` 当「最新」的脚本,**都会少一回合的数据**。
|
|
65
|
+
`graph.json` 只在**回合边界**(`agent/status idle`)落一次完整快照;回合内的每一步只往
|
|
66
|
+
`graph.json.delta.jsonl` 追加一帧。
|
|
67
|
+
|
|
68
|
+
| 你要什么 | 怎么拿 |
|
|
69
|
+
|---|---|
|
|
70
|
+
| **最新状态**(推荐) | **基线 + 增量一起读**:`fromJSON(graph.json 文本)` → 用基线内容哈希 `decodeDeltaFrames(delta 文本, baseHash)` → 逐帧 `applyDelta(g, frame)`。三个函数都在 `src/graph.mjs`(插件源码随包发布:`node_modules/dsh-gokifu-compact/src/graph.mjs`) |
|
|
71
|
+
| 上一回合基线(做 diff / 审计) | 直接读 `graph.json` 可以,但**要说明「这是基线不是最新」** |
|
|
72
|
+
| 崩溃安全 | 每帧带 `baseHash`(generation)+ `checksum31`:半写帧解码时就地停止,加载时把 delta 截到最后一条好帧;**旧 generation 的帧整体忽略** |
|
|
73
|
+
|
|
74
|
+
**为什么这样设计**:写放大从 **×80 / ×70 / ×62** 降到 **×37 / ×33 / ×31**(三张真实图 60 步 + 1 回合边界),
|
|
75
|
+
fold 后 flush 从 **429–836 ms** 降到 **≤95 ms**;代价是「读的人必须知道这是增量结构」。
|
|
76
|
+
|
|
77
|
+
**三条已知余量(选型时要知道)**
|
|
78
|
+
|
|
79
|
+
1. **短回合写放大**:每回合边界落一份完整基线(0.5–0.6 MB)。真实分布 p50=40 步/回合 ⇒ 写放大 ≈×20–25;
|
|
80
|
+
但**极短回合(<10 步,p10=13.9)**摊销不足,写放大仍可能 >×50。
|
|
81
|
+
2. **dirty > 100 的 flush**:fold 后只写 dirty 节点 —— 5–20 个 dirty 时 **9–23 ms**,100 个时约 **87–89 ms**
|
|
82
|
+
(改前全量 429–836 ms)。dirty 数极大时收益收窄。
|
|
83
|
+
3. **`appendFileSync` 的原子性依赖 OS**:本机 NTFS 上 1 KB 级追加已过半写测试;**跨平台(尤其网络文件系统)未验证**。
|
|
84
|
+
|
|
53
85
|
### 热上下文长什么样
|
|
54
86
|
|
|
55
87
|
每步 `agent/pre-step` 注入(内容不变时复用;变了就**就地替换**上一条,surface 永远只有一条):
|
|
@@ -152,8 +184,9 @@ node test/rollback.mjs # 回滚
|
|
|
152
184
|
和摘要本身。典型形状是**锯齿**:涨到阈值(窗口 45%)→ 压到约 20% → 再涨。如果你看到「只涨不降」,
|
|
153
185
|
先跑 `npx dsh-gokifu-compact doctor` 看压缩是不是在失败(`compaction/end` 的错误会直接列出来)。
|
|
154
186
|
|
|
155
|
-
**装完没反应 / 压缩没触发?** 插件在 DSH 启动时加载,**必须重启**。重启后跑 `
|
|
156
|
-
|
|
187
|
+
**装完没反应 / 压缩没触发?** 插件在 DSH 启动时加载,**必须重启**。重启后跑 `npx dsh-gokifu-compact doctor`
|
|
188
|
+
(在源码仓库里是 `node scripts/doctor.mjs`),它会告诉你四件事:装没装上、摘要模型能不能解析、
|
|
189
|
+
最近的压缩成功/失败、棋谱与树健不健康。
|
|
157
190
|
|
|
158
191
|
**会不会把失败记忆弄丢?** 不会。失败边由规则抽取并逐字注入摘要 prompt,模型输出后还会机械追加约束段;
|
|
159
192
|
模型没有「决定丢掉某条」的权限。真实模型对照里本插件保留 8/8,官方 8 段模板平均 4.67/8。
|
|
@@ -189,7 +222,8 @@ node test/rollback.mjs # 回滚
|
|
|
189
222
|
- [性能与成本](docs/性能与成本.md) · [真实计费核对](evidence/真实计费核对.md)
|
|
190
223
|
- [最终报告](docs/最终报告.md) · [P0 修复记录](docs/P0-worldhash-死循环-根因与修复方案.md)
|
|
191
224
|
|
|
192
|
-
> 更新记录:[CHANGELOG.md](./CHANGELOG.md)(发版流程见 `scripts/release.mjs`
|
|
225
|
+
> 更新记录:[CHANGELOG.md](./CHANGELOG.md)(发版流程见 `scripts/release.mjs`;维护侧的 `HANDOFF.md`
|
|
226
|
+
> 不随 npm 包发布,在 GitHub 仓库根目录)
|
|
193
227
|
|
|
194
228
|
## 许可
|
|
195
229
|
|