@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/refine.py CHANGED
@@ -1,605 +1,605 @@
1
- # -*- coding: utf-8 -*-
2
- """G6:node_ 结构提炼 · 小样本抽检工单与扩批闸门(只读预演,纯标准库)。
3
-
4
- 缺口(G6):`node_*`(P37 双子管线派生记忆)自带 `ccg_exempt=true`,正文只有
5
- 「功能名 / 生效条件」两行,`子功能 / 执行 / 不适用条件` 均未声明(实测 1324 条)。
6
- 若直接对全量跑归纳提炼(`consolidate.induce`),一旦**提炼口径**失准,就会把
7
- 模板化词面当成「共性条件」固化成概念节点,污染单一真相源。
8
-
9
- 裁定单条件 A:**先抽 20 条做提炼预演,人工核对提炼口径,绝不盲跑全量**。
10
-
11
- 本模块只做三件事(零 LLM、零第三方依赖,且**不改写任何节点**):
12
- 1. `plan` —— 确定性分层抽样出 20 条工单(原文摘录 / 字段缺口 / 口径声明);
13
- 2. `preview` —— 在抽检样本上跑与 `consolidate.induce` **同源的**聚类口径,
14
- 输出候选概念 + 每条共性条件的可追溯来源 + **样本内文档频率**
15
- (df 高 = 模板/通用水位线,不得当作区分性共性);
16
- 3. `apply`/`gate` —— 把抽检批次与人工裁决落 `_refine.jsonl` 留痕,并据此
17
- 计算「是否允许扩大批次」。
18
-
19
- 纪律与诚实边界:
20
- · 提炼执行仍由 `consolidate.induce` 承担(产出恒 `inferred`);本模块不实现
21
- 第二套提炼器,避免口径分叉——`preview` 复用同一套 `_jaccard/_common_terms`。
22
- · 抽检若取「20 条节点」,在库内实测**可能产不出候选**(`min_cluster=3` 时
23
- 20 条随机样本 0 簇);此时如实报 `candidates=0` 并**拒绝放行扩批**,
24
- 由 `sample_adequacy` 提示「按候选为抽检单位」或上调样本量。
25
- · 人工裁决必须由库外人类给出;本模块只做算术与留痕,**不代替裁决**。
26
- · 共性条件的去模板判据只用**词面统计**,不做语义猜测(宁可漏判,不可误判)。
27
- """
28
- from __future__ import annotations
29
-
30
- import hashlib
31
- import math
32
- import os
33
- import time
34
-
35
- from . import consolidate, nodefile
36
- from .fsutil import append_jsonl, read_jsonl
37
- from .mdcos import MdCGOS
38
-
39
- REFINE_LOG = "_refine.jsonl"
40
-
41
- #: 抽检对象 id 前缀(P37 派生记忆)
42
- PREFIX_DEFAULT = "node_"
43
- #: 裁定单条件 A 规定的抽检条数
44
- SAMPLE_N = 20
45
- #: 抽样种子:同 seed ⇒ 同样本(可复算、可复核)
46
- SAMPLE_SEED = "g6-sample-v1"
47
-
48
- MIN_JACCARD = consolidate.INDUCE_MIN_JACCARD
49
- MIN_CLUSTER = consolidate.INDUCE_MIN_CLUSTER
50
- MAX_TERMS = consolidate.INDUCE_MAX_TERMS
51
-
52
- #: 共性条件在**抽检样本**内的文档频率 ≥ 该值 → 判为模板/通用水位线,不具区分性
53
- GENERIC_DF = 0.80
54
- #: 工单正文摘录上限(超出即标 truncated;完整原文请 `cg read`)
55
- EXCERPT_MAX = 4000
56
- #: 扩批通过率闸门:人工核对「忠实」比例下限
57
- GATE_MIN_PASS_RATE = 0.90
58
-
59
- #: 样本量校准:目标产出候选数 / 有界爬坡上限(样本量非唯一杠杆,故设上限)
60
- CALIBRATE_TARGET = 20
61
- CALIBRATE_CAP = 200
62
-
63
- #: 人工核对裁决键(与工单 review_items 一一对应)
64
- VERDICT_KEYS = ("faithful", "added_info")
65
-
66
- #: 提炼口径声明——人工核对的就是这份口径,不是某一次的输出
67
- SPEC = {
68
- "method": "consolidate.induce 同源口径:bigram-jaccard 贪心聚类(确定性、零 LLM)",
69
- "min_cluster": MIN_CLUSTER,
70
- "min_jaccard": MIN_JACCARD,
71
- "fields": ["功能名", "生效条件", "子功能", "执行", "不适用条件"],
72
- "evidence": "inferred(未经验证,不得直接当事实使用)",
73
- "common_condition_source": "成员 positive_body 的词面统计(不做语义推断)",
74
- "review_items": [
75
- {"key": "faithful",
76
- "ask": "每条共性条件是否能在**全部**成员原文中原样找到"
77
- "(grounding_gap 为空即无缺口)"},
78
- {"key": "added_info",
79
- "ask": "概念是否引入了成员原文之外的词(外部知识/编造)——命中即整批否决"},
80
- {"key": "discriminating",
81
- "ask": "共性条件是否为区分性特征;df≥%.2f 的模板词不得充当共性"
82
- % GENERIC_DF},
83
- {"key": "member_consistency",
84
- "ask": "成员是否真属同一范畴(无强行拼团)"},
85
- ],
86
- "gate": {"min_pass_rate": GATE_MIN_PASS_RATE,
87
- "require_review_all": True,
88
- "fail_on_added_info": True},
89
- "honest_limits": [
90
- "本模块不执行提炼写入;扩批需 `consolidate.induce` 另开批次执行并留痕",
91
- "人工裁决由库外人类给出,本模块不代替裁决",
92
- ],
93
- }
94
-
95
- # ---- 通用工具 -------------------------------------------------------------
96
-
97
- # 生效条件:x 为 str 时返回 MdCGOS(x),否则原样返回 x;
98
- def _as_cg(x):
99
- return MdCGOS(x) if isinstance(x, str) else x
100
-
101
-
102
- # 生效条件:cg 具 "root" 属性时以该值为根、否则以 str(cg) 为根,与模块常量 REFINE_LOG 拼接返回;
103
- def _log_path(cg) -> str:
104
- root = cg.root if hasattr(cg, "root") else str(cg)
105
- return os.path.join(root, REFINE_LOG)
106
-
107
-
108
- # 生效条件:read_jsonl(_log_path(cg)) 返回假值时按空列表处理,仅保留 action 字段等于 "refine" 的记录;
109
- def _read_log(cg) -> list:
110
- return [r for r in (read_jsonl(_log_path(cg)) or [])
111
- if r.get("action") == "refine"]
112
-
113
-
114
- # 生效条件:cg 无 index 或 index["nodes"] 为假值时以空字典查找;nodes.get(nid) 为假值时返回空字典 {};
115
- def _entry(cg, nid):
116
- nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
117
- return nodes.get(nid) or {}
118
-
119
-
120
- # 生效条件:取 _entry(cg, nid) 的 path(假值回落空串)的父目录 basename;该 basename 为假值时返回 "(root)";
121
- def _family(cg, nid):
122
- """家族 = 节点所在 `cond_*` 目录名(无则 `(root)`)。"""
123
- p = str(_entry(cg, nid).get("path") or "").replace("\\", "/")
124
- d = os.path.basename(os.path.dirname(p))
125
- return d or "(root)"
126
-
127
-
128
- # 生效条件:对传入的 parts 逐项 str 后以 "|" 连接并 UTF-8 编码,返回 sha1 的 hexdigest;
129
- def _sha(*parts) -> str:
130
- h = hashlib.sha1("|".join(str(p) for p in parts).encode("utf-8"))
131
- return h.hexdigest()
132
-
133
-
134
- # 生效条件:遍历 cg.index 的 nodes,仅收 str(nid) 以 prefix 开头且条目 protected 为假值的 nid 进 ids(排序后返回),protected 为真值的计入 protected 计数;
135
- def _pool(cg, prefix) -> tuple:
136
- """抽检池:id 以 prefix 开头、非受保护节点(与 induce 的池口径一致)。"""
137
- nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
138
- ids, protected = [], 0
139
- for nid, e in nodes.items():
140
- if not str(nid).startswith(prefix):
141
- continue
142
- if (e or {}).get("protected"):
143
- protected += 1
144
- continue
145
- ids.append(nid)
146
- ids.sort()
147
- return ids, protected
148
-
149
-
150
- # 生效条件:cg 的 id 池经 prefix(假值回落 PREFIX_DEFAULT)过滤后,n 为 None 用 SAMPLE_N、否则 int(n),seed 假值回落 SAMPLE_SEED;pool 为空或 n<=0 时返回 ([], meta),否则按家族分层最大余数分配并在层内按 _sha(seed, id) 升序取前 k 个返回 (picked, meta);
151
- def sample_ids(cg, n=None, seed=None, prefix=None) -> tuple:
152
- """确定性分层抽样(家族=层,家族内按 sha1(seed|id) 排序)。
153
-
154
- 分层 + 最大余数分配:主家族按规模拿到多数名额,稀有家族亦保留席位(n 足够时)。
155
- 返回 `(ids, meta)`;同 `seed` 必得同样本——抽检可复核、可复算。
156
- """
157
- n = SAMPLE_N if n is None else int(n)
158
- seed = seed or SAMPLE_SEED
159
- prefix = prefix or PREFIX_DEFAULT
160
- pool, protected = _pool(cg, prefix)
161
- fam = {}
162
- for nid in pool:
163
- fam.setdefault(_family(cg, nid), []).append(nid)
164
- meta = {"prefix": prefix, "seed": seed, "requested": int(n),
165
- "pool": len(pool), "skipped_protected": protected,
166
- "families": len(fam), "sampled": 0, "strata": {}}
167
- if not pool or n <= 0:
168
- return [], meta
169
- n = min(int(n), len(pool))
170
- names = sorted(fam)
171
- raw = {f: n * len(fam[f]) / float(len(pool)) for f in names}
172
- alloc = {f: int(math.floor(raw[f])) for f in names}
173
- rest = n - sum(alloc.values())
174
- for f in sorted(names, key=lambda x: (-(raw[x] - alloc[x]), x))[:rest]:
175
- alloc[f] += 1
176
- picked, strata = [], {}
177
- for f in names:
178
- k = int(alloc[f])
179
- if k <= 0:
180
- continue
181
- rank = sorted(fam[f], key=lambda x: _sha(seed, x))
182
- take = rank[:k]
183
- picked.extend(take)
184
- strata[f] = {"pool": len(fam[f]), "picked": len(take)}
185
- picked.sort()
186
- meta["sampled"] = len(picked)
187
- meta["strata"] = strata
188
- return picked, meta
189
-
190
-
191
- # 生效条件:对 cg 中 nid 对应节点(cg.get(nid) 为假值时用空字典)生成字段;content 长度大于 EXCERPT_MAX 时 body 截断且 body_truncated 为 True,否则 body 为全 content;
192
- def _item(cg, nid) -> dict:
193
- node = cg.get(nid) or {}
194
- fm = node.get("frontmatter") or {}
195
- content = node.get("content") or ""
196
- comp = nodefile.ccg_completeness(content)
197
- e = _entry(cg, nid)
198
- truncated = len(content) > EXCERPT_MAX
199
- return {
200
- "id": nid, "family": _family(cg, nid), "layer": e.get("layer"),
201
- "tags": list(e.get("tags") or []), "importance": e.get("importance"),
202
- "edges": len(fm.get("edges") or []),
203
- "ccg_exempt": bool(fm.get("ccg_exempt")),
204
- "ccg_present": comp["present"],
205
- "ccg_missing": [m for m in nodefile.CCG_MARKS if m not in comp["present"]],
206
- "comment": fm.get("comment"),
207
- "body": content[:EXCERPT_MAX], "body_len": len(content),
208
- "body_truncated": truncated,
209
- "source_sha": _sha(content)[:16],
210
- }
211
-
212
-
213
- # ---- 预演:与 induce 同源的聚类口径 ---------------------------------------
214
-
215
- # 生效条件:pool 中能取到 grams 的 nid 进入 cache;按 keys 顺序贪心,与当前 a 的 _jaccard >= float(min_jaccard) 且未分配的后续 b 并入组,组大小达到 int(min_cluster) 才成组,返回 (cache, keys, groups);
216
- def _cluster(cg, pool, min_jaccard, min_cluster) -> tuple:
217
- """贪心聚类(与 `consolidate.induce_memories` 同源)。返回 (cache, keys, groups)。"""
218
- from . import subgraph
219
- cache = {}
220
- for nid in pool:
221
- got = subgraph._node_terms_and_grams(cg, nid)
222
- if got and got.get("grams"):
223
- cache[nid] = got
224
- keys = sorted(cache)
225
- assigned, groups = set(), []
226
- for i, a in enumerate(keys):
227
- if a in assigned:
228
- continue
229
- ga = cache[a]["grams"]
230
- grp = [b for b in keys[i + 1:]
231
- if b not in assigned
232
- and consolidate._jaccard(ga, cache[b]["grams"]) >= float(min_jaccard)]
233
- if len(grp) + 1 < int(min_cluster):
234
- continue
235
- members = [a] + grp
236
- assigned.update(members)
237
- groups.append(members)
238
- return cache, keys, groups
239
-
240
-
241
- # 生效条件:terms 为空时对每个 t 返回 df[t]=0;否则对 keys 中每个 k 的 cache[k]["pos"] 统计各 term 出现次数,返回 df;
242
- def _term_df(cache, keys, terms) -> dict:
243
- """词面在抽检样本内的文档频率(不含语义推断)。"""
244
- df = {t: 0 for t in terms}
245
- if not terms:
246
- return df
247
- for k in keys:
248
- pos = set(cache[k]["pos"])
249
- for t in terms:
250
- if t in pos:
251
- df[t] += 1
252
- return df
253
-
254
-
255
- # 生效条件:x 转为 cg;min_jaccard 为 None 取 MIN_JACCARD、否则 float(min_jaccard),min_cluster 为 None 取 MIN_CLUSTER、否则 int(min_cluster);ids 为真值时 pool 为显式 ids 去重排序且 meta 标记 explicit_ids=True,否则 pool/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix);对 pool 聚类后,require_conditions 为真且某组 common 为空时跳过该组并累计 skipped_no_cond;返回含 candidates 与 sample_adequacy 的只读预演字典;
256
- def preview(x, ids=None, n=None, seed=None, prefix=None,
257
- min_jaccard=None, min_cluster=None, require_conditions=True) -> dict:
258
- """提炼预演(只读):抽样 → 同源聚类 → 共性条件 + 来源 + 泛化度标记。"""
259
- cg = _as_cg(x)
260
- min_j = MIN_JACCARD if min_jaccard is None else float(min_jaccard)
261
- min_c = MIN_CLUSTER if min_cluster is None else int(min_cluster)
262
- if ids:
263
- pool = sorted({str(i) for i in ids})
264
- meta = {"prefix": prefix or PREFIX_DEFAULT, "seed": seed or SAMPLE_SEED,
265
- "requested": len(pool), "pool": len(pool),
266
- "skipped_protected": 0, "families": 0, "sampled": len(pool),
267
- "strata": {}, "explicit_ids": True}
268
- else:
269
- pool, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
270
-
271
- cache, keys, groups = _cluster(cg, pool, min_j, min_c)
272
- base = max(1, len(keys))
273
- cands, skipped_no_cond, all_terms = [], 0, set()
274
- for members in groups:
275
- common = consolidate._common_terms([cache[m]["pos"] for m in members])
276
- if require_conditions and not common:
277
- skipped_no_cond += 1
278
- continue
279
- neg = consolidate._union_terms([cache[m]["neg"] for m in members])
280
- all_terms.update(common)
281
- cands.append({"concept_id": consolidate._concept_id(members),
282
- "members": members, "size": len(members),
283
- "common_conditions": common,
284
- "non_applicable": neg})
285
-
286
- df = _term_df(cache, keys, sorted(all_terms))
287
- for c in cands:
288
- members = c["members"]
289
- src = {t: [m for m in members if t in cache[m]["pos"]]
290
- for t in c["common_conditions"]}
291
- gap = [t for t, ms in src.items() if len(ms) < len(members)]
292
- generic = [{"term": t, "df": df.get(t, 0),
293
- "ratio": round(df.get(t, 0) / float(base), 4)}
294
- for t in c["common_conditions"]
295
- if df.get(t, 0) / float(base) >= GENERIC_DF]
296
- c.update({
297
- "condition_sources": src,
298
- "grounding_gap": gap,
299
- "generic_conditions": generic,
300
- "discriminating": [t for t in c["common_conditions"]
301
- if df.get(t, 0) / float(base) < GENERIC_DF],
302
- "reason": ("%d 条记忆内容相近且共享条件「%s」→ 归纳为概念"
303
- % (len(members),
304
- "、".join(c["common_conditions"][:MAX_TERMS]) or "无")),
305
- })
306
- generic_only = sum(1 for c in cands if not c["discriminating"])
307
- adequacy = "ok" if cands else "insufficient"
308
- return {
309
- "root": cg.root, "dry_run": True, "readonly": True,
310
- "action": "refine_preview", "op": "maintain",
311
- "prefix": meta["prefix"], "seed": meta["seed"],
312
- "requested": meta["requested"], "pool": meta["pool"],
313
- "skipped_protected": meta["skipped_protected"],
314
- "families": meta["families"], "strata": meta["strata"],
315
- "sampled": meta["sampled"], "sample": pool,
316
- "indexed": len(keys), "min_cluster": min_c, "min_jaccard": min_j,
317
- "require_conditions": bool(require_conditions),
318
- "clusters": len(cands), "candidates": cands,
319
- "skipped_no_condition": skipped_no_cond,
320
- "candidates_generic_only": generic_only,
321
- "df_basis": "sampled", "generic_df_threshold": GENERIC_DF,
322
- "sample_adequacy": adequacy,
323
- "note": ("预演:未写盘、未改任何节点;候选供人工核对提炼口径"
324
- if cands else
325
- "抽检样本不足以产出候选(样本量或抽检单位需校准),不得据此扩批"),
326
- }
327
-
328
-
329
- # ---- 工单 ---------------------------------------------------------------
330
-
331
- # 生效条件:x 转为 cg;prefix 假值回落 PREFIX_DEFAULT,seed 假值回落 SAMPLE_SEED;ids 为真值时 sample 为显式 ids 去重排序且 real_pool=len(sample)、skipped=0、families=0,否则 sample/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix) 并取 meta["pool"]/meta["skipped_protected"]/meta["families"];items 为 sample 的 _item,preview 以 sample 为显式 ids 调用,返回只读工单;
332
- def plan(x, ids=None, n=None, seed=None, prefix=None,
333
- min_jaccard=None, min_cluster=None) -> dict:
334
- """抽检工单(只读):确定性样本 + 原文摘录 + 字段缺口 + 口径声明 + 预演。"""
335
- cg = _as_cg(x)
336
- prefix = prefix or PREFIX_DEFAULT
337
- seed = seed or SAMPLE_SEED
338
- if ids:
339
- sample = sorted({str(i) for i in ids})
340
- real_pool, skipped, families = len(sample), 0, 0
341
- else:
342
- sample, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
343
- real_pool, skipped, families = (meta["pool"], meta["skipped_protected"],
344
- meta["families"])
345
- items = [_item(cg, nid) for nid in sample]
346
- pv = preview(cg, ids=sample, prefix=prefix,
347
- min_jaccard=min_jaccard, min_cluster=min_cluster)
348
- strata = {}
349
- for it in items:
350
- k = it["family"]
351
- s = strata.setdefault(k, {"picked": 0, "missing_fields": {},
352
- "ccg_exempt": 0})
353
- s["picked"] += 1
354
- if it["ccg_exempt"]:
355
- s["ccg_exempt"] += 1
356
- for m in it["ccg_missing"]:
357
- s["missing_fields"][m] = s["missing_fields"].get(m, 0) + 1
358
- return {
359
- "root": cg.root, "dry_run": True, "readonly": True,
360
- "action": "refine", "op": "maintain",
361
- "prefix": prefix, "seed": seed,
362
- "requested": n if n is not None else (len(sample) if ids else SAMPLE_N),
363
- "pool": real_pool, "skipped_protected": skipped,
364
- "sampled": len(items), "families": families,
365
- "sample": sample, "sample_sha": _sha(*sample)[:16],
366
- "strata": strata, "items": items,
367
- "worklist": items, "spec": SPEC,
368
- "preview": {"clusters": pv["clusters"], "candidates": pv["candidates"],
369
- "min_cluster": pv["min_cluster"],
370
- "min_jaccard": pv["min_jaccard"],
371
- "skipped_no_condition": pv["skipped_no_condition"],
372
- "candidates_generic_only": pv["candidates_generic_only"],
373
- "sample_adequacy": pv["sample_adequacy"],
374
- "df_basis": pv["df_basis"],
375
- "generic_df_threshold": pv["generic_df_threshold"]},
376
- "gate_rule": SPEC["gate"],
377
- "note": ("抽检工单(只读):供人工核对提炼口径;核对通过前不得扩批。"
378
- "行 `apply=true` 只落抽检留痕,不改任何节点。"),
379
- }
380
-
381
-
382
- # 生效条件:x 转为 cg;target 为 None 取 CALIBRATE_TARGET、否则 int(target),cap 为 None 取 CALIBRATE_CAP、否则 int(cap),初始 n=min(SAMPLE_N if n0 is None else int(n0), cap);循环以 n 调 preview(cg, n=n, seed=seed, prefix=prefix, min_jaccard=min_jaccard, min_cluster=min_cluster) 并记录 step,直到 pv["clusters"] >= target 则 chosen=step 并 break,或 n >= min(cap, pool or cap) 则 break;chosen 为 None 时 blocked=True 并按 steps 汇总 recommend,否则 recommend 基于 chosen;返回只读校准报告;
383
- def calibrate(x, target=None, cap=None, n0=None, seed=None, prefix=None,
384
- min_jaccard=None, min_cluster=None) -> dict:
385
- """样本量校准(只读):20 条起步有界爬坡,直到产出 `target` 个候选或触顶。
386
-
387
- 目的是回答「20 条工单为什么交不出候选」,并给出**有界**的替代样本量;
388
- 若爬坡到顶仍无候选、或候选共性条件全为高 df 模板词,则如实报
389
- `blocked` 并指出真正的瓶颈是**提炼口径**而非样本量——不得据此扩批。
390
- """
391
- cg = _as_cg(x)
392
- target = CALIBRATE_TARGET if target is None else int(target)
393
- cap = CALIBRATE_CAP if cap is None else int(cap)
394
- n = min(SAMPLE_N if n0 is None else int(n0), cap)
395
- steps, chosen, pool = [], None, None
396
- while True:
397
- pv = preview(cg, n=n, seed=seed, prefix=prefix,
398
- min_jaccard=min_jaccard, min_cluster=min_cluster)
399
- pool = pv["pool"]
400
- step = {"n": n, "sampled": pv["sampled"], "clusters": pv["clusters"],
401
- "candidates_generic_only": pv["candidates_generic_only"],
402
- "discriminating_terms": sorted(
403
- {t for c in pv["candidates"] for t in c["discriminating"]})[:12]}
404
- steps.append(step)
405
- if pv["clusters"] >= target:
406
- chosen = step
407
- break
408
- top = min(cap, pool or cap)
409
- if n >= top:
410
- break
411
- n = min(n * 2, top)
412
- blocked = chosen is None
413
- saw_clusters = any(s["clusters"] for s in steps)
414
- total_c = sum(s["clusters"] for s in steps)
415
- gen_only = sum(s["candidates_generic_only"] for s in steps)
416
- if not blocked:
417
- rec = ("抽检样本量校准为 n=%d(产出 %d 个候选);仍须人工核对提炼口径,"
418
- "核对通过前不得扩批" % (chosen["n"], chosen["clusters"]))
419
- else:
420
- parts = ["爬坡至 cap=%d 仍只产出 %d 个候选(< target=%d),样本量不足以"
421
- "直接支撑抽检" % (cap, steps[-1]["clusters"], target)]
422
- if saw_clusters:
423
- if gen_only >= total_c:
424
- parts.append("且已产出候选的共性条件**全部**为高 df 模板词(无区分性)")
425
- else:
426
- parts.append("且候选中 %d/%d 的共性条件全为高 df 模板词,"
427
- "其余『区分性』项形如整行条件文本(含时间戳),"
428
- "属归一化缺失导致的伪区分性" % (gen_only, total_c))
429
- parts.append("须先修正提炼口径(剔除模板词 + 归一化/裁剪整行文本)"
430
- "或以「候选」为抽检单位重新校准;当前状态不得扩批")
431
- rec = ";".join(parts)
432
- return {"ok": True, "action": "refine_calibrate", "op": "maintain",
433
- "root": cg.root, "dry_run": True, "readonly": True,
434
- "target": target, "cap": cap, "pool": pool,
435
- "steps": steps, "chosen": chosen, "blocked": blocked,
436
- "sample_adequacy": "ok" if not blocked else "insufficient",
437
- "recommend": rec, "note": "只读校准:未写盘、未改任何节点。"}
438
-
439
-
440
- # ---- 留痕与扩批闸门 ------------------------------------------------------
441
-
442
- # 生效条件:candidates 中取 concept_id 为真值的 id 列表;verdicts 中为 dict 且 concept_id 非空且首次出现的条目计入 reviewed 并累加其中 faithful/added_info 的真值计数;ids 非空时按 reviewed < len(ids) → awaiting_human_review、added 非零 → added_info_detected、faithful_rate < GATE_MIN_PASS_RATE → faithful_rate_below_gate、否则 ok 且 expand_allowed=True;ids 为空时 reason="no_candidates" 且 expand_allowed=False;
443
- def _verdict_stats(verdicts, candidates) -> dict:
444
- ids = [c.get("concept_id") for c in candidates if c.get("concept_id")]
445
- seen, faithful, added = set(), 0, 0
446
- for v in verdicts or []:
447
- if not isinstance(v, dict):
448
- continue
449
- cid = v.get("concept_id")
450
- if not cid or cid in seen:
451
- continue
452
- seen.add(cid)
453
- faithful += 1 if v.get("faithful") else 0
454
- added += 1 if v.get("added_info") else 0
455
- reviewed = len(seen)
456
- rate = (faithful / float(reviewed)) if reviewed else 0.0
457
- expand, reason = False, "no_candidates"
458
- if ids:
459
- if reviewed < len(ids):
460
- reason = "awaiting_human_review"
461
- elif added:
462
- reason = "added_info_detected"
463
- elif rate < GATE_MIN_PASS_RATE:
464
- reason = "faithful_rate_below_gate"
465
- else:
466
- reason = "ok"
467
- expand = True
468
- return {"candidates": len(ids), "reviewed": reviewed,
469
- "faithful": faithful, "added_info": added,
470
- "faithful_rate": round(rate, 4),
471
- "min_pass_rate": GATE_MIN_PASS_RATE,
472
- "expand_allowed": expand, "reason": reason}
473
-
474
-
475
- # 生效条件:x 转为 cg;以 ids/n/seed/prefix/min_jaccard/min_cluster 调 plan 得到 p,batch 假值(含 None/空串)回落当前时间字符串;从 p["preview"]["candidates"] 与 verdicts 经 _verdict_stats 得 stats;把含 batch/actor/prefix/seed/sample/verdicts/gate/note 的 record 追加写入 _log_path(cg),返回 rep;
476
- def apply(x, batch=None, actor=None, verdicts=None, note=None,
477
- ids=None, n=None, seed=None, prefix=None,
478
- min_jaccard=None, min_cluster=None) -> dict:
479
- """落抽检批次 + 人工裁决到 `_refine.jsonl`。**不改写任何节点**。"""
480
- cg = _as_cg(x)
481
- p = plan(cg, ids=ids, n=n, seed=seed, prefix=prefix,
482
- min_jaccard=min_jaccard, min_cluster=min_cluster)
483
- batch = batch or time.strftime("%Y%m%d-%H%M%S")
484
- cands = p["preview"]["candidates"]
485
- stats = _verdict_stats(verdicts, cands)
486
- record = {
487
- "t": time.time(), "action": "refine", "batch": batch, "actor": actor,
488
- "prefix": p["prefix"], "seed": p["seed"], "requested": p["requested"],
489
- "pool": p["pool"], "sampled": p["sampled"],
490
- "sample": p["sample"], "sample_sha": p["sample_sha"],
491
- "sample_adequacy": p["preview"]["sample_adequacy"],
492
- "min_cluster": p["preview"].get("min_cluster"),
493
- "candidates": [{"concept_id": c["concept_id"], "members": c["members"],
494
- "common_conditions": c["common_conditions"],
495
- "grounding_gap": c["grounding_gap"],
496
- "discriminating": c["discriminating"]}
497
- for c in cands],
498
- "verdicts": list(verdicts or []), "gate": stats, "note": note,
499
- }
500
- append_jsonl(_log_path(cg), record)
501
- rep = {"ok": True, "action": "refine", "op": "maintain", "batch": batch,
502
- "root": cg.root, "sampled": record["sampled"],
503
- "candidates": stats["candidates"], "reviewed": stats["reviewed"],
504
- "gate": stats, "log": REFINE_LOG,
505
- "note": ("抽检留痕已落盘(未改任何节点);"
506
- + ("闸门放行扩批(仍需另开 induce 批次并留痕)"
507
- if stats["expand_allowed"] else
508
- "闸门未放行:" + stats["reason"]))}
509
- return rep
510
-
511
-
512
- # 生效条件:x 转为 cg;batch 为真值时只保留 _read_log(cg) 中 batch 字段相等的记录,过滤后为空时返回 batches=0、expand_allowed=False、reason="no_batch";否则取最后一条记录,以其 verdicts 与 candidates(假值转空列表)经 _verdict_stats 复算并返回 expand_allowed/reason 等;
513
- def gate(x, batch=None) -> dict:
514
- """扩批闸门:读留痕复算通过率(不写盘)。"""
515
- cg = _as_cg(x)
516
- recs = _read_log(cg)
517
- if batch:
518
- recs = [r for r in recs if r.get("batch") == batch]
519
- if not recs:
520
- return {"ok": True, "action": "refine_gate", "op": "maintain",
521
- "root": cg.root, "batch": batch, "batches": 0,
522
- "expand_allowed": False, "reason": "no_batch"}
523
- rec = recs[-1]
524
- stats = _verdict_stats(rec.get("verdicts"), rec.get("candidates") or [])
525
- return {"ok": True, "action": "refine_gate", "op": "maintain",
526
- "root": cg.root, "batch": rec.get("batch"),
527
- "batches": len({r.get("batch") for r in recs}),
528
- "sampled": rec.get("sampled"),
529
- "sample_adequacy": rec.get("sample_adequacy"),
530
- "gate": stats, "expand_allowed": stats["expand_allowed"],
531
- "reason": stats["reason"],
532
- "note": ("闸门放行:可另开 induce 批次扩大提炼规模并逐批留痕"
533
- if stats["expand_allowed"] else
534
- "闸门未放行,禁止扩大批次;不得盲跑全量。")}
535
-
536
-
537
- # 生效条件:batch 为真值时只保留该批次记录,total 先记为过滤后条数;limit 非 None 且 >=0 时以 recs[-int(limit):] 截尾(limit=0 因 [-0:] 等价 [0:] 仍返回全部记录),limit 为 None 或负数时不截断;
538
- def history(x, limit=100, batch=None) -> dict:
539
- cg = _as_cg(x)
540
- recs = _read_log(cg)
541
- if batch:
542
- recs = [r for r in recs if r.get("batch") == batch]
543
- total = len(recs)
544
- if limit is not None and limit >= 0:
545
- recs = recs[-int(limit):]
546
- return {"root": cg.root, "total": total, "returned": len(recs),
547
- "action": "refine", "records": recs}
548
-
549
-
550
- # ---- CLI(真实库抽检/预演用;MCP 侧走 maintain action) -------------------
551
-
552
- # 生效条件:解析 argv(None 时由 argparse 取 sys.argv)得到 root/n/seed/prefix/min-jaccard/min-cluster/report 等参数;a.calibrate 为真时调 calibrate 并打印 target/cap/pool/blocked/sample_adequacy/recommend 及每步摘要;否则 a.preview 为真选 preview、为假选 plan,调用后打印 thin 摘要与候选并返回 0;
553
- def _main(argv=None) -> int:
554
- import argparse
555
- import json
556
- ap = argparse.ArgumentParser(description="G6 提炼抽检(默认只预演,不写盘)")
557
- ap.add_argument("root", help="认知图库根")
558
- ap.add_argument("--n", type=int, default=SAMPLE_N, help="抽检条数(默认 20)")
559
- ap.add_argument("--seed", default=SAMPLE_SEED, help="抽样种子(同 seed 同样本)")
560
- ap.add_argument("--prefix", default=PREFIX_DEFAULT, help="id 前缀(默认 node_)")
561
- ap.add_argument("--min-jaccard", type=float, default=None)
562
- ap.add_argument("--min-cluster", type=int, default=None)
563
- ap.add_argument("--preview", action="store_true", help="只出预演(不出工单正文)")
564
- ap.add_argument("--calibrate", action="store_true",
565
- help="只做样本量校准(20 条起步有界爬坡)")
566
- ap.add_argument("--report", default=None, help="把汇总 JSON 另存一份")
567
- a = ap.parse_args(argv)
568
- if a.calibrate:
569
- rep = calibrate(a.root, seed=a.seed, prefix=a.prefix,
570
- min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
571
- if a.report:
572
- with open(a.report, "w", encoding="utf-8") as f:
573
- json.dump(rep, f, ensure_ascii=False, indent=2)
574
- print(json.dumps({k: rep[k] for k in
575
- ("target", "cap", "pool", "blocked",
576
- "sample_adequacy", "recommend")},
577
- ensure_ascii=False, indent=2))
578
- for s in rep["steps"]:
579
- print(" - n=%4d clusters=%2d generic_only=%2d discriminating=%s"
580
- % (s["n"], s["clusters"], s["candidates_generic_only"],
581
- s["discriminating_terms"][:6]))
582
- return 0
583
- fn = preview if a.preview else plan
584
- rep = fn(a.root, n=a.n, seed=a.seed, prefix=a.prefix,
585
- min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
586
- if a.report:
587
- with open(a.report, "w", encoding="utf-8") as f:
588
- json.dump(rep, f, ensure_ascii=False, indent=2)
589
- pv = rep.get("preview") if "preview" in rep else rep
590
- thin = {k: rep.get(k) for k in ("action", "prefix", "seed", "requested",
591
- "pool", "sampled", "families")}
592
- thin.update({k: pv.get(k) for k in ("clusters", "sample_adequacy",
593
- "candidates_generic_only")})
594
- print(json.dumps(thin, ensure_ascii=False, indent=2))
595
- for c in (pv.get("candidates") or []):
596
- print(" - %s size=%d common=%s generic=%s gap=%s"
597
- % (c["concept_id"], c["size"], c["common_conditions"][:4],
598
- [g["term"] for g in c.get("generic_conditions", [])],
599
- c.get("grounding_gap")))
600
- return 0
601
-
602
-
603
- if __name__ == "__main__":
604
- import sys
1
+ # -*- coding: utf-8 -*-
2
+ """G6:node_ 结构提炼 · 小样本抽检工单与扩批闸门(只读预演,纯标准库)。
3
+
4
+ 缺口(G6):`node_*`(P37 双子管线派生记忆)自带 `ccg_exempt=true`,正文只有
5
+ 「功能名 / 生效条件」两行,`子功能 / 执行 / 不适用条件` 均未声明(实测 1324 条)。
6
+ 若直接对全量跑归纳提炼(`consolidate.induce`),一旦**提炼口径**失准,就会把
7
+ 模板化词面当成「共性条件」固化成概念节点,污染单一真相源。
8
+
9
+ 裁定单条件 A:**先抽 20 条做提炼预演,人工核对提炼口径,绝不盲跑全量**。
10
+
11
+ 本模块只做三件事(零 LLM、零第三方依赖,且**不改写任何节点**):
12
+ 1. `plan` —— 确定性分层抽样出 20 条工单(原文摘录 / 字段缺口 / 口径声明);
13
+ 2. `preview` —— 在抽检样本上跑与 `consolidate.induce` **同源的**聚类口径,
14
+ 输出候选概念 + 每条共性条件的可追溯来源 + **样本内文档频率**
15
+ (df 高 = 模板/通用水位线,不得当作区分性共性);
16
+ 3. `apply`/`gate` —— 把抽检批次与人工裁决落 `_refine.jsonl` 留痕,并据此
17
+ 计算「是否允许扩大批次」。
18
+
19
+ 纪律与诚实边界:
20
+ · 提炼执行仍由 `consolidate.induce` 承担(产出恒 `inferred`);本模块不实现
21
+ 第二套提炼器,避免口径分叉——`preview` 复用同一套 `_jaccard/_common_terms`。
22
+ · 抽检若取「20 条节点」,在库内实测**可能产不出候选**(`min_cluster=3` 时
23
+ 20 条随机样本 0 簇);此时如实报 `candidates=0` 并**拒绝放行扩批**,
24
+ 由 `sample_adequacy` 提示「按候选为抽检单位」或上调样本量。
25
+ · 人工裁决必须由库外人类给出;本模块只做算术与留痕,**不代替裁决**。
26
+ · 共性条件的去模板判据只用**词面统计**,不做语义猜测(宁可漏判,不可误判)。
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import hashlib
31
+ import math
32
+ import os
33
+ import time
34
+
35
+ from . import consolidate, nodefile
36
+ from .fsutil import append_jsonl, read_jsonl
37
+ from .mdcos import MdCGOS
38
+
39
+ REFINE_LOG = "_refine.jsonl"
40
+
41
+ #: 抽检对象 id 前缀(P37 派生记忆)
42
+ PREFIX_DEFAULT = "node_"
43
+ #: 裁定单条件 A 规定的抽检条数
44
+ SAMPLE_N = 20
45
+ #: 抽样种子:同 seed ⇒ 同样本(可复算、可复核)
46
+ SAMPLE_SEED = "g6-sample-v1"
47
+
48
+ MIN_JACCARD = consolidate.INDUCE_MIN_JACCARD
49
+ MIN_CLUSTER = consolidate.INDUCE_MIN_CLUSTER
50
+ MAX_TERMS = consolidate.INDUCE_MAX_TERMS
51
+
52
+ #: 共性条件在**抽检样本**内的文档频率 ≥ 该值 → 判为模板/通用水位线,不具区分性
53
+ GENERIC_DF = 0.80
54
+ #: 工单正文摘录上限(超出即标 truncated;完整原文请 `cg read`)
55
+ EXCERPT_MAX = 4000
56
+ #: 扩批通过率闸门:人工核对「忠实」比例下限
57
+ GATE_MIN_PASS_RATE = 0.90
58
+
59
+ #: 样本量校准:目标产出候选数 / 有界爬坡上限(样本量非唯一杠杆,故设上限)
60
+ CALIBRATE_TARGET = 20
61
+ CALIBRATE_CAP = 200
62
+
63
+ #: 人工核对裁决键(与工单 review_items 一一对应)
64
+ VERDICT_KEYS = ("faithful", "added_info")
65
+
66
+ #: 提炼口径声明——人工核对的就是这份口径,不是某一次的输出
67
+ SPEC = {
68
+ "method": "consolidate.induce 同源口径:bigram-jaccard 贪心聚类(确定性、零 LLM)",
69
+ "min_cluster": MIN_CLUSTER,
70
+ "min_jaccard": MIN_JACCARD,
71
+ "fields": ["功能名", "生效条件", "子功能", "执行", "不适用条件"],
72
+ "evidence": "inferred(未经验证,不得直接当事实使用)",
73
+ "common_condition_source": "成员 positive_body 的词面统计(不做语义推断)",
74
+ "review_items": [
75
+ {"key": "faithful",
76
+ "ask": "每条共性条件是否能在**全部**成员原文中原样找到"
77
+ "(grounding_gap 为空即无缺口)"},
78
+ {"key": "added_info",
79
+ "ask": "概念是否引入了成员原文之外的词(外部知识/编造)——命中即整批否决"},
80
+ {"key": "discriminating",
81
+ "ask": "共性条件是否为区分性特征;df≥%.2f 的模板词不得充当共性"
82
+ % GENERIC_DF},
83
+ {"key": "member_consistency",
84
+ "ask": "成员是否真属同一范畴(无强行拼团)"},
85
+ ],
86
+ "gate": {"min_pass_rate": GATE_MIN_PASS_RATE,
87
+ "require_review_all": True,
88
+ "fail_on_added_info": True},
89
+ "honest_limits": [
90
+ "本模块不执行提炼写入;扩批需 `consolidate.induce` 另开批次执行并留痕",
91
+ "人工裁决由库外人类给出,本模块不代替裁决",
92
+ ],
93
+ }
94
+
95
+ # ---- 通用工具 -------------------------------------------------------------
96
+
97
+ # 生效条件:x 为 str 时返回 MdCGOS(x),否则原样返回 x;
98
+ def _as_cg(x):
99
+ return MdCGOS(x) if isinstance(x, str) else x
100
+
101
+
102
+ # 生效条件:cg 具 "root" 属性时以该值为根、否则以 str(cg) 为根,与模块常量 REFINE_LOG 拼接返回;
103
+ def _log_path(cg) -> str:
104
+ root = cg.root if hasattr(cg, "root") else str(cg)
105
+ return os.path.join(root, REFINE_LOG)
106
+
107
+
108
+ # 生效条件:read_jsonl(_log_path(cg)) 返回假值时按空列表处理,仅保留 action 字段等于 "refine" 的记录;
109
+ def _read_log(cg) -> list:
110
+ return [r for r in (read_jsonl(_log_path(cg)) or [])
111
+ if r.get("action") == "refine"]
112
+
113
+
114
+ # 生效条件:cg 无 index 或 index["nodes"] 为假值时以空字典查找;nodes.get(nid) 为假值时返回空字典 {};
115
+ def _entry(cg, nid):
116
+ nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
117
+ return nodes.get(nid) or {}
118
+
119
+
120
+ # 生效条件:取 _entry(cg, nid) 的 path(假值回落空串)的父目录 basename;该 basename 为假值时返回 "(root)";
121
+ def _family(cg, nid):
122
+ """家族 = 节点所在 `cond_*` 目录名(无则 `(root)`)。"""
123
+ p = str(_entry(cg, nid).get("path") or "").replace("\\", "/")
124
+ d = os.path.basename(os.path.dirname(p))
125
+ return d or "(root)"
126
+
127
+
128
+ # 生效条件:对传入的 parts 逐项 str 后以 "|" 连接并 UTF-8 编码,返回 sha1 的 hexdigest;
129
+ def _sha(*parts) -> str:
130
+ h = hashlib.sha1("|".join(str(p) for p in parts).encode("utf-8"))
131
+ return h.hexdigest()
132
+
133
+
134
+ # 生效条件:遍历 cg.index 的 nodes,仅收 str(nid) 以 prefix 开头且条目 protected 为假值的 nid 进 ids(排序后返回),protected 为真值的计入 protected 计数;
135
+ def _pool(cg, prefix) -> tuple:
136
+ """抽检池:id 以 prefix 开头、非受保护节点(与 induce 的池口径一致)。"""
137
+ nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
138
+ ids, protected = [], 0
139
+ for nid, e in nodes.items():
140
+ if not str(nid).startswith(prefix):
141
+ continue
142
+ if (e or {}).get("protected"):
143
+ protected += 1
144
+ continue
145
+ ids.append(nid)
146
+ ids.sort()
147
+ return ids, protected
148
+
149
+
150
+ # 生效条件:cg 的 id 池经 prefix(假值回落 PREFIX_DEFAULT)过滤后,n 为 None 用 SAMPLE_N、否则 int(n),seed 假值回落 SAMPLE_SEED;pool 为空或 n<=0 时返回 ([], meta),否则按家族分层最大余数分配并在层内按 _sha(seed, id) 升序取前 k 个返回 (picked, meta);
151
+ def sample_ids(cg, n=None, seed=None, prefix=None) -> tuple:
152
+ """确定性分层抽样(家族=层,家族内按 sha1(seed|id) 排序)。
153
+
154
+ 分层 + 最大余数分配:主家族按规模拿到多数名额,稀有家族亦保留席位(n 足够时)。
155
+ 返回 `(ids, meta)`;同 `seed` 必得同样本——抽检可复核、可复算。
156
+ """
157
+ n = SAMPLE_N if n is None else int(n)
158
+ seed = seed or SAMPLE_SEED
159
+ prefix = prefix or PREFIX_DEFAULT
160
+ pool, protected = _pool(cg, prefix)
161
+ fam = {}
162
+ for nid in pool:
163
+ fam.setdefault(_family(cg, nid), []).append(nid)
164
+ meta = {"prefix": prefix, "seed": seed, "requested": int(n),
165
+ "pool": len(pool), "skipped_protected": protected,
166
+ "families": len(fam), "sampled": 0, "strata": {}}
167
+ if not pool or n <= 0:
168
+ return [], meta
169
+ n = min(int(n), len(pool))
170
+ names = sorted(fam)
171
+ raw = {f: n * len(fam[f]) / float(len(pool)) for f in names}
172
+ alloc = {f: int(math.floor(raw[f])) for f in names}
173
+ rest = n - sum(alloc.values())
174
+ for f in sorted(names, key=lambda x: (-(raw[x] - alloc[x]), x))[:rest]:
175
+ alloc[f] += 1
176
+ picked, strata = [], {}
177
+ for f in names:
178
+ k = int(alloc[f])
179
+ if k <= 0:
180
+ continue
181
+ rank = sorted(fam[f], key=lambda x: _sha(seed, x))
182
+ take = rank[:k]
183
+ picked.extend(take)
184
+ strata[f] = {"pool": len(fam[f]), "picked": len(take)}
185
+ picked.sort()
186
+ meta["sampled"] = len(picked)
187
+ meta["strata"] = strata
188
+ return picked, meta
189
+
190
+
191
+ # 生效条件:对 cg 中 nid 对应节点(cg.get(nid) 为假值时用空字典)生成字段;content 长度大于 EXCERPT_MAX 时 body 截断且 body_truncated 为 True,否则 body 为全 content;
192
+ def _item(cg, nid) -> dict:
193
+ node = cg.get(nid) or {}
194
+ fm = node.get("frontmatter") or {}
195
+ content = node.get("content") or ""
196
+ comp = nodefile.ccg_completeness(content)
197
+ e = _entry(cg, nid)
198
+ truncated = len(content) > EXCERPT_MAX
199
+ return {
200
+ "id": nid, "family": _family(cg, nid), "layer": e.get("layer"),
201
+ "tags": list(e.get("tags") or []), "importance": e.get("importance"),
202
+ "edges": len(fm.get("edges") or []),
203
+ "ccg_exempt": bool(fm.get("ccg_exempt")),
204
+ "ccg_present": comp["present"],
205
+ "ccg_missing": [m for m in nodefile.CCG_MARKS if m not in comp["present"]],
206
+ "comment": fm.get("comment"),
207
+ "body": content[:EXCERPT_MAX], "body_len": len(content),
208
+ "body_truncated": truncated,
209
+ "source_sha": _sha(content)[:16],
210
+ }
211
+
212
+
213
+ # ---- 预演:与 induce 同源的聚类口径 ---------------------------------------
214
+
215
+ # 生效条件:pool 中能取到 grams 的 nid 进入 cache;按 keys 顺序贪心,与当前 a 的 _jaccard >= float(min_jaccard) 且未分配的后续 b 并入组,组大小达到 int(min_cluster) 才成组,返回 (cache, keys, groups);
216
+ def _cluster(cg, pool, min_jaccard, min_cluster) -> tuple:
217
+ """贪心聚类(与 `consolidate.induce_memories` 同源)。返回 (cache, keys, groups)。"""
218
+ from . import subgraph
219
+ cache = {}
220
+ for nid in pool:
221
+ got = subgraph._node_terms_and_grams(cg, nid)
222
+ if got and got.get("grams"):
223
+ cache[nid] = got
224
+ keys = sorted(cache)
225
+ assigned, groups = set(), []
226
+ for i, a in enumerate(keys):
227
+ if a in assigned:
228
+ continue
229
+ ga = cache[a]["grams"]
230
+ grp = [b for b in keys[i + 1:]
231
+ if b not in assigned
232
+ and consolidate._jaccard(ga, cache[b]["grams"]) >= float(min_jaccard)]
233
+ if len(grp) + 1 < int(min_cluster):
234
+ continue
235
+ members = [a] + grp
236
+ assigned.update(members)
237
+ groups.append(members)
238
+ return cache, keys, groups
239
+
240
+
241
+ # 生效条件:terms 为空时对每个 t 返回 df[t]=0;否则对 keys 中每个 k 的 cache[k]["pos"] 统计各 term 出现次数,返回 df;
242
+ def _term_df(cache, keys, terms) -> dict:
243
+ """词面在抽检样本内的文档频率(不含语义推断)。"""
244
+ df = {t: 0 for t in terms}
245
+ if not terms:
246
+ return df
247
+ for k in keys:
248
+ pos = set(cache[k]["pos"])
249
+ for t in terms:
250
+ if t in pos:
251
+ df[t] += 1
252
+ return df
253
+
254
+
255
+ # 生效条件:x 转为 cg;min_jaccard 为 None 取 MIN_JACCARD、否则 float(min_jaccard),min_cluster 为 None 取 MIN_CLUSTER、否则 int(min_cluster);ids 为真值时 pool 为显式 ids 去重排序且 meta 标记 explicit_ids=True,否则 pool/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix);对 pool 聚类后,require_conditions 为真且某组 common 为空时跳过该组并累计 skipped_no_cond;返回含 candidates 与 sample_adequacy 的只读预演字典;
256
+ def preview(x, ids=None, n=None, seed=None, prefix=None,
257
+ min_jaccard=None, min_cluster=None, require_conditions=True) -> dict:
258
+ """提炼预演(只读):抽样 → 同源聚类 → 共性条件 + 来源 + 泛化度标记。"""
259
+ cg = _as_cg(x)
260
+ min_j = MIN_JACCARD if min_jaccard is None else float(min_jaccard)
261
+ min_c = MIN_CLUSTER if min_cluster is None else int(min_cluster)
262
+ if ids:
263
+ pool = sorted({str(i) for i in ids})
264
+ meta = {"prefix": prefix or PREFIX_DEFAULT, "seed": seed or SAMPLE_SEED,
265
+ "requested": len(pool), "pool": len(pool),
266
+ "skipped_protected": 0, "families": 0, "sampled": len(pool),
267
+ "strata": {}, "explicit_ids": True}
268
+ else:
269
+ pool, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
270
+
271
+ cache, keys, groups = _cluster(cg, pool, min_j, min_c)
272
+ base = max(1, len(keys))
273
+ cands, skipped_no_cond, all_terms = [], 0, set()
274
+ for members in groups:
275
+ common = consolidate._common_terms([cache[m]["pos"] for m in members])
276
+ if require_conditions and not common:
277
+ skipped_no_cond += 1
278
+ continue
279
+ neg = consolidate._union_terms([cache[m]["neg"] for m in members])
280
+ all_terms.update(common)
281
+ cands.append({"concept_id": consolidate._concept_id(members),
282
+ "members": members, "size": len(members),
283
+ "common_conditions": common,
284
+ "non_applicable": neg})
285
+
286
+ df = _term_df(cache, keys, sorted(all_terms))
287
+ for c in cands:
288
+ members = c["members"]
289
+ src = {t: [m for m in members if t in cache[m]["pos"]]
290
+ for t in c["common_conditions"]}
291
+ gap = [t for t, ms in src.items() if len(ms) < len(members)]
292
+ generic = [{"term": t, "df": df.get(t, 0),
293
+ "ratio": round(df.get(t, 0) / float(base), 4)}
294
+ for t in c["common_conditions"]
295
+ if df.get(t, 0) / float(base) >= GENERIC_DF]
296
+ c.update({
297
+ "condition_sources": src,
298
+ "grounding_gap": gap,
299
+ "generic_conditions": generic,
300
+ "discriminating": [t for t in c["common_conditions"]
301
+ if df.get(t, 0) / float(base) < GENERIC_DF],
302
+ "reason": ("%d 条记忆内容相近且共享条件「%s」→ 归纳为概念"
303
+ % (len(members),
304
+ "、".join(c["common_conditions"][:MAX_TERMS]) or "无")),
305
+ })
306
+ generic_only = sum(1 for c in cands if not c["discriminating"])
307
+ adequacy = "ok" if cands else "insufficient"
308
+ return {
309
+ "root": cg.root, "dry_run": True, "readonly": True,
310
+ "action": "refine_preview", "op": "maintain",
311
+ "prefix": meta["prefix"], "seed": meta["seed"],
312
+ "requested": meta["requested"], "pool": meta["pool"],
313
+ "skipped_protected": meta["skipped_protected"],
314
+ "families": meta["families"], "strata": meta["strata"],
315
+ "sampled": meta["sampled"], "sample": pool,
316
+ "indexed": len(keys), "min_cluster": min_c, "min_jaccard": min_j,
317
+ "require_conditions": bool(require_conditions),
318
+ "clusters": len(cands), "candidates": cands,
319
+ "skipped_no_condition": skipped_no_cond,
320
+ "candidates_generic_only": generic_only,
321
+ "df_basis": "sampled", "generic_df_threshold": GENERIC_DF,
322
+ "sample_adequacy": adequacy,
323
+ "note": ("预演:未写盘、未改任何节点;候选供人工核对提炼口径"
324
+ if cands else
325
+ "抽检样本不足以产出候选(样本量或抽检单位需校准),不得据此扩批"),
326
+ }
327
+
328
+
329
+ # ---- 工单 ---------------------------------------------------------------
330
+
331
+ # 生效条件:x 转为 cg;prefix 假值回落 PREFIX_DEFAULT,seed 假值回落 SAMPLE_SEED;ids 为真值时 sample 为显式 ids 去重排序且 real_pool=len(sample)、skipped=0、families=0,否则 sample/meta 来自 sample_ids(cg, n=n, seed=seed, prefix=prefix) 并取 meta["pool"]/meta["skipped_protected"]/meta["families"];items 为 sample 的 _item,preview 以 sample 为显式 ids 调用,返回只读工单;
332
+ def plan(x, ids=None, n=None, seed=None, prefix=None,
333
+ min_jaccard=None, min_cluster=None) -> dict:
334
+ """抽检工单(只读):确定性样本 + 原文摘录 + 字段缺口 + 口径声明 + 预演。"""
335
+ cg = _as_cg(x)
336
+ prefix = prefix or PREFIX_DEFAULT
337
+ seed = seed or SAMPLE_SEED
338
+ if ids:
339
+ sample = sorted({str(i) for i in ids})
340
+ real_pool, skipped, families = len(sample), 0, 0
341
+ else:
342
+ sample, meta = sample_ids(cg, n=n, seed=seed, prefix=prefix)
343
+ real_pool, skipped, families = (meta["pool"], meta["skipped_protected"],
344
+ meta["families"])
345
+ items = [_item(cg, nid) for nid in sample]
346
+ pv = preview(cg, ids=sample, prefix=prefix,
347
+ min_jaccard=min_jaccard, min_cluster=min_cluster)
348
+ strata = {}
349
+ for it in items:
350
+ k = it["family"]
351
+ s = strata.setdefault(k, {"picked": 0, "missing_fields": {},
352
+ "ccg_exempt": 0})
353
+ s["picked"] += 1
354
+ if it["ccg_exempt"]:
355
+ s["ccg_exempt"] += 1
356
+ for m in it["ccg_missing"]:
357
+ s["missing_fields"][m] = s["missing_fields"].get(m, 0) + 1
358
+ return {
359
+ "root": cg.root, "dry_run": True, "readonly": True,
360
+ "action": "refine", "op": "maintain",
361
+ "prefix": prefix, "seed": seed,
362
+ "requested": n if n is not None else (len(sample) if ids else SAMPLE_N),
363
+ "pool": real_pool, "skipped_protected": skipped,
364
+ "sampled": len(items), "families": families,
365
+ "sample": sample, "sample_sha": _sha(*sample)[:16],
366
+ "strata": strata, "items": items,
367
+ "worklist": items, "spec": SPEC,
368
+ "preview": {"clusters": pv["clusters"], "candidates": pv["candidates"],
369
+ "min_cluster": pv["min_cluster"],
370
+ "min_jaccard": pv["min_jaccard"],
371
+ "skipped_no_condition": pv["skipped_no_condition"],
372
+ "candidates_generic_only": pv["candidates_generic_only"],
373
+ "sample_adequacy": pv["sample_adequacy"],
374
+ "df_basis": pv["df_basis"],
375
+ "generic_df_threshold": pv["generic_df_threshold"]},
376
+ "gate_rule": SPEC["gate"],
377
+ "note": ("抽检工单(只读):供人工核对提炼口径;核对通过前不得扩批。"
378
+ "行 `apply=true` 只落抽检留痕,不改任何节点。"),
379
+ }
380
+
381
+
382
+ # 生效条件:x 转为 cg;target 为 None 取 CALIBRATE_TARGET、否则 int(target),cap 为 None 取 CALIBRATE_CAP、否则 int(cap),初始 n=min(SAMPLE_N if n0 is None else int(n0), cap);循环以 n 调 preview(cg, n=n, seed=seed, prefix=prefix, min_jaccard=min_jaccard, min_cluster=min_cluster) 并记录 step,直到 pv["clusters"] >= target 则 chosen=step 并 break,或 n >= min(cap, pool or cap) 则 break;chosen 为 None 时 blocked=True 并按 steps 汇总 recommend,否则 recommend 基于 chosen;返回只读校准报告;
383
+ def calibrate(x, target=None, cap=None, n0=None, seed=None, prefix=None,
384
+ min_jaccard=None, min_cluster=None) -> dict:
385
+ """样本量校准(只读):20 条起步有界爬坡,直到产出 `target` 个候选或触顶。
386
+
387
+ 目的是回答「20 条工单为什么交不出候选」,并给出**有界**的替代样本量;
388
+ 若爬坡到顶仍无候选、或候选共性条件全为高 df 模板词,则如实报
389
+ `blocked` 并指出真正的瓶颈是**提炼口径**而非样本量——不得据此扩批。
390
+ """
391
+ cg = _as_cg(x)
392
+ target = CALIBRATE_TARGET if target is None else int(target)
393
+ cap = CALIBRATE_CAP if cap is None else int(cap)
394
+ n = min(SAMPLE_N if n0 is None else int(n0), cap)
395
+ steps, chosen, pool = [], None, None
396
+ while True:
397
+ pv = preview(cg, n=n, seed=seed, prefix=prefix,
398
+ min_jaccard=min_jaccard, min_cluster=min_cluster)
399
+ pool = pv["pool"]
400
+ step = {"n": n, "sampled": pv["sampled"], "clusters": pv["clusters"],
401
+ "candidates_generic_only": pv["candidates_generic_only"],
402
+ "discriminating_terms": sorted(
403
+ {t for c in pv["candidates"] for t in c["discriminating"]})[:12]}
404
+ steps.append(step)
405
+ if pv["clusters"] >= target:
406
+ chosen = step
407
+ break
408
+ top = min(cap, pool or cap)
409
+ if n >= top:
410
+ break
411
+ n = min(n * 2, top)
412
+ blocked = chosen is None
413
+ saw_clusters = any(s["clusters"] for s in steps)
414
+ total_c = sum(s["clusters"] for s in steps)
415
+ gen_only = sum(s["candidates_generic_only"] for s in steps)
416
+ if not blocked:
417
+ rec = ("抽检样本量校准为 n=%d(产出 %d 个候选);仍须人工核对提炼口径,"
418
+ "核对通过前不得扩批" % (chosen["n"], chosen["clusters"]))
419
+ else:
420
+ parts = ["爬坡至 cap=%d 仍只产出 %d 个候选(< target=%d),样本量不足以"
421
+ "直接支撑抽检" % (cap, steps[-1]["clusters"], target)]
422
+ if saw_clusters:
423
+ if gen_only >= total_c:
424
+ parts.append("且已产出候选的共性条件**全部**为高 df 模板词(无区分性)")
425
+ else:
426
+ parts.append("且候选中 %d/%d 的共性条件全为高 df 模板词,"
427
+ "其余『区分性』项形如整行条件文本(含时间戳),"
428
+ "属归一化缺失导致的伪区分性" % (gen_only, total_c))
429
+ parts.append("须先修正提炼口径(剔除模板词 + 归一化/裁剪整行文本)"
430
+ "或以「候选」为抽检单位重新校准;当前状态不得扩批")
431
+ rec = ";".join(parts)
432
+ return {"ok": True, "action": "refine_calibrate", "op": "maintain",
433
+ "root": cg.root, "dry_run": True, "readonly": True,
434
+ "target": target, "cap": cap, "pool": pool,
435
+ "steps": steps, "chosen": chosen, "blocked": blocked,
436
+ "sample_adequacy": "ok" if not blocked else "insufficient",
437
+ "recommend": rec, "note": "只读校准:未写盘、未改任何节点。"}
438
+
439
+
440
+ # ---- 留痕与扩批闸门 ------------------------------------------------------
441
+
442
+ # 生效条件:candidates 中取 concept_id 为真值的 id 列表;verdicts 中为 dict 且 concept_id 非空且首次出现的条目计入 reviewed 并累加其中 faithful/added_info 的真值计数;ids 非空时按 reviewed < len(ids) → awaiting_human_review、added 非零 → added_info_detected、faithful_rate < GATE_MIN_PASS_RATE → faithful_rate_below_gate、否则 ok 且 expand_allowed=True;ids 为空时 reason="no_candidates" 且 expand_allowed=False;
443
+ def _verdict_stats(verdicts, candidates) -> dict:
444
+ ids = [c.get("concept_id") for c in candidates if c.get("concept_id")]
445
+ seen, faithful, added = set(), 0, 0
446
+ for v in verdicts or []:
447
+ if not isinstance(v, dict):
448
+ continue
449
+ cid = v.get("concept_id")
450
+ if not cid or cid in seen:
451
+ continue
452
+ seen.add(cid)
453
+ faithful += 1 if v.get("faithful") else 0
454
+ added += 1 if v.get("added_info") else 0
455
+ reviewed = len(seen)
456
+ rate = (faithful / float(reviewed)) if reviewed else 0.0
457
+ expand, reason = False, "no_candidates"
458
+ if ids:
459
+ if reviewed < len(ids):
460
+ reason = "awaiting_human_review"
461
+ elif added:
462
+ reason = "added_info_detected"
463
+ elif rate < GATE_MIN_PASS_RATE:
464
+ reason = "faithful_rate_below_gate"
465
+ else:
466
+ reason = "ok"
467
+ expand = True
468
+ return {"candidates": len(ids), "reviewed": reviewed,
469
+ "faithful": faithful, "added_info": added,
470
+ "faithful_rate": round(rate, 4),
471
+ "min_pass_rate": GATE_MIN_PASS_RATE,
472
+ "expand_allowed": expand, "reason": reason}
473
+
474
+
475
+ # 生效条件:x 转为 cg;以 ids/n/seed/prefix/min_jaccard/min_cluster 调 plan 得到 p,batch 假值(含 None/空串)回落当前时间字符串;从 p["preview"]["candidates"] 与 verdicts 经 _verdict_stats 得 stats;把含 batch/actor/prefix/seed/sample/verdicts/gate/note 的 record 追加写入 _log_path(cg),返回 rep;
476
+ def apply(x, batch=None, actor=None, verdicts=None, note=None,
477
+ ids=None, n=None, seed=None, prefix=None,
478
+ min_jaccard=None, min_cluster=None) -> dict:
479
+ """落抽检批次 + 人工裁决到 `_refine.jsonl`。**不改写任何节点**。"""
480
+ cg = _as_cg(x)
481
+ p = plan(cg, ids=ids, n=n, seed=seed, prefix=prefix,
482
+ min_jaccard=min_jaccard, min_cluster=min_cluster)
483
+ batch = batch or time.strftime("%Y%m%d-%H%M%S")
484
+ cands = p["preview"]["candidates"]
485
+ stats = _verdict_stats(verdicts, cands)
486
+ record = {
487
+ "t": time.time(), "action": "refine", "batch": batch, "actor": actor,
488
+ "prefix": p["prefix"], "seed": p["seed"], "requested": p["requested"],
489
+ "pool": p["pool"], "sampled": p["sampled"],
490
+ "sample": p["sample"], "sample_sha": p["sample_sha"],
491
+ "sample_adequacy": p["preview"]["sample_adequacy"],
492
+ "min_cluster": p["preview"].get("min_cluster"),
493
+ "candidates": [{"concept_id": c["concept_id"], "members": c["members"],
494
+ "common_conditions": c["common_conditions"],
495
+ "grounding_gap": c["grounding_gap"],
496
+ "discriminating": c["discriminating"]}
497
+ for c in cands],
498
+ "verdicts": list(verdicts or []), "gate": stats, "note": note,
499
+ }
500
+ append_jsonl(_log_path(cg), record)
501
+ rep = {"ok": True, "action": "refine", "op": "maintain", "batch": batch,
502
+ "root": cg.root, "sampled": record["sampled"],
503
+ "candidates": stats["candidates"], "reviewed": stats["reviewed"],
504
+ "gate": stats, "log": REFINE_LOG,
505
+ "note": ("抽检留痕已落盘(未改任何节点);"
506
+ + ("闸门放行扩批(仍需另开 induce 批次并留痕)"
507
+ if stats["expand_allowed"] else
508
+ "闸门未放行:" + stats["reason"]))}
509
+ return rep
510
+
511
+
512
+ # 生效条件:x 转为 cg;batch 为真值时只保留 _read_log(cg) 中 batch 字段相等的记录,过滤后为空时返回 batches=0、expand_allowed=False、reason="no_batch";否则取最后一条记录,以其 verdicts 与 candidates(假值转空列表)经 _verdict_stats 复算并返回 expand_allowed/reason 等;
513
+ def gate(x, batch=None) -> dict:
514
+ """扩批闸门:读留痕复算通过率(不写盘)。"""
515
+ cg = _as_cg(x)
516
+ recs = _read_log(cg)
517
+ if batch:
518
+ recs = [r for r in recs if r.get("batch") == batch]
519
+ if not recs:
520
+ return {"ok": True, "action": "refine_gate", "op": "maintain",
521
+ "root": cg.root, "batch": batch, "batches": 0,
522
+ "expand_allowed": False, "reason": "no_batch"}
523
+ rec = recs[-1]
524
+ stats = _verdict_stats(rec.get("verdicts"), rec.get("candidates") or [])
525
+ return {"ok": True, "action": "refine_gate", "op": "maintain",
526
+ "root": cg.root, "batch": rec.get("batch"),
527
+ "batches": len({r.get("batch") for r in recs}),
528
+ "sampled": rec.get("sampled"),
529
+ "sample_adequacy": rec.get("sample_adequacy"),
530
+ "gate": stats, "expand_allowed": stats["expand_allowed"],
531
+ "reason": stats["reason"],
532
+ "note": ("闸门放行:可另开 induce 批次扩大提炼规模并逐批留痕"
533
+ if stats["expand_allowed"] else
534
+ "闸门未放行,禁止扩大批次;不得盲跑全量。")}
535
+
536
+
537
+ # 生效条件:batch 为真值时只保留该批次记录,total 先记为过滤后条数;limit 非 None 且 >=0 时以 recs[-int(limit):] 截尾(limit=0 因 [-0:] 等价 [0:] 仍返回全部记录),limit 为 None 或负数时不截断;
538
+ def history(x, limit=100, batch=None) -> dict:
539
+ cg = _as_cg(x)
540
+ recs = _read_log(cg)
541
+ if batch:
542
+ recs = [r for r in recs if r.get("batch") == batch]
543
+ total = len(recs)
544
+ if limit is not None and limit >= 0:
545
+ recs = recs[-int(limit):]
546
+ return {"root": cg.root, "total": total, "returned": len(recs),
547
+ "action": "refine", "records": recs}
548
+
549
+
550
+ # ---- CLI(真实库抽检/预演用;MCP 侧走 maintain action) -------------------
551
+
552
+ # 生效条件:解析 argv(None 时由 argparse 取 sys.argv)得到 root/n/seed/prefix/min-jaccard/min-cluster/report 等参数;a.calibrate 为真时调 calibrate 并打印 target/cap/pool/blocked/sample_adequacy/recommend 及每步摘要;否则 a.preview 为真选 preview、为假选 plan,调用后打印 thin 摘要与候选并返回 0;
553
+ def _main(argv=None) -> int:
554
+ import argparse
555
+ import json
556
+ ap = argparse.ArgumentParser(description="G6 提炼抽检(默认只预演,不写盘)")
557
+ ap.add_argument("root", help="认知图库根")
558
+ ap.add_argument("--n", type=int, default=SAMPLE_N, help="抽检条数(默认 20)")
559
+ ap.add_argument("--seed", default=SAMPLE_SEED, help="抽样种子(同 seed 同样本)")
560
+ ap.add_argument("--prefix", default=PREFIX_DEFAULT, help="id 前缀(默认 node_)")
561
+ ap.add_argument("--min-jaccard", type=float, default=None)
562
+ ap.add_argument("--min-cluster", type=int, default=None)
563
+ ap.add_argument("--preview", action="store_true", help="只出预演(不出工单正文)")
564
+ ap.add_argument("--calibrate", action="store_true",
565
+ help="只做样本量校准(20 条起步有界爬坡)")
566
+ ap.add_argument("--report", default=None, help="把汇总 JSON 另存一份")
567
+ a = ap.parse_args(argv)
568
+ if a.calibrate:
569
+ rep = calibrate(a.root, seed=a.seed, prefix=a.prefix,
570
+ min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
571
+ if a.report:
572
+ with open(a.report, "w", encoding="utf-8") as f:
573
+ json.dump(rep, f, ensure_ascii=False, indent=2)
574
+ print(json.dumps({k: rep[k] for k in
575
+ ("target", "cap", "pool", "blocked",
576
+ "sample_adequacy", "recommend")},
577
+ ensure_ascii=False, indent=2))
578
+ for s in rep["steps"]:
579
+ print(" - n=%4d clusters=%2d generic_only=%2d discriminating=%s"
580
+ % (s["n"], s["clusters"], s["candidates_generic_only"],
581
+ s["discriminating_terms"][:6]))
582
+ return 0
583
+ fn = preview if a.preview else plan
584
+ rep = fn(a.root, n=a.n, seed=a.seed, prefix=a.prefix,
585
+ min_jaccard=a.min_jaccard, min_cluster=a.min_cluster)
586
+ if a.report:
587
+ with open(a.report, "w", encoding="utf-8") as f:
588
+ json.dump(rep, f, ensure_ascii=False, indent=2)
589
+ pv = rep.get("preview") if "preview" in rep else rep
590
+ thin = {k: rep.get(k) for k in ("action", "prefix", "seed", "requested",
591
+ "pool", "sampled", "families")}
592
+ thin.update({k: pv.get(k) for k in ("clusters", "sample_adequacy",
593
+ "candidates_generic_only")})
594
+ print(json.dumps(thin, ensure_ascii=False, indent=2))
595
+ for c in (pv.get("candidates") or []):
596
+ print(" - %s size=%d common=%s generic=%s gap=%s"
597
+ % (c["concept_id"], c["size"], c["common_conditions"][:4],
598
+ [g["term"] for g in c.get("generic_conditions", [])],
599
+ c.get("grounding_gap")))
600
+ return 0
601
+
602
+
603
+ if __name__ == "__main__":
604
+ import sys
605
605
  sys.exit(_main())