@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/sources.py CHANGED
@@ -1,583 +1,817 @@
1
- # -*- coding: utf-8 -*-
2
- """md_cg · 设备驱动(记忆 OS #3):把外部会话流接进认知图
3
-
4
- 设计:
5
- Source(事件源)—— 把某种外部存储解析成统一事件流:
6
- {"t": 毫秒时间戳, "seq": 序号, "role": ..., "text": ..., "session": ..., "cwd": ...}
7
- Ingestor(摄取器)—— 增量 watermark + 去重 + 写节点 + 自动 fix-pair 挖掘。
8
-
9
- 内置源:
10
- · JsonlSource —— 通用 JSONL(每行一个事件对象)
11
- · DSHSessionSource —— DeepSeek Harness 会话(~/.dsh/sessions/**/session.jsonl[.zstd])
12
- zstd 为**可选**依赖:缺失时优雅降级(跳过 .zstd 文件并告警)
13
-
14
- 默认敏感度:会话内容是私有记忆 → sensitivity="private"(避免落入公开根)。
15
-
16
- 零第三方依赖(zstd 为可选增强)。
17
- """
18
- from __future__ import annotations
19
-
20
- import glob
21
- import json
22
- import os
23
- import time
24
-
25
- from .security import DEFAULT_SENSITIVITY
26
-
27
- # 会话事件的默认落层与敏感度
28
- SESSION_LAYER = "contextual"
29
- SESSION_SENSITIVITY = "private"
30
-
31
-
32
- # --------------------------------------------------------------------------
33
- # 事件源
34
- # --------------------------------------------------------------------------
35
-
36
- # 生效条件:无必填构造形参,类常量 name="source" 即实例默认;仅当子类覆写 events() 时才产出事件,基类 events() 恒抛 NotImplementedError。
37
- class Source:
38
- """事件源基类。"""
39
-
40
- name = "source"
41
-
42
- # 生效条件:任何调用都直接 raise NotImplementedError(基类占位,无其它分支)。
43
- def events(self):
44
- raise NotImplementedError
45
-
46
- # 生效条件:无前置;返回类常量 self.name(基类为 "source"),作为 watermark 的稳定标识,不含路径与运行期状态;
47
- def key(self):
48
- """源的稳定标识(用于 watermark)。"""
49
- return self.name
50
-
51
-
52
- # 生效条件:required 形参 path 总被存入 self.path;可选形参 name 为假值(None/空串)时 self.name 回落到 "jsonl:"+os.path.basename(path),为真值时用 name 本身,t_key/role_key/text_key/default_role 原样存入属性。
53
- class JsonlSource(Source):
54
- """通用 JSONL 会话源。
55
-
56
- 每行是事件对象;字段映射可配置:
57
- t_key —— 时间戳字段(默认 "time",也接受 ISO 字符串)
58
- role_key —— 角色字段(默认 "role")
59
- text_key —— 文本字段(默认 "text")
60
- """
61
-
62
- # 生效条件:传入 path;name 为假值(None/空串)时回落为 "jsonl:"+os.path.basename(path),t_key/role_key/text_key/default_role 原样存为属性(默认值 "time"/"role"/"text"/"user")。
63
- def __init__(self, path: str, name: str = None, t_key="time", role_key="role",
64
- text_key="text", default_role="user"):
65
- self.path = path
66
- self.name = name or ("jsonl:" + os.path.basename(path))
67
- self.t_key, self.role_key, self.text_key = t_key, role_key, text_key
68
- self.default_role = default_role
69
-
70
- # 生效条件:无前置;返回 self.name(构造时已回落为 "jsonl:"+basename(path)),只随构造参数变化、不随文件内容变化;
71
- def key(self):
72
- return self.name
73
-
74
- # 生效条件:o.get(self.t_key) 为 int/float 时返回 float(v) 乘 1000(v<1e12)或乘 1(否则);为字符串且 v[:19] 按 "%Y-%m-%dT%H:%M:%S" 解析成功时返回 mktime*1000,抛 ValueError 时返回 0.0;键缺失或其它类型返回 0.0。
75
- def _ts(self, o):
76
- v = o.get(self.t_key)
77
- if isinstance(v, (int, float)):
78
- return float(v) * (1000.0 if v < 1e12 else 1.0)
79
- if isinstance(v, str):
80
- try:
81
- return time.mktime(time.strptime(v[:19], "%Y-%m-%dT%H:%M:%S")) * 1000
82
- except ValueError:
83
- return 0.0
84
- return 0.0
85
-
86
- # 生效条件:os.path.exists(self.path) 为真时逐行产出,空行、json.loads 抛 ValueError、o.get(self.text_key) 为假的行被跳过,产出项为 t=self._ts(o)、seq=o.get("seq", i)、role=o.get(self.role_key) or self.default_role、text=str(text)、session/cwd 取 o 同名键;path 不存在时直接 return 不产出。
87
- def events(self):
88
- if not os.path.exists(self.path):
89
- return
90
- with open(self.path, encoding="utf-8", errors="replace") as f:
91
- for i, line in enumerate(f):
92
- line = line.strip()
93
- if not line:
94
- continue
95
- try:
96
- o = json.loads(line)
97
- except ValueError:
98
- continue
99
- text = o.get(self.text_key)
100
- if not text:
101
- continue
102
- yield {"t": self._ts(o), "seq": o.get("seq", i),
103
- "role": o.get(self.role_key) or self.default_role,
104
- "text": str(text), "session": o.get("session"),
105
- "cwd": o.get("cwd")}
106
-
107
-
108
- # 生效条件:path 指向的内容可读且 zstandard 可导入时返回 StringIO(raw.decode("utf-8", errors="replace"));ImportError 时返回 None。
109
- def _zstd_reader(path):
110
- """返回可读的文本迭代器;zstd 不可用返回 None。"""
111
- import io
112
- try:
113
- import zstandard as zstd
114
- except ImportError:
115
- return None
116
- with open(path, "rb") as f:
117
- raw = zstd.ZstdDecompressor().stream_reader(f).read()
118
- return io.StringIO(raw.decode("utf-8", errors="replace"))
119
-
120
-
121
- # 生效条件:required 形参 path 总被存入 self.path,可选形参 include_reasoning 原样存入,self.name 恒为 "dsh:"+os.path.basename(os.path.dirname(path))(与 include_reasoning 取值无关)。
122
- class DSHSessionSource(Source):
123
- """DeepSeek Harness 会话源。
124
-
125
- 事件映射:
126
- user/message → role=user
127
- assistant/message → role=assistant(只取 content[].text,reasoning 默认丢弃)
128
- tool/call → role=command("name(args)" 形式)
129
- tool/result → role=tool-output
130
- """
131
-
132
- # 生效条件:传入 path 即成立,include_reasoning 原样存为属性(默认 False),name 固定为 "dsh:"+os.path.basename(os.path.dirname(path))。
133
- def __init__(self, path: str, include_reasoning: bool = False):
134
- self.path = path
135
- self.include_reasoning = include_reasoning
136
- self.name = "dsh:" + os.path.basename(os.path.dirname(path))
137
-
138
- # 生效条件:无前置;返回 self.name(构造时固定为 "dsh:"+basename(dirname(path))),与 include_reasoning 取值无关;
139
- def key(self):
140
- return self.name
141
-
142
- @staticmethod
143
- # 生效条件:可选形参 root 为假值(None/空串)时改用默认目录 os.path.join(expanduser("~"),".dsh","sessions"),否则用传入 root;对 root 下递归 glob 到的 session.jsonl 与 session.jsonl.zstd 按 -os.path.getsize 降序排序(两处 glob 均无命中时为空列表),可选形参 limit 为假值(None/0)时返回全部 files,否则返回 files[:limit]。
144
- def discover(root: str = None, limit: int = None):
145
- root = root or os.path.join(os.path.expanduser("~"), ".dsh", "sessions")
146
- files = glob.glob(os.path.join(root, "**", "session.jsonl"), recursive=True)
147
- files += glob.glob(os.path.join(root, "**", "session.jsonl.zstd"), recursive=True)
148
- files.sort(key=lambda p: (-os.path.getsize(p), p))
149
- return files[:limit] if limit else files
150
-
151
- # 生效条件:self.path 以 ".zstd" 结尾时经 _zstd_reader 逐行产出(其返回 None 时 raise RuntimeError),否则以 utf-8/errors=replace 打开 self.path 逐行产出。
152
- def _lines(self):
153
- if self.path.endswith(".zstd"):
154
- fh = _zstd_reader(self.path)
155
- if fh is None:
156
- raise RuntimeError("zstd 不可用(pip install zstandard),跳过该会话")
157
- try:
158
- yield from fh
159
- finally:
160
- fh.close()
161
- else:
162
- with open(self.path, encoding="utf-8", errors="replace") as f:
163
- yield from f
164
-
165
- @staticmethod
166
- # 生效条件:required 形参 content 为 str 时原样返回 content;为 list 时收集其中 str 元素及 type 属于 ("text","input-text") 且 text 为真值的 dict 元素(取 str(c["text"])),以 "\n" 连接返回(无可收集元素时为空串 "");既非 str 也非 list 时返回 ""。
167
- def _text_of(content):
168
- """content 可能是 [{type,text}] 或字符串。"""
169
- if isinstance(content, str):
170
- return content
171
- if isinstance(content, list):
172
- parts = []
173
- for c in content:
174
- if isinstance(c, dict):
175
- if c.get("type") in ("text", "input-text") and c.get("text"):
176
- parts.append(str(c["text"]))
177
- elif isinstance(c, str):
178
- parts.append(c)
179
- return "\n".join(parts)
180
- return ""
181
-
182
- # 生效条件:逐行解析后按 o.get("type") 分派——"session" 只更新 sess/cwd 不产出;"user/message" 产出 role=user 与 _text_of(data.get("content"));"assistant/message" 产出 role=assistant 与 _text_of(msg.get("content")),include_reasoning 为真时再把 content 中 type=="reasoning" 且有 text 的项追加 "\n[reasoning] "+str(c["text"]);"tool/call" 产出 role=command 与 f"{name}({arguments or ''})";"tool/result" 产出 role=tool-output,仅当 inner[0] 为 dict 时 text=_text_of(inner[0].get("content"));其它 type 或 ev 中 text 为空的事件不产出。
183
- def events(self):
184
- sess = None
185
- cwd = None
186
- for line in self._lines():
187
- line = line.strip()
188
- if not line:
189
- continue
190
- try:
191
- o = json.loads(line)
192
- except ValueError:
193
- continue
194
- t = o.get("type")
195
- data = o.get("data") or {}
196
- if t == "session":
197
- sess, cwd = o.get("id"), o.get("cwd")
198
- continue
199
- ev = {"t": float(o.get("time") or 0), "seq": o.get("seq"),
200
- "session": sess, "cwd": cwd}
201
- if t == "user/message":
202
- ev["role"], ev["text"] = "user", self._text_of(data.get("content"))
203
- elif t == "assistant/message":
204
- msg = data.get("message") or {}
205
- ev["role"] = "assistant"
206
- ev["text"] = self._text_of(msg.get("content"))
207
- if self.include_reasoning:
208
- for c in (msg.get("content") or []):
209
- if isinstance(c, dict) and c.get("type") == "reasoning" \
210
- and c.get("text"):
211
- ev["text"] = (ev["text"] or "") + "\n[reasoning] " + str(c["text"])
212
- elif t == "tool/call":
213
- ev["role"] = "command"
214
- ev["text"] = f"{data.get('name')}({data.get('arguments') or ''})"
215
- elif t == "tool/result":
216
- msg = data.get("message") or {}
217
- inner = msg.get("content") or []
218
- txt = ""
219
- if inner and isinstance(inner[0], dict):
220
- txt = self._text_of(inner[0].get("content"))
221
- ev["role"], ev["text"] = "tool-output", txt
222
- else:
223
- continue
224
- if ev.get("text"):
225
- yield ev
226
-
227
-
228
- # --------------------------------------------------------------------------
229
- # 摄取器
230
- # --------------------------------------------------------------------------
231
-
232
- # 生效条件:required 形参 cg 总被存入并以其 cg.root 拼出 self.path=os.path.join(cg.root,"_sources.json");layer/sensitivity 原样存入,未传时取模块级常量 SESSION_LAYER、SESSION_SENSITIVITY 作为默认值。
233
- class Ingestor:
234
- """增量摄取:watermark + 去重 + 写节点 + 自动 fix-pair 挖掘。
235
-
236
- watermark 文件:<root>/_sources.json
237
- {source_key: {"t": 最后时间戳(ms), "seq": 最后序号, "count": 已摄取条数}}
238
- """
239
-
240
- # 生效条件:传入带 root 的 cg 即成立,layer/sensitivity 默认 SESSION_LAYER/SESSION_SENSITIVITY 并原样存为属性,路径为 os.path.join(cg.root, "_sources.json")。
241
- def __init__(self, cg, layer: str = SESSION_LAYER,
242
- sensitivity: str = SESSION_SENSITIVITY):
243
- self.cg = cg
244
- self.layer = layer
245
- self.sensitivity = sensitivity
246
- self.path = os.path.join(cg.root, "_sources.json")
247
-
248
- # ---- watermark ----
249
-
250
- # 生效条件:self.path 存在、json.load 成功且结果为 dict 时返回该 dict;path 不存在、抛 ValueError/OSError 或结果非 dict 时返回 {"schema": 1, "sources": {}}。
251
- def _load(self):
252
- if os.path.exists(self.path):
253
- try:
254
- with open(self.path, encoding="utf-8") as f:
255
- d = json.load(f)
256
- if isinstance(d, dict):
257
- return d
258
- except (ValueError, OSError):
259
- pass
260
- return {"schema": 1, "sources": {}}
261
-
262
- # 生效条件:传入 d 时以 ensure_ascii=False/indent=1 写入 self.path+".tmp",再 os.replace 覆盖 self.path。
263
- def _save(self, d):
264
- tmp = self.path + ".tmp"
265
- with open(tmp, "w", encoding="utf-8") as f:
266
- json.dump(d, f, ensure_ascii=False, indent=1)
267
- os.replace(tmp, self.path)
268
-
269
- # 生效条件:key 命中 self._load()["sources"] 时返回其值,缺 key 时返回 {}(_load 结果缺 "sources" 键则抛 KeyError)。
270
- def watermark(self, key: str):
271
- return self._load()["sources"].get(key, {})
272
-
273
- # 生效条件:调用即返回 dict(self._load()["sources"]) 的浅拷贝(_load 结果缺 "sources" 键则抛 KeyError)。
274
- def watermarks(self):
275
- return dict(self._load()["sources"])
276
-
277
- # ---- 摄取 ----
278
-
279
- # 生效条件:对必需形参 source,先按 watermark(source.key()) 跳过 t<last_t(wm 的 "t" 为假值时视作 0)及 t==last_t 且 last_seq 非 None 且 ev["seq"] 为 int 且 <= last_seq 的事件、并按 (session, seq) 去重,max_events 为真值(非 0/None)时取满即停;dry_run 为假时逐条 cg.add(nid 已在 cg.index["nodes"] 中则跳过,cg.add 抛异常则 denied+=1、记 last_error 并继续),denied 与 last_error 同时成立时补 last_error/hint,mine_fix_pairs 为真且 new_events 非空且非 dry_run 时结果附 fix_pairs,new_events 非空且非 dry_run 时以末事件写回水位(count 累加 written),最后返回 result。
280
- def ingest(self, source, mine_fix_pairs: bool = True, max_events: int = None,
281
- dry_run: bool = False):
282
- """摄取一个源的新事件。返回统计。
283
-
284
- 去重键:(session, seq) —— 同一事件不重复入库。
285
- 增量:只处理 (t, seq) 大于 watermark 的事件。
286
- """
287
- key = source.key()
288
- wm = self.watermark(key)
289
- last_t, last_seq = float(wm.get("t") or 0), wm.get("seq")
290
- new_events, seen = [], set()
291
- for ev in source.events():
292
- if ev.get("t", 0) < last_t:
293
- continue
294
- if ev.get("t", 0) == last_t and last_seq is not None \
295
- and isinstance(ev.get("seq"), int) and ev["seq"] <= last_seq:
296
- continue
297
- dedup = (ev.get("session"), ev.get("seq"))
298
- if dedup in seen:
299
- continue
300
- seen.add(dedup)
301
- new_events.append(ev)
302
- if max_events and len(new_events) >= max_events:
303
- break
304
-
305
- written, ids, denied = 0, [], 0
306
- if not dry_run:
307
- for ev in new_events:
308
- nid = "src_%s_%s" % (_sig(key)[:6], _sig(
309
- f"{ev.get('session')}:{ev.get('seq')}")[:10])
310
- if nid in self.cg.index["nodes"]:
311
- continue # 幂等
312
- body = ("# 功能名:会话事件\n"
313
- f"# 生效条件:检索「{str(ev.get('text'))[:20]}」\n"
314
- "# 子功能:记录会话事件\n"
315
- f"# 执行:{str(ev.get('text'))[:80]}\n"
316
- "# 验证方式:data(会话原始记录)\n"
317
- "# 不适用条件:其它会话\n\n"
318
- f"{ev.get('text')}\n")
319
- try:
320
- self.cg.add(nid, body, layer=self.layer, role=ev.get("role"),
321
- tags=["session", key], sensitivity=self.sensitivity,
322
- verification_basis="data",
323
- condition_space={"observation_position": key,
324
- "observation_tool": "会话流",
325
- "time_window": [ev.get("t") or 0,
326
- ev.get("t") or 0]})
327
- except Exception as exc: # noqa: BLE001 —— 权限/层错误不中断整批
328
- denied += 1
329
- self.last_error = f"{type(exc).__name__}: {exc}"
330
- continue
331
- ids.append(nid)
332
- written += 1
333
-
334
- result = {"source": key, "new_events": len(new_events), "written": written,
335
- "denied": denied, "ids": ids, "dry_run": dry_run,
336
- "sensitivity": self.sensitivity}
337
- if denied and getattr(self, "last_error", None):
338
- result["last_error"] = self.last_error
339
- result["hint"] = ("会话内容默认 sensitivity=private;"
340
- "调用方需 MDCG_CLEARANCE=private 才能写入")
341
- # 自动 fix-pair 挖掘(对标 deja-vu:错误→修复)
342
- if mine_fix_pairs and new_events and not dry_run:
343
- result["fix_pairs"] = self.cg.mine_fix_pairs(
344
- [{"role": e.get("role"), "text": e.get("text")} for e in new_events])
345
-
346
- if new_events and not dry_run:
347
- d = self._load()
348
- last = new_events[-1]
349
- d["sources"][key] = {
350
- "t": last.get("t") or last_t,
351
- "seq": last.get("seq"),
352
- "count": (wm.get("count") or 0) + written,
353
- "updated_at": time.time(),
354
- "path": getattr(source, "path", None),
355
- }
356
- self._save(d)
357
- return result
358
-
359
-
360
- # 生效条件:text 为 None 或假值时按 "" 参与 sha1;n 默认 12,返回 hexdigest 前 n 位(n=0 得空串)。
361
- def _sig(text: str, n: int = 12) -> str:
362
- import hashlib
363
- return hashlib.sha1((text or "").encode("utf-8")).hexdigest()[:n]
364
-
365
-
366
- # --------------------------------------------------------------------------
367
- # 摄取分派(P0 · ingest op):按扩展名选摄取方式,单一入口吃多种文件
368
- # --------------------------------------------------------------------------
369
- #
370
- # 三条摄取链:
371
- # session —— 会话流(.jsonl):走 Ingestor(watermark + 去重 + fix-pair)
372
- # doc —— 文档(.md/.txt/...):走 docindex.extract + refindex.add_items
373
- # code —— 代码(.py/.ts/...):走 codeindex.extract + refindex.add_items
374
- #
375
- # 设计要点:
376
- # - 注册表 INGEST_REGISTRY 是唯一真源:新增后缀只改这里。
377
- # - dir 动作分链处理:目录里 doc / code / jsonl 混放时各链互不干扰。
378
- # - 幂等:沿用 refindex.Ledger(size+mtime 水位)与 Ingestor watermark。
379
- # - 预演:dry_run=True 只统计、不写入(对应计划「可预演」要求)。
380
-
381
- INGEST_ACTIONS = ("file", "dir", "jsonl", "stat")
382
-
383
- INGEST_REGISTRY = {
384
- # 会话流
385
- ".jsonl": "session", ".ndjson": "session",
386
- # 文档
387
- ".md": "doc", ".markdown": "doc", ".txt": "doc", ".rst": "doc",
388
- ".html": "doc", ".htm": "doc",
389
- # 代码
390
- ".py": "code", ".js": "code", ".mjs": "code", ".ts": "code",
391
- ".tsx": "code", ".jsx": "code", ".go": "code", ".rs": "code",
392
- ".java": "code", ".kt": "code", ".swift": "code", ".rb": "code",
393
- ".php": "code", ".cs": "code", ".cpp": "code", ".cc": "code",
394
- ".c": "code", ".h": "code", ".hpp": "code",
395
- }
396
-
397
-
398
- # 生效条件:path 为 None 或空串时 splitext 得 "" 且未登记 → 返回 None;扩展名(小写)存在于 INGEST_REGISTRY 时返回其 kind。
399
- def dispatch_of(path: str):
400
- """按扩展名返回摄取方式(session / doc / code);未登记返回 None。"""
401
- return INGEST_REGISTRY.get(os.path.splitext(path or "")[1].lower())
402
-
403
-
404
- # 生效条件:required 形参 cg 总被存入;可选形参 sensitivity 为假值(None/空串)时内部 Ingestor 的 sensitivity 回落模块级常量 SESSION_SENSITIVITY,为真值时用传入的 sensitivity。
405
- class FileDispatcher:
406
- """单一入口吃多种文件:按扩展名分派到会话流 / 文档 / 代码三条摄取链。"""
407
-
408
- # 生效条件:传入 cg 即成立,sensitivity 为假值(None/空串)时所用 Ingestor 回落 SESSION_SENSITIVITY,否则用传入值。
409
- def __init__(self, cg, sensitivity=None):
410
- self.cg = cg
411
- # 会话链默认 sensitivity=private;调用方可显式覆盖(测试/受限环境)
412
- self.ingestor = Ingestor(cg, sensitivity=sensitivity or SESSION_SENSITIVITY)
413
-
414
- # ---- stat:看水位与支持面 ----
415
-
416
- # 生效条件:from . import refindex 与 refindex.Ledger(self.cg.root).stat() 均不抛异常时返回该 stat 结果,抛任何异常时返回 {}。
417
- def _ledger_stat(self):
418
- try:
419
- from . import refindex
420
- return refindex.Ledger(self.cg.root).stat()
421
- except Exception: # noqa: BLE001
422
- return {}
423
-
424
- # 生效条件:调用即返回由 INGEST_REGISTRY 按 kind 分组并排序后的 extensions、list(INGEST_ACTIONS)、self.ingestor.watermarks()、self._ledger_stat() 与固定 note。
425
- def stat(self):
426
- kinds = {}
427
- for ext, kind in INGEST_REGISTRY.items():
428
- kinds.setdefault(kind, []).append(ext)
429
- return {"ok": True, "kind": "stat", "actions": list(INGEST_ACTIONS),
430
- "extensions": {k: sorted(v) for k, v in sorted(kinds.items())},
431
- "watermarks": self.ingestor.watermarks(),
432
- "ledger": self._ledger_stat(),
433
- "note": ("注册表是唯一真源:新增后缀只改 INGEST_REGISTRY。"
434
- "watermarks=会话流水位;ledger=文档/代码文件水位。")}
435
-
436
- # ---- 单文件 ----
437
-
438
- # 生效条件:dispatch_of(path) 为 None 时返回 ok=False 的「不支持的后缀」结果;path 不是文件时返回 ok=False 的「文件不存在」结果;kind=="session" 时转 ingest_jsonl(path, dry_run=dry_run)(layer/sensitivity 不参与);其它 kind 转 _ingest_doc_or_code(path, kind, layer=layer, sensitivity=sensitivity, dry_run=dry_run)。
439
- def ingest_file(self, path, layer=None, sensitivity=None, dry_run=False):
440
- kind = dispatch_of(path)
441
- if kind is None:
442
- ext = os.path.splitext(path or "")[1] or "(无后缀)"
443
- return {"ok": False, "path": path, "error": f"不支持的后缀:{ext}",
444
- "supported": sorted(INGEST_REGISTRY)}
445
- if not os.path.isfile(path):
446
- return {"ok": False, "path": path, "error": "文件不存在"}
447
- if kind == "session":
448
- return self.ingest_jsonl(path, dry_run=dry_run)
449
- return self._ingest_doc_or_code(path, kind, layer=layer,
450
- sensitivity=sensitivity, dry_run=dry_run)
451
-
452
- # 生效条件:kind=="code" 用 codeindex 否则用 docindex;mod.extract 抛 ValueError 时返回 ok=False 的「抽取失败」;dry_run 为真时只返回 items 计数与前 20 个 node_id 不写盘;否则经 refindex.add_items(kind 为 code_ref/doc_ref)写入并返回 indexed、ids[:20] 与 sensitivity。
453
- def _ingest_doc_or_code(self, path, kind, layer=None, sensitivity=None,
454
- dry_run=False):
455
- from . import codeindex, docindex, refindex
456
- mod = codeindex if kind == "code" else docindex
457
- rel = os.path.basename(path)
458
- with open(path, encoding="utf-8", errors="replace") as f:
459
- src = f.read()
460
- try:
461
- items = mod.extract(src, path=rel,
462
- suffix=os.path.splitext(path)[1].lower())
463
- except ValueError as exc:
464
- return {"ok": False, "path": path, "error": f"抽取失败:{exc}"}
465
- items = items or []
466
- if dry_run:
467
- return {"ok": True, "dry_run": True, "kind": kind, "path": path,
468
- "items": len(items),
469
- "ids": [mod.node_id(i) for i in items[:20]],
470
- "note": "预演:只抽取计数,未写盘"}
471
- ref_kind = "code_ref" if kind == "code" else "doc_ref"
472
- ids, sens = refindex.add_items(
473
- self.cg, items, kind=ref_kind,
474
- root=os.path.dirname(path) or ".", layer=layer,
475
- sensitivity=sensitivity)
476
- return {"ok": True, "kind": kind, "path": path, "items": len(items),
477
- "indexed": len(ids), "ids": ids[:20], "sensitivity": sens}
478
-
479
- # ---- 目录 ----
480
-
481
- # 生效条件:调用即 os.walk(root) 统计每个文件名经 dispatch_of 得到的 kind(无匹配记 "unsupported")并返回 dry_run 预演计数结果。
482
- def _dry_dir(self, root):
483
- counts = {}
484
- for _dp, _dn, fns in os.walk(root):
485
- for name in fns:
486
- k = dispatch_of(name) or "unsupported"
487
- counts[k] = counts.get(k, 0) + 1
488
- return {"ok": True, "dry_run": True, "kind": "dir", "root": root,
489
- "counts": counts,
490
- "note": "预演:仅统计各链文件数,未做任何写入"}
491
-
492
- # 生效条件:root 非目录时返回 ok=False 的「目录不存在」;dry_run 为真时返回 _dry_dir(root);否则对 doc_ref/code_ref 两链各以 patterns/max_files/max_items/incremental/ledger 调 refindex.index_dir 与 add_items,并把 root 下 **/*.jsonl 前 max_files 个逐个 ingest_jsonl 后返回 out。
493
- def ingest_dir(self, root, layer=None, sensitivity=None, patterns=None,
494
- max_files=500, max_items=2000, incremental=False,
495
- dry_run=False):
496
- from . import refindex
497
- if not os.path.isdir(root):
498
- return {"ok": False, "error": f"目录不存在:{root}"}
499
- if dry_run:
500
- return self._dry_dir(root)
501
- ledger = refindex.Ledger(self.cg.root)
502
- out = {"ok": True, "kind": "dir", "root": root, "chains": {}}
503
- for ref_kind, key in (("doc_ref", "doc"), ("code_ref", "code")):
504
- items, errors, stats = refindex.index_dir(
505
- root, kind=ref_kind, patterns=patterns, max_files=max_files,
506
- max_items=max_items, incremental=incremental, ledger=ledger)
507
- ids, sens = refindex.add_items(self.cg, items, kind=ref_kind,
508
- root=root, layer=layer,
509
- sensitivity=sensitivity)
510
- out["chains"][key] = {
511
- "indexed": len(ids), "errors": len(errors),
512
- "files": stats.get("files"), "truncated": stats.get("truncated"),
513
- "skipped_unchanged": stats.get("skipped_unchanged", 0),
514
- "skipped_suffixes": stats.get("skipped_suffixes", []),
515
- "sensitivity": sens}
516
- # 会话流(.jsonl)逐个增量摄取
517
- jsons = sorted(glob.glob(os.path.join(root, "**", "*.jsonl"),
518
- recursive=True))[:max_files]
519
- ses = []
520
- for p in jsons:
521
- r = self.ingest_jsonl(p)
522
- ses.append({"path": p, "written": r.get("written", 0),
523
- "new_events": r.get("new_events", 0)})
524
- out["chains"]["session"] = {"files": len(jsons), "results": ses}
525
- return out
526
-
527
- # ---- 会话流 ----
528
-
529
- @staticmethod
530
- # 生效条件:required 形参 path 能被 open(...,encoding="utf-8",errors="replace") 打开时读前 50000 字符,head 含 '"user/message"'、'"assistant/message"'、'"tool/call"' 任一标记则返回 DSHSessionSource(path),否则返回 JsonlSource(path);打开抛 OSError 时直接返回 JsonlSource(path)。
531
- def _auto_source(path):
532
- """通用 JSONL vs DSH 会话:按内容探测,避免调用方选错源类型。"""
533
- try:
534
- with open(path, encoding="utf-8", errors="replace") as f:
535
- head = f.read(50000)
536
- except OSError:
537
- return JsonlSource(path)
538
- for marker in ('"user/message"', '"assistant/message"', '"tool/call"'):
539
- if marker in head:
540
- return DSHSessionSource(path)
541
- return JsonlSource(path)
542
-
543
- # 生效条件:path 不是文件时返回 ok=False 的「文件不存在」;否则经 _auto_source(path) 选源后调 self.ingestor.ingest(src, dry_run=dry_run, max_events=max_events),并补上 ok=True/kind/path/source_class 后返回。
544
- def ingest_jsonl(self, path, dry_run=False, max_events=None):
545
- if not os.path.isfile(path):
546
- return {"ok": False, "error": f"文件不存在:{path}"}
547
- src = self._auto_source(path)
548
- res = self.ingestor.ingest(src, dry_run=dry_run, max_events=max_events)
549
- res.update({"ok": True, "kind": "session", "path": path,
550
- "source_class": type(src).__name__})
551
- return res
552
-
553
-
554
- # 生效条件:cg 必填;action 为 None/空串时 (action or "stat") 归为 stat;file/jsonl 缺 path 返回 ok=False;dir 的 max_files/max_items 走 int(x or 500)/int(x or 2000),传 0 也变 500/2000;未知 action 抛 ValueError。
555
- def run(cg, action: str = "stat", **kw):
556
- """ingest op 唯一入口。"""
557
- act = (action or "stat").strip().lower()
558
- d = FileDispatcher(cg, sensitivity=kw.get("sensitivity"))
559
- if act == "stat":
560
- return d.stat()
561
- if act == "file":
562
- p = kw.get("path")
563
- if not p:
564
- return {"ok": False, "error": "file 动作需要 path"}
565
- return d.ingest_file(p, layer=kw.get("layer"),
566
- sensitivity=kw.get("sensitivity"),
567
- dry_run=bool(kw.get("dry_run")))
568
- if act == "dir":
569
- p = kw.get("path") or cg.root
570
- return d.ingest_dir(p, layer=kw.get("layer"),
571
- sensitivity=kw.get("sensitivity"),
572
- patterns=kw.get("patterns"),
573
- max_files=int(kw.get("max_files") or 500),
574
- max_items=int(kw.get("max_items") or 2000),
575
- incremental=bool(kw.get("incremental")),
576
- dry_run=bool(kw.get("dry_run")))
577
- if act == "jsonl":
578
- p = kw.get("path")
579
- if not p:
580
- return {"ok": False, "error": "jsonl 动作需要 path"}
581
- return d.ingest_jsonl(p, dry_run=bool(kw.get("dry_run")),
582
- max_events=kw.get("max_events"))
1
+ # -*- coding: utf-8 -*-
2
+ """md_cg · 设备驱动(记忆 OS #3):把外部会话流接进认知图
3
+
4
+ 设计:
5
+ Source(事件源)—— 把某种外部存储解析成统一事件流:
6
+ {"t": epoch 秒, "seq": 序号, "role": ..., "text": ..., "session": ..., "cwd": ...}
7
+
8
+ ⚠ **单位纪律(issue #23)**:`t` 一律 **epoch 秒**,与图内 `created_at` /
9
+ `condition_space.time_window` / `trust.parse_time` 同口径。外部源里的 13 位毫秒
10
+ (DSH 的 `time` 字段、通用 JSONL 的毫秒戳)在**源适配层**经 `trust.epoch_seconds`
11
+ 归一后即不外溢——否则 `condition_space.time_window` 会落毫秒,把
12
+ `stg(op=timeline)` 的倒序头部整片占满并顶掉 auto-recall。
13
+
14
+ Ingestor(摄取器)—— 增量 watermark + 去重 + 写节点 + 自动 fix-pair 挖掘。
15
+
16
+ 内置源:
17
+ · JsonlSource —— 通用 JSONL(每行一个事件对象)
18
+ · DSHSessionSource —— DeepSeek Harness 会话(~/.dsh/sessions/**/session.jsonl[.zstd])
19
+ zstd 为**可选**依赖:缺失时优雅降级(跳过 .zstd 文件并告警)
20
+
21
+ 默认敏感度:会话内容是私有记忆 → sensitivity="private"(避免落入公开根)。
22
+
23
+ 零第三方依赖(zstd 为可选增强)。
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import glob
28
+ import json
29
+ import os
30
+ import time
31
+
32
+ from . import trust
33
+ from .fsutil import publish
34
+ from .security import DEFAULT_SENSITIVITY
35
+
36
+ # 会话事件的默认落层与敏感度
37
+ SESSION_LAYER = "contextual"
38
+ SESSION_SENSITIVITY = "private"
39
+
40
+
41
+ # --------------------------------------------------------------------------
42
+ # 事件源
43
+ # --------------------------------------------------------------------------
44
+
45
+ # 生效条件:无必填构造形参,类常量 name="source" 即实例默认;仅当子类覆写 events() 时才产出事件,基类 events() 恒抛 NotImplementedError。
46
+ class Source:
47
+ """事件源基类。"""
48
+
49
+ name = "source"
50
+
51
+ # 生效条件:任何调用都直接 raise NotImplementedError(基类占位,无其它分支)。
52
+ def events(self):
53
+ raise NotImplementedError
54
+
55
+ # 生效条件:无前置;返回类常量 self.name(基类为 "source"),作为 watermark 的稳定标识,不含路径与运行期状态;
56
+ def key(self):
57
+ """源的稳定标识(用于 watermark)。"""
58
+ return self.name
59
+
60
+
61
+ # 生效条件:required 形参 path 总被存入 self.path;可选形参 name 为假值(None/空串)时 self.name 回落到 "jsonl:"+os.path.basename(path),为真值时用 name 本身,t_key/role_key/text_key/default_role 原样存入属性。
62
+ class JsonlSource(Source):
63
+ """通用 JSONL 会话源。
64
+
65
+ 每行是事件对象;字段映射可配置:
66
+ t_key —— 时间戳字段(默认 "time",也接受 ISO 字符串)
67
+ role_key —— 角色字段(默认 "role")
68
+ text_key —— 文本字段(默认 "text")
69
+ """
70
+
71
+ # 生效条件:传入 path;name 为假值(None/空串)时回落为 "jsonl:"+os.path.basename(path),t_key/role_key/text_key/default_role 原样存为属性(默认值 "time"/"role"/"text"/"user")。
72
+ def __init__(self, path: str, name: str = None, t_key="time", role_key="role",
73
+ text_key="text", default_role="user"):
74
+ self.path = path
75
+ self.name = name or ("jsonl:" + os.path.basename(path))
76
+ self.t_key, self.role_key, self.text_key = t_key, role_key, text_key
77
+ self.default_role = default_role
78
+
79
+ # 生效条件:无前置;返回 self.name(构造时已回落为 "jsonl:"+basename(path)),只随构造参数变化、不随文件内容变化;
80
+ def key(self):
81
+ return self.name
82
+
83
+ # 生效条件:o.get(self.t_key) 经 float 可转数值时返回 trust.epoch_seconds(该值)(秒值原样、13 位毫秒 /1000 归一到**秒**);float 抛 TypeError/ValueError 且 v 为字符串时按 v[:19] 与 "%Y-%m-%dT%H:%M:%S" 解析,成功返回 mktime(**秒**)、抛 ValueError 返回 0.0;键缺失/None/其它不可转类型返回 0.0。
84
+ def _ts(self, o):
85
+ v = o.get(self.t_key)
86
+ try:
87
+ return trust.epoch_seconds(float(v)) or 0.0
88
+ except (TypeError, ValueError):
89
+ pass
90
+ if isinstance(v, str):
91
+ try:
92
+ return time.mktime(time.strptime(v[:19], "%Y-%m-%dT%H:%M:%S"))
93
+ except ValueError:
94
+ return 0.0
95
+ return 0.0
96
+
97
+ # 生效条件:os.path.exists(self.path) 为真时逐行产出,空行、json.loads 抛 ValueError、o.get(self.text_key) 为假的行被跳过,产出项为 t=self._ts(o)、seq=o.get("seq", i)、role=o.get(self.role_key) or self.default_role、text=str(text)、session/cwd 取 o 同名键;path 不存在时直接 return 不产出。
98
+ def events(self):
99
+ if not os.path.exists(self.path):
100
+ return
101
+ with open(self.path, encoding="utf-8", errors="replace") as f:
102
+ for i, line in enumerate(f):
103
+ line = line.strip()
104
+ if not line:
105
+ continue
106
+ try:
107
+ o = json.loads(line)
108
+ except ValueError:
109
+ continue
110
+ text = o.get(self.text_key)
111
+ if not text:
112
+ continue
113
+ yield {"t": self._ts(o), "seq": o.get("seq", i),
114
+ "role": o.get(self.role_key) or self.default_role,
115
+ "text": str(text), "session": o.get("session"),
116
+ "cwd": o.get("cwd")}
117
+
118
+
119
+ # 生效条件:path 指向的内容可读且 zstandard 可导入时返回 StringIO(raw.decode("utf-8", errors="replace"));ImportError 时返回 None。
120
+ def _zstd_reader(path):
121
+ """返回可读的文本迭代器;zstd 不可用返回 None。"""
122
+ import io
123
+ try:
124
+ import zstandard as zstd
125
+ except ImportError:
126
+ return None
127
+ with open(path, "rb") as f:
128
+ raw = zstd.ZstdDecompressor().stream_reader(f).read()
129
+ return io.StringIO(raw.decode("utf-8", errors="replace"))
130
+
131
+
132
+ # 生效条件:required 形参 path 总被存入 self.path,可选形参 include_reasoning 原样存入,self.name 恒为 "dsh:"+os.path.basename(os.path.dirname(path))(与 include_reasoning 取值无关)。
133
+ class DSHSessionSource(Source):
134
+ """DeepSeek Harness 会话源。
135
+
136
+ 事件映射:
137
+ user/message → role=user
138
+ assistant/message → role=assistant(只取 content[].text,reasoning 默认丢弃)
139
+ tool/call → role=command("name(args)" 形式)
140
+ tool/result → role=tool-output
141
+ """
142
+
143
+ # 生效条件:传入 path 即成立,include_reasoning 原样存为属性(默认 False),name 固定为 "dsh:"+os.path.basename(os.path.dirname(path))。
144
+ def __init__(self, path: str, include_reasoning: bool = False):
145
+ self.path = path
146
+ self.include_reasoning = include_reasoning
147
+ self.name = "dsh:" + os.path.basename(os.path.dirname(path))
148
+
149
+ # 生效条件:无前置;返回 self.name(构造时固定为 "dsh:"+basename(dirname(path))),与 include_reasoning 取值无关;
150
+ def key(self):
151
+ return self.name
152
+
153
+ @staticmethod
154
+ # 生效条件:可选形参 root 为假值(None/空串)时改用默认目录 os.path.join(expanduser("~"),".dsh","sessions"),否则用传入 root;对 root 下递归 glob 到的 session.jsonl 与 session.jsonl.zstd 逐条 os.stat(抛 OSError 的条目跳过),按 (-st_size, path) 升序键排序(两处 glob 均无命中时为空列表),可选形参 limit 为假值(None/0)时返回全部 rows,否则返回 rows[:limit];每行为 (path, size, mtime) 三元组。
155
+ def discover_detailed(root: str = None, limit: int = None):
156
+ """候选会话 → `[(path, size, mtime), ...]`(**排序唯一真源**,`discover` 复用)。
157
+
158
+ 排序:会话**文件体积**降序 → `path` 升序(确定性终键)。
159
+ **刻意不按最近活动排序**——这正是 `source='auto'` 可能选中很久以前会话的
160
+ 原因;把 size/mtime 一并透出,让「为什么选它」在返回体里可辨
161
+ (issue #23 附带建议:先让依据可见,是否换策略另议)。
162
+ """
163
+ root = root or os.path.join(os.path.expanduser("~"), ".dsh", "sessions")
164
+ files = glob.glob(os.path.join(root, "**", "session.jsonl"), recursive=True)
165
+ files += glob.glob(os.path.join(root, "**", "session.jsonl.zstd"), recursive=True)
166
+ rows = []
167
+ for p in files:
168
+ try:
169
+ st = os.stat(p)
170
+ except OSError:
171
+ continue # 竞态删除:跳过而非整体失败
172
+ rows.append((p, st.st_size, st.st_mtime))
173
+ rows.sort(key=lambda r: (-r[1], r[0]))
174
+ return rows[:limit] if limit else rows
175
+
176
+ @staticmethod
177
+ # 生效条件:可选形参 root/limit 原样转交 discover_detailed,返回其结果的 path 列(同为体积降序、同确定性终键),limit 为假值时返回全部。
178
+ def discover(root: str = None, limit: int = None):
179
+ """候选会话路径(体积降序)——`discover_detailed` 的路径投影。"""
180
+ return [p for p, _s, _m in DSHSessionSource.discover_detailed(root, limit)]
181
+
182
+ # 生效条件:self.path 以 ".zstd" 结尾时经 _zstd_reader 逐行产出(其返回 None 时 raise RuntimeError),否则以 utf-8/errors=replace 打开 self.path 逐行产出。
183
+ def _lines(self):
184
+ if self.path.endswith(".zstd"):
185
+ fh = _zstd_reader(self.path)
186
+ if fh is None:
187
+ raise RuntimeError("zstd 不可用(pip install zstandard),跳过该会话")
188
+ try:
189
+ yield from fh
190
+ finally:
191
+ fh.close()
192
+ else:
193
+ with open(self.path, encoding="utf-8", errors="replace") as f:
194
+ yield from f
195
+
196
+ @staticmethod
197
+ # 生效条件:required 形参 content 为 str 时原样返回 content;为 list 时收集其中 str 元素及 type 属于 ("text","input-text") 且 text 为真值的 dict 元素(取 str(c["text"])),以 "\n" 连接返回(无可收集元素时为空串 "");既非 str 也非 list 时返回 ""。
198
+ def _text_of(content):
199
+ """content 可能是 [{type,text}] 或字符串。"""
200
+ if isinstance(content, str):
201
+ return content
202
+ if isinstance(content, list):
203
+ parts = []
204
+ for c in content:
205
+ if isinstance(c, dict):
206
+ if c.get("type") in ("text", "input-text") and c.get("text"):
207
+ parts.append(str(c["text"]))
208
+ elif isinstance(c, str):
209
+ parts.append(c)
210
+ return "\n".join(parts)
211
+ return ""
212
+
213
+ # 生效条件:逐行解析后按 o.get("type") 分派——"session" 只更新 sess/cwd 不产出;"user/message" 产出 role=user 与 _text_of(data.get("content"));"assistant/message" 产出 role=assistant 与 _text_of(msg.get("content")),include_reasoning 为真时再把 content 中 type=="reasoning" 且有 text 的项追加 "\n[reasoning] "+str(c["text"]);"tool/call" 产出 role=command 与 f"{name}({arguments or ''})";"tool/result" 产出 role=tool-output,仅当 inner[0] 为 dict 时 text=_text_of(inner[0].get("content"));其它 type 或 ev 中 text 为空的事件不产出。
214
+ def events(self):
215
+ sess = None
216
+ cwd = None
217
+ for line in self._lines():
218
+ line = line.strip()
219
+ if not line:
220
+ continue
221
+ try:
222
+ o = json.loads(line)
223
+ except ValueError:
224
+ continue
225
+ t = o.get("type")
226
+ data = o.get("data") or {}
227
+ if t == "session":
228
+ sess, cwd = o.get("id"), o.get("cwd")
229
+ continue
230
+ ev = {"t": trust.epoch_seconds(float(o.get("time") or 0)) or 0.0,
231
+ "seq": o.get("seq"),
232
+ "session": sess, "cwd": cwd}
233
+ if t == "user/message":
234
+ ev["role"], ev["text"] = "user", self._text_of(data.get("content"))
235
+ elif t == "assistant/message":
236
+ msg = data.get("message") or {}
237
+ ev["role"] = "assistant"
238
+ ev["text"] = self._text_of(msg.get("content"))
239
+ if self.include_reasoning:
240
+ for c in (msg.get("content") or []):
241
+ if isinstance(c, dict) and c.get("type") == "reasoning" \
242
+ and c.get("text"):
243
+ ev["text"] = (ev["text"] or "") + "\n[reasoning] " + str(c["text"])
244
+ elif t == "tool/call":
245
+ ev["role"] = "command"
246
+ ev["text"] = f"{data.get('name')}({data.get('arguments') or ''})"
247
+ elif t == "tool/result":
248
+ msg = data.get("message") or {}
249
+ inner = msg.get("content") or []
250
+ txt = ""
251
+ if inner and isinstance(inner[0], dict):
252
+ txt = self._text_of(inner[0].get("content"))
253
+ ev["role"], ev["text"] = "tool-output", txt
254
+ else:
255
+ continue
256
+ if ev.get("text"):
257
+ yield ev
258
+
259
+
260
+ # --------------------------------------------------------------------------
261
+ # 蜂巢任务事件源(M6:hive/jobs → contextual,D:\2_ai 蜂巢记忆架构设计.md §5)
262
+ # --------------------------------------------------------------------------
263
+
264
+ class HiveJobsSource(Source):
265
+ """蜂巢任务事件源。
266
+
267
+ 事件映射(设计稿 §5.3):
268
+ start → role=user,任务开始(task 摘要)
269
+ tool → **跳过**(防流水账爆炸;tool_trace 已在 result 有摘要)
270
+ handoff → role=assistant,续跑卡
271
+ error → role=assistant,「任务失败(job=…):…」(fix-pair 前件)
272
+ final → role=assistant,content 头
273
+ result.json 终态 → **权威确认事件**(progress 可能因强杀缺失 final):
274
+ done → 「任务完成(job=…)」;error/timeout/killed → 「任务失败(job=…)」
275
+
276
+ 排序:job_id 名升序 = 时间升序(job.rs 命名保证 h<unix_ms>_<pid>),
277
+ 跨 job 全序成立;seq 由本源按枚举顺序递增(ingest 去重键 = (session, seq))。
278
+ 解析失败的行/条目计入 self.skipped,不终杀批次。
279
+ """
280
+
281
+ name = "hive_jobs"
282
+
283
+ def __init__(self, root: str, error_sensitivity: str = "private"):
284
+ self.root = os.path.abspath(root)
285
+ self.skipped = 0
286
+ # §5.5「error:true 建议 private」——默认 private;调用方 clearance 不足时
287
+ # 可显式降级 error_sensitivity="internal"(显式权衡:接受失败细节入 internal,
288
+ # 换取误差归因原料不丢)。能否落盘由写入者 clearance 裁决(写隔离)。
289
+ self.error_sensitivity = error_sensitivity
290
+
291
+ def key(self):
292
+ """多仓/多池隔离:root 参与键(watermark 按源路径各自推进)。"""
293
+ return f"hive_jobs:{self.root}"
294
+
295
+ def _jobs(self):
296
+ try:
297
+ return sorted(d for d in os.listdir(self.root)
298
+ if d.startswith("h")
299
+ and os.path.isdir(os.path.join(self.root, d)))
300
+ except OSError:
301
+ return []
302
+
303
+ def _read_json(self, path):
304
+ try:
305
+ with open(path, encoding="utf-8") as f:
306
+ return json.load(f)
307
+ except (OSError, ValueError):
308
+ self.skipped += 1
309
+ return None
310
+
311
+ def events(self):
312
+ seq = 0
313
+ for job_id in self._jobs():
314
+ jdir = os.path.join(self.root, job_id)
315
+ sess = f"hive:{job_id}"
316
+ spec = self._read_json(os.path.join(jdir, "spec.json")) or {}
317
+ task = str(spec.get("user_prompt") or "")[:200]
318
+
319
+ has_final = False
320
+ prog = os.path.join(jdir, "progress.jsonl")
321
+ if os.path.isfile(prog):
322
+ try:
323
+ fh = open(prog, encoding="utf-8", errors="replace")
324
+ except OSError:
325
+ fh = None
326
+ if fh is not None:
327
+ with fh:
328
+ for line in fh:
329
+ line = line.strip()
330
+ if not line:
331
+ continue
332
+ try:
333
+ o = json.loads(line)
334
+ except ValueError:
335
+ self.skipped += 1
336
+ continue
337
+ kind = o.get("kind")
338
+ t = trust.epoch_seconds(float(o.get("ts") or 0)) or 0.0
339
+ if kind == "start":
340
+ seq += 1
341
+ yield {"t": t, "seq": seq, "session": sess,
342
+ "role": "user",
343
+ "text": f"任务开始(job={job_id}):{task}"}
344
+ elif kind == "handoff":
345
+ seq += 1
346
+ has_final = True
347
+ yield {"t": t, "seq": seq, "session": sess,
348
+ "role": "assistant",
349
+ "text": f"续跑卡(job={job_id}):"
350
+ f"{str(o.get('summary') or '')[:400]}"}
351
+ elif kind == "error":
352
+ seq += 1
353
+ ev = {"t": t, "seq": seq, "session": sess,
354
+ "role": "assistant",
355
+ "text": f"任务失败(job={job_id}):"
356
+ f"{str(o.get('error') or '')[:300]}"}
357
+ if self.error_sensitivity:
358
+ ev["sensitivity"] = self.error_sensitivity
359
+ yield ev
360
+ elif kind == "final":
361
+ seq += 1
362
+ has_final = True
363
+ yield {"t": t, "seq": seq, "session": sess,
364
+ "role": "assistant",
365
+ "text": str(o.get("content_head") or "")[:400]}
366
+ # tool / budget_stop / force_final / 其它 → 纯跳过
367
+ # (§5.3 原设计为聚合计数,实现从简——终态由 result.json
368
+ # 权威确认承载,无需逐条累计;防流水账爆炸目标不变。
369
+ # 注释曾照抄设计稿口径称「仅聚合计数」,与实现漂移,
370
+ # zcode 外评抓出,2026-09-23 修正)
371
+
372
+ # result.json 终态权威确认(progress 可能因强杀缺失 final)
373
+ res = self._read_json(os.path.join(jdir, "result.json")) or {}
374
+ state = "done" if res.get("ok") is True else \
375
+ ("error" if res.get("ok") is False else None)
376
+ if state is None:
377
+ continue
378
+ t = trust.epoch_seconds(float(res.get("finished_ts") or 0))
379
+ if not t:
380
+ # 兜底(批次8 实测缺陷):exec_cmd 旧版 result 无 finished_ts,
381
+ # t=0 会被水位整片过滤(确定性任务事件永远摄不进来)——
382
+ # 回落 result.json 的 mtime(产物诞生时间,同「产物说了算」族)
383
+ rpath = os.path.join(jdir, "result.json")
384
+ t = trust.epoch_seconds(os.path.getmtime(rpath)) if \
385
+ os.path.isfile(rpath) else 0.0
386
+ t = t or 0.0
387
+ head = str(res.get("content") or "")[:300]
388
+ err = str(res.get("error") or "")[:300]
389
+ seq += 1
390
+ if state == "done":
391
+ # 前缀独立成行:content 的行首结构(命令行等)不被破坏,
392
+ # 保证 mine_fix_pairs 的 _FIX_RE 行首启发式仍能命中修复证据
393
+ text = f"任务完成(job={job_id}):\n{head}"
394
+ # 重放命令行(批次8):cmd 任务(exec_cmd)的 result.steps 携带
395
+ # 原始 argv——附到事件正文,事件自身携带「做了什么」的可复放
396
+ # 信息,同时命令形态可被 _FIX_RE 行首启发式命中(fix-pair 原料)
397
+ steps = res.get("steps")
398
+ if isinstance(steps, list) and steps:
399
+ argv = (steps[-1] or {}).get("command") or []
400
+ if argv:
401
+ text += "\n" + " ".join(str(a) for a in argv)
402
+ yield {"t": t, "seq": seq, "session": sess, "role": "assistant",
403
+ "text": text}
404
+ else:
405
+ tag = "超时强杀" if st_err_is_timeout(err) else "失败"
406
+ ev = {"t": t, "seq": seq, "session": sess, "role": "assistant",
407
+ "text": f"任务{tag}(job={job_id}):{err or head}"
408
+ + ("" if has_final else "(progress 缺 final,"
409
+ "本条为 result 终态补位)")}
410
+ if self.error_sensitivity:
411
+ ev["sensitivity"] = self.error_sensitivity
412
+ yield ev
413
+
414
+
415
+ def st_err_is_timeout(err: str) -> bool:
416
+ return "超时" in (err or "")
417
+
418
+
419
+ # --------------------------------------------------------------------------
420
+ # 摄取器
421
+ # --------------------------------------------------------------------------
422
+
423
+ # 生效条件:required 形参 cg 总被存入并以其 cg.root 拼出 self.path=os.path.join(cg.root,"_sources.json");layer/sensitivity 原样存入,未传时取模块级常量 SESSION_LAYER、SESSION_SENSITIVITY 作为默认值。
424
+ class Ingestor:
425
+ """增量摄取:watermark + 去重 + 写节点 + 自动 fix-pair 挖掘。
426
+
427
+ watermark 文件:<root>/_sources.json
428
+ {source_key: {"t": 最后时间戳(epoch 秒), "seq": 最后序号, "count": 已摄取条数}}
429
+
430
+ 存量水位兼容:**旧版写入的是毫秒**,读回时经 `trust.epoch_seconds` 归一到秒,
431
+ 故升级后无需清 `_sources.json` 重建(否则 `ev["t"] < last_t` 恒真 → 新事件全被跳过)。
432
+ """
433
+
434
+ # 生效条件:传入带 root 的 cg 即成立,layer/sensitivity 默认 SESSION_LAYER/SESSION_SENSITIVITY 并原样存为属性,路径为 os.path.join(cg.root, "_sources.json")。
435
+ def __init__(self, cg, layer: str = SESSION_LAYER,
436
+ sensitivity: str = SESSION_SENSITIVITY):
437
+ self.cg = cg
438
+ self.layer = layer
439
+ self.sensitivity = sensitivity
440
+ self.path = os.path.join(cg.root, "_sources.json")
441
+
442
+ # ---- watermark ----
443
+
444
+ # 生效条件:self.path 存在、json.load 成功且结果为 dict 时返回该 dict;path 不存在、抛 ValueError/OSError 或结果非 dict 时返回 {"schema": 1, "sources": {}}。
445
+ def _load(self):
446
+ if os.path.exists(self.path):
447
+ try:
448
+ with open(self.path, encoding="utf-8") as f:
449
+ d = json.load(f)
450
+ if isinstance(d, dict):
451
+ return d
452
+ except (ValueError, OSError):
453
+ pass
454
+ return {"schema": 1, "sources": {}}
455
+
456
+ # 生效条件:传入 d 时以 ensure_ascii=False/indent=1 写入 self.path+".tmp",再 publish(带 Windows 短重试的 os.replace)覆盖 self.path。
457
+ def _save(self, d):
458
+ tmp = self.path + ".tmp"
459
+ with open(tmp, "w", encoding="utf-8") as f:
460
+ json.dump(d, f, ensure_ascii=False, indent=1)
461
+ publish(tmp, self.path)
462
+
463
+ # 生效条件:key 命中 self._load()["sources"] 时返回其值,缺 key 时返回 {}(_load 结果缺 "sources" 键则抛 KeyError)。
464
+ def watermark(self, key: str):
465
+ return self._load()["sources"].get(key, {})
466
+
467
+ # 生效条件:调用即返回 dict(self._load()["sources"]) 的浅拷贝(_load 结果缺 "sources" 键则抛 KeyError)。
468
+ def watermarks(self):
469
+ return dict(self._load()["sources"])
470
+
471
+ # ---- 摄取 ----
472
+
473
+ # 生效条件:对必需形参 source,先按 watermark(source.key()) 跳过 t<last_t(wm 的 "t" 为假值时视作 0)及 t==last_t 且 last_seq 非 None 且 ev["seq"] 为 int 且 <= last_seq 的事件、并按 (session, seq) 去重,max_events 为真值(非 0/None)时取满即停;dry_run 为假时逐条 cg.add(nid 已在 cg.index["nodes"] 中则跳过,cg.add 抛异常则 denied+=1、记 last_error 并继续),denied 与 last_error 同时成立时补 last_error/hint 与 denied_events 明细,mine_fix_pairs 为真且 new_events 非空且非 dry_run 时以 as_proposals=True 调 mine_fix_pairs(自动产物走审核队列)并结果附 fix_pairs,new_events 非空且非 dry_run 时以末事件写回水位(count 累加 written),最后返回 result。
474
+ def ingest(self, source, mine_fix_pairs: bool = False, max_events: int = None,
475
+ dry_run: bool = False):
476
+ """摄取一个源的新事件。返回统计。
477
+
478
+ 去重键:(session, seq) —— 同一事件不重复入库。
479
+ 增量:只处理 (t, seq) 大于 watermark 的事件。
480
+ mine_fix_pairs 默认 False(先落账后挖矿——§5.5 系统纪律化,
481
+ zcode 外评 break#2:默认 True 曾使 file/jsonl/mdcg_ingest 三入口
482
+ 绕过审核队列直写 knowledge 层);显式开启时产物走 propose 队列。
483
+ """
484
+ key = source.key()
485
+ wm = self.watermark(key)
486
+ # 水位单位归一:**存量水位是毫秒**(旧版 `_ts` 产出),不归一会让
487
+ # `ev["t"] < last_t` 恒真 → 升级后新事件被整片误判为「已处理」而跳过。
488
+ last_t, last_seq = trust.epoch_seconds(float(wm.get("t") or 0)) or 0.0, \
489
+ wm.get("seq")
490
+ new_events, seen = [], set()
491
+ denied_events = [] # 被拒事件明细(session/seq/原因)——可观测可重放
492
+ for ev in source.events():
493
+ if ev.get("t", 0) < last_t:
494
+ continue
495
+ if ev.get("t", 0) == last_t and last_seq is not None \
496
+ and isinstance(ev.get("seq"), int) and ev["seq"] <= last_seq:
497
+ continue
498
+ dedup = (ev.get("session"), ev.get("seq"))
499
+ if dedup in seen:
500
+ continue
501
+ seen.add(dedup)
502
+ new_events.append(ev)
503
+ if max_events and len(new_events) >= max_events:
504
+ break
505
+
506
+ written, ids, denied = 0, [], 0
507
+ if not dry_run:
508
+ for ev in new_events:
509
+ nid = "src_%s_%s" % (_sig(key)[:6], _sig(
510
+ f"{ev.get('session')}:{ev.get('seq')}")[:10])
511
+ if nid in self.cg.index["nodes"]:
512
+ continue # 幂等
513
+ body = ("# 功能名:会话事件\n"
514
+ f"# 生效条件:检索「{str(ev.get('text'))[:20]}」\n"
515
+ "# 子功能:记录会话事件\n"
516
+ f"# 执行:{str(ev.get('text'))[:80]}\n"
517
+ "# 验证方式:data(会话原始记录)\n"
518
+ "# 不适用条件:其它会话\n\n"
519
+ f"{ev.get('text')}\n")
520
+ try:
521
+ self.cg.add(nid, body, layer=self.layer, role=ev.get("role"),
522
+ tags=["session", key],
523
+ # 事件级密级覆盖(M6 §5.5):error 事件建议 private
524
+ # (失败细节可能含路径/配置),缺省回落源级默认
525
+ sensitivity=ev.get("sensitivity") or self.sensitivity,
526
+ verification_basis="data",
527
+ condition_space={"observation_position": key,
528
+ "observation_tool": "会话流",
529
+ "time_window": [ev.get("t") or 0,
530
+ ev.get("t") or 0]})
531
+ except Exception as exc: # noqa: BLE001 —— 权限/层错误不中断整批
532
+ denied += 1
533
+ # 诚实化(M6):denied 事件虽被水位跳过,但明细必须可见——
534
+ # 静默丢失会吞掉误差归因闭环的原料(error 事件恰是原料)
535
+ denied_events.append({
536
+ "session": ev.get("session"), "seq": ev.get("seq"),
537
+ "error": str(exc)[:150]})
538
+ self.last_error = f"{type(exc).__name__}: {exc}"
539
+ continue
540
+ ids.append(nid)
541
+ written += 1
542
+
543
+ result = {"source": key, "new_events": len(new_events), "written": written,
544
+ "denied": denied, "ids": ids, "dry_run": dry_run,
545
+ "sensitivity": self.sensitivity}
546
+ if denied_events:
547
+ result["denied_events"] = denied_events
548
+ if denied and getattr(self, "last_error", None):
549
+ result["last_error"] = self.last_error
550
+ result["hint"] = ("会话内容默认 sensitivity=private;"
551
+ "调用方需 MDCG_CLEARANCE=private 才能写入")
552
+ # 自动 fix-pair 挖掘(对标 deja-vu:错误→修复)——as_proposals=True:
553
+ # 自动管线产物走审核队列,绝不直写 knowledge 层(§5.5 系统纪律)
554
+ if mine_fix_pairs and new_events and not dry_run:
555
+ result["fix_pairs"] = self.cg.mine_fix_pairs(
556
+ [{"role": e.get("role"), "text": e.get("text")}
557
+ for e in new_events], as_proposals=True)
558
+
559
+ if new_events and not dry_run:
560
+ d = self._load()
561
+ last = new_events[-1]
562
+ d["sources"][key] = {
563
+ "t": last.get("t") or last_t,
564
+ "seq": last.get("seq"),
565
+ "count": (wm.get("count") or 0) + written,
566
+ "updated_at": time.time(),
567
+ "path": getattr(source, "path", None),
568
+ }
569
+ self._save(d)
570
+ return result
571
+
572
+
573
+ # 生效条件:text 为 None 或假值时按 "" 参与 sha1;n 默认 12,返回 hexdigest 前 n 位(n=0 得空串)。
574
+ def _sig(text: str, n: int = 12) -> str:
575
+ import hashlib
576
+ return hashlib.sha1((text or "").encode("utf-8")).hexdigest()[:n]
577
+
578
+
579
+ # --------------------------------------------------------------------------
580
+ # 摄取分派(P0 · ingest op):按扩展名选摄取方式,单一入口吃多种文件
581
+ # --------------------------------------------------------------------------
582
+ #
583
+ # 三条摄取链:
584
+ # session —— 会话流(.jsonl):走 Ingestor(watermark + 去重 + fix-pair)
585
+ # doc —— 文档(.md/.txt/...):走 docindex.extract + refindex.add_items
586
+ # code —— 代码(.py/.ts/...):走 codeindex.extract + refindex.add_items
587
+ #
588
+ # 设计要点:
589
+ # - 注册表 INGEST_REGISTRY 是唯一真源:新增后缀只改这里。
590
+ # - dir 动作分链处理:目录里 doc / code / jsonl 混放时各链互不干扰。
591
+ # - 幂等:沿用 refindex.Ledger(size+mtime 水位)与 Ingestor watermark。
592
+ # - 预演:dry_run=True 只统计、不写入(对应计划「可预演」要求)。
593
+
594
+ INGEST_ACTIONS = ("file", "dir", "jsonl", "stat", "hive")
595
+
596
+ INGEST_REGISTRY = {
597
+ # 会话流
598
+ ".jsonl": "session", ".ndjson": "session",
599
+ # 文档
600
+ ".md": "doc", ".markdown": "doc", ".txt": "doc", ".rst": "doc",
601
+ ".html": "doc", ".htm": "doc",
602
+ # 代码
603
+ ".py": "code", ".js": "code", ".mjs": "code", ".ts": "code",
604
+ ".tsx": "code", ".jsx": "code", ".go": "code", ".rs": "code",
605
+ ".java": "code", ".kt": "code", ".swift": "code", ".rb": "code",
606
+ ".php": "code", ".cs": "code", ".cpp": "code", ".cc": "code",
607
+ ".c": "code", ".h": "code", ".hpp": "code",
608
+ }
609
+
610
+
611
+ # 生效条件:path 为 None 或空串时 splitext 得 "" 且未登记 → 返回 None;扩展名(小写)存在于 INGEST_REGISTRY 时返回其 kind。
612
+ def dispatch_of(path: str):
613
+ """按扩展名返回摄取方式(session / doc / code);未登记返回 None。"""
614
+ return INGEST_REGISTRY.get(os.path.splitext(path or "")[1].lower())
615
+
616
+
617
+ # 生效条件:required 形参 cg 总被存入;可选形参 sensitivity 为假值(None/空串)时内部 Ingestor 的 sensitivity 回落模块级常量 SESSION_SENSITIVITY,为真值时用传入的 sensitivity。
618
+ class FileDispatcher:
619
+ """单一入口吃多种文件:按扩展名分派到会话流 / 文档 / 代码三条摄取链。"""
620
+
621
+ # 生效条件:传入 cg 即成立,sensitivity 为假值(None/空串)时所用 Ingestor 回落 SESSION_SENSITIVITY,否则用传入值。
622
+ def __init__(self, cg, sensitivity=None):
623
+ self.cg = cg
624
+ # 会话链默认 sensitivity=private;调用方可显式覆盖(测试/受限环境)
625
+ self.ingestor = Ingestor(cg, sensitivity=sensitivity or SESSION_SENSITIVITY)
626
+
627
+ # ---- stat:看水位与支持面 ----
628
+
629
+ # 生效条件:from . import refindex 与 refindex.Ledger(self.cg.root).stat() 均不抛异常时返回该 stat 结果,抛任何异常时返回 {}。
630
+ def _ledger_stat(self):
631
+ try:
632
+ from . import refindex
633
+ return refindex.Ledger(self.cg.root).stat()
634
+ except Exception: # noqa: BLE001
635
+ return {}
636
+
637
+ # 生效条件:调用即返回由 INGEST_REGISTRY 按 kind 分组并排序后的 extensions、list(INGEST_ACTIONS)、self.ingestor.watermarks()、self._ledger_stat() 与固定 note。
638
+ def stat(self):
639
+ kinds = {}
640
+ for ext, kind in INGEST_REGISTRY.items():
641
+ kinds.setdefault(kind, []).append(ext)
642
+ return {"ok": True, "kind": "stat", "actions": list(INGEST_ACTIONS),
643
+ "extensions": {k: sorted(v) for k, v in sorted(kinds.items())},
644
+ "watermarks": self.ingestor.watermarks(),
645
+ "ledger": self._ledger_stat(),
646
+ "note": ("注册表是唯一真源:新增后缀只改 INGEST_REGISTRY。"
647
+ "watermarks=会话流水位;ledger=文档/代码文件水位。")}
648
+
649
+ # ---- 单文件 ----
650
+
651
+ # 生效条件:dispatch_of(path) 为 None 时返回 ok=False 的「不支持的后缀」结果;path 不是文件时返回 ok=False 的「文件不存在」结果;kind=="session" 时转 ingest_jsonl(path, dry_run=dry_run)(layer/sensitivity 不参与);其它 kind 转 _ingest_doc_or_code(path, kind, layer=layer, sensitivity=sensitivity, dry_run=dry_run)。
652
+ def ingest_file(self, path, layer=None, sensitivity=None, dry_run=False):
653
+ kind = dispatch_of(path)
654
+ if kind is None:
655
+ ext = os.path.splitext(path or "")[1] or "(无后缀)"
656
+ return {"ok": False, "path": path, "error": f"不支持的后缀:{ext}",
657
+ "supported": sorted(INGEST_REGISTRY)}
658
+ if not os.path.isfile(path):
659
+ return {"ok": False, "path": path, "error": "文件不存在"}
660
+ if kind == "session":
661
+ return self.ingest_jsonl(path, dry_run=dry_run)
662
+ return self._ingest_doc_or_code(path, kind, layer=layer,
663
+ sensitivity=sensitivity, dry_run=dry_run)
664
+
665
+ # 生效条件:kind=="code" 用 codeindex 否则用 docindex;mod.extract 抛 ValueError 时返回 ok=False 的「抽取失败」;dry_run 为真时只返回 items 计数与前 20 个 node_id 不写盘;否则经 refindex.add_items(kind 为 code_ref/doc_ref)写入并返回 indexed、ids[:20] 与 sensitivity。
666
+ def _ingest_doc_or_code(self, path, kind, layer=None, sensitivity=None,
667
+ dry_run=False):
668
+ from . import codeindex, docindex, refindex
669
+ mod = codeindex if kind == "code" else docindex
670
+ rel = os.path.basename(path)
671
+ with open(path, encoding="utf-8", errors="replace") as f:
672
+ src = f.read()
673
+ try:
674
+ items = mod.extract(src, path=rel,
675
+ suffix=os.path.splitext(path)[1].lower())
676
+ except ValueError as exc:
677
+ return {"ok": False, "path": path, "error": f"抽取失败:{exc}"}
678
+ items = items or []
679
+ if dry_run:
680
+ return {"ok": True, "dry_run": True, "kind": kind, "path": path,
681
+ "items": len(items),
682
+ "ids": [mod.node_id(i) for i in items[:20]],
683
+ "note": "预演:只抽取计数,未写盘"}
684
+ ref_kind = "code_ref" if kind == "code" else "doc_ref"
685
+ ids, sens = refindex.add_items(
686
+ self.cg, items, kind=ref_kind,
687
+ root=os.path.dirname(path) or ".", layer=layer,
688
+ sensitivity=sensitivity)
689
+ return {"ok": True, "kind": kind, "path": path, "items": len(items),
690
+ "indexed": len(ids), "ids": ids[:20], "sensitivity": sens}
691
+
692
+ # ---- 目录 ----
693
+
694
+ # 生效条件:调用即 os.walk(root) 统计每个文件名经 dispatch_of 得到的 kind(无匹配记 "unsupported")并返回 dry_run 预演计数结果。
695
+ def _dry_dir(self, root):
696
+ counts = {}
697
+ for _dp, _dn, fns in os.walk(root):
698
+ for name in fns:
699
+ k = dispatch_of(name) or "unsupported"
700
+ counts[k] = counts.get(k, 0) + 1
701
+ return {"ok": True, "dry_run": True, "kind": "dir", "root": root,
702
+ "counts": counts,
703
+ "note": "预演:仅统计各链文件数,未做任何写入"}
704
+
705
+ # 生效条件:root 非目录时返回 ok=False 的「目录不存在」;dry_run 为真时返回 _dry_dir(root);否则对 doc_ref/code_ref 两链各以 patterns/max_files/max_items/incremental/ledger 调 refindex.index_dir 与 add_items,并把 root 下 **/*.jsonl 前 max_files 个逐个 ingest_jsonl 后返回 out。
706
+ def ingest_dir(self, root, layer=None, sensitivity=None, patterns=None,
707
+ max_files=500, max_items=2000, incremental=False,
708
+ dry_run=False):
709
+ from . import refindex
710
+ if not os.path.isdir(root):
711
+ return {"ok": False, "error": f"目录不存在:{root}"}
712
+ if dry_run:
713
+ return self._dry_dir(root)
714
+ ledger = refindex.Ledger(self.cg.root)
715
+ out = {"ok": True, "kind": "dir", "root": root, "chains": {}}
716
+ for ref_kind, key in (("doc_ref", "doc"), ("code_ref", "code")):
717
+ items, errors, stats = refindex.index_dir(
718
+ root, kind=ref_kind, patterns=patterns, max_files=max_files,
719
+ max_items=max_items, incremental=incremental, ledger=ledger)
720
+ ids, sens = refindex.add_items(self.cg, items, kind=ref_kind,
721
+ root=root, layer=layer,
722
+ sensitivity=sensitivity)
723
+ out["chains"][key] = {
724
+ "indexed": len(ids), "errors": len(errors),
725
+ "files": stats.get("files"), "truncated": stats.get("truncated"),
726
+ "skipped_unchanged": stats.get("skipped_unchanged", 0),
727
+ "skipped_suffixes": stats.get("skipped_suffixes", []),
728
+ "sensitivity": sens}
729
+ # 会话流(.jsonl)逐个增量摄取
730
+ jsons = sorted(glob.glob(os.path.join(root, "**", "*.jsonl"),
731
+ recursive=True))[:max_files]
732
+ ses = []
733
+ for p in jsons:
734
+ r = self.ingest_jsonl(p)
735
+ ses.append({"path": p, "written": r.get("written", 0),
736
+ "new_events": r.get("new_events", 0)})
737
+ out["chains"]["session"] = {"files": len(jsons), "results": ses}
738
+ return out
739
+
740
+ # ---- 会话流 ----
741
+
742
+ @staticmethod
743
+ # 生效条件:required 形参 path 能被 open(...,encoding="utf-8",errors="replace") 打开时读前 50000 字符,head 含 '"user/message"'、'"assistant/message"'、'"tool/call"' 任一标记则返回 DSHSessionSource(path),否则返回 JsonlSource(path);打开抛 OSError 时直接返回 JsonlSource(path)。
744
+ def _auto_source(path):
745
+ """通用 JSONL vs DSH 会话:按内容探测,避免调用方选错源类型。"""
746
+ try:
747
+ with open(path, encoding="utf-8", errors="replace") as f:
748
+ head = f.read(50000)
749
+ except OSError:
750
+ return JsonlSource(path)
751
+ for marker in ('"user/message"', '"assistant/message"', '"tool/call"'):
752
+ if marker in head:
753
+ return DSHSessionSource(path)
754
+ return JsonlSource(path)
755
+
756
+ # 生效条件:path 不是文件时返回 ok=False 的「文件不存在」;否则经 _auto_source(path) 选源后调 self.ingestor.ingest(src, dry_run=dry_run, max_events=max_events),并补上 ok=True/kind/path/source_class 后返回。
757
+ def ingest_jsonl(self, path, dry_run=False, max_events=None):
758
+ if not os.path.isfile(path):
759
+ return {"ok": False, "error": f"文件不存在:{path}"}
760
+ src = self._auto_source(path)
761
+ res = self.ingestor.ingest(src, dry_run=dry_run, max_events=max_events)
762
+ res.update({"ok": True, "kind": "session", "path": path,
763
+ "source_class": type(src).__name__})
764
+ return res
765
+
766
+
767
+ # 生效条件:cg 必填;action 为 None/空串时 (action or "stat") 归为 stat;file/jsonl 缺 path 返回 ok=False;dir 的 max_files/max_items 走 int(x or 500)/int(x or 2000),传 0 也变 500/2000;未知 action 抛 ValueError。
768
+ def run(cg, action: str = "stat", **kw):
769
+ """ingest op 唯一入口。"""
770
+ act = (action or "stat").strip().lower()
771
+ d = FileDispatcher(cg, sensitivity=kw.get("sensitivity"))
772
+ if act == "stat":
773
+ return d.stat()
774
+ if act == "file":
775
+ p = kw.get("path")
776
+ if not p:
777
+ return {"ok": False, "error": "file 动作需要 path"}
778
+ return d.ingest_file(p, layer=kw.get("layer"),
779
+ sensitivity=kw.get("sensitivity"),
780
+ dry_run=bool(kw.get("dry_run")))
781
+ if act == "dir":
782
+ p = kw.get("path") or cg.root
783
+ return d.ingest_dir(p, layer=kw.get("layer"),
784
+ sensitivity=kw.get("sensitivity"),
785
+ patterns=kw.get("patterns"),
786
+ max_files=int(kw.get("max_files") or 500),
787
+ max_items=int(kw.get("max_items") or 2000),
788
+ incremental=bool(kw.get("incremental")),
789
+ dry_run=bool(kw.get("dry_run")))
790
+ if act == "jsonl":
791
+ p = kw.get("path")
792
+ if not p:
793
+ return {"ok": False, "error": "jsonl 动作需要 path"}
794
+ return d.ingest_jsonl(p, dry_run=bool(kw.get("dry_run")),
795
+ max_events=kw.get("max_events"))
796
+ if act == "hive":
797
+ # M6:蜂巢任务事件源(hive/jobs → contextual)。root 解析链:显式 path >
798
+ # env MDCG_HIVE_JOBS > 报错指引(fail-closed,不猜仓库布局)。
799
+ # §5.5 硬纪律:mine_fix_pairs=False(先落账后挖矿——自动挖掘产物直写
800
+ # knowledge 层违反双轨制,实测 0→2 污染);默认密级 internal,
801
+ # error 事件由源层 per-event 覆写 private(失败细节可能含路径/配置)。
802
+ root = kw.get("path") or os.environ.get("MDCG_HIVE_JOBS")
803
+ if not root:
804
+ return {"ok": False, "error": (
805
+ "hive 动作需要 path(hive/jobs 目录),或设 env MDCG_HIVE_JOBS——"
806
+ "不猜测仓库布局(fail-closed)")}
807
+ src = HiveJobsSource(root, error_sensitivity=kw.get("error_sensitivity")
808
+ if kw.get("error_sensitivity") is not None else "private")
809
+ rep = Ingestor(cg, layer=kw.get("layer") or "contextual",
810
+ sensitivity=kw.get("sensitivity") or "internal"
811
+ ).ingest(src, mine_fix_pairs=False,
812
+ max_events=kw.get("max_events"),
813
+ dry_run=bool(kw.get("dry_run")))
814
+ rep["source"] = src.key()
815
+ rep["skipped"] = src.skipped
816
+ return rep
583
817
  raise ValueError(f"未知 ingest action:{action!r}(允许 {INGEST_ACTIONS})")