@theseus-whale/dsh-continuity 0.0.0-stage → 4.12.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.
Files changed (258) hide show
  1. package/INSTALL.md +25 -0
  2. package/PANEL.md +92 -0
  3. package/README.md +286 -2
  4. package/RELEASE-NOTES.md +732 -0
  5. package/bin/bc-acceptance-full.mjs +91 -0
  6. package/bin/bc-acceptance-tight.mjs +66 -0
  7. package/bin/bc-acceptance.mjs +22 -0
  8. package/bin/bc-add-keywords.mjs +21 -0
  9. package/bin/bc-demo.mjs +47 -0
  10. package/bin/bc-expand-hint.mjs +179 -0
  11. package/bin/bc-ingest-index.mjs +35 -0
  12. package/bin/bc-ingest.mjs +31 -0
  13. package/bin/bc-label-demo.mjs +18 -0
  14. package/bin/bc-relabel.mjs +150 -0
  15. package/bin/bc-sessionize.mjs +59 -0
  16. package/bin/coref_pipeline.py +47 -0
  17. package/bin/coref_service.py +57 -0
  18. package/bin/debug_ckip.py +16 -0
  19. package/bin/debug_ner_zh.py +18 -0
  20. package/bin/doctor.mjs +275 -0
  21. package/bin/en_ner_error_audit.py +33 -0
  22. package/bin/eval-label-rules.mjs +342 -0
  23. package/bin/export-turns.py +68 -0
  24. package/bin/extract-user-paragraph.mjs +14 -0
  25. package/bin/extraction-composite-smoke.mjs +28 -0
  26. package/bin/extraction-latency.mjs +58 -0
  27. package/bin/extraction-user-smoke.mjs +28 -0
  28. package/bin/extraction_service.py +164 -0
  29. package/bin/facet-active-experiment.mjs +110 -0
  30. package/bin/facet-covid.mjs +8 -0
  31. package/bin/facet-curve.mjs +27 -0
  32. package/bin/facet-embedding-train.mjs +20 -0
  33. package/bin/facet-embedding-validate.mjs +83 -0
  34. package/bin/facet-eval-roleplay.mjs +42 -0
  35. package/bin/facet-eval.mjs +38 -0
  36. package/bin/facet-fold.mjs +31 -0
  37. package/bin/facet-load.mjs +16 -0
  38. package/bin/facet-mlp-scan.mjs +45 -0
  39. package/bin/facet-mvp.mjs +34 -0
  40. package/bin/facet-online-experiments.mjs +78 -0
  41. package/bin/facet-online-race.mjs +99 -0
  42. package/bin/facet-online-validate.mjs +51 -0
  43. package/bin/facet-panel.mjs +22 -0
  44. package/bin/facet-passage.mjs +41 -0
  45. package/bin/facet-random-validate.mjs +48 -0
  46. package/bin/facet-roleplay.mjs +33 -0
  47. package/bin/facet-scan.mjs +56 -0
  48. package/bin/facet-segment.mjs +34 -0
  49. package/bin/facet-train.mjs +19 -0
  50. package/bin/facet-validate.mjs +103 -0
  51. package/bin/facet-zeroshot.mjs +14 -0
  52. package/bin/install-templates.mjs +72 -0
  53. package/bin/memory-bench.mjs +53 -0
  54. package/bin/memory-build-summary.mjs +43 -0
  55. package/bin/memory-build-vector.mjs +31 -0
  56. package/bin/memory-build.mjs +41 -0
  57. package/bin/memory-calibration.mjs +70 -0
  58. package/bin/memory-closed-loop.mjs +119 -0
  59. package/bin/memory-dry-run.mjs +70 -0
  60. package/bin/memory-eval.mjs +104 -0
  61. package/bin/memory-event-alias-test.mjs +89 -0
  62. package/bin/memory-extraction-bench.mjs +58 -0
  63. package/bin/memory-load-gate-replay.mjs +137 -0
  64. package/bin/memory-loop-test.mjs +90 -0
  65. package/bin/memory-pre-turn-harness.mjs +63 -0
  66. package/bin/memory-query.mjs +39 -0
  67. package/bin/memory-replay-hints.mjs +84 -0
  68. package/bin/memory-scale.mjs +58 -0
  69. package/bin/memory-scenario-test.mjs +146 -0
  70. package/bin/memory-user-loop.mjs +95 -0
  71. package/bin/ner_bench_tiny_zh.py +59 -0
  72. package/bin/ner_bench_zh.py +60 -0
  73. package/bin/ner_conll_en.py +139 -0
  74. package/bin/ner_direct_en.py +123 -0
  75. package/bin/ner_direct_zh.py +100 -0
  76. package/bin/ner_pipeline.py +208 -0
  77. package/bin/noise-selfcheck.mjs +50 -0
  78. package/bin/pack-integrity.mjs +47 -0
  79. package/bin/panel-check.mjs +57 -0
  80. package/bin/plugin-integrity.mjs +74 -0
  81. package/bin/preview-live.mjs +13 -0
  82. package/bin/public-install-check.mjs +222 -0
  83. package/bin/relabel-apply.mjs +272 -0
  84. package/bin/relabel-dry-run.mjs +381 -0
  85. package/bin/relik_pipeline.py +39 -0
  86. package/bin/relik_service.py +52 -0
  87. package/bin/srl_pipeline.py +55 -0
  88. package/bin/srl_service.py +68 -0
  89. package/bin/summary-index.mjs +151 -0
  90. package/bin/summary-query.mjs +79 -0
  91. package/bin/vec-check.mjs +27 -0
  92. package/bin/zh_ner_error_audit.py +36 -0
  93. package/bin/zh_ner_slot_eval.py +38 -0
  94. package/client.js +760 -0
  95. package/cordis.patch.yml +378 -0
  96. package/docs/API-NOTES.md +1743 -0
  97. package/docs/ARCHITECTURE.md +355 -0
  98. package/docs/CACHE.md +253 -0
  99. package/docs/CONDUCT.md +30 -0
  100. package/docs/CREATOR-HANDOFF.md +102 -0
  101. package/docs/EXPERIMENT.md +136 -0
  102. package/docs/FACET-MVP-REPORT.md +473 -0
  103. package/docs/GUIDE-0.4.66.md +195 -0
  104. package/docs/GUIDE.md +127 -0
  105. package/docs/MECHANISM.md +243 -0
  106. package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
  107. package/docs/README.md +37 -0
  108. package/docs/SESSION-LAYOUT.md +151 -0
  109. package/docs/history-map.md +249 -0
  110. package/docs/relabel-migration-wiring.md +366 -0
  111. package/guides/00-READ-FIRST.md +206 -0
  112. package/guides/01-INSTALL.md +207 -0
  113. package/guides/02-TABLES.md +225 -0
  114. package/guides/03-AUDIT.md +201 -0
  115. package/guides/04-REBUILD.md +306 -0
  116. package/guides/05-FIRST-RUN.md +191 -0
  117. package/guides/06-EMBEDDING.md +146 -0
  118. package/guides/07-PTC-AND-ASSETS.md +221 -0
  119. package/icon.svg +7 -0
  120. package/locale/en.json +6 -0
  121. package/locale/zh.json +6 -0
  122. package/models/README.md +13 -0
  123. package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
  124. package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  125. package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
  126. package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  127. package/package.json +139 -4
  128. package/src/asset-tool.mjs +237 -0
  129. package/src/assets.mjs +244 -0
  130. package/src/backfill.mjs +74 -0
  131. package/src/backup.mjs +80 -0
  132. package/src/deadend.mjs +95 -0
  133. package/src/deleted-store.mjs +35 -0
  134. package/src/engine.mjs +3831 -0
  135. package/src/facets/classifier.mjs +146 -0
  136. package/src/facets/embedding-service.mjs +53 -0
  137. package/src/facets/embedding-worker.mjs +27 -0
  138. package/src/facets/embedding.mjs +163 -0
  139. package/src/facets/extract.mjs +80 -0
  140. package/src/facets/fold-plan.mjs +84 -0
  141. package/src/facets/hybrid.mjs +29 -0
  142. package/src/facets/index.mjs +113 -0
  143. package/src/facets/linear-probe.mjs +43 -0
  144. package/src/facets/load.mjs +37 -0
  145. package/src/facets/mlp-probe.mjs +54 -0
  146. package/src/facets/mlp-service.mjs +53 -0
  147. package/src/facets/mlp-worker.mjs +26 -0
  148. package/src/facets/online-knn.mjs +58 -0
  149. package/src/facets/online-models.mjs +227 -0
  150. package/src/facets/panel-state.mjs +23 -0
  151. package/src/facets/panel.mjs +73 -0
  152. package/src/facets/random.mjs +26 -0
  153. package/src/facets/scanner.mjs +35 -0
  154. package/src/facets/schema.mjs +75 -0
  155. package/src/facets/seed-examples.mjs +58 -0
  156. package/src/facets/sensitivity.mjs +98 -0
  157. package/src/facets/service.mjs +53 -0
  158. package/src/facets/store.mjs +39 -0
  159. package/src/facets/worker.mjs +23 -0
  160. package/src/folds-store.mjs +109 -0
  161. package/src/groups.mjs +167 -0
  162. package/src/handoff-tool.mjs +51 -0
  163. package/src/handoff.mjs +127 -0
  164. package/src/index.mjs +649 -0
  165. package/src/inject-policy.mjs +17 -0
  166. package/src/learning.mjs +88 -0
  167. package/src/memory/alias-proposal.mjs +40 -0
  168. package/src/memory/bc-audit.mjs +245 -0
  169. package/src/memory/bc-index-updater.mjs +404 -0
  170. package/src/memory/bc-index.mjs +48 -0
  171. package/src/memory/bc-label.mjs +220 -0
  172. package/src/memory/bc-panel-state.mjs +107 -0
  173. package/src/memory/bc-pre-step.mjs +463 -0
  174. package/src/memory/bc-recall.mjs +470 -0
  175. package/src/memory/bc-shift.mjs +123 -0
  176. package/src/memory/bc-store.mjs +41 -0
  177. package/src/memory/bm25.mjs +112 -0
  178. package/src/memory/calibration.mjs +54 -0
  179. package/src/memory/chatter.mjs +29 -0
  180. package/src/memory/cmap-file.mjs +107 -0
  181. package/src/memory/concepts-file.mjs +41 -0
  182. package/src/memory/coref-slots.mjs +40 -0
  183. package/src/memory/event-alias-ledger.mjs +87 -0
  184. package/src/memory/event-log.mjs +95 -0
  185. package/src/memory/event-model.mjs +132 -0
  186. package/src/memory/event-segmenter.mjs +36 -0
  187. package/src/memory/event-summary-tree.mjs +122 -0
  188. package/src/memory/extraction-adapter.mjs +76 -0
  189. package/src/memory/extraction-composite.mjs +75 -0
  190. package/src/memory/extraction-core.mjs +76 -0
  191. package/src/memory/extraction-pre-step.mjs +161 -0
  192. package/src/memory/extraction-provider.mjs +300 -0
  193. package/src/memory/extraction-score.mjs +36 -0
  194. package/src/memory/facet-stabilizer.mjs +59 -0
  195. package/src/memory/history-map.mjs +139 -0
  196. package/src/memory/language-router.mjs +30 -0
  197. package/src/memory/load-gate.mjs +161 -0
  198. package/src/memory/mechanical-note.mjs +104 -0
  199. package/src/memory/onboarding.mjs +163 -0
  200. package/src/memory/persona.mjs +78 -0
  201. package/src/memory/planner.mjs +249 -0
  202. package/src/memory/pre-turn.mjs +80 -0
  203. package/src/memory/query-aliases.mjs +39 -0
  204. package/src/memory/recall-cue.mjs +43 -0
  205. package/src/memory/recall.mjs +65 -0
  206. package/src/memory/referent-candidates.mjs +55 -0
  207. package/src/memory/referent-ledger.mjs +86 -0
  208. package/src/memory/relabel-migrate.mjs +376 -0
  209. package/src/memory/relik-adapter.mjs +25 -0
  210. package/src/memory/special-keywords.mjs +60 -0
  211. package/src/memory/structured-adapters.mjs +75 -0
  212. package/src/memory/summary-index.mjs +335 -0
  213. package/src/memory/summary-tree.mjs +110 -0
  214. package/src/memory/term-extractor.mjs +44 -0
  215. package/src/memory/tokenize.mjs +9 -0
  216. package/src/memory/vector-index.mjs +54 -0
  217. package/src/memory/window-summary.mjs +133 -0
  218. package/src/mood.mjs +133 -0
  219. package/src/peer-tool.mjs +63 -0
  220. package/src/peer.mjs +144 -0
  221. package/src/policy.mjs +170 -0
  222. package/src/preflight.mjs +113 -0
  223. package/src/provenance.mjs +94 -0
  224. package/src/range.mjs +75 -0
  225. package/src/recall-exact-tool.mjs +267 -0
  226. package/src/recall-tool.mjs +521 -0
  227. package/src/recheck.mjs +99 -0
  228. package/src/save-start.mjs +29 -0
  229. package/src/session-guard.mjs +140 -0
  230. package/src/session-log-tool.mjs +322 -0
  231. package/src/session-log.mjs +606 -0
  232. package/src/skills.mjs +111 -0
  233. package/src/split-range.mjs +115 -0
  234. package/src/state.mjs +198 -0
  235. package/src/t0.mjs +149 -0
  236. package/src/tag-confirm.mjs +78 -0
  237. package/src/tag-detect.mjs +42 -0
  238. package/src/tag-maintenance.mjs +285 -0
  239. package/src/tag-reject.mjs +73 -0
  240. package/src/tags-store.mjs +94 -0
  241. package/src/tags.mjs +692 -0
  242. package/src/threads-backend.mjs +26 -0
  243. package/src/threads-core.mjs +1887 -0
  244. package/src/threads-store.mjs +207 -0
  245. package/src/threads-tool.mjs +522 -0
  246. package/src/threads.mjs +929 -0
  247. package/src/tool-preview.mjs +427 -0
  248. package/src/topic.mjs +63 -0
  249. package/src/training.mjs +75 -0
  250. package/src/turn-tag.mjs +62 -0
  251. package/src/user-turn.mjs +39 -0
  252. package/templates/cmap.md +11 -0
  253. package/templates/concepts.md +12 -0
  254. package/templates/habits.md +40 -0
  255. package/templates/keywords.md +6 -0
  256. package/templates/rebuild.md +134 -0
  257. package/templates/rules-safe.md +184 -0
  258. package/templates/rules.md +177 -0
@@ -0,0 +1,366 @@
1
+ # 历史重标迁移:接线方案(v3,已上线 4.11.33)
2
+
3
+ 状态:**已在 4.11.33 上线并按设计跑完一次真实迁移**(2026-11-05 10:29:35 → 10:33:06)。
4
+ 下文的"待复核"已是历史;实测结果见 §13。
5
+
6
+ v2 变更:① 补 per-session 写互斥 ② apply 改为 all-or-nothing ③ 新增"要不要重扫"的打分与 `labelStamp` 增量方案。
7
+ v3 变更:④ `conceptsFile` 必须取 session 词表(`sessionFile(sessionId,'concepts.md')`),不能用 persona/global 回退 ⑤ 覆盖前置检查 ⑥ 实测数据。
8
+
9
+ ## 0. 目标与非目标
10
+
11
+ 目标:
12
+ - 放 `bc-index.relabel-pending.json` 后,下次启动自动完成历史重标(只改 topic 四字段)
13
+ - **任何时刻不出现"迁移写盘"与"updater 追加写盘"互相覆盖**
14
+ - apply 失败 = 原文件零改动(all-or-nothing)
15
+ - 默认不做全量:用 `labelStamp` 让"重扫"变成事件而不是习惯
16
+
17
+ 非目标:
18
+ - 不改 workflow / 不改 raw / 不自动建 marker / 不自动调阈值
19
+
20
+ ## 1. 现有结构(读代码结论)
21
+
22
+ `bc-pre-step.mjs` `installBCPreStep` 的 session 作用域分支:
23
+
24
+ ```
25
+ const bySession = new Map();
26
+ const getSession = (sessionId) => { // 同步
27
+ entry = makeStep(indexFile, sessionId); // loadIndexJsonl + createBLabeler + createIndexUpdater
28
+ };
29
+ ctx.on('agent/pre-step', async (payload, next) => {
30
+ const entry = getSession(payload?.agent?.session?.id);
31
+ return entry.step(payload, next);
32
+ });
33
+ ```
34
+
35
+ - `createIndexUpdater` 写盘是**整文件重写**:`saveIndexJsonl(file, [...records, ...added])`
36
+ - `records` 是 `makeStep` 时读进内存 → 任何外部改写都会被下一次写盘覆盖(已实证:重标期间 live 追加 3 行)
37
+ - updater 内部有 `running` 标志(`if (running || ...) return`),但**没有对外的"等它写完"接口**
38
+
39
+ ## 2. 迁移模型:后台跑 + pre-step 暂停索引 + **每 session 写互斥**
40
+
41
+ ```
42
+ ① pre-step 首次遇到某 session 且 marker 在
43
+ → 启动后台迁移;sessionId 进 relabelling 集合
44
+ ② pre-step 发现 session ∈ relabelling → 直接 next()(不打标、不写盘)
45
+ ③ 迁移完成/失败 → relabelling.delete + bySession.delete
46
+ → 下次 pre-step 重建 entry,从磁盘重读 records
47
+ ④ 迁移窗口内的新轮次:stateFile 的 afterSeq 会在恢复后自动补齐
48
+ ```
49
+
50
+ ### 2.1 写互斥(两条都要,缺一不可)
51
+
52
+ **互斥 A:迁移启动前,等当前 updater 写完**
53
+
54
+ ```
55
+ updateIndex 增加 whenIdle(): Promise<void>
56
+ · **保存当前 running 的 promise**(不是只看 bool;bool 在 promise 刚结束的窗口里不可靠)
57
+ · running 为真 → 返回那个 promise;否则立即 resolve
58
+ 迁移第一步:await updateIndex.whenIdle()
59
+ ```
60
+
61
+ 理由:`relabelling` 集合是"从现在起忽略新的 pre-step",但**已经跑在途中的那一次 step 仍然会写盘**。
62
+ 不 await 它,迁移可能刚写完就被那一次 step 的 `records`(旧标签)整文件覆盖。
63
+
64
+ **互斥 B:updater 每次 save 前检查"没人正在迁移"**
65
+
66
+ ```
67
+ 共享 gate(同一 store 对象内):
68
+ { relabelling: Set<sessionId>, lockOwned: boolean }
69
+ updater step 内,saveIndexJsonl 之前:
70
+ if (gate.relabelling.has(sessionId)) → 放弃本次写
71
+ if (gate.lockOwned) → 同上(另一个进程/本进程在迁移)
72
+ ```
73
+
74
+ **放弃写盘时必须遵守两条(创 2026-11-04 补充):**
75
+
76
+ ```
77
+ ① 不推进 stateFile 的 lastSeq / afterSeq
78
+ ② added 留在"待重试队列"(内存 pending),下一次 step 先合并再写
79
+ ```
80
+
81
+ 否则被 gate 挡掉的那几轮**永远不会再被处理**(lastSeq 已前移,updater 认为已同步)。
82
+ 即在 gate 路径里:`pending.push(...added)` + `return`,**不要**调用 `saveState`。
83
+
84
+ 理由:互斥 A 只挡住"迁移开始那一刻";迁移进行中若有**非 pre-step 路径**触发了写盘(例如手动 tools 或第二个入口),B 是最后一道闸。
85
+ **A 与 B 同时存在**:A 保证顺序,B 保证事后,且 B 必须"挡得住且不丢数据"。
86
+
87
+ ### 2.2 锁(跨进程)
88
+
89
+ ```
90
+ 文件:<sessionDir>/bc-index.lock 内容:{ pid, startedAt, host }
91
+ 获取:fs.open(lock, 'wx') 成功即持有;EEXIST 时读 startedAt
92
+ · 超过 90 分钟 → 视为过期,覆盖 + warn
93
+ · 否则 abort,log 'relabel: another process holds lock'
94
+ 写盘:tmp 文件名带 pid;rename 前再确认 lock 属于自己
95
+ 释放:结束(成功/失败)unlink;进程崩溃靠过期回收
96
+ ```
97
+
98
+ ## 3. apply 必须 all-or-nothing
99
+
100
+ **dry-run**:允许 unresolved,统计里单独列出。
101
+
102
+ **apply**(写盘):只要出现下面任一情况 → **abort,marker 保留,原 index 一个字节不动**:
103
+
104
+ ```
105
+ ① readAllEvents 拿不全(见 §4)
106
+ ② index 里任一 turn 无法重建 text(出现 unresolved)
107
+ —— 必须在"写备份/写 tmp"之前检查;不能靠后面的字段校验兜底
108
+ (旧行若已带同 stamp,字段校验会通过 → rename + 删 marker,留下未重标行)
109
+ ③ 行数不一致
110
+ ④ 非目标字段深度不等(**labelStamp 例外**,见下)
111
+ ⑤ tmp 写入/rename 失败
112
+ ```
113
+
114
+ **②的检查位置(创 2026-11-04 指出必须前置)**:第一遍打标收集分数后,
115
+ 立即算 `unresolved = index 中 turn 不在 scored 里的行`;`> 0` → 直接 abort、**尚未写任何文件**。
116
+
117
+ **labelStamp 是第五个允许变更的字段(自相矛盾修正,创 2026-11-04 指出)**
118
+
119
+ §5.2 让每条记录带 `labelStamp`,而迁移只允许改 topic 四字段 —— 那第一次迁移就会被自己的校验 abort。
120
+
121
+ 二选一的定案:**把 `labelStamp` 加进 `ALLOWED_FIELDS`**,并额外加一条强校验:
122
+
123
+ ```
124
+ 迁移把每行写成"本次依据的戳"(sha256(A表内容|阈值|labelerVersion)[:12])
125
+ 校验时:after[i].labelStamp 必须恰好等于本次 stamp
126
+ · 不等(或缺失、或写成别的值)→ 视同越界,abort
127
+ ```
128
+
129
+ 这样"允许变"和"必须等于什么"同时成立,不会变成可以随便写 stamp 的口子。
130
+ (备选方案"迁移后再写 stamp"不采用:它会让迁移完成后的一瞬间存在"新 topic + 旧 stamp"的混合态。)
131
+
132
+ **明确不做"部分写"**:不写"能标多少标多少"。半套标签比旧标签更危险——它会让覆盖率看起来变好,实际是抽样偏差。
133
+
134
+ ## 4. 事件读取:provider 入参 + 只认全量 API,不猜
135
+
136
+ **模块入参(in-process 与 CLI 的差别)**
137
+
138
+ ```
139
+ runRelabelMigration({ indexFile, events, rawFile?, conceptsFile, markerFile, labeler, eventText, log })
140
+ · events: async () => Event[] 插件内用(宿主没有 raw 文件路径,只有 sessionQuery.readSession)
141
+ · rawFile: string CLI 专用;与 events 二选一
142
+ · 两者都没给 → abort 'no-event-source'
143
+ ```
144
+
145
+ ```
146
+ 优先:sessionQuery.readSession(sessionId) → { events }
147
+ (asset-tool / recall-tool / session-log-tool 都这么用,是最全的读法)
148
+ 检查:读回来的 events 是否覆盖 index 中所有 turn
149
+ 覆盖 = index 里每个 turn 都能在 events 推出的 turn 集合里找到
150
+ 不覆盖 → abort(apply)/ 记 unresolved(dry-run)
151
+
152
+ 若宿主只提供 readSessionTail(afterSeq, limit)(没有不带参的全量读)
153
+ → **apply 直接 abort**,log 'full-read-unavailable'
154
+ → 不写"反向翻页凑"的代码:只有当 API 明确支持 beforeSeq 时才谈翻页
155
+ ```
156
+
157
+ 理由:反向翻页在语义上要求"每页都稳定",而 tail API 不保证;猜错一页就会出现"少了几轮但看起来成功"的迁移——这正是我们要避免的。
158
+
159
+ ## 5. 要不要重扫:打分与增量(**新增,回应"每次重扫太占 CPU"**)
160
+
161
+ ### 5.1 第一层:依据没变就不扫(毫秒)
162
+
163
+ 判定依据只有三样:A 表内容、判定逻辑(labeler 版本 + 阈值)、打标输入规则。任何一样没变,重扫结果必然逐位相同(同模型、同输入、确定性)。
164
+
165
+ ### 5.2 `labelStamp`:让"过期"可数
166
+
167
+ ```
168
+ labelStamp = sha256(concepts.md 内容 + 阈值(topicMinScore/centerMin/lead) + labelerVersion)[:12]
169
+ ```
170
+
171
+ `labelerVersion` 必须来自真实来源,但**不能用包版本**(创 2026-11-04 定):
172
+
173
+ ```
174
+ ① bc-label.mjs 的显式常量 LABELER_VERSION(默认来源)
175
+ · 只在"会改变打标结果"时手工 bump:分块规则、阈值默认值、center/lead 判据、
176
+ 描述相似度算法、文本拼接规则
177
+ · 纯文档/日志/UI 改动**不 bump**
178
+ ② marker.labelerVersion(仅回归测试时显式覆盖)
179
+ ③ 都拿不到 → abort 'no-labeler-version'
180
+ ```
181
+
182
+ 用包版本会让每次发版(哪怕只改文档)把 stamp 全员失效 → 触发一次 30 分钟全量重扫,
183
+ 与"增量/不扫"的目标直接冲突。
184
+
185
+ - updater 每写一行都带 `labelStamp`
186
+ - 迁移前统计:`stale = count(rows where labelStamp !== current)`
187
+
188
+ ```
189
+ stale = 0 → 不扫(毫秒)
190
+ stale ≤ 5% → 只重标 stale 行(增量)
191
+ stale > 5% → 抽样估计,再决定
192
+ ```
193
+
194
+ 第一次迁移之后,新行自带当前 stamp,**A 表不变时永远是 0 行待标**。
195
+
196
+ ### 5.3 抽样估计(stamp 变了才做)
197
+
198
+ ```
199
+ 随机抽 N=50 行(按 turn 均匀)
200
+ change_rate = 改了的 / 50
201
+ coverage_after = 有 topic 的 / 50
202
+
203
+ 决策:
204
+ change_rate < 5% → 跳过(warn:表变了但影响小)
205
+ 5% ≤ change_rate < 20% → 增量(只重标 stale 行)
206
+ change_rate ≥ 20% → 全量
207
+ coverage_after < 40% 且 rate ≥ 5% → 无论多少都做
208
+ ```
209
+
210
+ 成本:抽样 50 行 ≈ 1 分钟;全量 2125 行 ≈ 30–40 分钟(今晚实测)。抽样是全量的 1/40。
211
+
212
+ ### 5.4 打分公式(给面板/log 用)
213
+
214
+ ```
215
+ staleness =
216
+ 0 依据未变
217
+ staleRatio (0–1) 有依据变更时的过期行占比
218
+ sampledChangeRate 已抽样时用实测变化率(更可信)
219
+ 触发:staleness > 0 且 (change_rate ≥ 5% 或 staleRatio ≥ 5%)
220
+ ```
221
+
222
+ ## 6. marker schema(v2)
223
+
224
+ ```json
225
+ {
226
+ "fields": ["topic"],
227
+ "baseline": "self-loo",
228
+ "conceptsHash": "<sha256>",
229
+ "labelStamp": "<12 hex>",
230
+ "thresholds": { "topicMinScore": 0.33, "centerMin": 0.02, "lead": 0.02 },
231
+ "mode": "auto|full|incremental",
232
+ "createdAt": "...",
233
+ "attempts": 0,
234
+ "lastError": null
235
+ }
236
+ ```
237
+
238
+ 校验:`fields` 必须恰为 `["topic"]`;阈值必须有限且在区间内(0.2–0.9 / 0–0.5 / 0–0.5),否则用默认 + warn;实际生效阈值写回 `effectiveThresholds`;`attempts > 3` 停止自动重试。
239
+
240
+ ## 7. 失败与状态
241
+
242
+ - `conceptsHash` 不符 → abort,marker 保留
243
+ - 事件不全 / 有 unresolved / 行数或字段校验失败 → abort(原文件不动),`attempts += 1`
244
+ - 成功 → unlink marker,log 统计(重打、换 topic、→空、空→topic、覆盖率 before/after)
245
+ - 覆盖率 < 50% → **只 warn**
246
+ - 所有路径 try/catch 包住,不抛到 pre-step
247
+
248
+ ## 8. 可观测性
249
+
250
+ `/dsh-continuity/state.json` 的 `engine` 增加:
251
+
252
+ ```json
253
+ "relabel": { "pending": true, "sessionId": "...", "status": "running|done|abort|idle",
254
+ "mode": "incremental", "stats": {...}, "startedAt": "..." }
255
+ ```
256
+
257
+ ## 9. 接线点(最小改动)
258
+
259
+ 1. `bc-pre-step.mjs`:`getSession` 改 async;返回 `{skip:true}` 或 entry
260
+ 2. pre-step handler:`await getSession(...)`,skip 就 `next()`
261
+ 3. `bc-index-updater.mjs`:加 `whenIdle()`;`step` 内 save 前检查 gate
262
+ 4. `index.mjs`:无需改动
263
+ 5. `relabel-migrate.mjs`:入参改 `{ events: async () => Event[] }`;加 lock / all-or-nothing / stamp 过滤 / 抽样模式
264
+
265
+ ### 9.1 实际实现(4.11.30,与上面两处不同)
266
+
267
+ `getSession` 保持**同步**,用哨兵返回,而不是改成 async:
268
+
269
+ ```
270
+ const SKIP_DURING_RELABEL = Symbol('skip-during-relabel')
271
+ getSession(sessionId):
272
+ entry 已缓存 → 直接返回
273
+ 否则:
274
+ marker 存在 && 本进程没尝试过 → relabelAttempted.add + startRelabel()(后台,不 await)
275
+ relabelling.has(sessionId) → return SKIP_DURING_RELABEL
276
+ 否则 → makeStep()(普通路径)
277
+ handler: entry === null || entry === SKIP → next()
278
+ ```
279
+
280
+ **每进程只自动尝试一次(`relabelAttempted`)**:
281
+ abort 后不死循环重试(否则每轮都 abort 刷日志),本进程恢复普通索引,marker 留着等下次启动或人工处理。
282
+ "跳过"只与 `relabelling` 集合绑定,不与 marker 绑定——所以 abort 之后索引会正常继续。
283
+
284
+ **隔离性**:`installBCPreStep` 新增可选参数 `relabelRunner`(默认 `runRelabelMigration`),
285
+ 测试可以注入假 runner,不需要模型、不碰真实 `~/.dsh`(用子进程 + 临时 USERPROFILE/HOME)。
286
+
287
+ ## 10. 测试计划
288
+
289
+ 单测:
290
+ - marker 缺失 / fields 非法 / baseline 非法 → no-op 或 abort
291
+ - conceptsHash 不符 → abort,文件字节不变
292
+ - 阈值非法 → 默认 + warn
293
+ - 锁 fresh → abort;锁过期 → 覆盖成功
294
+ - 事件不全 → **apply abort**(dry-run 记 unresolved)
295
+ - 行数/字段校验失败 → abort,原文件字节不变
296
+ - `labelStamp` 全等 → 0 行重标
297
+ - `decideTopic` vs labeler 判定一致(已验 8/8,固化成用例)
298
+
299
+ 集成(临时 session):
300
+ - 200 行小 index + 假事件源 → 成功:备份存在、marker 删除、非目标字段零改动
301
+ - 中途 kill → 重启:marker 在、tmp 被清理、再跑成功
302
+ - 迁移中触发 updater → 被 gate 挡住,不写盘
303
+
304
+ ## 11. 上线顺序
305
+
306
+ 1. 4.11.28:只合 `relabel-migrate.mjs` + 单测(不接线)
307
+ 2. 4.11.29:加 `labelStamp` + updater `whenIdle()` + gate
308
+ 3. 4.11.30:接线(后台 + skip + lock + state 暴露)
309
+ 4. 观察 1–2 天,本 session 打 marker 试一次(覆盖率 27% 的 warn 会打出来)
310
+ 5. 稳定后在 rules/模板写"其他鱼怎么用":**先换 A 表 → 打 marker → 重启**
311
+
312
+ ## 12. 已知取舍
313
+
314
+ - 迁移期间该 session 不索引(state 会补齐)
315
+ - 2k 行全量 30–40 分钟(本地 CPU,无网络);增量与抽样把日常成本压到接近 0
316
+ - 覆盖率低于 50% 不自动修(阈值/baseline 应人工决定)
317
+ - workflow 不重标(等 workflow baseline ≥100 样本)
318
+
319
+ ## 13. 实测结果(2026-11-05,真实 session)
320
+
321
+ 第一次真实迁移(4.11.33)完成于 2026-11-05 10:29:35 → 10:33:06:
322
+
323
+ ```
324
+ index 2148 行 · 重打 2148 · unresolved 0
325
+ 换 topic 889(→空 158 · 空→topic 582)· 不变 1259
326
+ 备份 bc-index.jsonl.bak-20261105023306
327
+ marker 删除;gate 释放;updater 追平(2150 行 / turn 2173)
328
+ labelStamp 覆盖 100%
329
+ ```
330
+
331
+ 分布:
332
+
333
+ ```
334
+ 有 topic 15.0% → 34.7%
335
+ 活跃标签(≥1%) 2 → 9
336
+ 最大单标签 权限与通知 12.2% → 跑团叙事 10.9%
337
+ ```
338
+
339
+ **两个实测修正(比设计预估更重要)**
340
+
341
+ 1. **速度:约 10 行/秒**(2148 行 / 211 秒)。
342
+ ONNX 推理吃多核:实测 +47 CPU 秒 / 8 墙钟秒 ≈ 6 核。
343
+ → **以后给别的实例估时用"2k 行 ≈ 3–5 分钟"**,不是 30 分钟。
344
+ 结论也跟着变:全量重标的代价比原设想低一个量级,"要不要采样/增量"的取舍可以放松,
345
+ `labelStamp` 仍然保留(它省的是"没变也重扫",不是"变了也要扫")。
346
+
347
+ 2. **`readSession` 是全量的**:83,463 事件 / 2,167 轮,8 秒读完。
348
+ 此前"有界快照"的猜测被实测否掉。
349
+ **覆盖前置检查保留**——它不是为这个假设服务的,而是安全网:
350
+ 任何"事件不全"的情况都必须在打标之前 0.1 秒失败。
351
+
352
+ **踩过的坑(都已写进 rules)**
353
+
354
+ - 失败必须先留文件痕迹:console 在桌面进程里看不到(4.11.31 的日志代码还因 `fs` 未导入而全哑)
355
+ - `catch {}` 会吞掉"用了不存在的变量":关键路径至少 `console.error` 一次
356
+ - 迁移必须用 **session 词表**(`sessionFile(sessionId,'concepts.md')`),不能用 persona/global 回退;
357
+ 否则 hash 永远不符、每次重启 0.1 秒 abort(实际症状:CPU/风扇抖一下就停)
358
+ - 测试夹具目录必须与生产同构,否则"路径正确"这类 bug 测不出来
359
+
360
+ ### 13.1 安全增量(4.11.34)
361
+
362
+ `labelStamp` 全表已等于当前 stamp → 0 行重打、直接删 marker 完成;只要有一行 stale → 全量重标。
363
+
364
+ 不做"逐行跳过 stale":self-LOO baseline 会因少算行而偏,写出来的 centered 不可信。`forceFull` 可强制全量。
365
+
366
+ 全量实测 3–5 分钟,所以增量首先是"没变就不扫",不是省全量时间。
@@ -0,0 +1,206 @@
1
+ # 00 — 先读这个
2
+
3
+ 如果你刚拿到这个包,先不要急着安装。
4
+
5
+ 这一页不是操作步骤,而是告诉你:
6
+
7
+ ```text
8
+ 这套东西到底在管什么
9
+ 哪些事它不会替你做
10
+ 你最容易误会的地方在哪里
11
+ ```
12
+
13
+ 读完再按 01–07 走。否则很容易出现一种情况:
14
+
15
+ PTC / 子 agent 与资产指针见 `07-PTC-AND-ASSETS.md`。
16
+
17
+ ```text
18
+ 插件装上了,面板也开了,
19
+ 但 A/B/C/K 全是空的,
20
+ 于是以为插件坏了。
21
+ ```
22
+
23
+ 那通常不是坏了,而是它本来就不会自动替你长出一套记忆。
24
+
25
+ ## 1. 它不是什么
26
+
27
+ 它不是 DSH 自带的记忆系统,也不是一个“安装后自动理解你”的黑盒。
28
+
29
+ 它是给每个 session 准备的一套本地记忆文件:
30
+
31
+ ```text
32
+ sessions/<sessionId>/
33
+ bc-index.jsonl 这一轮轮的指针
34
+ concepts.md A 表:话题
35
+ cmap.md C 表:用户词
36
+ keywords.md K 表:精确名
37
+ aliases.md 别名
38
+ rules.md 这个 session 的硬规则
39
+ habits.md 这个 session 的软偏好
40
+ ```
41
+
42
+ 它负责把这些文件组织起来、在上下文里放指针、在压缩时留下可回查的线索。
43
+
44
+ 它不负责替你想:
45
+
46
+ ```text
47
+ 哪些话题值得进 A
48
+ 哪些词应该进 K
49
+ 哪个用户词应该映射到哪个概念
50
+ ```
51
+
52
+ 这些需要这个 session 自己,或者你,明确写进去。
53
+
54
+ ## 2. 为什么每个 session 一套
55
+
56
+ 因为不同 session 本来就可能不是同一个“人”。
57
+
58
+ ```text
59
+ session A 在聊跑团
60
+ session B 在聊代码
61
+ session C 只是临时问一句
62
+ ```
63
+
64
+ 如果它们共用一套 A/C/K,B 会很容易把 A 的话题当成自己的记忆。
65
+
66
+ 所以这套设计选择:
67
+
68
+ ```text
69
+ 一个 session 一套 A/B/C/K
70
+ 新 session 从空表开始
71
+ 不继承另一个 session 的词表
72
+ ```
73
+
74
+ 代价是:
75
+
76
+ ```text
77
+ 新 session 不会一上来就“记得”任何概念
78
+ B 标签一开始也是空的
79
+ ```
80
+
81
+ 这是设计后果,不是故障。
82
+
83
+ ## 3. 为什么 A 空,B 也空
84
+
85
+ B 标签不是凭空生成的。它是用 A 表的概念去给每一轮打分。
86
+
87
+ ```text
88
+ A 表为空
89
+ -> 没有候选概念
90
+ -> 每一轮都无法打 B 标签
91
+ -> B 空
92
+ ```
93
+
94
+ 同样:
95
+
96
+ ```text
97
+ A 表为空
98
+ -> 审计没有可评分的概念
99
+ -> 审计跳过
100
+ ```
101
+
102
+ 所以一个 500 轮的 session,如果 A 从来没被写过,看到的就是:
103
+
104
+ ```text
105
+ B 空
106
+ A/C/K 空
107
+ weak turns 很多
108
+ ```
109
+
110
+ 这并不矛盾。
111
+
112
+ **比如这种情况:**
113
+
114
+ ```text
115
+ 如果 A 表是空的,而你以为“聊了 500 轮,B 应该自动有了”,
116
+ 则会看到 500 轮全是 weak、B 空、审计也一直不来。
117
+ ```
118
+
119
+ 这不是索引坏了,是 B 依赖的那张 A 表从未建立。
120
+
121
+ ## 4. 为什么 rules 这么重要
122
+
123
+ 每轮上下文里会有一个 `[规矩]` 指针,指向当前 session 的 `rules.md`。
124
+
125
+ 首轮、规则变更、承载消息被折叠后,还会注入 rules 全文。
126
+
127
+ 所以:
128
+
129
+ ```text
130
+ rules 写了什么,实例就会看到什么
131
+ rules 没写什么,实例通常就不会主动做
132
+ ```
133
+
134
+ **比如这种情况:**
135
+
136
+ ```text
137
+ 如果 rules 只写了“K 放精确名,C 放用户词”,
138
+ 却没写“A 表要怎么维护”,
139
+ 则实例会认真维护 K/C,但完全不碰 A。
140
+ ```
141
+
142
+ 这不是实例懒,是规则里没有这条。
143
+
144
+ ## 5. 三条底线
145
+
146
+ ### 不继承
147
+
148
+ ```text
149
+ 不要把另一个 session 的 concepts.md 复制过来
150
+ 不要共用一份 A/C/K
151
+ ```
152
+
153
+ ### 不伪造
154
+
155
+ ```text
156
+ 没有原文就说没有
157
+ 没有候选就说没找到
158
+ 不要把 [gist] 当原文
159
+ 不要把 B 标签当事实
160
+ ```
161
+
162
+ ### 不越界
163
+
164
+ ```text
165
+ 只改自己的 sessions/<id>/
166
+ 不碰别的 session
167
+ 不改安装目录、package.json、lockfile、tgz
168
+ ```
169
+
170
+ ## 6. 阅读顺序
171
+
172
+ 按顺序读,不要跳:
173
+
174
+ 1. [01-INSTALL.md](01-INSTALL.md) — 装进去,第一次启动看到什么
175
+ 2. [02-TABLES.md](02-TABLES.md) — A/B/C/K 各管什么,空表意味着什么
176
+ 3. [03-AUDIT.md](03-AUDIT.md) — 200 轮后提示什么,为什么有时不来
177
+ 4. [04-REBUILD.md](04-REBUILD.md) — 有历史但没表,怎么从旧对话里长出来
178
+ 5. [05-FIRST-RUN.md](05-FIRST-RUN.md) — 从空表到第一次审计的路线
179
+
180
+ 如果你只有一个新 session,先读 01、02、05。
181
+
182
+ 如果你要救一个已经聊了很久、但 A/B/C/K 空的 session,直接读 04。
183
+
184
+ 如果你发现“面板打开了但全是空表”,先问自己:
185
+
186
+ ```text
187
+ 这个 session 的 A 表,曾经被谁写过吗?
188
+ ```
189
+
190
+ 如果没有,空表就是答案。
191
+
192
+ ## 7. 这份文件是入口
193
+
194
+ `00-READ-FIRST.md` 不是装饰。
195
+
196
+ 它负责把“为什么”讲清楚,其他文件负责“怎么做”。
197
+
198
+ **比如这种情况:**
199
+
200
+ ```text
201
+ 如果只看 01 的命令,不看这里的 A/B 关系,
202
+ 则装完之后看到空表,很容易以为插件坏了。
203
+ ```
204
+
205
+
206
+ 如果没有,空表就是答案。