@furongjun1999/dsh-memory 0.4.11 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (580) hide show
  1. package/README.md +552 -465
  2. package/codebuddy/CODEBUDDY.md +11 -3
  3. package/codebuddy/README.md +92 -90
  4. package/codebuddy/mcp.json +27 -27
  5. package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -169
  6. package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -144
  7. package/docs/README.md +143 -111
  8. package/docs/discipline/harnesses.yaml +244 -226
  9. package/docs/discipline/templates/full.md.tmpl +61 -61
  10. package/docs/discipline/templates/rules.mdc.tmpl +68 -0
  11. package/docs/discipline/templates/skill.md.tmpl +23 -23
  12. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md +299 -299
  13. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md +239 -239
  14. package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
  15. package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
  16. package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
  17. package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
  18. package/docs/eval//345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md +174 -174
  19. package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
  20. package/docs/eval//346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md +223 -223
  21. package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
  22. package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
  23. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
  24. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
  25. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
  26. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
  27. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
  28. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
  29. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
  30. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
  31. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
  32. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
  33. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
  34. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
  35. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
  36. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
  37. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
  38. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
  39. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
  40. package/docs/{mdcg → hive}//344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md +165 -160
  41. package/docs/hive//344/273/244/347/211/214/350/257/255/344/271/211/344/277/256/346/255/243_/344/273/262/350/243/201/344/275/215_v0.1.md +76 -0
  42. package/docs/{mdcg → hive}//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.5.md +236 -236
  43. package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
  44. package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -43
  45. package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +85 -0
  46. package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +102 -102
  47. package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +47 -0
  48. package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +519 -503
  49. package/docs/hive//350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +85 -85
  50. package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +191 -0
  51. package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +90 -0
  52. package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
  53. package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
  54. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -216
  55. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
  56. package/docs/mdcg/lingshu_tutorial.html +14449 -14449
  57. package/docs/mdcg/release_v0.4.11.md +49 -0
  58. package/docs/mdcg/release_v0.4.5.md +55 -55
  59. package/docs/mdcg/tool_table_v0.3.0.md +117 -117
  60. package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -600
  61. package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
  62. package/docs/mdcg//345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +275 -275
  63. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
  64. package/docs/mdcg//346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +130 -130
  65. package/docs/mdcg//347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md +278 -278
  66. package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +172 -172
  67. package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
  68. package/docs/mdcg//350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +491 -491
  69. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md +340 -340
  70. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +588 -588
  71. package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -176
  72. package/docs/plans//345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +81 -81
  73. package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
  74. package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -242
  75. package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
  76. package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -129
  77. package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -134
  78. package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  79. package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -145
  80. package/docs/theory//347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +144 -144
  81. package/docs/theory//347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +198 -198
  82. package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -221
  83. package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -237
  84. package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
  85. package/docs//345/217/221/345/270/203/344/273/266/345/233/236/346/272/257/350/257/264/346/230/216_v0.1.md +82 -82
  86. package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +28 -2
  87. package/dsh/README.md +82 -82
  88. package/dsh/cordis.yml.example +139 -139
  89. package/dsh/update-lingshu.bat +11 -11
  90. package/lib/bridge.d.ts +9 -0
  91. package/lib/bridge.js +35 -0
  92. package/lib/hooks.js +36 -2
  93. package/lib/index.js +7 -1
  94. package/lib/lib/roleplay_web.js +116 -29
  95. package/lib/lib/token_store.d.ts +7 -1
  96. package/lib/lib/token_store.js +12 -3
  97. package/md_cg/__init__.py +7 -7
  98. package/md_cg/audit.py +379 -368
  99. package/md_cg/autonomy.py +287 -287
  100. package/md_cg/backfill.py +1328 -1327
  101. package/md_cg/backfill_bigdomain.py +34 -34
  102. package/md_cg/backfill_bucket_zh.py +35 -0
  103. package/md_cg/bench6_arms.py +410 -410
  104. package/md_cg/bench6_common.py +230 -230
  105. package/md_cg/bench6_competitors.py +212 -212
  106. package/md_cg/bench_axis_domain.py +257 -257
  107. package/md_cg/bench_blind_comp.py +308 -308
  108. package/md_cg/bench_e2e_judge.py +532 -0
  109. package/md_cg/bench_e2e_locomo_qa.py +368 -0
  110. package/md_cg/bench_e2e_qa.py +256 -0
  111. package/md_cg/bench_en_atoms_public.py +230 -230
  112. package/md_cg/bench_governance.py +348 -348
  113. package/md_cg/bench_lme_zh.py +410 -410
  114. package/md_cg/bench_locomo.py +121 -121
  115. package/md_cg/bench_locomo_zh.py +450 -450
  116. package/md_cg/bench_locomo_zh_public.py +147 -147
  117. package/md_cg/bench_longmem.py +112 -112
  118. package/md_cg/bench_membench.py +632 -632
  119. package/md_cg/bench_p0.py +149 -149
  120. package/md_cg/bench_progressive.py +287 -287
  121. package/md_cg/bench_role_views.py +238 -238
  122. package/md_cg/bench_task_ab.py +243 -243
  123. package/md_cg/bench_task_ab_llm.py +408 -408
  124. package/md_cg/bench_unified_en.py +204 -204
  125. package/md_cg/bench_zh_mad.py +601 -601
  126. package/md_cg/blindspot_tickets.py +123 -123
  127. package/md_cg/branches.py +301 -285
  128. package/md_cg/build_postings.py +73 -73
  129. package/md_cg/ccgc.py +1006 -948
  130. package/md_cg/census.py +132 -132
  131. package/md_cg/chain.py +315 -300
  132. package/md_cg/codeindex.py +531 -531
  133. package/md_cg/coldverify.py +292 -292
  134. package/md_cg/comment_gate.py +337 -337
  135. package/md_cg/cond_compose.py +190 -190
  136. package/md_cg/cond_facts.py +154 -154
  137. package/md_cg/cond_template.json +106 -106
  138. package/md_cg/condition_anchor.py +142 -142
  139. package/md_cg/conformance.py +726 -726
  140. package/md_cg/consistency.py +717 -717
  141. package/md_cg/consolidate.py +1537 -1439
  142. package/md_cg/corpus.py +110 -110
  143. package/md_cg/crosscheck.py +1098 -1097
  144. package/md_cg/crypto.py +3 -1
  145. package/md_cg/d_meta.py +310 -310
  146. package/md_cg/datapath.py +78 -18
  147. package/md_cg/docindex.py +473 -473
  148. package/md_cg/eval_common.py +575 -575
  149. package/md_cg/evidence.py +4 -2
  150. package/md_cg/evolution.py +477 -477
  151. package/md_cg/export.py +222 -220
  152. package/md_cg/forgetting.py +581 -581
  153. package/md_cg/fsutil.py +377 -329
  154. package/md_cg/hotcache.py +48 -7
  155. package/md_cg/hyperedge.py +251 -251
  156. package/md_cg/identity.py +390 -390
  157. package/md_cg/insight.py +500 -500
  158. package/md_cg/interop.py +338 -0
  159. package/md_cg/judgment_manifest.py +177 -0
  160. package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
  161. package/md_cg/lexicon/build_standard_en.py +171 -171
  162. package/md_cg/lexicon/expand_en_zh.py +211 -211
  163. package/md_cg/lifecycle.py +272 -272
  164. package/md_cg/linkref.py +280 -280
  165. package/md_cg/links.py +140 -107
  166. package/md_cg/mcp_server.py +403 -55
  167. package/md_cg/md_whitebox.py +345 -345
  168. package/md_cg/mdcg.py +783 -233
  169. package/md_cg/mdcos.py +500 -70
  170. package/md_cg/metacognition.py +591 -591
  171. package/md_cg/migrate.py +119 -119
  172. package/md_cg/migrate_aeis.py +221 -221
  173. package/md_cg/migrate_roleplay.py +293 -293
  174. package/md_cg/migrate_wisdom_graph.py +360 -360
  175. package/md_cg/mreview/__init__.py +25 -25
  176. package/md_cg/mreview/__main__.py +110 -110
  177. package/md_cg/mreview/bundle.py +178 -178
  178. package/md_cg/mreview/candidates.py +262 -262
  179. package/md_cg/mreview/govern.py +694 -693
  180. package/md_cg/mreview/locate.py +939 -939
  181. package/md_cg/mreview/pipeline.py +728 -728
  182. package/md_cg/mreview/rules/duplication.json +21 -21
  183. package/md_cg/mreview/rules/field_coverage.json +54 -54
  184. package/md_cg/mreview/rules/source_license.json +21 -21
  185. package/md_cg/mreview/rules/template_flow.json +21 -21
  186. package/md_cg/mreview/ruleset.py +252 -252
  187. package/md_cg/nodefile.py +575 -575
  188. package/md_cg/pooling.py +484 -472
  189. package/md_cg/postings.py +300 -298
  190. package/md_cg/predict.py +1100 -1100
  191. package/md_cg/progressive.py +123 -123
  192. package/md_cg/protect.py +272 -272
  193. package/md_cg/protocol/md_cg_gate.proto +33 -33
  194. package/md_cg/protocol.py +372 -372
  195. package/md_cg/provenance.py +582 -582
  196. package/md_cg/reach.py +453 -453
  197. package/md_cg/readcache.py +143 -0
  198. package/md_cg/reconcile.py +228 -0
  199. package/md_cg/refindex.py +833 -833
  200. package/md_cg/refine.py +604 -604
  201. package/md_cg/review_cli.py +170 -0
  202. package/md_cg/roleviews.py +89 -89
  203. package/md_cg/routing.py +393 -365
  204. package/md_cg/run_tests.py +211 -0
  205. package/md_cg/scrub.py +13 -3
  206. package/md_cg/security.py +128 -18
  207. package/md_cg/self_state.py +1029 -1029
  208. package/md_cg/selfreport.py +152 -151
  209. package/md_cg/semantic/__init__.py +10 -10
  210. package/md_cg/semantic/canonical.py +122 -122
  211. package/md_cg/semantic/en_normalizer.py +364 -364
  212. package/md_cg/semantic/en_zh_map.json +28694 -0
  213. package/md_cg/semantic/export_en_zh_map.py +64 -0
  214. package/md_cg/semantic/unify.py +45 -0
  215. package/md_cg/semantic/zh_en_atoms.py +139 -139
  216. package/md_cg/signer.py +7 -4
  217. package/md_cg/sources.py +816 -582
  218. package/md_cg/statushdr.py +179 -179
  219. package/md_cg/stg.py +54 -37
  220. package/md_cg/subgraph.py +729 -729
  221. package/md_cg/sustain.py +35 -5
  222. package/md_cg/tasks.py +470 -470
  223. package/md_cg/test_access_hints.py +147 -0
  224. package/md_cg/test_action_derive.py +203 -203
  225. package/md_cg/test_audit_rotate.py +270 -270
  226. package/md_cg/test_autonomy.py +143 -143
  227. package/md_cg/test_bench_governance.py +102 -102
  228. package/md_cg/test_blindspot_tickets.py +166 -166
  229. package/md_cg/test_branch_discard_tombstone.py +136 -0
  230. package/md_cg/test_branches.py +13 -3
  231. package/md_cg/test_ccg_perturb.py +184 -184
  232. package/md_cg/test_ccgc.py +433 -433
  233. package/md_cg/test_census_prune.py +81 -81
  234. package/md_cg/test_chain_read_isolate.py +168 -0
  235. package/md_cg/test_cond_compose_anchors.py +76 -76
  236. package/md_cg/test_cond_match.py +165 -165
  237. package/md_cg/test_condition_anchor.py +81 -81
  238. package/md_cg/test_d_meta.py +412 -412
  239. package/md_cg/test_datapath_device_name.py +203 -0
  240. package/md_cg/test_datapath_root.py +199 -199
  241. package/md_cg/test_emit_negtail_cache.py +156 -0
  242. package/md_cg/test_en_pipeline.py +22 -2
  243. package/md_cg/test_gain_gate.py +212 -212
  244. package/md_cg/test_govern_directread.py +421 -0
  245. package/md_cg/test_health_scale.py +173 -173
  246. package/md_cg/test_hive_ingest.py +285 -0
  247. package/md_cg/test_hot_cold.py +215 -215
  248. package/md_cg/test_hyperedge.py +245 -245
  249. package/md_cg/test_i26_empty_first_write.py +116 -0
  250. package/md_cg/test_i27_e041_identity.py +128 -0
  251. package/md_cg/test_i28_hotcache_prodpath.py +122 -0
  252. package/md_cg/test_i32_hotcache_env_key.py +218 -0
  253. package/md_cg/test_identity_attribution.py +96 -15
  254. package/md_cg/test_index_durability.py +17 -3
  255. package/md_cg/test_interop.py +95 -0
  256. package/md_cg/test_interop_judgment.py +228 -0
  257. package/md_cg/test_issue39_utf8_stdio.py +273 -0
  258. package/md_cg/test_lifecycle.py +309 -309
  259. package/md_cg/test_linkref.py +306 -306
  260. package/md_cg/test_links_concurrent_write.py +188 -0
  261. package/md_cg/test_lock.py +43 -43
  262. package/md_cg/test_md_access_parity.py +255 -255
  263. package/md_cg/test_md_writepath.py +345 -345
  264. package/md_cg/test_mdstore_search_parity.py +160 -0
  265. package/md_cg/test_merge_upsert.py +168 -0
  266. package/md_cg/test_mr_m2.py +587 -587
  267. package/md_cg/test_mr_m3.py +710 -710
  268. package/md_cg/test_mr_m4.py +485 -485
  269. package/md_cg/test_n123_derive_expiry_chain.py +205 -0
  270. package/md_cg/test_n130_verify_falsified_protect.py +185 -0
  271. package/md_cg/test_n131_merge_gate.py +205 -0
  272. package/md_cg/test_p0.py +250 -250
  273. package/md_cg/test_p1.py +316 -316
  274. package/md_cg/test_p10_identity.py +173 -173
  275. package/md_cg/test_p11_consistency.py +233 -233
  276. package/md_cg/test_p12_metacognition.py +212 -212
  277. package/md_cg/test_p13_encryption.py +241 -241
  278. package/md_cg/test_p14_sustain.py +249 -249
  279. package/md_cg/test_p15_scrub.py +280 -280
  280. package/md_cg/test_p16_self_state.py +301 -301
  281. package/md_cg/test_p17_predict.py +354 -354
  282. package/md_cg/test_p18_whitebox.py +171 -171
  283. package/md_cg/test_p19_migrate_roleplay.py +149 -149
  284. package/md_cg/test_p1x_ref_root.py +160 -0
  285. package/md_cg/test_p20_evolution.py +315 -315
  286. package/md_cg/test_p21_tokens.py +293 -270
  287. package/md_cg/test_p22_theory.py +175 -175
  288. package/md_cg/test_p23_links.py +311 -311
  289. package/md_cg/test_p24_evidence.py +227 -227
  290. package/md_cg/test_p25_weights.py +156 -156
  291. package/md_cg/test_p26_refindex.py +416 -416
  292. package/md_cg/test_p27_docindex.py +16 -7
  293. package/md_cg/test_p28_refcheck.py +305 -305
  294. package/md_cg/test_p29_session_ingest_export.py +354 -333
  295. package/md_cg/test_p2_mcp.py +3 -0
  296. package/md_cg/test_p3.py +11 -2
  297. package/md_cg/test_p30_maintain.py +330 -330
  298. package/md_cg/test_p31_insight.py +534 -534
  299. package/md_cg/test_p32_backfill.py +7 -1
  300. package/md_cg/test_p33_ccg_wiring.py +293 -293
  301. package/md_cg/test_p34_crosscheck.py +331 -331
  302. package/md_cg/test_p35_conditioned_claim.py +252 -252
  303. package/md_cg/test_p36_kp_align.py +230 -230
  304. package/md_cg/test_p37_condition_space.py +248 -248
  305. package/md_cg/test_p38_concurrent_flush.py +102 -0
  306. package/md_cg/test_p38_contextualize.py +273 -273
  307. package/md_cg/test_p39_verify_flow.py +153 -0
  308. package/md_cg/test_p39_vision_evidence.py +369 -369
  309. package/md_cg/test_p40_refine_worklist.py +241 -241
  310. package/md_cg/test_p41_evolve_patrol.py +224 -224
  311. package/md_cg/test_p42_provenance.py +269 -269
  312. package/md_cg/test_p43_pooling.py +412 -398
  313. package/md_cg/test_p44_md_whitebox.py +231 -231
  314. package/md_cg/test_p45_session_identity.py +219 -219
  315. package/md_cg/test_p46_unit_scope.py +272 -272
  316. package/md_cg/test_p47_session_view.py +316 -0
  317. package/md_cg/test_p4_fuzzy.py +223 -223
  318. package/md_cg/test_p5_semantic.py +226 -226
  319. package/md_cg/test_p6_consolidate.py +440 -387
  320. package/md_cg/test_p7_goals_recent.py +202 -202
  321. package/md_cg/test_p8_subgraph_chain.py +200 -200
  322. package/md_cg/test_p9_forget_protect.py +231 -231
  323. package/md_cg/test_predict_beta.py +135 -135
  324. package/md_cg/test_preflight_failclosed.py +100 -100
  325. package/md_cg/test_progressive.py +146 -146
  326. package/md_cg/test_propose_tail_index.py +157 -0
  327. package/md_cg/test_protocol.py +243 -243
  328. package/md_cg/test_reach.py +378 -378
  329. package/md_cg/test_reach_keys.py +201 -201
  330. package/md_cg/test_read_clip.py +141 -141
  331. package/md_cg/test_read_scope_b27.py +277 -0
  332. package/md_cg/test_readcache_default_on.py +168 -0
  333. package/md_cg/test_readcache_precise_inval.py +270 -0
  334. package/md_cg/test_readcache_prodpath.py +203 -0
  335. package/md_cg/test_reconcile_v0.py +294 -0
  336. package/md_cg/test_retr_gates_prodpath.py +140 -0
  337. package/md_cg/test_retr_s1.py +344 -340
  338. package/md_cg/test_retr_s1b.py +276 -209
  339. package/md_cg/test_retr_s3.py +194 -194
  340. package/md_cg/test_retr_s4.py +163 -163
  341. package/md_cg/test_retr_s5.py +200 -200
  342. package/md_cg/test_retr_s6.py +157 -157
  343. package/md_cg/test_retr_s7.py +392 -384
  344. package/md_cg/test_retr_s8_time.py +369 -316
  345. package/md_cg/test_retr_s9_edges.py +286 -286
  346. package/md_cg/test_retr_s9_entity_ctx.py +9 -3
  347. package/md_cg/test_retr_score_once.py +208 -0
  348. package/md_cg/test_review_conformance.py +367 -367
  349. package/md_cg/test_review_onepass.py +170 -0
  350. package/md_cg/test_role_views.py +354 -354
  351. package/md_cg/test_rrf_graph_seed_cache.py +154 -0
  352. package/md_cg/test_security_audit.py +155 -0
  353. package/md_cg/test_security_audit_b26.py +161 -0
  354. package/md_cg/test_security_audit_v21.py +250 -0
  355. package/md_cg/test_sem_noise.py +242 -242
  356. package/md_cg/test_semantic_canonical.py +16 -2
  357. package/md_cg/test_session_isolation.py +168 -0
  358. package/md_cg/test_snapshot_autoclose.py +187 -0
  359. package/md_cg/test_subproc_encoding.py +192 -192
  360. package/md_cg/test_sustain_mutual.py +153 -153
  361. package/md_cg/test_tail_watermark_race.py +208 -0
  362. package/md_cg/test_tasks.py +409 -409
  363. package/md_cg/test_tenant_env_override_warn.py +139 -0
  364. package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
  365. package/md_cg/test_tool_face.py +189 -189
  366. package/md_cg/test_transfer.py +180 -180
  367. package/md_cg/test_trust.py +361 -361
  368. package/md_cg/test_twophase.py +286 -286
  369. package/md_cg/test_v14_fixes.py +38 -20
  370. package/md_cg/test_validity_filter.py +280 -280
  371. package/md_cg/test_verify_answer.py +138 -138
  372. package/md_cg/test_verify_dirty_reconcile.py +157 -0
  373. package/md_cg/test_wisdom_md_store.py +292 -292
  374. package/md_cg/test_writelimit.py +197 -197
  375. package/md_cg/test_writepipe.py +214 -214
  376. package/md_cg/theory.py +6 -3
  377. package/md_cg/tokens.py +85 -14
  378. package/md_cg/tool_face.py +260 -260
  379. package/md_cg/trust.py +986 -950
  380. package/md_cg/twophase.py +231 -231
  381. package/md_cg/units.py +668 -667
  382. package/md_cg/vision_evidence.py +667 -666
  383. package/md_cg/weights.py +624 -624
  384. package/md_cg/whitebox.py +527 -527
  385. package/md_cg/whitebox_kb/__init__.py +37 -37
  386. package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
  387. package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
  388. package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
  389. package/md_cg/whitebox_kb/engine.py +310 -310
  390. package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  391. package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
  392. package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
  393. package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
  394. package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
  395. package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
  396. package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
  397. package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
  398. package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
  399. package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
  400. package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
  401. package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
  402. package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
  403. package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
  404. package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
  405. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
  406. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
  407. package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
  408. package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
  409. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
  410. package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
  411. package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
  412. package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
  413. package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
  414. package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
  415. package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
  416. package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
  417. package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
  418. package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
  419. package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
  420. package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
  421. package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
  422. package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
  423. package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
  424. package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
  425. package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
  426. package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
  427. package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
  428. package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
  429. package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
  430. package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
  431. package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
  432. package/md_cg/writelimit.py +356 -356
  433. package/md_cg/writepipe.py +20 -8
  434. package/package.json +101 -96
  435. package/skills/plugin.json +54 -54
  436. package/skills/skills/designer-perspective/SKILL.md +158 -158
  437. package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
  438. package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
  439. package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
  440. package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
  441. package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
  442. package/skills/skills/designer-perspective/scripts/designer.py +545 -545
  443. package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
  444. package/skills/skills/designer-perspective/tests/selftest.py +61 -61
  445. package/skills/skills/lingshu-browser/SKILL.md +60 -60
  446. package/skills/skills/lingshu-compiler/SKILL.md +56 -56
  447. package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
  448. package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
  449. package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
  450. package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
  451. package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
  452. package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
  453. package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
  454. package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
  455. package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
  456. package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
  457. package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
  458. package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
  459. package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
  460. package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
  461. package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
  462. package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
  463. package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
  464. package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
  465. package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
  466. package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
  467. package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
  468. package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
  469. package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
  470. package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
  471. package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
  472. package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
  473. package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
  474. package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
  475. package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
  476. package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
  477. package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
  478. package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
  479. package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
  480. package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
  481. package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
  482. package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
  483. package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
  484. package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
  485. package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
  486. package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
  487. package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
  488. package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
  489. package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
  490. package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
  491. package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
  492. package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
  493. package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
  494. package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
  495. package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
  496. package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
  497. package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
  498. package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
  499. package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
  500. package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
  501. package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
  502. package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
  503. package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
  504. package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
  505. package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
  506. package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
  507. package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
  508. package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
  509. package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
  510. package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
  511. package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
  512. package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
  513. package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
  514. package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
  515. package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
  516. package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
  517. package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
  518. package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
  519. package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
  520. package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
  521. package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
  522. package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
  523. package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
  524. package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
  525. package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
  526. package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
  527. package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
  528. package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
  529. package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
  530. package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
  531. package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
  532. package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
  533. package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
  534. package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
  535. package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
  536. package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
  537. package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
  538. package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
  539. package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
  540. package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
  541. package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
  542. package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
  543. package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
  544. package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
  545. package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
  546. package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
  547. package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
  548. package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
  549. package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
  550. package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
  551. package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
  552. package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
  553. package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
  554. package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
  555. package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
  556. package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
  557. package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
  558. package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
  559. package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
  560. package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
  561. package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
  562. package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
  563. package/skills/skills/lingshu-graph/SKILL.md +63 -63
  564. package/skills/skills/lingshu-net/SKILL.md +48 -48
  565. package/skills/skills/lingshu-os/SKILL.md +64 -64
  566. package/skills/skills/lingshu-pylang/SKILL.md +71 -71
  567. package/src/bridge.ts +33 -0
  568. package/src/hooks.ts +38 -2
  569. package/src/index.ts +526 -518
  570. package/src/lib/datapath.ts +326 -326
  571. package/src/lib/mdcg_client.ts +413 -413
  572. package/src/lib/mutual.ts +428 -428
  573. package/src/lib/prompt_safety.ts +62 -62
  574. package/src/lib/python_path.ts +71 -71
  575. package/src/lib/roleplay_web.ts +116 -29
  576. package/src/lib/token_store.ts +13 -3
  577. package/src/tools.ts +212 -212
  578. package/zcode/AGENTS.md +11 -3
  579. package/zcode/README.md +41 -41
  580. /package/docs/{mdcg → hive}//344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md" +0 -0
package/md_cg/predict.py CHANGED
@@ -1,1101 +1,1101 @@
1
- # -*- coding: utf-8 -*-
2
- """生成式预测:候选未来路线(**非必然未来**)。
3
-
4
- 对齐 AEIS `prediction.py`(PREDICTION-COMPLETION-PLAN-REV1-20260813-001)
5
- 四通道预测引擎的通道 3(生成式·因果路线图)+ 通道 4(语义式,经因果过滤门):
6
-
7
- - **D-001** 局部路径生成 + `uncertainty_bound`(候选未来,非必然未来)
8
- - **D-002** 语义邻近过滤门(伪因果防护):语义候选必须能"说清关系"才准入
9
- - **D-003** 局部线性近似 + `extrapolation_validity`(smooth/jump/unknown)
10
- - **D-004** 评分对齐 2.10 节 T_pred 四维度:
11
- trend 0.40 · boundary 0.20 · verification 0.25 · balance 0.15
12
- - **D-005** AttentionPolicy 适配器 + 降级路径(无策略时回退边置信度排序)
13
- - **D-006** 命中率动态校准(MIN_SAMPLES=50 · 2.7.2 动态死区)
14
-
15
- 盲区驱动:`blindspot_id` 指向 unresolved 节点/盲区邻域;声明
16
- `可预测性:unknowable` 的盲区**不生成路线**(结构性不可知)。
17
-
18
- 与 AEIS 的三处有意差异(代码内均已标注):
19
- 1. 语义邻近回退到**中文二元组 Jaccard**(AEIS 优先语义坐标,无坐标时同回退)
20
- 2. 命中历史**持久化**在 `_prediction.jsonl`(AEIS 在内存),跨进程可审计
21
- 3. DFS 增加 `path` 防环(AEIS 仅靠 horizon 截断),md 图允许显式环边
22
-
23
- 纯标准库 · 零外部依赖。
24
- """
25
- from __future__ import annotations
26
-
27
- import hashlib
28
- import os
29
- import re
30
- import time
31
-
32
- from .fsutil import append_jsonl, read_jsonl
33
-
34
- # ---------------------------------------------------------------- 常量
35
- HORIZON_DEFAULT = 3
36
- MAX_BRANCHES_DEFAULT = 5
37
- HORIZON_HARD = 16
38
- MAX_BRANCHES_HARD = 32
39
-
40
- MIN_SAMPLES = 50 # D-006 最低样本量
41
- BASE_HIT_RATE = 0.40 # 基线阈值(工程初值,非协议承诺)
42
- HIT_HISTORY_MAX = 200 # 命中历史滚动窗口
43
- EDGE_BOOST = 0.05 # 命中 → 因果/时序边置信度增量
44
-
45
- # Beta-Bernoulli 先验强度(等效先验样本数 κ,P-T-73/74 通道贝叶斯):
46
- # α0 = BASE_HIT_RATE·κ,β0 = (1−BASE_HIT_RATE)·κ。κ 越大小样本越向基线收缩。
47
- # 性质:先验均值 = BASE_HIT_RATE(与 hit_rate() 空历史兜底同值,口径无缝)。
48
- PRIOR_STRENGTH = 20
49
-
50
- CAUSAL_BRANCH_TYPES = ("causal", "sequential")
51
- SEMANTIC_TOP_K = 5
52
- SEMANTIC_MIN_SIM = 0.05
53
- SEMANTIC_CONF = 0.4 # 语义诱导候选固定置信度(AEIS 同值)
54
- PREFERENCE_THRESHOLD = 0.5 # D-005 偏好权重准入线
55
-
56
- W_TREND = 0.40 # D-004 四维权重(AEIS 2.10 节)
57
- W_BOUNDARY = 0.20
58
- W_VERIFICATION = 0.25
59
- W_BALANCE = 0.15
60
-
61
- # D_meta 第五维(opt-in,方案 §2.3):默认 0 = 不参与 composite、不落键、
62
- # SORT_KEYS 不扩张(默认返回与四维时代逐字节一致,默认零变更纪律)。
63
- # 显式启用:PREDICTION_META_DIM=<权重>(>0 才生效)
64
- # PREDICTION_META_PROXY=<代理名>(缺省 unmodeled_growth;**只取单一
65
- # 代理**——三代理加权合成违反智能论3.4 DEV-002a)
66
- # 纪律:导入期读一次环境变量,改值须重载模块;catalog().weights 同步扩张。
67
- try:
68
- W_META = float(os.environ.get("PREDICTION_META_DIM", "0") or 0.0)
69
- except ValueError: # 非法值按关闭处理:默认零变更优先,不炸导入
70
- W_META = 0.0
71
- W_META = W_META if W_META > 0 else 0.0
72
- META_PROXY = os.environ.get("PREDICTION_META_PROXY", "unmodeled_growth")
73
-
74
- SORT_KEYS = ("composite", "trend", "verification", "boundary", "balance")
75
- if W_META > 0: # 第五维开启时才扩张排序键(默认零变更)
76
- SORT_KEYS = SORT_KEYS + ("meta_pressure",)
77
- LOG_FILE = "_prediction.jsonl"
78
-
79
-
80
- # 生效条件:不适用(无必需形参与分支),恒返回四维权重的自描述 dict;仅当 W_META>0(PREDICTION_META_DIM 显式启用第五维)时追加 meta_pressure 键;
81
- def _weights():
82
- """自描述权重:默认四维逐字节不变,第五维 opt-in 时同步扩张。"""
83
- w = {"trend": W_TREND, "boundary": W_BOUNDARY,
84
- "verification": W_VERIFICATION, "balance": W_BALANCE}
85
- if W_META > 0:
86
- w["meta_pressure"] = W_META
87
- return w
88
-
89
- # 锚点解析纪律(盲区 → 锚点):推断脚手架与负记忆都不得充当锚点。
90
- # - `gap_hint`(待补线索)与 `scene`(情景重构产物)是「推断得出的引子/回放」,
91
- # 本身不构成可起推的事实;尤其 gap_hint 会把盲区描述逐字抄进自己的
92
- # `# 生效条件`,因而在该盲区的检索里必然排第一,若被当成锚点就形成
93
- # 「线索 → 0 条路线 → 永远 unresolved」的自我污染,learn 重复执行也不再幂等。
94
- # - `unresolved` / `rejected` 层是负记忆(已否决/未决问题),只能作为覆盖率
95
- # 提示,不能作为起点。
96
- ANCHOR_FETCH_K = 5
97
- ANCHOR_SKIP_TAGS = ("gap_hint", "scene")
98
- ANCHOR_SKIP_LAYERS = ("unresolved", "rejected")
99
-
100
-
101
- # ---------------------------------------------------------------- 基础
102
-
103
- # 生效条件:传入 cg 具 index 键且其 'nodes' 为非空映射时返回该映射,否则返回空字典 {};
104
- def _nodes(cg):
105
- return ((getattr(cg, "index", None) or {}).get("nodes") or {})
106
-
107
-
108
- # 生效条件:传入 cg 具 root 属性时返回 root 与模块级常量 LOG_FILE 的路径拼接,缺 root 则以 '.' 拼接;
109
- def log_path(cg):
110
- return os.path.join(getattr(cg, "root", "."), LOG_FILE)
111
-
112
-
113
- # 生效条件:传入 cg 与 rec 时向 log_path(cg) 追加一条 JSONL 记录,OSError 被吞掉且无返回值;
114
- def _append(cg, rec):
115
- try:
116
- append_jsonl(log_path(cg), rec)
117
- except OSError:
118
- pass
119
-
120
-
121
- # 生效条件:传入 cg 可读出 JSONL 记录时返回记录列表,读取抛 OSError 或 ValueError 时返回 [],limit 为正时仅返回末尾 limit 条;
122
- def _read_log(cg, limit=0):
123
- try:
124
- recs = list(read_jsonl(log_path(cg)))
125
- except (OSError, ValueError):
126
- recs = []
127
- if limit and int(limit) > 0:
128
- recs = recs[-int(limit):]
129
- return recs
130
-
131
-
132
- # 生效条件:传入 cg 的留痕中存在 type=='feedback' 的记录时返回其 hit 布尔序列末尾 limit 条(limit 默认常量 HIT_HISTORY_MAX),无此类记录则返回空列表;
133
- def _hit_history(cg, limit=HIT_HISTORY_MAX):
134
- """D-006 命中历史(持久化 · 差异 2):只取 feedback 记录。"""
135
- return [bool(r.get("hit")) for r in _read_log(cg)
136
- if r.get("type") == "feedback"][-int(limit):]
137
-
138
-
139
- # 生效条件:传入 cg 与 nid 时返回邻接表中 nid 的出边列表 [(目标 id, 边字典)],该键无出边则为 [];
140
- def _out_edges(cg, nid):
141
- """出边(含层级边):[(target_id, edge_dict)],复用 chain 邻接缓存。"""
142
- from . import chain
143
- return list((chain.adjacency(cg).get(nid) or []))
144
-
145
-
146
- # 生效条件:传入 cg 与 nid 时返回邻接表中所有以 nid 为目标的源节点集合,无入边则为空集;
147
- def _in_sources(cg, nid):
148
- """入边来源集合(父节点):用于 D-002「共同父节点」结构模式判定。"""
149
- from . import chain
150
- adj = chain.adjacency(cg)
151
- srcs = set()
152
- for src, outs in adj.items():
153
- for tgt, _e in outs:
154
- if tgt == nid:
155
- srcs.add(src)
156
- return srcs
157
-
158
-
159
- # 生效条件:传入 edge 的 'confidence' 可转 float 时返回裁剪到 [0.0,1.0] 的值,取键失败或转换抛 AttributeError/TypeError/ValueError 时返回 1.0;
160
- def _edge_conf(edge):
161
- try:
162
- return max(0.0, min(1.0, float(edge.get("confidence", 1.0))))
163
- except (AttributeError, TypeError, ValueError):
164
- return 1.0
165
-
166
-
167
- # ---------------------------------------------------------------- D-002 过滤门
168
-
169
- # 生效条件:causal_link 成立条件为 a_id 的出边中存在目标 b_id 且其关系类型属于模块级常量 CAUSAL_BRANCH_TYPES,成立返回 True,否则返回 False;
170
- def has_causal_link(cg, a_id, b_id):
171
- """直接因果/时序边:A → B 已声明(直通,无需过滤)。"""
172
- from . import chain
173
- for tgt, e in _out_edges(cg, a_id):
174
- if tgt == b_id and chain.edge_rel(e) in CAUSAL_BRANCH_TYPES:
175
- return True
176
- return False
177
-
178
-
179
- # 生效条件:传入 cg、a_id、b_id 时,a_id 与 b_id 的入边来源集合交集非空返回 True,交集为空返回 False;
180
- def has_structural_pattern(cg, a_id, b_id):
181
- """结构模式:A、B 共享父节点(可解释的间接关联 → 伪因果豁免)。"""
182
- return bool(_in_sources(cg, a_id) & _in_sources(cg, b_id))
183
-
184
-
185
- # 生效条件:cg 具 attention_policy 且非 None、且 get_weights() 返回可取值的映射时返回 float(weights.get(node_id, 0.0)),策略缺失、取权重抛异常或转换失败时返回 0.0;
186
- def preference_weight(cg, node_id):
187
- """D-005:AttentionPolicy 适配器(duck-typed `get_weights()`)。
188
-
189
- md_cg 默认无策略 → 0.0(对齐 AEIS:attention_policy=None 时该准入
190
- 条件不生效,只靠因果链/结构模式两道门)。
191
- """
192
- pol = getattr(cg, "attention_policy", None)
193
- if pol is None:
194
- return 0.0
195
- try:
196
- weights = pol.get_weights() or {}
197
- except Exception:
198
- return 0.0
199
- try:
200
- return float(weights.get(node_id, 0.0))
201
- except (TypeError, ValueError):
202
- return 0.0
203
-
204
-
205
- # 生效条件:cg/a_id/b_id 下 has_causal_link 成立返回 (True,'causal_link'),否则 has_structural_pattern 成立返回 (True,'structural_pattern'),否则 preference_weight(cg,b_id) 大于常量 PREFERENCE_THRESHOLD 返回 (True,'preference_weight'),三者皆不成立返回 (False,'rejected_semantic_only');
206
- def causal_gate(cg, a_id, b_id):
207
- """D-002 伪因果过滤门 → (准入?, 理由)。
208
-
209
- 语义邻近候选必须满足其一,否则视为「说不出关系的伪因果」而拒绝:
210
- 1. 已有因果/时序边(直通)
211
- 2. 共同父节点(结构模式)
212
- 3. 偏好权重 > 0.5(D-005 策略显式授权)
213
- """
214
- if has_causal_link(cg, a_id, b_id):
215
- return True, "causal_link"
216
- if has_structural_pattern(cg, a_id, b_id):
217
- return True, "structural_pattern"
218
- if preference_weight(cg, b_id) > PREFERENCE_THRESHOLD:
219
- return True, "preference_weight"
220
- return False, "rejected_semantic_only"
221
-
222
-
223
- # ---------------------------------------------------------------- 语义邻近
224
-
225
- # 生效条件:cg.get(node_id) 的 content 去空后非空时以前 200 字符检索,返回相似度不低于常量 SEMANTIC_MIN_SIM 且非自身的至多 k 个 (nid, similarity);content 为空或检索抛异常时返回 [];
226
- def semantic_neighbors(cg, node_id, k=SEMANTIC_TOP_K):
227
- """语义邻近候选:[(node_id, similarity)]。
228
-
229
- 差异 1:AEIS 优先语义坐标(protocol/hierarchy/condition 三维),无坐标
230
- 时回退 `char_bigram_jaccard`;md_cg 无坐标,直接用检索器的二元组打分
231
- 实现该回退路径(非新算法)。
232
- """
233
- node = cg.get(node_id) or {}
234
- text = (node.get("content") or "").strip()
235
- if not text:
236
- return []
237
- try:
238
- results, _meta = cg.search(text[:200], k=int(k) + 3,
239
- record=False, judge=False)
240
- except Exception:
241
- return []
242
- out = []
243
- for item in results:
244
- if isinstance(item, (tuple, list)):
245
- nd, score = item[0], float(item[1])
246
- else:
247
- nd, score = item, 0.0
248
- if not isinstance(nd, dict):
249
- continue
250
- nid = nd.get("id")
251
- if not nid:
252
- p = str(nd.get("path") or "")
253
- nid = os.path.basename(p)[:-3] if p.endswith(".md") else None
254
- if not nid or nid == node_id or score < SEMANTIC_MIN_SIM:
255
- continue
256
- out.append((nid, round(score, 4)))
257
- if len(out) >= int(k):
258
- break
259
- return out
260
-
261
-
262
- # ---------------------------------------------------------------- D-001 分支
263
-
264
- # 生效条件:传入 cg 与 node_id 时,其出边中关系类型属于常量 CAUSAL_BRANCH_TYPES 者直通为 causal 候选,semantic 为真时再并入经 causal_gate 放行的语义邻近候选,按 confidence 降序、node_id 升序返回;
265
- def branch_candidates(cg, node_id, semantic=True):
266
- """D-001 局部分支候选:因果/时序边直通 + 语义邻近(经 D-002 门)。"""
267
- from . import chain
268
- cands, seen = [], set()
269
- for tgt, e in _out_edges(cg, node_id):
270
- if tgt in seen:
271
- continue
272
- rel = chain.edge_rel(e)
273
- if rel not in CAUSAL_BRANCH_TYPES:
274
- continue
275
- seen.add(tgt)
276
- cands.append({"node_id": tgt, "confidence": _edge_conf(e),
277
- "source": "causal", "relation_type": rel,
278
- "condition": chain.edge_condition(e) or ""})
279
- if semantic:
280
- for tgt, sim in semantic_neighbors(cg, node_id):
281
- if tgt in seen:
282
- continue
283
- ok, why = causal_gate(cg, node_id, tgt)
284
- if not ok:
285
- continue
286
- seen.add(tgt)
287
- cands.append({"node_id": tgt, "confidence": SEMANTIC_CONF,
288
- "source": "semantic_induced", "relation_type": why,
289
- "condition": "", "similarity": sim})
290
- cands.sort(key=lambda c: (-c["confidence"], c["node_id"]))
291
- return cands
292
-
293
-
294
- # ---------------------------------------------------------------- D-003 / D-004
295
-
296
- # 生效条件:conf 可转 float 时裁剪到 [0,1] 得 c,按 base=1-c 返回 confidence=c 与裁剪到 [0,1] 的 [c-base*0.5, c+base*0.5] 区间,转换失败则按 c=0.0 计算;
297
- def _uncertainty(conf):
298
- """D-001 不确定带:base=1-conf,上下界 = conf ± base*0.5(AEIS 同式)。"""
299
- try:
300
- c = max(0.0, min(1.0, float(conf)))
301
- except (TypeError, ValueError):
302
- c = 0.0
303
- base = 1.0 - c
304
- return {"confidence": round(c, 4),
305
- "lower": round(max(0.0, c - base * 0.5), 4),
306
- "upper": round(min(1.0, c + base * 0.5), 4),
307
- "method": "linear_local_approx"}
308
-
309
-
310
- # 生效条件:传入 cg 与 path 且 path 非空时,返回其中 tags 含 'boundary' 或 has_neg_conditions,或 content 匹配「不适用|不确定|边界|盲区」的节点占比;path 为空返回 0.0;
311
- def _boundary_consistency(cg, path):
312
- """boundary 维:路径节点是否声明了适用边界/不确定条件(0-1)。"""
313
- if not path:
314
- return 0.0
315
- hit = 0
316
- for nid in path:
317
- e = _nodes(cg).get(nid) or {}
318
- tags = set(e.get("tags") or [])
319
- if "boundary" in tags or e.get("has_neg_conditions"):
320
- hit += 1
321
- continue
322
- node = cg.get(nid) or {}
323
- if re.search(r"不适用|不确定|边界|盲区", node.get("content") or ""):
324
- hit += 1
325
- return round(hit / len(path), 4)
326
-
327
-
328
- # 生效条件:传入 route 时返回其 sources 与 relations 去重并集大小除以 4.0 且上限 1.0 的值,两键皆缺时返回 0.0;
329
- def _branch_diversity(route):
330
- """balance 维:路径覆盖的推理通道维度数 / 4。
331
-
332
- AEIS 用语义坐标的维度数;md_cg 无语义坐标 → 用「来源通道
333
- (causal / semantic_induced)+ 边关系类型」作维度,保持 /4.0 归一化,
334
- 语义仍是「防单一偏好主导」。
335
- """
336
- dims = set(route.get("sources") or []) | set(route.get("relations") or [])
337
- return round(min(1.0, len(dims) / 4.0), 4)
338
-
339
-
340
- # 生效条件:route 的 'confs' 长度不少于 2 时,相邻差绝对值最大值不超过 0.35 返回 'smooth' 否则 'jump';长度不足 2 或无可算步长返回 'unknown';
341
- def extrapolation_validity(route):
342
- """D-003:局部线性外推有效性(smooth / jump / unknown)。"""
343
- confs = route.get("confs") or []
344
- if len(confs) < 2:
345
- return "unknown"
346
- steps = [confs[i] - confs[i + 1] for i in range(len(confs) - 1)]
347
- if not steps:
348
- return "unknown"
349
- return "smooth" if max(abs(s) for s in steps) <= 0.35 else "jump"
350
-
351
-
352
- # 生效条件:传入 cg 且 _hit_history(cg) 非空时返回其命中占比,序列为空时返回模块级常量 BASE_HIT_RATE;
353
- def hit_rate(cg):
354
- """历史命中率(无样本 → 基线 0.40,对齐 D-006)。"""
355
- h = _hit_history(cg)
356
- if not h:
357
- return BASE_HIT_RATE
358
- return round(sum(1 for x in h if x) / len(h), 4)
359
-
360
-
361
- # ---------------------------------------------------------------- P-T-73/74 通道贝叶斯
362
-
363
- # 生效条件:传入 hits 序列时以 base*prior_k 与 (1-base)*prior_k 为先验(prior_k 默认常量 PRIOR_STRENGTH、base 默认常量 BASE_HIT_RATE),按命中数 k 与总数 n 返回 alpha/beta/mean/std/ci95/samples/hits;hits 为空即为纯先验,mean 等于 base;
364
- def beta_posterior(hits, prior_k=PRIOR_STRENGTH, base=BASE_HIT_RATE):
365
- """Beta-Bernoulli 后验(纯函数):命中序列 → 后验参数与区间。
366
-
367
- 置信度≠可信度(P-T-73/74):`hit_rate` 是滑动计数(小样本过度自信:
368
- n=5 全命中 → 1.0);后验均值 = (α0+k)/(α0+β0+n) 随样本量向基线收缩
369
- (n=5 全命中 → 0.52),诚实反映「证据还很少」。置信度的动态死区
370
- (dynamic_hit_threshold)不动——本函数只提供**更诚实的可信度估计面**,
371
- 默认链路零行为变更。
372
-
373
- 返回:{alpha, beta, mean, std, ci95, samples, hits};
374
- 空序列 → 先验(mean=base,与 hit_rate 空历史兜底同值)。
375
- ci95 用正态近似 mean±1.96σ(零依赖;样本大时近似良好,小样本区间
376
- 偏窄,故另带 samples 供阅读方自判权重)。
377
- """
378
- a0 = float(base) * float(prior_k)
379
- b0 = (1.0 - float(base)) * float(prior_k)
380
- xs = [bool(x) for x in (hits or [])]
381
- k = sum(1 for x in xs if x)
382
- n = len(xs)
383
- a, b = a0 + k, b0 + (n - k)
384
- mean = a / (a + b)
385
- var = (a * b) / ((a + b) ** 2 * (a + b + 1.0))
386
- std = var ** 0.5
387
- return {"alpha": round(a, 4), "beta": round(b, 4),
388
- "mean": round(mean, 4), "std": round(std, 4),
389
- "ci95": [round(max(0.0, mean - 1.96 * std), 4),
390
- round(min(1.0, mean + 1.96 * std), 4)],
391
- "samples": n, "hits": k}
392
-
393
-
394
- # 生效条件:传入 cg 时把 type=='feedback' 的记录按 channel 字段(缺失归 'unlabeled')分组为 {channel: [hit...]},仅取末尾 limit 条(limit 默认常量 HIT_HISTORY_MAX);
395
- def channel_history(cg, limit=HIT_HISTORY_MAX):
396
- """feedback 留痕按通道分组(P-T-74 通道级可信度)→ {channel: [hit...]}。
397
-
398
- 通道 = 预测发出的面(feedback 的 `channel` 参数,如 "causal"/"semantic")。
399
- 早期留痕无 channel 字段 → 归 "unlabeled"(诚实标注,不冒充分通道)。
400
- """
401
- out = {}
402
- for r in _read_log(cg, limit=limit):
403
- if r.get("type") != "feedback":
404
- continue
405
- ch = str(r.get("channel") or "unlabeled")
406
- out.setdefault(ch, []).append(bool(r.get("hit")))
407
- return out
408
-
409
-
410
- # 生效条件:channel 非 None 时返回该通道(无记录即先验)的 beta_posterior 并附 channel 键,channel 为 None 时返回 {'all': 全量后验, 'channels': 各通道后验};
411
- def channel_posterior(cg, channel=None, limit=HIT_HISTORY_MAX):
412
- """通道级后验查询:channel=None → 全量+分通道;channel=str → 单通道。"""
413
- hist = channel_history(cg, limit=limit)
414
- if channel is not None:
415
- ch = str(channel)
416
- post = beta_posterior(hist.get(ch) or [])
417
- post["channel"] = ch
418
- return post
419
- all_hits = [x for hits in hist.values() for x in hits]
420
- return {"all": beta_posterior(all_hits),
421
- "channels": {ch: beta_posterior(hits)
422
- for ch, hits in sorted(hist.items())}}
423
-
424
-
425
- # 生效条件:传入 cg 与 route 时,trend 取 route 的 confidence、boundary 取 _boundary_consistency(cg, route['path'])、verification 缺省时取 hit_rate(cg)、balance 取 _branch_diversity(route),并按 W_TREND/W_BOUNDARY/W_VERIFICATION/W_BALANCE 加权返回 composite;仅当 W_META>0(PREDICTION_META_DIM 显式启用第五维)时才额外取 d_meta.pressure(d_meta.compute(cg), META_PROXY) 作第五维、落 meta_pressure 键、把 W_META*meta_pressure 计入 composite,关闭时返回键集合与 composite 逐字节不变;
426
- def score_route(cg, route, verification=None):
427
- """D-004 T_pred 四维评分 + **D_meta 第五维(opt-in)**。
428
-
429
- `verification` 跨路线共享(来自命中率)。第五维 `meta_pressure` 取
430
- **单一指定代理**(`PREDICTION_META_PROXY`,缺省 `unmodeled_growth`)——
431
- **不做三代理加权合成**(智能论3.4 §2.7.0 DEV-002a)。默认权重 0:
432
- 不落键、不改 composite、SORT_KEYS 不扩张(默认零变更纪律)。
433
- """
434
- trend = float(route.get("confidence") or 0.0)
435
- boundary = _boundary_consistency(cg, route.get("path") or [])
436
- ver = float(verification if verification is not None else hit_rate(cg))
437
- balance = _branch_diversity(route)
438
- composite = (W_TREND * trend + W_BOUNDARY * boundary
439
- + W_VERIFICATION * ver + W_BALANCE * balance)
440
- out = {"trend": round(trend, 4), "boundary": boundary,
441
- "verification": round(ver, 4), "balance": balance,
442
- "composite": round(composite, 4), "weights": _weights()}
443
- if W_META > 0: # 第五维 opt-in:默认不落键
444
- from . import d_meta # 惰性导入:叶子只读模块,防循环
445
- mp = d_meta.pressure(d_meta.compute(cg), META_PROXY)
446
- out["meta_pressure"] = mp
447
- out["composite"] = round(composite + W_META * mp, 4)
448
- return out
449
-
450
-
451
- # ---------------------------------------------------------------- 盲区驱动
452
-
453
- # 生效条件:blindspot_id 去空后非空且能取到盲区报表时,匹配 unresolved 的 node_id 返回 kind='unresolved',否则匹配 items 的 query 或其 _key 相等返回 kind='blindspot_cluster',均未命中或取报表抛异常返回 None;
454
- def find_blindspot(cg, blindspot_id):
455
- """按 id 定位盲区:unresolved 节点优先,其次盲区邻域(按 query 键)。"""
456
- from . import metacognition
457
- bid = str(blindspot_id or "").strip()
458
- if not bid:
459
- return None
460
- try:
461
- bs = metacognition.blindspots(cg)
462
- except Exception:
463
- return None
464
- for u in bs.get("unresolved") or []:
465
- if u.get("node_id") == bid:
466
- return {"id": bid, "kind": "unresolved",
467
- "description": u.get("content") or ""}
468
- for it in bs.get("items") or []:
469
- q = str(it.get("query") or "")
470
- if q == bid or metacognition._key(q) == bid:
471
- return {"id": bid, "kind": "blindspot_cluster", "description": q,
472
- "blindspot": it.get("blindspot"),
473
- "defer": it.get("defer")}
474
- return None
475
-
476
-
477
- # 生效条件:blindspot 的 description 中匹配到「可预测性:X」时返回 X 去除空白并小写,未匹配则返回 'predictable';
478
- def predictability(blindspot):
479
- """可预测性:盲区可显式声明 `# 可预测性:unknowable`。
480
-
481
- 未声明 → predictable(默认按局部不可知原理生成候选,不宣称必然)。
482
- """
483
- txt = str((blindspot or {}).get("description") or "")
484
- m = re.search(r"可预测性[::]\s*(\S+)", txt)
485
- return m.group(1).strip().lower() if m else "predictable"
486
-
487
-
488
- # 生效条件:description 去空后非空时以 cg.search 取常量 ANCHOR_FETCH_K 个候选,跳过 layer 属常量 ANCHOR_SKIP_LAYERS 或 tags 命中常量 ANCHOR_SKIP_TAGS 者,返回首个出边非空的合格节点 id,否则降级返回首个合格候选 id;description 为空、检索抛异常或候选全被过滤返回 None;
489
- def anchor_from_description(cg, description):
490
- """盲区描述 → 锚点节点(检索器打分,与 AEIS 的 LIKE→坐标回退同构)。
491
-
492
- 择锚规则:候选池内**可起推者优先**——出边非空是路线生成的必要条件,无出边的
493
- 节点只能靠语义回退硬凑路线,起点语义即失真。**全部不可起推时才降级**为首个合格
494
- 候选(诚实降级:宁可标注锚点弱,不假装有锚)。
495
- 动机(实测):同分并列时检索序不可依赖——「正题节点」与其「同名+近义描述」变体
496
- 词法分可完全相同(各自都为 1.0),纯按检索名次取首位会漂到无出边的近义变体上。
497
-
498
- 过滤与择锚分两关:先按标签/层过滤掉推断脚手架(`gap_hint`/`scene`)与负记忆
499
- (`unresolved`/`rejected`),再在剩余候选里按**可起推资格**择优——出边非空者
500
- 立即采纳,无出边者记为降级候选继续后看,全池无可起推者才降级取首个合格候选。
501
- 候选全被过滤时返回 None(等价「无锚点」),由调用方按 no_anchor 处理。
502
- 邻接表不可用(chain 取表异常)时退回「取首个合格候选」——辅助判据故障不应
503
- 让整条预测链失效(兜底优先于择优)。
504
- """
505
- q = str(description or "").strip()
506
- if not q:
507
- return None
508
- try:
509
- results, _meta = cg.search(q, k=ANCHOR_FETCH_K, record=False, judge=False)
510
- except Exception:
511
- return None
512
- try:
513
- from . import chain
514
- out_edges = chain.adjacency(cg)
515
- except Exception:
516
- out_edges = None
517
- fallback = None
518
- for item in results:
519
- nd = item[0] if isinstance(item, (tuple, list)) else item
520
- if not isinstance(nd, dict):
521
- continue
522
- fm = nd.get("frontmatter") or {}
523
- path = str(nd.get("path") or "")
524
- layer = str(fm.get("layer") or path.split("/")[0])
525
- if layer in ANCHOR_SKIP_LAYERS:
526
- continue
527
- if set(ANCHOR_SKIP_TAGS) & set(fm.get("tags") or []):
528
- continue
529
- nid = nd.get("id")
530
- if not nid:
531
- nid = os.path.basename(path)[:-3] if path.endswith(".md") else None
532
- if not nid:
533
- continue
534
- # 兜底:fs 派生的 id 可能是相对路径(如 unresolved/bs_x.md),归一为裸节点名
535
- if "/" in str(nid) and str(nid).endswith(".md"):
536
- nid = os.path.basename(str(nid))[:-3]
537
- # 可起推资格:出边非空是路线生成的必要条件;不可起推者只作降级候选。
538
- if out_edges is None or out_edges.get(nid):
539
- return nid
540
- if fallback is None:
541
- fallback = nid
542
- return fallback
543
-
544
-
545
- # ---------------------------------------------------------------- 路线生成
546
-
547
- # 生效条件:cg.get(nid) 的 content 含非空行时返回首行去掉开头 '#' 后的前 60 字符,否则返回 nid;
548
- def _label(cg, nid):
549
- node = cg.get(nid) or {}
550
- for line in (node.get("content") or "").splitlines():
551
- s = line.strip()
552
- if s:
553
- return re.sub(r"^#\s*", "", s)[:60]
554
- return nid
555
-
556
-
557
- # 生效条件:start_id 去空后非空且存在于 _nodes(cg) 时,horizon 裁剪到 [1, HORIZON_HARD]、max_branches 裁剪到 [1, MAX_BRANCHES_HARD] 后 DFS,返回 status='ok' 及带 uncertainty_bound/extrapolation_validity/score/path_labels 的 routes;start_id 为空或不在节点集中返回 status='start_not_found';
558
- def _generate(cg, start_id, horizon, max_branches, semantic=True):
559
- """D-001 局部路径 DFS 生成候选未来(差异 3:增加 path 防环)。"""
560
- start_id = str(start_id or "").strip()
561
- if not start_id or start_id not in _nodes(cg):
562
- return {"status": "start_not_found", "start_id": start_id,
563
- "routes": [], "meta": {"reason": "节点不存在"}}
564
- horizon = max(1, min(int(horizon), HORIZON_HARD))
565
- max_branches = max(1, min(int(max_branches), MAX_BRANCHES_HARD))
566
- ver = hit_rate(cg)
567
- out = []
568
-
569
- # 生效条件:当 depth < horizon(enclosing 上界)时,对 branch_candidates(cg, cur, semantic=semantic) 取前 max_branches 个候选展开——候选 node_id 已在 path 中则 continue 跳过,否则把 cand["confidence"] 连乘(初始 1.0,结果 round 4)连同 conds/sources/relations 各自追加 cand 对应字段后作为一条路径写入外层 out,并以该 node_id、depth+1 递归;depth >= horizon 时直接返回且不产生任何路径。
570
- def dfs(cur, path, confs, conds, sources, relations, depth):
571
- if depth >= horizon:
572
- return
573
- for cand in branch_candidates(cg, cur, semantic=semantic)[:max_branches]:
574
- nid = cand["node_id"]
575
- if nid in path:
576
- continue
577
- n_confs = confs + [cand["confidence"]]
578
- conf = 1.0
579
- for c in n_confs:
580
- conf *= c
581
- n_path = path + [nid]
582
- out.append({"path": n_path, "confidence": round(conf, 4),
583
- "confs": n_confs,
584
- "conditions": conds + [cand["condition"]],
585
- "sources": sources + [cand["source"]],
586
- "relations": relations + [cand["relation_type"]],
587
- "last_source": cand["source"]})
588
- dfs(nid, n_path, n_confs, conds + [cand["condition"]],
589
- sources + [cand["source"]], relations + [cand["relation_type"]],
590
- depth + 1)
591
-
592
- dfs(start_id, [start_id], [], [], [], [], 0)
593
- for r in out:
594
- r["uncertainty_bound"] = _uncertainty(r["confidence"])
595
- r["extrapolation_validity"] = extrapolation_validity(r)
596
- r["score"] = score_route(cg, r, verification=ver)
597
- r["path_labels"] = [_label(cg, n) for n in r["path"]]
598
- return {"status": "ok", "start_id": start_id, "routes": out,
599
- "meta": {"horizon": horizon, "max_branches": max_branches,
600
- "n_routes": len(out), "hit_rate": ver,
601
- "note": "候选未来,非必然未来(D-001)",
602
- "generated_at": time.time()}}
603
-
604
-
605
- # 生效条件:传入 cg、res、sort、limit 时,sort 不属于常量 SORT_KEYS 则退回 'composite',按该键排序并在 limit 为正时截断,追加一条 type 为 log_type(默认 'predict_routes')的留痕后返回 res;
606
- def _finalize(cg, res, sort, limit, log_type="predict_routes"):
607
- """排序 / 限流 / 留痕(预测本身可审计)。"""
608
- if sort not in SORT_KEYS:
609
- sort = "composite"
610
- res["routes"].sort(key=lambda r: (-float(r["score"].get(sort) or 0.0),
611
- r["path"]))
612
- if limit and int(limit) > 0:
613
- res["routes"] = res["routes"][:int(limit)]
614
- res["meta"]["sort"] = sort
615
- res["meta"]["n_returned"] = len(res["routes"])
616
- _append(cg, {"type": log_type, "t": time.time(),
617
- "start_id": res.get("start_id"),
618
- "blindspot_id": res["meta"].get("blindspot_id"),
619
- "n_routes": res["meta"]["n_routes"],
620
- "horizon": res["meta"]["horizon"], "sort": sort})
621
- return res
622
-
623
-
624
- # 生效条件:blindspot_id 非空时转交 routes_from_blindspot;否则 start_id 为空返回 status='no_start',_generate 返回非 ok 则原样返回,ok 则经 _finalize(cg, res, sort, limit) 返回;
625
- def routes(cg, start_id=None, blindspot_id=None, horizon=HORIZON_DEFAULT,
626
- max_branches=MAX_BRANCHES_DEFAULT, sort="composite", limit=0,
627
- semantic=True):
628
- """生成候选未来路线(D-001 ~ D-005)。
629
-
630
- start_id 起点节点 id
631
- blindspot_id 盲区驱动(unresolved 节点 id / 盲区邻域键)
632
- horizon 最大前推步数(默认 3)
633
- max_branches 每步最大分支数(默认 5)
634
- sort composite | trend | verification | boundary | balance
635
- """
636
- if blindspot_id:
637
- return routes_from_blindspot(cg, blindspot_id, horizon=horizon,
638
- max_branches=max_branches, sort=sort,
639
- limit=limit, semantic=semantic)
640
- if not start_id:
641
- return {"status": "no_start", "routes": [],
642
- "meta": {"reason": "缺少 start_id 或 blindspot_id"}}
643
- res = _generate(cg, start_id, horizon, max_branches, semantic)
644
- if res.get("status") != "ok":
645
- return res
646
- return _finalize(cg, res, sort, limit)
647
-
648
-
649
- # 生效条件:find_blindspot 得 None 时返回 status='blindspot_not_found',其 predictability 为 'unknowable' 时返回 status='unpredictable',anchor_from_description 无锚点时返回 status='no_anchor',有锚点则以其 _generate 并在 ok 时经 _finalize(log_type='predict_routes_blindspot')返回;
650
- def routes_from_blindspot(cg, blindspot_id, horizon=HORIZON_DEFAULT,
651
- max_branches=MAX_BRANCHES_DEFAULT, sort="composite",
652
- limit=0, semantic=True):
653
- """盲区驱动的生成式预测(v1.10)。"""
654
- bs = find_blindspot(cg, blindspot_id)
655
- if bs is None:
656
- return {"status": "blindspot_not_found", "blindspot_id": blindspot_id,
657
- "routes": [], "meta": {"reason": "未找到该盲区"}}
658
- if predictability(bs) == "unknowable":
659
- return {"status": "unpredictable", "reason": "structural_unknowability",
660
- "blindspot": bs, "routes": [],
661
- "meta": {"note": "盲区声明不可预测 → 不生成路线(拒绝编造)"}}
662
- anchor = anchor_from_description(cg, bs.get("description") or "")
663
- if not anchor:
664
- return {"status": "no_anchor", "blindspot": bs, "routes": [],
665
- "meta": {"reason": "盲区描述检索不到锚点节点"}}
666
- res = _generate(cg, anchor, horizon, max_branches, semantic)
667
- res["blindspot"] = bs
668
- if res.get("status") != "ok":
669
- return res
670
- res["meta"]["blindspot_id"] = blindspot_id
671
- res["meta"]["anchor"] = anchor
672
- return _finalize(cg, res, sort, limit, log_type="predict_routes_blindspot")
673
-
674
-
675
- # ---------------------------------------------------------------- D-006 反馈闭环
676
-
677
- _SAFE_LAYERS = ("knowledge", "contextual", "structural")
678
-
679
-
680
- # 生效条件:传入 edge 时返回其 'target'、'to'、'dst' 中首个非空值,三者皆空则返回 None;
681
- def _edge_target(edge):
682
- return edge.get("target") or edge.get("to") or edge.get("dst")
683
-
684
-
685
- # 生效条件:传入 cg 与 node_id 时,仅当存在指向 node_id 的因果/时序边且其源节点 frontmatter 的 layer 属于常量 _SAFE_LAYERS,才把该边 confidence 提升 min(1.0, 原值+delta)(delta 默认常量 EDGE_BOOST)并回写,返回被改源节点列表,无满足者返回空列表;
686
- def _boost_incoming(cg, node_id, delta=EDGE_BOOST, actor="predict"):
687
- """命中 → 指向该节点的因果/时序边置信度 +delta。
688
-
689
- 只改普通层(knowledge/contextual/structural):self/anchor/rejected/
690
- unresolved/goals 带专用 frontmatter,整体重写会丢字段。
691
- """
692
- from . import chain
693
- changed = []
694
- for src, outs in list(chain.adjacency(cg).items()):
695
- if not any(tgt == node_id and chain.edge_rel(e) in CAUSAL_BRANCH_TYPES
696
- for tgt, e in outs):
697
- continue
698
- node = cg.get(src)
699
- if not node:
700
- continue
701
- fm = node.get("frontmatter") or {}
702
- if fm.get("layer") not in _SAFE_LAYERS:
703
- continue
704
- edges, touched = [dict(e) for e in (fm.get("edges") or [])], False
705
- for ed in edges:
706
- if _edge_target(ed) != node_id:
707
- continue
708
- if chain.edge_rel(ed) not in CAUSAL_BRANCH_TYPES:
709
- continue
710
- old, new = _edge_conf(ed), min(1.0, _edge_conf(ed) + float(delta))
711
- if new != old:
712
- ed["confidence"] = round(new, 4)
713
- touched = True
714
- if not touched:
715
- continue
716
- # add() 会**重建** frontmatter:未显式传回的字段会被重置或丢失。
717
- # 尤其 sensitivity 缺省为 DEFAULT_SENSITIVITY("internal")——直接回写等于
718
- # 把 private/secret 节点**降级**;modality/created_at/证据计数同理。
719
- # 故除被覆盖的字段外,整表透传(actor 已单独传,避免重复关键字)。
720
- skip = {"id", "layer", "tags", "condition_space", "importance",
721
- "confidence", "edges", "verification_basis",
722
- "non_applicable_conditions", "actor", "path"}
723
- keep = {k: v for k, v in fm.items() if k not in skip}
724
- try:
725
- cg.add(src, node.get("content") or "",
726
- layer=fm.get("layer") or "knowledge",
727
- tags=fm.get("tags"),
728
- condition_space=fm.get("condition_space"),
729
- importance=fm.get("importance", 0.5),
730
- confidence=fm.get("confidence", 0.6), edges=edges,
731
- verification_basis=fm.get("verification_basis"),
732
- non_applicable_conditions=fm.get("non_applicable_conditions"),
733
- override=True, actor=actor, **keep)
734
- changed.append(src)
735
- except Exception:
736
- continue
737
- if changed:
738
- chain.invalidate_cache(cg)
739
- return changed
740
-
741
-
742
- # 生效条件:传入 cg 时若 _hit_history(cg, limit) 样本数少于常量 MIN_SAMPLES,返回 threshold 为常量 BASE_HIT_RATE 且 reflect=False;样本足够则以 max(BASE_HIT_RATE, mean-2σ) 为 threshold,命中均值低于该值时 reflect=True;
743
- def dynamic_hit_threshold(cg, limit=HIT_HISTORY_MAX):
744
- """D-006 命中率动态校准(2.7.2 动态死区)。
745
-
746
- 样本 < MIN_SAMPLES 不触发反思(小样本噪声);阈值 = max(BASE, mean-2σ)。
747
- """
748
- h = _hit_history(cg, limit=limit)
749
- n = len(h)
750
- if n < MIN_SAMPLES:
751
- return {"threshold": BASE_HIT_RATE, "samples": n,
752
- "min_samples": MIN_SAMPLES, "reflect": False,
753
- "note": f"样本不足({n}/{MIN_SAMPLES}),不触发反思"}
754
- mean = sum(1 for x in h if x) / n
755
- var = sum(((1.0 if x else 0.0) - mean) ** 2 for x in h) / n
756
- std = var ** 0.5
757
- th = max(BASE_HIT_RATE, mean - 2 * std)
758
- low = mean < th
759
- return {"threshold": round(th, 4), "samples": n, "mean": round(mean, 4),
760
- "std": round(std, 4), "min_samples": MIN_SAMPLES,
761
- "reflect": low,
762
- "note": "命中率低于动态阈值 → 建议反思(D-006)" if low
763
- else "命中率正常"}
764
-
765
-
766
- # 生效条件:cg 与 predicted_node_id 给出即执行——predicted_node_id 为假值(None/0/"")时 pred 取空串,actual_node_id 为假值时 act 回落为 pred,hit 为 None 时按 pred==act 判定;hit 为真时 out["boosted"] 取 _boost_incoming(cg, act, EDGE_BOOST, actor=actor),hit 为假时 out["rejected_id"] 取 cg.add_rejected(...)(该调用抛异常时改记 out["rejected_error"]),随后 out 合并 dynamic_hit_threshold(cg),sync_self 为真时导入 self_state.refresh(cg, actor=actor) 写 out["self_state"](抛异常时写 out["self_state_error"]),返回 out。
767
- def feedback(cg, predicted_node_id, actual_node_id=None, hit=None, note="",
768
- actor="predict", sync_self=True, channel=None):
769
- """预测反馈(D-006):hit → 边置信度 +0.05;miss → 登记 rejected。
770
-
771
- `hit` 未显式给出时按 `predicted == actual` 判定。
772
- `channel`(可选,P-T-74):预测发出的面(如 "causal"/"semantic"),
773
- 供通道级 Beta-Bernoulli 后验(channel_posterior)按面分层估计可信度;
774
- 不传 → 留痕归 "unlabeled",不改变任何既有行为。
775
-
776
- `sync_self`(默认 True):反馈后**回写自我模型**——刷新自我状态卡的
777
- 「预测校准」面,形成「预测 → 事实 → 误差 → 自我更新」闭环。
778
- 自我模型是二阶观测,其刷新失败不阻塞一阶反馈结果。
779
- """
780
- pred = str(predicted_node_id or "").strip()
781
- act = str(actual_node_id or "").strip() or pred
782
- if hit is None:
783
- hit = (pred == act)
784
- hit = bool(hit)
785
- _append(cg, {"type": "feedback", "t": time.time(), "predicted": pred,
786
- "actual": act, "hit": hit, "note": str(note or "")[:200],
787
- "channel": (str(channel) if channel else None)})
788
- out = {"ok": True, "hit": hit, "predicted": pred, "actual": act}
789
- if hit:
790
- out["boosted"] = _boost_incoming(cg, act, EDGE_BOOST, actor=actor)
791
- else:
792
- try:
793
- out["rejected_id"] = cg.add_rejected(
794
- hypothesis=f"预测未命中:{pred} → {act}",
795
- reason=str(note or "实际走向不同"),
796
- verification_basis="data", tags=["prediction", "miss"])
797
- except Exception as exc: # pragma: no cover
798
- out["rejected_error"] = str(exc)
799
- out.update(dynamic_hit_threshold(cg))
800
- # 闭环:预测误差 → 自我模型更新。延迟导入避免与 self_state 的循环依赖,
801
- # 且自我模型刷新属于二阶观测,失败不阻塞一阶反馈结果。
802
- if sync_self:
803
- try:
804
- from . import self_state
805
- out["self_state"] = self_state.refresh(cg, actor=actor)
806
- except Exception as exc: # pragma: no cover
807
- out["self_state_error"] = str(exc)
808
- return out
809
-
810
-
811
- # ---------------------------------------------------------------- P2 盲区学习闭环
812
-
813
- LEARN_LOG = "_learn.jsonl"
814
- LEARN_MAX_STEPS = 8
815
-
816
-
817
- # 生效条件:传入 cg 具 root 属性时返回 root 与模块级常量 LEARN_LOG 的路径拼接,缺 root 则以 '.' 拼接;
818
- def learn_log_path(cg):
819
- return os.path.join(getattr(cg, "root", "."), LEARN_LOG)
820
-
821
-
822
- # 生效条件:传入 cg 与 rec 时向 learn_log_path(cg) 追加一条 JSONL 记录,任何异常被吞掉且无返回值;
823
- def _learn_append(cg, rec):
824
- try:
825
- append_jsonl(learn_log_path(cg), rec)
826
- except Exception: # noqa: BLE001
827
- pass
828
-
829
-
830
- # 生效条件:nid 去空后非空、cg.get(nid) 的 frontmatter layer 等于 'knowledge' 且其 content 含常量 consolidate.CCG_REQUIRED 全部要素行时返回 True,否则返回 False;
831
- def _is_settled(cg, nid):
832
- """终点是否已达「可判定」态:知识层 ∧ CCG 五要素齐全。"""
833
- nid = str(nid or "").strip()
834
- if not nid:
835
- return False
836
- try:
837
- node = cg.get(nid) or {}
838
- except Exception: # noqa: BLE001
839
- return False
840
- fm = node.get("frontmatter") or {}
841
- if fm.get("layer") != "knowledge":
842
- return False
843
- try:
844
- from . import consolidate
845
- content = node.get("content") or ""
846
- return all(consolidate._has_ccg_line(content, k)
847
- for k in consolidate.CCG_REQUIRED)
848
- except Exception: # noqa: BLE001
849
- return False
850
-
851
-
852
- # 生效条件:传入 route 且其 'path' 非空时返回路径最后一个节点 id,路径为空或 route 为 None 时返回 None;
853
- def _terminal_node(route):
854
- path = list((route or {}).get("path") or [])
855
- return path[-1] if path else None
856
-
857
-
858
- # 生效条件:传入 cg、bid、step、actor 时,cg 中不存在由 bid 的 sha1 派生的 gap 节点 id 则写入 contextual 的 gap_hint 节点并返回该 id;该 id 已存在或 cg.add 抛异常时返回 None;
859
- def _write_gap(cg, bid, step, actor):
860
- """把待补线索写成 contextual 的 ``gap_hint`` 节点(幂等,非事实断言)。"""
861
- nid = "gap_%s" % hashlib.sha1(bid.encode("utf-8")).hexdigest()[:10]
862
- try:
863
- if cg.get(nid):
864
- return None
865
- except Exception: # noqa: BLE001
866
- pass
867
- content = ("# 功能名:盲区补全线索\n"
868
- "# 生效条件:%s\n"
869
- "# 子功能:为盲区补上条件/路径(待补,非既有事实)\n"
870
- "# 执行:%s\n"
871
- "# 不适用条件:结构性不可知\n"
872
- % (step.get("description") or bid, step.get("hint") or ""))
873
- try:
874
- cg.add(nid, content, layer="contextual",
875
- tags=["gap_hint", "learn"], importance=0.3,
876
- verification_basis="data", actor=actor,
877
- gap_blindspot=bid, gap_terminal=step.get("terminal"))
878
- return nid
879
- except Exception as exc: # noqa: BLE001
880
- step["write_error"] = "%s: %s" % (type(exc).__name__, exc)
881
- return None
882
-
883
-
884
- # 生效条件:blindspot_id 给出但 find_blindspot 找不到时返回 ok=False 与 status='not_found';否则逐条按 unknowable/no_anchor/unresolved/carried/resolved 判定,apply 为真且 cg 可写时才把 carried/unresolved 落成幂等 gap_hint 节点;
885
- def learn_blindspots(cg, blindspot_id=None, limit=LEARN_MAX_STEPS,
886
- horizon=HORIZON_DEFAULT, max_branches=MAX_BRANCHES_DEFAULT,
887
- apply=False, actor="insight", **extra):
888
- """盲区学习闭环(P2):盲区 → 路线假设 → 终态判定 → 登记。
889
-
890
- 终态五态(诚实优先,绝不编造):
891
-
892
- - ``unknowable`` 盲区声明结构性不可知 → 不生成路线;
893
- - ``no_anchor`` 描述检索不到锚点 → 无法起推;
894
- - ``unresolved`` 无任何可用路线 → 回填为待补线索;
895
- - ``carried`` 有路线但终点未达可判定态 → 记为待验证假设;
896
- - ``resolved`` 有路线且终点已在「知识层 + 五要素齐全」→ 认定补全。
897
-
898
- ``apply=True`` 时把 carried/unresolved 落成 contextual 的 ``gap_hint`` 节点
899
- (待补线索,非既有事实);节点 id 由盲区 id 派生 ⇒ 重复执行幂等。
900
- """
901
- from . import metacognition
902
- if blindspot_id:
903
- one = find_blindspot(cg, blindspot_id)
904
- if not one:
905
- return {"ok": False, "action": "learn", "status": "not_found",
906
- "reason": "未找到盲区:%s" % blindspot_id, "steps": [],
907
- "summary": {}}
908
- items = [one]
909
- else:
910
- # 注意:metacognition.blindspots 返回的是**报表 dict**,不是条目列表。
911
- # 必须显式取 unresolved(未解问题,含 node_id/content)与 items(盲区聚类,
912
- # 含 query/blindspot/defer)两段,并归一成 find_blindspot 的同构条目,
913
- # 否则 list(dict) 只会拿到键名并在 dict(it) 处崩溃。
914
- try:
915
- blind = metacognition.blindspots(cg) or {}
916
- except Exception: # noqa: BLE001
917
- blind = {}
918
- items = []
919
- for it in (blind.get("items") or []):
920
- q = str(it.get("query") or "").strip()
921
- if not q:
922
- continue
923
- items.append({"id": q, "kind": "blindspot_cluster", "description": q,
924
- "blindspot": it.get("blindspot"), "defer": it.get("defer")})
925
- for u in (blind.get("unresolved") or []):
926
- nid = str(u.get("node_id") or "").strip()
927
- if not nid:
928
- continue
929
- items.append({"id": nid, "kind": "unresolved",
930
- "description": u.get("content") or ""})
931
- steps, written = [], []
932
- summary = {"unknowable": 0, "no_anchor": 0, "unresolved": 0,
933
- "carried": 0, "resolved": 0}
934
- for it in items[:max(1, int(limit))]:
935
- bs = dict(it or {})
936
- bid = str(bs.get("id") or bs.get("query") or "").strip()
937
- desc = str(bs.get("description") or bs.get("query") or bid)
938
- pred = predictability(bs)
939
- step = {"blindspot_id": bid, "description": desc[:200],
940
- "predictability": pred, "routes": 0, "terminal": None,
941
- "terminal_node": None, "hint": "", "written": None}
942
- if pred == "unknowable":
943
- step["terminal"] = "unknowable"
944
- step["hint"] = "盲区声明结构性不可知:不生成路线(拒绝编造)"
945
- else:
946
- res = (routes_from_blindspot(cg, bid, horizon=horizon,
947
- max_branches=max_branches, limit=3)
948
- if bid else {"status": "no_start", "routes": []})
949
- rts = list(res.get("routes") or [])
950
- step["routes"] = len(rts)
951
- if res.get("status") in ("no_start", "no_anchor",
952
- "blindspot_not_found", "unpredictable"):
953
- step["terminal"] = "no_anchor"
954
- step["hint"] = "无可检索锚点:需先补描述或入口条件"
955
- elif not rts:
956
- step["terminal"] = "unresolved"
957
- step["hint"] = "无可用路线:需补因果边或放宽检索条件"
958
- else:
959
- settled = [r for r in rts if _is_settled(cg, _terminal_node(r))]
960
- if settled:
961
- step["terminal"] = "resolved"
962
- step["terminal_node"] = _terminal_node(settled[0])
963
- step["hint"] = "已存在通往「知识层 + 五要素齐全」终点的路线"
964
- else:
965
- step["terminal"] = "carried"
966
- step["terminal_node"] = _terminal_node(rts[0])
967
- step["hint"] = "路线终点未达可判定态:记为待验证假设"
968
- summary[step["terminal"]] = summary.get(step["terminal"], 0) + 1
969
- if apply and step["terminal"] in ("carried", "unresolved") and bid:
970
- step["written"] = _write_gap(cg, bid, step, actor)
971
- if step["written"]:
972
- written.append(step["written"])
973
- steps.append(step)
974
- rec = {"type": "learn_step", "t": time.time(), "actor": actor}
975
- rec.update(step)
976
- _learn_append(cg, rec)
977
- return {"ok": True, "action": "learn", "apply": bool(apply),
978
- "steps": steps, "summary": summary, "written": written,
979
- "note": ("apply=True:carried/unresolved 已落 gap_hint 待补线索;"
980
- "resolved 仅表示已有可判定终点,未改动任何事实层节点")}
981
-
982
-
983
- # 生效条件:传入 cg 时基于 _read_log(cg) 统计 type 以 'predict_routes' 开头的调用数与 n_routes 合计、feedback 样本与命中率(无样本取常量 BASE_HIT_RATE),并附 channel_posterior 与 dynamic_hit_threshold,recent 取末尾 limit 条(limit 默认 20,为 0 时为空);
984
- def stats(cg, limit=20):
985
- """预测统计:调用数、生成路线数、反馈样本、命中率、动态阈值。"""
986
- recs = _read_log(cg)
987
- calls = [r for r in recs
988
- if str(r.get("type") or "").startswith("predict_routes")]
989
- h = [bool(r.get("hit")) for r in recs if r.get("type") == "feedback"]
990
- return {"ok": True, "calls": len(calls),
991
- "routes_generated": sum(int(r.get("n_routes") or 0) for r in calls),
992
- "feedback_samples": len(h), "hits": sum(1 for x in h if x),
993
- "hit_rate": (round(sum(1 for x in h if x) / len(h), 4) if h
994
- else BASE_HIT_RATE),
995
- "beta": channel_posterior(cg),
996
- "dynamic": dynamic_hit_threshold(cg),
997
- "recent": recs[-int(limit):] if limit else []}
998
-
999
-
1000
- # ---------------------------------------------------------------- 因果推理
1001
-
1002
- # 生效条件:传入 cg 与 path 且 path 长度不少于 2 时,对每对相邻节点返回其在邻接表中且关系类型属于常量 CAUSAL_BRANCH_TYPES 的边条件(无匹配则为空串)逐跳列表,path 更短则返回空列表;
1003
- def _path_conditions(cg, path):
1004
- """路径上每跳的边条件(与节点对一一对应)。"""
1005
- from . import chain
1006
- adj = chain.adjacency(cg)
1007
- conds = []
1008
- for i in range(len(path) - 1):
1009
- a, b = path[i], path[i + 1]
1010
- cond = ""
1011
- for tgt, e in adj.get(a) or []:
1012
- if tgt == b and chain.edge_rel(e) in CAUSAL_BRANCH_TYPES:
1013
- cond = chain.edge_condition(e) or ""
1014
- break
1015
- conds.append({"from": a, "to": b, "condition": cond})
1016
- return conds
1017
-
1018
-
1019
- # 生效条件:a_id 或 b_id 去空后为空时返回 ok=False 与 error='missing_node';a_id 等于 b_id 返回可达且长度 0;否则仅在常量 CAUSAL_BRANCH_TYPES 边上于 max_depth(下限 1)内 BFS,可达返回路径与逐跳 conditions,不可达返回 reachable=False;
1020
- def causal_path(cg, a_id, b_id, max_depth=5):
1021
- """因果路径推理:A 能否沿因果/时序边到达 B(BFS,最短路径)。
1022
-
1023
- 这是 D-002 过滤门的完整语义(不止直接边):可达 → 可解释的因果关联;
1024
- 不可达 → 语义邻近只是共现,不得当作因果用于预测。
1025
- """
1026
- from . import chain
1027
- start, goal = str(a_id or "").strip(), str(b_id or "").strip()
1028
- if not start or not goal:
1029
- return {"ok": False, "error": "missing_node",
1030
- "detail": "需要 a_id 与 b_id"}
1031
- if start == goal:
1032
- return {"ok": True, "reachable": True, "path": [start], "length": 0,
1033
- "conditions": []}
1034
- depth_cap = max(1, int(max_depth))
1035
- adj = chain.adjacency(cg)
1036
- prev = {start: None}
1037
- queue = [(start, 0)]
1038
- while queue:
1039
- cur, d = queue.pop(0)
1040
- if d >= depth_cap:
1041
- continue
1042
- for tgt, e in adj.get(cur) or []:
1043
- if chain.edge_rel(e) not in CAUSAL_BRANCH_TYPES or tgt in prev:
1044
- continue
1045
- prev[tgt] = cur
1046
- if tgt == goal:
1047
- path, node = [], goal
1048
- while node is not None:
1049
- path.append(node)
1050
- node = prev[node]
1051
- path.reverse()
1052
- return {"ok": True, "reachable": True, "path": path,
1053
- "length": len(path) - 1,
1054
- "conditions": _path_conditions(cg, path)}
1055
- queue.append((tgt, d + 1))
1056
- return {"ok": True, "reachable": False, "path": None, "length": None,
1057
- "max_depth": depth_cap,
1058
- "note": "无因果路径:两者最多只是语义邻近(伪因果防护)"}
1059
-
1060
-
1061
- # ---------------------------------------------------------------- 自描述
1062
-
1063
- # 生效条件:不适用(无必需形参与模块级常量)
1064
- def catalog():
1065
- """决策编号 / 权重 / 校准参数 / 与 AEIS 的差异(供协议对照验证)。"""
1066
- return {
1067
- "module": "predict",
1068
- "theory": "AEIS prediction.py · "
1069
- "PREDICTION-COMPLETION-PLAN-REV1-20260813-001",
1070
- "channels": ["通道3 生成式(因果路线图)",
1071
- "通道4 语义式(经 D-002 伪因果过滤门)"],
1072
- "decisions": {
1073
- "D-001": "局部路径生成 + uncertainty_bound(候选未来,非必然未来)",
1074
- "D-002": "语义邻近过滤门:因果链 / 共同父节点 / 偏好权重 > 0.5",
1075
- "D-003": "局部线性近似 + extrapolation_validity(smooth/jump/unknown)",
1076
- "D-004": "T_pred 四维评分 trend/boundary/verification/balance",
1077
- "D-005": "AttentionPolicy 适配器 + 降级(边置信度排序)",
1078
- "D-006": "命中率动态校准(样本 < 50 不触发反思)",
1079
- "P-T-73/74": "通道贝叶斯后验(Beta-Bernoulli,置信度≠可信度;"
1080
- "feedback channel 参数 + channel_posterior 查询)",
1081
- },
1082
- "weights": _weights(), # 第五维 opt-in 时同步扩张(防文档漂移)
1083
- "calibration": {"min_samples": MIN_SAMPLES,
1084
- "base_hit_rate": BASE_HIT_RATE,
1085
- "edge_boost": EDGE_BOOST,
1086
- "hit_history_max": HIT_HISTORY_MAX},
1087
- "limits": {"horizon_default": HORIZON_DEFAULT,
1088
- "horizon_hard": HORIZON_HARD,
1089
- "max_branches_default": MAX_BRANCHES_DEFAULT,
1090
- "max_branches_hard": MAX_BRANCHES_HARD},
1091
- "semantic": {"top_k": SEMANTIC_TOP_K, "min_sim": SEMANTIC_MIN_SIM,
1092
- "induced_confidence": SEMANTIC_CONF},
1093
- "branch_types": list(CAUSAL_BRANCH_TYPES),
1094
- "differs_from_aeis": [
1095
- "语义邻近回退中文二元组 Jaccard(AEIS 优先语义坐标)",
1096
- "命中历史持久化在 _prediction.jsonl(AEIS 在内存)",
1097
- "DFS 增加 path 防环(AEIS 仅靠 horizon 截断)",
1098
- ],
1099
- "actions": ["routes", "routes_from_blindspot", "feedback", "stats",
1100
- "causal_path", "causal_gate", "catalog"],
1
+ # -*- coding: utf-8 -*-
2
+ """生成式预测:候选未来路线(**非必然未来**)。
3
+
4
+ 对齐 AEIS `prediction.py`(PREDICTION-COMPLETION-PLAN-REV1-20260813-001)
5
+ 四通道预测引擎的通道 3(生成式·因果路线图)+ 通道 4(语义式,经因果过滤门):
6
+
7
+ - **D-001** 局部路径生成 + `uncertainty_bound`(候选未来,非必然未来)
8
+ - **D-002** 语义邻近过滤门(伪因果防护):语义候选必须能"说清关系"才准入
9
+ - **D-003** 局部线性近似 + `extrapolation_validity`(smooth/jump/unknown)
10
+ - **D-004** 评分对齐 2.10 节 T_pred 四维度:
11
+ trend 0.40 · boundary 0.20 · verification 0.25 · balance 0.15
12
+ - **D-005** AttentionPolicy 适配器 + 降级路径(无策略时回退边置信度排序)
13
+ - **D-006** 命中率动态校准(MIN_SAMPLES=50 · 2.7.2 动态死区)
14
+
15
+ 盲区驱动:`blindspot_id` 指向 unresolved 节点/盲区邻域;声明
16
+ `可预测性:unknowable` 的盲区**不生成路线**(结构性不可知)。
17
+
18
+ 与 AEIS 的三处有意差异(代码内均已标注):
19
+ 1. 语义邻近回退到**中文二元组 Jaccard**(AEIS 优先语义坐标,无坐标时同回退)
20
+ 2. 命中历史**持久化**在 `_prediction.jsonl`(AEIS 在内存),跨进程可审计
21
+ 3. DFS 增加 `path` 防环(AEIS 仅靠 horizon 截断),md 图允许显式环边
22
+
23
+ 纯标准库 · 零外部依赖。
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import hashlib
28
+ import os
29
+ import re
30
+ import time
31
+
32
+ from .fsutil import append_jsonl, read_jsonl
33
+
34
+ # ---------------------------------------------------------------- 常量
35
+ HORIZON_DEFAULT = 3
36
+ MAX_BRANCHES_DEFAULT = 5
37
+ HORIZON_HARD = 16
38
+ MAX_BRANCHES_HARD = 32
39
+
40
+ MIN_SAMPLES = 50 # D-006 最低样本量
41
+ BASE_HIT_RATE = 0.40 # 基线阈值(工程初值,非协议承诺)
42
+ HIT_HISTORY_MAX = 200 # 命中历史滚动窗口
43
+ EDGE_BOOST = 0.05 # 命中 → 因果/时序边置信度增量
44
+
45
+ # Beta-Bernoulli 先验强度(等效先验样本数 κ,P-T-73/74 通道贝叶斯):
46
+ # α0 = BASE_HIT_RATE·κ,β0 = (1−BASE_HIT_RATE)·κ。κ 越大小样本越向基线收缩。
47
+ # 性质:先验均值 = BASE_HIT_RATE(与 hit_rate() 空历史兜底同值,口径无缝)。
48
+ PRIOR_STRENGTH = 20
49
+
50
+ CAUSAL_BRANCH_TYPES = ("causal", "sequential")
51
+ SEMANTIC_TOP_K = 5
52
+ SEMANTIC_MIN_SIM = 0.05
53
+ SEMANTIC_CONF = 0.4 # 语义诱导候选固定置信度(AEIS 同值)
54
+ PREFERENCE_THRESHOLD = 0.5 # D-005 偏好权重准入线
55
+
56
+ W_TREND = 0.40 # D-004 四维权重(AEIS 2.10 节)
57
+ W_BOUNDARY = 0.20
58
+ W_VERIFICATION = 0.25
59
+ W_BALANCE = 0.15
60
+
61
+ # D_meta 第五维(opt-in,方案 §2.3):默认 0 = 不参与 composite、不落键、
62
+ # SORT_KEYS 不扩张(默认返回与四维时代逐字节一致,默认零变更纪律)。
63
+ # 显式启用:PREDICTION_META_DIM=<权重>(>0 才生效)
64
+ # PREDICTION_META_PROXY=<代理名>(缺省 unmodeled_growth;**只取单一
65
+ # 代理**——三代理加权合成违反智能论3.4 DEV-002a)
66
+ # 纪律:导入期读一次环境变量,改值须重载模块;catalog().weights 同步扩张。
67
+ try:
68
+ W_META = float(os.environ.get("PREDICTION_META_DIM", "0") or 0.0)
69
+ except ValueError: # 非法值按关闭处理:默认零变更优先,不炸导入
70
+ W_META = 0.0
71
+ W_META = W_META if W_META > 0 else 0.0
72
+ META_PROXY = os.environ.get("PREDICTION_META_PROXY", "unmodeled_growth")
73
+
74
+ SORT_KEYS = ("composite", "trend", "verification", "boundary", "balance")
75
+ if W_META > 0: # 第五维开启时才扩张排序键(默认零变更)
76
+ SORT_KEYS = SORT_KEYS + ("meta_pressure",)
77
+ LOG_FILE = "_prediction.jsonl"
78
+
79
+
80
+ # 生效条件:不适用(无必需形参与分支),恒返回四维权重的自描述 dict;仅当 W_META>0(PREDICTION_META_DIM 显式启用第五维)时追加 meta_pressure 键;
81
+ def _weights():
82
+ """自描述权重:默认四维逐字节不变,第五维 opt-in 时同步扩张。"""
83
+ w = {"trend": W_TREND, "boundary": W_BOUNDARY,
84
+ "verification": W_VERIFICATION, "balance": W_BALANCE}
85
+ if W_META > 0:
86
+ w["meta_pressure"] = W_META
87
+ return w
88
+
89
+ # 锚点解析纪律(盲区 → 锚点):推断脚手架与负记忆都不得充当锚点。
90
+ # - `gap_hint`(待补线索)与 `scene`(情景重构产物)是「推断得出的引子/回放」,
91
+ # 本身不构成可起推的事实;尤其 gap_hint 会把盲区描述逐字抄进自己的
92
+ # `# 生效条件`,因而在该盲区的检索里必然排第一,若被当成锚点就形成
93
+ # 「线索 → 0 条路线 → 永远 unresolved」的自我污染,learn 重复执行也不再幂等。
94
+ # - `unresolved` / `rejected` 层是负记忆(已否决/未决问题),只能作为覆盖率
95
+ # 提示,不能作为起点。
96
+ ANCHOR_FETCH_K = 5
97
+ ANCHOR_SKIP_TAGS = ("gap_hint", "scene")
98
+ ANCHOR_SKIP_LAYERS = ("unresolved", "rejected")
99
+
100
+
101
+ # ---------------------------------------------------------------- 基础
102
+
103
+ # 生效条件:传入 cg 具 index 键且其 'nodes' 为非空映射时返回该映射,否则返回空字典 {};
104
+ def _nodes(cg):
105
+ return ((getattr(cg, "index", None) or {}).get("nodes") or {})
106
+
107
+
108
+ # 生效条件:传入 cg 具 root 属性时返回 root 与模块级常量 LOG_FILE 的路径拼接,缺 root 则以 '.' 拼接;
109
+ def log_path(cg):
110
+ return os.path.join(getattr(cg, "root", "."), LOG_FILE)
111
+
112
+
113
+ # 生效条件:传入 cg 与 rec 时向 log_path(cg) 追加一条 JSONL 记录,OSError 被吞掉且无返回值;
114
+ def _append(cg, rec):
115
+ try:
116
+ append_jsonl(log_path(cg), rec)
117
+ except OSError:
118
+ pass
119
+
120
+
121
+ # 生效条件:传入 cg 可读出 JSONL 记录时返回记录列表,读取抛 OSError 或 ValueError 时返回 [],limit 为正时仅返回末尾 limit 条;
122
+ def _read_log(cg, limit=0):
123
+ try:
124
+ recs = list(read_jsonl(log_path(cg)))
125
+ except (OSError, ValueError):
126
+ recs = []
127
+ if limit and int(limit) > 0:
128
+ recs = recs[-int(limit):]
129
+ return recs
130
+
131
+
132
+ # 生效条件:传入 cg 的留痕中存在 type=='feedback' 的记录时返回其 hit 布尔序列末尾 limit 条(limit 默认常量 HIT_HISTORY_MAX),无此类记录则返回空列表;
133
+ def _hit_history(cg, limit=HIT_HISTORY_MAX):
134
+ """D-006 命中历史(持久化 · 差异 2):只取 feedback 记录。"""
135
+ return [bool(r.get("hit")) for r in _read_log(cg)
136
+ if r.get("type") == "feedback"][-int(limit):]
137
+
138
+
139
+ # 生效条件:传入 cg 与 nid 时返回邻接表中 nid 的出边列表 [(目标 id, 边字典)],该键无出边则为 [];
140
+ def _out_edges(cg, nid):
141
+ """出边(含层级边):[(target_id, edge_dict)],复用 chain 邻接缓存。"""
142
+ from . import chain
143
+ return list((chain.adjacency(cg).get(nid) or []))
144
+
145
+
146
+ # 生效条件:传入 cg 与 nid 时返回邻接表中所有以 nid 为目标的源节点集合,无入边则为空集;
147
+ def _in_sources(cg, nid):
148
+ """入边来源集合(父节点):用于 D-002「共同父节点」结构模式判定。"""
149
+ from . import chain
150
+ adj = chain.adjacency(cg)
151
+ srcs = set()
152
+ for src, outs in adj.items():
153
+ for tgt, _e in outs:
154
+ if tgt == nid:
155
+ srcs.add(src)
156
+ return srcs
157
+
158
+
159
+ # 生效条件:传入 edge 的 'confidence' 可转 float 时返回裁剪到 [0.0,1.0] 的值,取键失败或转换抛 AttributeError/TypeError/ValueError 时返回 1.0;
160
+ def _edge_conf(edge):
161
+ try:
162
+ return max(0.0, min(1.0, float(edge.get("confidence", 1.0))))
163
+ except (AttributeError, TypeError, ValueError):
164
+ return 1.0
165
+
166
+
167
+ # ---------------------------------------------------------------- D-002 过滤门
168
+
169
+ # 生效条件:causal_link 成立条件为 a_id 的出边中存在目标 b_id 且其关系类型属于模块级常量 CAUSAL_BRANCH_TYPES,成立返回 True,否则返回 False;
170
+ def has_causal_link(cg, a_id, b_id):
171
+ """直接因果/时序边:A → B 已声明(直通,无需过滤)。"""
172
+ from . import chain
173
+ for tgt, e in _out_edges(cg, a_id):
174
+ if tgt == b_id and chain.edge_rel(e) in CAUSAL_BRANCH_TYPES:
175
+ return True
176
+ return False
177
+
178
+
179
+ # 生效条件:传入 cg、a_id、b_id 时,a_id 与 b_id 的入边来源集合交集非空返回 True,交集为空返回 False;
180
+ def has_structural_pattern(cg, a_id, b_id):
181
+ """结构模式:A、B 共享父节点(可解释的间接关联 → 伪因果豁免)。"""
182
+ return bool(_in_sources(cg, a_id) & _in_sources(cg, b_id))
183
+
184
+
185
+ # 生效条件:cg 具 attention_policy 且非 None、且 get_weights() 返回可取值的映射时返回 float(weights.get(node_id, 0.0)),策略缺失、取权重抛异常或转换失败时返回 0.0;
186
+ def preference_weight(cg, node_id):
187
+ """D-005:AttentionPolicy 适配器(duck-typed `get_weights()`)。
188
+
189
+ md_cg 默认无策略 → 0.0(对齐 AEIS:attention_policy=None 时该准入
190
+ 条件不生效,只靠因果链/结构模式两道门)。
191
+ """
192
+ pol = getattr(cg, "attention_policy", None)
193
+ if pol is None:
194
+ return 0.0
195
+ try:
196
+ weights = pol.get_weights() or {}
197
+ except Exception:
198
+ return 0.0
199
+ try:
200
+ return float(weights.get(node_id, 0.0))
201
+ except (TypeError, ValueError):
202
+ return 0.0
203
+
204
+
205
+ # 生效条件:cg/a_id/b_id 下 has_causal_link 成立返回 (True,'causal_link'),否则 has_structural_pattern 成立返回 (True,'structural_pattern'),否则 preference_weight(cg,b_id) 大于常量 PREFERENCE_THRESHOLD 返回 (True,'preference_weight'),三者皆不成立返回 (False,'rejected_semantic_only');
206
+ def causal_gate(cg, a_id, b_id):
207
+ """D-002 伪因果过滤门 → (准入?, 理由)。
208
+
209
+ 语义邻近候选必须满足其一,否则视为「说不出关系的伪因果」而拒绝:
210
+ 1. 已有因果/时序边(直通)
211
+ 2. 共同父节点(结构模式)
212
+ 3. 偏好权重 > 0.5(D-005 策略显式授权)
213
+ """
214
+ if has_causal_link(cg, a_id, b_id):
215
+ return True, "causal_link"
216
+ if has_structural_pattern(cg, a_id, b_id):
217
+ return True, "structural_pattern"
218
+ if preference_weight(cg, b_id) > PREFERENCE_THRESHOLD:
219
+ return True, "preference_weight"
220
+ return False, "rejected_semantic_only"
221
+
222
+
223
+ # ---------------------------------------------------------------- 语义邻近
224
+
225
+ # 生效条件:cg.get(node_id) 的 content 去空后非空时以前 200 字符检索,返回相似度不低于常量 SEMANTIC_MIN_SIM 且非自身的至多 k 个 (nid, similarity);content 为空或检索抛异常时返回 [];
226
+ def semantic_neighbors(cg, node_id, k=SEMANTIC_TOP_K):
227
+ """语义邻近候选:[(node_id, similarity)]。
228
+
229
+ 差异 1:AEIS 优先语义坐标(protocol/hierarchy/condition 三维),无坐标
230
+ 时回退 `char_bigram_jaccard`;md_cg 无坐标,直接用检索器的二元组打分
231
+ 实现该回退路径(非新算法)。
232
+ """
233
+ node = cg.get(node_id) or {}
234
+ text = (node.get("content") or "").strip()
235
+ if not text:
236
+ return []
237
+ try:
238
+ results, _meta = cg.search(text[:200], k=int(k) + 3,
239
+ record=False, judge=False)
240
+ except Exception:
241
+ return []
242
+ out = []
243
+ for item in results:
244
+ if isinstance(item, (tuple, list)):
245
+ nd, score = item[0], float(item[1])
246
+ else:
247
+ nd, score = item, 0.0
248
+ if not isinstance(nd, dict):
249
+ continue
250
+ nid = nd.get("id")
251
+ if not nid:
252
+ p = str(nd.get("path") or "")
253
+ nid = os.path.basename(p)[:-3] if p.endswith(".md") else None
254
+ if not nid or nid == node_id or score < SEMANTIC_MIN_SIM:
255
+ continue
256
+ out.append((nid, round(score, 4)))
257
+ if len(out) >= int(k):
258
+ break
259
+ return out
260
+
261
+
262
+ # ---------------------------------------------------------------- D-001 分支
263
+
264
+ # 生效条件:传入 cg 与 node_id 时,其出边中关系类型属于常量 CAUSAL_BRANCH_TYPES 者直通为 causal 候选,semantic 为真时再并入经 causal_gate 放行的语义邻近候选,按 confidence 降序、node_id 升序返回;
265
+ def branch_candidates(cg, node_id, semantic=True):
266
+ """D-001 局部分支候选:因果/时序边直通 + 语义邻近(经 D-002 门)。"""
267
+ from . import chain
268
+ cands, seen = [], set()
269
+ for tgt, e in _out_edges(cg, node_id):
270
+ if tgt in seen:
271
+ continue
272
+ rel = chain.edge_rel(e)
273
+ if rel not in CAUSAL_BRANCH_TYPES:
274
+ continue
275
+ seen.add(tgt)
276
+ cands.append({"node_id": tgt, "confidence": _edge_conf(e),
277
+ "source": "causal", "relation_type": rel,
278
+ "condition": chain.edge_condition(e) or ""})
279
+ if semantic:
280
+ for tgt, sim in semantic_neighbors(cg, node_id):
281
+ if tgt in seen:
282
+ continue
283
+ ok, why = causal_gate(cg, node_id, tgt)
284
+ if not ok:
285
+ continue
286
+ seen.add(tgt)
287
+ cands.append({"node_id": tgt, "confidence": SEMANTIC_CONF,
288
+ "source": "semantic_induced", "relation_type": why,
289
+ "condition": "", "similarity": sim})
290
+ cands.sort(key=lambda c: (-c["confidence"], c["node_id"]))
291
+ return cands
292
+
293
+
294
+ # ---------------------------------------------------------------- D-003 / D-004
295
+
296
+ # 生效条件:conf 可转 float 时裁剪到 [0,1] 得 c,按 base=1-c 返回 confidence=c 与裁剪到 [0,1] 的 [c-base*0.5, c+base*0.5] 区间,转换失败则按 c=0.0 计算;
297
+ def _uncertainty(conf):
298
+ """D-001 不确定带:base=1-conf,上下界 = conf ± base*0.5(AEIS 同式)。"""
299
+ try:
300
+ c = max(0.0, min(1.0, float(conf)))
301
+ except (TypeError, ValueError):
302
+ c = 0.0
303
+ base = 1.0 - c
304
+ return {"confidence": round(c, 4),
305
+ "lower": round(max(0.0, c - base * 0.5), 4),
306
+ "upper": round(min(1.0, c + base * 0.5), 4),
307
+ "method": "linear_local_approx"}
308
+
309
+
310
+ # 生效条件:传入 cg 与 path 且 path 非空时,返回其中 tags 含 'boundary' 或 has_neg_conditions,或 content 匹配「不适用|不确定|边界|盲区」的节点占比;path 为空返回 0.0;
311
+ def _boundary_consistency(cg, path):
312
+ """boundary 维:路径节点是否声明了适用边界/不确定条件(0-1)。"""
313
+ if not path:
314
+ return 0.0
315
+ hit = 0
316
+ for nid in path:
317
+ e = _nodes(cg).get(nid) or {}
318
+ tags = set(e.get("tags") or [])
319
+ if "boundary" in tags or e.get("has_neg_conditions"):
320
+ hit += 1
321
+ continue
322
+ node = cg.get(nid) or {}
323
+ if re.search(r"不适用|不确定|边界|盲区", node.get("content") or ""):
324
+ hit += 1
325
+ return round(hit / len(path), 4)
326
+
327
+
328
+ # 生效条件:传入 route 时返回其 sources 与 relations 去重并集大小除以 4.0 且上限 1.0 的值,两键皆缺时返回 0.0;
329
+ def _branch_diversity(route):
330
+ """balance 维:路径覆盖的推理通道维度数 / 4。
331
+
332
+ AEIS 用语义坐标的维度数;md_cg 无语义坐标 → 用「来源通道
333
+ (causal / semantic_induced)+ 边关系类型」作维度,保持 /4.0 归一化,
334
+ 语义仍是「防单一偏好主导」。
335
+ """
336
+ dims = set(route.get("sources") or []) | set(route.get("relations") or [])
337
+ return round(min(1.0, len(dims) / 4.0), 4)
338
+
339
+
340
+ # 生效条件:route 的 'confs' 长度不少于 2 时,相邻差绝对值最大值不超过 0.35 返回 'smooth' 否则 'jump';长度不足 2 或无可算步长返回 'unknown';
341
+ def extrapolation_validity(route):
342
+ """D-003:局部线性外推有效性(smooth / jump / unknown)。"""
343
+ confs = route.get("confs") or []
344
+ if len(confs) < 2:
345
+ return "unknown"
346
+ steps = [confs[i] - confs[i + 1] for i in range(len(confs) - 1)]
347
+ if not steps:
348
+ return "unknown"
349
+ return "smooth" if max(abs(s) for s in steps) <= 0.35 else "jump"
350
+
351
+
352
+ # 生效条件:传入 cg 且 _hit_history(cg) 非空时返回其命中占比,序列为空时返回模块级常量 BASE_HIT_RATE;
353
+ def hit_rate(cg):
354
+ """历史命中率(无样本 → 基线 0.40,对齐 D-006)。"""
355
+ h = _hit_history(cg)
356
+ if not h:
357
+ return BASE_HIT_RATE
358
+ return round(sum(1 for x in h if x) / len(h), 4)
359
+
360
+
361
+ # ---------------------------------------------------------------- P-T-73/74 通道贝叶斯
362
+
363
+ # 生效条件:传入 hits 序列时以 base*prior_k 与 (1-base)*prior_k 为先验(prior_k 默认常量 PRIOR_STRENGTH、base 默认常量 BASE_HIT_RATE),按命中数 k 与总数 n 返回 alpha/beta/mean/std/ci95/samples/hits;hits 为空即为纯先验,mean 等于 base;
364
+ def beta_posterior(hits, prior_k=PRIOR_STRENGTH, base=BASE_HIT_RATE):
365
+ """Beta-Bernoulli 后验(纯函数):命中序列 → 后验参数与区间。
366
+
367
+ 置信度≠可信度(P-T-73/74):`hit_rate` 是滑动计数(小样本过度自信:
368
+ n=5 全命中 → 1.0);后验均值 = (α0+k)/(α0+β0+n) 随样本量向基线收缩
369
+ (n=5 全命中 → 0.52),诚实反映「证据还很少」。置信度的动态死区
370
+ (dynamic_hit_threshold)不动——本函数只提供**更诚实的可信度估计面**,
371
+ 默认链路零行为变更。
372
+
373
+ 返回:{alpha, beta, mean, std, ci95, samples, hits};
374
+ 空序列 → 先验(mean=base,与 hit_rate 空历史兜底同值)。
375
+ ci95 用正态近似 mean±1.96σ(零依赖;样本大时近似良好,小样本区间
376
+ 偏窄,故另带 samples 供阅读方自判权重)。
377
+ """
378
+ a0 = float(base) * float(prior_k)
379
+ b0 = (1.0 - float(base)) * float(prior_k)
380
+ xs = [bool(x) for x in (hits or [])]
381
+ k = sum(1 for x in xs if x)
382
+ n = len(xs)
383
+ a, b = a0 + k, b0 + (n - k)
384
+ mean = a / (a + b)
385
+ var = (a * b) / ((a + b) ** 2 * (a + b + 1.0))
386
+ std = var ** 0.5
387
+ return {"alpha": round(a, 4), "beta": round(b, 4),
388
+ "mean": round(mean, 4), "std": round(std, 4),
389
+ "ci95": [round(max(0.0, mean - 1.96 * std), 4),
390
+ round(min(1.0, mean + 1.96 * std), 4)],
391
+ "samples": n, "hits": k}
392
+
393
+
394
+ # 生效条件:传入 cg 时把 type=='feedback' 的记录按 channel 字段(缺失归 'unlabeled')分组为 {channel: [hit...]},仅取末尾 limit 条(limit 默认常量 HIT_HISTORY_MAX);
395
+ def channel_history(cg, limit=HIT_HISTORY_MAX):
396
+ """feedback 留痕按通道分组(P-T-74 通道级可信度)→ {channel: [hit...]}。
397
+
398
+ 通道 = 预测发出的面(feedback 的 `channel` 参数,如 "causal"/"semantic")。
399
+ 早期留痕无 channel 字段 → 归 "unlabeled"(诚实标注,不冒充分通道)。
400
+ """
401
+ out = {}
402
+ for r in _read_log(cg, limit=limit):
403
+ if r.get("type") != "feedback":
404
+ continue
405
+ ch = str(r.get("channel") or "unlabeled")
406
+ out.setdefault(ch, []).append(bool(r.get("hit")))
407
+ return out
408
+
409
+
410
+ # 生效条件:channel 非 None 时返回该通道(无记录即先验)的 beta_posterior 并附 channel 键,channel 为 None 时返回 {'all': 全量后验, 'channels': 各通道后验};
411
+ def channel_posterior(cg, channel=None, limit=HIT_HISTORY_MAX):
412
+ """通道级后验查询:channel=None → 全量+分通道;channel=str → 单通道。"""
413
+ hist = channel_history(cg, limit=limit)
414
+ if channel is not None:
415
+ ch = str(channel)
416
+ post = beta_posterior(hist.get(ch) or [])
417
+ post["channel"] = ch
418
+ return post
419
+ all_hits = [x for hits in hist.values() for x in hits]
420
+ return {"all": beta_posterior(all_hits),
421
+ "channels": {ch: beta_posterior(hits)
422
+ for ch, hits in sorted(hist.items())}}
423
+
424
+
425
+ # 生效条件:传入 cg 与 route 时,trend 取 route 的 confidence、boundary 取 _boundary_consistency(cg, route['path'])、verification 缺省时取 hit_rate(cg)、balance 取 _branch_diversity(route),并按 W_TREND/W_BOUNDARY/W_VERIFICATION/W_BALANCE 加权返回 composite;仅当 W_META>0(PREDICTION_META_DIM 显式启用第五维)时才额外取 d_meta.pressure(d_meta.compute(cg), META_PROXY) 作第五维、落 meta_pressure 键、把 W_META*meta_pressure 计入 composite,关闭时返回键集合与 composite 逐字节不变;
426
+ def score_route(cg, route, verification=None):
427
+ """D-004 T_pred 四维评分 + **D_meta 第五维(opt-in)**。
428
+
429
+ `verification` 跨路线共享(来自命中率)。第五维 `meta_pressure` 取
430
+ **单一指定代理**(`PREDICTION_META_PROXY`,缺省 `unmodeled_growth`)——
431
+ **不做三代理加权合成**(智能论3.4 §2.7.0 DEV-002a)。默认权重 0:
432
+ 不落键、不改 composite、SORT_KEYS 不扩张(默认零变更纪律)。
433
+ """
434
+ trend = float(route.get("confidence") or 0.0)
435
+ boundary = _boundary_consistency(cg, route.get("path") or [])
436
+ ver = float(verification if verification is not None else hit_rate(cg))
437
+ balance = _branch_diversity(route)
438
+ composite = (W_TREND * trend + W_BOUNDARY * boundary
439
+ + W_VERIFICATION * ver + W_BALANCE * balance)
440
+ out = {"trend": round(trend, 4), "boundary": boundary,
441
+ "verification": round(ver, 4), "balance": balance,
442
+ "composite": round(composite, 4), "weights": _weights()}
443
+ if W_META > 0: # 第五维 opt-in:默认不落键
444
+ from . import d_meta # 惰性导入:叶子只读模块,防循环
445
+ mp = d_meta.pressure(d_meta.compute(cg), META_PROXY)
446
+ out["meta_pressure"] = mp
447
+ out["composite"] = round(composite + W_META * mp, 4)
448
+ return out
449
+
450
+
451
+ # ---------------------------------------------------------------- 盲区驱动
452
+
453
+ # 生效条件:blindspot_id 去空后非空且能取到盲区报表时,匹配 unresolved 的 node_id 返回 kind='unresolved',否则匹配 items 的 query 或其 _key 相等返回 kind='blindspot_cluster',均未命中或取报表抛异常返回 None;
454
+ def find_blindspot(cg, blindspot_id):
455
+ """按 id 定位盲区:unresolved 节点优先,其次盲区邻域(按 query 键)。"""
456
+ from . import metacognition
457
+ bid = str(blindspot_id or "").strip()
458
+ if not bid:
459
+ return None
460
+ try:
461
+ bs = metacognition.blindspots(cg)
462
+ except Exception:
463
+ return None
464
+ for u in bs.get("unresolved") or []:
465
+ if u.get("node_id") == bid:
466
+ return {"id": bid, "kind": "unresolved",
467
+ "description": u.get("content") or ""}
468
+ for it in bs.get("items") or []:
469
+ q = str(it.get("query") or "")
470
+ if q == bid or metacognition._key(q) == bid:
471
+ return {"id": bid, "kind": "blindspot_cluster", "description": q,
472
+ "blindspot": it.get("blindspot"),
473
+ "defer": it.get("defer")}
474
+ return None
475
+
476
+
477
+ # 生效条件:blindspot 的 description 中匹配到「可预测性:X」时返回 X 去除空白并小写,未匹配则返回 'predictable';
478
+ def predictability(blindspot):
479
+ """可预测性:盲区可显式声明 `# 可预测性:unknowable`。
480
+
481
+ 未声明 → predictable(默认按局部不可知原理生成候选,不宣称必然)。
482
+ """
483
+ txt = str((blindspot or {}).get("description") or "")
484
+ m = re.search(r"可预测性[::]\s*(\S+)", txt)
485
+ return m.group(1).strip().lower() if m else "predictable"
486
+
487
+
488
+ # 生效条件:description 去空后非空时以 cg.search 取常量 ANCHOR_FETCH_K 个候选,跳过 layer 属常量 ANCHOR_SKIP_LAYERS 或 tags 命中常量 ANCHOR_SKIP_TAGS 者,返回首个出边非空的合格节点 id,否则降级返回首个合格候选 id;description 为空、检索抛异常或候选全被过滤返回 None;
489
+ def anchor_from_description(cg, description):
490
+ """盲区描述 → 锚点节点(检索器打分,与 AEIS 的 LIKE→坐标回退同构)。
491
+
492
+ 择锚规则:候选池内**可起推者优先**——出边非空是路线生成的必要条件,无出边的
493
+ 节点只能靠语义回退硬凑路线,起点语义即失真。**全部不可起推时才降级**为首个合格
494
+ 候选(诚实降级:宁可标注锚点弱,不假装有锚)。
495
+ 动机(实测):同分并列时检索序不可依赖——「正题节点」与其「同名+近义描述」变体
496
+ 词法分可完全相同(各自都为 1.0),纯按检索名次取首位会漂到无出边的近义变体上。
497
+
498
+ 过滤与择锚分两关:先按标签/层过滤掉推断脚手架(`gap_hint`/`scene`)与负记忆
499
+ (`unresolved`/`rejected`),再在剩余候选里按**可起推资格**择优——出边非空者
500
+ 立即采纳,无出边者记为降级候选继续后看,全池无可起推者才降级取首个合格候选。
501
+ 候选全被过滤时返回 None(等价「无锚点」),由调用方按 no_anchor 处理。
502
+ 邻接表不可用(chain 取表异常)时退回「取首个合格候选」——辅助判据故障不应
503
+ 让整条预测链失效(兜底优先于择优)。
504
+ """
505
+ q = str(description or "").strip()
506
+ if not q:
507
+ return None
508
+ try:
509
+ results, _meta = cg.search(q, k=ANCHOR_FETCH_K, record=False, judge=False)
510
+ except Exception:
511
+ return None
512
+ try:
513
+ from . import chain
514
+ out_edges = chain.adjacency(cg)
515
+ except Exception:
516
+ out_edges = None
517
+ fallback = None
518
+ for item in results:
519
+ nd = item[0] if isinstance(item, (tuple, list)) else item
520
+ if not isinstance(nd, dict):
521
+ continue
522
+ fm = nd.get("frontmatter") or {}
523
+ path = str(nd.get("path") or "")
524
+ layer = str(fm.get("layer") or path.split("/")[0])
525
+ if layer in ANCHOR_SKIP_LAYERS:
526
+ continue
527
+ if set(ANCHOR_SKIP_TAGS) & set(fm.get("tags") or []):
528
+ continue
529
+ nid = nd.get("id")
530
+ if not nid:
531
+ nid = os.path.basename(path)[:-3] if path.endswith(".md") else None
532
+ if not nid:
533
+ continue
534
+ # 兜底:fs 派生的 id 可能是相对路径(如 unresolved/bs_x.md),归一为裸节点名
535
+ if "/" in str(nid) and str(nid).endswith(".md"):
536
+ nid = os.path.basename(str(nid))[:-3]
537
+ # 可起推资格:出边非空是路线生成的必要条件;不可起推者只作降级候选。
538
+ if out_edges is None or out_edges.get(nid):
539
+ return nid
540
+ if fallback is None:
541
+ fallback = nid
542
+ return fallback
543
+
544
+
545
+ # ---------------------------------------------------------------- 路线生成
546
+
547
+ # 生效条件:cg.get(nid) 的 content 含非空行时返回首行去掉开头 '#' 后的前 60 字符,否则返回 nid;
548
+ def _label(cg, nid):
549
+ node = cg.get(nid) or {}
550
+ for line in (node.get("content") or "").splitlines():
551
+ s = line.strip()
552
+ if s:
553
+ return re.sub(r"^#\s*", "", s)[:60]
554
+ return nid
555
+
556
+
557
+ # 生效条件:start_id 去空后非空且存在于 _nodes(cg) 时,horizon 裁剪到 [1, HORIZON_HARD]、max_branches 裁剪到 [1, MAX_BRANCHES_HARD] 后 DFS,返回 status='ok' 及带 uncertainty_bound/extrapolation_validity/score/path_labels 的 routes;start_id 为空或不在节点集中返回 status='start_not_found';
558
+ def _generate(cg, start_id, horizon, max_branches, semantic=True):
559
+ """D-001 局部路径 DFS 生成候选未来(差异 3:增加 path 防环)。"""
560
+ start_id = str(start_id or "").strip()
561
+ if not start_id or start_id not in _nodes(cg):
562
+ return {"status": "start_not_found", "start_id": start_id,
563
+ "routes": [], "meta": {"reason": "节点不存在"}}
564
+ horizon = max(1, min(int(horizon), HORIZON_HARD))
565
+ max_branches = max(1, min(int(max_branches), MAX_BRANCHES_HARD))
566
+ ver = hit_rate(cg)
567
+ out = []
568
+
569
+ # 生效条件:当 depth < horizon(enclosing 上界)时,对 branch_candidates(cg, cur, semantic=semantic) 取前 max_branches 个候选展开——候选 node_id 已在 path 中则 continue 跳过,否则把 cand["confidence"] 连乘(初始 1.0,结果 round 4)连同 conds/sources/relations 各自追加 cand 对应字段后作为一条路径写入外层 out,并以该 node_id、depth+1 递归;depth >= horizon 时直接返回且不产生任何路径。
570
+ def dfs(cur, path, confs, conds, sources, relations, depth):
571
+ if depth >= horizon:
572
+ return
573
+ for cand in branch_candidates(cg, cur, semantic=semantic)[:max_branches]:
574
+ nid = cand["node_id"]
575
+ if nid in path:
576
+ continue
577
+ n_confs = confs + [cand["confidence"]]
578
+ conf = 1.0
579
+ for c in n_confs:
580
+ conf *= c
581
+ n_path = path + [nid]
582
+ out.append({"path": n_path, "confidence": round(conf, 4),
583
+ "confs": n_confs,
584
+ "conditions": conds + [cand["condition"]],
585
+ "sources": sources + [cand["source"]],
586
+ "relations": relations + [cand["relation_type"]],
587
+ "last_source": cand["source"]})
588
+ dfs(nid, n_path, n_confs, conds + [cand["condition"]],
589
+ sources + [cand["source"]], relations + [cand["relation_type"]],
590
+ depth + 1)
591
+
592
+ dfs(start_id, [start_id], [], [], [], [], 0)
593
+ for r in out:
594
+ r["uncertainty_bound"] = _uncertainty(r["confidence"])
595
+ r["extrapolation_validity"] = extrapolation_validity(r)
596
+ r["score"] = score_route(cg, r, verification=ver)
597
+ r["path_labels"] = [_label(cg, n) for n in r["path"]]
598
+ return {"status": "ok", "start_id": start_id, "routes": out,
599
+ "meta": {"horizon": horizon, "max_branches": max_branches,
600
+ "n_routes": len(out), "hit_rate": ver,
601
+ "note": "候选未来,非必然未来(D-001)",
602
+ "generated_at": time.time()}}
603
+
604
+
605
+ # 生效条件:传入 cg、res、sort、limit 时,sort 不属于常量 SORT_KEYS 则退回 'composite',按该键排序并在 limit 为正时截断,追加一条 type 为 log_type(默认 'predict_routes')的留痕后返回 res;
606
+ def _finalize(cg, res, sort, limit, log_type="predict_routes"):
607
+ """排序 / 限流 / 留痕(预测本身可审计)。"""
608
+ if sort not in SORT_KEYS:
609
+ sort = "composite"
610
+ res["routes"].sort(key=lambda r: (-float(r["score"].get(sort) or 0.0),
611
+ r["path"]))
612
+ if limit and int(limit) > 0:
613
+ res["routes"] = res["routes"][:int(limit)]
614
+ res["meta"]["sort"] = sort
615
+ res["meta"]["n_returned"] = len(res["routes"])
616
+ _append(cg, {"type": log_type, "t": time.time(),
617
+ "start_id": res.get("start_id"),
618
+ "blindspot_id": res["meta"].get("blindspot_id"),
619
+ "n_routes": res["meta"]["n_routes"],
620
+ "horizon": res["meta"]["horizon"], "sort": sort})
621
+ return res
622
+
623
+
624
+ # 生效条件:blindspot_id 非空时转交 routes_from_blindspot;否则 start_id 为空返回 status='no_start',_generate 返回非 ok 则原样返回,ok 则经 _finalize(cg, res, sort, limit) 返回;
625
+ def routes(cg, start_id=None, blindspot_id=None, horizon=HORIZON_DEFAULT,
626
+ max_branches=MAX_BRANCHES_DEFAULT, sort="composite", limit=0,
627
+ semantic=True):
628
+ """生成候选未来路线(D-001 ~ D-005)。
629
+
630
+ start_id 起点节点 id
631
+ blindspot_id 盲区驱动(unresolved 节点 id / 盲区邻域键)
632
+ horizon 最大前推步数(默认 3)
633
+ max_branches 每步最大分支数(默认 5)
634
+ sort composite | trend | verification | boundary | balance
635
+ """
636
+ if blindspot_id:
637
+ return routes_from_blindspot(cg, blindspot_id, horizon=horizon,
638
+ max_branches=max_branches, sort=sort,
639
+ limit=limit, semantic=semantic)
640
+ if not start_id:
641
+ return {"status": "no_start", "routes": [],
642
+ "meta": {"reason": "缺少 start_id 或 blindspot_id"}}
643
+ res = _generate(cg, start_id, horizon, max_branches, semantic)
644
+ if res.get("status") != "ok":
645
+ return res
646
+ return _finalize(cg, res, sort, limit)
647
+
648
+
649
+ # 生效条件:find_blindspot 得 None 时返回 status='blindspot_not_found',其 predictability 为 'unknowable' 时返回 status='unpredictable',anchor_from_description 无锚点时返回 status='no_anchor',有锚点则以其 _generate 并在 ok 时经 _finalize(log_type='predict_routes_blindspot')返回;
650
+ def routes_from_blindspot(cg, blindspot_id, horizon=HORIZON_DEFAULT,
651
+ max_branches=MAX_BRANCHES_DEFAULT, sort="composite",
652
+ limit=0, semantic=True):
653
+ """盲区驱动的生成式预测(v1.10)。"""
654
+ bs = find_blindspot(cg, blindspot_id)
655
+ if bs is None:
656
+ return {"status": "blindspot_not_found", "blindspot_id": blindspot_id,
657
+ "routes": [], "meta": {"reason": "未找到该盲区"}}
658
+ if predictability(bs) == "unknowable":
659
+ return {"status": "unpredictable", "reason": "structural_unknowability",
660
+ "blindspot": bs, "routes": [],
661
+ "meta": {"note": "盲区声明不可预测 → 不生成路线(拒绝编造)"}}
662
+ anchor = anchor_from_description(cg, bs.get("description") or "")
663
+ if not anchor:
664
+ return {"status": "no_anchor", "blindspot": bs, "routes": [],
665
+ "meta": {"reason": "盲区描述检索不到锚点节点"}}
666
+ res = _generate(cg, anchor, horizon, max_branches, semantic)
667
+ res["blindspot"] = bs
668
+ if res.get("status") != "ok":
669
+ return res
670
+ res["meta"]["blindspot_id"] = blindspot_id
671
+ res["meta"]["anchor"] = anchor
672
+ return _finalize(cg, res, sort, limit, log_type="predict_routes_blindspot")
673
+
674
+
675
+ # ---------------------------------------------------------------- D-006 反馈闭环
676
+
677
+ _SAFE_LAYERS = ("knowledge", "contextual", "structural")
678
+
679
+
680
+ # 生效条件:传入 edge 时返回其 'target'、'to'、'dst' 中首个非空值,三者皆空则返回 None;
681
+ def _edge_target(edge):
682
+ return edge.get("target") or edge.get("to") or edge.get("dst")
683
+
684
+
685
+ # 生效条件:传入 cg 与 node_id 时,仅当存在指向 node_id 的因果/时序边且其源节点 frontmatter 的 layer 属于常量 _SAFE_LAYERS,才把该边 confidence 提升 min(1.0, 原值+delta)(delta 默认常量 EDGE_BOOST)并回写,返回被改源节点列表,无满足者返回空列表;
686
+ def _boost_incoming(cg, node_id, delta=EDGE_BOOST, actor="predict"):
687
+ """命中 → 指向该节点的因果/时序边置信度 +delta。
688
+
689
+ 只改普通层(knowledge/contextual/structural):self/anchor/rejected/
690
+ unresolved/goals 带专用 frontmatter,整体重写会丢字段。
691
+ """
692
+ from . import chain
693
+ changed = []
694
+ for src, outs in list(chain.adjacency(cg).items()):
695
+ if not any(tgt == node_id and chain.edge_rel(e) in CAUSAL_BRANCH_TYPES
696
+ for tgt, e in outs):
697
+ continue
698
+ node = cg.get(src)
699
+ if not node:
700
+ continue
701
+ fm = node.get("frontmatter") or {}
702
+ if fm.get("layer") not in _SAFE_LAYERS:
703
+ continue
704
+ edges, touched = [dict(e) for e in (fm.get("edges") or [])], False
705
+ for ed in edges:
706
+ if _edge_target(ed) != node_id:
707
+ continue
708
+ if chain.edge_rel(ed) not in CAUSAL_BRANCH_TYPES:
709
+ continue
710
+ old, new = _edge_conf(ed), min(1.0, _edge_conf(ed) + float(delta))
711
+ if new != old:
712
+ ed["confidence"] = round(new, 4)
713
+ touched = True
714
+ if not touched:
715
+ continue
716
+ # add() 会**重建** frontmatter:未显式传回的字段会被重置或丢失。
717
+ # 尤其 sensitivity 缺省为 DEFAULT_SENSITIVITY("internal")——直接回写等于
718
+ # 把 private/secret 节点**降级**;modality/created_at/证据计数同理。
719
+ # 故除被覆盖的字段外,整表透传(actor 已单独传,避免重复关键字)。
720
+ skip = {"id", "layer", "tags", "condition_space", "importance",
721
+ "confidence", "edges", "verification_basis",
722
+ "non_applicable_conditions", "actor", "path"}
723
+ keep = {k: v for k, v in fm.items() if k not in skip}
724
+ try:
725
+ cg.add(src, node.get("content") or "",
726
+ layer=fm.get("layer") or "knowledge",
727
+ tags=fm.get("tags"),
728
+ condition_space=fm.get("condition_space"),
729
+ importance=fm.get("importance", 0.5),
730
+ confidence=fm.get("confidence", 0.6), edges=edges,
731
+ verification_basis=fm.get("verification_basis"),
732
+ non_applicable_conditions=fm.get("non_applicable_conditions"),
733
+ override=True, actor=actor, **keep)
734
+ changed.append(src)
735
+ except Exception:
736
+ continue
737
+ if changed:
738
+ chain.invalidate_cache(cg)
739
+ return changed
740
+
741
+
742
+ # 生效条件:传入 cg 时若 _hit_history(cg, limit) 样本数少于常量 MIN_SAMPLES,返回 threshold 为常量 BASE_HIT_RATE 且 reflect=False;样本足够则以 max(BASE_HIT_RATE, mean-2σ) 为 threshold,命中均值低于该值时 reflect=True;
743
+ def dynamic_hit_threshold(cg, limit=HIT_HISTORY_MAX):
744
+ """D-006 命中率动态校准(2.7.2 动态死区)。
745
+
746
+ 样本 < MIN_SAMPLES 不触发反思(小样本噪声);阈值 = max(BASE, mean-2σ)。
747
+ """
748
+ h = _hit_history(cg, limit=limit)
749
+ n = len(h)
750
+ if n < MIN_SAMPLES:
751
+ return {"threshold": BASE_HIT_RATE, "samples": n,
752
+ "min_samples": MIN_SAMPLES, "reflect": False,
753
+ "note": f"样本不足({n}/{MIN_SAMPLES}),不触发反思"}
754
+ mean = sum(1 for x in h if x) / n
755
+ var = sum(((1.0 if x else 0.0) - mean) ** 2 for x in h) / n
756
+ std = var ** 0.5
757
+ th = max(BASE_HIT_RATE, mean - 2 * std)
758
+ low = mean < th
759
+ return {"threshold": round(th, 4), "samples": n, "mean": round(mean, 4),
760
+ "std": round(std, 4), "min_samples": MIN_SAMPLES,
761
+ "reflect": low,
762
+ "note": "命中率低于动态阈值 → 建议反思(D-006)" if low
763
+ else "命中率正常"}
764
+
765
+
766
+ # 生效条件:cg 与 predicted_node_id 给出即执行——predicted_node_id 为假值(None/0/"")时 pred 取空串,actual_node_id 为假值时 act 回落为 pred,hit 为 None 时按 pred==act 判定;hit 为真时 out["boosted"] 取 _boost_incoming(cg, act, EDGE_BOOST, actor=actor),hit 为假时 out["rejected_id"] 取 cg.add_rejected(...)(该调用抛异常时改记 out["rejected_error"]),随后 out 合并 dynamic_hit_threshold(cg),sync_self 为真时导入 self_state.refresh(cg, actor=actor) 写 out["self_state"](抛异常时写 out["self_state_error"]),返回 out。
767
+ def feedback(cg, predicted_node_id, actual_node_id=None, hit=None, note="",
768
+ actor="predict", sync_self=True, channel=None):
769
+ """预测反馈(D-006):hit → 边置信度 +0.05;miss → 登记 rejected。
770
+
771
+ `hit` 未显式给出时按 `predicted == actual` 判定。
772
+ `channel`(可选,P-T-74):预测发出的面(如 "causal"/"semantic"),
773
+ 供通道级 Beta-Bernoulli 后验(channel_posterior)按面分层估计可信度;
774
+ 不传 → 留痕归 "unlabeled",不改变任何既有行为。
775
+
776
+ `sync_self`(默认 True):反馈后**回写自我模型**——刷新自我状态卡的
777
+ 「预测校准」面,形成「预测 → 事实 → 误差 → 自我更新」闭环。
778
+ 自我模型是二阶观测,其刷新失败不阻塞一阶反馈结果。
779
+ """
780
+ pred = str(predicted_node_id or "").strip()
781
+ act = str(actual_node_id or "").strip() or pred
782
+ if hit is None:
783
+ hit = (pred == act)
784
+ hit = bool(hit)
785
+ _append(cg, {"type": "feedback", "t": time.time(), "predicted": pred,
786
+ "actual": act, "hit": hit, "note": str(note or "")[:200],
787
+ "channel": (str(channel) if channel else None)})
788
+ out = {"ok": True, "hit": hit, "predicted": pred, "actual": act}
789
+ if hit:
790
+ out["boosted"] = _boost_incoming(cg, act, EDGE_BOOST, actor=actor)
791
+ else:
792
+ try:
793
+ out["rejected_id"] = cg.add_rejected(
794
+ hypothesis=f"预测未命中:{pred} → {act}",
795
+ reason=str(note or "实际走向不同"),
796
+ verification_basis="data", tags=["prediction", "miss"])
797
+ except Exception as exc: # pragma: no cover
798
+ out["rejected_error"] = str(exc)
799
+ out.update(dynamic_hit_threshold(cg))
800
+ # 闭环:预测误差 → 自我模型更新。延迟导入避免与 self_state 的循环依赖,
801
+ # 且自我模型刷新属于二阶观测,失败不阻塞一阶反馈结果。
802
+ if sync_self:
803
+ try:
804
+ from . import self_state
805
+ out["self_state"] = self_state.refresh(cg, actor=actor)
806
+ except Exception as exc: # pragma: no cover
807
+ out["self_state_error"] = str(exc)
808
+ return out
809
+
810
+
811
+ # ---------------------------------------------------------------- P2 盲区学习闭环
812
+
813
+ LEARN_LOG = "_learn.jsonl"
814
+ LEARN_MAX_STEPS = 8
815
+
816
+
817
+ # 生效条件:传入 cg 具 root 属性时返回 root 与模块级常量 LEARN_LOG 的路径拼接,缺 root 则以 '.' 拼接;
818
+ def learn_log_path(cg):
819
+ return os.path.join(getattr(cg, "root", "."), LEARN_LOG)
820
+
821
+
822
+ # 生效条件:传入 cg 与 rec 时向 learn_log_path(cg) 追加一条 JSONL 记录,任何异常被吞掉且无返回值;
823
+ def _learn_append(cg, rec):
824
+ try:
825
+ append_jsonl(learn_log_path(cg), rec)
826
+ except Exception: # noqa: BLE001
827
+ pass
828
+
829
+
830
+ # 生效条件:nid 去空后非空、cg.get(nid) 的 frontmatter layer 等于 'knowledge' 且其 content 含常量 consolidate.CCG_REQUIRED 全部要素行时返回 True,否则返回 False;
831
+ def _is_settled(cg, nid):
832
+ """终点是否已达「可判定」态:知识层 ∧ CCG 五要素齐全。"""
833
+ nid = str(nid or "").strip()
834
+ if not nid:
835
+ return False
836
+ try:
837
+ node = cg.get(nid) or {}
838
+ except Exception: # noqa: BLE001
839
+ return False
840
+ fm = node.get("frontmatter") or {}
841
+ if fm.get("layer") != "knowledge":
842
+ return False
843
+ try:
844
+ from . import consolidate
845
+ content = node.get("content") or ""
846
+ return all(consolidate._has_ccg_line(content, k)
847
+ for k in consolidate.CCG_REQUIRED)
848
+ except Exception: # noqa: BLE001
849
+ return False
850
+
851
+
852
+ # 生效条件:传入 route 且其 'path' 非空时返回路径最后一个节点 id,路径为空或 route 为 None 时返回 None;
853
+ def _terminal_node(route):
854
+ path = list((route or {}).get("path") or [])
855
+ return path[-1] if path else None
856
+
857
+
858
+ # 生效条件:传入 cg、bid、step、actor 时,cg 中不存在由 bid 的 sha1 派生的 gap 节点 id 则写入 contextual 的 gap_hint 节点并返回该 id;该 id 已存在或 cg.add 抛异常时返回 None;
859
+ def _write_gap(cg, bid, step, actor):
860
+ """把待补线索写成 contextual 的 ``gap_hint`` 节点(幂等,非事实断言)。"""
861
+ nid = "gap_%s" % hashlib.sha1(bid.encode("utf-8")).hexdigest()[:10]
862
+ try:
863
+ if cg.get(nid):
864
+ return None
865
+ except Exception: # noqa: BLE001
866
+ pass
867
+ content = ("# 功能名:盲区补全线索\n"
868
+ "# 生效条件:%s\n"
869
+ "# 子功能:为盲区补上条件/路径(待补,非既有事实)\n"
870
+ "# 执行:%s\n"
871
+ "# 不适用条件:结构性不可知\n"
872
+ % (step.get("description") or bid, step.get("hint") or ""))
873
+ try:
874
+ cg.add(nid, content, layer="contextual",
875
+ tags=["gap_hint", "learn"], importance=0.3,
876
+ verification_basis="data", actor=actor,
877
+ gap_blindspot=bid, gap_terminal=step.get("terminal"))
878
+ return nid
879
+ except Exception as exc: # noqa: BLE001
880
+ step["write_error"] = "%s: %s" % (type(exc).__name__, exc)
881
+ return None
882
+
883
+
884
+ # 生效条件:blindspot_id 给出但 find_blindspot 找不到时返回 ok=False 与 status='not_found';否则逐条按 unknowable/no_anchor/unresolved/carried/resolved 判定,apply 为真且 cg 可写时才把 carried/unresolved 落成幂等 gap_hint 节点;
885
+ def learn_blindspots(cg, blindspot_id=None, limit=LEARN_MAX_STEPS,
886
+ horizon=HORIZON_DEFAULT, max_branches=MAX_BRANCHES_DEFAULT,
887
+ apply=False, actor="insight", **extra):
888
+ """盲区学习闭环(P2):盲区 → 路线假设 → 终态判定 → 登记。
889
+
890
+ 终态五态(诚实优先,绝不编造):
891
+
892
+ - ``unknowable`` 盲区声明结构性不可知 → 不生成路线;
893
+ - ``no_anchor`` 描述检索不到锚点 → 无法起推;
894
+ - ``unresolved`` 无任何可用路线 → 回填为待补线索;
895
+ - ``carried`` 有路线但终点未达可判定态 → 记为待验证假设;
896
+ - ``resolved`` 有路线且终点已在「知识层 + 五要素齐全」→ 认定补全。
897
+
898
+ ``apply=True`` 时把 carried/unresolved 落成 contextual 的 ``gap_hint`` 节点
899
+ (待补线索,非既有事实);节点 id 由盲区 id 派生 ⇒ 重复执行幂等。
900
+ """
901
+ from . import metacognition
902
+ if blindspot_id:
903
+ one = find_blindspot(cg, blindspot_id)
904
+ if not one:
905
+ return {"ok": False, "action": "learn", "status": "not_found",
906
+ "reason": "未找到盲区:%s" % blindspot_id, "steps": [],
907
+ "summary": {}}
908
+ items = [one]
909
+ else:
910
+ # 注意:metacognition.blindspots 返回的是**报表 dict**,不是条目列表。
911
+ # 必须显式取 unresolved(未解问题,含 node_id/content)与 items(盲区聚类,
912
+ # 含 query/blindspot/defer)两段,并归一成 find_blindspot 的同构条目,
913
+ # 否则 list(dict) 只会拿到键名并在 dict(it) 处崩溃。
914
+ try:
915
+ blind = metacognition.blindspots(cg) or {}
916
+ except Exception: # noqa: BLE001
917
+ blind = {}
918
+ items = []
919
+ for it in (blind.get("items") or []):
920
+ q = str(it.get("query") or "").strip()
921
+ if not q:
922
+ continue
923
+ items.append({"id": q, "kind": "blindspot_cluster", "description": q,
924
+ "blindspot": it.get("blindspot"), "defer": it.get("defer")})
925
+ for u in (blind.get("unresolved") or []):
926
+ nid = str(u.get("node_id") or "").strip()
927
+ if not nid:
928
+ continue
929
+ items.append({"id": nid, "kind": "unresolved",
930
+ "description": u.get("content") or ""})
931
+ steps, written = [], []
932
+ summary = {"unknowable": 0, "no_anchor": 0, "unresolved": 0,
933
+ "carried": 0, "resolved": 0}
934
+ for it in items[:max(1, int(limit))]:
935
+ bs = dict(it or {})
936
+ bid = str(bs.get("id") or bs.get("query") or "").strip()
937
+ desc = str(bs.get("description") or bs.get("query") or bid)
938
+ pred = predictability(bs)
939
+ step = {"blindspot_id": bid, "description": desc[:200],
940
+ "predictability": pred, "routes": 0, "terminal": None,
941
+ "terminal_node": None, "hint": "", "written": None}
942
+ if pred == "unknowable":
943
+ step["terminal"] = "unknowable"
944
+ step["hint"] = "盲区声明结构性不可知:不生成路线(拒绝编造)"
945
+ else:
946
+ res = (routes_from_blindspot(cg, bid, horizon=horizon,
947
+ max_branches=max_branches, limit=3)
948
+ if bid else {"status": "no_start", "routes": []})
949
+ rts = list(res.get("routes") or [])
950
+ step["routes"] = len(rts)
951
+ if res.get("status") in ("no_start", "no_anchor",
952
+ "blindspot_not_found", "unpredictable"):
953
+ step["terminal"] = "no_anchor"
954
+ step["hint"] = "无可检索锚点:需先补描述或入口条件"
955
+ elif not rts:
956
+ step["terminal"] = "unresolved"
957
+ step["hint"] = "无可用路线:需补因果边或放宽检索条件"
958
+ else:
959
+ settled = [r for r in rts if _is_settled(cg, _terminal_node(r))]
960
+ if settled:
961
+ step["terminal"] = "resolved"
962
+ step["terminal_node"] = _terminal_node(settled[0])
963
+ step["hint"] = "已存在通往「知识层 + 五要素齐全」终点的路线"
964
+ else:
965
+ step["terminal"] = "carried"
966
+ step["terminal_node"] = _terminal_node(rts[0])
967
+ step["hint"] = "路线终点未达可判定态:记为待验证假设"
968
+ summary[step["terminal"]] = summary.get(step["terminal"], 0) + 1
969
+ if apply and step["terminal"] in ("carried", "unresolved") and bid:
970
+ step["written"] = _write_gap(cg, bid, step, actor)
971
+ if step["written"]:
972
+ written.append(step["written"])
973
+ steps.append(step)
974
+ rec = {"type": "learn_step", "t": time.time(), "actor": actor}
975
+ rec.update(step)
976
+ _learn_append(cg, rec)
977
+ return {"ok": True, "action": "learn", "apply": bool(apply),
978
+ "steps": steps, "summary": summary, "written": written,
979
+ "note": ("apply=True:carried/unresolved 已落 gap_hint 待补线索;"
980
+ "resolved 仅表示已有可判定终点,未改动任何事实层节点")}
981
+
982
+
983
+ # 生效条件:传入 cg 时基于 _read_log(cg) 统计 type 以 'predict_routes' 开头的调用数与 n_routes 合计、feedback 样本与命中率(无样本取常量 BASE_HIT_RATE),并附 channel_posterior 与 dynamic_hit_threshold,recent 取末尾 limit 条(limit 默认 20,为 0 时为空);
984
+ def stats(cg, limit=20):
985
+ """预测统计:调用数、生成路线数、反馈样本、命中率、动态阈值。"""
986
+ recs = _read_log(cg)
987
+ calls = [r for r in recs
988
+ if str(r.get("type") or "").startswith("predict_routes")]
989
+ h = [bool(r.get("hit")) for r in recs if r.get("type") == "feedback"]
990
+ return {"ok": True, "calls": len(calls),
991
+ "routes_generated": sum(int(r.get("n_routes") or 0) for r in calls),
992
+ "feedback_samples": len(h), "hits": sum(1 for x in h if x),
993
+ "hit_rate": (round(sum(1 for x in h if x) / len(h), 4) if h
994
+ else BASE_HIT_RATE),
995
+ "beta": channel_posterior(cg),
996
+ "dynamic": dynamic_hit_threshold(cg),
997
+ "recent": recs[-int(limit):] if limit else []}
998
+
999
+
1000
+ # ---------------------------------------------------------------- 因果推理
1001
+
1002
+ # 生效条件:传入 cg 与 path 且 path 长度不少于 2 时,对每对相邻节点返回其在邻接表中且关系类型属于常量 CAUSAL_BRANCH_TYPES 的边条件(无匹配则为空串)逐跳列表,path 更短则返回空列表;
1003
+ def _path_conditions(cg, path):
1004
+ """路径上每跳的边条件(与节点对一一对应)。"""
1005
+ from . import chain
1006
+ adj = chain.adjacency(cg)
1007
+ conds = []
1008
+ for i in range(len(path) - 1):
1009
+ a, b = path[i], path[i + 1]
1010
+ cond = ""
1011
+ for tgt, e in adj.get(a) or []:
1012
+ if tgt == b and chain.edge_rel(e) in CAUSAL_BRANCH_TYPES:
1013
+ cond = chain.edge_condition(e) or ""
1014
+ break
1015
+ conds.append({"from": a, "to": b, "condition": cond})
1016
+ return conds
1017
+
1018
+
1019
+ # 生效条件:a_id 或 b_id 去空后为空时返回 ok=False 与 error='missing_node';a_id 等于 b_id 返回可达且长度 0;否则仅在常量 CAUSAL_BRANCH_TYPES 边上于 max_depth(下限 1)内 BFS,可达返回路径与逐跳 conditions,不可达返回 reachable=False;
1020
+ def causal_path(cg, a_id, b_id, max_depth=5):
1021
+ """因果路径推理:A 能否沿因果/时序边到达 B(BFS,最短路径)。
1022
+
1023
+ 这是 D-002 过滤门的完整语义(不止直接边):可达 → 可解释的因果关联;
1024
+ 不可达 → 语义邻近只是共现,不得当作因果用于预测。
1025
+ """
1026
+ from . import chain
1027
+ start, goal = str(a_id or "").strip(), str(b_id or "").strip()
1028
+ if not start or not goal:
1029
+ return {"ok": False, "error": "missing_node",
1030
+ "detail": "需要 a_id 与 b_id"}
1031
+ if start == goal:
1032
+ return {"ok": True, "reachable": True, "path": [start], "length": 0,
1033
+ "conditions": []}
1034
+ depth_cap = max(1, int(max_depth))
1035
+ adj = chain.adjacency(cg)
1036
+ prev = {start: None}
1037
+ queue = [(start, 0)]
1038
+ while queue:
1039
+ cur, d = queue.pop(0)
1040
+ if d >= depth_cap:
1041
+ continue
1042
+ for tgt, e in adj.get(cur) or []:
1043
+ if chain.edge_rel(e) not in CAUSAL_BRANCH_TYPES or tgt in prev:
1044
+ continue
1045
+ prev[tgt] = cur
1046
+ if tgt == goal:
1047
+ path, node = [], goal
1048
+ while node is not None:
1049
+ path.append(node)
1050
+ node = prev[node]
1051
+ path.reverse()
1052
+ return {"ok": True, "reachable": True, "path": path,
1053
+ "length": len(path) - 1,
1054
+ "conditions": _path_conditions(cg, path)}
1055
+ queue.append((tgt, d + 1))
1056
+ return {"ok": True, "reachable": False, "path": None, "length": None,
1057
+ "max_depth": depth_cap,
1058
+ "note": "无因果路径:两者最多只是语义邻近(伪因果防护)"}
1059
+
1060
+
1061
+ # ---------------------------------------------------------------- 自描述
1062
+
1063
+ # 生效条件:不适用(无必需形参与模块级常量)
1064
+ def catalog():
1065
+ """决策编号 / 权重 / 校准参数 / 与 AEIS 的差异(供协议对照验证)。"""
1066
+ return {
1067
+ "module": "predict",
1068
+ "theory": "AEIS prediction.py · "
1069
+ "PREDICTION-COMPLETION-PLAN-REV1-20260813-001",
1070
+ "channels": ["通道3 生成式(因果路线图)",
1071
+ "通道4 语义式(经 D-002 伪因果过滤门)"],
1072
+ "decisions": {
1073
+ "D-001": "局部路径生成 + uncertainty_bound(候选未来,非必然未来)",
1074
+ "D-002": "语义邻近过滤门:因果链 / 共同父节点 / 偏好权重 > 0.5",
1075
+ "D-003": "局部线性近似 + extrapolation_validity(smooth/jump/unknown)",
1076
+ "D-004": "T_pred 四维评分 trend/boundary/verification/balance",
1077
+ "D-005": "AttentionPolicy 适配器 + 降级(边置信度排序)",
1078
+ "D-006": "命中率动态校准(样本 < 50 不触发反思)",
1079
+ "P-T-73/74": "通道贝叶斯后验(Beta-Bernoulli,置信度≠可信度;"
1080
+ "feedback channel 参数 + channel_posterior 查询)",
1081
+ },
1082
+ "weights": _weights(), # 第五维 opt-in 时同步扩张(防文档漂移)
1083
+ "calibration": {"min_samples": MIN_SAMPLES,
1084
+ "base_hit_rate": BASE_HIT_RATE,
1085
+ "edge_boost": EDGE_BOOST,
1086
+ "hit_history_max": HIT_HISTORY_MAX},
1087
+ "limits": {"horizon_default": HORIZON_DEFAULT,
1088
+ "horizon_hard": HORIZON_HARD,
1089
+ "max_branches_default": MAX_BRANCHES_DEFAULT,
1090
+ "max_branches_hard": MAX_BRANCHES_HARD},
1091
+ "semantic": {"top_k": SEMANTIC_TOP_K, "min_sim": SEMANTIC_MIN_SIM,
1092
+ "induced_confidence": SEMANTIC_CONF},
1093
+ "branch_types": list(CAUSAL_BRANCH_TYPES),
1094
+ "differs_from_aeis": [
1095
+ "语义邻近回退中文二元组 Jaccard(AEIS 优先语义坐标)",
1096
+ "命中历史持久化在 _prediction.jsonl(AEIS 在内存)",
1097
+ "DFS 增加 path 防环(AEIS 仅靠 horizon 截断)",
1098
+ ],
1099
+ "actions": ["routes", "routes_from_blindspot", "feedback", "stats",
1100
+ "causal_path", "causal_gate", "catalog"],
1101
1101
  }