@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,195 @@
1
+ # Continuity 0.4.66 说明书
2
+
3
+ > **Historical (0.4.66).** 当前版本请读 `README.md` 和 `docs/ARCHITECTURE.md`。
4
+
5
+ > 给要装这套东西的实例(鱼)和它们的用户。
6
+ > 版本 0.4.66 · 本文只描述**已实现**的东西,不描述计划。
7
+
8
+ ---
9
+
10
+ ## 1. 这是什么
11
+
12
+ 一个 DSH 插件 + 一套放在 `~/.dsh/continuity` 的本地工具。它做三件事:
13
+
14
+ 1. **存**:把一段对话按线头(thread)收成“段(segment)”,带 `why / next / 残留`;
15
+ 2. **取**:按标签或 seq 区间把原文取回来(`load`);
16
+ 3. **判**:每轮把消息归到某个标签(T),并强制“每条内容都被判过”。
17
+
18
+ 它不接管模型的上下文,也不改 DSH 核心;它只在“折叠前后”和“每轮开头”做事。
19
+
20
+ ---
21
+
22
+ ## 2. 装了以后目录长什么样
23
+
24
+ ```
25
+ ~/.dsh/continuity/
26
+ rules.md 习惯全文(第一轮会整篇注入,之后只给标题行)
27
+ tags.json 标签表(T1…Tn)
28
+ threads.json 线头与段(存档索引)
29
+ turn-tags.jsonl 每轮一个标签(轮 → 标签)
30
+ topic-anchors.json 每个标签的“必需词”(不在 tags.json 里,因为会被归一化吃掉)
31
+ generic-words.json 泛词表(区分度低,不能单独当证据)
32
+ t0-decisions.jsonl 显式判 T0 的记录
33
+
34
+ turnmap.mjs turn ↔ seq ↔ 标签 的桥
35
+ t0decide.mjs 判 T0 / 判标签
36
+ newtopics.mjs save 前的新事物体检
37
+ tag-gap.mjs 标签缺口清单
38
+ match-tags.mjs 查表:这句话像哪个 T
39
+ kw-audit.mjs 词表体检 / explain(泛词不算证据)
40
+ doctor.mjs 修 session 日志格式错(如 system head 损坏)
41
+ ```
42
+
43
+ **没有这些工具,闸会拒绝但没法补。** 打包时必须一起带上。
44
+
45
+ ---
46
+
47
+ ## 3. 第一轮会发生什么
48
+
49
+ 头里会出现一行(注入的上下文):
50
+
51
+ ```
52
+ [线] 当前:<线头或空> · 下一步:<上次写的 next>
53
+ [标签] 当前 T? · 另有 N 条 · 未覆盖 M(含结构事件)
54
+ [习惯] 你的习惯:存 · 取 · 认 · …(标题行)
55
+ ```
56
+
57
+ 然后每轮还会出现:
58
+
59
+ | 出现 | 含义 | 我该做什么 |
60
+ |---|---|---|
61
+ | `[归位·上句] 判「…」· 命中 T3` | 引擎自动给上一条消息贴了标 | 不用管(已落) |
62
+ | `[候选] T12 · T14 —— 要贴吗?` | 零命中/多命中,需要人判 | 判:贴哪个 / T0 / 新建 |
63
+ | `[话题?] 上句更像 T8;凭 …` | 上句像换话题 | 先 `kw-audit explain`,再决定切不切 |
64
+ | `[打标?] turn N 还没有标签` | 上一轮漏标 | 立刻 `turnmap.mjs set --turn N --tag Tx` |
65
+ | `[死胡同?]`(save 时才出现) | 改动无判据 / 同处改多版 | 先写判据,或换地方改 |
66
+
67
+ ---
68
+
69
+ ## 4. 四道闸(save 时才会拒绝)
70
+
71
+ | 闸 | 拒绝条件 | 补法 |
72
+ |---|---|---|
73
+ | `t0-undecided` | 折叠区间里有未决消息 | `t0decide.mjs decide --seq N --as t0` 或贴标签 |
74
+ | `turn-untagged` | 有轮没有标签 | `turnmap.mjs set --turn N --tag Tx` |
75
+ | `tags-gap` | 已结束轮里缺 >2 个标签 | `tag-gap.mjs` 看清单补判 |
76
+ | `deadend-symptoms` | 同处改多版 / 改完仍同错 / 改动无判据 | 先写判据;真要带债保存,放 `t0-override.json` |
77
+
78
+ **闸拒绝不是故障**,是“该补的账列出来了”。
79
+
80
+ ---
81
+
82
+ ## 5. 怎么写 rules.md(重点)
83
+
84
+ ### 5.1 先分清三类规则
85
+
86
+ | 类 | 例子 | 写不写 |
87
+ |---|---|---|
88
+ | **不变量** | “未决 = 0”“每轮一个标” | 代码已强制 → **别重复写**,最多写一行指向闸 |
89
+ | **操作** | “save 前先跑 newtopics” | **写触发条件**:“当 X 时,跑 Y” |
90
+ | **判断** | “迹象 ≠ 状态”“只有用户陈述的结论标待验证” | **写判据**:“如果 A,则 B;反例是 C” |
91
+
92
+ ### 5.2 一条规则的标准形状
93
+
94
+ ```
95
+ 动词开头 + 触发条件 + 可执行动作(或可判定的判据) +(可选)一条反例
96
+ ```
97
+
98
+ 好:
99
+
100
+ > **收到 `[话题?]` 先体检再切**:跑 `kw-audit explain "<上句>"`;带 `(泛)` 的词不算证据 → 不切。
101
+
102
+ 坏:
103
+
104
+ > 要谨慎对待话题切换,保持敏感。 ← 没有触发条件,没有动作,无法判对错
105
+
106
+ ### 5.3 只从事故里长
107
+
108
+ **每条规则必须能回答:不加它,会出什么事?**
109
+
110
+ 写法:`事故(一句话) → 规则(一句话)`。事故可以是:
111
+ - 一次真实的错误(“我把「跑团」判成泛词”);
112
+ - 一次被用户指正;
113
+ - 一次闸拒绝。
114
+
115
+ **没有事故的规则不写**——那是许愿,不是规则。
116
+
117
+ ### 5.4 上限与合并
118
+
119
+ - 单个主题 ≤ 40 行;全文 ≤ 12KB;
120
+ - 追加前查重(`skill-append.mjs` 或人眼);
121
+ - 同一症状出现两次 → **合并成一条原则**,例证缩成一行;
122
+ - 合并后条数必须下降,否则不算合并。
123
+
124
+ ### 5.5 不要写的四类
125
+
126
+ 1. **格言体**:“保持诚实”“不猜不赌”——除非能落成动作;
127
+ 2. **重复闸**:闸已经挡了的事再写一遍,规则会变长却没变强;
128
+ 3. **形容词指令**:“尽量”“适当”“灵活”——没有判据;
129
+ 4. **别人的规则**:从别处抄来的、自己没付过代价的。
130
+
131
+ ### 5.6 演进节奏
132
+
133
+ ```
134
+ 第 1 天:只有 CONDUCT(30 行)
135
+ 第 1 周:每次闸拒绝 / 被指正 → 加一条,带事故
136
+ 第 1 月:合并一次,砍掉重复与格言
137
+ ```
138
+
139
+ **参差不齐是对的**:不同鱼有不同的历史,规则自然会不同。**相同的是底层不变量(代码强制)与工具接口**;不同的是判断口味。**不要为了整齐去统一口味**——那会变成表演。
140
+
141
+ ---
142
+
143
+ ## 6. 常用命令速查
144
+
145
+ ```bash
146
+ # 打标
147
+ node ~/.dsh/continuity/turnmap.mjs set --turn 1043 --tag T4
148
+
149
+ # 查表
150
+ node ~/.dsh/continuity/match-tags.mjs "这句话像哪个话题"
151
+
152
+ # 体检一个候选(泛词不算证据)
153
+ node ~/.dsh/continuity/kw-audit.mjs explain "上句原文"
154
+
155
+ # 轮 → seq(给 load 用)
156
+ node ~/.dsh/continuity/turnmap.mjs ranges --tag T4
157
+
158
+ # 判 T0
159
+ node ~/.dsh/continuity/t0decide.mjs decide --seq 12345 --as t0
160
+
161
+ # 标签缺口
162
+ node ~/.dsh/continuity/tag-gap.mjs
163
+
164
+ # 修 session 格式
165
+ node ~/.dsh/continuity/doctor.mjs "<session.v4.jsonl.zstd>"
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 7. 已知限制(别踩)
171
+
172
+ 1. **C7 只数 `edit/write`**:用 pwsh 脚本改文件,检测器看不到——要么用 edit/write,要么自己数改了几版;
173
+ 2. **已折叠的混段不切**:切它要重写摘要,等于改历史;它们带 `mixedTags` 标注,取时用窄范围;
174
+ 3. **load 只能取已入段的区间**:未保存的对话取不到(它本来就在上下文里);
175
+ 4. **mood A/B 目前空结果**:五个行为特征全不显著(nA=32 但只有 2 次独立注入,统计单位存疑),**没有证据**支持“痕迹改变风格”;
176
+ 5. **`tags.json` 会被插件归一化**:自定义字段(如 anchors)会被丢掉,必须放独立文件。
177
+
178
+ ---
179
+
180
+ ## 8. 出问题时
181
+
182
+ | 现象 | 先查 |
183
+ |---|---|
184
+ | save 一直被拒 | 看拒绝码,按码补账(见第 4 节) |
185
+ | 头里没有 `[打标?]` 但确实漏标 | `node turnmap.mjs todo` |
186
+ | 面板数字不动 | `curl` 本机 `/dsh-continuity/tags.json`(需登录态)或看 `threads.json` mtime |
187
+ | session 打不开 | `doctor.mjs` 先诊断,别直接删 |
188
+ | 标签判错 | `kw-audit explain` + `match-tags` 双查,别凭感觉改词表 |
189
+
190
+ ---
191
+
192
+ ## 9. 一句话总结
193
+
194
+ **代码管不变量,规则管判断,事故管规则。**
195
+ 不变量少而硬,判断多而软,事故是唯一的老师。
package/docs/GUIDE.md ADDED
@@ -0,0 +1,127 @@
1
+ # dsh-continuity 使用说明书(0.4.37)
2
+
3
+ > **Historical (0.4.37).** 当前版本请读 `README.md` 和 `docs/ARCHITECTURE.md`;下面的路径/行为可能已过时。
4
+
5
+ > 给新接手的 AI 实例。读完这一份,你应该知道:它是什么、数据在哪、什么自动、什么要你判断、坏了怎么查。
6
+
7
+ ## 1. 它是什么
8
+
9
+ Continuity 是 DSH 的一个**读侧插件**:它不接管对话,只在每轮请求前**注入一段上下文**,并在你调用 `save` / `load` / `tag` 时维护线头与标签。
10
+
11
+ 三个概念:
12
+
13
+ - **线头(thread)**:一段工作的存档单元,有 `label`、`next`、`segments`;
14
+ - **标签(tag)**:一种归属;一个 seq 可以属于多个标签(多标签);
15
+ - **组(group)**:若干标签的父视图(例如 `设备选购 = [T16, T17]`)。
16
+
17
+ **能力边界**:它只读会话、写自己的数据目录;**不改宿主程序**。
18
+
19
+ ## 2. 装在哪、版本怎么算
20
+
21
+ - 插件目录:`coopanion-continuity/dsh-plugin`;
22
+ - 安装方式:`~/.dsh/profiles/desktop/node_modules/dsh-continuity` 是指向它的 **junction/link**(不是拷贝);
23
+ - **版本是启动时快照的**:改代码后必须重启才生效。判断方法:比较进程 `StartTime` 与 `package.json` 的 mtime。
24
+
25
+ ## 3. 数据文件(都在 `~/.dsh/continuity/`)
26
+
27
+ | 文件 | 作用 | 能否手改 |
28
+ | --- | --- | --- |
29
+ | `threads.json` | 线头、段、gist | 别手改,用工具 |
30
+ | `tags.json` | `tags` / `runs` / `overrides` / `memberships` | 别手改,用脚本 |
31
+ | `groups.json` | 父视图(`source: manual` / `auto`) | 可手改 |
32
+ | `group-evidence.jsonl` | 每次多命中的证据(pair / members / 原句) | 只追加 |
33
+ | `groups-answered.json` | 已问/已答的归拢提议 | 只追加 |
34
+ | `learning/answered.jsonl` | 学习候选的答复(skip / kept) | 只追加 |
35
+ | `learning/candidates.json` | 最近一次扫描的重复错误 | 可删(会重建) |
36
+ | `context-log.jsonl` | 每次测量的 estTokens 等 | 只追加 |
37
+ | `newtag-asked.json` | 已问过的新标签片段 | 可删 |
38
+ | `rules.md` | 我自己的规则(全文只在首轮/折叠后注入) | **该我写** |
39
+ | `skills/*.md` | 技能(按线头+关键词注入) | **该我写** |
40
+
41
+ ## 4. 每轮注入链(顺序固定)
42
+
43
+ 1. `[线]` 当前线头 + `next`;
44
+ 2. `[标签]` 当前标签 + 未覆盖数(**含结构事件**,不是内容遗忘率);
45
+ 3. `[习惯]` 规则标题行(全文仅在首轮或承载消息被折叠后重注);
46
+ 4. `[技能]` 正在用的技能名 + 一次 `[技能正文]`;
47
+ 5. `[归位·上句]` 对**上一条**用户消息的判定(唯一 / 多标签 / 零命中);
48
+ 6. `[候选]` / `[新标签?]`(只在零命中时);
49
+ 7. `[学习]`(重复错误 ≥2 次);
50
+ 8. `[归拢]`(多命中证据 ≥2);
51
+ 9. `[拆分]`(每 24 轮检查,段数 ≥8)。
52
+
53
+ **状态没变且上一条头还在 → 不重复注入**(同一签名复用)。
54
+
55
+ ## 5. 什么自动、什么要你判断
56
+
57
+ 完整清单在 `AUTOMATION.md`。摘要:
58
+
59
+ **自动**:头三行、规则/技能注入、归位(唯一命中写 run、多命中写 memberships、候选唯一自动贴)、学习扫描、归拢证据与强凭据自动建组、拆分提醒、estTokens 落盘。
60
+
61
+ **你判断**:`[新标签?]` 建不建、`[学习]` skip/kept、`[拆分]` 拆不拆、`load` 取多深、弱凭据归拢(≥2 但 <3)。
62
+
63
+ **只有人**:训练模式开关、规则长期内容、重启/发布、删除合并标签。
64
+
65
+ ## 6. 工具语义
66
+
67
+ - `save`:收起当前段,可带 `label`(绑定已有线头)与 `next`(下一轮第一步);
68
+ - `load`:取回某段;默认 6 条有内容的记录,`full:true` 取整段;**切换线头前自动 save 当前线**;
69
+ - `tag` / `tag-turn`:落标;可多标签;
70
+ - `find`:按“属于”判定(主标签或显式 memberships 都算);
71
+ - `scan`:只看可见区间。
72
+
73
+ ## 7. 重启后自检(四条)
74
+
75
+ 1. 头在、`next` 是最新的;
76
+ 2. 活跃线头恰好一条;
77
+ 3. 标签数合理(薄);
78
+ 4. `estTokens` 有口径:跑 `node ~/.dsh/continuity/esttokens-check.mjs`,应为 `ok:true`。
79
+
80
+ 附加:内容覆盖率跑 `content-coverage.mjs`,**>2% 才补课**(头里那个数含结构事件,天然偏高,只看趋势)。
81
+
82
+ ## 8. 坏了怎么查
83
+
84
+ | 症状 | 先查 |
85
+ | --- | --- |
86
+ | 头没出现 | 插件是否加载、`rules.md` 是否可读、进程是否在跑 |
87
+ | 技能正文不注入 | 技能文件是否带 YAML frontmatter(会被剥掉)、BOM 是否存在 |
88
+ | 改代码没反应 | **版本自报**:进程启动时间是否晚于 `package.json` |
89
+ | 文案里出现 `#` 前多一个怪字符 | 文件被写入了 BOM,需以 UTF-8 无 BOM 重写 |
90
+ | 未覆盖数突然涨 | 跑 `uncovered-scan.mjs` 找洞;`backfill-holes.mjs --apply` 可自动插值 |
91
+ | 归拢不建组 | 看 `group-evidence.jsonl` 是否满 3 条;父名是否提炼得出 |
92
+
93
+ ## 9. 版本历史(0.4.11 → 0.4.17)
94
+
95
+ | 版本 | 变更 |
96
+ | --- | --- |
97
+ | 0.4.11 | 多标签 memberships;归拢 groups |
98
+ | 0.4.12 | 拆分提醒(段数);归拢父名回退「未命名组」 |
99
+ | 0.4.13 | `context-log.jsonl`,estTokens 可复核 |
100
+ | 0.4.14 | 多命中自动写多标签;只有零命中才提问 |
101
+ | 0.4.15 | 候选唯一自动贴;强凭据自动建组;补课自动插值 |
102
+ | 0.4.16 | 修复自动贴后多出一行矛盾提示 |
103
+ | 0.4.17 | 归拢证据独立(`group-evidence.jsonl`);父名从证据提炼,排除碎片 |
104
+ | 0.4.18 | `[存?]` 提醒:同线 40 轮未收时提示一次(不自动 save) |
105
+ | 0.4.19 | `[分叉]` 闸:连续 3 轮工具回执无新内容 → 提示换路或检索 |
106
+ | 0.4.20 | `[回查]`:头部被折叠后首次必查、之后每 40 轮;三选项 + `recheck.mjs` 记录 |
107
+ | 0.4.21 | `[回查]` 加一次性测试钩子 `recheck-force.json` |
108
+ | 0.4.22 | 修复字段未在构造函数初始化(`[回查]`/`[分叉]`/`[存?]` 曾静默失效);新增字段初始化回归测试 |
109
+ | 0.4.23 | `[回查]` 改按区间取头部三句(跳过 runtime 样板);`next` 空时标「未写」 |
110
+ | 0.4.24 | `[回正]`:每 50 轮从规则条款重推当前决定;`rebuild.mjs` 记录与条款校验 |
111
+ | 0.4.25 | **mood v0**:扫 reasoning 情绪句 → `mood.jsonl`;注入 ≤2 条标「非判据」;LRU + 冷却 + 过期排除 |
112
+ | 0.4.26 | mood 词表收紧:跳过讨论词表/技术译名的句子 |
113
+ | 0.4.27 | mood 节流:注入间隔 ≥3 小时;`mood-force.json` 可指定 seq |
114
+ | 0.4.28 | mood 质量闸(≥2 标记,或 1 标记+第一人称)+ 每条记录带出处 |
115
+ | 0.4.29 | **mood episode**:`mood-episode.mjs` 存「真实基线(引用 seq)+ 重写句」;`quiz`/`score` 做盲测与随机基线对照 |
116
+ | 0.4.30 | mood 改事件驱动:有新 episode 才念,每条一次 |
117
+ | 0.4.31 | 定位修正:`[心境]` 注明「痕迹,非判据」;`mood-ab.jsonl` 记注入时刻;`rules.md` 新增《痕迹不是状态》 |
118
+ | 0.4.32 | mood 注入改 FIFO:每轮最多念一条,游标只前进一格 |
119
+ | 0.4.33 | 两条线分开:`[心境]` → `[痕迹]`;新增 `behavior.mjs`(行为表 + 按轮聚合置换检验) |
120
+ | 0.4.34 | 修复:mood 扫描游标只在内存,重启重扫导致重复;改为按 seq 落盘;新增 `logs-stats.mjs` |
121
+ | 0.4.35 | **注入审计**:`inject-log.jsonl` 每轮记「注了什么/为什么/是否复用头」;`inject-audit.mjs` 检查每次折叠后规则或技能是否补回 |
122
+ | 0.4.36 | **接手三问**:save/load 后写 `handoff-pending.json`,下一轮注入「这里发生过什么 / 为什么 / 哪里可能错」,用完即删 |
123
+ | 0.4.37 | **T0 闸**:不变量从「T0<5%」改为「未决=0」;`t0decide.mjs` 决策台账;`save` 拒绝带未决折叠(`t0-override.json` 放行);只统计可见文本 |
124
+
125
+ ## 10. 一句话
126
+
127
+ **默认自动,判不出来才问;问要有凭据,凭据要能复核。**
@@ -0,0 +1,243 @@
1
+ # 机制契约(MECHANISM CONTRACT)
2
+
3
+ > **Historical (0.4.67).** 当前实现与路径请读 `docs/ARCHITECTURE.md`;本契约保留为设计史。
4
+
5
+ > 这份文件不是实现说明,是**可自检的契约**。
6
+ > 任何移植版(包括 Coo 的重写版)只要满足这里的条款,就算“同一个机制”;
7
+ > 不满足的条款,必须显式标注为“未实现”,不许假装满足。
8
+ >
9
+ > 版本对应 dsh-continuity 0.4.67。写于 2026-10-29。
10
+
11
+ ---
12
+
13
+ ## 0. 边界:这套机制是什么、不是什么
14
+
15
+ **是**:
16
+ - 一套“把对话按话题归属 + 把长历史收成可检索段”的机制;
17
+ - 一组**不变量**,用闸(gate)在 save 时强制;
18
+ - 一组**只读查询**(轮→seq→标签),让实例能自己取回历史。
19
+
20
+ **不是**:
21
+ - 不是上下文压缩算法本身(它调用宿主的 compaction);
22
+ - 不是模型行为约束(它只能拒绝 save,不能阻止模型说话);
23
+ - 不是多租户系统(数据按用户全局,不按会话隔离——见 §7 风险)。
24
+
25
+ ---
26
+
27
+ ## 1. 数据契约
28
+
29
+ ### 1.1 `tags.json` —— 标签表(唯一的标签来源)
30
+
31
+ ```jsonc
32
+ {
33
+ "version": 1,
34
+ "tags": {
35
+ "t-v2-t4": { "label": "Continuity 实现", "alias": "T4", "keywords": ["Continuity", "标签闸"] }
36
+ },
37
+ "runs": [[37567, 38018, "t-v2-t4"]], // [fromSeq, toSeq, tagId],追加式,后者覆盖前者
38
+ "overrides": { "12345": "t-v2-t4" }, // seq → tagId,优先级最高
39
+ "memberships": { "12345": ["t-v2-t4"] } // seq → [tagId],附加式多标签
40
+ }
41
+ ```
42
+
43
+ **条款**
44
+ - T1.1.1 `T0` **不是**条目,是“未分类”桶;任何代码不得把 `T0` 写进 `tags`、`runs`、`memberships`。
45
+ - T1.1.2 `keywords` 是**唯一主源**;其他文件(如 threads)里的关键词只能作回退,冲突时以本文件为准。
46
+ - T1.1.3 本文件由插件写入时会**按固定字段归一化**:未知字段会被丢弃。任何扩展字段必须放独立文件。
47
+ - T1.1.4 解析某 seq 的标签顺序:`overrides` → 覆盖它的最后一条 `runs` → 附加 `memberships` → 兜底 `T0`。
48
+
49
+ ### 1.2 `threads.json` —— 线头与段(存档索引)
50
+
51
+ ```jsonc
52
+ {
53
+ "sessions": {
54
+ "<sessionId>": {
55
+ "activeId": "t-v2-t4",
56
+ "threads": [{
57
+ "id": "t-v2-t4", "alias": "T4", "label": "Continuity 实现",
58
+ "segments": [{
59
+ "fromSeq": 37567, "toSeq": 38018,
60
+ "seqs": [37567, 37568], // 该段包含的消息 seq(可少于区间,不等于“漏”)
61
+ "count": 130, "tokens": 42788,
62
+ "why": "为什么收起", "next": "下一步",
63
+ "at": "ISO 时间", "spanStart": null, "spanEnd": null, "minutes": 0,
64
+ "archiveRef": null,
65
+ "gistSeq": 38019, // 仅折叠过的段有
66
+ "compactionId": "..." // 仅折叠过的段有
67
+ }]
68
+ }]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ **条款**
75
+ - T1.2.1 `activeId` **恰好一条**;切换线必须改它。
76
+ - T1.2.2 段的 `seqs` 是“指针”,**不是原文**;原文永远只在 session 日志里。
77
+ - T1.2.3 段可以重叠、可以乱序插入,但**并集必须覆盖所有消息 seq**(覆盖率 100%,见 §4 不变量 I5)。
78
+ - T1.2.4 已折叠段(有 `gistSeq`/`compactionId`)**不得切开**:会把“摘要 ↔ 段”的对应切断。
79
+ - T1.2.5 段的 `why`/`next` 是给人读的,不是判据;可为空字符串。
80
+
81
+ ### 1.3 `turn-tags.jsonl` —— 轮标签(轮是检索单位)
82
+
83
+ ```jsonc
84
+ {"turn": 1063, "tag": "t-v2-t4", "by": "self|auto|seed|review", "why": "可选", "at": "ISO 时间"}
85
+ ```
86
+
87
+ **条款**
88
+ - T1.3.1 追加式,**后写覆盖前写**;一行一个轮。
89
+ - T1.3.2 `by` 区分来源:`self`(实例判)/ `auto`(引擎推)/ `seed`(历史播种)/ `review`(复核)。
90
+ - T1.3.3 每条有消息的轮都必须有一行;没有消息的轮号不算缺。
91
+ - T1.3.4 轮号来自 `assistant/message` 或 `turn/start` 的 `data.turn`。**`user/message` 没有 `data.turn`**,不得用它推轮号。
92
+
93
+ ### 1.4 辅助文件(可选,但移植时必须知道它们存在)
94
+
95
+ | 文件 | 作用 | 缺了会怎样 |
96
+ |---|---|---|
97
+ | `topic-anchors.json` | 每个标签的“必需词” | 话题提示会按关键词乱切 |
98
+ | `generic-words.json` | 泛词表(区分度低) | 泛词会被当成证据 |
99
+ | `t0-decisions.jsonl` | 显式判 T0 的记录 | T0 闸无法复核 |
100
+ | `groups.json` | 归拢用的组(**不参与搜索**) | 面板少一栏 |
101
+ | `backup-state.json` | 冷备状态 | 备份重复或不做 |
102
+
103
+ ---
104
+
105
+ ## 2. 不变量(INVARIANTS)
106
+
107
+ 移植版必须逐条自检,并在面板/日志里能看到“当前是否满足”。
108
+
109
+ | # | 不变量 | 判据(可执行) |
110
+ |---|---|---|
111
+ | **I1** | 每条有内容的消息都有归属 | 对全量消息跑 `tagsAtSeq`;结果不得为 `null`(兜底 `T0` 也算有归属,但 `T0` 比例要可解释) |
112
+ | **I2** | 每条有消息的轮都有轮标签 | `turn-tags` 的轮集合 ⊇ 有消息的轮集合 |
113
+ | **I3** | 每条内容都被“判过” | 未决消息数 = 0(未决 = 有内容、无标签、无显式 T0 决定) |
114
+ | **I4** | 折叠后原文可取回 | 任一已折叠段,都能用 `load fromSeq..toSeq` 取回原消息 |
115
+ | **I5** | 段覆盖率为 100% | 所有消息 seq ∈ 某个段的区间;`uncovered = 0` |
116
+ | **I6** | 关键词只有一个主源 | 改 `tags.json` 的关键词,匹配行为必须跟着变 |
117
+ | **I7** | 活跃线恰好一条 | `threads.json.activeId` 存在且指向存在的 thread |
118
+
119
+ **自检脚本建议**(移植版自己实现,或照抄本仓库同名脚本):
120
+ ```
121
+ I1/I5 → coverage.mjs(消息全量 vs 段区间)
122
+ I2 → turnmap.mjs todo
123
+ I3 → t0-gate.mjs
124
+ I4 → 任取一个 gistSeq 段,load 一次比对条数
125
+ I6 → 改一个词的匹配行为,再改回去
126
+ I7 → 读 threads.json 的 activeId
127
+ ```
128
+
129
+ ---
130
+
131
+ ## 3. 闸(GATES)——save 时才拒绝
132
+
133
+ **闸的作用是“把该补的账列出来”,不是惩罚。**
134
+
135
+ | 闸 | 触发条件 | 拒绝码 | 补法 |
136
+ |---|---|---|---|
137
+ | T0 闸 | 折叠区间内有“未决消息” | `t0-undecided` | `t0decide.mjs decide --seq N --as t0`,或贴标签 |
138
+ | 轮标签闸 | 折叠区间内有轮没有标签 | `turn-untagged` | `turnmap.mjs set --turn N --tag Tx` |
139
+ | 缺口闸 | 已结束的轮里缺 >2 个标签 | `tags-gap` | `tag-gap.mjs` 列清单 → 补判 |
140
+ | 死胡同闸 | 折叠区间尾部 12 轮出现:同处改多版 / 改完仍同错 / 改动无判据 | `deadend-symptoms` | 先写判据;真要带债保存,放 `t0-override.json` |
141
+
142
+ **条款**
143
+ - T3.1 闸只能**拒绝**,不能修改数据。
144
+ - T3.2 每个拒绝码必须带**可执行的补法命令**;否则这条闸就是“拒绝但没法修”。
145
+ - T3.3 `t0-override.json` 是一次性放行(读完即删),用于“我知道有债,先存”。
146
+ - T3.4 闸自身的异常**不得阻断 save**(宁可漏闸,不可卡死)。
147
+
148
+ ---
149
+
150
+ ## 4. 工具契约
151
+
152
+ 所有工具是**本地脚本**,不联网。移植版必须提供等价命令(名字可不同,语义必须同)。
153
+
154
+ | 命令 | 输入 | 输出 | 失败码 |
155
+ |---|---|---|---|
156
+ | `turnmap set` | `--turn N --tag Tx [--by self]` | 一行确认 | 参数缺失 → 退出 2 |
157
+ | `turnmap todo` | 无 | `{totalTurns, todo, sample}` | 读不到会话 → 退出 1 |
158
+ | `turnmap ranges` | `--tag Tx` | `{tag, turns, ranges[]}` | 未知标签 → 空列表 |
159
+ | `turnmap show` | `--turn N` | `{turn, seq, tag, by}` | — |
160
+ | `t0decide decide` | `--seq N --as t0\|tag:Tx --reason R` | 追加一行决定 | — |
161
+ | `t0decide status` | `--from S` | 未决清单 | — |
162
+ | `tag-gap` | 无 | 缺口清单 | — |
163
+ | `newtopics` | `--from S --review` | 每轮首句 + `best` | — |
164
+ | `match-tags` | `"<文本>"` | 候选(含 score/hit) | 空 = 无候选 |
165
+ | `kw-audit explain` | `"<文本>"` | 命中词 + 是否泛词 + verdict | 无泛词表 → 视为全部不泛 |
166
+ | `doctor` | `<session 文件>` | violations + verdict | 解析失败 → 退出 1 |
167
+
168
+ **条款**
169
+ - T4.1 所有工具**只读或追加**,除 `turnmap set` / `t0decide` / `doctor --fix` 外不得改文件。
170
+ - T4.2 `doctor --fix` 必须写到新文件,**绝不覆盖原文件**。
171
+ - T4.3 输出必须是 JSON 或可 grep 的行;不要“看起来像成功”的散文。
172
+
173
+ ---
174
+
175
+ ## 5. 注入契约(体现为“头”)
176
+
177
+ 每轮开始,宿主侧应提供(顺序不重要,内容重要):
178
+
179
+ ```
180
+ [线] 当前:<线头> · 下一步:<next>
181
+ [标签] 当前 T<x> · 另有 N 条 · 未覆盖 M(含结构事件)
182
+ [习惯] 标题行(不是全文)
183
+ [归位·上句] 判「…」· 命中 T3 → 已自动归入
184
+ [打标?] 缺 N 轮(…) ← 只在缺口存在时
185
+ [话题?] 上句更像 T8;凭 … ← 只在候选≠当前线时
186
+ [死胡同?] … ← 只在 save 闸预检命中时
187
+ ```
188
+
189
+ **条款**
190
+ - T5.1 注入**只追加到对话尾部**,不得写进 system/tool 前缀。
191
+ - T5.2 每条提示都必须**自解释**(含“我该跑什么命令”)。
192
+ - T5.3 注入失败**不得阻断回合**;但必须在日志里留下 `inject-*` 记录,可用于审计。
193
+
194
+ ---
195
+
196
+ ## 6. 已知的静默失败点(移植时最容易丢)
197
+
198
+ 按踩坑顺序,每一条都真实发生过:
199
+
200
+ | # | 塌陷方式 | 症状 | 防御 |
201
+ |---|---|---|---|
202
+ | 1 | 关键词双源(threads 覆盖 tags) | 改词无效,**不报错** | I6 + 启动时打印“关键词源” |
203
+ | 2 | `tags.json` 归一化吃自定义字段 | anchors 消失,**不报错** | 扩展字段放独立文件;启动时校验文件存在 |
204
+ | 3 | 重切段时漏掉“无标签 seq” | 覆盖率 99.68%,**不报错** | 每次重切后强制跑 I5 |
205
+ | 4 | 备份文件名重复前缀 | 清理不生效,**不报错** | 备份后打印实际文件名与保留份数 |
206
+ | 5 | 轮标签只有“上一轮”提醒 | 早期漏标永久沉底 | 提醒必须报**整段缺口**(见 [打标?] 格式) |
207
+ | 6 | 闸只给拒绝码不给补法 | 实例卡死 | T3.2 |
208
+ | 7 | `load` 被当成“会清空上下文” | 用它去断循环,无效 | 明确:load 只追加 |
209
+
210
+ **判断标准**:任何一个防御点,**如果它失效了,屏幕上必须有东西变红/变吵**。静默 = 没实现。
211
+
212
+ ---
213
+
214
+ ## 7. 多实例风险(如果 Coo 与我在同一台机器)
215
+
216
+ - `tags.json` / `threads.json` / `turn-tags.jsonl` 是**按用户全局**,不是按会话隔离。
217
+ - 两个实例同时写,会互相覆盖轮号与标签 → **数据打架**。
218
+ - 因此交接的第一阶段只能**只读**:读 tags.md / 面板 / threads 索引,不写。
219
+ - 要真正共写,需要先加“每会话命名空间”(当前未实现)。
220
+
221
+ ---
222
+
223
+ ## 8. 移植验收清单(照着打勾)
224
+
225
+ ```
226
+ [ ] 1. 三个数据文件存在且能解析(tags / threads / turn-tags)
227
+ [ ] 2. I1–I7 全部跑一遍,结果贴进日志
228
+ [ ] 3. 制造一次“漏标”,下一轮必须出现 [打标?] 且带完整缺口
229
+ [ ] 4. 制造一次“未决”,save 必须被拒,补完后必须放行
230
+ [ ] 5. 任取一个已折叠段,load 回来比对条数一致(I4)
231
+ [ ] 6. 改一个关键词,匹配行为必须变(I6),改回必须复原
232
+ [ ] 7. 备份跑一次:文件名正确、保留份数正确
233
+ [ ] 8. 故意写一个非法标签(T0 进 runs),自检必须报出来
234
+ ```
235
+
236
+ **八条全过 = 机制移植成功;任何一条不过,都还算“看起来像”。**
237
+
238
+ ---
239
+
240
+ ## 9. 一句话
241
+
242
+ > **机制不是代码,是“不变量 + 闸 + 可查的数据”。**
243
+ > 代码可以重写,不变量不能重写。