@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
@@ -0,0 +1,21 @@
1
+ {
2
+ "spec_version": 1,
3
+ "domain": "duplication",
4
+ "rules": [
5
+ {
6
+ "id": "R-DUP-HASH-GROUP",
7
+ "title": "包内内容指纹重复",
8
+ "severity": "HIGH",
9
+ "matcher": {},
10
+ "mechanical": [{"check": "dup_hash_group", "issue_kind": "dup_content"}],
11
+ "llm": [
12
+ {
13
+ "check": "merge_or_keep",
14
+ "question": "同指纹成员是否应合并为一个节点(保留最新、其余合并计数)?给出可合并子集与理由;若属刻意的多副本(不同 session/分支)则说明保留理由。",
15
+ "expect": "可合并子集 或 保留理由"
16
+ }
17
+ ],
18
+ "remedy": "合并(merge;受保护节点须剔除)"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "spec_version": 1,
3
+ "domain": "field_coverage",
4
+ "rules": [
5
+ {
6
+ "id": "R-ROLE-MISSING",
7
+ "title": "knowledge 节点缺 role 归属",
8
+ "severity": "WARN",
9
+ "matcher": {"layer": ["knowledge"]},
10
+ "mechanical": [{"check": "field_absent", "field": "role", "issue_kind": "missing_field"}],
11
+ "llm": [
12
+ {
13
+ "check": "role_inferable",
14
+ "question": "该节点能否从 writer/tags/正文确定 role(user/agent/tool/system/command 等)?能则给出 role 值,不能则 UNKNOWN。只输出 role 判定,不改写正文。",
15
+ "expect": "role 取值 或 UNKNOWN"
16
+ }
17
+ ],
18
+ "remedy": "按 writer/tags 回填 role(backfill,M4 由 designer 终裁)"
19
+ },
20
+ {
21
+ "id": "R-EVIDENCE-ZERO",
22
+ "title": "knowledge 节点无验证证据计数",
23
+ "severity": "WARN",
24
+ "matcher": {"layer": ["knowledge"]},
25
+ "mechanical": [{"check": "evidence_zero", "issue_kind": "missing_field"}],
26
+ "llm": [
27
+ {
28
+ "check": "evidence_sufficient",
29
+ "question": "verification_basis 与正文四要素是否已自足(不依赖 evidence_count 也能复核)?给出 SUFFICIENT 或 NEEDS_EVIDENCE 与理由。",
30
+ "expect": "SUFFICIENT 或 NEEDS_EVIDENCE"
31
+ }
32
+ ],
33
+ "remedy": "回填 evidence_count 或降权(backfill / demote)"
34
+ },
35
+ {
36
+ "id": "R-BASIS-MISSING",
37
+ "title": "节点缺验证基底(无法判定验证档)",
38
+ "severity": "WARN",
39
+ "matcher": {},
40
+ "mechanical": [{"check": "field_absent", "field": "verification_basis", "issue_kind": "missing_field"}],
41
+ "llm": [],
42
+ "remedy": "补 verification_basis(backfill;逃逸口 other 供『分析过但无法归类』)"
43
+ },
44
+ {
45
+ "id": "R-BASIS-RATIO-PACKAGE",
46
+ "title": "包内基底覆盖率过低(包级对照片段)",
47
+ "severity": "INFO",
48
+ "matcher": {},
49
+ "mechanical": [{"check": "field_ratio_below", "field": "verification_basis", "ratio": 0.9}],
50
+ "llm": [],
51
+ "remedy": "整包回填(backfill,需人工裁决)"
52
+ }
53
+ ]
54
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "spec_version": 1,
3
+ "domain": "source_license",
4
+ "rules": [
5
+ {
6
+ "id": "R-BASIS-LICENSE",
7
+ "title": "赛道与验证基底不相容(来源执照)",
8
+ "severity": "HIGH",
9
+ "matcher": {},
10
+ "mechanical": [{"check": "basis_licensed", "issue_kind": "weak_source"}],
11
+ "llm": [
12
+ {
13
+ "check": "basis_replace",
14
+ "question": "该节点基底与赛道不相容:应替换为相容基底、降级还是移层?给出裁决与理由(允许基底见 evidence 字段)。",
15
+ "expect": "替换基底 或 降权 或 移层"
16
+ }
17
+ ],
18
+ "remedy": "替换基底 / 降权 / 移层(backfill / demote / move_layer)"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "spec_version": 1,
3
+ "domain": "template_flow",
4
+ "rules": [
5
+ {
6
+ "id": "R-TEMPLATE-FLOW",
7
+ "title": "同模板流水(去数字骨架相同)",
8
+ "severity": "INFO",
9
+ "matcher": {},
10
+ "mechanical": [{"check": "template_flow_digits_only", "issue_kind": "template_flow"}],
11
+ "llm": [
12
+ {
13
+ "check": "batch_summary_candidate",
14
+ "question": "这些同模板条目是否应合并为一个『批次汇总』节点(保留可追溯信息与关键差异)?给出结论与理由。",
15
+ "expect": "MERGE_TO_SUMMARY 或 KEEP_AS_IS"
16
+ }
17
+ ],
18
+ "remedy": "合并为批次汇总节点(merge,样本一次裁决整组)"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,238 @@
1
+ # -*- coding: utf-8 -*-
2
+ """记忆评审流水线 · 级 3:D3 规则装配(确定性,零写入)。
3
+
4
+ 真源:docs/记忆评审系统_立项设计与施工交接_20260915.md §3 第 3 级 + §5 D3。
5
+
6
+ **规则全落数据文件**(`rules/*.json`)——新增/调整规则**零代码改动**(Ghidra G2 吸收件:
7
+ 把「规则」与「引擎」解耦,规则库可增删而引擎冻结)。引擎只提供**机械检查器注册表**:
8
+
9
+ ==================== ==========================================================
10
+ 检查器 语义(确定性、纯输入、可复算)
11
+ ==================== ==========================================================
12
+ field_absent 条目字段为空 → 逐条 issue
13
+ evidence_zero evidence_count==0 → 逐条 issue
14
+ field_ratio_below 包级字段覆盖率低于阈值 → 一条包级 issue
15
+ dup_hash_group 包内同 content_hash → 除首条外逐条 issue
16
+ template_flow_digits_only 包内正文骨架相同(只换数字)→ 除首条外逐条 issue
17
+ basis_licensed 来源执照——赛道与验证基底不相容(复用 crosscheck 判据)
18
+ ==================== ==========================================================
19
+
20
+ 机械检查在蜂巢 job **之前**本地跑完:LLM 只看机械无法裁决的语义问题(token 经济 + 可复算)。
21
+ 规则 matcher 语义:`layer` / `tags_any` 表示「包内存在该类条目」→ 规则入包,
22
+ 检查执行时按同一条件**过滤到该类条目**(绝不跨层套用)。
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import collections
27
+ import json
28
+ import os
29
+
30
+ from .. import crosscheck as CC
31
+ from .. import writelimit as WL
32
+
33
+ RULES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "rules")
34
+ SEVERITIES = ("INFO", "WARN", "HIGH")
35
+ _BLANK = (None, "", "None", "[]", "{}")
36
+
37
+
38
+ def _blank(v) -> bool:
39
+ if isinstance(v, (list, tuple, dict)):
40
+ return not v
41
+ return v is None or str(v).strip() in ("", "None")
42
+
43
+
44
+ def _as_int(v) -> int:
45
+ try:
46
+ return int(v)
47
+ except (TypeError, ValueError):
48
+ return 0
49
+
50
+
51
+ def _issue(entry, rule, kind, field, evidence, severity=None) -> dict:
52
+ return {"ref": entry.get("ref"), "node_id": entry.get("node_id"),
53
+ "field": field, "issue_kind": kind, "evidence": evidence,
54
+ "rule_id": rule["id"], "severity": severity or rule.get("severity") or "WARN"}
55
+
56
+
57
+ # ---------------------------- 机械检查器注册表 ----------------------------
58
+
59
+ def _chk_field_absent(entries, spec, rule):
60
+ field = spec.get("field")
61
+ return [_issue(e, rule, spec.get("issue_kind") or "missing_field", field,
62
+ "字段 %s 为空" % field) for e in entries if _blank(e.get(field))]
63
+
64
+
65
+ def _chk_evidence_zero(entries, spec, rule):
66
+ return [_issue(e, rule, spec.get("issue_kind") or "missing_field", "evidence_count",
67
+ "evidence_count=0(无验证证据计数)")
68
+ for e in entries if _as_int(e.get("evidence_count")) == 0]
69
+
70
+
71
+ def _chk_field_ratio_below(entries, spec, rule):
72
+ field, thr = spec.get("field"), float(spec.get("ratio", 1.0))
73
+ ratio = sum(1 for e in entries if not _blank(e.get(field))) / max(len(entries), 1)
74
+ if ratio >= thr:
75
+ return []
76
+ return [{"ref": None, "node_id": None, "field": field, "issue_kind": "package_ratio",
77
+ "evidence": "包内 %s 覆盖率 %.3f < %.3f(%d 条)" % (field, ratio, thr, len(entries)),
78
+ "rule_id": rule["id"], "severity": rule.get("severity") or "INFO"}]
79
+
80
+
81
+ def _chk_dup_hash_group(entries, spec, rule):
82
+ seen, out = {}, []
83
+ for e in entries:
84
+ h = str(e.get("content_hash") or "")
85
+ if _blank(h) or h == "None":
86
+ continue
87
+ if h in seen:
88
+ out.append(_issue(e, rule, "dup_content", "content_hash",
89
+ "同包内与 %s 同指纹 %s" % (seen[h], h)))
90
+ else:
91
+ seen[h] = e.get("ref")
92
+ return out
93
+
94
+
95
+ def _chk_template_flow(entries, spec, rule):
96
+ seen, out = {}, []
97
+ for e in entries:
98
+ sk = WL._skeleton(e.get("excerpt") or "")
99
+ if not sk or len(sk) < WL.MIN_SKELETON:
100
+ continue
101
+ if sk in seen:
102
+ out.append(_issue(e, rule, "template_flow", "content",
103
+ "同模板流水(去数字骨架与 %s 相同):%s" % (seen[sk], sk[:40])))
104
+ else:
105
+ seen[sk] = e.get("ref")
106
+ return out
107
+
108
+
109
+ def _chk_basis_licensed(entries, spec, rule):
110
+ out = []
111
+ for e in entries:
112
+ basis = e.get("verification_basis")
113
+ if _blank(basis):
114
+ continue
115
+ fm = {"layer": e.get("layer"), "tags": e.get("tags") or []}
116
+ track = CC.classify_track(fm, e.get("excerpt") or "")
117
+ if not CC.basis_licensed(track, basis):
118
+ out.append(_issue(e, rule, "weak_source", "verification_basis",
119
+ "赛道 %s(政策 %s)不允许基底 %s;允许 %s"
120
+ % (track, CC.source_policy(track), basis,
121
+ "/".join(CC.allowed_basis(track)))))
122
+ return out
123
+
124
+
125
+ MECH_CHECKS = {
126
+ "field_absent": _chk_field_absent,
127
+ "evidence_zero": _chk_evidence_zero,
128
+ "field_ratio_below": _chk_field_ratio_below,
129
+ "dup_hash_group": _chk_dup_hash_group,
130
+ "template_flow_digits_only": _chk_template_flow,
131
+ "basis_licensed": _chk_basis_licensed,
132
+ }
133
+
134
+
135
+ # ---------------------------- 规则库加载 ----------------------------
136
+
137
+ def load_rules(rules_dir=None) -> dict:
138
+ """读 `rules/*.json`。格式非法/字段缺失/id 重复 → 抛 ValueError(不静默降级)。"""
139
+ d = rules_dir or RULES_DIR
140
+ files = sorted(f for f in os.listdir(d) if f.endswith(".json")) if os.path.isdir(d) else []
141
+ rules, ids = [], {}
142
+ for fn in files:
143
+ with open(os.path.join(d, fn), encoding="utf-8") as f:
144
+ doc = json.load(f)
145
+ for r in doc.get("rules") or []:
146
+ miss = [k for k in ("id", "matcher", "severity") if k not in r]
147
+ if miss:
148
+ raise ValueError("规则库 %s:规则缺字段 %s(%s)" % (fn, miss, r.get("id")))
149
+ if r["severity"] not in SEVERITIES:
150
+ raise ValueError("规则库 %s:severity=%s 非法(%s)" % (fn, r["severity"], r["id"]))
151
+ if r["id"] in ids:
152
+ raise ValueError("规则 id 重复:%s(%s / %s)" % (r["id"], ids[r["id"]], fn))
153
+ ids[r["id"]] = fn
154
+ for spec in list(r.get("mechanical") or []):
155
+ if spec.get("check") not in MECH_CHECKS:
156
+ raise ValueError("规则库 %s:未知机械检查 %s(%s)"
157
+ % (fn, spec.get("check"), r["id"]))
158
+ rules.append(r)
159
+ return {"rules_dir": d, "files": files, "rules": rules, "count": len(rules)}
160
+
161
+
162
+ def _match(rule, ctx) -> tuple:
163
+ """matcher 语义:包内**存在**该类条目即入包(执行时按同条件过滤)。"""
164
+ m = rule.get("matcher") or {}
165
+ if m.get("layer") and not (ctx["layers"] & set(m["layer"])):
166
+ return False, "包内无 %s 层条目" % "/".join(m["layer"])
167
+ if m.get("tags_any") and not (ctx["tag_prefixes"] & set(m["tags_any"])):
168
+ return False, "包内无 %s 标签" % "/".join(m["tags_any"])
169
+ return True, "matcher 命中"
170
+
171
+
172
+ def _scope(entries, rule) -> list:
173
+ m = rule.get("matcher") or {}
174
+ if m.get("layer"):
175
+ want = set(m["layer"])
176
+ return [e for e in entries if str(e.get("layer")) in want]
177
+ return list(entries)
178
+
179
+
180
+ def _as_rules(rules, rules_dir=None) -> list:
181
+ """规则入参防呆:None→读规则库;{"rules":[...]}→取内层;列表→原样。
182
+
183
+ 仿 cg/stg「op 必填」防呆:传错形态时给出明确入口,而非静默 AttributeError。
184
+ """
185
+ if rules is None:
186
+ return load_rules(rules_dir)["rules"]
187
+ if isinstance(rules, dict):
188
+ if "rules" not in rules:
189
+ raise ValueError("规则入参 dict 缺少 rules 键(应为规则列表或 load_rules 结果)")
190
+ return list(rules.get("rules") or [])
191
+ return list(rules)
192
+
193
+
194
+ def assemble(pkg: dict, *, rules=None, rules_dir=None) -> dict:
195
+ """单个待评包 → 规则集 + 机械检查结果 + 待 LLM 检查清单。"""
196
+ rl = _as_rules(rules, rules_dir)
197
+ entries = pkg.get("entries") or []
198
+ ctx = {"layers": {str(e.get("layer") or "") for e in entries},
199
+ "tag_prefixes": {str(t).split(":", 1)[0] for e in entries
200
+ for t in (e.get("tags") or [])}}
201
+ matched, mech, llm = [], [], []
202
+ for rule in rl:
203
+ ok, why = _match(rule, ctx)
204
+ if not ok:
205
+ continue
206
+ scoped = _scope(entries, rule)
207
+ matched.append({"id": rule["id"], "title": rule.get("title"),
208
+ "severity": rule.get("severity"), "why": why,
209
+ "scope": len(scoped), "remedy": rule.get("remedy")})
210
+ for spec in rule.get("mechanical") or []:
211
+ mech += MECH_CHECKS[spec["check"]](scoped, spec, rule)
212
+ for spec in rule.get("llm") or []:
213
+ llm.append({"rule_id": rule["id"], "check": spec.get("check"),
214
+ "question": spec.get("question"),
215
+ "expect": spec.get("expect"),
216
+ "scope_refs": [e.get("ref") for e in scoped][:200]})
217
+ cnt = collections.Counter(i["issue_kind"] for i in mech)
218
+ return {"bundle_id": pkg.get("bundle_id"), "group_kind": pkg.get("group_kind"),
219
+ "group_key": pkg.get("group_key"), "size": len(entries),
220
+ "matched": matched, "mechanical": mech, "llm": llm,
221
+ "mechanical_by_kind": dict(cnt),
222
+ "mechanical_flagged": len({i.get("ref") for i in mech if i.get("ref")})}
223
+
224
+
225
+ def assemble_all(result: dict, *, rules=None, rules_dir=None) -> dict:
226
+ """全部包装配 + 汇总(包级统计供 M2 spec 与 M4 量化复跑)。"""
227
+ rl = _as_rules(rules, rules_dir)
228
+ out = [assemble(p, rules=rl) for p in result.get("bundles") or []]
229
+ kinds = collections.Counter()
230
+ for a in out:
231
+ kinds.update(a["mechanical_by_kind"])
232
+ return {"packages": out, "stats": {
233
+ "bundles": len(out),
234
+ "mechanical_total": sum(len(a["mechanical"]) for a in out),
235
+ "mechanical_by_kind": dict(kinds),
236
+ "llm_checks": sum(len(a["llm"]) for a in out),
237
+ "rules": [r["id"] for r in rl]},
238
+ "bundles_input": result.get("stats")}
package/md_cg/nodefile.py CHANGED
@@ -37,6 +37,7 @@ CCG 要素的语义对应(白箱第 1 篇第 17 章):
37
37
  拿 `observation_position` 单槽加前缀冒充,等于把坐标的一维当成整条生效条件。
38
38
  故 CCG_REQUIRED == CCG_MARKS:6 行缺一不可,缺则补写、或按 q-3 判 BLINDSPOT。
39
39
  """
40
+ import hashlib
40
41
  import json
41
42
  import time
42
43
 
@@ -48,6 +49,27 @@ CCG_MARKS = ("功能名", "生效条件", "子功能", "执行", "验证方式",
48
49
  # 见 `condition_space_text`)——生效条件必须由 condition_space 四槽合成显式声明,
49
50
  # 不存在「常用条件默认省略」的合法情形。缺它即缺证据:补写,或判 BLINDSPOT。
50
51
  CCG_REQUIRED = CCG_MARKS
52
+
53
+ # ---- 裁定 B(使用者 2026-09-16):六要素的契约角色(术语真源) -----------------
54
+ # 六要素不是注释/描述,而是**接口契约**——每行在契约里有一个确定角色:
55
+ # 功能名 = 签名 signature(可执行入口符号)
56
+ # 生效条件 = 前置条件 precondition(由 condition_space 四槽合成,唯一入口)
57
+ # 子功能 = 依赖 dependency
58
+ # 执行 = 调用 invocation
59
+ # 验证方式 = 后置条件 postcondition + test
60
+ # 不适用条件 = 拒绝域 rejection_domain
61
+ # 「生效条件」的契约角色正是 **precondition**——这与其「不可隐含、必须四槽合成」
62
+ # 的既有硬约束同源:缺前置条件的接口无法判定可否调用,故缺参即编译错误。
63
+ # 本常量是术语的**唯一真源**;其余模块(如 ccgc.CONTRACT_ROLES)一律引用本处,
64
+ # 禁止各自再定义一份,防「术语双写法」漂移。
65
+ CCG_CONTRACT_ROLES = {
66
+ "功能名": "签名 signature(可执行入口符号)",
67
+ "生效条件": "前置条件 precondition",
68
+ "子功能": "依赖 dependency",
69
+ "执行": "调用 invocation",
70
+ "验证方式": "后置条件 postcondition + test",
71
+ "不适用条件": "拒绝域 rejection_domain",
72
+ }
51
73
  # 外部验证基底的可取值(frontmatter.verification_basis)
52
74
  #
53
75
  # 分两档(口径:文科宽松、理科严格):
@@ -64,6 +86,22 @@ REPRODUCIBLE_BASIS = ("compiler", "test", "measurement", "formal_proof", "data")
64
86
  CONSISTENCY_BASIS = ("textbook", "public_kb")
65
87
 
66
88
 
89
+ def content_hash(content: str) -> str:
90
+ """节点正文的**内容指纹**(sha256 前 12 位)——全仓唯一实现。
91
+
92
+ 两处共用且必须逐位一致,否则对账/变更探测会永远"匹配成功"而失去意义
93
+ (与 `codeindex.region_hash` 同一纪律:两侧各写一份哈希算法,漂移就悄悄失效):
94
+ ① 索引快照 `_index.json` 的 `content_hash`(变更探测);
95
+ ② 写入两段式(`twophase`)的 intent 载荷指纹——崩溃后靠它回答
96
+ 「那笔写入到底落盘了没有」。
97
+
98
+ 口径:只哈希**正文**,不含 frontmatter——frontmatter 会被写路径正常改写
99
+ (importance / protected / 生命周期状态 / 时间戳),把它算进去则「内容没变、
100
+ 元数据变了」也会判成不匹配,对账会大面积误报 interrupted。
101
+ """
102
+ return hashlib.sha256((content or "").encode("utf-8")).hexdigest()[:12]
103
+
104
+
67
105
  def dumps(frontmatter: dict, content: str) -> str:
68
106
  lines = [_DELIM]
69
107
  for k in sorted(frontmatter):
package/md_cg/predict.py CHANGED
@@ -428,10 +428,18 @@ def predictability(blindspot):
428
428
  def anchor_from_description(cg, description):
429
429
  """盲区描述 → 锚点节点(检索器打分,与 AEIS 的 LIKE→坐标回退同构)。
430
430
 
431
- 只取**首个可用候选**:按标签/层过滤掉推断脚手架(`gap_hint`/`scene`)与
432
- 负记忆(`unresolved`/`rejected`),其余按检索名次顺延。过滤理由见上方常量注释。
433
- 候选全被过滤时返回 None(等价「无锚点」),由调用方按 no_anchor 处理,
434
- 而不是硬凑一个不可起推的节点。
431
+ 择锚规则:候选池内**可起推者优先**——出边非空是路线生成的必要条件,无出边的
432
+ 节点只能靠语义回退硬凑路线,起点语义即失真。**全部不可起推时才降级**为首个合格
433
+ 候选(诚实降级:宁可标注锚点弱,不假装有锚)。
434
+ 动机(实测):同分并列时检索序不可依赖——「正题节点」与其「同名+近义描述」变体
435
+ 词法分可完全相同(各自都为 1.0),纯按检索名次取首位会漂到无出边的近义变体上。
436
+
437
+ 过滤与择锚分两关:先按标签/层过滤掉推断脚手架(`gap_hint`/`scene`)与负记忆
438
+ (`unresolved`/`rejected`),再在剩余候选里按**可起推资格**择优——出边非空者
439
+ 立即采纳,无出边者记为降级候选继续后看,全池无可起推者才降级取首个合格候选。
440
+ 候选全被过滤时返回 None(等价「无锚点」),由调用方按 no_anchor 处理。
441
+ 邻接表不可用(chain 取表异常)时退回「取首个合格候选」——辅助判据故障不应
442
+ 让整条预测链失效(兜底优先于择优)。
435
443
  """
436
444
  q = str(description or "").strip()
437
445
  if not q:
@@ -440,6 +448,12 @@ def anchor_from_description(cg, description):
440
448
  results, _meta = cg.search(q, k=ANCHOR_FETCH_K, record=False, judge=False)
441
449
  except Exception:
442
450
  return None
451
+ try:
452
+ from . import chain
453
+ out_edges = chain.adjacency(cg)
454
+ except Exception:
455
+ out_edges = None
456
+ fallback = None
443
457
  for item in results:
444
458
  nd = item[0] if isinstance(item, (tuple, list)) else item
445
459
  if not isinstance(nd, dict):
@@ -459,8 +473,12 @@ def anchor_from_description(cg, description):
459
473
  # 兜底:fs 派生的 id 可能是相对路径(如 unresolved/bs_x.md),归一为裸节点名
460
474
  if "/" in str(nid) and str(nid).endswith(".md"):
461
475
  nid = os.path.basename(str(nid))[:-3]
462
- return nid
463
- return None
476
+ # 可起推资格:出边非空是路线生成的必要条件;不可起推者只作降级候选。
477
+ if out_edges is None or out_edges.get(nid):
478
+ return nid
479
+ if fallback is None:
480
+ fallback = nid
481
+ return fallback
464
482
 
465
483
 
466
484
  # ---------------------------------------------------------------- 路线生成
package/md_cg/protect.py CHANGED
@@ -3,14 +3,14 @@
3
3
 
4
4
  理论出处(全部来自本仓已有文档,非外部发明):
5
5
 
6
- · `docs/灵枢_自我层定义.md:6`
6
+ · `docs/mdcg/灵枢_自我层定义.md:6`
7
7
  SELF 层 = 身份 + 价值观 + 图接口,**不可遗忘**、跨会话自动加载。
8
8
  任何实例都能改写自我层 = 自我认知可被任意覆写,与「自我」定义冲突。
9
- · `docs/认知图_MD目录方案_v0.1.md:53`
9
+ · `docs/mdcg/认知图_MD目录方案_v0.1.md:53`
10
10
  `anchor/ # 锚点层(不可遗忘,保护)`
11
- · `docs/认知图_MD目录方案_v0.1.md:412-416`
11
+ · `docs/mdcg/认知图_MD目录方案_v0.1.md:412-416`
12
12
  「importance 提升(保护:不可遗忘记入 anchor/ 且**受保护标记**)」
13
- · `docs/tool_table_v0.3.0.md:15`
13
+ · `docs/mdcg/tool_table_v0.3.0.md:15`
14
14
  「importance_hint 可显式提示重要性(**≥0.7 触发不可遗忘保护**)」
15
15
 
16
16
  三层保护(任一命中即受保护):
package/md_cg/refindex.py CHANGED
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """md_cg · 统一 ref 协议 + 索引水位(增量)+ 漂移/悬空巡检
3
3
 
4
- 对照 `docs/认知图_索引与工程规范化_计划_v0.1.md` 的 R3(修 D + 修 F):
4
+ 对照 `docs/mdcg/认知图_索引与工程规范化_计划_v0.1.md` 的 R3(修 D + 修 F):
5
5
 
6
6
  **D 漂移 / 悬空检测(本模块 `check_refs`)**
7
7
  扫描带 `code_ref` / `doc_ref` 的节点,回两类问题:
@@ -598,6 +598,25 @@ def _forget_many(cg, plan, why: str) -> tuple:
598
598
  return done, blocked
599
599
 
600
600
 
601
+ def _drop_ghosts(cg, ghosts) -> list:
602
+ """摘除幽灵条目的索引记录(节点文件已不存在,没有可软删的实体)。
603
+
604
+ 走 `MdCG._unstage`:它顺带落删除记录,保证幽灵不会再次从分片日志里复活。
605
+ 基础层没有该能力时如实返回空列表(不假装成功)。
606
+ """
607
+ drop = getattr(cg, "_unstage", None)
608
+ if not callable(drop):
609
+ return []
610
+ out = []
611
+ for nid in ghosts:
612
+ try:
613
+ drop(nid)
614
+ out.append(nid)
615
+ except Exception: # 单条失败不拖垮整批
616
+ continue
617
+ return out
618
+
619
+
601
620
  def prune_orphans(cg, *, kind: str, root: str, items, dry_run: bool = False,
602
621
  reason: str = "") -> dict:
603
622
  """清退「同 root + 同 path,但已不在本次产出里」的**过期代**节点。
@@ -663,8 +682,23 @@ def prune_dangling(cg, *, only_roots=None, dry_run: bool = False,
663
682
  `check_refs` 只报告不处置(其原话是「悬空需人工处置」),本函数就是那个
664
683
  出口——已删脚本、被搬走的文档留下的残留节点一次清掉,而不是逐条手工
665
684
  `forget`。判定与巡检共用 `probe_ref` 的唯一实现,口径不会打架。
685
+
686
+ 另清**幽灵条目**(ghosts):索引有条目、节点文件却不存在。它们是历史
687
+ 「删除只摘内存索引、不落盘」的遗留——`cg.get` 取不回 → 悬空清退够不着它,
688
+ 而 `check_refs` 走 ledger 会一直报 → dangling 永不归零。判据只用唯一真源
689
+ (节点 .md 不存在即脏索引),与「索引是派生物」的宣言一致;`only_roots`
690
+ 非空时跳过(幽灵条目无 ref,无法归因到某个源大域)。
666
691
  """
667
692
  nodes = (getattr(cg, "index", {}) or {}).get("nodes") or {}
693
+ ghosts = []
694
+ if not only_roots:
695
+ for nid, e in list(nodes.items()):
696
+ tags = (e or {}).get("tags") or []
697
+ if not any(t in ("code", "doc") for t in tags):
698
+ continue
699
+ path = (e or {}).get("path") or ""
700
+ if path and not os.path.exists(os.path.join(cg.root, path)):
701
+ ghosts.append(nid)
668
702
  # 同 prune_orphans:ref 只在节点 frontmatter 里,索引条目里没有,
669
703
  # 必须 cg.get 取回节点再 ref_of(否则恒空、静默不删)。
670
704
  todo = []
@@ -684,7 +718,7 @@ def prune_dangling(cg, *, only_roots=None, dry_run: bool = False,
684
718
  if only_roots and not any(_same_root(ref.get("root"), r) for r in only_roots):
685
719
  continue
686
720
  todo.append((nid, ref))
687
- truncated = len(todo) > max_nodes
721
+ truncated = len(todo) > max_nodes or len(ghosts) > max_nodes
688
722
  plan = []
689
723
  for nid, ref in todo[:max_nodes]:
690
724
  try:
@@ -695,14 +729,19 @@ def prune_dangling(cg, *, only_roots=None, dry_run: bool = False,
695
729
 
696
730
  why = reason or ("源文件已删除,索引节点悬空(回读必然失败),"
697
731
  "清退以消除永不消失的 dangling")
732
+ ghost_plan = sorted(ghosts)[:max_nodes]
698
733
  base = {"scanned": len(nodes), "candidates": len(plan),
734
+ "ghosts": len(ghost_plan),
699
735
  "dry_run": bool(dry_run), "truncated": truncated,
700
736
  "max_nodes": max_nodes}
701
737
  if dry_run:
702
738
  return {**base, "ok": True, "count": len(plan), "pruned": sorted(plan)[:50],
739
+ "ghost_pruned": ghost_plan[:50],
703
740
  "skipped_protected": [], "reason": why}
704
741
  done, blocked = _forget_many(cg, plan, why)
742
+ dropped = _drop_ghosts(cg, ghost_plan)
705
743
  return {**base, "ok": True, "count": len(done), "pruned": done[:50],
744
+ "ghost_pruned": dropped[:50],
706
745
  "skipped_protected": blocked[:20], "reason": why}
707
746
 
708
747
 
package/md_cg/scrub.py CHANGED
@@ -622,7 +622,11 @@ def decontaminate(cg, node_ids=None, *, kinds=None, dry_run: bool = True,
622
622
  fm = _fm_of(cg, nid, None)
623
623
  if str(fm.get("layer") or "") != "contextual":
624
624
  cg._move_layer(nid, "contextual", reason=f"scrub:{kind}")
625
- done.append("demote")
625
+ # 层降级 = 生命周期降级(②):状态同批推进(负路由,失败不抛)
626
+ _st = cg.set_state(nid, "demoted", reason=f"scrub:{kind}",
627
+ actor=actor or "scrub")
628
+ done.append("demote" if _st.get("ok") else
629
+ f"demote_state:{_st.get('error')}")
626
630
  except Exception as exc: # noqa: BLE001
627
631
  done.append(f"demote_failed:{type(exc).__name__}")
628
632
  n_applied += 1
@@ -2,7 +2,7 @@
2
2
  """md_cg · 自我状态层(SELF STATE):薄自我 + 富索引
3
3
 
4
4
  理论约束(本仓已有文档,非外部发明):
5
- · `docs/灵枢_自我层定义.md:6`
5
+ · `docs/mdcg/灵枢_自我层定义.md:6`
6
6
  SELF 层 = 身份 + 价值观 + 图接口,**不可遗忘**、跨会话自动加载。
7
7
  自我层是**薄的**——它不该变成事件仓库。
8
8
  · 智能论 §十一:情绪 = 信息差二阶 d^2D/dt^2;§十:P_gap / P_trust;