@furongjun1999/dsh-memory 0.4.7 → 0.4.8

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 (172) hide show
  1. package/README.md +92 -37
  2. package/codebuddy/CODEBUDDY.md +195 -185
  3. 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 -0
  4. package/docs/README.md +111 -0
  5. package/docs/discipline/harnesses.yaml +215 -152
  6. package/docs/discipline/templates/full.md.tmpl +60 -58
  7. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
  8. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
  9. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
  10. package/docs/experiments/m4-role-probe/census.py +86 -0
  11. package/docs/experiments/m4-role-probe/probe.py +89 -0
  12. package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
  13. package/docs/experiments/mapped_confidence/post_check.py +75 -0
  14. package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
  15. package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
  16. 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 +14 -2
  17. package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +9 -9
  18. package/docs/{ → mdcg/}/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 +1 -1
  19. 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 +261 -0
  20. 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 +1 -1
  21. 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 +148 -0
  22. 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 +2 -2
  23. package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
  24. package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
  25. 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 +1 -1
  26. 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 +4 -4
  27. package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
  28. 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 -0
  29. package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
  30. 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 -0
  31. 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 -0
  32. 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 -0
  33. 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 -0
  34. 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
  35. 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 +1 -1
  36. 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 -0
  37. 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 -0
  38. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
  39. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -0
  40. 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 +34 -6
  41. package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
  42. package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
  43. package/dsh/README.md +1 -1
  44. package/lib/hooks.d.ts +5 -0
  45. package/lib/hooks.js +10 -1
  46. package/lib/lib/prompt_safety.d.ts +48 -0
  47. package/lib/lib/prompt_safety.js +62 -0
  48. package/md_cg/audit.py +76 -2
  49. package/md_cg/bench_membench.py +10 -5
  50. package/md_cg/bench_progressive.py +48 -9
  51. package/md_cg/branches.py +275 -0
  52. package/md_cg/ccgc.py +884 -0
  53. package/md_cg/conformance.py +662 -0
  54. package/md_cg/consistency.py +1 -1
  55. package/md_cg/corpus.py +1 -1
  56. package/md_cg/eval_common.py +6 -5
  57. package/md_cg/forgetting.py +11 -3
  58. package/md_cg/fsutil.py +63 -1
  59. package/md_cg/identity.py +2 -2
  60. package/md_cg/insight.py +2 -2
  61. package/md_cg/lifecycle.py +262 -0
  62. package/md_cg/mcp_server.py +547 -140
  63. package/md_cg/mdcg.py +206 -18
  64. package/md_cg/mdcos.py +512 -50
  65. package/md_cg/metacognition.py +2 -2
  66. package/md_cg/mreview/__init__.py +25 -0
  67. package/md_cg/mreview/__main__.py +107 -0
  68. package/md_cg/mreview/bundle.py +170 -0
  69. package/md_cg/mreview/candidates.py +253 -0
  70. package/md_cg/mreview/govern.py +674 -0
  71. package/md_cg/mreview/locate.py +905 -0
  72. package/md_cg/mreview/pipeline.py +701 -0
  73. package/md_cg/mreview/rules/duplication.json +21 -0
  74. package/md_cg/mreview/rules/field_coverage.json +54 -0
  75. package/md_cg/mreview/rules/source_license.json +21 -0
  76. package/md_cg/mreview/rules/template_flow.json +21 -0
  77. package/md_cg/mreview/ruleset.py +238 -0
  78. package/md_cg/nodefile.py +38 -0
  79. package/md_cg/predict.py +24 -6
  80. package/md_cg/protect.py +4 -4
  81. package/md_cg/refindex.py +41 -2
  82. package/md_cg/scrub.py +5 -1
  83. package/md_cg/self_state.py +1 -1
  84. package/md_cg/semantic/en_normalizer.py +85 -25
  85. package/md_cg/sustain.py +18 -0
  86. package/md_cg/tasks.py +447 -0
  87. package/md_cg/test_action_derive.py +203 -0
  88. package/md_cg/test_audit_rotate.py +270 -0
  89. package/md_cg/test_branches.py +249 -0
  90. package/md_cg/test_ccg_perturb.py +1 -1
  91. package/md_cg/test_ccgc.py +423 -0
  92. package/md_cg/test_conformance.py +343 -0
  93. package/md_cg/test_en_pipeline.py +24 -0
  94. package/md_cg/test_health_scale.py +173 -0
  95. package/md_cg/test_identity_attribution.py +6 -0
  96. package/md_cg/test_index_durability.py +224 -0
  97. package/md_cg/test_lifecycle.py +309 -0
  98. package/md_cg/test_mr_m1.py +679 -0
  99. package/md_cg/test_mr_m2.py +587 -0
  100. package/md_cg/test_mr_m3.py +703 -0
  101. package/md_cg/test_mr_m4.py +485 -0
  102. package/md_cg/test_p1.py +1 -1
  103. package/md_cg/test_p11_consistency.py +1 -1
  104. package/md_cg/test_p12_metacognition.py +2 -2
  105. package/md_cg/test_p16_self_state.py +1 -1
  106. package/md_cg/test_p26_refindex.py +1 -1
  107. package/md_cg/test_p27_docindex.py +22 -2
  108. package/md_cg/test_p28_refcheck.py +1 -1
  109. package/md_cg/test_p29_session_ingest_export.py +1 -1
  110. package/md_cg/test_p2_mcp.py +7 -1
  111. package/md_cg/test_p38_contextualize.py +1 -1
  112. package/md_cg/test_p39_vision_evidence.py +1 -1
  113. package/md_cg/test_p40_refine_worklist.py +1 -1
  114. package/md_cg/test_p41_evolve_patrol.py +1 -1
  115. package/md_cg/test_p42_provenance.py +1 -1
  116. package/md_cg/test_p43_pooling.py +41 -13
  117. package/md_cg/test_read_clip.py +137 -0
  118. package/md_cg/test_review_conformance.py +310 -0
  119. package/md_cg/test_tasks.py +409 -0
  120. package/md_cg/test_tool_face.py +189 -0
  121. package/md_cg/test_twophase.py +286 -0
  122. package/md_cg/test_writepipe.py +210 -0
  123. package/md_cg/theory.py +1 -1
  124. package/md_cg/tokens.py +95 -5
  125. package/md_cg/tool_face.py +250 -0
  126. package/md_cg/twophase.py +221 -0
  127. package/md_cg/units.py +546 -0
  128. package/md_cg/vision_evidence.py +1 -1
  129. package/md_cg/weights.py +1 -1
  130. package/md_cg/whitebox.py +1 -1
  131. package/md_cg/whitebox_kb/data/verify_cache.json +28 -0
  132. package/md_cg/whitebox_kb/data/verify_savings.jsonl +46 -0
  133. package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
  134. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-shm +0 -0
  135. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-wal +0 -0
  136. package/md_cg/writelimit.py +19 -3
  137. package/md_cg/writepipe.py +372 -0
  138. package/package.json +1 -1
  139. package/src/hooks.ts +10 -1
  140. package/src/lib/prompt_safety.ts +62 -0
  141. package/zcode/AGENTS.md +195 -185
  142. package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
  143. /package/docs/{AGI → 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" +0 -0
  144. /package/docs/{AGI → 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" +0 -0
  145. /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" +0 -0
  146. /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" +0 -0
  147. /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
  148. /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
  149. /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
  150. /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
  151. /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
  152. /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
  153. /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
  154. /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
  155. /package/docs/{ → mdcg/}/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" +0 -0
  156. /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" +0 -0
  157. /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
  158. /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
  159. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
  160. /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" +0 -0
  161. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
  162. /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" +0 -0
  163. /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
  164. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
  165. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
  166. /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
  167. /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
  168. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
  169. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
  170. /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
  171. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
  172. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
@@ -91,15 +91,18 @@ def exp_locomo():
91
91
  print("[progressive] 实验一 locomo-zh-500 · 语料 %d / 题 %d"
92
92
  % (len(corpus), len(questions)))
93
93
 
94
+ pool_sizes = [] # G1 各阶段候选池大小(统计面)
95
+
94
96
  def rank_fn(used):
95
- return [(nid, 0.0) for nid in
96
- full_rank(frozenset(used), docs)[:20]]
97
+ ranked = full_rank(frozenset(used), docs)[:20]
98
+ pool_sizes.append(len(ranked))
99
+ return [(nid, 0.0) for nid in ranked]
97
100
 
98
101
  st_g0 = {"h1": 0, "h5": 0, "h10": 0, "rr": 0.0}
99
102
  st_g2 = dict(st_g0)
100
103
  st_i = dict(st_g0)
101
104
  st_f = dict(st_g0)
102
- steps_sum = conv = n = 0
105
+ steps_sum = conv = n = fa10_sum = 0
103
106
  for q in questions:
104
107
  ev = set(q.get("evidence_turns") or [])
105
108
  qa = [w for w in zh_map_en(str(q.get("question") or ""),
@@ -117,8 +120,11 @@ def exp_locomo():
117
120
  ratio=0.5, k=10, max_stages=4)
118
121
  _agg(rank_of(ev, full_rank(frozenset(tr["stages"][0]["used"]),
119
122
  docs)), st_i)
120
- _agg(rank_of(ev, full_rank(frozenset(tr["stages"][-1]["used"]),
121
- docs)), st_f)
123
+ fin_all = full_rank(frozenset(tr["stages"][-1]["used"]), docs)
124
+ _agg(rank_of(ev, fin_all), st_f)
125
+ # 保守 false_accept 口径:final top10 内非 gold 条数(本语料无资格
126
+ # 判定面,非 gold 全计为假接受——上界,见打印行口径注明)
127
+ fa10_sum += sum(1 for nid in fin_all[:10] if nid not in ev)
122
128
  steps_sum += len(tr["added"])
123
129
  conv += 1 if (tr["converged"] or len(tr["added"]) < 3) else 0
124
130
 
@@ -130,7 +136,16 @@ def exp_locomo():
130
136
  print(" refinement: 平均加条件数=%.2f 一步收敛率=%.1f%% (n=%d, %.0fs)"
131
137
  % (steps_sum / max(n, 1), conv * 100.0 / max(n, 1), n,
132
138
  time.time() - t0))
133
- return {"g0": st_g0, "g2": st_g2, "g1i": st_i, "g1f": st_f, "n": n}
139
+ print(" false_accept(保守口径)=%.2f%% =G1 final top10 非 gold 平均占比"
140
+ "(上界:本语料无 CCG 注释面,非 gold 全计为假接受)"
141
+ % (fa10_sum * 10.0 / max(n, 1)))
142
+ print(" 候选池: G1 各阶段平均 top 池大小=%.1f (k=20 上限)"
143
+ % (sum(pool_sizes) / max(len(pool_sizes), 1)))
144
+ print(" 四态分布: 本实验为原子面(语料无 CCG 注释,judge_qualification "
145
+ "将全判 BLINDSPOT)——资格判定不适用,不硬造数字;四态实证见实验二")
146
+ return {"g0": st_g0, "g2": st_g2, "g1i": st_i, "g1f": st_f, "n": n,
147
+ "fa10_conservative": fa10_sum * 10.0 / max(n, 1),
148
+ "avg_pool": sum(pool_sizes) / max(len(pool_sizes), 1)}
134
149
 
135
150
 
136
151
  def exp_controlled():
@@ -179,13 +194,32 @@ def exp_controlled():
179
194
  out.append(nid)
180
195
  return out
181
196
 
197
+ def state_dist(res):
198
+ """渐进循环四态分布(top10 内 judge state 计数,只列非零项)。
199
+
200
+ judge info 直接复用 search_rrf(judge=True) 注入的
201
+ judge_qualification 产物(结果第三元),不重复判定。
202
+ """
203
+ d = {"ACCEPT": 0, "DEFER": 0, "REJECT": 0, "BLINDSPOT": 0,
204
+ "无judge": 0}
205
+ for r in res[:10]:
206
+ st = (r[2] or {}).get("state") if len(r) > 2 else None
207
+ d[st if st in d else "无judge"] += 1
208
+ return {k: v for k, v in d.items() if v}
209
+
210
+ def fa_rate(res, fa):
211
+ return 100.0 * len(fa) / max(1, min(10, len(res)))
212
+
182
213
  # 宽检索(不加任何条件;judgeinfo 直接取 results 第三元)
183
214
  wide_res, _m = cg.search_rrf(QUERY, k=10, judge=True, **KW)
184
215
  wide = [(r[0]["id"], r[1]) for r in wide_res]
185
216
  fa_pre = false_accepts(wide_res)
186
217
  top1_pre = wide[0][0] if wide else None
187
- print(" 宽检索 top1=%s top10 内 False ACCEPT=%s"
188
- % (top1_pre, fa_pre or "无"))
218
+ print(" 宽检索 top1=%s top10 内 False ACCEPT=%s(率=%.1f%%)"
219
+ % (top1_pre, fa_pre or "无", fa_rate(wide_res, fa_pre)))
220
+ print(" 四态分布(宽检索 top10)=%s 候选池=%d judge 剔除=%s"
221
+ % (state_dist(wide_res), len(wide_res),
222
+ (_m or {}).get("judge_filtered", 0)))
189
223
 
190
224
  # 渐进:候选间区分性条件词 → 带条件重排
191
225
  POOL = ["我", "喝水", "吃饭", "电脑", "书", "音乐", "出门",
@@ -203,7 +237,12 @@ def exp_controlled():
203
237
  fa_post = false_accepts(refined_res)
204
238
  top1_post = refined_res[0][0]["id"] if refined_res else None
205
239
  print(" 区分性条件=%r → 重排 top1=%s top10 内 "
206
- "False ACCEPT=%s" % (cond, top1_post, fa_post or "无"))
240
+ "False ACCEPT=%s(率=%.1f%%)"
241
+ % (cond, top1_post, fa_post or "无",
242
+ fa_rate(refined_res, fa_post)))
243
+ print(" 四态分布(重排 top10)=%s 候选池=%d judge 剔除=%s"
244
+ % (state_dist(refined_res), len(refined_res),
245
+ (_m2 or {}).get("judge_filtered", 0)))
207
246
  ok1 = top1_post in gold_ids
208
247
  no_new = set(fa_post) <= set(fa_pre)
209
248
  print(" 断言①渐进后 top1∈gold:%s" % ("PASS" if ok1 else "FAIL"))
@@ -0,0 +1,275 @@
1
+ # -*- coding: utf-8 -*-
2
+ """记忆演化分支(Pi 机制移植④,交接文档 §3④「记忆演化 fork」,P0)。
3
+
4
+ pi:会话 = entry 树,任意历史节点可 fork;被放弃旁支由 branch_summary
5
+ 归档定长摘要。灵枢映射(单一演化线 → 假设分支实验场):
6
+
7
+ - fork:把主支节点复制成分支实验副本(fm.branch_id / branched_from),
8
+ 副本 derived_from=[主支]、relation="split_from"——血缘单向可回溯;
9
+ - 分支节点默认从主支检索隐身(_candidates 按 branch 过滤,一处生效
10
+ 全链路穿透)——实验不污染主支(验收:召回主支时分支节点零命中);
11
+ - merge:按 branched_from 溯源回写主支(derived_from=[分支 nid] 追加进
12
+ 主支原链、relation="merged_from"),override 走快照——合并后链完整;
13
+ - discard:校验 BRANCH_MARKS 必填 → 写教训节点(knowledge 层、可检索)
14
+ → 分支文件移入冷区 _branches/<id>/(物理保留、索引摘除、永不删除)。
15
+
16
+ 与 writepipe 同哲学:库层函数收 cg,权限闸保持库层强制——
17
+ fork/merge 走 add 的 require_layer_write 自动覆盖;
18
+ discard 改变库可见性,需 can_admin(principal.require_admin)。
19
+ 生命周期事件走 _audit.jsonl(③ 两段式载体,零新日志格式)。
20
+ """
21
+
22
+ import os
23
+ import re
24
+ import uuid
25
+
26
+ #: discard 的 branch_summary 必填标记(复用 NEG_MEMORY_MARKS 的必填防呆模式:
27
+ #: 「假设 + 结果 + 教训」缺一不收——放弃分支必须留下可复用的教训)。
28
+ BRANCH_MARKS = ("分支假设", "实验结果", "教训")
29
+
30
+ #: 分支文件冷区(LAYERS 之外,_scan_nodes 不扫——天然不进索引与检索)。
31
+ COLD_DIR = "_branches"
32
+
33
+ _BRANCH_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
34
+
35
+
36
+ def branch_tag(branch_id: str) -> str:
37
+ """分支节点的标注 tag(fork 时写入;branch_summary 也带它以便反查)。"""
38
+ return "branch:" + str(branch_id or "").strip().lower()
39
+
40
+
41
+ def branch_node_id(orig: str, branch_id: str) -> str:
42
+ """分支副本 id:`<主支nid>@<branch_id>`(@ 文件名合法、撞名不可能)。"""
43
+ return "%s@%s" % (orig, str(branch_id or "").strip().lower())
44
+
45
+
46
+ def _audit(cg, op: str, **meta) -> None:
47
+ """生命周期留痕;审计失败绝不阻断主流程(G8 同哲学)。"""
48
+ a = getattr(cg, "_audit", None)
49
+ if a is None:
50
+ return
51
+ try:
52
+ a(op, None, **meta)
53
+ except Exception:
54
+ pass
55
+
56
+
57
+ def _iter_branch_nodes(cg, branch_id: str):
58
+ bid = str(branch_id or "").strip().lower()
59
+ return [(k, v) for k, v in (cg.index.get("nodes") or {}).items()
60
+ if v.get("branch_id") == bid]
61
+
62
+
63
+ def _fm_of(rec):
64
+ """get 返回形态兼容:{'frontmatter': fm, 'content': c} 或平铺 fm。"""
65
+ if not rec:
66
+ return {}
67
+ return rec.get("frontmatter") or rec
68
+
69
+
70
+ def fork(cg, node_ids, branch_id=None, note=None) -> dict:
71
+ """把主支节点复制成分支实验副本。
72
+
73
+ 返回 {ok, branch_id, tag, forked:[{from,to}], skipped:[{id,why}]}。
74
+ 幂等:同 (主支节点, branch_id) 的副本已存在则 skipped,不重复复制。
75
+ """
76
+ ids = [str(x).strip() for x in (node_ids or []) if str(x).strip()]
77
+ if not ids:
78
+ return {"ok": False, "error": "node_ids 为空(至少指定一个主支节点)"}
79
+ if branch_id is None:
80
+ branch_id = "br_" + uuid.uuid4().hex[:8]
81
+ branch_id = str(branch_id).strip().lower()
82
+ if not _BRANCH_RE.match(branch_id):
83
+ return {"ok": False, "error": "branch_id 非法(%s):%r"
84
+ % (_BRANCH_RE.pattern, branch_id)}
85
+ forked, skipped = [], []
86
+ for nid in ids:
87
+ if "@" in nid:
88
+ skipped.append({"id": nid, "why": "已是分支节点,不支持再 fork"})
89
+ continue
90
+ rec = cg.get(nid)
91
+ if not rec:
92
+ skipped.append({"id": nid, "why": "主支节点不存在或不可见"})
93
+ continue
94
+ fm = _fm_of(rec)
95
+ bid = branch_node_id(nid, branch_id)
96
+ if cg.get(bid):
97
+ skipped.append({"id": nid, "why": "副本已存在:%s" % bid})
98
+ continue
99
+ cg.add(bid, rec.get("content") or "",
100
+ layer=fm.get("layer") or "knowledge",
101
+ role=fm.get("role"),
102
+ tags=list(fm.get("tags") or []),
103
+ condition_space=fm.get("condition_space"),
104
+ importance=float(fm.get("importance", 0.5)),
105
+ verification_basis=fm.get("verification_basis") or "test",
106
+ non_applicable_conditions=fm.get("non_applicable_conditions"),
107
+ consistency=False,
108
+ derived_from=[nid], relation="split_from",
109
+ branch_id=branch_id, branched_from=nid, branch_note=note,
110
+ actor="branch_fork")
111
+ forked.append({"from": nid, "to": bid})
112
+ _audit(cg, "branch_fork", branch_id=branch_id,
113
+ forked=[f["to"] for f in forked])
114
+ return {"ok": bool(forked), "branch_id": branch_id,
115
+ "tag": branch_tag(branch_id), "forked": forked, "skipped": skipped}
116
+
117
+
118
+ def search(cg, query: str, branch_id: str, **kw):
119
+ """分支内检索:主支 + 本分支可见、其他分支隐身(_candidates branch 过滤)。"""
120
+ return cg.search(query, branch=str(branch_id or "").strip().lower(), **kw)
121
+
122
+
123
+ def rewrite(cg, node_id, content, tags=None, importance=None,
124
+ actor="branch_rewrite", **extra) -> dict:
125
+ """分支实验改写的**唯一正路**(防裸 add 丢分支归属)。
126
+
127
+ 背景:add 是全量重建 fm——裸 add 改写分支节点而不重传 branch_id,
128
+ 节点会在索引里退化成主支(实验内容直接泄漏进主支检索)。本帮手:
129
+ - 只接受在册分支节点(含 @ 且 entry.branch_id 在册);
130
+ - 归属与血缘(branch_id / branched_from / branch_note / derived_from /
131
+ derived_relation)一律从旧 fm 继承,调用方**不可改**(extra 里
132
+ 同名键静默剥离)——「分支上怎么改内容都行,改不了它是谁的实验」;
133
+ - 层/条件空间/验证基底默认继承旧 fm(tags/importance 可显式覆盖);
134
+ - override=True → 旧内容自动快照留痕(③ 快照机制,零新日志)。
135
+ 写权限由 add 的 require_layer_write 库层强制,不在此重复设闸。
136
+ """
137
+ nid = str(node_id or "").strip()
138
+ if "@" not in nid:
139
+ return {"ok": False,
140
+ "error": "非分支节点(须为 <主支>@<branch_id> 形态):%s" % nid}
141
+ e = (cg.index.get("nodes") or {}).get(nid)
142
+ if not e or not e.get("branch_id"):
143
+ return {"ok": False, "error": "分支节点不存在或无分支归属:%s" % nid}
144
+ rec = cg.get(nid)
145
+ fm = _fm_of(rec)
146
+ if isinstance(content, dict) or content is None:
147
+ return {"ok": False, "error": "content 须为字符串"}
148
+ for k in ("branch_id", "branched_from", "branch_note", "derived_from",
149
+ "relation", "merged_from_branch", "override", "consistency"):
150
+ extra.pop(k, None) # 归属/血缘/写法闸不可经 extra 篡改
151
+ cg.add(nid, str(content or ""),
152
+ layer=fm.get("layer") or e.get("layer") or "knowledge",
153
+ tags=list(tags if tags is not None else (fm.get("tags") or [])),
154
+ importance=float(importance if importance is not None
155
+ else (fm.get("importance") or 0.5)),
156
+ condition_space=fm.get("condition_space"),
157
+ verification_basis=fm.get("verification_basis") or "test",
158
+ non_applicable_conditions=fm.get("non_applicable_conditions"),
159
+ override=True, consistency=False,
160
+ derived_from=list(fm.get("derived_from") or []),
161
+ relation=fm.get("derived_relation") or "split_from",
162
+ branch_id=fm.get("branch_id") or e.get("branch_id"),
163
+ branched_from=fm.get("branched_from") or e.get("branched_from"),
164
+ branch_note=fm.get("branch_note"),
165
+ actor=actor, **extra)
166
+ _audit(cg, "branch_rewrite", branch_id=e.get("branch_id"), node=nid)
167
+ return {"ok": True, "node": nid, "branch_id": e.get("branch_id")}
168
+
169
+
170
+ def merge(cg, branch_id, reason=None) -> dict:
171
+ """把分支上的改写按 branched_from 溯源回写主支。
172
+
173
+ - 主支内容 = 分支内容;层/tags/重要性继承主支(实验元数据不带回去);
174
+ - 主支 derived_from 追加分支 nid(原链保留)、relation="merged_from";
175
+ - override=True → 主支旧内容自动快照留痕;
176
+ - 分支节点保留(merge 不自动删——显式两步、可逆、诚实)。
177
+ """
178
+ bid = str(branch_id or "").strip().lower()
179
+ ents = _iter_branch_nodes(cg, bid)
180
+ if not ents:
181
+ return {"ok": False, "error": "分支不存在或无节点:%s" % bid}
182
+ merged, skipped = [], []
183
+ for nid, e in ents:
184
+ orig = e.get("branched_from")
185
+ rec = cg.get(orig) if orig else None
186
+ if not rec:
187
+ skipped.append({"id": nid, "why": "主支节点不存在:%s" % orig})
188
+ continue
189
+ src = cg.get(nid)
190
+ old_fm = _fm_of(rec)
191
+ src_fm = _fm_of(src)
192
+ chain = list(old_fm.get("derived_from") or [])
193
+ if nid not in chain:
194
+ chain.append(nid)
195
+ cg.add(orig, (src or {}).get("content") or "",
196
+ layer=old_fm.get("layer") or e.get("layer") or "knowledge",
197
+ tags=list(old_fm.get("tags") or e.get("tags") or []),
198
+ condition_space=src_fm.get("condition_space"),
199
+ importance=float(old_fm.get("importance", 0.5)),
200
+ verification_basis=old_fm.get("verification_basis") or "test",
201
+ non_applicable_conditions=old_fm.get("non_applicable_conditions"),
202
+ override=True, consistency=False,
203
+ derived_from=chain, relation="merged_from",
204
+ merged_from_branch=bid, actor="branch_merge")
205
+ merged.append({"branch_node": nid, "target": orig})
206
+ _audit(cg, "branch_merge", branch_id=bid,
207
+ merged=[m["target"] for m in merged], reason=reason)
208
+ return {"ok": bool(merged), "branch_id": bid, "merged": merged,
209
+ "skipped": skipped,
210
+ "hint": "分支节点保留:可 branch_search 继续实验,或 discard 冷归档"}
211
+
212
+
213
+ def discard(cg, branch_id, summary: str) -> dict:
214
+ """放弃分支:校验教训必填 → 写 branch_summary 教训节点 → 分支冷归档。
215
+
216
+ - summary 必须含 BRANCH_MARKS 三标记(缺一不收,防「无声放弃」);
217
+ - 教训节点进 knowledge 层、tags 带 branch:<id>——可被正常检索召回;
218
+ - 分支节点文件移入 _branches/<id>/(物理保留不违背永不删除,
219
+ LAYERS 之外故 _scan_nodes 不再扫到——索引与主支检索天然干净);
220
+ - 需 can_admin(改变库可见性属高危操作)。
221
+ """
222
+ p = getattr(cg, "principal", None)
223
+ if p is not None:
224
+ p.require_admin("branch_discard")
225
+ bid = str(branch_id or "").strip().lower()
226
+ missing = [m for m in BRANCH_MARKS if m not in (summary or "")]
227
+ if missing:
228
+ return {"ok": False, "error": "branch_summary 缺必填标记",
229
+ "missing": missing, "required": list(BRANCH_MARKS)}
230
+ ents = _iter_branch_nodes(cg, bid)
231
+ if not ents:
232
+ return {"ok": False, "error": "分支不存在或无节点:%s" % bid}
233
+ body = summary if summary.lstrip().startswith("#") else \
234
+ "# branch_summary:%s\n\n%s" % (bid, summary)
235
+ sid = cg.add("branch_summary_%s" % bid, body, layer="knowledge",
236
+ tags=["branch_summary", branch_tag(bid)],
237
+ verification_basis="test", consistency=False,
238
+ actor="branch_discard")
239
+ cold = os.path.join(cg.root, COLD_DIR, bid)
240
+ os.makedirs(cold, exist_ok=True)
241
+ moved = 0
242
+ for nid, e in ents:
243
+ path = e.get("path")
244
+ if path:
245
+ src = os.path.join(cg.root, str(path).replace("/", os.sep))
246
+ if os.path.exists(src):
247
+ os.replace(src, os.path.join(cold, os.path.basename(src)))
248
+ moved += 1
249
+ (cg.index.get("nodes") or {}).pop(nid, None)
250
+ fl = getattr(cg, "flush", None)
251
+ if fl is not None:
252
+ try:
253
+ fl()
254
+ except Exception:
255
+ pass
256
+ _audit(cg, "branch_discard", branch_id=bid, moved=moved, summary_node=sid)
257
+ return {"ok": True, "branch_id": bid, "moved": moved,
258
+ "summary_node": sid, "cold_dir": "%s/%s" % (COLD_DIR, bid)}
259
+
260
+
261
+ def list_branches(cg) -> dict:
262
+ """按 branch_id 聚合现存分支(节点清单 + 溯源主支)。"""
263
+ groups = {}
264
+ for nid, e in (cg.index.get("nodes") or {}).items():
265
+ bid = e.get("branch_id")
266
+ if not bid:
267
+ continue
268
+ g = groups.setdefault(bid, {"branch_id": bid, "tag": branch_tag(bid),
269
+ "nodes": [], "branched_from": []})
270
+ g["nodes"].append(nid)
271
+ bf = e.get("branched_from")
272
+ if bf and bf not in g["branched_from"]:
273
+ g["branched_from"].append(bf)
274
+ return {"ok": True,
275
+ "branches": sorted(groups.values(), key=lambda g: g["branch_id"])}