@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
package/md_cg/corpus.py CHANGED
@@ -84,7 +84,7 @@ def seed(cg, marks=True, layer="knowledge", verification_basis="data"):
84
84
  def reset_root(root, clean=False):
85
85
  """确保测试根存在:保证「重跑 ≡ 首跑」,且上一轮残留节点不会被当成先验。
86
86
 
87
- 默认**不删目录**(对齐本仓已裁决的设计,见 docs/认知图_MD目录方案_v0.1.md
87
+ 默认**不删目录**(对齐本仓已裁决的设计,见 docs/mdcg/认知图_MD目录方案_v0.1.md
88
88
  「幂等性:迁移按 node id 原子覆盖写,可反复执行,**不需要先清空目录**」):
89
89
  `seed()` 用固定 node id 原子覆盖,同一套语料重跑落在同一批文件上,集合封闭。
90
90
 
@@ -61,11 +61,12 @@ PATHS_CAL = PATHS + ("semantic",) # 口径 B:显式启用条件结构路(
61
61
  def unlock_global_cap():
62
62
  """评测口径:解除 GLOBAL_CAP 截断(bench_membench patch_lexical_full 同法)。
63
63
 
64
- 默认 GLOBAL_CAP=500 会把 LIKE 预筛命中按「插入序」截到前 500 条再打分;
65
- 大型英文语料(turn 高度互相似)预筛命中数千条 → 证据 turn 被插入序随机
66
- 截掉,检索质量混入插入序 lottery(sanity:证据原文自检索仅 10% hit@1)。
67
- 评测主口径解除之,只测排序质量。mdcg 与 mdcos 各持一份 from-import 值,
68
- 必须双改(test_p43_pooling 先例)。
64
+ 【2026-09-16 后语义更新】截断依据已由「插入序」改为「相关度」(`mdcg
65
+ .cut_by_relevance` 与 `mdcos._lexical` 均先全量打分再排序截断,cap 值仍
66
+ 500)——插入序 lottery 已消除。评测仍解除 cap 的理由只剩一条:避免
67
+ 「按相关度截掉尾部」在长语料上压低召回天花板(相近似 turn 数千条时,
68
+ 排 501 名之后仍可能是证据)——评测要测**排序质量**而非截断策略。
69
+ mdcg 与 mdcos 各持一份 from-import 值,必须双改(test_p43_pooling 先例)。
69
70
  """
70
71
  import md_cg.mdcg as m
71
72
  import md_cg.mdcos as mo
@@ -9,12 +9,12 @@
9
9
  · `docs/白箱智能系列·第五篇:174-180`
10
10
  「把经历兑换成结构…整理完之后记忆库变小了,但信息量反而更可用——
11
11
  噪音被扔掉了,骨架被留下。」
12
- · AEIS 工具表 `docs/tool_table_v0.3.0.md:15-19`
12
+ · AEIS 工具表 `docs/mdcg/tool_table_v0.3.0.md:15-19`
13
13
  `prefeed`(H1 新奇检测 → 高新奇输入当场强化编码)、
14
14
  `pattern_separation`(H3 扫描相似节点对)、
15
15
  `nightly_cleanup`(知识层夜间整理、无边孤岛降级)。
16
16
  本模块 = 这三件事的**写入侧前置版**:不等夜间整理,写之前就裁决。
17
- · `docs/智能的公理化基石.md:758-763` —— **诚实边界**
17
+ · `docs/theory/智能的公理化基石.md:758-763` —— **诚实边界**
18
18
  「信息差与热力学熵之间只能进行结构类比,不应宣称数学同构」。
19
19
  故本模块一律称「自信息代理 / 惊奇度」,**不称香农熵**,也不做熵的物理断言。
20
20
 
@@ -47,7 +47,7 @@ import math
47
47
  import os
48
48
  import time
49
49
 
50
- from . import nodefile
50
+ from . import lifecycle, nodefile
51
51
  from .fsutil import append_jsonl, atomic_write, read_jsonl
52
52
  from .mdcg import bigrams
53
53
 
@@ -256,11 +256,19 @@ def reinforce(cg, node_id, delta=0.05):
256
256
  fm["protected"] = True
257
257
  fm["protection_reason"] = (f"importance={imp:.2f}≥{PROTECT_IMPORTANCE}"
258
258
  f"(重复强化)")
259
+ # ② 显式状态机收口(2026-09-16):MERGE 的语义是「又一次见到」= **重新激活**
260
+ # 信号——已降权(demoted)/已定型(converged)的节点经状态机**逐级回升**到
261
+ # active(archived→active 亦合法,归档节点被再次见到即恢复参与);active 为
262
+ # 幂等 no-op(不写字段、不留痕)。protected 只豁免**降级**,回升不受限。
263
+ lifecycle.stamp(fm, "active", reason="MERGE 重复强化(回升)",
264
+ actor="forgetting:reinforce")
259
265
  cg._write_node(node_id, os.path.join(cg.root, node["path"]),
260
266
  fm, node.get("content") or "")
261
267
  e = ((getattr(cg, "index", None) or {}).get("nodes") or {}).get(node_id)
262
268
  if e is not None:
263
269
  e["importance"] = imp
270
+ if fm.get(lifecycle.STATE_FIELD):
271
+ e[lifecycle.STATE_FIELD] = fm[lifecycle.STATE_FIELD]
264
272
  if fm.get("protected"):
265
273
  e["protected"] = True
266
274
  e["protection_reason"] = fm["protection_reason"]
package/md_cg/fsutil.py CHANGED
@@ -96,12 +96,18 @@ class FileLock:
96
96
 
97
97
  超时后放弃并放行(best-effort):写被拒绝的代价大于一次竞态——这与
98
98
  deja-vu 对 usage 日志锁的取舍一致("a racing write beats a lost injection")。
99
+
100
+ strict=True 反转该取舍:超时抛 TimeoutError 而非放行。适用于**不能丢的写**
101
+ (审核队列 inbox/decisions、裁决记录)——锁竞争失败时显式报错让调用方重试,
102
+ 好过静默放行后退化为无锁并发(丢一条提案/裁决比让写入者等一下代价大)。
99
103
  """
100
104
 
101
- def __init__(self, path: str, timeout: float = 10.0, poll: float = 0.01):
105
+ def __init__(self, path: str, timeout: float = 10.0, poll: float = 0.01,
106
+ strict: bool = False):
102
107
  self.path = path + ".lock"
103
108
  self.timeout = timeout
104
109
  self.poll = poll
110
+ self.strict = strict
105
111
  self._f = None
106
112
  self.acquired = False
107
113
 
@@ -122,6 +128,9 @@ class FileLock:
122
128
  if e.errno not in (errno.EACCES, errno.EAGAIN, errno.EDEADLK):
123
129
  raise
124
130
  if time.time() > deadline:
131
+ if self.strict:
132
+ raise TimeoutError(
133
+ f"FileLock 超时未获锁(strict):{self.path}")
125
134
  return self # 放行,不阻断写路径
126
135
  time.sleep(self.poll)
127
136
 
@@ -193,6 +202,59 @@ def read_jsonl(path: str):
193
202
  continue
194
203
 
195
204
 
205
+ _COUNT_CACHE = {} # abspath -> (bytes_scanned, mtime_ns, lines)
206
+
207
+
208
+ def count_jsonl(path: str, chunk: int = 1 << 20) -> int:
209
+ """数 append-only 日志的行数——**流式计数、不物化**(内存 O(1))。
210
+
211
+ 存在的唯一理由:`len(list(read_jsonl(p)))` 是**危险的默认写法**。它把整份
212
+ 日志解析成对象列表,日志一长就是灾难——本机实测(2026-09-16):审计日志
213
+ 4.3 GB,一次 health/盘点调用解析速率 ~1 MB/s、RSS 涨到 5 GB+ 仍在涨、
214
+ 数十分钟不返回;调用方超时重试又在别的进程里再排一次队,最终把整条 MCP
215
+ 通道堵死(一个只读的「体检」把服务打死,代价与收益完全不成比例)。
216
+ 这里只数字节里的换行:不解析、不驻留,速度只受磁盘限制;进程内按
217
+ (已扫字节数, mtime) 缓存,文件只增长时只扫新增字节(O(增量))。
218
+
219
+ 语义边界(诚实声明):数的是**换行符**,不是 JSON 记录——
220
+ ① 完整写入的日志(`append_jsonl` 每条尾带 `\\n`)换行数 == 记录数,与
221
+ `len(list(read_jsonl(p)))` 逐位相等(test_health_scale ⑦ 守卫);
222
+ ② 末尾**未终止的半截行**不计入(下界,最多差 1 行;见 append_jsonl 的
223
+ ends_mid_line 修补分支——并发交错被杀的进程会留下这种尾巴);
224
+ ③ 非法 JSON 行计入行数而 `read_jsonl` 会跳过——健康度是量级指标,不做
225
+ 逐行校验,逐行解析正是上面那场事故的根因。
226
+ """
227
+ key = os.path.abspath(path or "")
228
+ try:
229
+ st = os.stat(key)
230
+ except OSError:
231
+ return 0
232
+ start, total = 0, 0
233
+ prev = _COUNT_CACHE.get(key)
234
+ if prev:
235
+ p_scan, p_mtime, p_count = prev
236
+ if p_scan == st.st_size and p_mtime == st.st_mtime_ns:
237
+ return p_count # 完全未变:零 IO
238
+ if p_scan < st.st_size and p_mtime != st.st_mtime_ns:
239
+ start, total = p_scan, p_count # 只增长:扫增量
240
+ scanned, lines = start, 0
241
+ try:
242
+ with open(key, "rb") as f:
243
+ if start:
244
+ f.seek(start)
245
+ while True:
246
+ buf = f.read(chunk)
247
+ if not buf:
248
+ break
249
+ scanned += len(buf)
250
+ lines += buf.count(b"\n")
251
+ except OSError:
252
+ return total or 0
253
+ total += lines
254
+ _COUNT_CACHE[key] = (scanned, st.st_mtime_ns, total)
255
+ return total
256
+
257
+
196
258
  class ShardedLog:
197
259
  """每写者独占一个分片的 append-only 日志——不能丢记录时用它。
198
260
 
package/md_cg/identity.py CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  理论出处
5
5
  --------
6
- · 智能论 v3.4 §十三「五大单元」·位置效应(`docs/智能的公理化基石.md:540-556`):
6
+ · 智能论 v3.4 §十三「五大单元」·位置效应(`docs/theory/智能的公理化基石.md:540-556`):
7
7
 
8
8
  单元 位置效应 核心职责
9
9
  记录单元 全 保存观测、过程、结果和误差
@@ -16,7 +16,7 @@
16
16
  所以「画像」不该只刻画用户,而应识别**每个主体的身份锚点 + 位置 + 特征**,
17
17
  其中也包括智能体自身(self)。
18
18
 
19
- · 扮演论 v3.3(`README.md:231-271`、`docs/tool_table_v0.3.0.md:86-92`):
19
+ · 扮演论 v3.3(`README.md:231-271`、`docs/mdcg/tool_table_v0.3.0.md:86-92`):
20
20
  智能即扮演;角色卡 = 条件空间声明起点;三接口 =
21
21
  memory(历史 → 知识层)/
22
22
  anchor(自我锚点 → SELF 层,不可遗忘)/
package/md_cg/insight.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """洞察条件层(P2 · insight):洞见事件四件套。
3
3
 
4
- 对齐 `docs/tool_table_v0.3.0.md`(insight_record / insight_verify /
5
- insight_report / insight_window)与 `docs/灵枢82工具_功能整理与迁移映射_v0.1.md`
4
+ 对齐 `docs/mdcg/tool_table_v0.3.0.md`(insight_record / insight_verify /
5
+ insight_report / insight_window)与 `docs/mdcg/灵枢82工具_功能整理与迁移映射_v0.1.md`
6
6
  §五 P2 缺口。
7
7
 
8
8
  四件套
@@ -0,0 +1,262 @@
1
+ # -*- coding: utf-8 -*-
2
+ """md_cg · 节点显式生命周期状态机(可学习优点 ②·显式状态机)
3
+
4
+ 出处(pi):
5
+ 运行操作 = 13 个**叶子显式状态机**(`session/types.ts:315-329`),drive 循环按
6
+ `at` 分发;一轮无任何进展即抛 `SessionInvariantError`(`drive.ts:101-104`)
7
+ ——非法迁移在**结构上不可能**,而不是靠调用方自觉。
8
+
9
+ 灵枢现状(本模块要收口的四处散落状态):
10
+ · `writelimit._demote` 用 **tag** `tidy:converged` 记「同构聚合已定型」
11
+ · `forgetting.reinforce` 用 **importance** 升降记「强化 / 弱化」
12
+ · `protect` 用 **fm.protected** 记「不可遗忘」
13
+ · `_evolution/ledger.md` 记账本
14
+ 四种载体各说各话:谁都能把已定型节点再降一次权、把归档节点悄悄覆写成新的,
15
+ 而**没有任何地方校验「这一步迁移合不合法」**。
16
+
17
+ 本模块是该问题的唯一真源:状态集 `STATES` + 合法迁移表 `TRANSITIONS` + 单点裁决
18
+ `check()` + 单点推进 `set_state()`。口径:
19
+
20
+ · 状态落 **frontmatter.lifecycle_state**(节点 md 是唯一真源);索引快照带同名字
21
+ 段 →「免读文件可查」(`_index.json` 即可回答「这节点什么状态」)。
22
+ **为何不叫 `state`**(对交接文档建议集的偏离,取证理由):`state` 在 md_cg 内
23
+ 已被**裁决四态**(ACCEPT/REJECT/DEFER/BLINDSPOT)占用——`audit._verdict` 的返
24
+ 回体、`judge_qualification()` 的返回、`mdcos.search` 结果项、检索结果条目的
25
+ `entry["state"]` 全都用它。若生命周期也叫 `state`,则同一外层结构里
26
+ `entry["state"]`(裁决)与 `entry["frontmatter"]["state"]`(生命周期)并存,
27
+ 人类与 agent 读面必生歧义;本仓既有 `self_state` 即为同一动机的先例。
28
+ 故取 `lifecycle_state`:语义自明、零歧义。
29
+ · 存量节点**缺 state 即视为 active**(`state_of` 的缺省),故不回填也能工作;
30
+ `backfill()` 提供一次性回填(幂等、可 dry-run)。
31
+ · **受保护节点豁免降级**:`protected`/`immutable` 节点不接受降级迁移,需
32
+ `override=True` 显式放行——「保护 = 不可遗忘」在状态层的落点。
33
+ · 非法迁移**拒绝**(负路由):`check()` 返回 `(ok, code, reason)`,`set_state()`
34
+ 返回 `ok=False` + 机器可读 `error` 而不抛;写路径要硬拒用 `require_transition()`。
35
+ · 永不删除:`archived` 只是状态,节点文件仍在(可逆、可审计)。
36
+ · 幂等:同状态迁移是 no-op(不写盘、不留痕)——重复发起不会把历史刷爆。
37
+
38
+ 状态语义(建议集,逐级降级 / 逐级回升):
39
+ active 正常:参与检索与写入面
40
+ converged 已定型:同构聚合的成员(writelimit tidy 的落点)
41
+ demoted 已降权:importance 被下调(forgetting 弱化的落点)
42
+ archived 已归档:不再参与默认检索,但文件保留、可显式恢复
43
+ """
44
+ from __future__ import annotations
45
+
46
+ import os
47
+ import time
48
+
49
+ from .fsutil import append_jsonl
50
+
51
+ #: 状态全集。顺序即「降级方向」:active → converged → demoted → archived。
52
+ STATES = ("active", "converged", "demoted", "archived")
53
+ #: frontmatter 字段名(索引快照同名透出)。刻意避开 `state`——见模块 docstring
54
+ #: 「为何不叫 state」:该名已被裁决四态占用,同名会让读面产生歧义。
55
+ STATE_FIELD = "lifecycle_state"
56
+ #: 迁移留痕字段(frontmatter 侧历史,滚动保留最近 HISTORY_KEEP 条)
57
+ HISTORY_FIELD = "state_history"
58
+ HISTORY_KEEP = 20
59
+ #: 状态迁移审计(append-only,与 _forgetting.jsonl / _maintain.jsonl 同风格)
60
+ AUDIT_FILE = "_lifecycle.jsonl"
61
+
62
+ #: 降级序位(用于「受保护豁免降级」的判定)
63
+ _RANK = {"active": 0, "converged": 1, "demoted": 2, "archived": 3}
64
+
65
+ #: 合法迁移表——**逐级降级、逐级回升**,不跳级:
66
+ #: active ⇄ converged 定型 / 定型撤销
67
+ #: active → demoted 直接弱化(forgetting 降权)
68
+ #: converged → demoted 定型后再弱化
69
+ #: demoted ⇄ active 被强化回升 / 再弱化
70
+ #: demoted → archived 归档
71
+ #: archived → active 显式恢复
72
+ #: 刻意不收录的(即拒绝的):active→archived(跳级归档)、archived→converged /
73
+ #: archived→demoted(跳级回落)、converged→archived(跳级归档)。
74
+ TRANSITIONS = frozenset({
75
+ ("active", "converged"),
76
+ ("active", "demoted"),
77
+ ("converged", "active"),
78
+ ("converged", "demoted"),
79
+ ("demoted", "active"),
80
+ ("demoted", "archived"),
81
+ ("archived", "active"),
82
+ })
83
+
84
+
85
+ class TransitionError(ValueError):
86
+ """非法生命周期迁移(写路径用 `require_transition` 直接抛这个)。"""
87
+
88
+ def __init__(self, src, dst, code, reason):
89
+ super().__init__(f"非法状态迁移 {src}→{dst}({code}):{reason}")
90
+ self.src, self.dst, self.code, self.reason = src, dst, code, reason
91
+
92
+
93
+ # ---------------------------------------------------------------- 纯函数裁决
94
+
95
+ def state_of(fm) -> str:
96
+ """frontmatter → 状态;缺字段或未知值 → "active"(存量兼容,不猜测)。"""
97
+ if not isinstance(fm, dict):
98
+ return "active"
99
+ s = fm.get(STATE_FIELD)
100
+ return s if s in STATES else "active"
101
+
102
+
103
+ def is_downgrade(src: str, dst: str) -> bool:
104
+ """是否向「更低」的状态迁移(active < converged < demoted < archived)。"""
105
+ return _RANK.get(dst, 0) > _RANK.get(src, 0)
106
+
107
+
108
+ def can_transition(src, dst) -> bool:
109
+ """迁移是否合法(含幂等;未知 src 按 active 处理)。"""
110
+ src = src if src in STATES else "active"
111
+ if dst not in STATES:
112
+ return False
113
+ if src == dst:
114
+ return True # 幂等:同状态是 no-op
115
+ return (src, dst) in TRANSITIONS
116
+
117
+
118
+ def check(src, dst, protected: bool = False, override: bool = False):
119
+ """迁移合法性裁决 → `(ok, code, reason)`。**唯一裁决点**(纯函数,无 IO)。
120
+
121
+ code 取值:`ok` / `noop`(同状态,不写盘)/ `unknown_state` /
122
+ `illegal_transition` / `protected`。
123
+ """
124
+ src = src if src in STATES else "active"
125
+ if dst not in STATES:
126
+ return False, "unknown_state", f"未知状态 {dst!r}(允许:{STATES})"
127
+ if src == dst:
128
+ return True, "noop", "状态未变(幂等,不写盘)"
129
+ if (src, dst) not in TRANSITIONS:
130
+ legal = "、".join(f"{a}→{b}" for a, b in sorted(TRANSITIONS))
131
+ return False, "illegal_transition", (
132
+ f"{src}→{dst} 不在合法迁移表内(允许:{legal})——"
133
+ "降级与回升都须逐级,跨级迁移一律拒绝")
134
+ if is_downgrade(src, dst) and protected and not override:
135
+ return False, "protected", (
136
+ f"受保护节点不接受降级迁移 {src}→{dst}(保护 = 不可遗忘);"
137
+ "确需降级请显式 override=True")
138
+ return True, "ok", f"{src}→{dst} 合法"
139
+
140
+
141
+ def require_transition(src, dst, protected: bool = False, override: bool = False):
142
+ """合法则返回 code,非法则抛 `TransitionError`(写路径的硬拒入口)。"""
143
+ ok, code, why = check(src, dst, protected=protected, override=override)
144
+ if not ok:
145
+ raise TransitionError(src if src in STATES else "active", dst, code, why)
146
+ return code
147
+
148
+
149
+ def stamp(fm: dict, dst: str, reason: str = None, actor: str = None,
150
+ override: bool = False):
151
+ """在给定 frontmatter 上**就地**推进状态(无 IO)→ `(ok, code, why)`。
152
+
153
+ 与 `set_state()` 共用同一裁决点(`check`),供**需与其他字段同批落盘**的收
154
+ 口点使用——`writelimit` 的降权要一次写盘改 tags/importance/状态,`forgetting`
155
+ 的强化同理;否则状态得单独再写一次盘(同一节点两倍 IO)。
156
+ 合法且非幂等时写入 `STATE_FIELD` 并追加 `HISTORY_FIELD`;失败/幂等不动 fm。
157
+ """
158
+ src = state_of(fm)
159
+ prot = bool(fm.get("protected") or fm.get("immutable"))
160
+ ok, code, why = check(src, dst, protected=prot, override=override)
161
+ if not ok or code == "noop":
162
+ return ok, code, why
163
+ fm[STATE_FIELD] = dst
164
+ hist = list(fm.get(HISTORY_FIELD) or [])
165
+ hist.append({"at": time.time(), "from": src, "to": dst,
166
+ "reason": reason, "actor": actor})
167
+ fm[HISTORY_FIELD] = hist[-HISTORY_KEEP:]
168
+ return True, code, why
169
+
170
+
171
+ # ---------------------------------------------------------------- 索引同步
172
+
173
+ def _sync_index(cg, node_id, fm) -> None:
174
+ """把 state 同步进索引快照(免读文件可查);无索引实现时静默跳过。
175
+
176
+ 只同步 `state` 一个键:迁移历史已在 frontmatter 与 `_lifecycle.jsonl` 留痕,
177
+ 放进索引会让快照无谓膨胀。
178
+ """
179
+ idx = getattr(cg, "index", None)
180
+ if not isinstance(idx, dict):
181
+ return
182
+ e = (idx.get("nodes") or {}).get(node_id)
183
+ if e is None:
184
+ return
185
+ e[STATE_FIELD] = fm.get(STATE_FIELD)
186
+ dirty = getattr(cg, "_dirty", None)
187
+ if isinstance(dirty, dict):
188
+ dirty[node_id] = e
189
+ flush = getattr(cg, "flush", None)
190
+ if callable(flush):
191
+ flush()
192
+
193
+
194
+ # ---------------------------------------------------------------- 推进 / 回填
195
+
196
+ def set_state(cg, node_id: str, dst: str, reason: str = None,
197
+ actor: str = None, override: bool = False) -> dict:
198
+ """推进一个节点的生命周期状态(**唯一推进入口**)。
199
+
200
+ 非法迁移不走异常而是返回 `{"ok": False, "error": <code>, ...}`(负路由:
201
+ 调用方自行决定降级处理;要硬拒请用 `require_transition`)。幂等迁移返回
202
+ `changed=False` 且不写盘。返回体始终带 `from` / `to` / `code` 便于审计。
203
+ """
204
+ node = cg.get(node_id)
205
+ if not node:
206
+ return {"ok": False, "error": "node_not_found", "node_id": node_id}
207
+ fm = dict(node.get("frontmatter") or {})
208
+ src = state_of(fm)
209
+ ok, code, why = stamp(fm, dst, reason=reason, actor=actor, override=override)
210
+ base = {"node_id": node_id, "from": src, "to": dst, "code": code}
211
+ if not ok:
212
+ return {**base, "ok": False, "error": code, "reason": why}
213
+ if code == "noop":
214
+ return {**base, "ok": True, "changed": False, "reason": why}
215
+ at = fm[HISTORY_FIELD][-1]["at"]
216
+ cg._write_node(node_id, os.path.join(cg.root, node["path"]), fm,
217
+ node.get("content") or "")
218
+ _sync_index(cg, node_id, fm)
219
+ try:
220
+ append_jsonl(os.path.join(cg.root, AUDIT_FILE), {
221
+ "t": at, "action": "set_state", "node_id": node_id,
222
+ "from": src, "to": dst, "reason": reason, "actor": actor,
223
+ "override": bool(override)})
224
+ except Exception: # 审计失败不阻断状态推进
225
+ pass
226
+ return {**base, "ok": True, "changed": True, "reason": why, "at": at}
227
+
228
+
229
+ def backfill(cg, apply: bool = False, limit: int = 5000) -> dict:
230
+ """存量回填:把缺 `state` 的节点显式补成 `active`(幂等)。
231
+
232
+ 缺字段本就按 active 解释(`state_of`),故回填**不是让功能工作的前提**,
233
+ 而是把「缺省」变成「显式」——索引/审计里从此可直接读到状态。
234
+ `apply=False`(默认)只盘点。写不动(文件取不回)的节点进 `failed` 如实上报。
235
+ """
236
+ nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
237
+ missing = []
238
+ for nid, e in list(nodes.items()):
239
+ if len(missing) >= limit:
240
+ break
241
+ if (e or {}).get(STATE_FIELD) in STATES:
242
+ continue
243
+ missing.append(nid)
244
+ missing.sort()
245
+ if not apply:
246
+ return {"ok": True, "dry_run": True, "scanned": len(nodes),
247
+ "missing": len(missing), "planned": missing[:50]}
248
+ done, failed = [], []
249
+ for nid in missing:
250
+ node = cg.get(nid)
251
+ if not node:
252
+ failed.append(nid)
253
+ continue
254
+ fm = dict(node.get("frontmatter") or {})
255
+ fm[STATE_FIELD] = state_of(fm) # 缺省 active → 显式落盘
256
+ cg._write_node(nid, os.path.join(cg.root, node["path"]), fm,
257
+ node.get("content") or "")
258
+ _sync_index(cg, nid, fm)
259
+ done.append(nid)
260
+ return {"ok": True, "dry_run": False, "scanned": len(nodes),
261
+ "missing": len(missing), "backfilled": len(done),
262
+ "planned": done[:50], "failed": failed[:20]}