@furongjun1999/dsh-memory 0.4.11 → 0.5.1

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 (580) hide show
  1. package/README.md +552 -465
  2. package/codebuddy/CODEBUDDY.md +11 -3
  3. package/codebuddy/README.md +92 -90
  4. package/codebuddy/mcp.json +27 -27
  5. package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -169
  6. package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -144
  7. package/docs/README.md +143 -111
  8. package/docs/discipline/harnesses.yaml +244 -226
  9. package/docs/discipline/templates/full.md.tmpl +61 -61
  10. package/docs/discipline/templates/rules.mdc.tmpl +68 -0
  11. package/docs/discipline/templates/skill.md.tmpl +23 -23
  12. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md +299 -299
  13. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md +239 -239
  14. package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
  15. package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
  16. package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
  17. package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
  18. package/docs/eval//345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md +174 -174
  19. package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
  20. package/docs/eval//346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md +223 -223
  21. package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
  22. package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
  23. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
  24. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
  25. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
  26. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
  27. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
  28. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
  29. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
  30. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
  31. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
  32. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
  33. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
  34. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
  35. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
  36. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
  37. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
  38. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
  39. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
  40. package/docs/{mdcg → hive}//344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md +165 -160
  41. package/docs/hive//344/273/244/347/211/214/350/257/255/344/271/211/344/277/256/346/255/243_/344/273/262/350/243/201/344/275/215_v0.1.md +76 -0
  42. package/docs/{mdcg → hive}//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.5.md +236 -236
  43. package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
  44. package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -43
  45. package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +85 -0
  46. package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +102 -102
  47. package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +47 -0
  48. package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +519 -503
  49. package/docs/hive//350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +85 -85
  50. package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +191 -0
  51. package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +90 -0
  52. package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
  53. package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
  54. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -216
  55. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
  56. package/docs/mdcg/lingshu_tutorial.html +14449 -14449
  57. package/docs/mdcg/release_v0.4.11.md +49 -0
  58. package/docs/mdcg/release_v0.4.5.md +55 -55
  59. package/docs/mdcg/tool_table_v0.3.0.md +117 -117
  60. package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -600
  61. package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
  62. package/docs/mdcg//345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +275 -275
  63. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
  64. package/docs/mdcg//346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +130 -130
  65. package/docs/mdcg//347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md +278 -278
  66. package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +172 -172
  67. package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
  68. package/docs/mdcg//350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +491 -491
  69. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md +340 -340
  70. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +588 -588
  71. package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -176
  72. package/docs/plans//345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +81 -81
  73. package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
  74. package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -242
  75. package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
  76. package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -129
  77. package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -134
  78. package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  79. package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -145
  80. package/docs/theory//347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +144 -144
  81. package/docs/theory//347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +198 -198
  82. package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -221
  83. package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -237
  84. package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
  85. package/docs//345/217/221/345/270/203/344/273/266/345/233/236/346/272/257/350/257/264/346/230/216_v0.1.md +82 -82
  86. package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +28 -2
  87. package/dsh/README.md +82 -82
  88. package/dsh/cordis.yml.example +139 -139
  89. package/dsh/update-lingshu.bat +11 -11
  90. package/lib/bridge.d.ts +9 -0
  91. package/lib/bridge.js +35 -0
  92. package/lib/hooks.js +36 -2
  93. package/lib/index.js +7 -1
  94. package/lib/lib/roleplay_web.js +116 -29
  95. package/lib/lib/token_store.d.ts +7 -1
  96. package/lib/lib/token_store.js +12 -3
  97. package/md_cg/__init__.py +7 -7
  98. package/md_cg/audit.py +379 -368
  99. package/md_cg/autonomy.py +287 -287
  100. package/md_cg/backfill.py +1328 -1327
  101. package/md_cg/backfill_bigdomain.py +34 -34
  102. package/md_cg/backfill_bucket_zh.py +35 -0
  103. package/md_cg/bench6_arms.py +410 -410
  104. package/md_cg/bench6_common.py +230 -230
  105. package/md_cg/bench6_competitors.py +212 -212
  106. package/md_cg/bench_axis_domain.py +257 -257
  107. package/md_cg/bench_blind_comp.py +308 -308
  108. package/md_cg/bench_e2e_judge.py +532 -0
  109. package/md_cg/bench_e2e_locomo_qa.py +368 -0
  110. package/md_cg/bench_e2e_qa.py +256 -0
  111. package/md_cg/bench_en_atoms_public.py +230 -230
  112. package/md_cg/bench_governance.py +348 -348
  113. package/md_cg/bench_lme_zh.py +410 -410
  114. package/md_cg/bench_locomo.py +121 -121
  115. package/md_cg/bench_locomo_zh.py +450 -450
  116. package/md_cg/bench_locomo_zh_public.py +147 -147
  117. package/md_cg/bench_longmem.py +112 -112
  118. package/md_cg/bench_membench.py +632 -632
  119. package/md_cg/bench_p0.py +149 -149
  120. package/md_cg/bench_progressive.py +287 -287
  121. package/md_cg/bench_role_views.py +238 -238
  122. package/md_cg/bench_task_ab.py +243 -243
  123. package/md_cg/bench_task_ab_llm.py +408 -408
  124. package/md_cg/bench_unified_en.py +204 -204
  125. package/md_cg/bench_zh_mad.py +601 -601
  126. package/md_cg/blindspot_tickets.py +123 -123
  127. package/md_cg/branches.py +301 -285
  128. package/md_cg/build_postings.py +73 -73
  129. package/md_cg/ccgc.py +1006 -948
  130. package/md_cg/census.py +132 -132
  131. package/md_cg/chain.py +315 -300
  132. package/md_cg/codeindex.py +531 -531
  133. package/md_cg/coldverify.py +292 -292
  134. package/md_cg/comment_gate.py +337 -337
  135. package/md_cg/cond_compose.py +190 -190
  136. package/md_cg/cond_facts.py +154 -154
  137. package/md_cg/cond_template.json +106 -106
  138. package/md_cg/condition_anchor.py +142 -142
  139. package/md_cg/conformance.py +726 -726
  140. package/md_cg/consistency.py +717 -717
  141. package/md_cg/consolidate.py +1537 -1439
  142. package/md_cg/corpus.py +110 -110
  143. package/md_cg/crosscheck.py +1098 -1097
  144. package/md_cg/crypto.py +3 -1
  145. package/md_cg/d_meta.py +310 -310
  146. package/md_cg/datapath.py +78 -18
  147. package/md_cg/docindex.py +473 -473
  148. package/md_cg/eval_common.py +575 -575
  149. package/md_cg/evidence.py +4 -2
  150. package/md_cg/evolution.py +477 -477
  151. package/md_cg/export.py +222 -220
  152. package/md_cg/forgetting.py +581 -581
  153. package/md_cg/fsutil.py +377 -329
  154. package/md_cg/hotcache.py +48 -7
  155. package/md_cg/hyperedge.py +251 -251
  156. package/md_cg/identity.py +390 -390
  157. package/md_cg/insight.py +500 -500
  158. package/md_cg/interop.py +338 -0
  159. package/md_cg/judgment_manifest.py +177 -0
  160. package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
  161. package/md_cg/lexicon/build_standard_en.py +171 -171
  162. package/md_cg/lexicon/expand_en_zh.py +211 -211
  163. package/md_cg/lifecycle.py +272 -272
  164. package/md_cg/linkref.py +280 -280
  165. package/md_cg/links.py +140 -107
  166. package/md_cg/mcp_server.py +403 -55
  167. package/md_cg/md_whitebox.py +345 -345
  168. package/md_cg/mdcg.py +783 -233
  169. package/md_cg/mdcos.py +500 -70
  170. package/md_cg/metacognition.py +591 -591
  171. package/md_cg/migrate.py +119 -119
  172. package/md_cg/migrate_aeis.py +221 -221
  173. package/md_cg/migrate_roleplay.py +293 -293
  174. package/md_cg/migrate_wisdom_graph.py +360 -360
  175. package/md_cg/mreview/__init__.py +25 -25
  176. package/md_cg/mreview/__main__.py +110 -110
  177. package/md_cg/mreview/bundle.py +178 -178
  178. package/md_cg/mreview/candidates.py +262 -262
  179. package/md_cg/mreview/govern.py +694 -693
  180. package/md_cg/mreview/locate.py +939 -939
  181. package/md_cg/mreview/pipeline.py +728 -728
  182. package/md_cg/mreview/rules/duplication.json +21 -21
  183. package/md_cg/mreview/rules/field_coverage.json +54 -54
  184. package/md_cg/mreview/rules/source_license.json +21 -21
  185. package/md_cg/mreview/rules/template_flow.json +21 -21
  186. package/md_cg/mreview/ruleset.py +252 -252
  187. package/md_cg/nodefile.py +575 -575
  188. package/md_cg/pooling.py +484 -472
  189. package/md_cg/postings.py +300 -298
  190. package/md_cg/predict.py +1100 -1100
  191. package/md_cg/progressive.py +123 -123
  192. package/md_cg/protect.py +272 -272
  193. package/md_cg/protocol/md_cg_gate.proto +33 -33
  194. package/md_cg/protocol.py +372 -372
  195. package/md_cg/provenance.py +582 -582
  196. package/md_cg/reach.py +453 -453
  197. package/md_cg/readcache.py +143 -0
  198. package/md_cg/reconcile.py +228 -0
  199. package/md_cg/refindex.py +833 -833
  200. package/md_cg/refine.py +604 -604
  201. package/md_cg/review_cli.py +170 -0
  202. package/md_cg/roleviews.py +89 -89
  203. package/md_cg/routing.py +393 -365
  204. package/md_cg/run_tests.py +211 -0
  205. package/md_cg/scrub.py +13 -3
  206. package/md_cg/security.py +128 -18
  207. package/md_cg/self_state.py +1029 -1029
  208. package/md_cg/selfreport.py +152 -151
  209. package/md_cg/semantic/__init__.py +10 -10
  210. package/md_cg/semantic/canonical.py +122 -122
  211. package/md_cg/semantic/en_normalizer.py +364 -364
  212. package/md_cg/semantic/en_zh_map.json +28694 -0
  213. package/md_cg/semantic/export_en_zh_map.py +64 -0
  214. package/md_cg/semantic/unify.py +45 -0
  215. package/md_cg/semantic/zh_en_atoms.py +139 -139
  216. package/md_cg/signer.py +7 -4
  217. package/md_cg/sources.py +816 -582
  218. package/md_cg/statushdr.py +179 -179
  219. package/md_cg/stg.py +54 -37
  220. package/md_cg/subgraph.py +729 -729
  221. package/md_cg/sustain.py +35 -5
  222. package/md_cg/tasks.py +470 -470
  223. package/md_cg/test_access_hints.py +147 -0
  224. package/md_cg/test_action_derive.py +203 -203
  225. package/md_cg/test_audit_rotate.py +270 -270
  226. package/md_cg/test_autonomy.py +143 -143
  227. package/md_cg/test_bench_governance.py +102 -102
  228. package/md_cg/test_blindspot_tickets.py +166 -166
  229. package/md_cg/test_branch_discard_tombstone.py +136 -0
  230. package/md_cg/test_branches.py +13 -3
  231. package/md_cg/test_ccg_perturb.py +184 -184
  232. package/md_cg/test_ccgc.py +433 -433
  233. package/md_cg/test_census_prune.py +81 -81
  234. package/md_cg/test_chain_read_isolate.py +168 -0
  235. package/md_cg/test_cond_compose_anchors.py +76 -76
  236. package/md_cg/test_cond_match.py +165 -165
  237. package/md_cg/test_condition_anchor.py +81 -81
  238. package/md_cg/test_d_meta.py +412 -412
  239. package/md_cg/test_datapath_device_name.py +203 -0
  240. package/md_cg/test_datapath_root.py +199 -199
  241. package/md_cg/test_emit_negtail_cache.py +156 -0
  242. package/md_cg/test_en_pipeline.py +22 -2
  243. package/md_cg/test_gain_gate.py +212 -212
  244. package/md_cg/test_govern_directread.py +421 -0
  245. package/md_cg/test_health_scale.py +173 -173
  246. package/md_cg/test_hive_ingest.py +285 -0
  247. package/md_cg/test_hot_cold.py +215 -215
  248. package/md_cg/test_hyperedge.py +245 -245
  249. package/md_cg/test_i26_empty_first_write.py +116 -0
  250. package/md_cg/test_i27_e041_identity.py +128 -0
  251. package/md_cg/test_i28_hotcache_prodpath.py +122 -0
  252. package/md_cg/test_i32_hotcache_env_key.py +218 -0
  253. package/md_cg/test_identity_attribution.py +96 -15
  254. package/md_cg/test_index_durability.py +17 -3
  255. package/md_cg/test_interop.py +95 -0
  256. package/md_cg/test_interop_judgment.py +228 -0
  257. package/md_cg/test_issue39_utf8_stdio.py +273 -0
  258. package/md_cg/test_lifecycle.py +309 -309
  259. package/md_cg/test_linkref.py +306 -306
  260. package/md_cg/test_links_concurrent_write.py +188 -0
  261. package/md_cg/test_lock.py +43 -43
  262. package/md_cg/test_md_access_parity.py +255 -255
  263. package/md_cg/test_md_writepath.py +345 -345
  264. package/md_cg/test_mdstore_search_parity.py +160 -0
  265. package/md_cg/test_merge_upsert.py +168 -0
  266. package/md_cg/test_mr_m2.py +587 -587
  267. package/md_cg/test_mr_m3.py +710 -710
  268. package/md_cg/test_mr_m4.py +485 -485
  269. package/md_cg/test_n123_derive_expiry_chain.py +205 -0
  270. package/md_cg/test_n130_verify_falsified_protect.py +185 -0
  271. package/md_cg/test_n131_merge_gate.py +205 -0
  272. package/md_cg/test_p0.py +250 -250
  273. package/md_cg/test_p1.py +316 -316
  274. package/md_cg/test_p10_identity.py +173 -173
  275. package/md_cg/test_p11_consistency.py +233 -233
  276. package/md_cg/test_p12_metacognition.py +212 -212
  277. package/md_cg/test_p13_encryption.py +241 -241
  278. package/md_cg/test_p14_sustain.py +249 -249
  279. package/md_cg/test_p15_scrub.py +280 -280
  280. package/md_cg/test_p16_self_state.py +301 -301
  281. package/md_cg/test_p17_predict.py +354 -354
  282. package/md_cg/test_p18_whitebox.py +171 -171
  283. package/md_cg/test_p19_migrate_roleplay.py +149 -149
  284. package/md_cg/test_p1x_ref_root.py +160 -0
  285. package/md_cg/test_p20_evolution.py +315 -315
  286. package/md_cg/test_p21_tokens.py +293 -270
  287. package/md_cg/test_p22_theory.py +175 -175
  288. package/md_cg/test_p23_links.py +311 -311
  289. package/md_cg/test_p24_evidence.py +227 -227
  290. package/md_cg/test_p25_weights.py +156 -156
  291. package/md_cg/test_p26_refindex.py +416 -416
  292. package/md_cg/test_p27_docindex.py +16 -7
  293. package/md_cg/test_p28_refcheck.py +305 -305
  294. package/md_cg/test_p29_session_ingest_export.py +354 -333
  295. package/md_cg/test_p2_mcp.py +3 -0
  296. package/md_cg/test_p3.py +11 -2
  297. package/md_cg/test_p30_maintain.py +330 -330
  298. package/md_cg/test_p31_insight.py +534 -534
  299. package/md_cg/test_p32_backfill.py +7 -1
  300. package/md_cg/test_p33_ccg_wiring.py +293 -293
  301. package/md_cg/test_p34_crosscheck.py +331 -331
  302. package/md_cg/test_p35_conditioned_claim.py +252 -252
  303. package/md_cg/test_p36_kp_align.py +230 -230
  304. package/md_cg/test_p37_condition_space.py +248 -248
  305. package/md_cg/test_p38_concurrent_flush.py +102 -0
  306. package/md_cg/test_p38_contextualize.py +273 -273
  307. package/md_cg/test_p39_verify_flow.py +153 -0
  308. package/md_cg/test_p39_vision_evidence.py +369 -369
  309. package/md_cg/test_p40_refine_worklist.py +241 -241
  310. package/md_cg/test_p41_evolve_patrol.py +224 -224
  311. package/md_cg/test_p42_provenance.py +269 -269
  312. package/md_cg/test_p43_pooling.py +412 -398
  313. package/md_cg/test_p44_md_whitebox.py +231 -231
  314. package/md_cg/test_p45_session_identity.py +219 -219
  315. package/md_cg/test_p46_unit_scope.py +272 -272
  316. package/md_cg/test_p47_session_view.py +316 -0
  317. package/md_cg/test_p4_fuzzy.py +223 -223
  318. package/md_cg/test_p5_semantic.py +226 -226
  319. package/md_cg/test_p6_consolidate.py +440 -387
  320. package/md_cg/test_p7_goals_recent.py +202 -202
  321. package/md_cg/test_p8_subgraph_chain.py +200 -200
  322. package/md_cg/test_p9_forget_protect.py +231 -231
  323. package/md_cg/test_predict_beta.py +135 -135
  324. package/md_cg/test_preflight_failclosed.py +100 -100
  325. package/md_cg/test_progressive.py +146 -146
  326. package/md_cg/test_propose_tail_index.py +157 -0
  327. package/md_cg/test_protocol.py +243 -243
  328. package/md_cg/test_reach.py +378 -378
  329. package/md_cg/test_reach_keys.py +201 -201
  330. package/md_cg/test_read_clip.py +141 -141
  331. package/md_cg/test_read_scope_b27.py +277 -0
  332. package/md_cg/test_readcache_default_on.py +168 -0
  333. package/md_cg/test_readcache_precise_inval.py +270 -0
  334. package/md_cg/test_readcache_prodpath.py +203 -0
  335. package/md_cg/test_reconcile_v0.py +294 -0
  336. package/md_cg/test_retr_gates_prodpath.py +140 -0
  337. package/md_cg/test_retr_s1.py +344 -340
  338. package/md_cg/test_retr_s1b.py +276 -209
  339. package/md_cg/test_retr_s3.py +194 -194
  340. package/md_cg/test_retr_s4.py +163 -163
  341. package/md_cg/test_retr_s5.py +200 -200
  342. package/md_cg/test_retr_s6.py +157 -157
  343. package/md_cg/test_retr_s7.py +392 -384
  344. package/md_cg/test_retr_s8_time.py +369 -316
  345. package/md_cg/test_retr_s9_edges.py +286 -286
  346. package/md_cg/test_retr_s9_entity_ctx.py +9 -3
  347. package/md_cg/test_retr_score_once.py +208 -0
  348. package/md_cg/test_review_conformance.py +367 -367
  349. package/md_cg/test_review_onepass.py +170 -0
  350. package/md_cg/test_role_views.py +354 -354
  351. package/md_cg/test_rrf_graph_seed_cache.py +154 -0
  352. package/md_cg/test_security_audit.py +155 -0
  353. package/md_cg/test_security_audit_b26.py +161 -0
  354. package/md_cg/test_security_audit_v21.py +250 -0
  355. package/md_cg/test_sem_noise.py +242 -242
  356. package/md_cg/test_semantic_canonical.py +16 -2
  357. package/md_cg/test_session_isolation.py +168 -0
  358. package/md_cg/test_snapshot_autoclose.py +187 -0
  359. package/md_cg/test_subproc_encoding.py +192 -192
  360. package/md_cg/test_sustain_mutual.py +153 -153
  361. package/md_cg/test_tail_watermark_race.py +208 -0
  362. package/md_cg/test_tasks.py +409 -409
  363. package/md_cg/test_tenant_env_override_warn.py +139 -0
  364. package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
  365. package/md_cg/test_tool_face.py +189 -189
  366. package/md_cg/test_transfer.py +180 -180
  367. package/md_cg/test_trust.py +361 -361
  368. package/md_cg/test_twophase.py +286 -286
  369. package/md_cg/test_v14_fixes.py +38 -20
  370. package/md_cg/test_validity_filter.py +280 -280
  371. package/md_cg/test_verify_answer.py +138 -138
  372. package/md_cg/test_verify_dirty_reconcile.py +157 -0
  373. package/md_cg/test_wisdom_md_store.py +292 -292
  374. package/md_cg/test_writelimit.py +197 -197
  375. package/md_cg/test_writepipe.py +214 -214
  376. package/md_cg/theory.py +6 -3
  377. package/md_cg/tokens.py +85 -14
  378. package/md_cg/tool_face.py +260 -260
  379. package/md_cg/trust.py +986 -950
  380. package/md_cg/twophase.py +231 -231
  381. package/md_cg/units.py +668 -667
  382. package/md_cg/vision_evidence.py +667 -666
  383. package/md_cg/weights.py +624 -624
  384. package/md_cg/whitebox.py +527 -527
  385. package/md_cg/whitebox_kb/__init__.py +37 -37
  386. package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
  387. package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
  388. package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
  389. package/md_cg/whitebox_kb/engine.py +310 -310
  390. package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  391. package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
  392. package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
  393. package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
  394. package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
  395. package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
  396. package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
  397. package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
  398. package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
  399. package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
  400. package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
  401. package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
  402. package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
  403. package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
  404. package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
  405. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
  406. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
  407. package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
  408. package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
  409. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
  410. package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
  411. package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
  412. package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
  413. package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
  414. package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
  415. package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
  416. package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
  417. package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
  418. package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
  419. package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
  420. package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
  421. package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
  422. package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
  423. package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
  424. package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
  425. package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
  426. package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
  427. package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
  428. package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
  429. package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
  430. package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
  431. package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
  432. package/md_cg/writelimit.py +356 -356
  433. package/md_cg/writepipe.py +20 -8
  434. package/package.json +101 -96
  435. package/skills/plugin.json +54 -54
  436. package/skills/skills/designer-perspective/SKILL.md +158 -158
  437. package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
  438. package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
  439. package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
  440. package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
  441. package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
  442. package/skills/skills/designer-perspective/scripts/designer.py +545 -545
  443. package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
  444. package/skills/skills/designer-perspective/tests/selftest.py +61 -61
  445. package/skills/skills/lingshu-browser/SKILL.md +60 -60
  446. package/skills/skills/lingshu-compiler/SKILL.md +56 -56
  447. package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
  448. package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
  449. package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
  450. package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
  451. package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
  452. package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
  453. package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
  454. package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
  455. package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
  456. package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
  457. package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
  458. package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
  459. package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
  460. package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
  461. package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
  462. package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
  463. package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
  464. package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
  465. package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
  466. package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
  467. package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
  468. package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
  469. package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
  470. package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
  471. package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
  472. package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
  473. package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
  474. package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
  475. package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
  476. package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
  477. package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
  478. package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
  479. package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
  480. package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
  481. package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
  482. package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
  483. package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
  484. package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
  485. package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
  486. package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
  487. package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
  488. package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
  489. package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
  490. package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
  491. package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
  492. package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
  493. package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
  494. package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
  495. package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
  496. package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
  497. package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
  498. package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
  499. package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
  500. package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
  501. package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
  502. package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
  503. package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
  504. package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
  505. package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
  506. package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
  507. package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
  508. package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
  509. package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
  510. package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
  511. package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
  512. package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
  513. package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
  514. package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
  515. package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
  516. package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
  517. package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
  518. package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
  519. package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
  520. package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
  521. package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
  522. package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
  523. package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
  524. package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
  525. package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
  526. package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
  527. package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
  528. package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
  529. package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
  530. package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
  531. package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
  532. package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
  533. package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
  534. package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
  535. package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
  536. package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
  537. package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
  538. package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
  539. package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
  540. package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
  541. package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
  542. package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
  543. package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
  544. package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
  545. package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
  546. package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
  547. package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
  548. package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
  549. package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
  550. package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
  551. package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
  552. package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
  553. package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
  554. package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
  555. package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
  556. package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
  557. package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
  558. package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
  559. package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
  560. package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
  561. package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
  562. package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
  563. package/skills/skills/lingshu-graph/SKILL.md +63 -63
  564. package/skills/skills/lingshu-net/SKILL.md +48 -48
  565. package/skills/skills/lingshu-os/SKILL.md +64 -64
  566. package/skills/skills/lingshu-pylang/SKILL.md +71 -71
  567. package/src/bridge.ts +33 -0
  568. package/src/hooks.ts +38 -2
  569. package/src/index.ts +526 -518
  570. package/src/lib/datapath.ts +326 -326
  571. package/src/lib/mdcg_client.ts +413 -413
  572. package/src/lib/mutual.ts +428 -428
  573. package/src/lib/prompt_safety.ts +62 -62
  574. package/src/lib/python_path.ts +71 -71
  575. package/src/lib/roleplay_web.ts +116 -29
  576. package/src/lib/token_store.ts +13 -3
  577. package/src/tools.ts +212 -212
  578. package/zcode/AGENTS.md +11 -3
  579. package/zcode/README.md +41 -41
  580. /package/docs/{mdcg → hive}//344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md" +0 -0
@@ -1,576 +1,576 @@
1
- # -*- coding: utf-8 -*-
2
- """公开基准评测公共层:LongMemEval-S / LoCoMo → 统一格式 → 机判指标。
3
-
4
- 评测口径(诚实条款,报告必须原样声明):
5
- * hit@K 为「证据命中」机判:Top-K 至少含一条证据 turn(LongMemEval 用
6
- answer_session_ids 映射的证据集,LoCoMo 用 qrels 标注),非 LLM-judge,
7
- 与论文口径(LLM 判 answer 文本正确)不可直接比较,仅用于系统间同口径对比。
8
- * 负例拒答(refusal):Top-1 融合分 < 阈值线判为拒答。线 = 该数据集正例
9
- hit@1 题 Top-1 分的 10 分位(各数据集独立校准),报告附校准明细与敏感性。
10
- * LongMemEval-S 实测无 abstention 题(该类仅 M 版有),负例组以 LoCoMo
11
- adversarial + 自建 bench 噪声层承担。
12
-
13
- 统一题格式(两个数据集转换后一致;转换产物为 data/external/ 下的本地派生副本):
14
- {"qid", "qtype", "question", "answer", "evidence_turns": [turn_id, ...]}
15
- 统一语料行:{"id", "text", ...}(建库时日期/说话人内联进文本供词法路召回)
16
-
17
- 被测 API:MdCGOS(root).add / .search_rrf(四路 RRF:lexical,bucket,entity,graph)。
18
- 评测库独立于主库:_md_cg_eval_longmem / _md_cg_eval_locomo(.gitignore 已盖)。
19
-
20
- 双口径(评测设计核心,报告须分栏声明):
21
- 口径 A legacy —— 原始 turn 直接入库(corpus.marks=False 同形的「旧库迁移
22
- 对照物」)。此形态下 entity 路无 tags 恒空、负路由无条件
23
- 结构恒不触发、judge 走 legacy 分支:测的是检索底座下界。
24
- 口径 B calibrated —— 确定性白箱标定器把 turn 加工为 CCG 五要素 md 条目
25
- (生效/不适用条件、子功能、执行、验证方式 + tags +
26
- condition_space),再入库。测的是记忆系统完整形态
27
- (写入标定管线 + 四路召回 + 负路由 + 四态)。
28
-
29
- 标定红线(诚实条款):
30
- * 确定性:标定器是纯规则函数,零 LLM、零第三方依赖,同输入必同输出,
31
- 标定器源码随报告公开,第三方可重放;
32
- * 盲于查询集:标定只读 turn 自身字段(说话人/时间/正文句式),不读任何
33
- question/answer 字段——否则即数据泄漏,评测作废。
34
- """
35
- import json
36
- import os
37
- import random
38
- import sys
39
- import time
40
-
41
- HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
42
- sys.path.insert(0, HERE)
43
-
44
- EXT = os.path.join(HERE, "data", "external")
45
- RESULTS = os.path.join(EXT, "eval_results")
46
- ROOT_LM = os.path.join(HERE, "_md_cg_eval_longmem")
47
- ROOT_LC = os.path.join(HERE, "_md_cg_eval_locomo")
48
-
49
- LM_Q = os.path.join(EXT, "longmemeval", "lme_s_questions.jsonl")
50
- LM_H = os.path.join(EXT, "longmemeval", "lme_s_haystack.jsonl")
51
- LC_Q = os.path.join(EXT, "locomo", "locomo_questions.jsonl")
52
- LC_CORPUS = os.path.join(EXT, "locomo", "locomo_corpus.jsonl")
53
-
54
- ROOT_LM_CAL = ROOT_LM + "_cal" # 口径 B 独立库(标定形态)
55
- ROOT_LC_CAL = ROOT_LC + "_cal"
56
-
57
- PATHS = ("lexical", "bucket", "entity", "graph") # 引擎默认四路 RRF
58
- PATHS_CAL = PATHS + ("semantic",) # 口径 B:显式启用条件结构路(负路由所在)
59
-
60
-
61
- # 生效条件:仅评测口径调用(主库不调用);必须双改 md_cg.mdcg 与 md_cg.mdcos 两份 from-import 的同名值(漏改其一即静默失效),置为 10**9;无返回值、不落盘;
62
- def unlock_global_cap():
63
- """评测口径:解除 GLOBAL_CAP 截断(bench_membench patch_lexical_full 同法)。
64
-
65
- 【2026-09-16 后语义更新】截断依据已由「插入序」改为「相关度」(`mdcg
66
- .cut_by_relevance` 与 `mdcos._lexical` 均先全量打分再排序截断,cap 值仍
67
- 500)——插入序 lottery 已消除。评测仍解除 cap 的理由只剩一条:避免
68
- 「按相关度截掉尾部」在长语料上压低召回天花板(相近似 turn 数千条时,
69
- 排 501 名之后仍可能是证据)——评测要测**排序质量**而非截断策略。
70
- mdcg 与 mdcos 各持一份 from-import 值,必须双改(test_p43_pooling 先例)。
71
- """
72
- import md_cg.mdcg as m
73
- import md_cg.mdcos as mo
74
- m.GLOBAL_CAP = 10 ** 9
75
- mo.GLOBAL_CAP = 10 ** 9
76
-
77
-
78
- # 生效条件:仅评测口径调用(短条目语料下 jaccard 会退化);把 md_cg.mdcg.SCORE_MODE 置为 "jaccard"(mdcos 读同一份、无需双改);无返回值、不落盘;
79
- def use_jaccard():
80
- """评测口径:词法打分切 jaccard(对称归一化,长度自惩罚)。
81
-
82
- 默认 legacy(|qb∩db|/|qb|)只归一化查询侧——长 turn 语料里证据被长文档
83
- 挤出 Top-5,LongMemEval-S 上 precise/temporal/reference 的 hit@1 全为 0%。
84
- 切 jaccard 后同口径实测:precise 0%→8.97% / temporal 0%→6.77% /
85
- interference 1.28%→20.51% / reference 0%→3.76%(与 Rust
86
- `mdcg-eval --score jaccard` 及既有 trec_longmem_jac.json 逐位一致)。
87
-
88
- 只改 mdcg 一处即可(不像 GLOBAL_CAP 被 mdcos from-import 成副本):
89
- `lexical_sim` 在自身模块内引用全局 SCORE_MODE,且 mdcos 的 MdCGOS 继承
90
- MdCG._score,读的是同一份。
91
-
92
- **仅作评测口径**:主库(短条目)不切——短文档上 jaccard 会退化
93
- (test_p17_predict G4:可预测锚点由因果起点 a 错配到语义邻居 x)。
94
- """
95
- import md_cg.mdcg as m
96
- m.SCORE_MODE = "jaccard"
97
-
98
-
99
- # 生效条件:对 path 调用 open(path, encoding="utf-8") 后逐行读取,仅对 strip 后非空的 line yield json.loads(line);
100
- def iter_jsonl(path):
101
- with open(path, encoding="utf-8") as f:
102
- for line in f:
103
- line = line.strip()
104
- if line:
105
- yield json.loads(line)
106
-
107
-
108
- # 生效条件:ds 等于 "lm" 时 path=LM_Q,否则 path=LC_Q;先由 iter_jsonl(path) 取全部 rows,再仅当 qtypes 为真值时保留 r["qtype"] 在 qtypes 中的行(qtypes 为 None/空串/空容器等假值则不过滤);
109
- def load_questions(ds, qtypes=None):
110
- """ds: 'lm' | 'lc'。qtypes 过滤题型(None=全部)。"""
111
- path = LM_Q if ds == "lm" else LC_Q
112
- rows = list(iter_jsonl(path))
113
- if qtypes:
114
- rows = [r for r in rows if r["qtype"] in qtypes]
115
- return rows
116
-
117
-
118
- # 生效条件:对 rows/n(seed 默认 7),若 not n 或 n >= len(rows) 则原样返回 rows,否则返回 random.Random(seed).sample(rows, n);
119
- def sample_questions(rows, n, seed=7):
120
- if not n or n >= len(rows):
121
- return rows
122
- return random.Random(seed).sample(rows, n)
123
-
124
-
125
- # ------------------------------------------------------------------ 建库
126
-
127
- # 生效条件:对 t,若 t.get("speaker") 为真值则返回 f"{t['speaker']}: {t['text']} [{t['date']}]",否则返回 f"{t['text']} [{t['date']}]";
128
- def lm_turn_text(t):
129
- """LongMemEval turn → 入库文本(日期内联,词法路可召回时间词)。"""
130
- return f"{t['speaker']}: {t['text']} [{t['date']}]" if t.get("speaker") \
131
- else f"{t['text']} [{t['date']}]"
132
-
133
-
134
- # 生效条件:对 r,若 r.get("title") 为真值则返回 f"{r['text']} [{r['title']}]",否则返回 r["text"];
135
- def lc_turn_text(r):
136
- """LoCoMo turn → 入库文本(title 含 Data time 时间戳,内联保序)。"""
137
- return f"{r['text']} [{r['title']}]" if r.get("title") else r["text"]
138
-
139
-
140
- # ---------------- 白箱标定器(口径 B;纯规则、确定性、盲于查询集) ----------------
141
-
142
- # 句首功能词表:`[A-Z][a-z]+` 实体近似会把句首大写词误抽为实体,予以排除。
143
- # 词表固定随报告公开——它决定实体抽取边界,属标定器配置的一部分。
144
- _STOP_HEADS = frozenset(
145
- "I We You They He She It This That There Then So But And Or What When Where "
146
- "Why How Did Do Does Have Has Had Will Would Can Could Should My Your Our His "
147
- "Her Its Their In On At To For With From About After Before During Is Am Are "
148
- "Was Were The A An That's Let's Yeah Okay Yes No Well Now Just Really Maybe "
149
- "Let Get Got Know Think Want Need Make Take Say Said Tell Ask See Look Come "
150
- "Go Going If Because When While How's What's Where's Who's".split())
151
-
152
- # 强否定标记:原文含它才生成不适用条件(弱否定不生成,宁缺勿造——
153
- # forgetting.payload 已证明模板骨架会虚高重复度且无判别力)。
154
- _NEG_MARKS = ("n't", " not ", " never ", "nobody", "nothing", "no one", "none of")
155
-
156
-
157
- # 生效条件:对 text(limit 默认 4),按正则收集非 _STOP_HEADS 开头的大写词与 4 位年/时刻/日期数字并去重后,返回前 limit 项;
158
- def _extract_entities(text, limit=4):
159
- """确定性实体近似:连续大写词(人名/地名)+ 日期/时间数字。只读 turn 正文。
160
- 纯数字编号("1.")不是实体——只保留年份/时刻/日期形态,否则列表编号会
161
- 批量污染 tags 与生效条件行(实测教训)。"""
162
- import re
163
- ents = []
164
- for m in re.findall(r"[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?", text):
165
- if m.split()[0] not in _STOP_HEADS and m not in ents:
166
- ents.append(m)
167
- for m in re.findall(r"\d{4}|\d{1,2}:\d{2}|\d{1,2}/\d{1,2}(?:/\d{2,4})?", text):
168
- if m not in ents:
169
- ents.append(m)
170
- return ents[:limit]
171
-
172
-
173
- # 生效条件:对 title,若 title 为假值则返回空串,否则按 YYYY-MM-DD、英文星期日期、d/d/d 三个正则顺序取首个匹配串,无匹配返回空串;
174
- def _date_tag(title):
175
- """从 title 抽规范化日期串做 tag。整段 title 入 tags 会污染实体路
176
- (_path_entity 的 `query in t` 方向:长 tag 是误报源)。"""
177
- if not title:
178
- return ""
179
- import re
180
- m = (re.search(r"\d{4}-\d{2}-\d{2}", title)
181
- or re.search(r"[A-Za-z]+day\s+\d{1,2}\s+[A-Za-z]+,?\s*\d{4}", title)
182
- or re.search(r"\d{1,2}/\d{1,2}/\d{2,4}", title))
183
- return m.group(0) if m else ""
184
-
185
-
186
- # 生效条件:r 为语料 turn 行(缺 text/role 等键时按空串处理);date_key/title_key 为 None 时对应日期取空串;返回 (CCG 五要素正文, tags, condition_space) 三元组,仅当原文出现强否定标记才生成不适用条件、否则显式写「(无)」;
187
- def calibrate_turn(r, date_key=None, title_key=None):
188
- """turn 行 → (CCG 五要素正文, tags, condition_space)。
189
-
190
- v2 归因修正:自建语料 100% 召回的机制条件是「查询-证据词面同源」
191
- (对照实验:节点特异词在查询中出现 → 100%;全同质问法 → 11.9%)。
192
- 因此——
193
- 1. 特异词(说话人/日期/实体)必须进五要素**每一行**(词法路对全文打
194
- Jaccard:条件行的特异词是净增益,同质模板词是净稀释——v1 抽样
195
- 3.3% < legacy 16% 的根源,与 corpus.body 把 dom/point/aspect 内嵌
196
- 每一行同构);
197
- 2. 模板骨架压到最短(corpus.body 固定尾巴仅 12 字,v1 有 30+ 字同质词);
198
- 3. 五要素行承担条件结构职责(semantic 路/judge 读),不承诺解决
199
- 查询-证据表述鸿沟——那是公开数据集词法召回的真实天花板。
200
- tags 用裸词(说话人/日期/实体):_path_entity 的匹配是 `t in query or
201
- query in t`,带 `ent:` 前缀的 tag 在自然语言查询下永不命中(实测教训,
202
- corpus 的 `domain:` 桶 tag 是给 bucket/条件路由用的,两者用途不同)。
203
- 不适用条件仅当原文含强否定标记时生成(对 adversarial「是否说过」类问题
204
- 有真实判别力);否则显式写「(无)」。
205
- """
206
- import re
207
- text = str(r.get("text") or "").strip()
208
- speaker = str(r.get("speaker") or "").strip()
209
- date = str(r.get(date_key) or "").strip() if date_key else ""
210
- title = str(r.get(title_key) or "").strip() if title_key else ""
211
- ents = [e for e in _extract_entities(text) if e != speaker]
212
- who = speaker or (next((e for e in ents if not e[0].isdigit()), None) or "匿名")
213
- dtag = date or _date_tag(title)
214
- low = f" {text.lower()} "
215
- neg = next((m for m in _NEG_MARKS if m in low), None)
216
- when = f"于{dtag}" if dtag else ""
217
- ents_head = "/".join(ents[:2])
218
-
219
- body = (
220
- f"# 功能名:{who}·会话记忆{when}\n"
221
- f"# 生效条件:查询涉及「{who}」{when}"
222
- + (f"所述「{ents_head}」" if ents_head else "") + "时生效\n"
223
- f"# 子功能:{who}的会话条目{when}\n"
224
- f"# 执行:{who}会话原文回溯\n"
225
- f"# 验证方式:data\n"
226
- f"# 不适用条件:"
227
- + (f"断言与原文相反(含{neg.strip()}语义)" if neg else "(无)") + "\n\n"
228
- + text
229
- )
230
- tags = [t for t in (who, dtag) if t] + ents[:3]
231
- cond_space = {
232
- "observation_position": f"{who} 的会话陈述" + (f"({dtag})" if dtag else ""),
233
- "observation_tool": "会话记录",
234
- "existence_constraint": "公开",
235
- }
236
- return body, tags, cond_space
237
-
238
-
239
- # 生效条件:任意 t 与可选 ctx(默认 None)传入即原样转调 calibrate_turn(t, date_key="date", ctx=ctx),返回其结果;
240
- def calibrate_lm_turn(t, ctx=None):
241
- return calibrate_turn(t, date_key="date", ctx=ctx)
242
-
243
-
244
- # 生效条件:任意 r 与可选 ctx(默认 None)传入即原样转调 calibrate_turn(r, title_key="title", ctx=ctx),返回其结果;
245
- def calibrate_lc_turn(r, ctx=None):
246
- return calibrate_turn(r, title_key="title", ctx=ctx)
247
-
248
-
249
- # ============ 写入时加工管线(v3)——「查询时的泛化提前到写入时完成」 ============
250
- # 外部对标结论:检索只是链路最后一跳;完整系统在写入时做实体规范化/意图抽象/
251
- # 指代消解,把语料加工成可沿链行走的形态。以下三步全部是**确定性规则近似**
252
- # (纯标准库、盲于查询集、随报告公开词表),不引入任何模型依赖。
253
-
254
- # 生效条件:对 corpus_path(min_df 默认 3),逐行取 text=str(r.get("text") or "")、spk=str(r.get("speaker") or "").strip(),以 {spk}(spk 真值)或空集合并入正文大写词首词(排除 _STOP_HEADS)更新 df 计数,最后返回计数 >= min_df 的词集合;
255
- def build_canon(corpus_path, min_df=3):
256
- """离线实体规范化:全语料扫一遍,聚合大写词/人名的文档频次(df)。
257
- df≥min_df 的是 canonical 实体(会话成员 + 高频专名)——单 turn 局部抽取
258
- 看不见「Alice 在 200 条 turn 里反复出现」这个全库事实,这正是写入时
259
- (offline)加工区别于查询时(online)匹配的价值所在。"""
260
- from collections import Counter
261
- df = Counter()
262
- import re as _re
263
- for r in iter_jsonl(corpus_path):
264
- text = str(r.get("text") or "")
265
- spk = str(r.get("speaker") or "").strip()
266
- ws = {spk} if spk else set()
267
- ws |= set(_re.findall(r"[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?", text))
268
- ws = {w.split()[0] for w in ws if w.split()[0] not in _STOP_HEADS}
269
- df.update(ws)
270
- return {w for w, c in df.items() if c >= min_df}
271
-
272
-
273
- # 意图槽位(离线意图抽象的确定性近似):英文动词短语原样入条件行——
274
- # 保持词面同源(查询 "What did Alice plan" 与条件行 "plan" 直接 bigram 命中),
275
- # 不翻译成中文(同义词表是中文,翻译即断链)。三类槽覆盖 LoCoMo 问题分布:
276
- # 计划/过去事件/偏好。
277
- _INTENT_HEADS = ("plan", "going to", "thinking of", "thinking about", "want to",
278
- "will be", "need to", "have to", "decide", "suggest",
279
- "remember", "talked about", "talk about", "mention",
280
- "last time", "tell you", "told you", "hear",
281
- "prefer", "favorite", "love", "hate", "enjoy")
282
-
283
-
284
- # 生效条件:对 text 小写并前后加空格得到 low,返回 _INTENT_HEADS 中满足 f" {h}" 出现在 low 的项;
285
- def _intent_of(text):
286
- low = f" {text.lower()} "
287
- return [h for h in _INTENT_HEADS if f" {h}" in low]
288
-
289
-
290
- # 生效条件:r 为语料 turn 行;ctx 为写入时全库视图(canon/intents/last_canon),ctx 为 None 时按空视图处理并退化为 v2 行为;返回 (CCG 五要素正文, tags, condition_space) 三元组;
291
- def calibrate_turn(r, date_key=None, title_key=None, ctx=None):
292
- """turn 行 → (CCG 五要素正文, tags, condition_space)。
293
-
294
- v3(写入时加工管线):在 v2(特异词进每一行)之上叠加三步离线加工,
295
- ctx 携带全库视图:
296
- - ctx["canon"]:canonical 实体表(build_canon 产物)。条目涉及的
297
- canonical 实体进生效条件行——查询以人名开头时,词法/实体路沿
298
- 规范化实体直接落到「该人名相关条目」集合(链路第一跳)。
299
- - ctx["intents"]:意图槽位短语进子功能行——意图抽象的词面同源版。
300
- - ctx["last_canon"]:前一条的 canonical 实体(写入时序状态)。本条
301
- 无任何实体而前条有 → 条件行附「承接 {last}」——保守指代消解:
302
- 只附加假设、不删原文(判别力交给 RRF 多路共识,虚报风险可控)。
303
- tags 用裸词(说话人/日期/实体):_path_entity 的匹配是 `t in query or
304
- query in t`,带 `ent:` 前缀的 tag 在自然语言查询下永不命中(实测教训)。
305
- 不适用条件仅当原文含强否定标记时生成;否则显式写「(无)」。
306
- """
307
- import re
308
- text = str(r.get("text") or "").strip()
309
- speaker = str(r.get("speaker") or "").strip()
310
- date = str(r.get(date_key) or "").strip() if date_key else ""
311
- title = str(r.get(title_key) or "").strip() if title_key else ""
312
- canon = (ctx or {}).get("canon") or frozenset()
313
- ctx_ents = _extract_entities(text)
314
- canon_hits = [e for e in dict.fromkeys(
315
- [speaker] if speaker else []) if e in canon]
316
- if canon:
317
- local = re.findall(r"[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?", text)
318
- canon_hits += [w for w in dict.fromkeys(w.split()[0] for w in local)
319
- if w in canon and w not in canon_hits]
320
- else:
321
- canon_hits += [e for e in ctx_ents if e != speaker]
322
- last = (ctx or {}).get("last_canon")
323
- if not canon_hits and last:
324
- canon_hits = list(last)
325
- who = speaker or (next((e for e in canon_hits if not e[0].isdigit()), None)
326
- or "匿名")
327
- dtag = date or _date_tag(title)
328
- low = f" {text.lower()} "
329
- neg = next((m for m in _NEG_MARKS if m in low), None)
330
- when = f"于{dtag}" if dtag else ""
331
- ent_head = "/".join(canon_hits[:3])
332
- intents = _intent_of(text)
333
-
334
- body = (
335
- f"# 功能名:{who}·会话记忆{when}\n"
336
- f"# 生效条件:查询涉及「{ent_head or who}」{when}时生效\n"
337
- f"# 子功能:{who}的会话条目"
338
- + (f"(意图:{'/'.join(intents[:3])})" if intents else "") + "\n"
339
- f"# 执行:{who}会话原文回溯\n"
340
- f"# 验证方式:data\n"
341
- f"# 不适用条件:"
342
- + (f"断言与原文相反(含{neg.strip()}语义)" if neg else "(无)") + "\n\n"
343
- + text
344
- )
345
- tags = [t for t in (who, dtag) if t] + canon_hits[:3]
346
- cond_space = {
347
- "observation_position": f"{who} 的会话陈述" + (f"({dtag})" if dtag else ""),
348
- "observation_tool": "会话记录",
349
- "existence_constraint": "公开",
350
- }
351
- return body, tags, cond_space
352
-
353
-
354
- # 生效条件:rebuild=True 且 os.path.isdir(root) 时先 rmtree(root);cg_cls 为假值(如 None)时回落 MdCGOS(root, autoflush=500);语料行数 n_rows ≤ cg.index["nodes"] 现有节点数(n_rows=0 的空语料也满足)时直接复用返回 cg;否则逐行写入——calib_of 为真走标定口径 B(先 build_canon,calib_of(r, {"canon": canon, "last_canon": last_canon}) 取 body/tags/condition_space),calib_of 为假(含 None)走 legacy 口径 A(用 text_of(r)),id 已在 cg.index["nodes"] 的行跳过,verbose 为真时每 20000 行打印进度,循环后 cg.flush() 再返回 cg;
355
- def build_eval_cg(cg_cls, root, corpus_path, text_of, src_tag, verbose=True,
356
- calib_of=None, rebuild=False):
357
- """幂等建库:节点数已达语料行数则直接复用。返回 cg(不 flush 句柄)。
358
-
359
- calib_of=None → 口径 A(legacy 裸文本);传入 calibrate_*_turn → 口径 B
360
- (五要素标定条目 + tags + condition_space)。两口径用不同 root,互不污染。
361
- rebuild=True 删库重建(标定器改版后必须重建——幂等按节点数判断,识别不了
362
- 同节点数的旧版标定条目)。
363
- v3:标定口径先全语料 build_canon(离线实体规范化),写入循环维护
364
- last_canon(时序状态,供保守指代承接)——两遍扫描,仍 O(N)。
365
- """
366
- from md_cg.mdcos import MdCGOS
367
- if rebuild and os.path.isdir(root):
368
- import shutil
369
- shutil.rmtree(root, ignore_errors=True)
370
- n_rows = sum(1 for _ in iter_jsonl(corpus_path))
371
- cg = cg_cls(root, autoflush=500) if cg_cls else MdCGOS(root, autoflush=500)
372
- have = len(cg.index["nodes"])
373
- if have >= n_rows:
374
- if verbose:
375
- print(f" 建库复用 {root}:{have} 节点(语料 {n_rows} 行)")
376
- return cg
377
- canon = build_canon(corpus_path) if calib_of else frozenset()
378
- if verbose and calib_of:
379
- print(f" 写入时加工:canonical 实体 {len(canon)} 个(df≥3)")
380
- if verbose:
381
- print(f" 建库 {root}:已有 {have},语料 {n_rows} 行,开始写入 "
382
- f"({'标定口径 B' if calib_of else 'legacy 口径 A'})…")
383
- t0 = time.time()
384
- last_canon = None
385
- for i, r in enumerate(iter_jsonl(corpus_path), 1):
386
- if r["id"] in cg.index["nodes"]:
387
- continue
388
- if calib_of:
389
- body, tags, cond_space = calib_of(r, {"canon": canon,
390
- "last_canon": last_canon})
391
- cg.add(r["id"], body, layer="contextual", tags=tags,
392
- condition_space=cond_space, eval_src=src_tag,
393
- verification_basis="data")
394
- hits = [t for t in tags[1:] if not t.replace(".", "")
395
- .replace(":", "").isdigit()]
396
- last_canon = hits or last_canon
397
- else:
398
- cg.add(r["id"], text_of(r), layer="contextual",
399
- eval_src=src_tag, verification_basis="data")
400
- if verbose and i % 20000 == 0:
401
- print(f" … {i}/{n_rows}({time.time() - t0:.0f}s)")
402
- cg.flush()
403
- if verbose:
404
- print(f" 建库完成:{len(cg.index['nodes'])} 节点,{time.time() - t0:.0f}s")
405
- return cg
406
-
407
-
408
- # 生效条件:对传入 cg,用闭包 cache(键为 entry["path"])包装其原 cg._read 并赋回 cg._read,返回该 cache;仅当 p = entry["path"] 不在 cache 时调用 orig(entry) 并缓存其(含假值)结果,p 已在 cache 中时直接返回缓存值;
409
- def install_read_cache(cg):
410
- """评测只读:节点文件读进内存,避免逐次检索重复磁盘 I/O。"""
411
- cache = {}
412
- orig = cg._read
413
-
414
- # 生效条件:entry["path"] 未在闭包 cache 中时调用 orig(entry) 存入并返回,已在 cache 中则直接返回缓存值(cache 与原 _read 由外层 install_read_cache 提供);
415
- def _cached(entry):
416
- p = entry["path"]
417
- if p not in cache:
418
- cache[p] = orig(entry)
419
- return cache[p]
420
-
421
- cg._read = _cached
422
- return cache
423
-
424
-
425
- # ------------------------------------------------------------------ 检索与指标
426
-
427
- # 生效条件:对 cg/query(k 默认 5、paths 默认 PATHS、judge 默认 False),若 fusion 为真值则加入 kw["fusion"],若 path_weights 为真值则加入 kw["path_weights"],若 context is not None(含空 dict/空串)则加入 kw["context"],再调用 cg.search_rrf(...);
428
- def run_query(cg, query, k=5, paths=PATHS, judge=False, fusion=None,
429
- path_weights=None, context=None):
430
- """单查询,返回 (结果四元组列表, meta)。
431
- fusion/path_weights 覆盖融合方式与路权重(按路拆解归因用)。
432
-
433
- context 是**条件约束(bucket)路可被开启的唯一入口**:mdcos._path_bucket 在
434
- `context is None` 时直接 `return []`(mdcos.py:435),而 search_rrf 的默认
435
- paths 里恰恰含 "bucket"——即「名义四路、实际三路」。传 context 之前,全部
436
- 既有四路 RRF 口径的评测都未曾开启过条件路由(先例:四轴消融 + Rust 空壳)。
437
- """
438
- kw = {}
439
- if fusion:
440
- kw["fusion"] = fusion
441
- if path_weights:
442
- kw["path_weights"] = path_weights
443
- if context is not None:
444
- kw["context"] = context
445
- return cg.search_rrf(query, k=k, paths=paths, judge=judge, record=False, **kw)
446
-
447
-
448
- # 生效条件:当 evidence 为真值且 res 中某 r 的 r[0]["id"] 属于 evidence 时返回首个 1-based 排名,否则(evidence 为假值或未命中)返回 0;
449
- def first_evidence_rank(res, evidence):
450
- """首个证据 turn 的排名(1-based;未命中 0)。"""
451
- if not evidence:
452
- return 0
453
- for i, r in enumerate(res, 1):
454
- if r[0]["id"] in evidence:
455
- return i
456
- return 0
457
-
458
-
459
- # 生效条件:对 questions 中每个 it,若 context_of 为真值则 ctx=context_of(it) 否则 ctx=None;以 k/paths/judge/fusion/path_weights 调用 run_query,按 evidence_turns 算 first_evidence_rank,收集 qid/qtype/rank/top1_score/n_res 行,返回 rows;
460
- def evaluate_group(cg, questions, k=5, paths=PATHS, judge=False, verbose=True,
461
- fusion=None, path_weights=None, context_of=None):
462
- """一组题 → per-question 明细行。负例组传 judge=False(拒答看分数线)。
463
-
464
- context_of: 可调用 `q -> context dict`(返回 None 表示该题不下发情境)。
465
- 缺省 None → 全组不传 context(**既有行为完全不变**)。传入即开启条件路由,
466
- 是「条件约束路的净效应」单变量实验的开关。
467
- """
468
- rows = []
469
- t0 = time.time()
470
- for i, it in enumerate(questions, 1):
471
- ctx = context_of(it) if context_of else None
472
- res, meta = run_query(cg, it["question"], k=k, paths=paths, judge=judge,
473
- fusion=fusion, path_weights=path_weights,
474
- context=ctx)
475
- rank = first_evidence_rank(res, set(it.get("evidence_turns") or []))
476
- rows.append({
477
- "qid": it["qid"], "qtype": it["qtype"],
478
- "rank": rank, "top1_score": res[0][1] if res else 0.0,
479
- "n_res": len(res),
480
- })
481
- if verbose and i % 100 == 0:
482
- print(f" 查询 {i}/{len(questions)}({time.time() - t0:.0f}s)")
483
- return rows
484
-
485
-
486
- # 生效条件:当 rows 为真值时按 k(默认 5)计算 hit@1、hit@k、MRR、score_p10/p50 及 by_qtype 指标,否则返回 {"n": 0};
487
- def summarize(rows, k=5):
488
- """hit@1 / hit@K / MRR + 分题型。score_p10 供拒答线校准。"""
489
- if not rows:
490
- return {"n": 0}
491
- hit1 = sum(1 for r in rows if r["rank"] == 1)
492
- hitk = sum(1 for r in rows if 0 < r["rank"] <= k)
493
- mrr = sum(1.0 / r["rank"] for r in rows if r["rank"]) / len(rows)
494
- top1 = sorted(r["top1_score"] for r in rows)
495
- out = {
496
- "n": len(rows),
497
- "hit@1": hit1 / len(rows),
498
- f"hit@{k}": hitk / len(rows),
499
- "mrr": mrr,
500
- "score_p10": top1[max(0, len(top1) // 10)],
501
- "score_p50": top1[len(top1) // 2],
502
- }
503
- by = {}
504
- for r in rows:
505
- by.setdefault(r["qtype"], []).append(r)
506
- out["by_qtype"] = {
507
- qt: {
508
- "n": len(g),
509
- "hit@1": sum(1 for r in g if r["rank"] == 1) / len(g),
510
- f"hit@{k}": sum(1 for r in g if 0 < r["rank"] <= k) / len(g),
511
- "mrr": sum(1.0 / r["rank"] for r in g if r["rank"]) / len(g),
512
- } for qt, g in sorted(by.items())
513
- }
514
- return out
515
-
516
-
517
- # 生效条件:当 rows 非空时按 r["top1_score"] < line 统计拒答数并返回拒绝率,否则返回 {"n": 0};
518
- def refusal_metrics(rows, line):
519
- """负例组:Top-1 分 < line 记为拒答。返回拒答率与明细统计。"""
520
- n = len(rows)
521
- if not n:
522
- return {"n": 0}
523
- ref = sum(1 for r in rows if r["top1_score"] < line)
524
- return {"n": n, "refusal_rate": ref / n,
525
- "refused": ref, "line": line}
526
-
527
-
528
- # 生效条件:当 rows 非空时返回 r["top1_score"] < line 的行占比,否则返回 0.0;
529
- def false_refusal_rate(rows, line):
530
- """正例误杀(被错误拒答的正例比例):正例 Top-1 分 < line。"""
531
- n = len(rows)
532
- if not n:
533
- return 0.0
534
- return sum(1 for r in rows if r["top1_score"] < line) / n
535
-
536
-
537
- # 生效条件:当 pos_rows 中存在 rank==1 的行时,返回这些行 top1_score 升序后 max(0, len//10) 索引处的值,否则返回 0.0;
538
- def calibrate_line(pos_rows):
539
- """拒答线 = 正例 hit@1 题 Top-1 分的 10 分位。"""
540
- scores = sorted(r["top1_score"] for r in pos_rows if r["rank"] == 1)
541
- if not scores:
542
- return 0.0
543
- return scores[max(0, len(scores) // 10)]
544
-
545
-
546
- # 生效条件:当 name/payload 给出时,确保 RESULTS 目录(os.makedirs(RESULTS, exist_ok=True)),将 payload 以 ensure_ascii=False、indent=2 写入 RESULTS/name 并返回 path;
547
- def save_result(name, payload):
548
- """评测结果 JSON 落盘(报告数据源)。"""
549
- os.makedirs(RESULTS, exist_ok=True)
550
- path = os.path.join(RESULTS, name)
551
- with open(path, "w", encoding="utf-8") as f:
552
- json.dump(payload, f, ensure_ascii=False, indent=2)
553
- print(f" 结果 → {path}")
554
- return path
555
-
556
-
557
- # 生效条件:对 groups(k 默认 5),若某组 s 的 s.get("n") 为假(缺 n 或 n=0)则打印 0 行并跳过,否则按 s["hit@1"]、s[f"hit@{k}"]、s["mrr"] 打印并遍历 s.get("by_qtype", {}) 输出子行;
558
- def print_table(title, groups, k=5):
559
- """groups: {组名: summarize 输出}。"""
560
- print(f"\n== {title}(证据命中口径,k={k})==")
561
- print(f"{'组':<28}{'n':>6}{'hit@1':>9}{f'hit@{k}':>9}{'MRR':>8}")
562
- print("-" * 62)
563
- for name, s in groups.items():
564
- if not s.get("n"):
565
- print(f"{name:<28}{0:>6} -")
566
- continue
567
- print(f"{name:<28}{s['n']:>6}{s['hit@1']:>9.1%}"
568
- f"{s[f'hit@{k}']:>9.1%}{s['mrr']:>8.3f}")
569
- for qt, st in s.get("by_qtype", {}).items():
570
- print(f" ├ {qt:<24}{st['n']:>6}{st['hit@1']:>9.1%}"
571
- f"{st[f'hit@{k}']:>9.1%}{st['mrr']:>8.3f}")
572
-
573
-
574
- # 生效条件:对任意 x,返回 f"{x * 100:.1f}%" 的百分比字符串;
575
- def pct(x):
1
+ # -*- coding: utf-8 -*-
2
+ """公开基准评测公共层:LongMemEval-S / LoCoMo → 统一格式 → 机判指标。
3
+
4
+ 评测口径(诚实条款,报告必须原样声明):
5
+ * hit@K 为「证据命中」机判:Top-K 至少含一条证据 turn(LongMemEval 用
6
+ answer_session_ids 映射的证据集,LoCoMo 用 qrels 标注),非 LLM-judge,
7
+ 与论文口径(LLM 判 answer 文本正确)不可直接比较,仅用于系统间同口径对比。
8
+ * 负例拒答(refusal):Top-1 融合分 < 阈值线判为拒答。线 = 该数据集正例
9
+ hit@1 题 Top-1 分的 10 分位(各数据集独立校准),报告附校准明细与敏感性。
10
+ * LongMemEval-S 实测无 abstention 题(该类仅 M 版有),负例组以 LoCoMo
11
+ adversarial + 自建 bench 噪声层承担。
12
+
13
+ 统一题格式(两个数据集转换后一致;转换产物为 data/external/ 下的本地派生副本):
14
+ {"qid", "qtype", "question", "answer", "evidence_turns": [turn_id, ...]}
15
+ 统一语料行:{"id", "text", ...}(建库时日期/说话人内联进文本供词法路召回)
16
+
17
+ 被测 API:MdCGOS(root).add / .search_rrf(四路 RRF:lexical,bucket,entity,graph)。
18
+ 评测库独立于主库:_md_cg_eval_longmem / _md_cg_eval_locomo(.gitignore 已盖)。
19
+
20
+ 双口径(评测设计核心,报告须分栏声明):
21
+ 口径 A legacy —— 原始 turn 直接入库(corpus.marks=False 同形的「旧库迁移
22
+ 对照物」)。此形态下 entity 路无 tags 恒空、负路由无条件
23
+ 结构恒不触发、judge 走 legacy 分支:测的是检索底座下界。
24
+ 口径 B calibrated —— 确定性白箱标定器把 turn 加工为 CCG 五要素 md 条目
25
+ (生效/不适用条件、子功能、执行、验证方式 + tags +
26
+ condition_space),再入库。测的是记忆系统完整形态
27
+ (写入标定管线 + 四路召回 + 负路由 + 四态)。
28
+
29
+ 标定红线(诚实条款):
30
+ * 确定性:标定器是纯规则函数,零 LLM、零第三方依赖,同输入必同输出,
31
+ 标定器源码随报告公开,第三方可重放;
32
+ * 盲于查询集:标定只读 turn 自身字段(说话人/时间/正文句式),不读任何
33
+ question/answer 字段——否则即数据泄漏,评测作废。
34
+ """
35
+ import json
36
+ import os
37
+ import random
38
+ import sys
39
+ import time
40
+
41
+ HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
42
+ sys.path.insert(0, HERE)
43
+
44
+ EXT = os.path.join(HERE, "data", "external")
45
+ RESULTS = os.path.join(EXT, "eval_results")
46
+ ROOT_LM = os.path.join(HERE, "_md_cg_eval_longmem")
47
+ ROOT_LC = os.path.join(HERE, "_md_cg_eval_locomo")
48
+
49
+ LM_Q = os.path.join(EXT, "longmemeval", "lme_s_questions.jsonl")
50
+ LM_H = os.path.join(EXT, "longmemeval", "lme_s_haystack.jsonl")
51
+ LC_Q = os.path.join(EXT, "locomo", "locomo_questions.jsonl")
52
+ LC_CORPUS = os.path.join(EXT, "locomo", "locomo_corpus.jsonl")
53
+
54
+ ROOT_LM_CAL = ROOT_LM + "_cal" # 口径 B 独立库(标定形态)
55
+ ROOT_LC_CAL = ROOT_LC + "_cal"
56
+
57
+ PATHS = ("lexical", "bucket", "entity", "graph") # 引擎默认四路 RRF
58
+ PATHS_CAL = PATHS + ("semantic",) # 口径 B:显式启用条件结构路(负路由所在)
59
+
60
+
61
+ # 生效条件:仅评测口径调用(主库不调用);必须双改 md_cg.mdcg 与 md_cg.mdcos 两份 from-import 的同名值(漏改其一即静默失效),置为 10**9;无返回值、不落盘;
62
+ def unlock_global_cap():
63
+ """评测口径:解除 GLOBAL_CAP 截断(bench_membench patch_lexical_full 同法)。
64
+
65
+ 【2026-09-16 后语义更新】截断依据已由「插入序」改为「相关度」(`mdcg
66
+ .cut_by_relevance` 与 `mdcos._lexical` 均先全量打分再排序截断,cap 值仍
67
+ 500)——插入序 lottery 已消除。评测仍解除 cap 的理由只剩一条:避免
68
+ 「按相关度截掉尾部」在长语料上压低召回天花板(相近似 turn 数千条时,
69
+ 排 501 名之后仍可能是证据)——评测要测**排序质量**而非截断策略。
70
+ mdcg 与 mdcos 各持一份 from-import 值,必须双改(test_p43_pooling 先例)。
71
+ """
72
+ import md_cg.mdcg as m
73
+ import md_cg.mdcos as mo
74
+ m.GLOBAL_CAP = 10 ** 9
75
+ mo.GLOBAL_CAP = 10 ** 9
76
+
77
+
78
+ # 生效条件:仅评测口径调用(短条目语料下 jaccard 会退化);把 md_cg.mdcg.SCORE_MODE 置为 "jaccard"(mdcos 读同一份、无需双改);无返回值、不落盘;
79
+ def use_jaccard():
80
+ """评测口径:词法打分切 jaccard(对称归一化,长度自惩罚)。
81
+
82
+ 默认 legacy(|qb∩db|/|qb|)只归一化查询侧——长 turn 语料里证据被长文档
83
+ 挤出 Top-5,LongMemEval-S 上 precise/temporal/reference 的 hit@1 全为 0%。
84
+ 切 jaccard 后同口径实测:precise 0%→8.97% / temporal 0%→6.77% /
85
+ interference 1.28%→20.51% / reference 0%→3.76%(与 Rust
86
+ `mdcg-eval --score jaccard` 及既有 trec_longmem_jac.json 逐位一致)。
87
+
88
+ 只改 mdcg 一处即可(不像 GLOBAL_CAP 被 mdcos from-import 成副本):
89
+ `lexical_sim` 在自身模块内引用全局 SCORE_MODE,且 mdcos 的 MdCGOS 继承
90
+ MdCG._score,读的是同一份。
91
+
92
+ **仅作评测口径**:主库(短条目)不切——短文档上 jaccard 会退化
93
+ (test_p17_predict G4:可预测锚点由因果起点 a 错配到语义邻居 x)。
94
+ """
95
+ import md_cg.mdcg as m
96
+ m.SCORE_MODE = "jaccard"
97
+
98
+
99
+ # 生效条件:对 path 调用 open(path, encoding="utf-8") 后逐行读取,仅对 strip 后非空的 line yield json.loads(line);
100
+ def iter_jsonl(path):
101
+ with open(path, encoding="utf-8") as f:
102
+ for line in f:
103
+ line = line.strip()
104
+ if line:
105
+ yield json.loads(line)
106
+
107
+
108
+ # 生效条件:ds 等于 "lm" 时 path=LM_Q,否则 path=LC_Q;先由 iter_jsonl(path) 取全部 rows,再仅当 qtypes 为真值时保留 r["qtype"] 在 qtypes 中的行(qtypes 为 None/空串/空容器等假值则不过滤);
109
+ def load_questions(ds, qtypes=None):
110
+ """ds: 'lm' | 'lc'。qtypes 过滤题型(None=全部)。"""
111
+ path = LM_Q if ds == "lm" else LC_Q
112
+ rows = list(iter_jsonl(path))
113
+ if qtypes:
114
+ rows = [r for r in rows if r["qtype"] in qtypes]
115
+ return rows
116
+
117
+
118
+ # 生效条件:对 rows/n(seed 默认 7),若 not n 或 n >= len(rows) 则原样返回 rows,否则返回 random.Random(seed).sample(rows, n);
119
+ def sample_questions(rows, n, seed=7):
120
+ if not n or n >= len(rows):
121
+ return rows
122
+ return random.Random(seed).sample(rows, n)
123
+
124
+
125
+ # ------------------------------------------------------------------ 建库
126
+
127
+ # 生效条件:对 t,若 t.get("speaker") 为真值则返回 f"{t['speaker']}: {t['text']} [{t['date']}]",否则返回 f"{t['text']} [{t['date']}]";
128
+ def lm_turn_text(t):
129
+ """LongMemEval turn → 入库文本(日期内联,词法路可召回时间词)。"""
130
+ return f"{t['speaker']}: {t['text']} [{t['date']}]" if t.get("speaker") \
131
+ else f"{t['text']} [{t['date']}]"
132
+
133
+
134
+ # 生效条件:对 r,若 r.get("title") 为真值则返回 f"{r['text']} [{r['title']}]",否则返回 r["text"];
135
+ def lc_turn_text(r):
136
+ """LoCoMo turn → 入库文本(title 含 Data time 时间戳,内联保序)。"""
137
+ return f"{r['text']} [{r['title']}]" if r.get("title") else r["text"]
138
+
139
+
140
+ # ---------------- 白箱标定器(口径 B;纯规则、确定性、盲于查询集) ----------------
141
+
142
+ # 句首功能词表:`[A-Z][a-z]+` 实体近似会把句首大写词误抽为实体,予以排除。
143
+ # 词表固定随报告公开——它决定实体抽取边界,属标定器配置的一部分。
144
+ _STOP_HEADS = frozenset(
145
+ "I We You They He She It This That There Then So But And Or What When Where "
146
+ "Why How Did Do Does Have Has Had Will Would Can Could Should My Your Our His "
147
+ "Her Its Their In On At To For With From About After Before During Is Am Are "
148
+ "Was Were The A An That's Let's Yeah Okay Yes No Well Now Just Really Maybe "
149
+ "Let Get Got Know Think Want Need Make Take Say Said Tell Ask See Look Come "
150
+ "Go Going If Because When While How's What's Where's Who's".split())
151
+
152
+ # 强否定标记:原文含它才生成不适用条件(弱否定不生成,宁缺勿造——
153
+ # forgetting.payload 已证明模板骨架会虚高重复度且无判别力)。
154
+ _NEG_MARKS = ("n't", " not ", " never ", "nobody", "nothing", "no one", "none of")
155
+
156
+
157
+ # 生效条件:对 text(limit 默认 4),按正则收集非 _STOP_HEADS 开头的大写词与 4 位年/时刻/日期数字并去重后,返回前 limit 项;
158
+ def _extract_entities(text, limit=4):
159
+ """确定性实体近似:连续大写词(人名/地名)+ 日期/时间数字。只读 turn 正文。
160
+ 纯数字编号("1.")不是实体——只保留年份/时刻/日期形态,否则列表编号会
161
+ 批量污染 tags 与生效条件行(实测教训)。"""
162
+ import re
163
+ ents = []
164
+ for m in re.findall(r"[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?", text):
165
+ if m.split()[0] not in _STOP_HEADS and m not in ents:
166
+ ents.append(m)
167
+ for m in re.findall(r"\d{4}|\d{1,2}:\d{2}|\d{1,2}/\d{1,2}(?:/\d{2,4})?", text):
168
+ if m not in ents:
169
+ ents.append(m)
170
+ return ents[:limit]
171
+
172
+
173
+ # 生效条件:对 title,若 title 为假值则返回空串,否则按 YYYY-MM-DD、英文星期日期、d/d/d 三个正则顺序取首个匹配串,无匹配返回空串;
174
+ def _date_tag(title):
175
+ """从 title 抽规范化日期串做 tag。整段 title 入 tags 会污染实体路
176
+ (_path_entity 的 `query in t` 方向:长 tag 是误报源)。"""
177
+ if not title:
178
+ return ""
179
+ import re
180
+ m = (re.search(r"\d{4}-\d{2}-\d{2}", title)
181
+ or re.search(r"[A-Za-z]+day\s+\d{1,2}\s+[A-Za-z]+,?\s*\d{4}", title)
182
+ or re.search(r"\d{1,2}/\d{1,2}/\d{2,4}", title))
183
+ return m.group(0) if m else ""
184
+
185
+
186
+ # 生效条件:r 为语料 turn 行(缺 text/role 等键时按空串处理);date_key/title_key 为 None 时对应日期取空串;返回 (CCG 五要素正文, tags, condition_space) 三元组,仅当原文出现强否定标记才生成不适用条件、否则显式写「(无)」;
187
+ def calibrate_turn(r, date_key=None, title_key=None):
188
+ """turn 行 → (CCG 五要素正文, tags, condition_space)。
189
+
190
+ v2 归因修正:自建语料 100% 召回的机制条件是「查询-证据词面同源」
191
+ (对照实验:节点特异词在查询中出现 → 100%;全同质问法 → 11.9%)。
192
+ 因此——
193
+ 1. 特异词(说话人/日期/实体)必须进五要素**每一行**(词法路对全文打
194
+ Jaccard:条件行的特异词是净增益,同质模板词是净稀释——v1 抽样
195
+ 3.3% < legacy 16% 的根源,与 corpus.body 把 dom/point/aspect 内嵌
196
+ 每一行同构);
197
+ 2. 模板骨架压到最短(corpus.body 固定尾巴仅 12 字,v1 有 30+ 字同质词);
198
+ 3. 五要素行承担条件结构职责(semantic 路/judge 读),不承诺解决
199
+ 查询-证据表述鸿沟——那是公开数据集词法召回的真实天花板。
200
+ tags 用裸词(说话人/日期/实体):_path_entity 的匹配是 `t in query or
201
+ query in t`,带 `ent:` 前缀的 tag 在自然语言查询下永不命中(实测教训,
202
+ corpus 的 `domain:` 桶 tag 是给 bucket/条件路由用的,两者用途不同)。
203
+ 不适用条件仅当原文含强否定标记时生成(对 adversarial「是否说过」类问题
204
+ 有真实判别力);否则显式写「(无)」。
205
+ """
206
+ import re
207
+ text = str(r.get("text") or "").strip()
208
+ speaker = str(r.get("speaker") or "").strip()
209
+ date = str(r.get(date_key) or "").strip() if date_key else ""
210
+ title = str(r.get(title_key) or "").strip() if title_key else ""
211
+ ents = [e for e in _extract_entities(text) if e != speaker]
212
+ who = speaker or (next((e for e in ents if not e[0].isdigit()), None) or "匿名")
213
+ dtag = date or _date_tag(title)
214
+ low = f" {text.lower()} "
215
+ neg = next((m for m in _NEG_MARKS if m in low), None)
216
+ when = f"于{dtag}" if dtag else ""
217
+ ents_head = "/".join(ents[:2])
218
+
219
+ body = (
220
+ f"# 功能名:{who}·会话记忆{when}\n"
221
+ f"# 生效条件:查询涉及「{who}」{when}"
222
+ + (f"所述「{ents_head}」" if ents_head else "") + "时生效\n"
223
+ f"# 子功能:{who}的会话条目{when}\n"
224
+ f"# 执行:{who}会话原文回溯\n"
225
+ f"# 验证方式:data\n"
226
+ f"# 不适用条件:"
227
+ + (f"断言与原文相反(含{neg.strip()}语义)" if neg else "(无)") + "\n\n"
228
+ + text
229
+ )
230
+ tags = [t for t in (who, dtag) if t] + ents[:3]
231
+ cond_space = {
232
+ "observation_position": f"{who} 的会话陈述" + (f"({dtag})" if dtag else ""),
233
+ "observation_tool": "会话记录",
234
+ "existence_constraint": "公开",
235
+ }
236
+ return body, tags, cond_space
237
+
238
+
239
+ # 生效条件:任意 t 与可选 ctx(默认 None)传入即原样转调 calibrate_turn(t, date_key="date", ctx=ctx),返回其结果;
240
+ def calibrate_lm_turn(t, ctx=None):
241
+ return calibrate_turn(t, date_key="date", ctx=ctx)
242
+
243
+
244
+ # 生效条件:任意 r 与可选 ctx(默认 None)传入即原样转调 calibrate_turn(r, title_key="title", ctx=ctx),返回其结果;
245
+ def calibrate_lc_turn(r, ctx=None):
246
+ return calibrate_turn(r, title_key="title", ctx=ctx)
247
+
248
+
249
+ # ============ 写入时加工管线(v3)——「查询时的泛化提前到写入时完成」 ============
250
+ # 外部对标结论:检索只是链路最后一跳;完整系统在写入时做实体规范化/意图抽象/
251
+ # 指代消解,把语料加工成可沿链行走的形态。以下三步全部是**确定性规则近似**
252
+ # (纯标准库、盲于查询集、随报告公开词表),不引入任何模型依赖。
253
+
254
+ # 生效条件:对 corpus_path(min_df 默认 3),逐行取 text=str(r.get("text") or "")、spk=str(r.get("speaker") or "").strip(),以 {spk}(spk 真值)或空集合并入正文大写词首词(排除 _STOP_HEADS)更新 df 计数,最后返回计数 >= min_df 的词集合;
255
+ def build_canon(corpus_path, min_df=3):
256
+ """离线实体规范化:全语料扫一遍,聚合大写词/人名的文档频次(df)。
257
+ df≥min_df 的是 canonical 实体(会话成员 + 高频专名)——单 turn 局部抽取
258
+ 看不见「Alice 在 200 条 turn 里反复出现」这个全库事实,这正是写入时
259
+ (offline)加工区别于查询时(online)匹配的价值所在。"""
260
+ from collections import Counter
261
+ df = Counter()
262
+ import re as _re
263
+ for r in iter_jsonl(corpus_path):
264
+ text = str(r.get("text") or "")
265
+ spk = str(r.get("speaker") or "").strip()
266
+ ws = {spk} if spk else set()
267
+ ws |= set(_re.findall(r"[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?", text))
268
+ ws = {w.split()[0] for w in ws if w.split()[0] not in _STOP_HEADS}
269
+ df.update(ws)
270
+ return {w for w, c in df.items() if c >= min_df}
271
+
272
+
273
+ # 意图槽位(离线意图抽象的确定性近似):英文动词短语原样入条件行——
274
+ # 保持词面同源(查询 "What did Alice plan" 与条件行 "plan" 直接 bigram 命中),
275
+ # 不翻译成中文(同义词表是中文,翻译即断链)。三类槽覆盖 LoCoMo 问题分布:
276
+ # 计划/过去事件/偏好。
277
+ _INTENT_HEADS = ("plan", "going to", "thinking of", "thinking about", "want to",
278
+ "will be", "need to", "have to", "decide", "suggest",
279
+ "remember", "talked about", "talk about", "mention",
280
+ "last time", "tell you", "told you", "hear",
281
+ "prefer", "favorite", "love", "hate", "enjoy")
282
+
283
+
284
+ # 生效条件:对 text 小写并前后加空格得到 low,返回 _INTENT_HEADS 中满足 f" {h}" 出现在 low 的项;
285
+ def _intent_of(text):
286
+ low = f" {text.lower()} "
287
+ return [h for h in _INTENT_HEADS if f" {h}" in low]
288
+
289
+
290
+ # 生效条件:r 为语料 turn 行;ctx 为写入时全库视图(canon/intents/last_canon),ctx 为 None 时按空视图处理并退化为 v2 行为;返回 (CCG 五要素正文, tags, condition_space) 三元组;
291
+ def calibrate_turn(r, date_key=None, title_key=None, ctx=None):
292
+ """turn 行 → (CCG 五要素正文, tags, condition_space)。
293
+
294
+ v3(写入时加工管线):在 v2(特异词进每一行)之上叠加三步离线加工,
295
+ ctx 携带全库视图:
296
+ - ctx["canon"]:canonical 实体表(build_canon 产物)。条目涉及的
297
+ canonical 实体进生效条件行——查询以人名开头时,词法/实体路沿
298
+ 规范化实体直接落到「该人名相关条目」集合(链路第一跳)。
299
+ - ctx["intents"]:意图槽位短语进子功能行——意图抽象的词面同源版。
300
+ - ctx["last_canon"]:前一条的 canonical 实体(写入时序状态)。本条
301
+ 无任何实体而前条有 → 条件行附「承接 {last}」——保守指代消解:
302
+ 只附加假设、不删原文(判别力交给 RRF 多路共识,虚报风险可控)。
303
+ tags 用裸词(说话人/日期/实体):_path_entity 的匹配是 `t in query or
304
+ query in t`,带 `ent:` 前缀的 tag 在自然语言查询下永不命中(实测教训)。
305
+ 不适用条件仅当原文含强否定标记时生成;否则显式写「(无)」。
306
+ """
307
+ import re
308
+ text = str(r.get("text") or "").strip()
309
+ speaker = str(r.get("speaker") or "").strip()
310
+ date = str(r.get(date_key) or "").strip() if date_key else ""
311
+ title = str(r.get(title_key) or "").strip() if title_key else ""
312
+ canon = (ctx or {}).get("canon") or frozenset()
313
+ ctx_ents = _extract_entities(text)
314
+ canon_hits = [e for e in dict.fromkeys(
315
+ [speaker] if speaker else []) if e in canon]
316
+ if canon:
317
+ local = re.findall(r"[A-Z][a-z]+(?:\s+[A-Z][a-z]+)?", text)
318
+ canon_hits += [w for w in dict.fromkeys(w.split()[0] for w in local)
319
+ if w in canon and w not in canon_hits]
320
+ else:
321
+ canon_hits += [e for e in ctx_ents if e != speaker]
322
+ last = (ctx or {}).get("last_canon")
323
+ if not canon_hits and last:
324
+ canon_hits = list(last)
325
+ who = speaker or (next((e for e in canon_hits if not e[0].isdigit()), None)
326
+ or "匿名")
327
+ dtag = date or _date_tag(title)
328
+ low = f" {text.lower()} "
329
+ neg = next((m for m in _NEG_MARKS if m in low), None)
330
+ when = f"于{dtag}" if dtag else ""
331
+ ent_head = "/".join(canon_hits[:3])
332
+ intents = _intent_of(text)
333
+
334
+ body = (
335
+ f"# 功能名:{who}·会话记忆{when}\n"
336
+ f"# 生效条件:查询涉及「{ent_head or who}」{when}时生效\n"
337
+ f"# 子功能:{who}的会话条目"
338
+ + (f"(意图:{'/'.join(intents[:3])})" if intents else "") + "\n"
339
+ f"# 执行:{who}会话原文回溯\n"
340
+ f"# 验证方式:data\n"
341
+ f"# 不适用条件:"
342
+ + (f"断言与原文相反(含{neg.strip()}语义)" if neg else "(无)") + "\n\n"
343
+ + text
344
+ )
345
+ tags = [t for t in (who, dtag) if t] + canon_hits[:3]
346
+ cond_space = {
347
+ "observation_position": f"{who} 的会话陈述" + (f"({dtag})" if dtag else ""),
348
+ "observation_tool": "会话记录",
349
+ "existence_constraint": "公开",
350
+ }
351
+ return body, tags, cond_space
352
+
353
+
354
+ # 生效条件:rebuild=True 且 os.path.isdir(root) 时先 rmtree(root);cg_cls 为假值(如 None)时回落 MdCGOS(root, autoflush=500);语料行数 n_rows ≤ cg.index["nodes"] 现有节点数(n_rows=0 的空语料也满足)时直接复用返回 cg;否则逐行写入——calib_of 为真走标定口径 B(先 build_canon,calib_of(r, {"canon": canon, "last_canon": last_canon}) 取 body/tags/condition_space),calib_of 为假(含 None)走 legacy 口径 A(用 text_of(r)),id 已在 cg.index["nodes"] 的行跳过,verbose 为真时每 20000 行打印进度,循环后 cg.flush() 再返回 cg;
355
+ def build_eval_cg(cg_cls, root, corpus_path, text_of, src_tag, verbose=True,
356
+ calib_of=None, rebuild=False):
357
+ """幂等建库:节点数已达语料行数则直接复用。返回 cg(不 flush 句柄)。
358
+
359
+ calib_of=None → 口径 A(legacy 裸文本);传入 calibrate_*_turn → 口径 B
360
+ (五要素标定条目 + tags + condition_space)。两口径用不同 root,互不污染。
361
+ rebuild=True 删库重建(标定器改版后必须重建——幂等按节点数判断,识别不了
362
+ 同节点数的旧版标定条目)。
363
+ v3:标定口径先全语料 build_canon(离线实体规范化),写入循环维护
364
+ last_canon(时序状态,供保守指代承接)——两遍扫描,仍 O(N)。
365
+ """
366
+ from md_cg.mdcos import MdCGOS
367
+ if rebuild and os.path.isdir(root):
368
+ import shutil
369
+ shutil.rmtree(root, ignore_errors=True)
370
+ n_rows = sum(1 for _ in iter_jsonl(corpus_path))
371
+ cg = cg_cls(root, autoflush=500) if cg_cls else MdCGOS(root, autoflush=500)
372
+ have = len(cg.index["nodes"])
373
+ if have >= n_rows:
374
+ if verbose:
375
+ print(f" 建库复用 {root}:{have} 节点(语料 {n_rows} 行)")
376
+ return cg
377
+ canon = build_canon(corpus_path) if calib_of else frozenset()
378
+ if verbose and calib_of:
379
+ print(f" 写入时加工:canonical 实体 {len(canon)} 个(df≥3)")
380
+ if verbose:
381
+ print(f" 建库 {root}:已有 {have},语料 {n_rows} 行,开始写入 "
382
+ f"({'标定口径 B' if calib_of else 'legacy 口径 A'})…")
383
+ t0 = time.time()
384
+ last_canon = None
385
+ for i, r in enumerate(iter_jsonl(corpus_path), 1):
386
+ if r["id"] in cg.index["nodes"]:
387
+ continue
388
+ if calib_of:
389
+ body, tags, cond_space = calib_of(r, {"canon": canon,
390
+ "last_canon": last_canon})
391
+ cg.add(r["id"], body, layer="contextual", tags=tags,
392
+ condition_space=cond_space, eval_src=src_tag,
393
+ verification_basis="data")
394
+ hits = [t for t in tags[1:] if not t.replace(".", "")
395
+ .replace(":", "").isdigit()]
396
+ last_canon = hits or last_canon
397
+ else:
398
+ cg.add(r["id"], text_of(r), layer="contextual",
399
+ eval_src=src_tag, verification_basis="data")
400
+ if verbose and i % 20000 == 0:
401
+ print(f" … {i}/{n_rows}({time.time() - t0:.0f}s)")
402
+ cg.flush()
403
+ if verbose:
404
+ print(f" 建库完成:{len(cg.index['nodes'])} 节点,{time.time() - t0:.0f}s")
405
+ return cg
406
+
407
+
408
+ # 生效条件:对传入 cg,用闭包 cache(键为 entry["path"])包装其原 cg._read 并赋回 cg._read,返回该 cache;仅当 p = entry["path"] 不在 cache 时调用 orig(entry) 并缓存其(含假值)结果,p 已在 cache 中时直接返回缓存值;
409
+ def install_read_cache(cg):
410
+ """评测只读:节点文件读进内存,避免逐次检索重复磁盘 I/O。"""
411
+ cache = {}
412
+ orig = cg._read
413
+
414
+ # 生效条件:entry["path"] 未在闭包 cache 中时调用 orig(entry) 存入并返回,已在 cache 中则直接返回缓存值(cache 与原 _read 由外层 install_read_cache 提供);
415
+ def _cached(entry):
416
+ p = entry["path"]
417
+ if p not in cache:
418
+ cache[p] = orig(entry)
419
+ return cache[p]
420
+
421
+ cg._read = _cached
422
+ return cache
423
+
424
+
425
+ # ------------------------------------------------------------------ 检索与指标
426
+
427
+ # 生效条件:对 cg/query(k 默认 5、paths 默认 PATHS、judge 默认 False),若 fusion 为真值则加入 kw["fusion"],若 path_weights 为真值则加入 kw["path_weights"],若 context is not None(含空 dict/空串)则加入 kw["context"],再调用 cg.search_rrf(...);
428
+ def run_query(cg, query, k=5, paths=PATHS, judge=False, fusion=None,
429
+ path_weights=None, context=None):
430
+ """单查询,返回 (结果四元组列表, meta)。
431
+ fusion/path_weights 覆盖融合方式与路权重(按路拆解归因用)。
432
+
433
+ context 是**条件约束(bucket)路可被开启的唯一入口**:mdcos._path_bucket 在
434
+ `context is None` 时直接 `return []`(mdcos.py:435),而 search_rrf 的默认
435
+ paths 里恰恰含 "bucket"——即「名义四路、实际三路」。传 context 之前,全部
436
+ 既有四路 RRF 口径的评测都未曾开启过条件路由(先例:四轴消融 + Rust 空壳)。
437
+ """
438
+ kw = {}
439
+ if fusion:
440
+ kw["fusion"] = fusion
441
+ if path_weights:
442
+ kw["path_weights"] = path_weights
443
+ if context is not None:
444
+ kw["context"] = context
445
+ return cg.search_rrf(query, k=k, paths=paths, judge=judge, record=False, **kw)
446
+
447
+
448
+ # 生效条件:当 evidence 为真值且 res 中某 r 的 r[0]["id"] 属于 evidence 时返回首个 1-based 排名,否则(evidence 为假值或未命中)返回 0;
449
+ def first_evidence_rank(res, evidence):
450
+ """首个证据 turn 的排名(1-based;未命中 0)。"""
451
+ if not evidence:
452
+ return 0
453
+ for i, r in enumerate(res, 1):
454
+ if r[0]["id"] in evidence:
455
+ return i
456
+ return 0
457
+
458
+
459
+ # 生效条件:对 questions 中每个 it,若 context_of 为真值则 ctx=context_of(it) 否则 ctx=None;以 k/paths/judge/fusion/path_weights 调用 run_query,按 evidence_turns 算 first_evidence_rank,收集 qid/qtype/rank/top1_score/n_res 行,返回 rows;
460
+ def evaluate_group(cg, questions, k=5, paths=PATHS, judge=False, verbose=True,
461
+ fusion=None, path_weights=None, context_of=None):
462
+ """一组题 → per-question 明细行。负例组传 judge=False(拒答看分数线)。
463
+
464
+ context_of: 可调用 `q -> context dict`(返回 None 表示该题不下发情境)。
465
+ 缺省 None → 全组不传 context(**既有行为完全不变**)。传入即开启条件路由,
466
+ 是「条件约束路的净效应」单变量实验的开关。
467
+ """
468
+ rows = []
469
+ t0 = time.time()
470
+ for i, it in enumerate(questions, 1):
471
+ ctx = context_of(it) if context_of else None
472
+ res, meta = run_query(cg, it["question"], k=k, paths=paths, judge=judge,
473
+ fusion=fusion, path_weights=path_weights,
474
+ context=ctx)
475
+ rank = first_evidence_rank(res, set(it.get("evidence_turns") or []))
476
+ rows.append({
477
+ "qid": it["qid"], "qtype": it["qtype"],
478
+ "rank": rank, "top1_score": res[0][1] if res else 0.0,
479
+ "n_res": len(res),
480
+ })
481
+ if verbose and i % 100 == 0:
482
+ print(f" 查询 {i}/{len(questions)}({time.time() - t0:.0f}s)")
483
+ return rows
484
+
485
+
486
+ # 生效条件:当 rows 为真值时按 k(默认 5)计算 hit@1、hit@k、MRR、score_p10/p50 及 by_qtype 指标,否则返回 {"n": 0};
487
+ def summarize(rows, k=5):
488
+ """hit@1 / hit@K / MRR + 分题型。score_p10 供拒答线校准。"""
489
+ if not rows:
490
+ return {"n": 0}
491
+ hit1 = sum(1 for r in rows if r["rank"] == 1)
492
+ hitk = sum(1 for r in rows if 0 < r["rank"] <= k)
493
+ mrr = sum(1.0 / r["rank"] for r in rows if r["rank"]) / len(rows)
494
+ top1 = sorted(r["top1_score"] for r in rows)
495
+ out = {
496
+ "n": len(rows),
497
+ "hit@1": hit1 / len(rows),
498
+ f"hit@{k}": hitk / len(rows),
499
+ "mrr": mrr,
500
+ "score_p10": top1[max(0, len(top1) // 10)],
501
+ "score_p50": top1[len(top1) // 2],
502
+ }
503
+ by = {}
504
+ for r in rows:
505
+ by.setdefault(r["qtype"], []).append(r)
506
+ out["by_qtype"] = {
507
+ qt: {
508
+ "n": len(g),
509
+ "hit@1": sum(1 for r in g if r["rank"] == 1) / len(g),
510
+ f"hit@{k}": sum(1 for r in g if 0 < r["rank"] <= k) / len(g),
511
+ "mrr": sum(1.0 / r["rank"] for r in g if r["rank"]) / len(g),
512
+ } for qt, g in sorted(by.items())
513
+ }
514
+ return out
515
+
516
+
517
+ # 生效条件:当 rows 非空时按 r["top1_score"] < line 统计拒答数并返回拒绝率,否则返回 {"n": 0};
518
+ def refusal_metrics(rows, line):
519
+ """负例组:Top-1 分 < line 记为拒答。返回拒答率与明细统计。"""
520
+ n = len(rows)
521
+ if not n:
522
+ return {"n": 0}
523
+ ref = sum(1 for r in rows if r["top1_score"] < line)
524
+ return {"n": n, "refusal_rate": ref / n,
525
+ "refused": ref, "line": line}
526
+
527
+
528
+ # 生效条件:当 rows 非空时返回 r["top1_score"] < line 的行占比,否则返回 0.0;
529
+ def false_refusal_rate(rows, line):
530
+ """正例误杀(被错误拒答的正例比例):正例 Top-1 分 < line。"""
531
+ n = len(rows)
532
+ if not n:
533
+ return 0.0
534
+ return sum(1 for r in rows if r["top1_score"] < line) / n
535
+
536
+
537
+ # 生效条件:当 pos_rows 中存在 rank==1 的行时,返回这些行 top1_score 升序后 max(0, len//10) 索引处的值,否则返回 0.0;
538
+ def calibrate_line(pos_rows):
539
+ """拒答线 = 正例 hit@1 题 Top-1 分的 10 分位。"""
540
+ scores = sorted(r["top1_score"] for r in pos_rows if r["rank"] == 1)
541
+ if not scores:
542
+ return 0.0
543
+ return scores[max(0, len(scores) // 10)]
544
+
545
+
546
+ # 生效条件:当 name/payload 给出时,确保 RESULTS 目录(os.makedirs(RESULTS, exist_ok=True)),将 payload 以 ensure_ascii=False、indent=2 写入 RESULTS/name 并返回 path;
547
+ def save_result(name, payload):
548
+ """评测结果 JSON 落盘(报告数据源)。"""
549
+ os.makedirs(RESULTS, exist_ok=True)
550
+ path = os.path.join(RESULTS, name)
551
+ with open(path, "w", encoding="utf-8") as f:
552
+ json.dump(payload, f, ensure_ascii=False, indent=2)
553
+ print(f" 结果 → {path}")
554
+ return path
555
+
556
+
557
+ # 生效条件:对 groups(k 默认 5),若某组 s 的 s.get("n") 为假(缺 n 或 n=0)则打印 0 行并跳过,否则按 s["hit@1"]、s[f"hit@{k}"]、s["mrr"] 打印并遍历 s.get("by_qtype", {}) 输出子行;
558
+ def print_table(title, groups, k=5):
559
+ """groups: {组名: summarize 输出}。"""
560
+ print(f"\n== {title}(证据命中口径,k={k})==")
561
+ print(f"{'组':<28}{'n':>6}{'hit@1':>9}{f'hit@{k}':>9}{'MRR':>8}")
562
+ print("-" * 62)
563
+ for name, s in groups.items():
564
+ if not s.get("n"):
565
+ print(f"{name:<28}{0:>6} -")
566
+ continue
567
+ print(f"{name:<28}{s['n']:>6}{s['hit@1']:>9.1%}"
568
+ f"{s[f'hit@{k}']:>9.1%}{s['mrr']:>8.3f}")
569
+ for qt, st in s.get("by_qtype", {}).items():
570
+ print(f" ├ {qt:<24}{st['n']:>6}{st['hit@1']:>9.1%}"
571
+ f"{st[f'hit@{k}']:>9.1%}{st['mrr']:>8.3f}")
572
+
573
+
574
+ # 生效条件:对任意 x,返回 f"{x * 100:.1f}%" 的百分比字符串;
575
+ def pct(x):
576
576
  return f"{x * 100:.1f}%"