@furongjun1999/dsh-memory 0.4.11 → 0.5.0

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 (504) hide show
  1. package/README.md +16 -16
  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 +142 -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//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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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 -503
  24. 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
  25. 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
  26. 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
  27. 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
  28. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
  29. package/docs/mdcg/lingshu_tutorial.html +14449 -14449
  30. package/docs/mdcg/release_v0.4.11.md +49 -0
  31. package/docs/mdcg/release_v0.4.5.md +55 -55
  32. package/docs/mdcg/tool_table_v0.3.0.md +117 -117
  33. 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
  34. 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
  35. 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
  36. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
  37. 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
  38. 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
  39. 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
  40. package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
  47. 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
  48. 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
  49. 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
  50. package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. package/dsh/README.md +82 -82
  59. package/dsh/cordis.yml.example +139 -139
  60. package/dsh/update-lingshu.bat +11 -11
  61. package/lib/hooks.js +36 -2
  62. package/lib/lib/roleplay_web.js +427 -427
  63. package/md_cg/__init__.py +7 -7
  64. package/md_cg/audit.py +368 -368
  65. package/md_cg/autonomy.py +287 -287
  66. package/md_cg/backfill.py +1327 -1327
  67. package/md_cg/backfill_bigdomain.py +34 -34
  68. package/md_cg/bench6_arms.py +410 -410
  69. package/md_cg/bench6_common.py +230 -230
  70. package/md_cg/bench6_competitors.py +212 -212
  71. package/md_cg/bench_axis_domain.py +257 -257
  72. package/md_cg/bench_blind_comp.py +308 -308
  73. package/md_cg/bench_en_atoms_public.py +230 -230
  74. package/md_cg/bench_governance.py +348 -348
  75. package/md_cg/bench_lme_zh.py +410 -410
  76. package/md_cg/bench_locomo.py +121 -121
  77. package/md_cg/bench_locomo_zh.py +450 -450
  78. package/md_cg/bench_locomo_zh_public.py +147 -147
  79. package/md_cg/bench_longmem.py +112 -112
  80. package/md_cg/bench_membench.py +632 -632
  81. package/md_cg/bench_p0.py +149 -149
  82. package/md_cg/bench_progressive.py +287 -287
  83. package/md_cg/bench_role_views.py +238 -238
  84. package/md_cg/bench_task_ab.py +243 -243
  85. package/md_cg/bench_task_ab_llm.py +408 -408
  86. package/md_cg/bench_unified_en.py +204 -204
  87. package/md_cg/bench_zh_mad.py +601 -601
  88. package/md_cg/blindspot_tickets.py +123 -123
  89. package/md_cg/branches.py +285 -285
  90. package/md_cg/build_postings.py +73 -73
  91. package/md_cg/ccgc.py +1005 -948
  92. package/md_cg/census.py +132 -132
  93. package/md_cg/chain.py +300 -300
  94. package/md_cg/codeindex.py +531 -531
  95. package/md_cg/coldverify.py +292 -292
  96. package/md_cg/comment_gate.py +337 -337
  97. package/md_cg/cond_compose.py +190 -190
  98. package/md_cg/cond_facts.py +154 -154
  99. package/md_cg/cond_template.json +106 -106
  100. package/md_cg/condition_anchor.py +142 -142
  101. package/md_cg/conformance.py +726 -726
  102. package/md_cg/consistency.py +717 -717
  103. package/md_cg/consolidate.py +1536 -1439
  104. package/md_cg/corpus.py +110 -110
  105. package/md_cg/crosscheck.py +1097 -1097
  106. package/md_cg/crypto.py +437 -437
  107. package/md_cg/d_meta.py +310 -310
  108. package/md_cg/datapath.py +334 -334
  109. package/md_cg/docindex.py +473 -473
  110. package/md_cg/eval_common.py +575 -575
  111. package/md_cg/evidence.py +580 -580
  112. package/md_cg/evolution.py +477 -477
  113. package/md_cg/export.py +220 -220
  114. package/md_cg/forgetting.py +581 -581
  115. package/md_cg/fsutil.py +329 -329
  116. package/md_cg/hotcache.py +238 -214
  117. package/md_cg/hyperedge.py +251 -251
  118. package/md_cg/identity.py +390 -390
  119. package/md_cg/insight.py +500 -500
  120. package/md_cg/interop.py +199 -0
  121. package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
  122. package/md_cg/lexicon/build_standard_en.py +171 -171
  123. package/md_cg/lexicon/expand_en_zh.py +211 -211
  124. package/md_cg/lifecycle.py +272 -272
  125. package/md_cg/linkref.py +280 -280
  126. package/md_cg/links.py +622 -622
  127. package/md_cg/mcp_server.py +129 -32
  128. package/md_cg/md_whitebox.py +345 -345
  129. package/md_cg/mdcg.py +176 -117
  130. package/md_cg/mdcos.py +79 -13
  131. package/md_cg/metacognition.py +591 -591
  132. package/md_cg/migrate.py +119 -119
  133. package/md_cg/migrate_aeis.py +221 -221
  134. package/md_cg/migrate_roleplay.py +293 -293
  135. package/md_cg/migrate_wisdom_graph.py +360 -360
  136. package/md_cg/mreview/__init__.py +25 -25
  137. package/md_cg/mreview/__main__.py +110 -110
  138. package/md_cg/mreview/bundle.py +178 -178
  139. package/md_cg/mreview/candidates.py +262 -262
  140. package/md_cg/mreview/govern.py +693 -693
  141. package/md_cg/mreview/locate.py +939 -939
  142. package/md_cg/mreview/pipeline.py +728 -728
  143. package/md_cg/mreview/rules/duplication.json +21 -21
  144. package/md_cg/mreview/rules/field_coverage.json +54 -54
  145. package/md_cg/mreview/rules/source_license.json +21 -21
  146. package/md_cg/mreview/rules/template_flow.json +21 -21
  147. package/md_cg/mreview/ruleset.py +252 -252
  148. package/md_cg/nodefile.py +575 -575
  149. package/md_cg/pooling.py +484 -472
  150. package/md_cg/postings.py +298 -298
  151. package/md_cg/predict.py +1100 -1100
  152. package/md_cg/progressive.py +123 -123
  153. package/md_cg/protect.py +272 -272
  154. package/md_cg/protocol/md_cg_gate.proto +33 -33
  155. package/md_cg/protocol.py +372 -372
  156. package/md_cg/provenance.py +582 -582
  157. package/md_cg/reach.py +453 -453
  158. package/md_cg/readcache.py +85 -0
  159. package/md_cg/refindex.py +833 -833
  160. package/md_cg/refine.py +604 -604
  161. package/md_cg/roleviews.py +89 -89
  162. package/md_cg/routing.py +365 -365
  163. package/md_cg/scrub.py +852 -852
  164. package/md_cg/security.py +274 -274
  165. package/md_cg/self_state.py +1029 -1029
  166. package/md_cg/selfreport.py +151 -151
  167. package/md_cg/semantic/__init__.py +10 -10
  168. package/md_cg/semantic/canonical.py +122 -122
  169. package/md_cg/semantic/en_normalizer.py +364 -364
  170. package/md_cg/semantic/en_zh_map.json +28694 -0
  171. package/md_cg/semantic/export_en_zh_map.py +64 -0
  172. package/md_cg/semantic/unify.py +45 -0
  173. package/md_cg/semantic/zh_en_atoms.py +139 -139
  174. package/md_cg/signer.py +562 -562
  175. package/md_cg/sources.py +815 -582
  176. package/md_cg/statushdr.py +179 -179
  177. package/md_cg/stg.py +48 -37
  178. package/md_cg/subgraph.py +729 -729
  179. package/md_cg/sustain.py +1138 -1138
  180. package/md_cg/tasks.py +470 -470
  181. package/md_cg/test_action_derive.py +203 -203
  182. package/md_cg/test_audit_rotate.py +270 -270
  183. package/md_cg/test_autonomy.py +143 -143
  184. package/md_cg/test_bench_governance.py +102 -102
  185. package/md_cg/test_blindspot_tickets.py +166 -166
  186. package/md_cg/test_branches.py +249 -249
  187. package/md_cg/test_ccg_perturb.py +184 -184
  188. package/md_cg/test_ccgc.py +433 -433
  189. package/md_cg/test_census_prune.py +81 -81
  190. package/md_cg/test_cond_compose_anchors.py +76 -76
  191. package/md_cg/test_cond_match.py +165 -165
  192. package/md_cg/test_condition_anchor.py +81 -81
  193. package/md_cg/test_d_meta.py +412 -412
  194. package/md_cg/test_datapath_root.py +199 -199
  195. package/md_cg/test_en_pipeline.py +166 -166
  196. package/md_cg/test_gain_gate.py +212 -212
  197. package/md_cg/test_health_scale.py +173 -173
  198. package/md_cg/test_hive_ingest.py +285 -0
  199. package/md_cg/test_hot_cold.py +215 -215
  200. package/md_cg/test_hyperedge.py +245 -245
  201. package/md_cg/test_i26_empty_first_write.py +116 -0
  202. package/md_cg/test_i27_e041_identity.py +128 -0
  203. package/md_cg/test_i28_hotcache_prodpath.py +122 -0
  204. package/md_cg/test_identity_attribution.py +147 -147
  205. package/md_cg/test_index_durability.py +224 -224
  206. package/md_cg/test_interop.py +93 -0
  207. package/md_cg/test_lifecycle.py +309 -309
  208. package/md_cg/test_linkref.py +306 -306
  209. package/md_cg/test_lock.py +43 -43
  210. package/md_cg/test_md_access_parity.py +255 -255
  211. package/md_cg/test_md_writepath.py +345 -345
  212. package/md_cg/test_mdstore_search_parity.py +160 -0
  213. package/md_cg/test_mr_m2.py +587 -587
  214. package/md_cg/test_mr_m3.py +710 -710
  215. package/md_cg/test_mr_m4.py +485 -485
  216. package/md_cg/test_p0.py +250 -250
  217. package/md_cg/test_p1.py +316 -316
  218. package/md_cg/test_p10_identity.py +173 -173
  219. package/md_cg/test_p11_consistency.py +233 -233
  220. package/md_cg/test_p12_metacognition.py +212 -212
  221. package/md_cg/test_p13_encryption.py +241 -241
  222. package/md_cg/test_p14_sustain.py +249 -249
  223. package/md_cg/test_p15_scrub.py +280 -280
  224. package/md_cg/test_p16_self_state.py +301 -301
  225. package/md_cg/test_p17_predict.py +354 -354
  226. package/md_cg/test_p18_whitebox.py +171 -171
  227. package/md_cg/test_p19_migrate_roleplay.py +149 -149
  228. package/md_cg/test_p20_evolution.py +315 -315
  229. package/md_cg/test_p21_tokens.py +293 -270
  230. package/md_cg/test_p22_theory.py +175 -175
  231. package/md_cg/test_p23_links.py +311 -311
  232. package/md_cg/test_p24_evidence.py +227 -227
  233. package/md_cg/test_p25_weights.py +156 -156
  234. package/md_cg/test_p26_refindex.py +416 -416
  235. package/md_cg/test_p27_docindex.py +765 -765
  236. package/md_cg/test_p28_refcheck.py +305 -305
  237. package/md_cg/test_p29_session_ingest_export.py +354 -333
  238. package/md_cg/test_p3.py +11 -2
  239. package/md_cg/test_p30_maintain.py +330 -330
  240. package/md_cg/test_p31_insight.py +534 -534
  241. package/md_cg/test_p32_backfill.py +298 -298
  242. package/md_cg/test_p33_ccg_wiring.py +293 -293
  243. package/md_cg/test_p34_crosscheck.py +331 -331
  244. package/md_cg/test_p35_conditioned_claim.py +252 -252
  245. package/md_cg/test_p36_kp_align.py +230 -230
  246. package/md_cg/test_p37_condition_space.py +248 -248
  247. package/md_cg/test_p38_concurrent_flush.py +102 -0
  248. package/md_cg/test_p38_contextualize.py +273 -273
  249. package/md_cg/test_p39_verify_flow.py +113 -0
  250. package/md_cg/test_p39_vision_evidence.py +369 -369
  251. package/md_cg/test_p40_refine_worklist.py +241 -241
  252. package/md_cg/test_p41_evolve_patrol.py +224 -224
  253. package/md_cg/test_p42_provenance.py +269 -269
  254. package/md_cg/test_p43_pooling.py +412 -398
  255. package/md_cg/test_p44_md_whitebox.py +231 -231
  256. package/md_cg/test_p45_session_identity.py +219 -219
  257. package/md_cg/test_p46_unit_scope.py +272 -272
  258. package/md_cg/test_p47_session_view.py +281 -0
  259. package/md_cg/test_p4_fuzzy.py +223 -223
  260. package/md_cg/test_p5_semantic.py +226 -226
  261. package/md_cg/test_p6_consolidate.py +440 -387
  262. package/md_cg/test_p7_goals_recent.py +202 -202
  263. package/md_cg/test_p8_subgraph_chain.py +200 -200
  264. package/md_cg/test_p9_forget_protect.py +231 -231
  265. package/md_cg/test_predict_beta.py +135 -135
  266. package/md_cg/test_preflight_failclosed.py +100 -100
  267. package/md_cg/test_progressive.py +146 -146
  268. package/md_cg/test_protocol.py +243 -243
  269. package/md_cg/test_reach.py +378 -378
  270. package/md_cg/test_reach_keys.py +201 -201
  271. package/md_cg/test_read_clip.py +141 -141
  272. package/md_cg/test_readcache_prodpath.py +155 -0
  273. package/md_cg/test_retr_gates_prodpath.py +140 -0
  274. package/md_cg/test_retr_s1.py +340 -340
  275. package/md_cg/test_retr_s1b.py +209 -209
  276. package/md_cg/test_retr_s3.py +194 -194
  277. package/md_cg/test_retr_s4.py +163 -163
  278. package/md_cg/test_retr_s5.py +200 -200
  279. package/md_cg/test_retr_s6.py +157 -157
  280. package/md_cg/test_retr_s7.py +384 -384
  281. package/md_cg/test_retr_s8_time.py +369 -316
  282. package/md_cg/test_retr_s9_edges.py +286 -286
  283. package/md_cg/test_retr_s9_entity_ctx.py +175 -175
  284. package/md_cg/test_review_conformance.py +367 -367
  285. package/md_cg/test_role_views.py +354 -354
  286. package/md_cg/test_sem_noise.py +242 -242
  287. package/md_cg/test_semantic_canonical.py +241 -241
  288. package/md_cg/test_subproc_encoding.py +192 -192
  289. package/md_cg/test_sustain_mutual.py +153 -153
  290. package/md_cg/test_tasks.py +409 -409
  291. package/md_cg/test_tool_face.py +189 -189
  292. package/md_cg/test_transfer.py +180 -180
  293. package/md_cg/test_trust.py +361 -361
  294. package/md_cg/test_twophase.py +286 -286
  295. package/md_cg/test_v14_fixes.py +397 -397
  296. package/md_cg/test_validity_filter.py +280 -280
  297. package/md_cg/test_verify_answer.py +138 -138
  298. package/md_cg/test_wisdom_md_store.py +292 -292
  299. package/md_cg/test_writelimit.py +197 -197
  300. package/md_cg/test_writepipe.py +214 -214
  301. package/md_cg/theory.py +273 -273
  302. package/md_cg/tokens.py +677 -663
  303. package/md_cg/tool_face.py +260 -260
  304. package/md_cg/trust.py +986 -950
  305. package/md_cg/twophase.py +231 -231
  306. package/md_cg/units.py +667 -667
  307. package/md_cg/vision_evidence.py +666 -666
  308. package/md_cg/weights.py +624 -624
  309. package/md_cg/whitebox.py +527 -527
  310. package/md_cg/whitebox_kb/__init__.py +37 -37
  311. package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
  312. package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
  313. package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
  314. package/md_cg/whitebox_kb/engine.py +310 -310
  315. package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  316. package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
  317. package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
  318. package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
  319. package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
  320. package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
  321. package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
  322. package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
  323. package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
  324. package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
  325. package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
  326. package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
  327. package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
  328. package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
  329. package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
  330. package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
  331. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
  332. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
  333. package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
  334. package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
  335. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
  336. package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
  337. package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
  338. package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
  339. package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
  340. package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
  341. package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
  342. package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
  343. package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
  344. package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
  345. package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
  346. package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
  347. package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
  348. package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
  349. package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
  350. package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
  351. package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
  352. package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
  353. package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
  354. package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
  355. package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
  356. package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
  357. package/md_cg/writelimit.py +356 -356
  358. package/md_cg/writepipe.py +550 -542
  359. package/package.json +97 -96
  360. package/skills/plugin.json +54 -54
  361. package/skills/skills/designer-perspective/SKILL.md +158 -158
  362. package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
  363. package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
  364. package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
  365. package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
  366. package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
  367. package/skills/skills/designer-perspective/scripts/designer.py +545 -545
  368. package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
  369. package/skills/skills/designer-perspective/tests/selftest.py +61 -61
  370. package/skills/skills/lingshu-browser/SKILL.md +60 -60
  371. package/skills/skills/lingshu-compiler/SKILL.md +56 -56
  372. package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
  373. package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
  374. package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
  375. package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
  376. package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
  377. package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
  378. package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
  379. package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
  380. package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
  381. package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
  382. package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
  383. package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
  384. package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
  385. package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
  386. package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
  387. package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
  388. package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
  389. package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
  390. package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
  391. package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
  392. package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
  393. package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
  394. package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
  395. package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
  396. package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
  397. package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
  398. package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
  399. package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
  400. package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
  401. package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
  402. package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
  403. package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
  404. package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
  405. package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
  406. package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
  407. package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
  408. package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
  409. package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
  410. package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
  411. package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
  412. package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
  413. package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
  414. package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
  415. package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
  416. package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
  417. package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
  418. package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
  419. package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
  420. package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
  421. package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
  422. package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
  423. package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
  424. package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
  425. package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
  426. package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
  427. package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
  428. package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
  429. package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
  430. package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
  431. package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
  432. package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
  433. package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
  434. package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
  435. package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
  436. package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
  437. package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
  438. package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
  439. package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
  440. package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
  441. package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
  442. package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
  443. package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
  444. package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
  445. package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
  446. package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
  447. package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
  448. package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
  449. package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
  450. package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
  451. package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
  452. package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
  453. package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
  454. package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
  455. package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
  456. package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
  457. package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
  458. package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
  459. package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
  460. package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
  461. package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
  462. package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
  463. package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
  464. package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
  465. package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
  466. package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
  467. package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
  468. package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
  469. package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
  470. package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
  471. package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
  472. package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
  473. package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
  474. package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
  475. package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
  476. package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
  477. package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
  478. package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
  479. package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
  480. package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
  481. package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
  482. package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
  483. package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
  484. package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
  485. package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
  486. package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
  487. package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
  488. package/skills/skills/lingshu-graph/SKILL.md +63 -63
  489. package/skills/skills/lingshu-net/SKILL.md +48 -48
  490. package/skills/skills/lingshu-os/SKILL.md +64 -64
  491. package/skills/skills/lingshu-pylang/SKILL.md +71 -71
  492. package/src/bridge.ts +401 -401
  493. package/src/hooks.ts +38 -2
  494. package/src/lib/datapath.ts +326 -326
  495. package/src/lib/mdcg_client.ts +413 -413
  496. package/src/lib/mutual.ts +428 -428
  497. package/src/lib/prompt_safety.ts +62 -62
  498. package/src/lib/python_path.ts +71 -71
  499. package/src/lib/roleplay_web.ts +932 -932
  500. package/src/lib/token_store.ts +192 -192
  501. package/src/tools.ts +212 -212
  502. package/zcode/AGENTS.md +11 -3
  503. package/zcode/README.md +41 -41
  504. /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/tasks.py CHANGED
@@ -1,471 +1,471 @@
1
- """结构层任务实体 —— structural 层正式业务写入口(2026-09-16)。
2
-
3
- 使用者裁定(按既有记忆分层,三层分工):
4
-
5
- 任务 → structural 层(与「协议/自我/信任」同级;
6
- 跨会话稳定、不可遗忘——`DEMOTE_CONFIDENCE`
7
- 的降级面只覆盖 knowledge 层)
8
- 执行任务的中间信息 → contextual 层(不落库,由 AI 上下文自理)
9
- 任务知识 / 外部参考 / 交接文档 → knowledge 层(被验证的重要信息,按需调用;
10
- 节点 tags 记 `task:<slug>` 实现反向关联)
11
-
12
- 消除 AI 失忆四症状:
13
-
14
- ① 忘记已实现的工程 → `session_tasks()` 供 `session_recall` 装配
15
- 「进行中 + 近期完成」,新会话开机即见
16
- ② 计划与实际不符 → `plan_add()` 累积「计划变更」节,偏差可追溯
17
- ③ 缺核验 → 状态迁 `done` 时「结果」节必填(缺一不收,
18
- 对齐 `branches.BRANCH_MARKS` 的同款闸)
19
- ④ 换表述即新任务 → 身份判据 = 语义命名 slug(**刻意不用内容哈希**);
20
- `find_similar()` 只提示疑似同族,不自动合并
21
-
22
- 为什么身份不用内容哈希:`add_goal` 的 `goal_<sha1(text)>` 正是第 ④ 点的病根——
23
- 同一目标换个说法就变成新 goal、重复开工。任务名是使用者给的稳定标识,
24
- 重复登记必须更新原卡而不是再开一张。
25
-
26
- 与 goals 的关系:**不双写**。goals 是「检索定向槽」(谁该被检索到),任务是
27
- 「工程台账」(做到哪一步、结果是什么)——语义不同,双写必漂移(既有实证:
28
- 覆盖写会让库正文回退首版)。会话装配面各自成段。
29
-
30
- 与 branches 同哲学:库层函数收 `cg`;写盘走 `cg.add`(`require_layer_write`
31
- 权限闸自动生效,库层不绕闸);生命周期事件走 `cg._audit`(不新造日志格式,
32
- 审计失败绝不阻断主流程)。
33
-
34
- 正文格式 = CCG 六要素 + 三节(计划 / 计划变更 / 结果):
35
-
36
- # 功能名:<任务名>
37
- # 生效条件:<任务适用范围;填「无条件」则豁免资格判定中的正条件确认>
38
- # 子功能:<任务目标>
39
- # 执行:<状态>|<进度说明>
40
- # 验证方式:<验收判据>
41
- # 不适用条件:<边界>
42
-
43
- ## 计划
44
- ## 计划变更
45
- ## 结果
46
- """
47
- from __future__ import annotations
48
-
49
- import re
50
- import time
51
-
52
- TASK_STATUSES = ("active", "blocked", "done", "dropped")
53
-
54
- #: 状态中文投影(只用于正文可读性,机械判据始终用英文值)
55
- STATUS_ZH = {"active": "进行中", "blocked": "受阻", "done": "完成", "dropped": "放弃"}
56
-
57
- #: 任务节点统一标签(第一个是身份标记,第二个是任务族标记)
58
- TASK_TAG = "task"
59
- TASK_PREFIX = "task_"
60
-
61
- #: ≥0.7 会被 protect 自动打「不可遗忘」标记——即「任务结果必须得到维护」的
62
- #: 架构层保障:普通 forget 搬不走它,只有显式解保护才动得了。
63
- DEFAULT_IMPORTANCE = 0.8
64
-
65
- #: 正文节名
66
- SEC_PLAN = "计划"
67
- SEC_CHANGE = "计划变更"
68
- SEC_RESULT = "结果"
69
-
70
- #: 语义 slug 守卫——首字符须为中文/字母/数字,其后允许 `_ . -`,总长 ≤64。
71
- #: 与 `branches._BRANCH_RE` 同风格(防路径穿越与非法文件名),但放宽到 Unicode:
72
- #: 任务名多为中文,缩到 ASCII 会逼使用者起英文别名,反而制造第二个身份。
73
- _SLUG_RE = re.compile(r"^[0-9A-Za-z\u4e00-\u9fff][0-9A-Za-z\u4e00-\u9fff_.-]{0,63}$")
74
- _ILLEGAL_RE = re.compile(r"[^0-9A-Za-z\u4e00-\u9fff_.-]+")
75
-
76
- # ---------------------------------------------------------------- 命名与解析
77
-
78
- # 生效条件:name 为 None/空串或 strip 后为空时返回空串,否则把 `/`、`\`、`..` 及 _ILLEGAL_RE 命中字符折叠为 `-`、压缩连续 `-` 并去首尾 `-.` 后取前 64 字符再去首尾 `-.` 返回;
79
- def slugify(name: str) -> str:
80
- """任务名 → 语义 slug(稳定标识;同 slug 即同任务)。
81
-
82
- 只做「可安全落文件名」的归一:路径分隔符与非法字符折叠为 `-`,连续 `-`
83
- 压成一个。**不做语义改写**(不翻译、不去停用词)——slug 是对外可见的
84
- 身份,擅自改写会让使用者按原名检索时对不上号。
85
- """
86
- s = (name or "").strip()
87
- if not s:
88
- return ""
89
- s = s.replace("/", "-").replace("\\", "-").replace("..", "-")
90
- s = _ILLEGAL_RE.sub("-", s)
91
- s = re.sub(r"-{2,}", "-", s).strip("-.")
92
- return s[:64].strip("-.")
93
-
94
-
95
- # 生效条件:name(`name or ""` 后 strip)先剥掉已有的 TASK_PREFIX 再 slugify,结果非空且被 _SLUG_RE.match 命中时返回 TASK_PREFIX + slug;name 为假值或归一化后为空、不匹配时返回 ""。
96
- def task_node_id(name: str) -> str:
97
- """任务名或节点 id → 规范节点 id(`task_<slug>`)。非法名返回空串。"""
98
- s = (name or "").strip()
99
- if s.startswith(TASK_PREFIX):
100
- s = s[len(TASK_PREFIX):]
101
- slug = slugify(s)
102
- if not slug or not _SLUG_RE.match(slug):
103
- return ""
104
- return TASK_PREFIX + slug
105
-
106
-
107
- # 生效条件:v 为 None 或 str(v).strip() 为 "" 时返回 False,否则返回 True。
108
- def _has(v) -> bool:
109
- """「本次调用是否提供了该字段」——空串/None 一律视为未提供(不静默清空)。"""
110
- return v is not None and str(v).strip() != ""
111
-
112
-
113
- # 生效条件:无必需形参且无模块级常量约束,恒返回 time.strftime("%Y-%m-%d %H:%M") 的当前时间文本。
114
- def _today() -> str:
115
- return time.strftime("%Y-%m-%d %H:%M")
116
-
117
-
118
- # 生效条件:content(`content or ""` 后 splitlines)中有某行 strip 后以 `# ` + field + `:` 开头时,返回该行该前缀之后的去空白内容;content 为假值或无此匹配行时返回 ""。
119
- def _field_line(content: str, field: str) -> str:
120
- """从正文抽 `# <字段>:` 行的值(与 `mdcos._ccg_field` 同源口径,
121
- 本模块不反向 import mdcos,避免包内循环依赖)。"""
122
- pre = "# " + field + ":"
123
- for line in (content or "").splitlines():
124
- s = line.strip()
125
- if s.startswith(pre):
126
- return s[len(pre):].strip()
127
- return ""
128
-
129
-
130
- # 生效条件:(content or "") 的行中 strip 后以 "## " 开头者成为节名 s[3:].strip() 并切换当前节,其余行累入当前节,返回各节内容以 "\n" join 后 strip 的字典;无任何标题行时仅返回 {"__body__": 全篇 strip};content 为 None/空串时返回 {"__body__": ""}。
131
- def sections(content: str) -> dict:
132
- """正文 → `{节名: 节内容}`;无标题部分归入 `__body__`。"""
133
- out: dict = {"__body__": []}
134
- cur = "__body__"
135
- for line in (content or "").splitlines():
136
- s = line.strip()
137
- if s.startswith("## "):
138
- cur = s[3:].strip()
139
- out.setdefault(cur, [])
140
- continue
141
- out.setdefault(cur, []).append(line)
142
- return {k: "\n".join(v).strip() for k, v in out.items()}
143
-
144
-
145
- # 生效条件:t = (text or "").strip(),t 为 ""/"(无)"/"(未填)" 时返回 "",否则 len(t) <= n(默认 200)时返回 t,超出时返回 t[:n].rstrip() + "…"。
146
- def _brief(text: str, n: int = 200) -> str:
147
- t = (text or "").strip()
148
- if t in ("", "(无)", "(未填)"):
149
- return ""
150
- return t if len(t) <= n else t[:n].rstrip() + "…"
151
-
152
-
153
- # 生效条件:以 (old_text or "").strip() 为 base(base 为「(无)」或「(未填)」时置空),_has(change) 判定为假时返回 base or "(无)",为真时拼出 `- [今日] change.strip()`,base 非空返回 base+"\n"+该行再 strip,base 为空只返回该行;
154
- def _append_change(old_text: str, change: str) -> str:
155
- """「计划变更」节追加一行(累积式,不覆盖历史)。"""
156
- base = (old_text or "").strip()
157
- if base in ("(无)", "(未填)"):
158
- base = ""
159
- if not _has(change):
160
- return base or "(无)"
161
- line = "- [%s] %s" % (_today(), str(change).strip())
162
- return (base + "\n" + line).strip() if base else line
163
-
164
-
165
- # 生效条件:name 为必需形参(`name or ""` 后 strip 填 `# 功能名:` 行);condition、goal、acceptance、boundary、plan、changes、result 各经 _has 判定,未提供时分别落「无条件」「(未填:任务目标待补)」「other」「任务转 done/dropped 终态后不再作为进行中任务参与装配」「(未填)」「(无)」与空串;status 经 STATUS_ZH.get(status, status) 映射、未命中时原样输出,note 经 _has 为真时以 `|` 拼在执行行后。
166
- def render(name: str, *, condition: str = "", goal: str = "", status: str = "active",
167
- note: str = "", acceptance: str = "", boundary: str = "",
168
- plan: str = "", changes: str = "", result: str = "") -> str:
169
- """按 CCG 六要素 + 三节渲染任务正文。"""
170
- exec_line = STATUS_ZH.get(status, status)
171
- if _has(note):
172
- exec_line = exec_line + "|" + str(note).strip()
173
- return "\n".join([
174
- "# 功能名:%s" % (name or "").strip(),
175
- "# 生效条件:%s" % (condition.strip() if _has(condition) else "无条件"),
176
- "# 子功能:%s" % (goal.strip() if _has(goal) else "(未填:任务目标待补)"),
177
- "# 执行:%s" % exec_line,
178
- "# 验证方式:%s" % (acceptance.strip() if _has(acceptance) else "other"),
179
- "# 不适用条件:%s" % (boundary.strip() if _has(boundary)
180
- else "任务转 done/dropped 终态后不再作为进行中任务参与装配"),
181
- "",
182
- "## %s" % SEC_PLAN,
183
- (plan.strip() if _has(plan) else "(未填)"),
184
- "",
185
- "## %s" % SEC_CHANGE,
186
- (changes.strip() if _has(changes) else "(无)"),
187
- "",
188
- "## %s" % SEC_RESULT,
189
- (result.strip() if _has(result) else ""),
190
- "",
191
- ])
192
-
193
-
194
- # ---------------------------------------------------------------- 读写
195
-
196
- # 生效条件:nid 为假值(None/空串)时返回 None;否则 cg.get(nid) 命中且其 frontmatter.layer == "structural"、TASK_TAG 在 frontmatter.tags(`or []`)中时,返回 {id, fm, content, path, sec}(sec 为 sections(content));记录缺失或层/标签不符时返回 None。
197
- def _read_task(cg, nid: str):
198
- """读回任务卡;层或标签不符一律视为不存在(防串号:别的节点占用了同 id)。"""
199
- if not nid:
200
- return None
201
- rec = cg.get(nid)
202
- if not rec:
203
- return None
204
- fm = rec.get("frontmatter") or {}
205
- if fm.get("layer") != "structural" or TASK_TAG not in (fm.get("tags") or []):
206
- return None
207
- content = rec.get("content") or ""
208
- return {"id": nid, "fm": fm, "content": content,
209
- "path": rec.get("path"), "sec": sections(content)}
210
-
211
-
212
- # 生效条件:以 layer="structural"、tags=list(tags)、importance=float(importance)、override=True、task_name=name、task_status=status、task_updated_at=time.time()、actor=actor or "task" 等构成 kw,extra 为真值时经 kw.update(extra) 追加覆盖,随后调用 cg.add(nid, content, **kw) 并返回其结果;
213
- def _write(cg, nid: str, name: str, content: str, status: str, tags,
214
- importance: float, actor, extra: dict = None):
215
- """唯一写盘点——走 `cg.add`(权限闸 / 归属注入全部复用既有链路)。
216
-
217
- `override=True` 的含义:任务更新是**系统自身的幂等更新**(同 slug 即同任务),
218
- 显式声明该意图后,若节点已带 `protected`/`self_state` 标记,protect 会走
219
- 「旧版本快照 + 审计留痕」的放行分支而不是静默改写——任务台账天然获得版本快照。
220
- """
221
- kw = dict(layer="structural", tags=list(tags), importance=float(importance),
222
- confidence=1.0, verification_basis="other",
223
- override=True, consistency=False,
224
- task_name=name, task_status=status,
225
- task_updated_at=time.time(), actor=actor or "task")
226
- if extra:
227
- kw.update(extra)
228
- return cg.add(nid, content, **kw)
229
-
230
-
231
- # 生效条件:cg 具有 _audit 属性(getattr(cg, "_audit", None) 非 None)时以 (op, nid, **meta) 调用它,且其中抛出的任何异常被吞掉;cg 无该属性时不调用,两种路径均不返回内容。
232
- def _audit(cg, op: str, nid: str, **meta) -> None:
233
- """生命周期留痕;审计失败绝不阻断主流程(与 branches._audit 同哲学)。"""
234
- a = getattr(cg, "_audit", None)
235
- if a is None:
236
- return
237
- try:
238
- a(op, nid, **meta)
239
- except Exception: # noqa: BLE001
240
- pass
241
-
242
-
243
- # 生效条件:_read_task(cg, nid) 命中任务卡时返回摘要条目——id 取 rec['id'],name 取 fm.task_name 或(缺失/假值时)rec['id'],status 取 fm.task_status 或 "active",plan/changes/result 取对应节的 _brief 摘要(缺节回落 ""),并带 created_at/updated_at/path;未命中时返回 None。
244
- def _entry(cg, nid: str):
245
- """节点 id → 对外任务条目(摘要形态;全字段查 `get_task`)。"""
246
- rec = _read_task(cg, nid)
247
- if not rec:
248
- return None
249
- fm, sec = rec["fm"], rec["sec"]
250
- return {"id": rec["id"],
251
- "name": fm.get("task_name") or rec["id"],
252
- "status": fm.get("task_status") or "active",
253
- "plan": _brief(sec.get(SEC_PLAN, "")),
254
- "changes": _brief(sec.get(SEC_CHANGE, "")),
255
- "result": _brief(sec.get(SEC_RESULT, "")),
256
- "created_at": fm.get("task_created_at"),
257
- "updated_at": fm.get("task_updated_at"),
258
- "path": rec["path"]}
259
-
260
-
261
- # ---------------------------------------------------------------- 写操作
262
-
263
- # 生效条件:slugify(name) 为空串或不匹配 _SLUG_RE 时返回 {'ok': False, 含 slug 的非法名 error};否则 st = str(status if _has(status) else (旧卡 task_status or "active")).strip().lower(),st 不在 TASK_STATUSES 时返回未知状态错误,st == "done" 且合并后结果节 _has(new_res) 为假时返回「转 done 必须填结果」拒收,其余情况渲染写入并返回按 old 是否为 None 区分新建/更新的 out。
264
- def upsert(cg, name: str, *, plan: str = None, status: str = None,
265
- result: str = None, condition: str = None, goal: str = None,
266
- acceptance: str = None, boundary: str = None, change: str = None,
267
- note: str = None, tags=None, importance: float = None,
268
- actor: str = None) -> dict:
269
- """登记或更新一张任务卡(同 slug 即同任务;重复登记不新建卡)。
270
-
271
- 未提供的字段**沿用旧值**(不静默清空);`status="done"` 且合并后「结果」节
272
- 仍为空 → 拒收,盘上内容不变。
273
-
274
- :param note: 进度说明,落 `# 执行:` 行(`|` 之后)。
275
- :param change: 本次发现的新问题/计划偏差,追加到「计划变更」节。
276
- """
277
- slug = slugify(name)
278
- if not slug or not _SLUG_RE.match(slug):
279
- return {"ok": False,
280
- "error": "任务名非法(slug=%r);要求:中文/字母/数字起头,"
281
- "仅含中文/字母/数字/_ . -,长度 ≤64" % (slug,)}
282
-
283
- nid = TASK_PREFIX + slug
284
- old = _read_task(cg, nid)
285
- prev_fm = old["fm"] if old else {}
286
- prev = old["sec"] if old else {}
287
-
288
- display = (name or "").strip() or prev_fm.get("task_name") or slug
289
- st = str(status if _has(status) else (prev_fm.get("task_status") or "active")).strip().lower()
290
- if st not in TASK_STATUSES:
291
- return {"ok": False, "node_id": nid,
292
- "error": "未知任务状态:%r(可选 %s)" % (st, list(TASK_STATUSES))}
293
-
294
- # 未提供 → 沿用旧值(结果与计划绝不被静默清空)
295
- new_plan = plan if _has(plan) else prev.get(SEC_PLAN, "")
296
- new_res = result if _has(result) else prev.get(SEC_RESULT, "")
297
- new_cond = condition if _has(condition) else _field_line(old["content"], "生效条件") if old else ""
298
- new_goal = goal if _has(goal) else _field_line(old["content"], "子功能") if old else ""
299
- new_acc = acceptance if _has(acceptance) else _field_line(old["content"], "验证方式") if old else ""
300
- new_bnd = boundary if _has(boundary) else _field_line(old["content"], "不适用条件") if old else ""
301
- new_note = note if _has(note) else _exec_note(old["content"]) if old else ""
302
- new_changes = _append_change(prev.get(SEC_CHANGE, ""), change)
303
-
304
- if st == "done" and not _has(new_res):
305
- return {"ok": False, "node_id": nid,
306
- "error": "任务转 done 必须填写「结果」——结果不可缺失(缺一不收)",
307
- "hint": "补 result 后重试;盘上内容未变更"}
308
-
309
- content = render(display, condition=new_cond, goal=new_goal, status=st,
310
- note=new_note, acceptance=new_acc, boundary=new_bnd,
311
- plan=new_plan, changes=new_changes, result=new_res)
312
- tg = list(dict.fromkeys([TASK_TAG, "%s:%s" % (TASK_TAG, slug)]
313
- + [str(t) for t in (tags or []) if str(t).strip()]))
314
- imp = DEFAULT_IMPORTANCE if importance is None else float(importance)
315
- created = prev_fm.get("task_created_at") or time.time()
316
- _write(cg, nid, display, content, st, tg, imp, actor,
317
- extra={"task_created_at": created, "task_slug": slug})
318
- _audit(cg, "task_open" if old is None else "task_update", nid,
319
- status=st, slug=slug, name=display)
320
- out = {"ok": True, "node_id": nid, "name": display, "status": st,
321
- "created": old is None,
322
- "result_present": _has(new_res),
323
- "hint": "新建任务卡" if old is None else "已更新原卡(同 slug 即同任务)"}
324
- if slug != (name or "").strip():
325
- # 身份被归一化了就必须说出来——否则调用方按原名去找会找不到卡
326
- out["slug_normalized"] = slug
327
- return out
328
-
329
-
330
- # 生效条件:content 中 `# 执行:` 行的值含 "|" 时返回第一个 "|" 之后去空白的内容;content 为假值、无该行或该行值不含 "|" 时返回 ""。
331
- def _exec_note(content: str) -> str:
332
- """从 `# 执行:` 行取 `|` 之后的进度说明。"""
333
- val = _field_line(content, "执行")
334
- return val.split("|", 1)[1].strip() if "|" in val else ""
335
-
336
-
337
- # 生效条件:node_id 经 task_node_id 得到非空 nid 且 _read_task 命中该卡时,以旧卡 task_name(缺失/假值回落 nid)、status、result、note、actor 转调 upsert 并返回其结果;node_id 非法时返回 ok=False「非法任务标识」,卡不存在时返回 ok=False「任务不存在」。
338
- def set_status(cg, node_id: str, status: str, *, result: str = None,
339
- note: str = None, actor: str = None) -> dict:
340
- """任务状态迁移(进行中/受阻/完成/放弃)。迁 done 且无结果 → 拒收。"""
341
- nid = task_node_id(node_id)
342
- if not nid:
343
- return {"ok": False, "error": "非法任务标识:%r" % (node_id,)}
344
- old = _read_task(cg, nid)
345
- if not old:
346
- return {"ok": False, "node_id": nid, "error": "任务不存在:%s" % nid}
347
- return upsert(cg, old["fm"].get("task_name") or nid, status=status,
348
- result=result, note=note, actor=actor)
349
-
350
-
351
- # 生效条件:change 经 _has 判定为已提供、且 node_id 经 task_node_id 非空、_read_task 命中该卡时,以旧卡 task_name(缺失/假值回落 nid)与 change、actor 转调 upsert;change 为 None/空串/纯空白时返回 ok=False「计划变更内容为空」,node_id 非法或卡不存在时返回 ok=False 对应错误。
352
- def plan_add(cg, node_id: str, change: str, *, actor: str = None) -> dict:
353
- """计划变更追加——执行中发现的错误/新问题/偏差,累积进「计划变更」节。"""
354
- if not _has(change):
355
- return {"ok": False, "error": "计划变更内容为空"}
356
- nid = task_node_id(node_id)
357
- if not nid:
358
- return {"ok": False, "error": "非法任务标识:%r" % (node_id,)}
359
- old = _read_task(cg, nid)
360
- if not old:
361
- return {"ok": False, "node_id": nid, "error": "任务不存在:%s" % nid}
362
- return upsert(cg, old["fm"].get("task_name") or nid, change=change, actor=actor)
363
-
364
-
365
- # ---------------------------------------------------------------- 读操作
366
-
367
- # 生效条件:node_id 经 task_node_id 得到的 nid 对应一张 _read_task 命中的任务卡时返回 ok=True 的全字段(condition/goal/acceptance/boundary 由 _field_line 抽取、note 由 _exec_note 抽取、plan/changes/result 取节原文不截断、tags 取 fm.tags 或 []);否则返回 ok=False,error 中的标识取 nid 或原 node_id。
368
- def get_task(cg, node_id: str) -> dict:
369
- """单卡全字段读回(计划/变更/结果不截断)。"""
370
- nid = task_node_id(node_id)
371
- rec = _read_task(cg, nid)
372
- if not rec:
373
- return {"ok": False, "error": "任务不存在:%s" % (nid or node_id)}
374
- fm, sec = rec["fm"], rec["sec"]
375
- return {"ok": True, "id": rec["id"], "name": fm.get("task_name") or rec["id"],
376
- "status": fm.get("task_status") or "active",
377
- "condition": _field_line(rec["content"], "生效条件"),
378
- "goal": _field_line(rec["content"], "子功能"),
379
- "note": _exec_note(rec["content"]),
380
- "acceptance": _field_line(rec["content"], "验证方式"),
381
- "boundary": _field_line(rec["content"], "不适用条件"),
382
- "plan": sec.get(SEC_PLAN, ""),
383
- "changes": sec.get(SEC_CHANGE, ""),
384
- "result": sec.get(SEC_RESULT, ""),
385
- "tags": list(fm.get("tags") or []),
386
- "created_at": fm.get("task_created_at"),
387
- "updated_at": fm.get("task_updated_at"),
388
- "path": rec["path"]}
389
-
390
-
391
- # 生效条件:status 经 `str(status or "").strip().lower()` 为非空且不在 TASK_STATUSES 时返回 ok=False 未知状态;否则收集 cg.index["nodes"] 中各 _entry 可读任务、按 updated_at 倒序,status 非空时再按该状态过滤,total 为过滤后截断前的条数,limit 为真值时取前 max(int(limit), 1) 条——limit 为 None、0 或空串(假值)时不截断而返回全量 filtered 列表。
392
- def list_tasks(cg, status: str = None, limit: int = None) -> dict:
393
- """任务清单(按 updated_at 倒序);status 可选 active/blocked/done/dropped。"""
394
- st = str(status or "").strip().lower() or None
395
- if st and st not in TASK_STATUSES:
396
- return {"ok": False, "error": "未知任务状态:%r(可选 %s)" % (st, list(TASK_STATUSES))}
397
- items = []
398
- for nid in list((cg.index.get("nodes") or {}).keys()):
399
- t = _entry(cg, nid)
400
- if t:
401
- items.append(t)
402
- items.sort(key=lambda x: -(x.get("updated_at") or 0))
403
- if st:
404
- items = [t for t in items if t["status"] == st]
405
- total = len(items)
406
- if limit:
407
- items = items[:max(int(limit), 1)]
408
- return {"ok": True, "count": len(items), "total": total, "tasks": items}
409
-
410
-
411
- # 生效条件:遍历 cg.index["nodes"],把 _entry 可读且 status 为 active/blocked 的归入 active、status 为 done 的归入 done,各自按 updated_at 倒序;返回 active[:max(int(active_limit), 0)] 与 done[:max(int(done_limit), 0)](active_limit/done_limit 传 0 或负数时对应列表为空)以及两组截断前的 active_total/done_total。
412
- def session_tasks(cg, active_limit: int = 5, done_limit: int = 5) -> dict:
413
- """会话装配用:进行中(active/blocked)+ 近期完成(done,按 updated_at 倒序)。
414
-
415
- 这是第 ① 点(忘记已实现的工程)的机制解:上下文装配面在会话开始时把
416
- 「还没做完的」与「刚做完的」一起带出来,agent 不必先想到去查。
417
- """
418
- active, done = [], []
419
- for nid in list((cg.index.get("nodes") or {}).keys()):
420
- t = _entry(cg, nid)
421
- if not t:
422
- continue
423
- if t["status"] in ("active", "blocked"):
424
- active.append(t)
425
- elif t["status"] == "done":
426
- done.append(t)
427
- # 终键 id:同 updated_at 并列时定序,否则顺序回落到 cg.index["nodes"]
428
- # 的物理序(增量路径=写入序,重建路径=nid 序)。
429
- key = lambda x: (-(x.get("updated_at") or 0), # noqa: E731
430
- str(x.get("id") or ""))
431
- active.sort(key=key)
432
- done.sort(key=key)
433
- return {"ok": True,
434
- "active": active[:max(int(active_limit), 0)],
435
- "done": done[:max(int(done_limit), 0)],
436
- "active_total": len(active), "done_total": len(done)}
437
-
438
-
439
- # 生效条件:name 经 task_node_id 得 nid,exists 为同 id 任务卡是否被 _read_task 命中;k 经 `int(k or 5)` 再 max(…, 1)(k 为 None/0/空串回落 5,负数取 1)得到 kk,cg.search 抛异常时按无结果处理,只保留元素长度 ≥3、frontmatter.tags 含 TASK_TAG 且 node.id != nid 的条目,返回 out[:kk] 与 note。
440
- def find_similar(cg, name: str, k: int = 5) -> dict:
441
- """同族任务提示(**只提示,不自动合并**——是否同一任务由调用方裁决)。
442
-
443
- 身份判据是命名而非相似度:这里只回答「已经有一张同名的卡吗」与
444
- 「还有哪些卡看起来像」。相似度只能产生候选,不能授予「同一任务」的资格。
445
- """
446
- nid = task_node_id(name)
447
- exact = _read_task(cg, nid) is not None
448
- kk = max(int(k or 5), 1)
449
- res = []
450
- try:
451
- res, _meta = cg.search(name, layer="structural", k=kk + (1 if exact else 0),
452
- record=False)
453
- except Exception: # noqa: BLE001
454
- res = []
455
- out = []
456
- for item in res or []:
457
- if not (isinstance(item, (tuple, list)) and len(item) >= 3):
458
- continue
459
- node, score, qual = item[0], item[1], item[2]
460
- fm = (node.get("frontmatter") or {}) if isinstance(node, dict) else {}
461
- if TASK_TAG not in (fm.get("tags") or []):
462
- continue
463
- if node.get("id") == nid:
464
- continue
465
- out.append({"id": node.get("id"),
466
- "name": fm.get("task_name") or node.get("id"),
467
- "status": fm.get("task_status"),
468
- "score": round(float(score), 4),
469
- "qualification": (qual or {}).get("state") if isinstance(qual, dict) else None})
470
- return {"ok": True, "node_id": nid, "exists": exact, "similar": out[:kk],
1
+ """结构层任务实体 —— structural 层正式业务写入口(2026-09-16)。
2
+
3
+ 使用者裁定(按既有记忆分层,三层分工):
4
+
5
+ 任务 → structural 层(与「协议/自我/信任」同级;
6
+ 跨会话稳定、不可遗忘——`DEMOTE_CONFIDENCE`
7
+ 的降级面只覆盖 knowledge 层)
8
+ 执行任务的中间信息 → contextual 层(不落库,由 AI 上下文自理)
9
+ 任务知识 / 外部参考 / 交接文档 → knowledge 层(被验证的重要信息,按需调用;
10
+ 节点 tags 记 `task:<slug>` 实现反向关联)
11
+
12
+ 消除 AI 失忆四症状:
13
+
14
+ ① 忘记已实现的工程 → `session_tasks()` 供 `session_recall` 装配
15
+ 「进行中 + 近期完成」,新会话开机即见
16
+ ② 计划与实际不符 → `plan_add()` 累积「计划变更」节,偏差可追溯
17
+ ③ 缺核验 → 状态迁 `done` 时「结果」节必填(缺一不收,
18
+ 对齐 `branches.BRANCH_MARKS` 的同款闸)
19
+ ④ 换表述即新任务 → 身份判据 = 语义命名 slug(**刻意不用内容哈希**);
20
+ `find_similar()` 只提示疑似同族,不自动合并
21
+
22
+ 为什么身份不用内容哈希:`add_goal` 的 `goal_<sha1(text)>` 正是第 ④ 点的病根——
23
+ 同一目标换个说法就变成新 goal、重复开工。任务名是使用者给的稳定标识,
24
+ 重复登记必须更新原卡而不是再开一张。
25
+
26
+ 与 goals 的关系:**不双写**。goals 是「检索定向槽」(谁该被检索到),任务是
27
+ 「工程台账」(做到哪一步、结果是什么)——语义不同,双写必漂移(既有实证:
28
+ 覆盖写会让库正文回退首版)。会话装配面各自成段。
29
+
30
+ 与 branches 同哲学:库层函数收 `cg`;写盘走 `cg.add`(`require_layer_write`
31
+ 权限闸自动生效,库层不绕闸);生命周期事件走 `cg._audit`(不新造日志格式,
32
+ 审计失败绝不阻断主流程)。
33
+
34
+ 正文格式 = CCG 六要素 + 三节(计划 / 计划变更 / 结果):
35
+
36
+ # 功能名:<任务名>
37
+ # 生效条件:<任务适用范围;填「无条件」则豁免资格判定中的正条件确认>
38
+ # 子功能:<任务目标>
39
+ # 执行:<状态>|<进度说明>
40
+ # 验证方式:<验收判据>
41
+ # 不适用条件:<边界>
42
+
43
+ ## 计划
44
+ ## 计划变更
45
+ ## 结果
46
+ """
47
+ from __future__ import annotations
48
+
49
+ import re
50
+ import time
51
+
52
+ TASK_STATUSES = ("active", "blocked", "done", "dropped")
53
+
54
+ #: 状态中文投影(只用于正文可读性,机械判据始终用英文值)
55
+ STATUS_ZH = {"active": "进行中", "blocked": "受阻", "done": "完成", "dropped": "放弃"}
56
+
57
+ #: 任务节点统一标签(第一个是身份标记,第二个是任务族标记)
58
+ TASK_TAG = "task"
59
+ TASK_PREFIX = "task_"
60
+
61
+ #: ≥0.7 会被 protect 自动打「不可遗忘」标记——即「任务结果必须得到维护」的
62
+ #: 架构层保障:普通 forget 搬不走它,只有显式解保护才动得了。
63
+ DEFAULT_IMPORTANCE = 0.8
64
+
65
+ #: 正文节名
66
+ SEC_PLAN = "计划"
67
+ SEC_CHANGE = "计划变更"
68
+ SEC_RESULT = "结果"
69
+
70
+ #: 语义 slug 守卫——首字符须为中文/字母/数字,其后允许 `_ . -`,总长 ≤64。
71
+ #: 与 `branches._BRANCH_RE` 同风格(防路径穿越与非法文件名),但放宽到 Unicode:
72
+ #: 任务名多为中文,缩到 ASCII 会逼使用者起英文别名,反而制造第二个身份。
73
+ _SLUG_RE = re.compile(r"^[0-9A-Za-z\u4e00-\u9fff][0-9A-Za-z\u4e00-\u9fff_.-]{0,63}$")
74
+ _ILLEGAL_RE = re.compile(r"[^0-9A-Za-z\u4e00-\u9fff_.-]+")
75
+
76
+ # ---------------------------------------------------------------- 命名与解析
77
+
78
+ # 生效条件:name 为 None/空串或 strip 后为空时返回空串,否则把 `/`、`\`、`..` 及 _ILLEGAL_RE 命中字符折叠为 `-`、压缩连续 `-` 并去首尾 `-.` 后取前 64 字符再去首尾 `-.` 返回;
79
+ def slugify(name: str) -> str:
80
+ """任务名 → 语义 slug(稳定标识;同 slug 即同任务)。
81
+
82
+ 只做「可安全落文件名」的归一:路径分隔符与非法字符折叠为 `-`,连续 `-`
83
+ 压成一个。**不做语义改写**(不翻译、不去停用词)——slug 是对外可见的
84
+ 身份,擅自改写会让使用者按原名检索时对不上号。
85
+ """
86
+ s = (name or "").strip()
87
+ if not s:
88
+ return ""
89
+ s = s.replace("/", "-").replace("\\", "-").replace("..", "-")
90
+ s = _ILLEGAL_RE.sub("-", s)
91
+ s = re.sub(r"-{2,}", "-", s).strip("-.")
92
+ return s[:64].strip("-.")
93
+
94
+
95
+ # 生效条件:name(`name or ""` 后 strip)先剥掉已有的 TASK_PREFIX 再 slugify,结果非空且被 _SLUG_RE.match 命中时返回 TASK_PREFIX + slug;name 为假值或归一化后为空、不匹配时返回 ""。
96
+ def task_node_id(name: str) -> str:
97
+ """任务名或节点 id → 规范节点 id(`task_<slug>`)。非法名返回空串。"""
98
+ s = (name or "").strip()
99
+ if s.startswith(TASK_PREFIX):
100
+ s = s[len(TASK_PREFIX):]
101
+ slug = slugify(s)
102
+ if not slug or not _SLUG_RE.match(slug):
103
+ return ""
104
+ return TASK_PREFIX + slug
105
+
106
+
107
+ # 生效条件:v 为 None 或 str(v).strip() 为 "" 时返回 False,否则返回 True。
108
+ def _has(v) -> bool:
109
+ """「本次调用是否提供了该字段」——空串/None 一律视为未提供(不静默清空)。"""
110
+ return v is not None and str(v).strip() != ""
111
+
112
+
113
+ # 生效条件:无必需形参且无模块级常量约束,恒返回 time.strftime("%Y-%m-%d %H:%M") 的当前时间文本。
114
+ def _today() -> str:
115
+ return time.strftime("%Y-%m-%d %H:%M")
116
+
117
+
118
+ # 生效条件:content(`content or ""` 后 splitlines)中有某行 strip 后以 `# ` + field + `:` 开头时,返回该行该前缀之后的去空白内容;content 为假值或无此匹配行时返回 ""。
119
+ def _field_line(content: str, field: str) -> str:
120
+ """从正文抽 `# <字段>:` 行的值(与 `mdcos._ccg_field` 同源口径,
121
+ 本模块不反向 import mdcos,避免包内循环依赖)。"""
122
+ pre = "# " + field + ":"
123
+ for line in (content or "").splitlines():
124
+ s = line.strip()
125
+ if s.startswith(pre):
126
+ return s[len(pre):].strip()
127
+ return ""
128
+
129
+
130
+ # 生效条件:(content or "") 的行中 strip 后以 "## " 开头者成为节名 s[3:].strip() 并切换当前节,其余行累入当前节,返回各节内容以 "\n" join 后 strip 的字典;无任何标题行时仅返回 {"__body__": 全篇 strip};content 为 None/空串时返回 {"__body__": ""}。
131
+ def sections(content: str) -> dict:
132
+ """正文 → `{节名: 节内容}`;无标题部分归入 `__body__`。"""
133
+ out: dict = {"__body__": []}
134
+ cur = "__body__"
135
+ for line in (content or "").splitlines():
136
+ s = line.strip()
137
+ if s.startswith("## "):
138
+ cur = s[3:].strip()
139
+ out.setdefault(cur, [])
140
+ continue
141
+ out.setdefault(cur, []).append(line)
142
+ return {k: "\n".join(v).strip() for k, v in out.items()}
143
+
144
+
145
+ # 生效条件:t = (text or "").strip(),t 为 ""/"(无)"/"(未填)" 时返回 "",否则 len(t) <= n(默认 200)时返回 t,超出时返回 t[:n].rstrip() + "…"。
146
+ def _brief(text: str, n: int = 200) -> str:
147
+ t = (text or "").strip()
148
+ if t in ("", "(无)", "(未填)"):
149
+ return ""
150
+ return t if len(t) <= n else t[:n].rstrip() + "…"
151
+
152
+
153
+ # 生效条件:以 (old_text or "").strip() 为 base(base 为「(无)」或「(未填)」时置空),_has(change) 判定为假时返回 base or "(无)",为真时拼出 `- [今日] change.strip()`,base 非空返回 base+"\n"+该行再 strip,base 为空只返回该行;
154
+ def _append_change(old_text: str, change: str) -> str:
155
+ """「计划变更」节追加一行(累积式,不覆盖历史)。"""
156
+ base = (old_text or "").strip()
157
+ if base in ("(无)", "(未填)"):
158
+ base = ""
159
+ if not _has(change):
160
+ return base or "(无)"
161
+ line = "- [%s] %s" % (_today(), str(change).strip())
162
+ return (base + "\n" + line).strip() if base else line
163
+
164
+
165
+ # 生效条件:name 为必需形参(`name or ""` 后 strip 填 `# 功能名:` 行);condition、goal、acceptance、boundary、plan、changes、result 各经 _has 判定,未提供时分别落「无条件」「(未填:任务目标待补)」「other」「任务转 done/dropped 终态后不再作为进行中任务参与装配」「(未填)」「(无)」与空串;status 经 STATUS_ZH.get(status, status) 映射、未命中时原样输出,note 经 _has 为真时以 `|` 拼在执行行后。
166
+ def render(name: str, *, condition: str = "", goal: str = "", status: str = "active",
167
+ note: str = "", acceptance: str = "", boundary: str = "",
168
+ plan: str = "", changes: str = "", result: str = "") -> str:
169
+ """按 CCG 六要素 + 三节渲染任务正文。"""
170
+ exec_line = STATUS_ZH.get(status, status)
171
+ if _has(note):
172
+ exec_line = exec_line + "|" + str(note).strip()
173
+ return "\n".join([
174
+ "# 功能名:%s" % (name or "").strip(),
175
+ "# 生效条件:%s" % (condition.strip() if _has(condition) else "无条件"),
176
+ "# 子功能:%s" % (goal.strip() if _has(goal) else "(未填:任务目标待补)"),
177
+ "# 执行:%s" % exec_line,
178
+ "# 验证方式:%s" % (acceptance.strip() if _has(acceptance) else "other"),
179
+ "# 不适用条件:%s" % (boundary.strip() if _has(boundary)
180
+ else "任务转 done/dropped 终态后不再作为进行中任务参与装配"),
181
+ "",
182
+ "## %s" % SEC_PLAN,
183
+ (plan.strip() if _has(plan) else "(未填)"),
184
+ "",
185
+ "## %s" % SEC_CHANGE,
186
+ (changes.strip() if _has(changes) else "(无)"),
187
+ "",
188
+ "## %s" % SEC_RESULT,
189
+ (result.strip() if _has(result) else ""),
190
+ "",
191
+ ])
192
+
193
+
194
+ # ---------------------------------------------------------------- 读写
195
+
196
+ # 生效条件:nid 为假值(None/空串)时返回 None;否则 cg.get(nid) 命中且其 frontmatter.layer == "structural"、TASK_TAG 在 frontmatter.tags(`or []`)中时,返回 {id, fm, content, path, sec}(sec 为 sections(content));记录缺失或层/标签不符时返回 None。
197
+ def _read_task(cg, nid: str):
198
+ """读回任务卡;层或标签不符一律视为不存在(防串号:别的节点占用了同 id)。"""
199
+ if not nid:
200
+ return None
201
+ rec = cg.get(nid)
202
+ if not rec:
203
+ return None
204
+ fm = rec.get("frontmatter") or {}
205
+ if fm.get("layer") != "structural" or TASK_TAG not in (fm.get("tags") or []):
206
+ return None
207
+ content = rec.get("content") or ""
208
+ return {"id": nid, "fm": fm, "content": content,
209
+ "path": rec.get("path"), "sec": sections(content)}
210
+
211
+
212
+ # 生效条件:以 layer="structural"、tags=list(tags)、importance=float(importance)、override=True、task_name=name、task_status=status、task_updated_at=time.time()、actor=actor or "task" 等构成 kw,extra 为真值时经 kw.update(extra) 追加覆盖,随后调用 cg.add(nid, content, **kw) 并返回其结果;
213
+ def _write(cg, nid: str, name: str, content: str, status: str, tags,
214
+ importance: float, actor, extra: dict = None):
215
+ """唯一写盘点——走 `cg.add`(权限闸 / 归属注入全部复用既有链路)。
216
+
217
+ `override=True` 的含义:任务更新是**系统自身的幂等更新**(同 slug 即同任务),
218
+ 显式声明该意图后,若节点已带 `protected`/`self_state` 标记,protect 会走
219
+ 「旧版本快照 + 审计留痕」的放行分支而不是静默改写——任务台账天然获得版本快照。
220
+ """
221
+ kw = dict(layer="structural", tags=list(tags), importance=float(importance),
222
+ confidence=1.0, verification_basis="other",
223
+ override=True, consistency=False,
224
+ task_name=name, task_status=status,
225
+ task_updated_at=time.time(), actor=actor or "task")
226
+ if extra:
227
+ kw.update(extra)
228
+ return cg.add(nid, content, **kw)
229
+
230
+
231
+ # 生效条件:cg 具有 _audit 属性(getattr(cg, "_audit", None) 非 None)时以 (op, nid, **meta) 调用它,且其中抛出的任何异常被吞掉;cg 无该属性时不调用,两种路径均不返回内容。
232
+ def _audit(cg, op: str, nid: str, **meta) -> None:
233
+ """生命周期留痕;审计失败绝不阻断主流程(与 branches._audit 同哲学)。"""
234
+ a = getattr(cg, "_audit", None)
235
+ if a is None:
236
+ return
237
+ try:
238
+ a(op, nid, **meta)
239
+ except Exception: # noqa: BLE001
240
+ pass
241
+
242
+
243
+ # 生效条件:_read_task(cg, nid) 命中任务卡时返回摘要条目——id 取 rec['id'],name 取 fm.task_name 或(缺失/假值时)rec['id'],status 取 fm.task_status 或 "active",plan/changes/result 取对应节的 _brief 摘要(缺节回落 ""),并带 created_at/updated_at/path;未命中时返回 None。
244
+ def _entry(cg, nid: str):
245
+ """节点 id → 对外任务条目(摘要形态;全字段查 `get_task`)。"""
246
+ rec = _read_task(cg, nid)
247
+ if not rec:
248
+ return None
249
+ fm, sec = rec["fm"], rec["sec"]
250
+ return {"id": rec["id"],
251
+ "name": fm.get("task_name") or rec["id"],
252
+ "status": fm.get("task_status") or "active",
253
+ "plan": _brief(sec.get(SEC_PLAN, "")),
254
+ "changes": _brief(sec.get(SEC_CHANGE, "")),
255
+ "result": _brief(sec.get(SEC_RESULT, "")),
256
+ "created_at": fm.get("task_created_at"),
257
+ "updated_at": fm.get("task_updated_at"),
258
+ "path": rec["path"]}
259
+
260
+
261
+ # ---------------------------------------------------------------- 写操作
262
+
263
+ # 生效条件:slugify(name) 为空串或不匹配 _SLUG_RE 时返回 {'ok': False, 含 slug 的非法名 error};否则 st = str(status if _has(status) else (旧卡 task_status or "active")).strip().lower(),st 不在 TASK_STATUSES 时返回未知状态错误,st == "done" 且合并后结果节 _has(new_res) 为假时返回「转 done 必须填结果」拒收,其余情况渲染写入并返回按 old 是否为 None 区分新建/更新的 out。
264
+ def upsert(cg, name: str, *, plan: str = None, status: str = None,
265
+ result: str = None, condition: str = None, goal: str = None,
266
+ acceptance: str = None, boundary: str = None, change: str = None,
267
+ note: str = None, tags=None, importance: float = None,
268
+ actor: str = None) -> dict:
269
+ """登记或更新一张任务卡(同 slug 即同任务;重复登记不新建卡)。
270
+
271
+ 未提供的字段**沿用旧值**(不静默清空);`status="done"` 且合并后「结果」节
272
+ 仍为空 → 拒收,盘上内容不变。
273
+
274
+ :param note: 进度说明,落 `# 执行:` 行(`|` 之后)。
275
+ :param change: 本次发现的新问题/计划偏差,追加到「计划变更」节。
276
+ """
277
+ slug = slugify(name)
278
+ if not slug or not _SLUG_RE.match(slug):
279
+ return {"ok": False,
280
+ "error": "任务名非法(slug=%r);要求:中文/字母/数字起头,"
281
+ "仅含中文/字母/数字/_ . -,长度 ≤64" % (slug,)}
282
+
283
+ nid = TASK_PREFIX + slug
284
+ old = _read_task(cg, nid)
285
+ prev_fm = old["fm"] if old else {}
286
+ prev = old["sec"] if old else {}
287
+
288
+ display = (name or "").strip() or prev_fm.get("task_name") or slug
289
+ st = str(status if _has(status) else (prev_fm.get("task_status") or "active")).strip().lower()
290
+ if st not in TASK_STATUSES:
291
+ return {"ok": False, "node_id": nid,
292
+ "error": "未知任务状态:%r(可选 %s)" % (st, list(TASK_STATUSES))}
293
+
294
+ # 未提供 → 沿用旧值(结果与计划绝不被静默清空)
295
+ new_plan = plan if _has(plan) else prev.get(SEC_PLAN, "")
296
+ new_res = result if _has(result) else prev.get(SEC_RESULT, "")
297
+ new_cond = condition if _has(condition) else _field_line(old["content"], "生效条件") if old else ""
298
+ new_goal = goal if _has(goal) else _field_line(old["content"], "子功能") if old else ""
299
+ new_acc = acceptance if _has(acceptance) else _field_line(old["content"], "验证方式") if old else ""
300
+ new_bnd = boundary if _has(boundary) else _field_line(old["content"], "不适用条件") if old else ""
301
+ new_note = note if _has(note) else _exec_note(old["content"]) if old else ""
302
+ new_changes = _append_change(prev.get(SEC_CHANGE, ""), change)
303
+
304
+ if st == "done" and not _has(new_res):
305
+ return {"ok": False, "node_id": nid,
306
+ "error": "任务转 done 必须填写「结果」——结果不可缺失(缺一不收)",
307
+ "hint": "补 result 后重试;盘上内容未变更"}
308
+
309
+ content = render(display, condition=new_cond, goal=new_goal, status=st,
310
+ note=new_note, acceptance=new_acc, boundary=new_bnd,
311
+ plan=new_plan, changes=new_changes, result=new_res)
312
+ tg = list(dict.fromkeys([TASK_TAG, "%s:%s" % (TASK_TAG, slug)]
313
+ + [str(t) for t in (tags or []) if str(t).strip()]))
314
+ imp = DEFAULT_IMPORTANCE if importance is None else float(importance)
315
+ created = prev_fm.get("task_created_at") or time.time()
316
+ _write(cg, nid, display, content, st, tg, imp, actor,
317
+ extra={"task_created_at": created, "task_slug": slug})
318
+ _audit(cg, "task_open" if old is None else "task_update", nid,
319
+ status=st, slug=slug, name=display)
320
+ out = {"ok": True, "node_id": nid, "name": display, "status": st,
321
+ "created": old is None,
322
+ "result_present": _has(new_res),
323
+ "hint": "新建任务卡" if old is None else "已更新原卡(同 slug 即同任务)"}
324
+ if slug != (name or "").strip():
325
+ # 身份被归一化了就必须说出来——否则调用方按原名去找会找不到卡
326
+ out["slug_normalized"] = slug
327
+ return out
328
+
329
+
330
+ # 生效条件:content 中 `# 执行:` 行的值含 "|" 时返回第一个 "|" 之后去空白的内容;content 为假值、无该行或该行值不含 "|" 时返回 ""。
331
+ def _exec_note(content: str) -> str:
332
+ """从 `# 执行:` 行取 `|` 之后的进度说明。"""
333
+ val = _field_line(content, "执行")
334
+ return val.split("|", 1)[1].strip() if "|" in val else ""
335
+
336
+
337
+ # 生效条件:node_id 经 task_node_id 得到非空 nid 且 _read_task 命中该卡时,以旧卡 task_name(缺失/假值回落 nid)、status、result、note、actor 转调 upsert 并返回其结果;node_id 非法时返回 ok=False「非法任务标识」,卡不存在时返回 ok=False「任务不存在」。
338
+ def set_status(cg, node_id: str, status: str, *, result: str = None,
339
+ note: str = None, actor: str = None) -> dict:
340
+ """任务状态迁移(进行中/受阻/完成/放弃)。迁 done 且无结果 → 拒收。"""
341
+ nid = task_node_id(node_id)
342
+ if not nid:
343
+ return {"ok": False, "error": "非法任务标识:%r" % (node_id,)}
344
+ old = _read_task(cg, nid)
345
+ if not old:
346
+ return {"ok": False, "node_id": nid, "error": "任务不存在:%s" % nid}
347
+ return upsert(cg, old["fm"].get("task_name") or nid, status=status,
348
+ result=result, note=note, actor=actor)
349
+
350
+
351
+ # 生效条件:change 经 _has 判定为已提供、且 node_id 经 task_node_id 非空、_read_task 命中该卡时,以旧卡 task_name(缺失/假值回落 nid)与 change、actor 转调 upsert;change 为 None/空串/纯空白时返回 ok=False「计划变更内容为空」,node_id 非法或卡不存在时返回 ok=False 对应错误。
352
+ def plan_add(cg, node_id: str, change: str, *, actor: str = None) -> dict:
353
+ """计划变更追加——执行中发现的错误/新问题/偏差,累积进「计划变更」节。"""
354
+ if not _has(change):
355
+ return {"ok": False, "error": "计划变更内容为空"}
356
+ nid = task_node_id(node_id)
357
+ if not nid:
358
+ return {"ok": False, "error": "非法任务标识:%r" % (node_id,)}
359
+ old = _read_task(cg, nid)
360
+ if not old:
361
+ return {"ok": False, "node_id": nid, "error": "任务不存在:%s" % nid}
362
+ return upsert(cg, old["fm"].get("task_name") or nid, change=change, actor=actor)
363
+
364
+
365
+ # ---------------------------------------------------------------- 读操作
366
+
367
+ # 生效条件:node_id 经 task_node_id 得到的 nid 对应一张 _read_task 命中的任务卡时返回 ok=True 的全字段(condition/goal/acceptance/boundary 由 _field_line 抽取、note 由 _exec_note 抽取、plan/changes/result 取节原文不截断、tags 取 fm.tags 或 []);否则返回 ok=False,error 中的标识取 nid 或原 node_id。
368
+ def get_task(cg, node_id: str) -> dict:
369
+ """单卡全字段读回(计划/变更/结果不截断)。"""
370
+ nid = task_node_id(node_id)
371
+ rec = _read_task(cg, nid)
372
+ if not rec:
373
+ return {"ok": False, "error": "任务不存在:%s" % (nid or node_id)}
374
+ fm, sec = rec["fm"], rec["sec"]
375
+ return {"ok": True, "id": rec["id"], "name": fm.get("task_name") or rec["id"],
376
+ "status": fm.get("task_status") or "active",
377
+ "condition": _field_line(rec["content"], "生效条件"),
378
+ "goal": _field_line(rec["content"], "子功能"),
379
+ "note": _exec_note(rec["content"]),
380
+ "acceptance": _field_line(rec["content"], "验证方式"),
381
+ "boundary": _field_line(rec["content"], "不适用条件"),
382
+ "plan": sec.get(SEC_PLAN, ""),
383
+ "changes": sec.get(SEC_CHANGE, ""),
384
+ "result": sec.get(SEC_RESULT, ""),
385
+ "tags": list(fm.get("tags") or []),
386
+ "created_at": fm.get("task_created_at"),
387
+ "updated_at": fm.get("task_updated_at"),
388
+ "path": rec["path"]}
389
+
390
+
391
+ # 生效条件:status 经 `str(status or "").strip().lower()` 为非空且不在 TASK_STATUSES 时返回 ok=False 未知状态;否则收集 cg.index["nodes"] 中各 _entry 可读任务、按 updated_at 倒序,status 非空时再按该状态过滤,total 为过滤后截断前的条数,limit 为真值时取前 max(int(limit), 1) 条——limit 为 None、0 或空串(假值)时不截断而返回全量 filtered 列表。
392
+ def list_tasks(cg, status: str = None, limit: int = None) -> dict:
393
+ """任务清单(按 updated_at 倒序);status 可选 active/blocked/done/dropped。"""
394
+ st = str(status or "").strip().lower() or None
395
+ if st and st not in TASK_STATUSES:
396
+ return {"ok": False, "error": "未知任务状态:%r(可选 %s)" % (st, list(TASK_STATUSES))}
397
+ items = []
398
+ for nid in list((cg.index.get("nodes") or {}).keys()):
399
+ t = _entry(cg, nid)
400
+ if t:
401
+ items.append(t)
402
+ items.sort(key=lambda x: -(x.get("updated_at") or 0))
403
+ if st:
404
+ items = [t for t in items if t["status"] == st]
405
+ total = len(items)
406
+ if limit:
407
+ items = items[:max(int(limit), 1)]
408
+ return {"ok": True, "count": len(items), "total": total, "tasks": items}
409
+
410
+
411
+ # 生效条件:遍历 cg.index["nodes"],把 _entry 可读且 status 为 active/blocked 的归入 active、status 为 done 的归入 done,各自按 updated_at 倒序;返回 active[:max(int(active_limit), 0)] 与 done[:max(int(done_limit), 0)](active_limit/done_limit 传 0 或负数时对应列表为空)以及两组截断前的 active_total/done_total。
412
+ def session_tasks(cg, active_limit: int = 5, done_limit: int = 5) -> dict:
413
+ """会话装配用:进行中(active/blocked)+ 近期完成(done,按 updated_at 倒序)。
414
+
415
+ 这是第 ① 点(忘记已实现的工程)的机制解:上下文装配面在会话开始时把
416
+ 「还没做完的」与「刚做完的」一起带出来,agent 不必先想到去查。
417
+ """
418
+ active, done = [], []
419
+ for nid in list((cg.index.get("nodes") or {}).keys()):
420
+ t = _entry(cg, nid)
421
+ if not t:
422
+ continue
423
+ if t["status"] in ("active", "blocked"):
424
+ active.append(t)
425
+ elif t["status"] == "done":
426
+ done.append(t)
427
+ # 终键 id:同 updated_at 并列时定序,否则顺序回落到 cg.index["nodes"]
428
+ # 的物理序(增量路径=写入序,重建路径=nid 序)。
429
+ key = lambda x: (-(x.get("updated_at") or 0), # noqa: E731
430
+ str(x.get("id") or ""))
431
+ active.sort(key=key)
432
+ done.sort(key=key)
433
+ return {"ok": True,
434
+ "active": active[:max(int(active_limit), 0)],
435
+ "done": done[:max(int(done_limit), 0)],
436
+ "active_total": len(active), "done_total": len(done)}
437
+
438
+
439
+ # 生效条件:name 经 task_node_id 得 nid,exists 为同 id 任务卡是否被 _read_task 命中;k 经 `int(k or 5)` 再 max(…, 1)(k 为 None/0/空串回落 5,负数取 1)得到 kk,cg.search 抛异常时按无结果处理,只保留元素长度 ≥3、frontmatter.tags 含 TASK_TAG 且 node.id != nid 的条目,返回 out[:kk] 与 note。
440
+ def find_similar(cg, name: str, k: int = 5) -> dict:
441
+ """同族任务提示(**只提示,不自动合并**——是否同一任务由调用方裁决)。
442
+
443
+ 身份判据是命名而非相似度:这里只回答「已经有一张同名的卡吗」与
444
+ 「还有哪些卡看起来像」。相似度只能产生候选,不能授予「同一任务」的资格。
445
+ """
446
+ nid = task_node_id(name)
447
+ exact = _read_task(cg, nid) is not None
448
+ kk = max(int(k or 5), 1)
449
+ res = []
450
+ try:
451
+ res, _meta = cg.search(name, layer="structural", k=kk + (1 if exact else 0),
452
+ record=False)
453
+ except Exception: # noqa: BLE001
454
+ res = []
455
+ out = []
456
+ for item in res or []:
457
+ if not (isinstance(item, (tuple, list)) and len(item) >= 3):
458
+ continue
459
+ node, score, qual = item[0], item[1], item[2]
460
+ fm = (node.get("frontmatter") or {}) if isinstance(node, dict) else {}
461
+ if TASK_TAG not in (fm.get("tags") or []):
462
+ continue
463
+ if node.get("id") == nid:
464
+ continue
465
+ out.append({"id": node.get("id"),
466
+ "name": fm.get("task_name") or node.get("id"),
467
+ "status": fm.get("task_status"),
468
+ "score": round(float(score), 4),
469
+ "qualification": (qual or {}).get("state") if isinstance(qual, dict) else None})
470
+ return {"ok": True, "node_id": nid, "exists": exact, "similar": out[:kk],
471
471
  "note": "仅提示疑似同族任务,不自动合并——终裁权在调用方"}