@furongjun1999/dsh-memory 0.4.8 → 0.4.10

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 (232) hide show
  1. package/README.md +54 -26
  2. package/codebuddy/CODEBUDDY.md +196 -195
  3. package/codebuddy/README.md +13 -1
  4. package/codebuddy/mcp.json +9 -0
  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 -0
  6. package/docs/README.md +1 -1
  7. package/docs/discipline/harnesses.yaml +18 -7
  8. package/docs/discipline/templates/full.md.tmpl +4 -3
  9. package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
  10. package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
  11. package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
  12. package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
  13. package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
  14. package/docs/hive//345/244/232/347/253/257harness/351/200/232/344/277/241/345/245/221/347/272/246_v0.1.md +42 -0
  15. 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 -0
  16. 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 -0
  17. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S1b/344/270/216/345/217/254/345/233/236/346/235/203/350/241/241_v0.1.md +57 -0
  18. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S7/345/200/222/346/216/222/345/200/231/351/200/211/345/261/202_v0.1.md +126 -0
  19. 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 +503 -0
  20. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -0
  21. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
  22. package/docs/mdcg//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_/345/245/221/347/272/246_v0.1.md +82 -0
  23. 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 -0
  24. 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 +47 -45
  25. package/docs/mdcg//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/{206_v0.4.md → 206_v0.5.md} +92 -4
  26. 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 -0
  27. package/docs/mdcg//347/216/257/344/272/214_/347/231/275/347/256/261/345/241/253/345/205/205/346/265/201/346/260/264/347/272/277_v0.1.md +31 -0
  28. package/docs/mdcg//350/267/250/347/253/257/351/252/214/350/257/201/344/270/216/345/220/214/346/255/245/345/215/217/350/256/256_v0.1.md +93 -0
  29. 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 +2 -2
  30. 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 +3 -3
  31. 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 +2 -2
  32. 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 +434 -433
  33. package/docs//347/201/265/346/236/242/350/207/252/346/210/221/346/224/271/350/277/233/345/267/245/344/275/234/350/256/241/345/210/222_/345/244/226/351/203/250/347/240/224/347/251/266/347/263/273/345/210/227/345/220/270/346/224/266_v1_20260919.md +286 -0
  34. package/dsh/README.md +33 -0
  35. package/dsh/cordis.yml.example +13 -7
  36. package/dsh/hive-mcp-probe.mjs +94 -0
  37. package/dsh/hive-mcp.example.yml +62 -0
  38. package/dsh/update-lingshu.bat +11 -0
  39. package/dsh/update-lingshu.ps1 +337 -0
  40. package/lib/hooks.d.ts +3 -0
  41. package/lib/hooks.js +17 -23
  42. package/lib/index.d.ts +4 -2
  43. package/lib/index.js +29 -6
  44. package/lib/lib/datapath.d.ts +76 -1
  45. package/lib/lib/datapath.js +199 -13
  46. package/lib/lib/mdcg_client.d.ts +40 -3
  47. package/lib/lib/mdcg_client.js +46 -22
  48. package/lib/lib/mutual.js +4 -4
  49. package/lib/lib/token_store.js +4 -5
  50. package/md_cg/audit.py +17 -2
  51. package/md_cg/autonomy.py +86 -15
  52. package/md_cg/backfill.py +36 -1
  53. package/md_cg/backfill_bigdomain.py +34 -0
  54. package/md_cg/bench6_arms.py +28 -1
  55. package/md_cg/bench6_common.py +10 -1
  56. package/md_cg/bench6_competitors.py +6 -1
  57. package/md_cg/bench_axis_domain.py +9 -1
  58. package/md_cg/bench_blind_comp.py +7 -1
  59. package/md_cg/bench_en_atoms_public.py +9 -0
  60. package/md_cg/bench_governance.py +348 -0
  61. package/md_cg/bench_lme_zh.py +16 -1
  62. package/md_cg/bench_locomo.py +2 -1
  63. package/md_cg/bench_locomo_zh.py +16 -1
  64. package/md_cg/bench_locomo_zh_public.py +4 -1
  65. package/md_cg/bench_longmem.py +2 -1
  66. package/md_cg/bench_membench.py +27 -1
  67. package/md_cg/bench_p0.py +4 -1
  68. package/md_cg/bench_progressive.py +13 -1
  69. package/md_cg/bench_role_views.py +238 -0
  70. package/md_cg/bench_task_ab.py +8 -1
  71. package/md_cg/bench_task_ab_llm.py +13 -1
  72. package/md_cg/bench_unified_en.py +6 -1
  73. package/md_cg/bench_zh_mad.py +20 -1
  74. package/md_cg/blindspot_tickets.py +123 -0
  75. package/md_cg/branches.py +12 -1
  76. package/md_cg/build_postings.py +73 -0
  77. package/md_cg/ccgc.py +67 -2
  78. package/md_cg/census.py +5 -1
  79. package/md_cg/chain.py +24 -3
  80. package/md_cg/codeindex.py +134 -17
  81. package/md_cg/coldverify.py +265 -0
  82. package/md_cg/comment_gate.py +338 -0
  83. package/md_cg/cond_compose.py +190 -0
  84. package/md_cg/cond_facts.py +155 -0
  85. package/md_cg/cond_template.json +107 -0
  86. package/md_cg/condition_anchor.py +143 -0
  87. package/md_cg/conformance.py +69 -4
  88. package/md_cg/consistency.py +24 -1
  89. package/md_cg/consolidate.py +53 -2
  90. package/md_cg/corpus.py +4 -0
  91. package/md_cg/crosscheck.py +42 -2
  92. package/md_cg/crypto.py +35 -1
  93. package/md_cg/d_meta.py +310 -0
  94. package/md_cg/datapath.py +201 -26
  95. package/md_cg/docindex.py +122 -1
  96. package/md_cg/eval_common.py +29 -1
  97. package/md_cg/evidence.py +27 -1
  98. package/md_cg/evolution.py +21 -1
  99. package/md_cg/export.py +11 -1
  100. package/md_cg/forgetting.py +23 -1
  101. package/md_cg/fsutil.py +18 -1
  102. package/md_cg/hotcache.py +214 -0
  103. package/md_cg/hyperedge.py +251 -0
  104. package/md_cg/identity.py +18 -1
  105. package/md_cg/insight.py +17 -1
  106. package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
  107. package/md_cg/lexicon/build_standard_en.py +171 -168
  108. package/md_cg/lexicon/expand_en_zh.py +6 -0
  109. package/md_cg/lifecycle.py +12 -1
  110. package/md_cg/linkref.py +281 -0
  111. package/md_cg/links.py +29 -1
  112. package/md_cg/mcp_server.py +362 -43
  113. package/md_cg/md_whitebox.py +53 -1
  114. package/md_cg/mdcg.py +1003 -27
  115. package/md_cg/mdcos.py +558 -36
  116. package/md_cg/metacognition.py +37 -2
  117. package/md_cg/migrate.py +4 -0
  118. package/md_cg/migrate_aeis.py +221 -213
  119. package/md_cg/migrate_roleplay.py +8 -0
  120. package/md_cg/migrate_wisdom_graph.py +14 -1
  121. package/md_cg/mreview/__main__.py +3 -0
  122. package/md_cg/mreview/bundle.py +8 -0
  123. package/md_cg/mreview/candidates.py +9 -0
  124. package/md_cg/mreview/govern.py +21 -1
  125. package/md_cg/mreview/locate.py +34 -0
  126. package/md_cg/mreview/pipeline.py +29 -1
  127. package/md_cg/mreview/ruleset.py +16 -1
  128. package/md_cg/nodefile.py +233 -3
  129. package/md_cg/pooling.py +23 -1
  130. package/md_cg/postings.py +298 -0
  131. package/md_cg/predict.py +89 -9
  132. package/md_cg/progressive.py +3 -0
  133. package/md_cg/protect.py +14 -1
  134. package/md_cg/protocol.py +372 -0
  135. package/md_cg/provenance.py +262 -0
  136. package/md_cg/reach.py +453 -0
  137. package/md_cg/refindex.py +47 -2
  138. package/md_cg/refine.py +20 -1
  139. package/md_cg/roleviews.py +89 -0
  140. package/md_cg/routing.py +76 -0
  141. package/md_cg/scrub.py +63 -2
  142. package/md_cg/security.py +26 -1
  143. package/md_cg/self_state.py +64 -1
  144. package/md_cg/selfreport.py +151 -0
  145. package/md_cg/semantic/canonical.py +5 -0
  146. package/md_cg/semantic/en_normalizer.py +364 -355
  147. package/md_cg/semantic/zh_en_atoms.py +139 -136
  148. package/md_cg/signer.py +41 -1
  149. package/md_cg/sources.py +583 -547
  150. package/md_cg/statushdr.py +179 -0
  151. package/md_cg/stg.py +59 -18
  152. package/md_cg/subgraph.py +23 -0
  153. package/md_cg/sustain.py +56 -1
  154. package/md_cg/tasks.py +26 -2
  155. package/md_cg/test_autonomy.py +26 -0
  156. package/md_cg/test_bench_governance.py +102 -0
  157. package/md_cg/test_blindspot_tickets.py +166 -0
  158. package/md_cg/test_ccgc.py +10 -0
  159. package/md_cg/test_codeindex.py +338 -0
  160. package/md_cg/test_comment_gate.py +187 -0
  161. package/md_cg/test_cond_compose_anchors.py +76 -0
  162. package/md_cg/test_condition_anchor.py +82 -0
  163. package/md_cg/test_d_meta.py +412 -0
  164. package/md_cg/test_datapath_root.py +188 -0
  165. package/md_cg/test_gain_gate.py +47 -1
  166. package/md_cg/test_hot_cold.py +187 -0
  167. package/md_cg/test_hyperedge.py +245 -0
  168. package/md_cg/test_linkref.py +306 -0
  169. package/md_cg/test_md_access_parity.py +15 -3
  170. package/md_cg/test_mr_m1.py +108 -18
  171. package/md_cg/test_mr_m3.py +8 -1
  172. package/md_cg/test_p26_refindex.py +49 -20
  173. package/md_cg/test_p27_docindex.py +236 -2
  174. package/md_cg/test_p2_mcp.py +1 -1
  175. package/md_cg/test_p31_insight.py +24 -0
  176. package/md_cg/test_p44_md_whitebox.py +14 -1
  177. package/md_cg/test_protocol.py +243 -0
  178. package/md_cg/test_reach.py +378 -0
  179. package/md_cg/test_reach_keys.py +201 -0
  180. package/md_cg/test_reach_meta_exits.py +145 -0
  181. package/md_cg/test_read_clip.py +8 -4
  182. package/md_cg/test_retr_s1.py +340 -0
  183. package/md_cg/test_retr_s1b.py +209 -0
  184. package/md_cg/test_retr_s3.py +194 -0
  185. package/md_cg/test_retr_s4.py +163 -0
  186. package/md_cg/test_retr_s5.py +200 -0
  187. package/md_cg/test_retr_s6.py +157 -0
  188. package/md_cg/test_retr_s7.py +385 -0
  189. package/md_cg/test_retr_s8_time.py +316 -0
  190. package/md_cg/test_retr_s9_edges.py +286 -0
  191. package/md_cg/test_retr_s9_entity_ctx.py +175 -0
  192. package/md_cg/test_review_conformance.py +59 -2
  193. package/md_cg/test_role_views.py +354 -0
  194. package/md_cg/test_subproc_encoding.py +188 -0
  195. package/md_cg/test_trust.py +361 -0
  196. package/md_cg/test_units_poll.py +71 -0
  197. package/md_cg/test_v14_fixes.py +397 -0
  198. package/md_cg/test_validity_filter.py +280 -0
  199. package/md_cg/test_wisdom_md_store.py +7 -3
  200. package/md_cg/test_writepipe.py +5 -1
  201. package/md_cg/theory.py +16 -1
  202. package/md_cg/tokens.py +40 -8
  203. package/md_cg/tool_face.py +13 -2
  204. package/md_cg/trust.py +943 -0
  205. package/md_cg/twophase.py +12 -1
  206. package/md_cg/units.py +132 -10
  207. package/md_cg/vision_evidence.py +24 -1
  208. package/md_cg/weights.py +24 -1
  209. package/md_cg/whitebox.py +32 -1
  210. package/md_cg/whitebox_kb/data/verify_cache.json +21210 -365
  211. package/md_cg/whitebox_kb/data/verify_savings.jsonl +5078 -0
  212. package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
  213. package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
  214. package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
  215. package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
  216. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
  217. package/md_cg/writelimit.py +18 -4
  218. package/md_cg/writepipe.py +178 -7
  219. package/package.json +2 -2
  220. package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
  221. package/skills/skills/designer-perspective/scripts/designer.py +17 -1
  222. package/skills/skills/designer-perspective/tests/selftest.py +3 -1
  223. package/src/hooks.ts +17 -21
  224. package/src/index.ts +33 -6
  225. package/src/lib/datapath.ts +211 -13
  226. package/src/lib/mdcg_client.ts +64 -25
  227. package/src/lib/mutual.ts +411 -411
  228. package/src/lib/token_store.ts +4 -5
  229. package/zcode/AGENTS.md +196 -195
  230. package/zcode/README.md +4 -0
  231. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-shm +0 -0
  232. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-wal +0 -0
@@ -0,0 +1,348 @@
1
+ # -*- coding: utf-8 -*-
2
+ """治理四指标只读评估器(阶段三 §5.1/§5.2 基线测量,纯合成确定性语料,不改任何行为面)。
3
+
4
+ 四指标(对照值来自 docs/灵枢自我改进工作计划_外部研究系列吸收 §5):
5
+ g1 修正传播率 — 上游信任态降级后,依赖下游一跳同步失效比例
6
+ (承载=trust.set_state+mark_dependents;对照 MAGE 91.2%)
7
+ g2 矛盾处理率 — 矛盾/否定证据被正确裁决比例
8
+ (承载=judge_qualification 负条件 REJECT + judge_ranking 终排剔除;
9
+ 对照 MAGE 88.5%)
10
+ g3 过期取回率 — 检索 top-k 返回已过期节点比例(越低越好)
11
+ (口径唯一真源=trust.is_expired / scrub._EXPIRY_KEYS;
12
+ 主口径=validity 防线残留率,副口径=默认面暴露率;对照 MAGE 3.1%)
13
+ g4 拒答 F1 — 不可答问题拒答的精确率/召回率/F1
14
+ (拒答判据=空结果 或 top1 资格态∈{BLINDSPOT, DEFER};
15
+ 对照 REMem 64.0)
16
+
17
+ 设计约束:
18
+ - 纯合成语料、无随机源,重复运行结果逐位一致(第 5 条可回放);
19
+ - tempfile 库,不触碰真实认知图库(评估器只读);
20
+ - 语料守卫断言(语料本身不合法立即 fail,防指标虚高——对齐 bench_blind_comp
21
+ assert_material 纪律)。
22
+ 运行:python -X utf8 -m md_cg.bench_governance [--out <json>]
23
+ """
24
+ import argparse
25
+ import json
26
+ import os
27
+ import shutil
28
+ import subprocess
29
+ import sys
30
+ import tempfile
31
+ import time
32
+
33
+ from .mdcg import MdCG
34
+ from .mdcos import MdCGOS
35
+ from . import trust
36
+
37
+ BASELINE_REF = {
38
+ "g1_correction_propagation": {"ours": None, "mage": 0.912},
39
+ "g2_contradiction_handling": {"ours": None, "mage": 0.885},
40
+ "g3_expired_recall": {"ours": None, "mage": 0.031, "note": "越低越好"},
41
+ "g4_refusal_f1": {"ours": None, "remem_f1": 64.0, "note": "REMem 为其自报口径,仅作量级对照"},
42
+ }
43
+
44
+ # ---------------------------------------------------------------- 语料 ----
45
+
46
+ NOW = None # main 内注入,保证同一次运行内时间一致
47
+
48
+
49
+ def _doc(title, fact, cond="无条件", neg=None):
50
+ """CCG 六要素正文(与 test_sem_noise._doc 同构:负条件双写中的正文行)。"""
51
+ lines = [
52
+ "# 功能名:" + title,
53
+ "# 生效条件:" + cond,
54
+ "# 子功能:" + title + "事实卡",
55
+ "# 执行:检索面事实提供",
56
+ "# 验证方式:test",
57
+ "# 不适用条件:" + (neg or "无"),
58
+ "",
59
+ fact,
60
+ ]
61
+ return "\n".join(lines)
62
+
63
+
64
+ # 可答事实:条件词面刻意出现在对应 query 里(正条件确认可命中 → ACCEPT)
65
+ FACTS = [
66
+ # (nid, 事实句, 正条件行, query)
67
+ ("g_f1", "灵枢检索缓存默认保留 128 条。", "载体:缓存配置", "缓存配置里默认缓存多大"),
68
+ ("g_f2", "蜂巢心跳间隔为 30 秒。", "载体:蜂巢参数", "蜂巢参数中心跳间隔是多少"),
69
+ ("g_f3", "评测器统一经 eval_common 汇总指标。", "载体:评测约定", "评测约定的汇总入口在哪"),
70
+ ("g_f4", "python 命令统一走 argv 列表加显式 UTF-8。", "无条件", "python 命令执行要用什么编码约定"),
71
+ ("g_f5", "语义摘要路默认关闭、显式 opt-in 才启用。", "无条件", "语义摘要路默认开还是关"),
72
+ ("g_f6", "分支节点不出库、不进主支检索。", "无条件", "分支节点会不会进主支检索"),
73
+ ]
74
+
75
+ # 否定反事实:负条件双写(kwarg + 正文行),否定情境 query=「你刚才在干什么」
76
+ NEG_QUERY = "你刚才在干什么"
77
+ NEG_COND = "刚才在干什么"
78
+ NEGATIVES = [
79
+ ("g_n1", "我没有在喝水。"),
80
+ ("g_n2", "我没有在写代码。"),
81
+ ("g_n3", "我没有在开会。"),
82
+ ("g_n4", "我没有在跑步。"),
83
+ ]
84
+
85
+ # 过期节点:与可答事实同题竞争(旧版本数值已失效),effective_until 已过
86
+ STALE = [
87
+ ("g_e1", "蜂巢心跳间隔曾为 10 秒(旧版)。", "g_f2"),
88
+ ("g_e2", "评测器旧口径按 k=3 汇总(已废弃)。", "g_f3"),
89
+ ("g_e3", "检索缓存默认曾为 64 条(旧版)。", "g_f1"),
90
+ ]
91
+
92
+ # 修正传播链:地基 U + 一跳依赖 D1..D4
93
+ U_GOV = "g_u_gov"
94
+ DEPENDENTS = ["g_d1", "g_d2", "g_d3", "g_d4"]
95
+
96
+ # 无关背景(干扰;CCG 完整可 ACCEPT——拒答 F1 的诚实下界来自它)
97
+ BACKDROP = [
98
+ ("g_b1", "工作区在约定中默认使用简体中文交流。"),
99
+ ("g_b2", "认知图只给知识与建议能力名,不执行操作。"),
100
+ ("g_b3", "留痕粒度按任务合并,不逐命令逐行。"),
101
+ ]
102
+
103
+ # 不可答问题(与语料词面零重叠;期望系统拒答)
104
+ UNANSWERABLE = [
105
+ "用户 2026 年春节在哪过的",
106
+ "项目预算总共批了多少万",
107
+ "作者最喜欢的菜是什么",
108
+ "下个版本计划支持日语吗",
109
+ "服务器部署在哪个机房",
110
+ "团队现在有多少人",
111
+ "竞品报价单是多少",
112
+ "昨天的会议纪要说了什么",
113
+ ]
114
+
115
+
116
+ def _assert_material(now):
117
+ """语料守卫:素材纪律机器断言(违者 fail,防指标虚高)。"""
118
+ fact_ids = {nid for nid, *_ in FACTS}
119
+ stale_ids = {nid for nid, *_ in STALE}
120
+ neg_ids = {nid for nid, *_ in NEGATIVES}
121
+ assert not (fact_ids & stale_ids | fact_ids & neg_ids | stale_ids & neg_ids), "语料 id 重叠"
122
+ assert len(UNANSWERABLE) >= 5 and len(FACTS) >= 5, "探针集过小"
123
+ # 不可答题与语料词面零重叠(粗粒度:语料特征词不出现于不可答题面)
124
+ lex = "".join(f for _, f, *_ in FACTS) + "".join(f for _, f in NEGATIVES) \
125
+ + "".join(f for _, f, _ in STALE) + "".join(f for _, f in BACKDROP)
126
+ for q in UNANSWERABLE:
127
+ overlap = [w for w in ("缓存", "蜂巢", "心跳", "评测", "python", "语义摘要", "分支") if w in q]
128
+ assert not overlap, f"不可答题词面泄漏:{q} 命中 {overlap}"
129
+ assert "无条件" not in lex or True # 语料自洽由 judge 实测兜底
130
+ for _, _, base in STALE:
131
+ assert base in fact_ids, "过期节点必须与某可答事实同题竞争"
132
+
133
+
134
+ def build_corpus(cg, now):
135
+ """建确定性语料。返回节点数。"""
136
+ n = 0
137
+ # 可答事实(F1-F3 带条件、F4-F6 无条件豁免)
138
+ for nid, fact, cond, _q in FACTS:
139
+ cg.add(nid, _doc(nid, fact, cond=cond), layer="knowledge",
140
+ verification_basis="test")
141
+ n += 1
142
+ # 否定反事实(负条件双写)
143
+ for nid, sent in NEGATIVES:
144
+ cg.add(nid, _doc(nid, sent, cond="时间:即时情境", neg=NEG_COND), layer="knowledge",
145
+ verification_basis="test", non_applicable_conditions=[NEG_COND])
146
+ n += 1
147
+ # 过期节点(effective_until 已过;expired_at 冗余由 add 派生)
148
+ for nid, fact, _base in STALE:
149
+ cg.add(nid, _doc(nid, fact, cond="无条件"), layer="knowledge",
150
+ verification_basis="test",
151
+ effective_from=now - 7200.0, effective_until=now - 3600.0)
152
+ n += 1
153
+ # 无关背景
154
+ for nid, fact in BACKDROP:
155
+ cg.add(nid, _doc(nid, fact), layer="knowledge", verification_basis="test")
156
+ n += 1
157
+ # 修正传播链:地基 + 一跳依赖(depends_on=依赖图语义,trust.dependents_index 消费;
158
+ # derived_from=血缘溯源,语义不同不混用)
159
+ cg.add(U_GOV, _doc(U_GOV, "地基结论:核心算法口径以文档 A 为准。", cond="无条件"),
160
+ layer="knowledge", verification_basis="test")
161
+ n += 1
162
+ for did in DEPENDENTS:
163
+ cg.add(did, _doc(did, f"{did} 结论:沿用地基口径实现细节。", cond="无条件"),
164
+ layer="knowledge", verification_basis="test", depends_on=[U_GOV])
165
+ n += 1
166
+ return n
167
+
168
+
169
+ # ---------------------------------------------------------------- 指标 ----
170
+
171
+ def m1_correction_propagation(cg):
172
+ """g1:上游降级 doubted → mark_dependents 一跳同步传播率。"""
173
+ r0 = trust.set_state(cg, U_GOV, "doubted", reason="基线评测:地基被修正")
174
+ assert isinstance(r0, dict) and r0.get("ok"), f"上游降级失败:{r0}"
175
+ r = trust.mark_dependents(cg, U_GOV, reason="基线评测:上游修正传播", apply=True)
176
+ deps, upd = r.get("dependents") or [], r.get("updated") or []
177
+ assert sorted(deps) == sorted(DEPENDENTS), f"依赖索引不符预期:{deps}"
178
+ rate = (len(upd) / len(deps)) if deps else None
179
+ return {"metric": "correction_propagation", "value": rate,
180
+ "detail": {"dependents": len(deps), "updated": len(upd),
181
+ "skipped": r.get("skipped"), "reference_mage": 0.912}}
182
+
183
+
184
+ def _is_refusal(results):
185
+ """拒答判据:空结果 或 top1 资格态 ∈ {BLINDSPOT, DEFER}。"""
186
+ if not results:
187
+ return True, "empty"
188
+ entry = results[0]
189
+ qual = entry[2] if len(entry) > 2 else None # 条目=(node, score, qual, prov)
190
+ st = (qual or {}).get("state") if isinstance(qual, dict) else None
191
+ return st in ("BLINDSPOT", "DEFER"), st
192
+
193
+
194
+ def m2_contradiction_handling(cg):
195
+ """g2:否定证据正确裁决率 = 定点裁决 + 检索防线(skip 项不入分母)。"""
196
+ correct = wrong = skipped = 0
197
+ detail = {"judged": [], "retrieval": []}
198
+ # (a) 定点裁决:否定节点在否定情境下应 REJECT(judge_qualification 直调)
199
+ for nid, _sent in NEGATIVES:
200
+ node = cg.get(nid)
201
+ qual = MdCG.judge_qualification(
202
+ {"frontmatter": node.get("frontmatter") or {}, "content": node.get("content") or ""},
203
+ NEG_QUERY, None)
204
+ st = qual.get("state")
205
+ detail["judged"].append({"nid": nid, "state": st})
206
+ if st == "REJECT":
207
+ correct += 1
208
+ elif st is None:
209
+ skipped += 1
210
+ else:
211
+ wrong += 1
212
+ # (b) 检索防线:judge_ranking 终排后 top-k 无否定节点冒充
213
+ res, meta = cg.search_rrf(NEG_QUERY, k=10, judge=True, judge_ranking=True,
214
+ record=False)
215
+ ids = [r[0]["id"] for r in res]
216
+ leaked = [i for i in ids if i in {n for n, _ in NEGATIVES}]
217
+ detail["retrieval"] = {"top_ids": ids[:5], "leaked_negatives": leaked,
218
+ "judge_filtered": (meta or {}).get("judge_filtered")}
219
+ if leaked:
220
+ wrong += 1
221
+ else:
222
+ correct += 1
223
+ total = correct + wrong
224
+ return {"metric": "contradiction_handling", "value": (correct / total) if total else None,
225
+ "detail": dict(detail, correct=correct, wrong=wrong, skipped=skipped,
226
+ reference_mage=0.885)}
227
+
228
+
229
+ def m3_expired_recall(cg, now):
230
+ """g3:过期取回率。主口径=validity 防线残留率;副口径=默认面暴露率。"""
231
+ stale_ids = {nid for nid, *_ in STALE}
232
+ probe = "蜂巢参数中心跳间隔是多少" # 与过期节点 g_e1 同题竞争
233
+ out = {}
234
+ for tag, kw in (("raw", {}), ("guard", {"validity": True}),
235
+ ("judge_ranking", {"judge_ranking": True})):
236
+ res, _meta = cg.search_rrf(probe, k=10, judge=True, record=False, **kw)
237
+ ids = [r[0]["id"] for r in res]
238
+ hits = [i for i in ids if i in stale_ids]
239
+ out[tag] = {"top_ids": ids[:5], "stale_hits": hits,
240
+ "rate": (len(hits) / len(ids)) if ids else None}
241
+ return {"metric": "expired_recall",
242
+ "value": out["guard"]["rate"], # 主口径:防线后残留(越低越好)
243
+ "detail": dict(out, probe=probe, stale_total=len(stale_ids),
244
+ reference_mage=0.031, note="raw=默认面暴露;guard=validity 防线")}
245
+
246
+
247
+ def m4_refusal_f1(cg):
248
+ """g4:拒答精确率/召回率/F1(正类=不可答)。"""
249
+ tp = fn = fp = tn = 0
250
+ detail = {"unanswerable": [], "answerable": []}
251
+ for q in UNANSWERABLE:
252
+ res, _m = cg.search_rrf(q, k=3, judge=True, judge_ranking=True, record=False)
253
+ refused, st = _is_refusal(res)
254
+ detail["unanswerable"].append({"q": q, "refused": refused, "top_state": st})
255
+ if refused:
256
+ tp += 1
257
+ else:
258
+ fn += 1
259
+ for _nid, _fact, _cond, q in FACTS:
260
+ res, _m = cg.search_rrf(q, k=3, judge=True, judge_ranking=True, record=False)
261
+ refused, st = _is_refusal(res)
262
+ detail["answerable"].append({"q": q, "refused": refused, "top_state": st})
263
+ if refused:
264
+ fp += 1
265
+ else:
266
+ tn += 1
267
+ p = tp / (tp + fp) if (tp + fp) else None
268
+ r = tp / (tp + fn) if (tp + fn) else None
269
+ if p is not None and r is not None and p + r:
270
+ f1 = 2 * p * r / (p + r)
271
+ else:
272
+ # tp+fp=0:系统从未拒答(无 query 侧拒答信号)——保守编码 F1=0.0 并如实注明
273
+ f1 = 0.0
274
+ return {"metric": "refusal_f1",
275
+ "value": f1,
276
+ "detail": {"precision": p, "recall": r, "tp": tp, "fn": fn, "fp": fp, "tn": tn,
277
+ "refusal_rule": "empty or top1 state in {BLINDSPOT, DEFER}",
278
+ "note": ("no refusal signal: judge 资格态只判节点可用性,"
279
+ "不判答案存在性;不可答题被无关 ACCEPT 节点冒充全部未拒"
280
+ if (tp + fp) == 0 else ""),
281
+ "reference_remem_f1": 64.0}}
282
+
283
+
284
+ # ---------------------------------------------------------------- 主流程 ----
285
+
286
+ def run(root=None, keep=False):
287
+ """跑四指标,返回 (report_dict, root)。root 供测试复检。"""
288
+ now = time.time()
289
+ _assert_material(now)
290
+ root = root or tempfile.mkdtemp(prefix="mdcg_governance_")
291
+ try:
292
+ cg = MdCGOS(root, actor="bench_governance", autoflush=200)
293
+ n_nodes = build_corpus(cg, now)
294
+ cg.flush()
295
+ report = {
296
+ "meta": {
297
+ "bench": "bench_governance",
298
+ "date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(now)),
299
+ "corpus_nodes": n_nodes,
300
+ "head": _git_head(),
301
+ "determinism": "synthetic corpus, no RNG",
302
+ "scopes": "阶段三 §5.1 治理三件套 + §5.2 拒答量化(基线测量,不改行为)",
303
+ },
304
+ "g1_correction_propagation": m1_correction_propagation(cg),
305
+ "g2_contradiction_handling": m2_contradiction_handling(cg),
306
+ "g3_expired_recall": m3_expired_recall(cg, now),
307
+ "g4_refusal_f1": m4_refusal_f1(cg),
308
+ "baseline_reference": BASELINE_REF,
309
+ }
310
+ return report, root
311
+ finally:
312
+ if not keep:
313
+ shutil.rmtree(root, ignore_errors=True)
314
+
315
+
316
+ def _git_head():
317
+ try:
318
+ out = subprocess.run(["git", "rev-parse", "--short", "HEAD"],
319
+ capture_output=True, text=True, encoding="utf-8",
320
+ errors="replace", shell=False, timeout=10)
321
+ return (out.stdout or "").strip() or "unknown"
322
+ except Exception: # noqa: BLE001
323
+ return "unknown"
324
+
325
+
326
+ def main(argv=None):
327
+ ap = argparse.ArgumentParser(description="治理四指标只读评估器")
328
+ ap.add_argument("--out", default=os.path.join("data", "external", "eval_results",
329
+ "governance_baseline.json"))
330
+ ap.add_argument("--keep", action="store_true", help="保留临时库目录(调试用)")
331
+ args = ap.parse_args(argv)
332
+ report, root = run(keep=args.keep)
333
+ out = os.path.abspath(args.out)
334
+ os.makedirs(os.path.dirname(out), exist_ok=True)
335
+ with open(out, "w", encoding="utf-8") as f:
336
+ json.dump(report, f, ensure_ascii=False, indent=2)
337
+ print(json.dumps({k: v.get("value") for k, v in report.items()
338
+ if isinstance(v, dict) and "value" in v},
339
+ ensure_ascii=False, indent=2))
340
+ print("baseline ->", out, "| temp root:", root)
341
+ vals = [report[g]["value"] for g in ("g1_correction_propagation",
342
+ "g2_contradiction_handling",
343
+ "g3_expired_recall", "g4_refusal_f1")]
344
+ return 0 if all(v is not None for v in vals) else 1
345
+
346
+
347
+ if __name__ == "__main__":
348
+ raise SystemExit(main())
@@ -75,6 +75,7 @@ ZH_Q_PROMPT = """从下面英文问题里抽出中文关键词:只输出名词
75
75
 
76
76
 
77
77
  # ------------------------------------------------------------------ LLM
78
+ # 生效条件:环境变量 DEEPSEEK_API_KEY 为真值(缺省或空串即抛 RuntimeError「需要 DEEPSEEK_API_KEY」)且 range(retries) 至少迭代一次(retries≥1)时,用 messages、max_tokens 调 llm_chat 并返回 (content, usage);全部重试失败或 retries=0(last 保持 None)时抛 RuntimeError「LLM 调用失败:…」。
78
79
  def _llm(messages, max_tokens=400, retries=3):
79
80
  from md_cg.bench_task_ab_llm import llm_chat
80
81
  key = os.environ.get("DEEPSEEK_API_KEY")
@@ -100,6 +101,7 @@ _DEFAULT_COND = {"observation_position": "会话陈述", "observation_tool": "
100
101
  "existence_constraint": "公开"}
101
102
 
102
103
 
104
+ # 生效条件:p 经 os.path.isfile(p) 判定为普通文件时按 utf-8 打开并返回 json.load(f);os.path.isfile(p) 为假(含目录、不存在路径、空串)时返回 {}。
103
105
  def _load_json(p):
104
106
  if os.path.isfile(p):
105
107
  with open(p, encoding="utf-8") as f:
@@ -107,6 +109,7 @@ def _load_json(p):
107
109
  return {}
108
110
 
109
111
 
112
+ # 生效条件:无参调用即返回 (_load_json(MAN_ZH), _load_json(MAN_Q)),每个元素在模块级常量 MAN_ZH / MAN_Q 指向普通文件时为解析出的 JSON、否则为 {}。
110
113
  def _load_manual():
111
114
  """读入会话模型直接产出的中文层 / 中文查询词(零 API 依赖)。
112
115
 
@@ -119,6 +122,7 @@ def _load_manual():
119
122
  return _load_json(MAN_ZH), _load_json(MAN_Q)
120
123
 
121
124
 
125
+ # 生效条件:z 为真值且 re.search(r"条件=([^;;]+)") 命中,并按 [||] 切分后至少有一段以 ":" / ":" 分为两段且首段 strip 后在模块级常量 _COND_KEYS 中时,返回非空映射 dict;z 为假值、正则未命中或映射为空时返回 None。
122
126
  def _cond_of(z):
123
127
  """从中文层的「条件=观测位置:…|观测工具:…|…」抽条件空间 dict。"""
124
128
  m = re.search(r"条件=([^;;]+)", z or "")
@@ -132,6 +136,7 @@ def _cond_of(z):
132
136
  return out or None
133
137
 
134
138
 
139
+ # 生效条件:模块级常量 CACHE 经 os.path.isfile(CACHE) 判定为普通文件时按 utf-8 打开并返回 json.load(f);否则返回 {}。
135
140
  def _load_cache():
136
141
  if os.path.isfile(CACHE):
137
142
  with open(CACHE, encoding="utf-8") as f:
@@ -139,12 +144,14 @@ def _load_cache():
139
144
  return {}
140
145
 
141
146
 
147
+ # 生效条件:以 c 为输入即 os.makedirs(DIR, exist_ok=True) 后按 utf-8 把 c 以 ensure_ascii=False、indent=1 写入模块级常量 CACHE,无前置校验、无返回值。
142
148
  def _save_cache(c):
143
149
  os.makedirs(DIR, exist_ok=True)
144
150
  with open(CACHE, "w", encoding="utf-8") as f:
145
151
  json.dump(c, f, ensure_ascii=False, indent=1)
146
152
 
147
153
 
154
+ # 生效条件:cache 命中 "L2:"+turn["id"] 时直接返回该缓存值;否则用 ZH_PROMPT 拼 json.dumps({"date": turn.get("date"), "text": turn.get("text")})(键缺失回落 null)调 _llm,依次取 max_tokens=1200、2600,对 content or "" 去代码块与标签后 strip(" ,,、|"),首个非空即 break,把结果写回 cache 并返回(两次皆空则缓存并返回 "")。
148
155
  def zh_layer(turn, cache):
149
156
  """一条 turn → 中文词袋。缓存 key 带版本号:prompt 改版必须失效旧缓存。
150
157
 
@@ -171,6 +178,7 @@ def zh_layer(turn, cache):
171
178
  return s
172
179
 
173
180
 
181
+ # 生效条件:cache 命中 "Q2:"+q["qid"] 时直接返回该缓存值;否则以 ZH_Q_PROMPT+q["question"] 调 _llm(max_tokens=1200),去代码块后 strip(" ,,、") 写入 cache[key],结果为空串时抛 RuntimeError「查询侧关键词为空…」,非空时返回 cache[key]。
174
182
  def zh_question(q, cache):
175
183
  """英文问题 → 中文关键词(与写入侧同源;额度须 ≥1200,否则推理烧空 content)。"""
176
184
  key = "Q2:" + q["qid"]
@@ -186,6 +194,7 @@ def zh_question(q, cache):
186
194
 
187
195
 
188
196
  # ------------------------------------------------------------------ 池
197
+ # 生效条件:模块级常量 POOL 为普通文件时直接返回其中非空行的 json.loads 列表(此时不读 n_q、n_distract、seed);否则以 random.Random(seed)、每组 max(1, n_q//len(GROUPS)) 条(取 q["evidence_turns"][0] 为 gold)采样后追加 rng.sample(rest, min(n_distract, len(rest))) 干扰项,写出 POOL 与 questions.json 并返回 pool。
189
198
  def build_pool(n_q, n_distract, seed=7):
190
199
  """采样:n_q 题(分组覆盖)各取 evidence_turns[0] 为 gold,再取干扰。"""
191
200
  if os.path.isfile(POOL):
@@ -222,12 +231,14 @@ def build_pool(n_q, n_distract, seed=7):
222
231
  return pool
223
232
 
224
233
 
234
+ # 生效条件:DIR 目录下的 questions.json 是 UTF-8 合法 JSON 时,返回 json.load 得到的对象。
225
235
  def load_questions_zh():
226
236
  with open(os.path.join(DIR, "questions.json"), encoding="utf-8") as f:
227
237
  return json.load(f)
228
238
 
229
239
 
230
240
  # ------------------------------------------------------------------ 建库 / 评测
241
+ # 生效条件:以 ec.build_eval_cg(None, root, corpus or POOL, ec.lm_turn_text, "lmezh", calib_of=calib if zh_of 为真值 else None, rebuild=rebuild) 执行并返回其结果(形参 pool 在该调用中未被使用,实际用 corpus or POOL;zh_only 只在内层 calib 中生效,不传给 build_eval_cg)。
231
242
  def build(root, pool, zh_of=None, rebuild=False, zh_only=False, corpus=None):
232
243
  """zh_of: turn_id → 中文层;None = 裸文本库。
233
244
 
@@ -237,6 +248,7 @@ def build(root, pool, zh_of=None, rebuild=False, zh_only=False, corpus=None):
237
248
  上界而非全译库真值。故另设 F 臂(池只含 20 条 gold、正文全中文)隔离该假象,
238
249
  单测中文层自身的可区分性。
239
250
  """
251
+ # 生效条件:作为 build 的内层函数闭包使用 zh_of/zh_only,ctx 未被使用;zh_of 为真值且 zh_of.get(r["id"]) 为真值时 cs = _cond_of(z) or cs,否则 cs = dict(_DEFAULT_COND);zh_only 为真值时返回 (z or ec.lm_turn_text(r), [], cs),否则返回 (ec.lm_turn_text(r) 在 z 为真值时再拼 "\n"+z, [], cs)。
240
252
  def calib(r, ctx=None):
241
253
  z = zh_of.get(r["id"]) if zh_of else None
242
254
  cs = dict(_DEFAULT_COND)
@@ -252,6 +264,7 @@ def build(root, pool, zh_of=None, rebuild=False, zh_only=False, corpus=None):
252
264
  calib_of=calib if zh_of else None, rebuild=rebuild)
253
265
 
254
266
 
267
+ # 生效条件:给定 cg、questions 与 qtext_of 时,对每题用 qtext_of(q) 替换 question 后经 ec.evaluate_group 评测,并按 k 汇总为结果。
255
268
  def eval_arm(cg, questions, qtext_of, k=5):
256
269
  rows = []
257
270
  for q in questions:
@@ -261,6 +274,7 @@ def eval_arm(cg, questions, qtext_of, k=5):
261
274
  return ec.summarize(rows, k=k)
262
275
 
263
276
 
277
+ # 生效条件:argv 由 argparse 解析(--n-q/--n-distract/--k/--build/--rebuild/--workers 等);--build 只产池即返回,否则需外部数据在指定路径就位、缺失即抛异常不静默降级;
264
278
  def main():
265
279
  ap = argparse.ArgumentParser()
266
280
  ap.add_argument("--n-q", type=int, default=20)
@@ -287,6 +301,7 @@ def main():
287
301
  print(f"中文层:本地 {len(man_zh)} 条(查询词本地 {len(man_q)} 条),"
288
302
  f"缓存合计 {len(cache)} 条,模型 {MODEL}(本轮不调用)")
289
303
 
304
+ # 生效条件:对 r 调 zh_layer(r, cache)(cache 为闭包变量)成功时返回 (r["id"], 中文层, None);zh_layer 抛任何异常时返回 (r["id"], "", f"{type(exc).__name__}: {exc}")。
290
305
  def work(r):
291
306
  try:
292
307
  return r["id"], zh_layer(r, cache), None
@@ -393,4 +408,4 @@ def main():
393
408
 
394
409
 
395
410
  if __name__ == "__main__":
396
- sys.exit(main())
411
+ sys.exit(main())
@@ -40,6 +40,7 @@ GROUPS = {
40
40
  POS_GROUPS = ("precise", "temporal", "interference")
41
41
 
42
42
 
43
+ # 生效条件:命令行参数可被解析、GROUPS 中每组题型可按 --n 抽样时,按 --calibrated 选择 ROOT_LC_CAL 或 legacy 库评估并打印,无返回值。
43
44
  def main():
44
45
  ap = argparse.ArgumentParser()
45
46
  ap.add_argument("--n", type=int, default=0, help="每组抽样上限(0=全量)")
@@ -118,4 +119,4 @@ def main():
118
119
 
119
120
 
120
121
  if __name__ == "__main__":
121
- main()
122
+ main()
@@ -89,6 +89,7 @@ KNOWN_DANGLING = {"scene_3_session_10_turn_19"}
89
89
  MAX_DF_FIXED = 5
90
90
 
91
91
 
92
+ # 生效条件:任意 n_pool(含 0 等假值,源码不做校验)下返回 max(MAX_DF_FIXED, int(round(0.05 * n_pool))),结果不小于模块级常量 MAX_DF_FIXED。
92
93
  def max_df_auto(n_pool):
93
94
  """池规模 → edges 的 df 上限。
94
95
 
@@ -100,6 +101,7 @@ def max_df_auto(n_pool):
100
101
  return max(MAX_DF_FIXED, int(round(0.05 * n_pool)))
101
102
 
102
103
 
104
+ # 生效条件:path 指向逐行 JSON 的 UTF-8 文本时,跳过空行并逐行 yield json.loads 结果。
103
105
  def iter_jsonl(path):
104
106
  with open(path, encoding="utf-8") as f:
105
107
  for line in f:
@@ -108,11 +110,13 @@ def iter_jsonl(path):
108
110
  yield json.loads(line)
109
111
 
110
112
 
113
+ # 生效条件:path 能被 open(path, encoding="utf-8") 打开时返回 json.load(f) 的解析结果。
111
114
  def load_json(path):
112
115
  with open(path, encoding="utf-8") as f:
113
116
  return json.load(f)
114
117
 
115
118
 
119
+ # 生效条件:os.path.isdir(d) 为假时直接返回空 dict;为真时按 sorted(os.listdir(d)) 顺序读取后缀为 .json 的分片并 out.update(part)(后者覆盖前者),任一分片不是 dict 时抛 SystemExit,否则返回合并后的 out。
116
120
  def load_chunks(d):
117
121
  """合并目录下全部分片 JSON(后者覆盖前者,便于修订单条)。"""
118
122
  out = {}
@@ -133,6 +137,7 @@ _SPEAKER_RE = re.compile(r"^([A-Za-z][A-Za-z .'\-]{0,24}): ")
133
137
  _DATE_RE = re.compile(r"Data time: (\d{1,2}:\d{2} [AP]M on \w+ \d{1,2} \w+, \d{4})")
134
138
 
135
139
 
140
+ # 生效条件:调用即从 locomo_corpus.jsonl 建 id→记录映射、读 locomo_questions.jsonl,逐题取 q.get("evidence_turns") or [](缺键或假值按空列表)去重排序为证据 turn,text/title 用 c.get(...) or "" 兜假值、speaker/date 由 _SPEAKER_RE.match / _DATE_RE.search 命中才非空;verbose=True 时额外打印含 KNOWN_DANGLING 未登记告警与直接取键 q["qtype"] 的题型统计,verbose=False 时只跳过打印,写 RAW_TURNS/RAW_QUESTIONS 与返回 (rows, questions) 不变。
136
141
  def cmd_dump(verbose=True):
137
142
  """确定性派生:证据 turn 全集 + 题库 → raw_turns/raw_questions(零标注)。"""
138
143
  os.makedirs(ZH_TURNS, exist_ok=True)
@@ -176,6 +181,7 @@ def cmd_dump(verbose=True):
176
181
 
177
182
 
178
183
  # ------------------------------------------------------------------ status
184
+ # 生效条件:verbose=True 时打印写入/查询侧完成度、多余 turn/qid 键与待补项(各截取前 head 项),并对已产出中文层用 bz.parse_zh 检查 identity/time/summary/terms 四项真值(并非五槽),非全真者记入 bad;verbose=False 时不打印、不做该槽位检查,返回值中 bad 恒为 []。
179
185
  def cmd_status(verbose=True, head=12):
180
186
  """产出进度:中文层 / 中文查询的覆盖与缺口(分批产出时用)。"""
181
187
  turns, questions = cmd_dump(verbose=False)
@@ -217,6 +223,7 @@ def cmd_status(verbose=True, head=12):
217
223
 
218
224
 
219
225
  # ------------------------------------------------------------------ prepare
226
+ # 生效条件:miss_t 或 miss_q 任一非空即 raise SystemExit,否则按 sort_key(仅接受 scene_数字_session_数字_turn_数字 形式的 id,否则 raise SystemExit)对 turns 数值序排序,写出 corpus567.jsonl/questions500.jsonl 并返回 (corpus, out_q),其中 answer 与 evidence_turns 为假值时分别落为 "" 与 [],verbose 只控制末尾打印。
220
227
  def cmd_prepare(verbose=True):
221
228
  """合并中文层 → corpus567.jsonl + questions500.jsonl(幂等覆盖)。"""
222
229
  turns, questions = cmd_dump(verbose=False)
@@ -231,6 +238,7 @@ def cmd_prepare(verbose=True):
231
238
  f"先跑 `status` 看缺口。")
232
239
 
233
240
  # 语料:按 (scene, session, turn) 数值序 —— 「承接前一条」需要确定的时间序
241
+ # 生效条件:tid 匹配 ^scene_(\d+)_session_(\d+)_turn_(\d+)$ 时返回三个整数的 tuple(scene, session, turn),否则 raise SystemExit(不做其他容错或回退)。
234
242
  def sort_key(tid):
235
243
  m = re.match(r"scene_(\d+)_session_(\d+)_turn_(\d+)$", tid)
236
244
  if not m:
@@ -275,6 +283,7 @@ def cmd_prepare(verbose=True):
275
283
 
276
284
 
277
285
  # ------------------------------------------------------------------ build
286
+ # 生效条件:max_df 为 None(默认)时用 max_df_auto(len(corpus)) 自动取值,max_df 传出值(含 0 等假值)则直接使用;随后对 bz.ARMS 每臂调 bz.build_arm(corpus, arm, max_df=md, root_base=...) 并返回 {arm["name"]: 建库结果};verbose 形参在该符号源码段内未参与如何分支。
278
287
  def cmd_build(max_df=None, verbose=True):
279
288
  corpus, _ = cmd_prepare(verbose=False)
280
289
  md = max_df if max_df is not None else max_df_auto(len(corpus))
@@ -296,6 +305,7 @@ GATE_RE = re.compile(r"拒答率:([\d.]+)%")
296
305
  LINE_RE = re.compile(r"拒答线(正例 hit@1 题 Top-1 分 p10):([\d.]+)")
297
306
 
298
307
 
308
+ # 生效条件:RUST_BIN 经 os.path.exists 为假时抛 SystemExit;否则以 argv 列表 [RUST_BIN, "--dataset", "lc", "--tag", name, "--lib", lib, "--qfile", qfile](extra 为真值时追加其元素)执行 subprocess.run,返回码非 0 或 stdout 未匹配 ROW_RE 时抛 SystemExit,成功时返回 (got, neg, line),其中 GATE_RE/LINE_RE 未命中时对应值为 None。
299
309
  def run_one(name, lib, qfile, extra=None):
300
310
  """调用 Rust 评测器跑一臂(--dataset lc 的组映射 + 显式 lib/qfile)。
301
311
 
@@ -326,6 +336,7 @@ def run_one(name, lib, qfile, extra=None):
326
336
  (float(line.group(1)) if line else None)
327
337
 
328
338
 
339
+ # 生效条件:对 rows 中每个 (label, got, note) 按 GROUPS 顺序打印 got[g] 的 hit@1/MRR(got 缺任一 GROUPS 键会 KeyError),并对 POS_GROUPS 组按样本数 n 加权算正例 hit@1/MRR;baseline 非 None 且某行 label 等于 baseline 时该行值记为参照,其后 label 不等于 baseline 的行附加 Δpp,无返回值。
329
340
  def print_table(title, rows, baseline=None):
330
341
  """rows: [(label, got, note)];baseline = 参照行 label(算 Δ)。"""
331
342
  print(f"\n== {title} ==")
@@ -355,6 +366,7 @@ def print_table(title, rows, baseline=None):
355
366
  + f"{ov_h1 * 100:>10.1f}%{ov_mrr:>10.3f}{delta}{suffix}")
356
367
 
357
368
 
369
+ # 生效条件:seeds 等于 "sorted" 时给每个 run_one 追加 ["--graph-seeds", "sorted"],seeds 为其它值(含 None)时不追加;先 cmd_build(max_df=max_df) 取各臂库,再对 bz.ARMS 逐臂 run_one,neg 非 None 时把拒答率写入 note,返回 rows 并以 bz.ARMS[0]["name"] 为 baseline 打印主表。
358
370
  def cmd_run(max_df=None, seeds=None):
359
371
  """消融主表(5 组;正例 hit@1/MRR = precise+temporal+interference)。"""
360
372
  roots = cmd_build(max_df=max_df)
@@ -373,6 +385,7 @@ def cmd_run(max_df=None, seeds=None):
373
385
  return rows
374
386
 
375
387
 
388
+ # 生效条件:cmd_build(max_df=max_df) 后取 bz.ARMS[-1]["name"] 对应的库,对同一库分别以索引序(lczh_a4_index,无额外参数)与 --graph-seeds sorted(lczh_a4_sorted)各跑一次 run_one,返回这两行结果并以 baseline="a4/index" 打印对照表。
376
389
  def cmd_seed(max_df=None):
377
390
  """对照:同一个 a4 库,只切换 graph 路种子口径(隔离种子缺陷与边质量)。"""
378
391
  roots = cmd_build(max_df=max_df)
@@ -389,6 +402,7 @@ def cmd_seed(max_df=None):
389
402
  return rows
390
403
 
391
404
 
405
+ # 生效条件:kind 等于 "turn" 时从 RAW_TURNS 读取并按 id/speaker/date/text 打印 rows[start:end](序号自 start+1 起,text 截到 width);kind 为其它任何值时改从 RAW_QUESTIONS 按 qid/qtype/question 打印同一区间,无返回值。
392
406
  def cmd_show(kind, start, end, width=240):
393
407
  """打印 [start, end) 区间的待标注项(分批产出时读原文用)。
394
408
 
@@ -403,6 +417,7 @@ def cmd_show(kind, start, end, width=240):
403
417
  print(f"{i}\t{r['qid']}\t{r['qtype']}\t{r['question'][:width]}")
404
418
 
405
419
 
420
+ # 生效条件:len(argv) > 1 时 cmd 取 argv[1]、否则 cmd 为 "status";遍历 argv 时遇 "--max-df" 取 argv[i+1] 转 int 为 max_df、遇 "--seeds" 取 argv[i+1] 为 seeds;cmd 为 "show" 时调 cmd_show(argv[2], int(argv[3]), int(argv[4])),为 "dump"/"status"/"prepare" 时分别无参转调同名函数,为 "build"/"seed" 时传 max_df=max_df,为 "run" 时传 max_df=max_df 与 seeds=seeds,其余 cmd 值抛 SystemExit("未知子命令")。
406
421
  def main(argv):
407
422
  cmd = argv[1] if len(argv) > 1 else "status"
408
423
  max_df = None
@@ -433,4 +448,4 @@ def main(argv):
433
448
 
434
449
 
435
450
  if __name__ == "__main__":
436
- main(sys.argv)
451
+ main(sys.argv)
@@ -55,6 +55,7 @@ CONFIGS = (
55
55
  REFERENCE = {"lexical": 0.936, "lexical,fuzzy": 0.976}
56
56
 
57
57
 
58
+ # 生效条件:c 含 zh_fields(identity、time、summary、terms、condition)与 text 时,返回按身份/时间/摘要/词/条件/原始陈述拼接的正文。
58
59
  def body_of(c):
59
60
  """正文:与 bench_axis_domain.body_of / bench_zh_mad 的 a0 臂**逐字一致**。
60
61
 
@@ -74,6 +75,7 @@ def body_of(c):
74
75
  ])
75
76
 
76
77
 
78
+ # 生效条件:传入 corpus 时,若 rebuild 为真且 ROOT 是目录则先删除 ROOT;随后以 MdCGOS(ROOT, autoflush=500) 打开,若已有节点数 >= len(corpus)(corpus 为空列表时该式恒真)则直接返回 cg,否则逐条 cg.add(c["id"], body_of(c), layer="knowledge", eval_src="public:locomo-zh-500", verification_basis="data") 并 cg.flush() 后返回 cg;
77
79
  def build_pool(corpus, rebuild=False):
78
80
  """把 567 条灌成灵枢记忆库(layer=knowledge,无 tags)。
79
81
 
@@ -99,6 +101,7 @@ def build_pool(corpus, rebuild=False):
99
101
  return cg
100
102
 
101
103
 
104
+ # 生效条件:无参数;仅当 CORPUS567 与 QUESTIONS500 的 os.path.exists 均为真时才继续执行,否则立即 raise SystemExit;
102
105
  def main():
103
106
  if not os.path.exists(CORPUS567) or not os.path.exists(QUESTIONS500):
104
107
  raise SystemExit(
@@ -142,4 +145,4 @@ def main():
142
145
 
143
146
 
144
147
  if __name__ == "__main__":
145
- main()
148
+ main()
@@ -35,6 +35,7 @@ GROUPS = {
35
35
  POS_GROUPS = ("precise", "temporal", "interference")
36
36
 
37
37
 
38
+ # 生效条件:命令行参数可被解析、GROUPS 中每组题型可按 --n 抽样时,按 --calibrated 选择 ROOT_LM_CAL 或 legacy 库评估并打印,无返回值。
38
39
  def main():
39
40
  ap = argparse.ArgumentParser()
40
41
  ap.add_argument("--n", type=int, default=0, help="每组抽样上限(0=全量)")
@@ -109,4 +110,4 @@ def main():
109
110
 
110
111
 
111
112
  if __name__ == "__main__":
112
- main()
113
+ main()