@furongjun1999/dsh-memory 0.4.7 → 0.4.9

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 (325) hide show
  1. package/README.md +130 -47
  2. package/codebuddy/CODEBUDDY.md +21 -10
  3. package/codebuddy/README.md +13 -1
  4. package/codebuddy/mcp.json +9 -0
  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 -0
  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 -0
  7. package/docs/README.md +111 -0
  8. package/docs/discipline/harnesses.yaml +226 -152
  9. package/docs/discipline/templates/full.md.tmpl +61 -58
  10. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
  11. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
  12. package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
  13. package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
  14. package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
  15. package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
  16. package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
  17. package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
  18. package/docs/experiments/m4-role-probe/census.py +86 -0
  19. package/docs/experiments/m4-role-probe/probe.py +89 -0
  20. package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
  21. package/docs/experiments/mapped_confidence/post_check.py +75 -0
  22. package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
  23. package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
  24. package/docs/hive//345/244/232/347/253/257harness/351/200/232/344/277/241/345/245/221/347/272/246_v0.1.md +42 -0
  25. 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 -0
  26. 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 -0
  27. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S1b/344/270/216/345/217/254/345/233/236/346/235/203/350/241/241_v0.1.md +57 -0
  28. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S7/345/200/222/346/216/222/345/200/231/351/200/211/345/261/202_v0.1.md +126 -0
  29. 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 +503 -0
  30. package/docs/{ → hive/}/350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +14 -2
  31. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -0
  32. package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
  33. package/docs/{ → mdcg/}/344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
  34. package/docs/mdcg//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_/345/245/221/347/272/246_v0.1.md +82 -0
  35. 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 -0
  36. 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 +263 -0
  37. package/docs/{ → mdcg/}/345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +1 -1
  38. package/docs/mdcg//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.5.md +236 -0
  39. package/docs/{ → mdcg/}/346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +2 -2
  40. package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
  41. 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 -0
  42. package/docs/mdcg//347/216/257/344/272/214_/347/231/275/347/256/261/345/241/253/345/205/205/346/265/201/346/260/264/347/272/277_v0.1.md +31 -0
  43. package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
  44. package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +1 -1
  45. package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +4 -4
  46. package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
  47. package/docs/mdcg//350/267/250/347/253/257/351/252/214/350/257/201/344/270/216/345/220/214/346/255/245/345/215/217/350/256/256_v0.1.md +93 -0
  48. package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -0
  49. package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
  50. package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -0
  51. package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -0
  52. package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -0
  53. package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -0
  54. 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 +6 -6
  55. package/docs/{ → theory/}/347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +1 -1
  56. package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -0
  57. package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -0
  58. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
  59. package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -0
  60. 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 +434 -405
  61. package/docs//347/201/265/346/236/242/350/207/252/346/210/221/346/224/271/350/277/233/345/267/245/344/275/234/350/256/241/345/210/222_/345/244/226/351/203/250/347/240/224/347/251/266/347/263/273/345/210/227/345/220/270/346/224/266_v1_20260919.md +286 -0
  62. package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
  63. package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
  64. package/dsh/README.md +34 -1
  65. package/dsh/cordis.yml.example +13 -7
  66. package/dsh/hive-mcp-probe.mjs +94 -0
  67. package/dsh/hive-mcp.example.yml +62 -0
  68. package/dsh/update-lingshu.bat +11 -0
  69. package/dsh/update-lingshu.ps1 +337 -0
  70. package/lib/hooks.d.ts +8 -0
  71. package/lib/hooks.js +27 -24
  72. package/lib/index.d.ts +4 -2
  73. package/lib/index.js +29 -6
  74. package/lib/lib/datapath.d.ts +76 -1
  75. package/lib/lib/datapath.js +199 -13
  76. package/lib/lib/mdcg_client.d.ts +6 -3
  77. package/lib/lib/mdcg_client.js +6 -5
  78. package/lib/lib/mutual.js +4 -4
  79. package/lib/lib/prompt_safety.d.ts +48 -0
  80. package/lib/lib/prompt_safety.js +62 -0
  81. package/lib/lib/token_store.js +4 -5
  82. package/md_cg/audit.py +91 -2
  83. package/md_cg/autonomy.py +86 -15
  84. package/md_cg/backfill.py +36 -1
  85. package/md_cg/backfill_bigdomain.py +34 -0
  86. package/md_cg/bench6_arms.py +28 -1
  87. package/md_cg/bench6_common.py +10 -1
  88. package/md_cg/bench6_competitors.py +6 -1
  89. package/md_cg/bench_axis_domain.py +9 -1
  90. package/md_cg/bench_blind_comp.py +7 -1
  91. package/md_cg/bench_en_atoms_public.py +9 -0
  92. package/md_cg/bench_governance.py +348 -0
  93. package/md_cg/bench_lme_zh.py +16 -1
  94. package/md_cg/bench_locomo.py +2 -1
  95. package/md_cg/bench_locomo_zh.py +16 -1
  96. package/md_cg/bench_locomo_zh_public.py +4 -1
  97. package/md_cg/bench_longmem.py +2 -1
  98. package/md_cg/bench_membench.py +37 -6
  99. package/md_cg/bench_p0.py +4 -1
  100. package/md_cg/bench_progressive.py +61 -10
  101. package/md_cg/bench_role_views.py +238 -0
  102. package/md_cg/bench_task_ab.py +8 -1
  103. package/md_cg/bench_task_ab_llm.py +13 -1
  104. package/md_cg/bench_unified_en.py +6 -1
  105. package/md_cg/bench_zh_mad.py +20 -1
  106. package/md_cg/blindspot_tickets.py +123 -0
  107. package/md_cg/branches.py +286 -0
  108. package/md_cg/build_postings.py +73 -0
  109. package/md_cg/ccgc.py +949 -0
  110. package/md_cg/census.py +5 -1
  111. package/md_cg/chain.py +24 -3
  112. package/md_cg/codeindex.py +134 -17
  113. package/md_cg/coldverify.py +265 -0
  114. package/md_cg/comment_gate.py +338 -0
  115. package/md_cg/cond_compose.py +190 -0
  116. package/md_cg/cond_facts.py +155 -0
  117. package/md_cg/cond_template.json +107 -0
  118. package/md_cg/condition_anchor.py +143 -0
  119. package/md_cg/conformance.py +727 -0
  120. package/md_cg/consistency.py +25 -2
  121. package/md_cg/consolidate.py +53 -2
  122. package/md_cg/corpus.py +5 -1
  123. package/md_cg/crosscheck.py +42 -2
  124. package/md_cg/crypto.py +35 -1
  125. package/md_cg/d_meta.py +310 -0
  126. package/md_cg/datapath.py +201 -26
  127. package/md_cg/docindex.py +122 -1
  128. package/md_cg/eval_common.py +35 -6
  129. package/md_cg/evidence.py +27 -1
  130. package/md_cg/evolution.py +21 -1
  131. package/md_cg/export.py +11 -1
  132. package/md_cg/forgetting.py +34 -4
  133. package/md_cg/fsutil.py +81 -2
  134. package/md_cg/hotcache.py +214 -0
  135. package/md_cg/hyperedge.py +251 -0
  136. package/md_cg/identity.py +20 -3
  137. package/md_cg/insight.py +19 -3
  138. package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
  139. package/md_cg/lexicon/build_standard_en.py +171 -168
  140. package/md_cg/lexicon/expand_en_zh.py +6 -0
  141. package/md_cg/lifecycle.py +273 -0
  142. package/md_cg/linkref.py +281 -0
  143. package/md_cg/links.py +29 -1
  144. package/md_cg/mcp_server.py +894 -168
  145. package/md_cg/md_whitebox.py +53 -1
  146. package/md_cg/mdcg.py +1205 -41
  147. package/md_cg/mdcos.py +1060 -76
  148. package/md_cg/metacognition.py +39 -4
  149. package/md_cg/migrate.py +4 -0
  150. package/md_cg/migrate_aeis.py +221 -213
  151. package/md_cg/migrate_roleplay.py +8 -0
  152. package/md_cg/migrate_wisdom_graph.py +14 -1
  153. package/md_cg/mreview/__init__.py +25 -0
  154. package/md_cg/mreview/__main__.py +110 -0
  155. package/md_cg/mreview/bundle.py +178 -0
  156. package/md_cg/mreview/candidates.py +262 -0
  157. package/md_cg/mreview/govern.py +694 -0
  158. package/md_cg/mreview/locate.py +939 -0
  159. package/md_cg/mreview/pipeline.py +729 -0
  160. package/md_cg/mreview/rules/duplication.json +21 -0
  161. package/md_cg/mreview/rules/field_coverage.json +54 -0
  162. package/md_cg/mreview/rules/source_license.json +21 -0
  163. package/md_cg/mreview/rules/template_flow.json +21 -0
  164. package/md_cg/mreview/ruleset.py +253 -0
  165. package/md_cg/nodefile.py +269 -1
  166. package/md_cg/pooling.py +23 -1
  167. package/md_cg/postings.py +298 -0
  168. package/md_cg/predict.py +113 -15
  169. package/md_cg/progressive.py +3 -0
  170. package/md_cg/protect.py +18 -5
  171. package/md_cg/protocol.py +372 -0
  172. package/md_cg/provenance.py +262 -0
  173. package/md_cg/reach.py +453 -0
  174. package/md_cg/refindex.py +88 -4
  175. package/md_cg/refine.py +20 -1
  176. package/md_cg/roleviews.py +89 -0
  177. package/md_cg/routing.py +76 -0
  178. package/md_cg/scrub.py +68 -3
  179. package/md_cg/security.py +26 -1
  180. package/md_cg/self_state.py +65 -2
  181. package/md_cg/selfreport.py +151 -0
  182. package/md_cg/semantic/canonical.py +5 -0
  183. package/md_cg/semantic/en_normalizer.py +364 -295
  184. package/md_cg/semantic/zh_en_atoms.py +139 -136
  185. package/md_cg/signer.py +41 -1
  186. package/md_cg/sources.py +583 -547
  187. package/md_cg/statushdr.py +179 -0
  188. package/md_cg/stg.py +59 -18
  189. package/md_cg/subgraph.py +23 -0
  190. package/md_cg/sustain.py +74 -1
  191. package/md_cg/tasks.py +471 -0
  192. package/md_cg/test_action_derive.py +203 -0
  193. package/md_cg/test_audit_rotate.py +270 -0
  194. package/md_cg/test_autonomy.py +26 -0
  195. package/md_cg/test_bench_governance.py +102 -0
  196. package/md_cg/test_blindspot_tickets.py +166 -0
  197. package/md_cg/test_branches.py +249 -0
  198. package/md_cg/test_ccg_perturb.py +1 -1
  199. package/md_cg/test_ccgc.py +433 -0
  200. package/md_cg/test_codeindex.py +338 -0
  201. package/md_cg/test_comment_gate.py +187 -0
  202. package/md_cg/test_cond_compose_anchors.py +76 -0
  203. package/md_cg/test_condition_anchor.py +82 -0
  204. package/md_cg/test_conformance.py +343 -0
  205. package/md_cg/test_d_meta.py +412 -0
  206. package/md_cg/test_datapath_root.py +188 -0
  207. package/md_cg/test_en_pipeline.py +24 -0
  208. package/md_cg/test_gain_gate.py +47 -1
  209. package/md_cg/test_health_scale.py +173 -0
  210. package/md_cg/test_hot_cold.py +187 -0
  211. package/md_cg/test_hyperedge.py +245 -0
  212. package/md_cg/test_identity_attribution.py +6 -0
  213. package/md_cg/test_index_durability.py +224 -0
  214. package/md_cg/test_lifecycle.py +309 -0
  215. package/md_cg/test_linkref.py +306 -0
  216. package/md_cg/test_md_access_parity.py +15 -3
  217. package/md_cg/test_mr_m1.py +769 -0
  218. package/md_cg/test_mr_m2.py +587 -0
  219. package/md_cg/test_mr_m3.py +710 -0
  220. package/md_cg/test_mr_m4.py +485 -0
  221. package/md_cg/test_p1.py +1 -1
  222. package/md_cg/test_p11_consistency.py +1 -1
  223. package/md_cg/test_p12_metacognition.py +2 -2
  224. package/md_cg/test_p16_self_state.py +1 -1
  225. package/md_cg/test_p26_refindex.py +50 -21
  226. package/md_cg/test_p27_docindex.py +258 -4
  227. package/md_cg/test_p28_refcheck.py +1 -1
  228. package/md_cg/test_p29_session_ingest_export.py +1 -1
  229. package/md_cg/test_p2_mcp.py +7 -1
  230. package/md_cg/test_p31_insight.py +24 -0
  231. package/md_cg/test_p38_contextualize.py +1 -1
  232. package/md_cg/test_p39_vision_evidence.py +1 -1
  233. package/md_cg/test_p40_refine_worklist.py +1 -1
  234. package/md_cg/test_p41_evolve_patrol.py +1 -1
  235. package/md_cg/test_p42_provenance.py +1 -1
  236. package/md_cg/test_p43_pooling.py +41 -13
  237. package/md_cg/test_p44_md_whitebox.py +14 -1
  238. package/md_cg/test_protocol.py +243 -0
  239. package/md_cg/test_reach.py +378 -0
  240. package/md_cg/test_reach_keys.py +201 -0
  241. package/md_cg/test_reach_meta_exits.py +145 -0
  242. package/md_cg/test_read_clip.py +141 -0
  243. package/md_cg/test_retr_s1.py +340 -0
  244. package/md_cg/test_retr_s1b.py +209 -0
  245. package/md_cg/test_retr_s3.py +194 -0
  246. package/md_cg/test_retr_s4.py +163 -0
  247. package/md_cg/test_retr_s5.py +200 -0
  248. package/md_cg/test_retr_s6.py +157 -0
  249. package/md_cg/test_retr_s7.py +385 -0
  250. package/md_cg/test_retr_s8_time.py +316 -0
  251. package/md_cg/test_retr_s9_edges.py +286 -0
  252. package/md_cg/test_retr_s9_entity_ctx.py +175 -0
  253. package/md_cg/test_review_conformance.py +367 -0
  254. package/md_cg/test_role_views.py +354 -0
  255. package/md_cg/test_tasks.py +409 -0
  256. package/md_cg/test_tool_face.py +189 -0
  257. package/md_cg/test_trust.py +361 -0
  258. package/md_cg/test_twophase.py +286 -0
  259. package/md_cg/test_units_poll.py +71 -0
  260. package/md_cg/test_v14_fixes.py +397 -0
  261. package/md_cg/test_validity_filter.py +280 -0
  262. package/md_cg/test_wisdom_md_store.py +7 -3
  263. package/md_cg/test_writepipe.py +214 -0
  264. package/md_cg/theory.py +17 -2
  265. package/md_cg/tokens.py +134 -12
  266. package/md_cg/tool_face.py +261 -0
  267. package/md_cg/trust.py +943 -0
  268. package/md_cg/twophase.py +232 -0
  269. package/md_cg/units.py +665 -0
  270. package/md_cg/vision_evidence.py +25 -2
  271. package/md_cg/weights.py +25 -2
  272. package/md_cg/whitebox.py +33 -2
  273. package/md_cg/whitebox_kb/data/verify_cache.json +21233 -360
  274. package/md_cg/whitebox_kb/data/verify_savings.jsonl +5124 -0
  275. package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
  276. package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
  277. package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
  278. package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
  279. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
  280. package/md_cg/writelimit.py +37 -7
  281. package/md_cg/writepipe.py +543 -0
  282. package/package.json +2 -2
  283. package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
  284. package/skills/skills/designer-perspective/scripts/designer.py +17 -1
  285. package/skills/skills/designer-perspective/tests/selftest.py +3 -1
  286. package/src/hooks.ts +27 -22
  287. package/src/index.ts +33 -6
  288. package/src/lib/datapath.ts +211 -13
  289. package/src/lib/mdcg_client.ts +12 -8
  290. package/src/lib/mutual.ts +411 -411
  291. package/src/lib/prompt_safety.ts +62 -0
  292. package/src/lib/token_store.ts +4 -5
  293. package/zcode/AGENTS.md +21 -10
  294. package/zcode/README.md +4 -0
  295. package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
  296. /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md" +0 -0
  297. /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md" +0 -0
  298. /package/docs/{ → eval/}/345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md" +0 -0
  299. /package/docs/{ → eval/}/346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md" +0 -0
  300. /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
  301. /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
  302. /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
  303. /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
  304. /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
  305. /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
  306. /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
  307. /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
  308. /package/docs/{ → mdcg/}/344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md" +0 -0
  309. /package/docs/{ → mdcg/}/347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md" +0 -0
  310. /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
  311. /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
  312. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
  313. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md" +0 -0
  314. /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
  315. /package/docs/{ → plans/}/345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md" +0 -0
  316. /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
  317. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
  318. /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
  319. /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
  320. /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
  321. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
  322. /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
  323. /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
  324. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
  325. /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
@@ -50,6 +50,7 @@ DIM_KEYWORDS = (
50
50
  )
51
51
 
52
52
 
53
+ # 生效条件:对 sys.stdout 与 sys.stderr 依次尝试 reconfigure(encoding="utf-8"),任何异常都被 try/except 吞掉后继续;
53
54
  def _stdout_utf8():
54
55
  for stream in (sys.stdout, sys.stderr):
55
56
  try:
@@ -58,6 +59,7 @@ def _stdout_utf8():
58
59
  pass
59
60
 
60
61
 
62
+ # 生效条件:text 为 None 或空串时返回 default,text 已是 dict/list 时原样返回,否则返回 json.loads(text) 的结果;
61
63
  def _load_json_arg(text, default):
62
64
  if text is None or text == "":
63
65
  return default
@@ -66,12 +68,14 @@ def _load_json_arg(text, default):
66
68
  return json.loads(text)
67
69
 
68
70
 
71
+ # 生效条件:value 为假值(None/""/[]/{}/0)时返回 [],value 是 str 时返回 [value],其余返回 list(value);
69
72
  def _as_list(value):
70
73
  if not value:
71
74
  return []
72
75
  return [value] if isinstance(value, str) else list(value)
73
76
 
74
77
 
78
+ # 生效条件:text 为假值时先置为 "",按 DIM_KEYWORDS 顺序返回首个含命中关键词的维度,全部未命中返回 "条件边界";
75
79
  def classify_dimension(text):
76
80
  """把失配描述归入五失配维度之一;未命中归入「条件边界」。"""
77
81
  text = text or ""
@@ -81,10 +85,12 @@ def classify_dimension(text):
81
85
  return "条件边界"
82
86
 
83
87
 
88
+ # 生效条件:key 在 FOUR_DIMS 中时原样返回 key,否则返回 classify_dimension(key) 的归类结果;
84
89
  def _dim_of_key(key):
85
90
  return key if key in FOUR_DIMS else classify_dimension(key)
86
91
 
87
92
 
93
+ # 生效条件:conditions 为假值(None/""/{})时返回 {},conditions 是 str 时先 json.loads 再 dict(...),其余直接 dict(conditions);
88
94
  def normalize_conditions(conditions):
89
95
  if not conditions:
90
96
  return {}
@@ -95,6 +101,7 @@ def normalize_conditions(conditions):
95
101
 
96
102
  # ---------------------------------------------------------------- 步骤 1:声明
97
103
 
104
+ # 生效条件:position 为假值时回落 "designer",不在 POSITION_MAP 中时 raise SystemExit,space 假值时用空 OrderedDict 并 setdefault 观测位置,返回含 space_id、layer、missing_dims 的 OrderedDict;
98
105
  def declare_core(position="designer", space=None):
99
106
  """条件空间声明:四维 + 有效范围 + 切换规则(3.1/3.2)。"""
100
107
  position = position or "designer"
@@ -115,6 +122,7 @@ def declare_core(position="designer", space=None):
115
122
 
116
123
  # ---------------------------------------------------------------- 步骤 2-3:白箱三问 + 四态
117
124
 
125
+ # 生效条件:evidence 取 candidate["evidence"] or conditions["anchors"] 再 _as_list,vpath 取 verification_path or candidate["verification_path"] 再 _as_list;evidence 为空返回 (False,"观测工具",detail),否则 conditions 缺任一 FOUR_DIMS 时返回 (False, 首个缺失维度, detail),否则 vpath 为空返回 (False,"条件边界",detail),全齐返回 (True,None,detail);
118
126
  def whitebox_check(candidate, conditions, verification_path=None):
119
127
  """白箱三问(第〇章)。返回 (ok, missing_dim, detail)。"""
120
128
  evidence = _as_list(candidate.get("evidence") or conditions.get("anchors"))
@@ -134,6 +142,7 @@ def whitebox_check(candidate, conditions, verification_path=None):
134
142
  return True, None, detail
135
143
 
136
144
 
145
+ # 生效条件:candidates 为假值(None/[])判 BLINDSPOT,否则逐候选按 not_applicable 与 query 的冲突记 REJECT、when 与 conditions 不符记 DEFER,全为 REJECT 判 REJECT、无合格候选取首个 DEFER、多个合格候选仅唯一 distinguishing 时选之否则 DEFER,verdict 仍空时由 whitebox_check(cand, conditions, verification_path) 定 ACCEPT 或 DEFER;
137
146
  def judge_core(query, conditions=None, candidates=None, verification_path=None):
138
147
  """四态资格裁决(6章.2):先拒绝,后接受。"""
139
148
  conditions = normalize_conditions(conditions)
@@ -248,6 +257,7 @@ def emit_mcp_calls(result):
248
257
  return calls
249
258
 
250
259
 
260
+ # 生效条件:从 result["conditions"] 取四维(.get 缺键显示 <未声明>)、从 result["traces"] 中首个含 "whitebox" 的项渲染白箱行,emit_mcp 为真值时追加 emit_mcp_calls(result) 的 JSON,返回 '\n'.join(lines);
251
261
  def render_verdict(result, emit_mcp=False):
252
262
  c = result["conditions"]
253
263
  lines = ["[判定单]",
@@ -275,6 +285,7 @@ def render_verdict(result, emit_mcp=False):
275
285
 
276
286
  # ---------------------------------------------------------------- 步骤 5:归因 / 蒸馏
277
287
 
288
+ # 生效条件:error 为假值时先 dict({}),missing 取 error.get("missing",""),dim 取 error.get("dimension") or classify_dimension(missing) 且不在 FIVE_MISMATCH 时回落 "条件边界",返回含 triple/mismatch_dim/discipline 的 OrderedDict;
278
289
  def attribute_core(error):
279
290
  """条件层归因:三方条件空间失配 + 五失配维度定位(附录22.3)。"""
280
291
  error = dict(error or {})
@@ -297,6 +308,7 @@ def attribute_core(error):
297
308
  ])
298
309
 
299
310
 
311
+ # 生效条件:对 rows 每行按 dimension 或 classify_dimension(row.get("missing","")) 分组(不在 FIVE_MISMATCH 归入 "条件边界"),每组以首行 symptom/missing 生成一条带 when/rule/change 的纪律项,rows 为空返回空 items 列表;
300
312
  def distill_core(rows):
301
313
  """失败集合 → 带适用条件的可复用纪律(1.1.1)。"""
302
314
  groups = defaultdict(list)
@@ -318,6 +330,7 @@ def distill_core(rows):
318
330
  return items
319
331
 
320
332
 
333
+ # 生效条件:对 items 逐项(依赖 it["missing"]/["rule"]/["when"]/["change"]/["evidence"])编号渲染 markdown 段落,items 为空时返回仅含标题与说明的文本;
321
334
  def render_distill(items):
322
335
  lines = ["# 蒸馏纪律(失败资产化)", "",
323
336
  "> 失败 → 根因(条件层)→ 可复用纪律(带适用条件)→ 注入 → 不再犯(1.1.1)", ""]
@@ -334,6 +347,7 @@ def render_distill(items):
334
347
 
335
348
  # ---------------------------------------------------------------- probe / selftest
336
349
 
350
+ # 生效条件:按 os.environ.get("DMCG_HOME") 再 DEFAULT_DMCG_HOME 顺序,跳过假值或不含 md_cg 子目录的 base,命中的 base 插入 sys.path 后 import md_cg.mdcos,成功返回 available=True/mode=read-only,import 抛异常返回 available=False/degraded 附 error,全部未命中返回 available=False、base=None;
337
351
  def probe_mdcg():
338
352
  """探测 md_cg 认知图可用性(只读 import,不实例化、不写库)。"""
339
353
  bases = [os.environ.get("DMCG_HOME"), DEFAULT_DMCG_HOME]
@@ -358,6 +372,7 @@ def probe_mdcg():
358
372
  ("error", "未找到 md_cg(可设 DMCG_HOME 指向 dsh-memory 根目录)")])
359
373
 
360
374
 
375
+ # 生效条件:按 case.get("kind") 分派 —— declare 比对 declare_core 的 missing_dims 与 expect 的 dims_complete/observation_position_contains,judge 与 verify 比对 judge_core 的 verdict/missing_dim 与 expect 的 state/missing_dim/require_verification_path/require_missing_dim,missing 比对 attribute_core 的 mismatch_dim,其余 kind 记 ok=False;
361
376
  def run_cases(cases):
362
377
  results = []
363
378
  for case in cases:
@@ -397,6 +412,7 @@ def run_cases(cases):
397
412
 
398
413
  # ---------------------------------------------------------------- CLI
399
414
 
415
+ # 生效条件:items 为假值(None/空列表)时返回空 OrderedDict,否则每项不含 "=" 时 raise SystemExit,含 "=" 的按首个 "=" 拆分并 strip 后写入 space;
400
416
  def _parse_space(items):
401
417
  space = OrderedDict()
402
418
  for it in items or []:
@@ -527,4 +543,4 @@ def main(argv=None):
527
543
 
528
544
 
529
545
  if __name__ == "__main__":
530
- sys.exit(main())
546
+ sys.exit(main())
@@ -21,6 +21,7 @@ sys.path.insert(0, os.path.join(ROOT, "scripts"))
21
21
  import designer # noqa: E402 (路径注入后导入)
22
22
 
23
23
 
24
+ # 生效条件:读取 cases_path 的 JSONL(strip 后为空的行跳过)交给 designer.run_cases,返回 (results, passed, total) 三元组;
24
25
  def run(cases_path):
25
26
  with open(cases_path, encoding="utf-8") as fh:
26
27
  cases = [json.loads(line) for line in fh if line.strip()]
@@ -30,6 +31,7 @@ def run(cases_path):
30
31
  return results, passed, total
31
32
 
32
33
 
34
+ # 生效条件:argv(None 时取 sys.argv)解析后 --cases 缺省为 HERE/cases.jsonl,调用 run 后 passed==total 返回 0 否则返回 1,--json 为真值时打印 JSON 汇总、否则打印逐项明细;
33
35
  def main(argv=None):
34
36
  ap = argparse.ArgumentParser(prog="selftest.py",
35
37
  description="设计者视角 · 认知能力验收")
@@ -57,4 +59,4 @@ def main(argv=None):
57
59
 
58
60
 
59
61
  if __name__ == "__main__":
60
- sys.exit(main())
62
+ sys.exit(main())
package/src/hooks.ts CHANGED
@@ -21,6 +21,14 @@
21
21
  * 模型请求组装 system prompt 时自动注入灵枢最近记忆
22
22
  * (`stg(op=timeline)`,最近记忆节点时间线),让记忆"自动可用"而不只依赖
23
23
  * Agent 主动调用 recall/think 工具。失败静默(不影响请求)。
24
+ * ⚠️ 该注入块的**稳定性**决定宿主是否新追加快照:内容没变时也必须照旧 push
25
+ * (宿主按渲染后的整段文本去重);跳过 push 反而会各追加一份「有块/无块」的快照
26
+ * —— 详见 installMemoryHooks 里的长注释。
27
+ *
28
+ * ⚠️ 注入文本**必经** escapePromptBraces(src/lib/prompt_safety.ts,issue #16):
29
+ * 宿主对 context 文本做严格 `{{variable}}` 插值,裸 `{{` 会让每轮 assemble 抛错
30
+ * → 会话永久不可用(记忆永久在库,非偶发)。记忆真源不动,只在**注入副本**上
31
+ * 打断 `{{`——新增任何 push context/section 的代码,同样必须过这道转义。
24
32
  */
25
33
 
26
34
  import '@deepseek-ai/dsh-session'
@@ -29,6 +37,7 @@ import type { Context } from '@deepseek-ai/cordis'
29
37
  import type { SessionEvent } from '@deepseek-ai/dsh-session'
30
38
  import type { ContentBlock } from '@deepseek-ai/dsh-llm'
31
39
  import type { MdcgClient } from './lib/mdcg_client.js'
40
+ import { escapePromptBraces } from './lib/prompt_safety.js'
32
41
 
33
42
  /** 自动记忆开关。 */
34
43
  export interface MemoryHooksOptions {
@@ -120,8 +129,6 @@ const RECALL_MIN_CHARS = 24
120
129
  const RECALL_MAX_CHARS = 1400
121
130
  /** 永久层不自动注入(按需用 mdcg_recall / lingshu_stg 取)。 */
122
131
  const RECALL_SKIP_LAYERS = new Set(['anchor', 'self'])
123
- /** 连续跳过多少步后强制补一次(内容未变也刷新,防止压缩归档后块消失)。 */
124
- const RECALL_REPUSH_EVERY = 8
125
132
 
126
133
  /** 分级递减渲染:按距当前的次序逐档收窄,早期条目信息量更大。
127
134
  *
@@ -153,17 +160,6 @@ function formatTimelineDecayed(payload: unknown): string {
153
160
  return out.join('\n').slice(0, RECALL_MAX_CHARS)
154
161
  }
155
162
 
156
- /** 去重:内容与上次相同就不再 push 新副本。
157
- *
158
- * 背景:本钩子挂在 `system-prompt/assemble` 上,每个 step 都会 push 一份,而每份
159
- * 都会留成独立的 surface 节点 → 同一块累积 N 份(实测 11 份 ≈2700 tok/请求),
160
- * 随步数线性增长。去重后同一块只占 1 份,外加每 RECALL_REPUSH_EVERY 步一次自愈刷新。 */
161
- function shouldPushRecall(text: string, lastText: string, skippedSincePush: number): boolean {
162
- if (!text) return false
163
- if (text !== lastText) return true
164
- return skippedSincePush >= RECALL_REPUSH_EVERY
165
- }
166
-
167
163
  /** 安装自动记忆钩子(effect 作用域内,随插件卸载自动移除)。
168
164
  *
169
165
  * mdcg 为 null(config.mdcg.enabled=false)时自动记忆整体停用:记忆真源是
@@ -194,25 +190,34 @@ export function installMemoryHooks(ctx: Context, mdcg: MdcgClient | null, opts:
194
190
 
195
191
  // P1 完善(自动 recall 注入):每次模型请求组装 system prompt 时,注入灵枢最近记忆。
196
192
  // 用 system-prompt/assemble 事件(waterfall)而非 llm/stream——后者请求 deep-frozen 不可改写。
193
+ //
194
+ // ⚠️ 必须**每步都 push**,哪怕内容与上一步逐字节相同。原因在宿主侧(dsh-agent-loop 的
195
+ // RuntimeContextProjection):assembly.contexts 会被渲染成一段「运行时上下文快照」,
196
+ // 每个 step 拿渲染后的**整段文本**与上一份已提交的快照比对,**只有不同才**在会话里
197
+ // append 一条新的 user/message(append 语义,旧的不会被替换或移除)。于是:
198
+ // · 内容不变 + 照旧 push → 渲染文本不变 → 宿主不追加任何东西(零开销、零增长);
199
+ // · 内容不变 + 跳过 push → 渲染文本**变了**(少了本块)→ 宿主追加一份「没有本块」的
200
+ // 快照;下一步再 push 又把本块加回来 → **再**追加一份。跳过一次反而多花两份快照
201
+ // (实测每份 ~250 tok),这正是 v0.4.8「每 8 步强制补一次」的自愈刷新会把长会话的
202
+ // inject 推到 30k+ tok 的原因。
203
+ // 因此本实现把「要不要补」交还给宿主:压缩归档后宿主会把 retained 置空并重新投影快照
204
+ // (RuntimeContextProjection 的 retained === null 分支),本块自然跟着回来——
205
+ // 不需要插件自己数步数做自愈。
197
206
  if (opts.autoRecall) {
198
207
  const recallLimit = Math.max(1, Math.min(10, opts.autoRecallLimit || 4))
199
- // 去重状态:同一块内容只保留一份 surface 节点,避免随步数线性增长。
200
- let lastRecallText = ''
201
- let skippedSincePush = 0
202
208
  ctx.on('system-prompt/assemble', async (assembly, _ctx, next) => {
203
209
  try {
204
210
  // 异步取最近记忆节点(失败静默——不阻塞模型请求)
205
211
  if (graph.isReady()) {
206
212
  const text = formatTimelineDecayed(await graph.timeline(recallLimit))
207
- if (shouldPushRecall(text, lastRecallText, skippedSincePush)) {
208
- lastRecallText = text
209
- skippedSincePush = 0
213
+ if (text) {
214
+ // 注入边界转义(issue #16):宿主 system-prompt 对 context 文本做严格
215
+ // `{{variable}}` 插值,裸 `{{` 会 throw → 该轮请求整体失败。记忆原文
216
+ // (含用户命令里的 `{{.X}}`)必须保真落库,故只在注入副本上打断 `{{`。
210
217
  assembly.contexts.push({
211
218
  name: 'lingshu:auto-recall',
212
- text: `【灵枢最近记忆】\n${text.slice(0, RECALL_MAX_CHARS)}`,
219
+ text: escapePromptBraces(`【灵枢最近记忆】\n${text.slice(0, RECALL_MAX_CHARS)}`),
213
220
  })
214
- } else {
215
- skippedSincePush += 1
216
221
  }
217
222
  }
218
223
  }
package/src/index.ts CHANGED
@@ -17,8 +17,10 @@
17
17
  * name: '@furongjun1999/dsh-memory'
18
18
  * config:
19
19
  * mdcg: # 记忆真源:认知图(md 文档)
20
- * # root = 记忆写入路径(用户可改)。缺省 = 插件仓自身 data/mdcg。
21
- * # 覆盖优先级:env MDCG_ROOT > <插件仓>/data/paths.json > 本项 > 默认
20
+ * # root = 记忆写入路径(用户可改)。缺省 = 用户级状态根 data/mdcg
21
+ * # (~/.dsh/.dsh-memory/data/mdcg;**不在插件包内**——包内数据会被
22
+ * # pnpm 更新连目录一起删掉,见 src/lib/datapath.ts 头注)
23
+ * # 覆盖优先级:env MDCG_ROOT > <用户级状态根>/paths.json > 本项 > 默认
22
24
  * # root: 'D:/somewhere/mdcg' # 例:把记忆库放到别处
23
25
  * actor: 'dsh-memory'
24
26
  * env: # 写入凭据:默认【关闭】,由你决定是否打开
@@ -52,7 +54,7 @@ import { installMemoryHooks, type MemoryHooksOptions } from './hooks.js'
52
54
  // LIB 本地库:角色扮演网页 / 互维维护 / 白箱 LLM 适配器统一收在 src/lib/。
53
55
  import { installRoleplayWeb } from './lib/roleplay_web.js'
54
56
  import { MdcgClient } from './lib/mdcg_client.js'
55
- import { describeDataPaths, mdcgRoot } from './lib/datapath.js'
57
+ import { describeDataPaths, migrateLegacyData, mdcgRoot, repoRoot } from './lib/datapath.js'
56
58
  // 写入凭据密钥环(首启引导):显式配置 → ~/.mdcg/token → 首启自动签发。
57
59
  import { resolveToken, type TokenResolution } from './lib/token_store.js'
58
60
 
@@ -178,8 +180,9 @@ export const Config: z<Config> = z.object({
178
180
  .default({ enabled: false, heartbeatMs: 10 * 60 * 1000 }),
179
181
  /** 认知图(md_cg):记忆唯一真源。
180
182
  * root 为**记忆写入路径**(用户可改)。缺省空串 = 未指定,按优先级解析:
181
- * ① env MDCG_ROOT ② `<插件仓>/data/paths.json` 的 root
182
- * ③ 本项 ④ 默认 `<插件仓>/data/mdcg`
183
+ * ① env MDCG_ROOT ② `<用户级状态根>/paths.json` 的 root
184
+ * (旧 `<插件仓>/data/paths.json` 兼容读)
185
+ * ③ 本项 ④ 默认 `<用户级状态根>/data/mdcg`
183
186
  * 相对路径一律相对**插件仓根**解析——历史教训:相对 cwd 的相对路径随
184
187
  * 宿主 cwd 漂移,cwd 落在别仓时记忆真源分裂成互不可见的两处。
185
188
  * tenant/actor 决定私有内容加解密的身份:与 migrate_roleplay 的
@@ -255,15 +258,39 @@ export async function apply(ctx: Context, config: Config): Promise<void> {
255
258
  timeoutMs: config.toolCallTimeoutMs,
256
259
  maxRetryDelayMs: config.maxRetryDelayMs,
257
260
  })
261
+ // 数据面迁出插件包(issue #18 相邻问题):旧版把运行时数据与路径配置写在包内
262
+ // `<pkg>/data`,pnpm 更新会连目录一起替换(实机实证:data/ 54 文件 → 0,
263
+ // 46 条记忆节点靠人工备份回填)。首启把「旧位置仍有货」的数据**复制**到用户级
264
+ // 数据根;必须在 mdcg.start() 之前——store 要在 Python 子进程接管前就位。
265
+ const migrated = migrateLegacyData()
266
+ if (migrated.ran) {
267
+ ctx.logger.info(
268
+ `dsh-memory: 数据面已迁出插件包(旧位置只复制未删除):`
269
+ + `${migrated.from} → ${migrated.to}(${migrated.copied} 项`
270
+ + `${migrated.failed.length ? `,失败 ${migrated.failed.join(',')}` : ''})`,
271
+ )
272
+ }
258
273
  mdcg.start()
259
274
  // 启动留痕:把「记忆真源在哪、由谁决定、路径是否存在」写进可审计日志,
260
275
  // 避免再次出现「以为在记忆、其实写到了别仓」的静默分裂(历史事故)。
261
276
  const dp = describeDataPaths(config.mdcg.root)
262
277
  ctx.logger.info(
263
278
  `dsh-memory: 记忆真源路径 = ${dp.mdcgRoot}(来源 ${dp.source},`
264
- + `dataRoot=${dp.dataRoot},存在=${dp.mdcgRootExists ? '是' : '否(首次写入将创建)'},`
279
+ + `状态根=${dp.stateRoot},dataRoot=${dp.dataRoot},`
280
+ + `存在=${dp.mdcgRootExists ? '是' : '否(首次写入将创建)'},`
265
281
  + `用户可改:${dp.pathsFile})`,
266
282
  )
283
+ // 包管理器装的插件 + 路径配置还在旧包内位置:pnpm 下次更新会把该文件连目录一起
284
+ // 删除,用户配置随之丢失(回落默认根 → 表现为「记忆不见了」)。只在真的会被删的
285
+ // 布局下提醒(开发用的 git clone 不含 node_modules 段 → 不打扰)。
286
+ const pnpmManaged = repoRoot().split(/[\\/]/).includes('node_modules')
287
+ if (pnpmManaged && String(dp.pathsFileSource) === 'legacy') {
288
+ ctx.logger.warn(
289
+ `dsh-memory: 路径配置仍在插件包内(${dp.pathsFile})——pnpm 更新该包会连目录`
290
+ + `一起删除。请复制到 ${String(dp.stateRoot)}/paths.json 后重启`
291
+ + `(数据面已自动复制到 ${String(dp.dataRoot)})。`,
292
+ )
293
+ }
267
294
  brainReady = await mdcg.waitReady()
268
295
  if (brainReady) {
269
296
  ctx.logger.info(`dsh-memory: 认知图已就绪(MDCG_ROOT=${resolvedRoot})`)
@@ -2,21 +2,33 @@
2
2
  * datapath.ts · 灵枢数据根解析(记忆写入路径可配置)
3
3
  *
4
4
  * 与 Python 侧 `md_cg/datapath.py` **同口径**——两侧读同一份
5
- * `<插件仓>/data/paths.json`,避免大脑与脚本对「记忆真源在哪」各持一说。
5
+ * `<用户级状态根>/paths.json`(新位置;旧版 `<插件仓>/data/paths.json`
6
+ * 仍兼容读),避免大脑与脚本对「记忆真源在哪」各持一说。
7
+ *
8
+ * 为什么默认不再取「插件仓自身 data/」(issue #18 相邻问题,数据丢失级):
9
+ * DSH 插件按 hoisted 布局装在 `<profile>/node_modules/<pkg>`,`pnpm` 更新该包
10
+ * 会**整个替换包目录**——运行时数据落在包内(`<pkg>/data`)时,每次更新成功
11
+ * 即连目录一起删掉(贡献者实机实证:`data/` 54 文件 → 0,46 条记忆节点只能
12
+ * 靠人工备份回填);`paths.json` 同址,用户配置一并丢失。故默认数据根与配置
13
+ * 文件一律落**用户级状态根**(见 `stateRoot()`),与包目录彻底解耦。
6
14
  *
7
15
  * 优先级(高 → 低):
8
16
  * 1. 环境变量 `MDCG_DATA_ROOT`(数据根)/ `MDCG_ROOT`(认知图根)
9
- * 2. 用户可编辑配置 `<插件仓>/data/paths.json` 的 `data_root` / `root`
17
+ * 2. 用户可编辑配置 `paths.json` 的 `data_root` / `root`
18
+ * (位置:`<用户级状态根>/paths.json`;旧 `<插件仓>/data/paths.json` 兼容读)
10
19
  * 3. 插件配置项 `mdcg.root`(可为空=未指定)
11
- * 4. 默认 `<插件仓>/data/mdcg`(**自身仓库**,与进程 cwd 解耦)
20
+ * 4. 默认 `<用户级状态根>/data/mdcg`
12
21
  *
13
- * 为什么默认不再取「相对 cwd 的 data/mdcg」:
22
+ * 为什么默认不取「相对 cwd 的 data/mdcg」:
14
23
  * node 侧插件与 python 侧脚本的 cwd 不同,相对路径会漂移成
15
24
  * `<cwd>/data/mdcg`。历史事故:宿主 cwd 落在 AEIS 时,记忆真源
16
25
  * 分裂到 `AEIS/data/mdcg`,与插件仓 `data/` 变成互不可见的两处。
17
- * 锚定 `package.json` 所在目录后,默认值稳定且可预期。
26
+ *
27
+ * 旧数据接手:`migrateLegacyData()` 在默认数据根生效且旧包内位置仍有内容时
28
+ * 一次性**复制**(不删除、不覆盖)到用户级位置——见该函数注释。
18
29
  */
19
- import { existsSync, readFileSync } from 'node:fs'
30
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'
31
+ import { homedir, tmpdir } from 'node:os'
20
32
  import { delimiter, dirname, isAbsolute, join, resolve } from 'node:path'
21
33
  import { fileURLToPath } from 'node:url'
22
34
 
@@ -43,9 +55,56 @@ export function repoRoot(): string {
43
55
  return cachedRepoRoot
44
56
  }
45
57
 
46
- /** 用户可编辑的路径配置文件(固定在插件仓 data/,不受 dataRoot 取值影响)。 */
58
+ export const ENV_STATE_ROOT = 'MDCG_STATE_ROOT'
59
+
60
+ let cachedStateRoot: string | null = null
61
+
62
+ /**
63
+ * 用户级状态根(**插件包目录之外**):路径配置与默认数据根的落点。
64
+ * 优先级:`MDCG_STATE_ROOT` → `$DSH_HOME/.dsh-memory` → `~/.dsh/.dsh-memory`。
65
+ * `DSH_HOME` **未必**由宿主注入(本仓 `dsh/dsh-web-start.bat` 自行 set),
66
+ * 故家目录兜底;`~/.dsh` 是本插件既有约定(`bridge.ts` 调试日志同址)。
67
+ */
68
+ export function stateRoot(): string {
69
+ if (cachedStateRoot) return cachedStateRoot
70
+ const env = process.env[ENV_STATE_ROOT]
71
+ if (env) {
72
+ cachedStateRoot = resolve(env)
73
+ return cachedStateRoot
74
+ }
75
+ const dshHome = process.env['DSH_HOME']
76
+ cachedStateRoot = dshHome
77
+ ? join(resolve(dshHome), '.dsh-memory')
78
+ : join(homedir(), '.dsh', '.dsh-memory')
79
+ return cachedStateRoot
80
+ }
81
+
82
+ /** 旧版落点(插件包内 `data/`)——更新时会被整个替换,只作兼容读与迁移源。 */
83
+ export function legacyDataRoot(): string {
84
+ return join(repoRoot(), 'data')
85
+ }
86
+
87
+ function samePath(a: string, b: string): boolean {
88
+ return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b
89
+ }
90
+
91
+ /**
92
+ * 用户可编辑的路径配置文件:
93
+ * `<用户级状态根>/paths.json` → 不存在则回落**旧** `<插件仓>/data/paths.json`
94
+ * (兼容读)→ 都不存在返回新位置(首次写入时创建)。
95
+ * 不做自动复制:配置文件是「谁说了算」的唯一真源,悄悄复制会让旧件的后续编辑
96
+ * 静默失效——迁移只搬数据面,配置显式交给用户搬(或用 `set_user_root`)。
97
+ */
47
98
  export function pathsFile(): string {
48
- return join(repoRoot(), 'data', 'paths.json')
99
+ const current = join(stateRoot(), 'paths.json')
100
+ if (existsSync(current)) return current
101
+ const legacy = join(legacyDataRoot(), 'paths.json')
102
+ return existsSync(legacy) ? legacy : current
103
+ }
104
+
105
+ /** 生效中的 paths.json 位置来源:`user`=用户级 / `legacy`=旧包内(兼容读)。 */
106
+ export function pathsFileSource(): 'user' | 'legacy' {
107
+ return samePath(dirname(pathsFile()), stateRoot()) ? 'user' : 'legacy'
49
108
  }
50
109
 
51
110
  function readUserPaths(): Record<string, unknown> {
@@ -63,8 +122,141 @@ function anchor(p: string): string {
63
122
  return isAbsolute(p) ? p : resolve(repoRoot(), p)
64
123
  }
65
124
 
125
+ /** 默认数据根 = 用户级状态根下 `data/`(**插件包目录之外**,更新不触碰)。 */
66
126
  export function defaultDataRoot(): string {
67
- return join(repoRoot(), 'data')
127
+ return join(stateRoot(), 'data')
128
+ }
129
+
130
+ export interface LegacyMigration {
131
+ /** 是否真的搬了(false 时 `reason` 说明为何没搬)。 */
132
+ ran: boolean
133
+ reason: string
134
+ from: string
135
+ to: string
136
+ /** 复制成功的顶层条目数。 */
137
+ copied: number
138
+ /** 复制失败的顶层条目(权限/占用等,逐项吞掉不抛)。 */
139
+ failed: string[]
140
+ }
141
+
142
+ /** dst 为「空目录」时 true(不存在/非目录/有内容均为 false)。 */
143
+ function isEmptyDir(p: string): boolean {
144
+ try {
145
+ return readdirSync(p).length === 0
146
+ } catch {
147
+ return false
148
+ }
149
+ }
150
+
151
+ /**
152
+ * 一次性迁移:把旧版留在**插件包内** `data/` 的数据面复制到用户级默认数据根。
153
+ *
154
+ * 触发条件(三条同时满足,缺一不动):
155
+ * ① 数据根未被显式配置(env `MDCG_DATA_ROOT` / paths.json 的 `data_root` 都未设,
156
+ * 即 `dataRoot()` 恰为默认值)——显式配置是用户的决定,越权搬运等于改他的真源;
157
+ * ② 旧位置存在;
158
+ * ③ 目标顶层条目不存在或为空目录(**不覆盖、不合并**已有数据)。
159
+ *
160
+ * 只复制不删除(旧目录随后由 pnpm 更新自然移除);`paths.json` 不复制
161
+ * (配置走 `pathsFile()` 兼容读,复制会制造「哪个文件说了算」的两说)。
162
+ * 任何异常逐项吞掉并记入 `failed`——迁移是增益,不该成为启动失败源。
163
+ *
164
+ * 边界(如实):pnpm 更新是**先替换包目录再启动新代码**,故旧数据在升级瞬间
165
+ * 即已消失,本函数只能接手「旧位置那时仍在」的情形(手工安装、就地覆盖、
166
+ * 或包目录未被清理);已在升级中丢掉的数据无法由此恢复——发布说明须提示
167
+ * 0.4.8 及更早用户升级前备份 `<profile>/node_modules/<pkg>/data/`。
168
+ */
169
+ export function migrateLegacyData(): LegacyMigration {
170
+ const from = legacyDataRoot()
171
+ const to = defaultDataRoot()
172
+ const out: LegacyMigration = { ran: false, reason: '', from, to, copied: 0, failed: [] }
173
+ if (!samePath(dataRoot(), to)) {
174
+ out.reason = '数据根为显式配置(env/paths.json),不迁移'
175
+ return out
176
+ }
177
+ if (!existsSync(from)) {
178
+ out.reason = '旧位置不存在(多为更新时已随包目录被替换)'
179
+ return out
180
+ }
181
+ let entries: string[]
182
+ try {
183
+ entries = readdirSync(from)
184
+ } catch {
185
+ out.reason = '旧位置不可读'
186
+ return out
187
+ }
188
+ const names = entries.filter((n) => n !== 'paths.json')
189
+ if (!names.length) {
190
+ out.reason = '旧位置无数据'
191
+ return out
192
+ }
193
+ for (const name of names) {
194
+ const src = join(from, name)
195
+ const dst = join(to, name)
196
+ // 新位置已有实质内容 → 跳过(宁可少搬,不可覆盖)
197
+ if (existsSync(dst) && !isEmptyDir(dst)) continue
198
+ try {
199
+ mkdirSync(to, { recursive: true })
200
+ cpSync(src, dst, { recursive: true, force: false, errorOnExist: false })
201
+ out.copied += 1
202
+ } catch {
203
+ out.failed.push(name)
204
+ }
205
+ }
206
+ out.ran = out.copied > 0
207
+ if (!out.ran) out.reason = out.failed.length ? '目标不可写' : '新位置已有数据,未覆盖'
208
+ return out
209
+ }
210
+
211
+ export const ENV_CHILD_CWD = 'MDCG_CHILD_CWD'
212
+
213
+ let cachedRunRoot: string | null = null
214
+
215
+ /**
216
+ * 子进程的工作目录(**必须落在插件包目录之外**)。
217
+ *
218
+ * 为什么不能沿用 `repoRoot()`:Windows 不允许删除/改名「正被某进程当作 CWD」
219
+ * 的目录。DSH 的插件按 hoisted 布局安装在 `<profile>/node_modules/<pkg>`,
220
+ * 而 `pnpm` 每次更新该包都要先 `rmdir` 包目录 → 子进程一旦把包目录当 CWD,
221
+ * 更新必然 `ERR_PNPM_EBUSY: resource busy or locked`(含升级回滚一起失败,
222
+ * 应用内永远升不动这个插件)。注意落点也**不能**是 `<包>/data`——它仍在包内,
223
+ * 实测同样 `err=32`。
224
+ *
225
+ * 落点要求「稳定存在」:进程 cwd 指向已消失的目录会引出新的怪问题,故按稳定度
226
+ * 排候选目录,取**第一个能建成**的:
227
+ * 1. `MDCG_CHILD_CWD`(显式覆盖)
228
+ * 2. `$DSH_HOME/.dsh-memory/run`
229
+ * 3. `~/.dsh/.dsh-memory/run` —— 家目录兜底:`DSH_HOME` **未必**由宿主注入
230
+ * (本仓 `dsh/dsh-web-start.bat` 是自行 `set DSH_HOME=%USERPROFILE%\.dsh`),
231
+ * 而 `~/.dsh` 是本插件既有约定(`bridge.ts` 调试日志、`token_store` 密钥环同址)
232
+ * 4. 系统临时目录(可能被清理,故排最后)
233
+ * 全部建不成时不抛错,交给 spawn 报错——cwd 落点只是防呆,不该成为启动失败源。
234
+ *
235
+ * 安全性:`python -m` 的模块解析由 `pythonPathValue()` 的 PYTHONPATH 独立保证,
236
+ * 不依赖 cwd;已实测 cwd=包目录 与 cwd=包外 时,MCP initialize 握手与
237
+ * tools/list 工具面(33 个工具)完全一致。
238
+ */
239
+ export function runRoot(): string {
240
+ if (cachedRunRoot) return cachedRunRoot
241
+ const candidates: string[] = []
242
+ const override = process.env[ENV_CHILD_CWD]
243
+ if (override) candidates.push(resolve(override))
244
+ const dshHome = process.env['DSH_HOME']
245
+ if (dshHome) candidates.push(join(resolve(dshHome), '.dsh-memory', 'run'))
246
+ candidates.push(join(homedir(), '.dsh', '.dsh-memory', 'run'))
247
+ candidates.push(join(tmpdir(), 'dsh-memory-run'))
248
+ for (const dir of candidates) {
249
+ try {
250
+ mkdirSync(dir, { recursive: true })
251
+ cachedRunRoot = dir
252
+ return dir
253
+ } catch {
254
+ /* 该候选不可用(父目录只读等)→ 试下一个 */
255
+ }
256
+ }
257
+ const last = candidates[candidates.length - 1] ?? tmpdir()
258
+ cachedRunRoot = last
259
+ return last
68
260
  }
69
261
 
70
262
  /** 数据根(记忆/账本/运行态的父目录)。 */
@@ -110,18 +302,24 @@ export function describeDataPaths(configured?: string): Record<string, string |
110
302
  const dr = dataRoot()
111
303
  const mr = mdcgRoot(configured)
112
304
  const paths = readUserPaths()
113
- let source = 'default(插件仓自身 data/)'
305
+ const pfSource = pathsFileSource()
306
+ let source = 'default(用户级状态根 data/)'
114
307
  if (process.env[ENV_DATA_ROOT]) source = `env:${ENV_DATA_ROOT}`
115
308
  else if (process.env[ENV_MDCG_ROOT]) source = `env:${ENV_MDCG_ROOT}`
116
- else if (typeof paths['data_root'] === 'string' || typeof paths['root'] === 'string') source = 'paths.json'
117
- else if (configured && configured.trim()) source = 'config.mdcg.root'
309
+ else if (typeof paths['data_root'] === 'string' || typeof paths['root'] === 'string') {
310
+ source = pfSource === 'user' ? 'paths.json(用户级)' : 'paths.json(兼容读旧包内位置)'
311
+ } else if (configured && configured.trim()) source = 'config.mdcg.root'
118
312
  return {
119
313
  repoRoot: repoRoot(),
314
+ stateRoot: stateRoot(),
120
315
  dataRoot: dr,
121
316
  mdcgRoot: mr,
122
317
  source,
123
318
  pathsFile: pathsFile(),
124
- isDefault: dr === defaultDataRoot(),
319
+ pathsFileSource: pfSource,
320
+ isDefault: samePath(dr, defaultDataRoot()),
321
+ legacyDataRoot: legacyDataRoot(),
322
+ legacyDataExists: existsSync(legacyDataRoot()),
125
323
  dataRootExists: existsSync(dr),
126
324
  mdcgRootExists: existsSync(mr),
127
325
  }
@@ -37,7 +37,7 @@
37
37
 
38
38
  import { delimiter } from 'node:path'
39
39
  import { LingshuBridge, type McpCallResult } from '../bridge.js'
40
- import { pythonPathValue, repoRoot } from './datapath.js'
40
+ import { pythonPathValue, runRoot } from './datapath.js'
41
41
 
42
42
  /** md_cg 子进程与根目录配置。 */
43
43
  export interface MdcgOptions {
@@ -47,9 +47,12 @@ export interface MdcgOptions {
47
47
  args?: string[]
48
48
  /** 认知图根目录(MDCG_ROOT)。 */
49
49
  root: string
50
- /** Python 子进程工作目录,默认**插件仓根**(issue #12:Python 只把 cwd
51
- * 注入 sys.path,宿主在插件仓外启动时 `python -m md_cg.mcp_server`
52
- * 找不到随包 md_cg → 必然 ModuleNotFoundError → 静默降级只读 guest)。 */
50
+ /** Python 子进程工作目录,默认 `runRoot()`——**插件包目录之外**的稳定目录
51
+ * (issue #18:Windows 不允许删除/改名「正被某进程当作 cwd」的目录,
52
+ * cwd 落在包内会让 pnpm 更新本包必然 `ERR_PNPM_EBUSY` 且永不自愈)。
53
+ * 模块解析**不依赖 cwd**:`python -m` 靠 `PYTHONPATH`
54
+ * (见 `pythonPathValue()`)解析随包 md_cg,issue #12 口径不变。
55
+ * 显式传入本项时完全尊重原值。 */
53
56
  cwd?: string
54
57
  /** 调用主体标识(MDCG_ACTOR)。私有内容按 (tenant, actor) 派生 DEK,
55
58
  * 故与迁移脚本 --actor 必须一致,否则读不到已迁移节点。 */
@@ -122,10 +125,11 @@ export class MdcgClient {
122
125
  readonly bridge: LingshuBridge
123
126
 
124
127
  constructor(opts: MdcgOptions) {
125
- // issue #12:cwd 与 PYTHONPATH 双保险锚定插件仓根——cwd 是 `python -m`
126
- // 解析随包包的主通道,PYTHONPATH 覆盖显式自定义 args 的场景。opts.env
127
- // 显式提供 PYTHONPATH 时完全接管(其展开在最后:显式配置原样尊重)。
128
- const cwd = opts.cwd ?? repoRoot()
128
+ // 子进程 cwd 必须落在插件包目录之外,否则 pnpm 更新本插件时
129
+ // rmdir 包目录会撞上 Windows 的「目录被当作 CWD」共享冲突 → ERR_PNPM_EBUSY。
130
+ // 模块解析不依赖 cwd:PYTHONPATH(pythonPathValue())已锚定随包 md_cg;
131
+ // opts.env 显式提供 PYTHONPATH 时完全接管(其展开在最后)。详见 datapath.runRoot()。
132
+ const cwd = opts.cwd ?? runRoot()
129
133
  const env: Record<string, string> = {
130
134
  // ⚠️ 必须显式 utf-8:Windows 下 piped 子进程默认 gbk + surrogateescape,
131
135
  // Node 写出的 UTF-8 中文会被解成孤立代理字符(\udcXX),md_cg 在落盘 /