@furongjun1999/dsh-memory 0.4.11 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (580) hide show
  1. package/README.md +552 -465
  2. package/codebuddy/CODEBUDDY.md +11 -3
  3. package/codebuddy/README.md +92 -90
  4. package/codebuddy/mcp.json +27 -27
  5. package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -169
  6. package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -144
  7. package/docs/README.md +143 -111
  8. package/docs/discipline/harnesses.yaml +244 -226
  9. package/docs/discipline/templates/full.md.tmpl +61 -61
  10. package/docs/discipline/templates/rules.mdc.tmpl +68 -0
  11. package/docs/discipline/templates/skill.md.tmpl +23 -23
  12. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md +299 -299
  13. package/docs/eval/AGI/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md +239 -239
  14. package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
  15. package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
  16. package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
  17. package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
  18. package/docs/eval//345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md +174 -174
  19. package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
  20. package/docs/eval//346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md +223 -223
  21. package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
  22. package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
  23. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
  24. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
  25. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
  26. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
  27. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
  28. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
  29. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
  30. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
  31. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
  32. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
  33. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
  34. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
  35. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
  36. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
  37. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
  38. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
  39. package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
  40. package/docs/{mdcg → hive}//344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md +165 -160
  41. package/docs/hive//344/273/244/347/211/214/350/257/255/344/271/211/344/277/256/346/255/243_/344/273/262/350/243/201/344/275/215_v0.1.md +76 -0
  42. package/docs/{mdcg → hive}//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.5.md +236 -236
  43. package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
  44. package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -43
  45. package/docs/hive//346/243/200/347/264/242/347/256/227/346/263/225/345/217/243/345/276/204/345/257/271/347/205/247_v0.1.md +85 -0
  46. package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +102 -102
  47. package/docs/hive//350/234/202/345/267/242M6_ingest/345/256/236/346/226/275/350/256/241/345/210/222_v0.1.md +47 -0
  48. package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +519 -503
  49. package/docs/hive//350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +85 -85
  50. package/docs/hive//350/234/202/345/267/242/350/256/276/350/256/241_/347/220/206/350/256/272/345/257/271/351/275/220_v0.1.md +191 -0
  51. package/docs/hive//350/234/202/345/267/242/350/277/255/344/273/243_/345/256/217/350/247/202/344/270/216/347/276/244/344/275/223/350/260/203/345/272/246_v0.1.md +90 -0
  52. package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
  53. package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
  54. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -216
  55. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.10.md +646 -646
  56. package/docs/mdcg/lingshu_tutorial.html +14449 -14449
  57. package/docs/mdcg/release_v0.4.11.md +49 -0
  58. package/docs/mdcg/release_v0.4.5.md +55 -55
  59. package/docs/mdcg/tool_table_v0.3.0.md +117 -117
  60. package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -600
  61. package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
  62. package/docs/mdcg//345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +275 -275
  63. package/docs/mdcg//345/217/221/345/270/203/351/227/250/347/246/201/351/223/276_v0.1.md +54 -0
  64. package/docs/mdcg//346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +130 -130
  65. package/docs/mdcg//347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md +278 -278
  66. package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +172 -172
  67. package/docs/mdcg//347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +270 -270
  68. package/docs/mdcg//350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +491 -491
  69. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md +340 -340
  70. package/docs/mdcg//350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +588 -588
  71. package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -176
  72. package/docs/plans//345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +81 -81
  73. package/docs/swarm//350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +704 -704
  74. package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -242
  75. package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
  76. package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -129
  77. package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -134
  78. package/docs/theory//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  79. package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -145
  80. package/docs/theory//347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +144 -144
  81. package/docs/theory//347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +198 -198
  82. package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -221
  83. package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -237
  84. package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
  85. package/docs//345/217/221/345/270/203/344/273/266/345/233/236/346/272/257/350/257/264/346/230/216_v0.1.md +82 -82
  86. package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +28 -2
  87. package/dsh/README.md +82 -82
  88. package/dsh/cordis.yml.example +139 -139
  89. package/dsh/update-lingshu.bat +11 -11
  90. package/lib/bridge.d.ts +9 -0
  91. package/lib/bridge.js +35 -0
  92. package/lib/hooks.js +36 -2
  93. package/lib/index.js +7 -1
  94. package/lib/lib/roleplay_web.js +116 -29
  95. package/lib/lib/token_store.d.ts +7 -1
  96. package/lib/lib/token_store.js +12 -3
  97. package/md_cg/__init__.py +7 -7
  98. package/md_cg/audit.py +379 -368
  99. package/md_cg/autonomy.py +287 -287
  100. package/md_cg/backfill.py +1328 -1327
  101. package/md_cg/backfill_bigdomain.py +34 -34
  102. package/md_cg/backfill_bucket_zh.py +35 -0
  103. package/md_cg/bench6_arms.py +410 -410
  104. package/md_cg/bench6_common.py +230 -230
  105. package/md_cg/bench6_competitors.py +212 -212
  106. package/md_cg/bench_axis_domain.py +257 -257
  107. package/md_cg/bench_blind_comp.py +308 -308
  108. package/md_cg/bench_e2e_judge.py +532 -0
  109. package/md_cg/bench_e2e_locomo_qa.py +368 -0
  110. package/md_cg/bench_e2e_qa.py +256 -0
  111. package/md_cg/bench_en_atoms_public.py +230 -230
  112. package/md_cg/bench_governance.py +348 -348
  113. package/md_cg/bench_lme_zh.py +410 -410
  114. package/md_cg/bench_locomo.py +121 -121
  115. package/md_cg/bench_locomo_zh.py +450 -450
  116. package/md_cg/bench_locomo_zh_public.py +147 -147
  117. package/md_cg/bench_longmem.py +112 -112
  118. package/md_cg/bench_membench.py +632 -632
  119. package/md_cg/bench_p0.py +149 -149
  120. package/md_cg/bench_progressive.py +287 -287
  121. package/md_cg/bench_role_views.py +238 -238
  122. package/md_cg/bench_task_ab.py +243 -243
  123. package/md_cg/bench_task_ab_llm.py +408 -408
  124. package/md_cg/bench_unified_en.py +204 -204
  125. package/md_cg/bench_zh_mad.py +601 -601
  126. package/md_cg/blindspot_tickets.py +123 -123
  127. package/md_cg/branches.py +301 -285
  128. package/md_cg/build_postings.py +73 -73
  129. package/md_cg/ccgc.py +1006 -948
  130. package/md_cg/census.py +132 -132
  131. package/md_cg/chain.py +315 -300
  132. package/md_cg/codeindex.py +531 -531
  133. package/md_cg/coldverify.py +292 -292
  134. package/md_cg/comment_gate.py +337 -337
  135. package/md_cg/cond_compose.py +190 -190
  136. package/md_cg/cond_facts.py +154 -154
  137. package/md_cg/cond_template.json +106 -106
  138. package/md_cg/condition_anchor.py +142 -142
  139. package/md_cg/conformance.py +726 -726
  140. package/md_cg/consistency.py +717 -717
  141. package/md_cg/consolidate.py +1537 -1439
  142. package/md_cg/corpus.py +110 -110
  143. package/md_cg/crosscheck.py +1098 -1097
  144. package/md_cg/crypto.py +3 -1
  145. package/md_cg/d_meta.py +310 -310
  146. package/md_cg/datapath.py +78 -18
  147. package/md_cg/docindex.py +473 -473
  148. package/md_cg/eval_common.py +575 -575
  149. package/md_cg/evidence.py +4 -2
  150. package/md_cg/evolution.py +477 -477
  151. package/md_cg/export.py +222 -220
  152. package/md_cg/forgetting.py +581 -581
  153. package/md_cg/fsutil.py +377 -329
  154. package/md_cg/hotcache.py +48 -7
  155. package/md_cg/hyperedge.py +251 -251
  156. package/md_cg/identity.py +390 -390
  157. package/md_cg/insight.py +500 -500
  158. package/md_cg/interop.py +338 -0
  159. package/md_cg/judgment_manifest.py +177 -0
  160. package/md_cg/lexicon/build_cedict_en_zh.py +329 -329
  161. package/md_cg/lexicon/build_standard_en.py +171 -171
  162. package/md_cg/lexicon/expand_en_zh.py +211 -211
  163. package/md_cg/lifecycle.py +272 -272
  164. package/md_cg/linkref.py +280 -280
  165. package/md_cg/links.py +140 -107
  166. package/md_cg/mcp_server.py +403 -55
  167. package/md_cg/md_whitebox.py +345 -345
  168. package/md_cg/mdcg.py +783 -233
  169. package/md_cg/mdcos.py +500 -70
  170. package/md_cg/metacognition.py +591 -591
  171. package/md_cg/migrate.py +119 -119
  172. package/md_cg/migrate_aeis.py +221 -221
  173. package/md_cg/migrate_roleplay.py +293 -293
  174. package/md_cg/migrate_wisdom_graph.py +360 -360
  175. package/md_cg/mreview/__init__.py +25 -25
  176. package/md_cg/mreview/__main__.py +110 -110
  177. package/md_cg/mreview/bundle.py +178 -178
  178. package/md_cg/mreview/candidates.py +262 -262
  179. package/md_cg/mreview/govern.py +694 -693
  180. package/md_cg/mreview/locate.py +939 -939
  181. package/md_cg/mreview/pipeline.py +728 -728
  182. package/md_cg/mreview/rules/duplication.json +21 -21
  183. package/md_cg/mreview/rules/field_coverage.json +54 -54
  184. package/md_cg/mreview/rules/source_license.json +21 -21
  185. package/md_cg/mreview/rules/template_flow.json +21 -21
  186. package/md_cg/mreview/ruleset.py +252 -252
  187. package/md_cg/nodefile.py +575 -575
  188. package/md_cg/pooling.py +484 -472
  189. package/md_cg/postings.py +300 -298
  190. package/md_cg/predict.py +1100 -1100
  191. package/md_cg/progressive.py +123 -123
  192. package/md_cg/protect.py +272 -272
  193. package/md_cg/protocol/md_cg_gate.proto +33 -33
  194. package/md_cg/protocol.py +372 -372
  195. package/md_cg/provenance.py +582 -582
  196. package/md_cg/reach.py +453 -453
  197. package/md_cg/readcache.py +143 -0
  198. package/md_cg/reconcile.py +228 -0
  199. package/md_cg/refindex.py +833 -833
  200. package/md_cg/refine.py +604 -604
  201. package/md_cg/review_cli.py +170 -0
  202. package/md_cg/roleviews.py +89 -89
  203. package/md_cg/routing.py +393 -365
  204. package/md_cg/run_tests.py +211 -0
  205. package/md_cg/scrub.py +13 -3
  206. package/md_cg/security.py +128 -18
  207. package/md_cg/self_state.py +1029 -1029
  208. package/md_cg/selfreport.py +152 -151
  209. package/md_cg/semantic/__init__.py +10 -10
  210. package/md_cg/semantic/canonical.py +122 -122
  211. package/md_cg/semantic/en_normalizer.py +364 -364
  212. package/md_cg/semantic/en_zh_map.json +28694 -0
  213. package/md_cg/semantic/export_en_zh_map.py +64 -0
  214. package/md_cg/semantic/unify.py +45 -0
  215. package/md_cg/semantic/zh_en_atoms.py +139 -139
  216. package/md_cg/signer.py +7 -4
  217. package/md_cg/sources.py +816 -582
  218. package/md_cg/statushdr.py +179 -179
  219. package/md_cg/stg.py +54 -37
  220. package/md_cg/subgraph.py +729 -729
  221. package/md_cg/sustain.py +35 -5
  222. package/md_cg/tasks.py +470 -470
  223. package/md_cg/test_access_hints.py +147 -0
  224. package/md_cg/test_action_derive.py +203 -203
  225. package/md_cg/test_audit_rotate.py +270 -270
  226. package/md_cg/test_autonomy.py +143 -143
  227. package/md_cg/test_bench_governance.py +102 -102
  228. package/md_cg/test_blindspot_tickets.py +166 -166
  229. package/md_cg/test_branch_discard_tombstone.py +136 -0
  230. package/md_cg/test_branches.py +13 -3
  231. package/md_cg/test_ccg_perturb.py +184 -184
  232. package/md_cg/test_ccgc.py +433 -433
  233. package/md_cg/test_census_prune.py +81 -81
  234. package/md_cg/test_chain_read_isolate.py +168 -0
  235. package/md_cg/test_cond_compose_anchors.py +76 -76
  236. package/md_cg/test_cond_match.py +165 -165
  237. package/md_cg/test_condition_anchor.py +81 -81
  238. package/md_cg/test_d_meta.py +412 -412
  239. package/md_cg/test_datapath_device_name.py +203 -0
  240. package/md_cg/test_datapath_root.py +199 -199
  241. package/md_cg/test_emit_negtail_cache.py +156 -0
  242. package/md_cg/test_en_pipeline.py +22 -2
  243. package/md_cg/test_gain_gate.py +212 -212
  244. package/md_cg/test_govern_directread.py +421 -0
  245. package/md_cg/test_health_scale.py +173 -173
  246. package/md_cg/test_hive_ingest.py +285 -0
  247. package/md_cg/test_hot_cold.py +215 -215
  248. package/md_cg/test_hyperedge.py +245 -245
  249. package/md_cg/test_i26_empty_first_write.py +116 -0
  250. package/md_cg/test_i27_e041_identity.py +128 -0
  251. package/md_cg/test_i28_hotcache_prodpath.py +122 -0
  252. package/md_cg/test_i32_hotcache_env_key.py +218 -0
  253. package/md_cg/test_identity_attribution.py +96 -15
  254. package/md_cg/test_index_durability.py +17 -3
  255. package/md_cg/test_interop.py +95 -0
  256. package/md_cg/test_interop_judgment.py +228 -0
  257. package/md_cg/test_issue39_utf8_stdio.py +273 -0
  258. package/md_cg/test_lifecycle.py +309 -309
  259. package/md_cg/test_linkref.py +306 -306
  260. package/md_cg/test_links_concurrent_write.py +188 -0
  261. package/md_cg/test_lock.py +43 -43
  262. package/md_cg/test_md_access_parity.py +255 -255
  263. package/md_cg/test_md_writepath.py +345 -345
  264. package/md_cg/test_mdstore_search_parity.py +160 -0
  265. package/md_cg/test_merge_upsert.py +168 -0
  266. package/md_cg/test_mr_m2.py +587 -587
  267. package/md_cg/test_mr_m3.py +710 -710
  268. package/md_cg/test_mr_m4.py +485 -485
  269. package/md_cg/test_n123_derive_expiry_chain.py +205 -0
  270. package/md_cg/test_n130_verify_falsified_protect.py +185 -0
  271. package/md_cg/test_n131_merge_gate.py +205 -0
  272. package/md_cg/test_p0.py +250 -250
  273. package/md_cg/test_p1.py +316 -316
  274. package/md_cg/test_p10_identity.py +173 -173
  275. package/md_cg/test_p11_consistency.py +233 -233
  276. package/md_cg/test_p12_metacognition.py +212 -212
  277. package/md_cg/test_p13_encryption.py +241 -241
  278. package/md_cg/test_p14_sustain.py +249 -249
  279. package/md_cg/test_p15_scrub.py +280 -280
  280. package/md_cg/test_p16_self_state.py +301 -301
  281. package/md_cg/test_p17_predict.py +354 -354
  282. package/md_cg/test_p18_whitebox.py +171 -171
  283. package/md_cg/test_p19_migrate_roleplay.py +149 -149
  284. package/md_cg/test_p1x_ref_root.py +160 -0
  285. package/md_cg/test_p20_evolution.py +315 -315
  286. package/md_cg/test_p21_tokens.py +293 -270
  287. package/md_cg/test_p22_theory.py +175 -175
  288. package/md_cg/test_p23_links.py +311 -311
  289. package/md_cg/test_p24_evidence.py +227 -227
  290. package/md_cg/test_p25_weights.py +156 -156
  291. package/md_cg/test_p26_refindex.py +416 -416
  292. package/md_cg/test_p27_docindex.py +16 -7
  293. package/md_cg/test_p28_refcheck.py +305 -305
  294. package/md_cg/test_p29_session_ingest_export.py +354 -333
  295. package/md_cg/test_p2_mcp.py +3 -0
  296. package/md_cg/test_p3.py +11 -2
  297. package/md_cg/test_p30_maintain.py +330 -330
  298. package/md_cg/test_p31_insight.py +534 -534
  299. package/md_cg/test_p32_backfill.py +7 -1
  300. package/md_cg/test_p33_ccg_wiring.py +293 -293
  301. package/md_cg/test_p34_crosscheck.py +331 -331
  302. package/md_cg/test_p35_conditioned_claim.py +252 -252
  303. package/md_cg/test_p36_kp_align.py +230 -230
  304. package/md_cg/test_p37_condition_space.py +248 -248
  305. package/md_cg/test_p38_concurrent_flush.py +102 -0
  306. package/md_cg/test_p38_contextualize.py +273 -273
  307. package/md_cg/test_p39_verify_flow.py +153 -0
  308. package/md_cg/test_p39_vision_evidence.py +369 -369
  309. package/md_cg/test_p40_refine_worklist.py +241 -241
  310. package/md_cg/test_p41_evolve_patrol.py +224 -224
  311. package/md_cg/test_p42_provenance.py +269 -269
  312. package/md_cg/test_p43_pooling.py +412 -398
  313. package/md_cg/test_p44_md_whitebox.py +231 -231
  314. package/md_cg/test_p45_session_identity.py +219 -219
  315. package/md_cg/test_p46_unit_scope.py +272 -272
  316. package/md_cg/test_p47_session_view.py +316 -0
  317. package/md_cg/test_p4_fuzzy.py +223 -223
  318. package/md_cg/test_p5_semantic.py +226 -226
  319. package/md_cg/test_p6_consolidate.py +440 -387
  320. package/md_cg/test_p7_goals_recent.py +202 -202
  321. package/md_cg/test_p8_subgraph_chain.py +200 -200
  322. package/md_cg/test_p9_forget_protect.py +231 -231
  323. package/md_cg/test_predict_beta.py +135 -135
  324. package/md_cg/test_preflight_failclosed.py +100 -100
  325. package/md_cg/test_progressive.py +146 -146
  326. package/md_cg/test_propose_tail_index.py +157 -0
  327. package/md_cg/test_protocol.py +243 -243
  328. package/md_cg/test_reach.py +378 -378
  329. package/md_cg/test_reach_keys.py +201 -201
  330. package/md_cg/test_read_clip.py +141 -141
  331. package/md_cg/test_read_scope_b27.py +277 -0
  332. package/md_cg/test_readcache_default_on.py +168 -0
  333. package/md_cg/test_readcache_precise_inval.py +270 -0
  334. package/md_cg/test_readcache_prodpath.py +203 -0
  335. package/md_cg/test_reconcile_v0.py +294 -0
  336. package/md_cg/test_retr_gates_prodpath.py +140 -0
  337. package/md_cg/test_retr_s1.py +344 -340
  338. package/md_cg/test_retr_s1b.py +276 -209
  339. package/md_cg/test_retr_s3.py +194 -194
  340. package/md_cg/test_retr_s4.py +163 -163
  341. package/md_cg/test_retr_s5.py +200 -200
  342. package/md_cg/test_retr_s6.py +157 -157
  343. package/md_cg/test_retr_s7.py +392 -384
  344. package/md_cg/test_retr_s8_time.py +369 -316
  345. package/md_cg/test_retr_s9_edges.py +286 -286
  346. package/md_cg/test_retr_s9_entity_ctx.py +9 -3
  347. package/md_cg/test_retr_score_once.py +208 -0
  348. package/md_cg/test_review_conformance.py +367 -367
  349. package/md_cg/test_review_onepass.py +170 -0
  350. package/md_cg/test_role_views.py +354 -354
  351. package/md_cg/test_rrf_graph_seed_cache.py +154 -0
  352. package/md_cg/test_security_audit.py +155 -0
  353. package/md_cg/test_security_audit_b26.py +161 -0
  354. package/md_cg/test_security_audit_v21.py +250 -0
  355. package/md_cg/test_sem_noise.py +242 -242
  356. package/md_cg/test_semantic_canonical.py +16 -2
  357. package/md_cg/test_session_isolation.py +168 -0
  358. package/md_cg/test_snapshot_autoclose.py +187 -0
  359. package/md_cg/test_subproc_encoding.py +192 -192
  360. package/md_cg/test_sustain_mutual.py +153 -153
  361. package/md_cg/test_tail_watermark_race.py +208 -0
  362. package/md_cg/test_tasks.py +409 -409
  363. package/md_cg/test_tenant_env_override_warn.py +139 -0
  364. package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
  365. package/md_cg/test_tool_face.py +189 -189
  366. package/md_cg/test_transfer.py +180 -180
  367. package/md_cg/test_trust.py +361 -361
  368. package/md_cg/test_twophase.py +286 -286
  369. package/md_cg/test_v14_fixes.py +38 -20
  370. package/md_cg/test_validity_filter.py +280 -280
  371. package/md_cg/test_verify_answer.py +138 -138
  372. package/md_cg/test_verify_dirty_reconcile.py +157 -0
  373. package/md_cg/test_wisdom_md_store.py +292 -292
  374. package/md_cg/test_writelimit.py +197 -197
  375. package/md_cg/test_writepipe.py +214 -214
  376. package/md_cg/theory.py +6 -3
  377. package/md_cg/tokens.py +85 -14
  378. package/md_cg/tool_face.py +260 -260
  379. package/md_cg/trust.py +986 -950
  380. package/md_cg/twophase.py +231 -231
  381. package/md_cg/units.py +668 -667
  382. package/md_cg/vision_evidence.py +667 -666
  383. package/md_cg/weights.py +624 -624
  384. package/md_cg/whitebox.py +527 -527
  385. package/md_cg/whitebox_kb/__init__.py +37 -37
  386. package/md_cg/whitebox_kb/aeis_core/__init__.py +42 -42
  387. package/md_cg/whitebox_kb/aeis_core/semantic.py +280 -280
  388. package/md_cg/whitebox_kb/aeis_core/textutil.py +13 -13
  389. package/md_cg/whitebox_kb/engine.py +310 -310
  390. package/md_cg/whitebox_kb/seed_knowledge//346/231/272/350/203/275/350/256/2723.4.md +5260 -5260
  391. package/md_cg/whitebox_kb/wisdom/browser_units.py +2631 -2631
  392. package/md_cg/whitebox_kb/wisdom/causal_discover.py +432 -432
  393. package/md_cg/whitebox_kb/wisdom/chat_engine.py +1506 -1506
  394. package/md_cg/whitebox_kb/wisdom/compiler_code_units.py +3033 -3033
  395. package/md_cg/whitebox_kb/wisdom/condition_algebra.py +112 -112
  396. package/md_cg/whitebox_kb/wisdom/condition_frame.py +315 -315
  397. package/md_cg/whitebox_kb/wisdom/condition_kb.py +108 -108
  398. package/md_cg/whitebox_kb/wisdom/conflict_map.json +4445 -4445
  399. package/md_cg/whitebox_kb/wisdom/core/lexer.py +512 -512
  400. package/md_cg/whitebox_kb/wisdom/core/name_checker.py +1023 -1023
  401. package/md_cg/whitebox_kb/wisdom/cspmn.py +258 -258
  402. package/md_cg/whitebox_kb/wisdom/csre.py +264 -264
  403. package/md_cg/whitebox_kb/wisdom/danmaku_audit.py +252 -252
  404. package/md_cg/whitebox_kb/wisdom/distilled_condition_units.json +6417 -6417
  405. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902b.json +1950 -1950
  406. package/md_cg/whitebox_kb/wisdom/docs/WB-EVAL-20260902c.json +1296 -1296
  407. package/md_cg/whitebox_kb/wisdom/docs/whitebox_capability_graph_demo.json +59 -59
  408. package/md_cg/whitebox_kb/wisdom/graph_db_units.py +3089 -3089
  409. package/md_cg/whitebox_kb/wisdom/knowledge_points.py +318 -318
  410. package/md_cg/whitebox_kb/wisdom/md_access.py +470 -470
  411. package/md_cg/whitebox_kb/wisdom/md_store.py +276 -251
  412. package/md_cg/whitebox_kb/wisdom/migrate_wisdom.py +476 -476
  413. package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
  414. package/md_cg/whitebox_kb/wisdom/navigate.py +248 -248
  415. package/md_cg/whitebox_kb/wisdom/neural_retrieve.py +224 -224
  416. package/md_cg/whitebox_kb/wisdom/os_units.py +2735 -2735
  417. package/md_cg/whitebox_kb/wisdom/pattern_separation.py +376 -376
  418. package/md_cg/whitebox_kb/wisdom/prereq_map.json +364 -364
  419. package/md_cg/whitebox_kb/wisdom/python_code_units.py +2766 -2766
  420. package/md_cg/whitebox_kb/wisdom/role_solidified.json +7 -7
  421. package/md_cg/whitebox_kb/wisdom/route_memory.py +244 -244
  422. package/md_cg/whitebox_kb/wisdom/scene_reconstruction.py +148 -148
  423. package/md_cg/whitebox_kb/wisdom/snr_report.json +40 -40
  424. package/md_cg/whitebox_kb/wisdom/test_code_compose_domains.py +7541 -7541
  425. package/md_cg/whitebox_kb/wisdom/test_compiler_self_bootstrap.py +354 -354
  426. package/md_cg/whitebox_kb/wisdom/test_ecosystem_assembly.py +158 -158
  427. package/md_cg/whitebox_kb/wisdom/test_ecosystem_demos.py +193 -193
  428. package/md_cg/whitebox_kb/wisdom/test_graph_db.py +292 -292
  429. package/md_cg/whitebox_kb/wisdom/test_python_self_bootstrap.py +160 -160
  430. package/md_cg/whitebox_kb/wisdom/trigger_words_index.json +4366 -4366
  431. package/md_cg/whitebox_kb/wisdom/verifier.py +1297 -1297
  432. package/md_cg/writelimit.py +356 -356
  433. package/md_cg/writepipe.py +20 -8
  434. package/package.json +101 -96
  435. package/skills/plugin.json +54 -54
  436. package/skills/skills/designer-perspective/SKILL.md +158 -158
  437. package/skills/skills/designer-perspective/references/01-observation-position.md +66 -66
  438. package/skills/skills/designer-perspective/references/02-structure-recognition.md +62 -62
  439. package/skills/skills/designer-perspective/references/03-direction-judgment.md +55 -55
  440. package/skills/skills/designer-perspective/references/04-qualification-verdict.md +72 -72
  441. package/skills/skills/designer-perspective/references/05-condition-attribution.md +74 -74
  442. package/skills/skills/designer-perspective/scripts/designer.py +545 -545
  443. package/skills/skills/designer-perspective/tests/cases.jsonl +17 -17
  444. package/skills/skills/designer-perspective/tests/selftest.py +61 -61
  445. package/skills/skills/lingshu-browser/SKILL.md +60 -60
  446. package/skills/skills/lingshu-compiler/SKILL.md +56 -56
  447. package/skills/skills/lingshu-compiler/units/analyze-type-infer/SKILL.md +45 -45
  448. package/skills/skills/lingshu-compiler/units/check-name-real/SKILL.md +45 -45
  449. package/skills/skills/lingshu-compiler/units/compile-assign/SKILL.md +45 -45
  450. package/skills/skills/lingshu-compiler/units/compile-expr-tree/SKILL.md +45 -45
  451. package/skills/skills/lingshu-compiler/units/compile-full-pipeline/SKILL.md +45 -45
  452. package/skills/skills/lingshu-compiler/units/compile-func-def/SKILL.md +45 -45
  453. package/skills/skills/lingshu-compiler/units/compile-if-then/SKILL.md +45 -45
  454. package/skills/skills/lingshu-compiler/units/compile-logic-expr/SKILL.md +45 -45
  455. package/skills/skills/lingshu-compiler/units/compile-recursive/SKILL.md +45 -45
  456. package/skills/skills/lingshu-compiler/units/compile-scope/SKILL.md +45 -45
  457. package/skills/skills/lingshu-compiler/units/compile-type-check/SKILL.md +45 -45
  458. package/skills/skills/lingshu-compiler/units/compile-while/SKILL.md +45 -45
  459. package/skills/skills/lingshu-compiler/units/compiler-0010c4bf/SKILL.md +45 -45
  460. package/skills/skills/lingshu-compiler/units/compiler-0355bffb/SKILL.md +45 -45
  461. package/skills/skills/lingshu-compiler/units/compiler-0361708a/SKILL.md +45 -45
  462. package/skills/skills/lingshu-compiler/units/compiler-054a0414/SKILL.md +45 -45
  463. package/skills/skills/lingshu-compiler/units/compiler-05a1691a/SKILL.md +45 -45
  464. package/skills/skills/lingshu-compiler/units/compiler-05eeed1e/SKILL.md +45 -45
  465. package/skills/skills/lingshu-compiler/units/compiler-0622a1f6/SKILL.md +45 -45
  466. package/skills/skills/lingshu-compiler/units/compiler-08b54217/SKILL.md +45 -45
  467. package/skills/skills/lingshu-compiler/units/compiler-0a62b70c/SKILL.md +45 -45
  468. package/skills/skills/lingshu-compiler/units/compiler-0ab24d00/SKILL.md +45 -45
  469. package/skills/skills/lingshu-compiler/units/compiler-0e093688/SKILL.md +45 -45
  470. package/skills/skills/lingshu-compiler/units/compiler-0e82b966/SKILL.md +45 -45
  471. package/skills/skills/lingshu-compiler/units/compiler-0ee9b9b9/SKILL.md +45 -45
  472. package/skills/skills/lingshu-compiler/units/compiler-0f3787e6/SKILL.md +45 -45
  473. package/skills/skills/lingshu-compiler/units/compiler-1028685f/SKILL.md +45 -45
  474. package/skills/skills/lingshu-compiler/units/compiler-11897630/SKILL.md +45 -45
  475. package/skills/skills/lingshu-compiler/units/compiler-16661b9b/SKILL.md +45 -45
  476. package/skills/skills/lingshu-compiler/units/compiler-1f722303/SKILL.md +45 -45
  477. package/skills/skills/lingshu-compiler/units/compiler-25be1262/SKILL.md +45 -45
  478. package/skills/skills/lingshu-compiler/units/compiler-2a76ba07/SKILL.md +45 -45
  479. package/skills/skills/lingshu-compiler/units/compiler-2dbea54a/SKILL.md +45 -45
  480. package/skills/skills/lingshu-compiler/units/compiler-2df52f16/SKILL.md +45 -45
  481. package/skills/skills/lingshu-compiler/units/compiler-2ec2c9d2/SKILL.md +45 -45
  482. package/skills/skills/lingshu-compiler/units/compiler-2f8c8f39/SKILL.md +45 -45
  483. package/skills/skills/lingshu-compiler/units/compiler-38378ac7/SKILL.md +45 -45
  484. package/skills/skills/lingshu-compiler/units/compiler-39457d2e/SKILL.md +45 -45
  485. package/skills/skills/lingshu-compiler/units/compiler-39fb5926/SKILL.md +45 -45
  486. package/skills/skills/lingshu-compiler/units/compiler-47f4fbfa/SKILL.md +45 -45
  487. package/skills/skills/lingshu-compiler/units/compiler-4a8cd1f1/SKILL.md +45 -45
  488. package/skills/skills/lingshu-compiler/units/compiler-4b230b6b/SKILL.md +45 -45
  489. package/skills/skills/lingshu-compiler/units/compiler-4cbbda95/SKILL.md +45 -45
  490. package/skills/skills/lingshu-compiler/units/compiler-4d5a68ff/SKILL.md +45 -45
  491. package/skills/skills/lingshu-compiler/units/compiler-56dc9bdd/SKILL.md +45 -45
  492. package/skills/skills/lingshu-compiler/units/compiler-57e76ebe/SKILL.md +45 -45
  493. package/skills/skills/lingshu-compiler/units/compiler-61ff016b/SKILL.md +45 -45
  494. package/skills/skills/lingshu-compiler/units/compiler-63eae588/SKILL.md +45 -45
  495. package/skills/skills/lingshu-compiler/units/compiler-64c4224b/SKILL.md +45 -45
  496. package/skills/skills/lingshu-compiler/units/compiler-66377955/SKILL.md +45 -45
  497. package/skills/skills/lingshu-compiler/units/compiler-674ab4f6/SKILL.md +45 -45
  498. package/skills/skills/lingshu-compiler/units/compiler-6af76fd6/SKILL.md +45 -45
  499. package/skills/skills/lingshu-compiler/units/compiler-6d979db2/SKILL.md +45 -45
  500. package/skills/skills/lingshu-compiler/units/compiler-6de326c0/SKILL.md +45 -45
  501. package/skills/skills/lingshu-compiler/units/compiler-6f1c0eea/SKILL.md +45 -45
  502. package/skills/skills/lingshu-compiler/units/compiler-724d6c9b/SKILL.md +45 -45
  503. package/skills/skills/lingshu-compiler/units/compiler-7690177f/SKILL.md +45 -45
  504. package/skills/skills/lingshu-compiler/units/compiler-7b229a7d/SKILL.md +45 -45
  505. package/skills/skills/lingshu-compiler/units/compiler-7f471b3a/SKILL.md +45 -45
  506. package/skills/skills/lingshu-compiler/units/compiler-815cad08/SKILL.md +45 -45
  507. package/skills/skills/lingshu-compiler/units/compiler-83c61634/SKILL.md +45 -45
  508. package/skills/skills/lingshu-compiler/units/compiler-8c798c81/SKILL.md +45 -45
  509. package/skills/skills/lingshu-compiler/units/compiler-8dd747ac/SKILL.md +45 -45
  510. package/skills/skills/lingshu-compiler/units/compiler-90324d0a/SKILL.md +45 -45
  511. package/skills/skills/lingshu-compiler/units/compiler-94b8d72d/SKILL.md +45 -45
  512. package/skills/skills/lingshu-compiler/units/compiler-94f12231/SKILL.md +45 -45
  513. package/skills/skills/lingshu-compiler/units/compiler-95937c16/SKILL.md +45 -45
  514. package/skills/skills/lingshu-compiler/units/compiler-98a5625b/SKILL.md +45 -45
  515. package/skills/skills/lingshu-compiler/units/compiler-98b4c42f/SKILL.md +45 -45
  516. package/skills/skills/lingshu-compiler/units/compiler-98e3894a/SKILL.md +45 -45
  517. package/skills/skills/lingshu-compiler/units/compiler-9bdfe4b8/SKILL.md +45 -45
  518. package/skills/skills/lingshu-compiler/units/compiler-9d9b4e83/SKILL.md +45 -45
  519. package/skills/skills/lingshu-compiler/units/compiler-9f7be5ad/SKILL.md +45 -45
  520. package/skills/skills/lingshu-compiler/units/compiler-a6daf076/SKILL.md +45 -45
  521. package/skills/skills/lingshu-compiler/units/compiler-a7995a0c/SKILL.md +45 -45
  522. package/skills/skills/lingshu-compiler/units/compiler-a8399248/SKILL.md +45 -45
  523. package/skills/skills/lingshu-compiler/units/compiler-aabbd099/SKILL.md +45 -45
  524. package/skills/skills/lingshu-compiler/units/compiler-afe169d8/SKILL.md +45 -45
  525. package/skills/skills/lingshu-compiler/units/compiler-b0678bda/SKILL.md +45 -45
  526. package/skills/skills/lingshu-compiler/units/compiler-b09bd196/SKILL.md +45 -45
  527. package/skills/skills/lingshu-compiler/units/compiler-b1396e23/SKILL.md +45 -45
  528. package/skills/skills/lingshu-compiler/units/compiler-b9b31ce0/SKILL.md +45 -45
  529. package/skills/skills/lingshu-compiler/units/compiler-c10264a7/SKILL.md +45 -45
  530. package/skills/skills/lingshu-compiler/units/compiler-cb1e8e4b/SKILL.md +45 -45
  531. package/skills/skills/lingshu-compiler/units/compiler-ccafd438/SKILL.md +45 -45
  532. package/skills/skills/lingshu-compiler/units/compiler-cda9c262/SKILL.md +45 -45
  533. package/skills/skills/lingshu-compiler/units/compiler-ce648068/SKILL.md +45 -45
  534. package/skills/skills/lingshu-compiler/units/compiler-cf5776a4/SKILL.md +45 -45
  535. package/skills/skills/lingshu-compiler/units/compiler-d974e5d3/SKILL.md +45 -45
  536. package/skills/skills/lingshu-compiler/units/compiler-e3979fd3/SKILL.md +45 -45
  537. package/skills/skills/lingshu-compiler/units/compiler-eb1cf2b5/SKILL.md +45 -45
  538. package/skills/skills/lingshu-compiler/units/compiler-ecb30d5b/SKILL.md +45 -45
  539. package/skills/skills/lingshu-compiler/units/compiler-f8c8b24b/SKILL.md +45 -45
  540. package/skills/skills/lingshu-compiler/units/compiler-f99fedbe/SKILL.md +45 -45
  541. package/skills/skills/lingshu-compiler/units/compiler-fa8ff5f7/SKILL.md +45 -45
  542. package/skills/skills/lingshu-compiler/units/compiler-fe1b058d/SKILL.md +45 -45
  543. package/skills/skills/lingshu-compiler/units/lex-chinese-program/SKILL.md +45 -45
  544. package/skills/skills/lingshu-compiler/units/lex-dao-de-jing/SKILL.md +45 -45
  545. package/skills/skills/lingshu-compiler/units/lex-nine-chapters/SKILL.md +45 -45
  546. package/skills/skills/lingshu-compiler/units/vm-arithmetic/SKILL.md +45 -45
  547. package/skills/skills/lingshu-compiler/units/vm-array-ops/SKILL.md +45 -45
  548. package/skills/skills/lingshu-compiler/units/vm-closure-call/SKILL.md +45 -45
  549. package/skills/skills/lingshu-compiler/units/vm-closure-create/SKILL.md +45 -45
  550. package/skills/skills/lingshu-compiler/units/vm-compare/SKILL.md +45 -45
  551. package/skills/skills/lingshu-compiler/units/vm-cond-jump/SKILL.md +45 -45
  552. package/skills/skills/lingshu-compiler/units/vm-cond-space/SKILL.md +45 -45
  553. package/skills/skills/lingshu-compiler/units/vm-exception/SKILL.md +45 -45
  554. package/skills/skills/lingshu-compiler/units/vm-func-call/SKILL.md +45 -45
  555. package/skills/skills/lingshu-compiler/units/vm-loop-run/SKILL.md +45 -45
  556. package/skills/skills/lingshu-compiler/units/vm-profiling/SKILL.md +45 -45
  557. package/skills/skills/lingshu-compiler/units/vm-refcount/SKILL.md +45 -45
  558. package/skills/skills/lingshu-compiler/units/vm-run-loop/SKILL.md +45 -45
  559. package/skills/skills/lingshu-compiler/units/vm-short-circuit/SKILL.md +45 -45
  560. package/skills/skills/lingshu-compiler/units/vm-stack-guard/SKILL.md +45 -45
  561. package/skills/skills/lingshu-compiler/units/vm-stack-ops/SKILL.md +45 -45
  562. package/skills/skills/lingshu-compiler/units/vm-trust-accum/SKILL.md +45 -45
  563. package/skills/skills/lingshu-graph/SKILL.md +63 -63
  564. package/skills/skills/lingshu-net/SKILL.md +48 -48
  565. package/skills/skills/lingshu-os/SKILL.md +64 -64
  566. package/skills/skills/lingshu-pylang/SKILL.md +71 -71
  567. package/src/bridge.ts +33 -0
  568. package/src/hooks.ts +38 -2
  569. package/src/index.ts +526 -518
  570. package/src/lib/datapath.ts +326 -326
  571. package/src/lib/mdcg_client.ts +413 -413
  572. package/src/lib/mutual.ts +428 -428
  573. package/src/lib/prompt_safety.ts +62 -62
  574. package/src/lib/python_path.ts +71 -71
  575. package/src/lib/roleplay_web.ts +116 -29
  576. package/src/lib/token_store.ts +13 -3
  577. package/src/tools.ts +212 -212
  578. package/zcode/AGENTS.md +11 -3
  579. package/zcode/README.md +41 -41
  580. /package/docs/{mdcg → hive}//344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md" +0 -0
@@ -1,1297 +1,1297 @@
1
- # -*- coding: utf-8 -*-
2
- """verifier.py · 白箱本地校验器(Zero-LLM Verifier)v1
3
-
4
- 目标:把「白箱写代码 → 外部校验(是否符合规范/能否执行)」全部本地化,
5
- 彻底抛开大模型依赖。重复校验走本地缓存(替代 99.9% 的 LLM 缓存命中)。
6
-
7
- 设计:docs/白箱本地校验器_ZeroLLM_Verifier.md
8
-
9
- 校验链(六层,全部确定性):
10
- ① 校验指纹 → 本地缓存(已校验过 → 直接返回)
11
- ② L1 语法:ast.parse + 结构规则
12
- ③ L2 样例:输入→期望断言运行(物理基底裁决)
13
- ④ L3 边界:额外边界用例
14
- ⑤ 规范符合性:condition_kb 语义对齐 + 结构规范(白箱规则)
15
- ⑥ 集成测试:依赖单元组装 + 回归
16
-
17
- 用法:
18
- python verifier.py --verify "os_112|工作窃取|<code文件路径>" # 校验单个
19
- python verifier.py --cache-stats # 缓存统计
20
- python verifier.py --self-test # 自测
21
- """
22
- from __future__ import annotations
23
-
24
- import ast
25
- import copy
26
- import hashlib
27
- import importlib
28
- import json
29
- import os
30
- import subprocess
31
- import sys
32
- import tempfile
33
- import time
34
- import traceback
35
- import types
36
- from dataclasses import dataclass, field, asdict
37
- from typing import Any, Callable, Dict, List, Optional, Tuple
38
-
39
- HERE = os.path.dirname(os.path.abspath(__file__))
40
- sys.path.insert(0, HERE)
41
-
42
- CACHE_FILE = os.path.join(HERE, "..", "data", "verify_cache.json")
43
- SAVINGS_LOG = os.path.join(HERE, "..", "data", "verify_savings.jsonl")
44
- DATA_DIR = os.path.join(HERE, "..", "data")
45
-
46
-
47
- # ============ 一、数据结构 ============
48
-
49
- def _stable_serialize(obj: Any) -> Any:
50
- """任意类型确定性序列化(缓存键基础)。
51
-
52
- cases 里可能出现 set/bytes/lambda 等 json 不可序列化类型(如集合推导的
53
- {1,2,3} 与 lambda、帧解析的 bytes)——指纹必须对它们稳定:
54
- set/frozenset → 元素 str 排序化(去重保稳定)
55
- bytes/bytearray → hex
56
- callable(lambda/函数)→ __code__ 确定性摘要(co_code+常量+名,跨进程稳定,
57
- 不含内存地址——lambda 的 repr 含地址会导致重启后指纹漂移、缓存失效)
58
- 其余 → repr 回退(域单元 cases 无自定义类实例,足够)。
59
- """
60
- if obj is None or isinstance(obj, (bool, int, float, str)):
61
- return obj
62
- if isinstance(obj, (list, tuple)):
63
- return [_stable_serialize(x) for x in obj]
64
- if isinstance(obj, (set, frozenset)):
65
- return {"__set__": sorted(str(_stable_serialize(x)) for x in obj)}
66
- if isinstance(obj, dict):
67
- return {"__dict__": {str(k): _stable_serialize(v)
68
- for k, v in sorted(obj.items(), key=lambda kv: str(kv[0]))}}
69
- if isinstance(obj, (bytes, bytearray)):
70
- return {"__bytes__": bytes(obj).hex()}
71
- if isinstance(obj, types.CodeType):
72
- # 嵌套 code 对象(lambda 的 co_consts 里可能含 <listcomp>/嵌套 lambda):
73
- # repr 含内存地址(<code object at 0x…>)跨进程漂移——必须确定性摘要
74
- digest = hashlib.sha256(json.dumps(
75
- [obj.co_code.hex(), list(obj.co_consts), list(obj.co_names)],
76
- ensure_ascii=False, sort_keys=True, default=_stable_serialize,
77
- ).encode("utf-8")).hexdigest()[:16]
78
- return {"__code__": digest}
79
- if callable(obj):
80
- co = getattr(obj, "__code__", None)
81
- if co is not None:
82
- digest = hashlib.sha256(json.dumps(
83
- [co.co_code.hex(), list(co.co_consts), list(co.co_names)],
84
- ensure_ascii=False, sort_keys=True, default=_stable_serialize,
85
- ).encode("utf-8")).hexdigest()[:16]
86
- return {"__fn__": digest}
87
- return {"__fn__": repr(obj)}
88
- try:
89
- return {"__obj__": repr(obj)}
90
- except Exception:
91
- return {"__obj__": type(obj).__name__}
92
-
93
-
94
- def _assert_match(got: Any, exp: Any) -> bool:
95
- """结果断言:dict 期望做子集匹配(got 包含 exp 全部键值即可)。
96
-
97
- 白箱单元常返回完整状态字典(如 VM 执行循环返回
98
- {'trust':…, 'symbols':…, 'cond':…, 'stack':…}),而样例期望只关心其中
99
- 关键字段(如 {'trust': 0.8})——完整相等会误判,子集匹配符合「状态断言」语义。
100
- """
101
- if isinstance(exp, dict) and isinstance(got, dict):
102
- return all(got.get(k) == v for k, v in exp.items())
103
- return got == exp
104
-
105
-
106
- @dataclass
107
- class VerifyRequest:
108
- """校验请求:白箱生成的代码 + 单元定义。
109
-
110
- cases/deps 做深拷贝(不可变快照)——被校验代码可能原地修改输入
111
- (如排序 arr[j], arr[j+1] = ...),若引用外部共享 list 会污染原始数据,
112
- 导致相同请求产生不同指纹(缓存失效)。
113
- """
114
- task: str # 任务描述(排序/循环编译/图遍历…)
115
- code: str # 白箱生成的代码
116
- unit_id: str = "" # 单元标识(域+序号,如 os_112)
117
- lang: str = "python" # 语言
118
- cases: List[Tuple[Any, Any]] = field(default_factory=list) # (输入, 期望)
119
- deps: List[str] = field(default_factory=list) # 依赖单元
120
- expected_structure: Dict[str, Any] = field(default_factory=dict) # 规范约束
121
-
122
- def __post_init__(self) -> None:
123
- """深拷贝防污染 + 指纹预计算(外部原地修改会让指纹漂移·缓存错配——P0 纪律)。"""
124
- # 深拷贝可变字段——防止被校验代码原地修改污染外部共享对象
125
- self.cases = copy.deepcopy(self.cases)
126
- self.deps = list(self.deps)
127
- self.expected_structure = dict(self.expected_structure)
128
-
129
- def fingerprint(self) -> str:
130
- """确定性指纹:相同请求 → 相同指纹(缓存键)。
131
-
132
- 先整体经 _stable_serialize 归一化再 dumps——json.dumps 的 default 钩子
133
- 只处理值、不处理 dict 键(tuple 键如事件委托的 {('btn','click'):...}
134
- 直接崩溃),故必须把整个结构(含键)先递归归一化。
135
- """
136
- payload = json.dumps(_stable_serialize({
137
- "task": self.task, "code": self.code, "unit_id": self.unit_id,
138
- "lang": self.lang, "cases": self.cases, "deps": self.deps,
139
- "structure": self.expected_structure,
140
- }), ensure_ascii=False, sort_keys=True)
141
- return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:32]
142
-
143
-
144
- @dataclass
145
- class VerifyResult:
146
- """校验结果。"""
147
- ok: bool
148
- checks: List[Dict[str, Any]] = field(default_factory=list) # [{level, ok, evidence}]
149
- fingerprint: str = ""
150
- cached: bool = False
151
- reason: str = ""
152
- verified_at: str = "" # 校验时间戳(ISO)
153
- version: int = 0 # 校验器规则版本(VERIFIER_VERSION 快照)
154
-
155
- def summary(self) -> str:
156
- parts = [f"✅ 通过" if self.ok else f"❌ 失败"]
157
- for c in self.checks:
158
- mark = "✓" if c["ok"] else "✗"
159
- parts.append(f"{mark} {c['level']}")
160
- if self.reason:
161
- parts.append(f"原因: {self.reason}")
162
- if self.version:
163
- parts.append(f"v{self.version}")
164
- return " | ".join(parts)
165
-
166
-
167
- # ============ 二、本地缓存(替代 LLM 缓存命中) ============
168
-
169
- # 校验器规则版本:L1/L2/L3/规范/集成规则升级时必须 +1——
170
- # 旧版本缓存结果在规则已变的场景下不再可信,强制全部失效重验
171
- # (等价于「协议升级 → 相关缓存刷新」,GLM 建议的 protocol_version 落地)。
172
- # v6(2026-08-31):cases 形态修复(list→tuple)后指纹归一化同值,旧 False
173
- # 条目对修复免疫;bump 版本作废全部旧缓存——长驻进程内存快照复活毒条目的
174
- # 终局解(版本不符整体清空,任何进程加载即重建)。
175
- VERIFIER_VERSION = 10 # v10:结构规范的字面匹配改大小写不敏感——该表是语言无关的
176
- # 任务语义判据(要求去重结构出现),字面取自 Python 命名习惯
177
- # (set/seen),JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确
178
- # 实现判为「未见对应结构」(实测 code-js/去重 假失败)。v9:
179
- # Python 模板(含 {fn} 占位符)先展开占位符再走 Python
180
- # 判据——逐字复用 _render_placeholders(模板不再降级为文本判据,
181
- # R1/R2/R3/R4 全量生效)。v8:非 Python 源码改走文本判据(Rust/JS
182
- # 不再静默跳过假通过)+ `_audit_all` 审计面纳入语言单元表;
183
- # v7:R3/R4 由死代码转为软模式恒执行 + 判据校准(入口/窗口/顶层)
184
- # + evidence 诚实化——判据变更必须 bump,否则旧判定被缓存复用
185
- _CACHE_VERSION_KEY = "_verifier_version"
186
- _STATS_KEY = "_stats"
187
-
188
-
189
- def estimate_tokens(text: str) -> int:
190
- """确定性 token 估算(用于「减少的 LLM 输入」度量,非精确计费)。
191
-
192
- 中文混合内容:中文字符按 1.2 token/字(中文 1 字 ≈ 1-1.5 token),
193
- 其余(代码/英文/标点)按 0.3 token/字符(英文/代码 1 token ≈ 3-4 字符)。
194
- """
195
- cjk = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff')
196
- other = len(text) - cjk
197
- return int(cjk * 1.2 + other * 0.3)
198
-
199
-
200
- def _table_payload_chars() -> int:
201
- """「整张表」内容量:六域单元库定义(pattern+cases)总字符数。
202
-
203
- 设计文档背景:原方案「每次把整张表塞进 LLM 上下文查一次」——
204
- 表 = 白箱单元库(规则/模式/样例),本地化后表不再输入 LLM。
205
- """
206
- from compiler_code_units import COMPILER_UNITS
207
- from python_code_units import PYTHON_UNITS
208
- from graph_db_units import GRAPH_UNITS
209
- from os_units import OS_UNITS
210
- from browser_units import BROWSER_UNITS
211
- from net_units import NET_UNITS
212
- total = 0
213
- for units in (COMPILER_UNITS, PYTHON_UNITS, GRAPH_UNITS,
214
- OS_UNITS, BROWSER_UNITS, NET_UNITS):
215
- for uid, u in units.items():
216
- total += len(uid) + len(u.get("task", "")) + len(u.get("pattern", "")) \
217
- + len(str(u.get("cases", [])))
218
- return total
219
-
220
-
221
- class VerifyCache:
222
- """校验结果本地缓存。相同指纹 → 零计算返回。
223
-
224
- hits/misses 为进程内命中计数;「减少的 LLM 输入」以 append-only JSONL
225
- 审计日志持久化(data/verify_savings.jsonl,每行一次校验的输入度量)——
226
- 跨进程累计、可审计、可重放(复现证据)。
227
- """
228
-
229
- def __init__(self, path: str = CACHE_FILE, version: int = VERIFIER_VERSION,
230
- savings_log: Optional[str] = SAVINGS_LOG):
231
- """组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
232
- self.path = path
233
- self.version = version
234
- self.savings_log = savings_log
235
- self._data: Dict[str, Dict[str, Any]] = {}
236
- self.hits = 0
237
- self.misses = 0
238
- self._table_chars: Optional[int] = None
239
- self._load()
240
-
241
- def _load(self) -> None:
242
- """从磁盘回放缓存快照(版本不符整体作废防脏读)。"""
243
- try:
244
- if os.path.exists(self.path):
245
- with open(self.path, "r", encoding="utf-8") as f:
246
- self._data = json.load(f)
247
- except Exception:
248
- self._data = {}
249
- # 版本不符 → 规则已变,旧缓存结果不可信,清空重建
250
- if self._data.get(_CACHE_VERSION_KEY) != self.version:
251
- self._data = {_CACHE_VERSION_KEY: self.version}
252
- self._flush()
253
-
254
- def record_input(self, payload_chars: int, hit: bool) -> None:
255
- """记录一次经本地校验的输入度量(append-only 审计日志)。
256
-
257
- 每次校验(含缓存命中)都意味着:该内容(表 + 请求)不再输入 LLM →
258
- 省下的 token 计入日志。hit 标记是否为缓存命中(重复校验零计算)。
259
- savings_log=None(隔离模式,如变异测试)时不写日志。
260
- """
261
- if not self.savings_log:
262
- return
263
- table = self._table_chars
264
- if table is None:
265
- try:
266
- table = _table_payload_chars()
267
- except Exception:
268
- table = 0
269
- self._table_chars = table
270
- text = payload_chars + table
271
- half = text // 2
272
- tokens = estimate_tokens("中" * half + "x" * (text - half))
273
- try:
274
- os.makedirs(DATA_DIR, exist_ok=True)
275
- with open(self.savings_log, "a", encoding="utf-8") as f:
276
- f.write(json.dumps({
277
- "t": time.strftime("%Y-%m-%d %H:%M:%S"),
278
- "payload_chars": payload_chars, "table_chars": table,
279
- "saved_chars": text, "saved_tokens": tokens,
280
- "hit": bool(hit),
281
- }, ensure_ascii=False) + "\n")
282
- except Exception:
283
- pass
284
-
285
- def savings(self) -> Dict[str, Any]:
286
- """跨进程累计:从审计日志聚合「减少的 LLM 输入」。
287
-
288
- 读 self.savings_log(与写入同源)——此前误读模块常量 SAVINGS_LOG,
289
- 自定义 savings_log 路径时写入 A 聚合读 B,聚合恒 0(issue #4 审计发现)。
290
- """
291
- total_chars = total_tokens = hits = reqs = 0
292
- log = self.savings_log
293
- try:
294
- if log and os.path.exists(log):
295
- for line in open(log, encoding="utf-8"):
296
- line = line.strip()
297
- if not line:
298
- continue
299
- d = json.loads(line)
300
- total_chars += d.get("saved_chars", 0)
301
- total_tokens += d.get("saved_tokens", 0)
302
- reqs += 1
303
- if d.get("hit"):
304
- hits += 1
305
- except Exception:
306
- pass
307
- return {"requests": reqs, "hits": hits,
308
- "saved_chars": total_chars, "saved_tokens": total_tokens}
309
-
310
- def get(self, fingerprint: str) -> Optional[VerifyResult]:
311
- """按指纹取缓存校验结果:命中计 hits 并标记 cached=True;未命中计 misses 返回 None。"""
312
- entry = self._data.get(fingerprint)
313
- if entry is None:
314
- self.misses += 1
315
- return None
316
- self.hits += 1
317
- r = VerifyResult(ok=entry["ok"], fingerprint=fingerprint, cached=True)
318
- r.checks = entry.get("checks", [])
319
- r.reason = entry.get("reason", "")
320
- r.verified_at = entry.get("verified_at", "")
321
- r.version = entry.get("version", 0)
322
- return r
323
-
324
- def put(self, result: VerifyResult) -> None:
325
- """写入指纹→结果映射并落盘持久化(附版本键防跨版本脏读)。"""
326
- self._data[result.fingerprint] = {
327
- "ok": result.ok,
328
- "checks": result.checks,
329
- "reason": result.reason,
330
- "verified_at": result.verified_at,
331
- "version": result.version,
332
- }
333
- self._data[_CACHE_VERSION_KEY] = self.version
334
- self._flush()
335
-
336
- def _flush(self) -> None:
337
- """内存态刷盘:读-合并-原子替换(多进程并发写友好)。
338
-
339
- bootstrap 循环进程与交互进程共用同一缓存文件——先吸收磁盘上
340
- 他进程新增的条目再写,消除「整文件写覆盖」;临时文件+os.replace
341
- 原子替换,消除「读到半写状态」。残余窗口仅剩同指纹同时写的
342
- 几毫秒,缓存可重建(最坏=丢条目重跑 verify),工程可接受。
343
- """
344
- try:
345
- os.makedirs(DATA_DIR, exist_ok=True)
346
- try:
347
- if os.path.exists(self.path):
348
- with open(self.path, "r", encoding="utf-8") as f:
349
- disk = json.load(f)
350
- # 版本一致性守卫:磁盘条目版本与本实例不同 → 全部忽略
351
- # (版本 bump 即整体作废,不能被合并逻辑复活旧条目)
352
- if isinstance(disk, dict) and disk.get(_CACHE_VERSION_KEY) == self.version:
353
- for k, v in disk.items():
354
- if k not in self._data:
355
- self._data[k] = v
356
- except Exception:
357
- pass # 磁盘半写/损坏 → 以内存为准
358
- tmp = self.path + ".tmp"
359
- with open(tmp, "w", encoding="utf-8") as f:
360
- json.dump(self._data, f, ensure_ascii=False, indent=1)
361
- os.replace(tmp, self.path)
362
- except Exception:
363
- pass
364
-
365
- def stats(self) -> Dict[str, Any]:
366
- """聚合统计:条目总数、通过数、命中率(hits/(hits+misses))。"""
367
- entries = {k: v for k, v in self._data.items() if k != _CACHE_VERSION_KEY}
368
- n = len(entries)
369
- n_pass = sum(1 for v in entries.values() if v.get("ok"))
370
- total_reqs = self.hits + self.misses
371
- hit_rate = round(100.0 * self.hits / total_reqs, 1) if total_reqs else 0.0
372
- return {"total": n, "passed": n_pass, "failed": n - n_pass,
373
- "hits": self.hits, "misses": self.misses,
374
- "hit_rate": hit_rate}
375
-
376
-
377
- # ============ 三、校验器 ============
378
-
379
- #: 唯一真源(codeindex._body_comments)的解析缓存:None=未解析,False=不可用
380
- _CI_BODY = None
381
-
382
-
383
- def _ci_body_comments():
384
- """解析并缓存 `codeindex._body_comments`(body 窗口的唯一真源)。
385
-
386
- 三种加载形态各试一次;全部失败返回 None(调用方走等价兜底)。
387
- 缓存避免每次校验都付一次 import 代价(暖缓存路径要求零额外开销)。
388
- """
389
- global _CI_BODY
390
- if _CI_BODY is None:
391
- fn = False
392
- cands = ["md_cg.codeindex"]
393
- pkg = __package__ or ""
394
- if pkg.count(".") >= 2: # 如 md_cg.whitebox_kb.wisdom
395
- cands.insert(0, pkg.rsplit(".", 1)[0].rsplit(".", 1)[0] + ".codeindex")
396
- for mod in cands:
397
- try:
398
- fn = getattr(importlib.import_module(mod), "_body_comments", False)
399
- except Exception: # noqa: BLE001
400
- continue
401
- if fn:
402
- break
403
- _CI_BODY = fn or False
404
- return _CI_BODY or None
405
-
406
-
407
- class Verifier:
408
- """本地校验器:六层校验链,零 LLM。"""
409
-
410
- def __init__(self, cache: Optional[VerifyCache] = None):
411
- """组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
412
- self.cache = cache or VerifyCache()
413
-
414
- # ---------- 主入口 ----------
415
- def verify(self, req: VerifyRequest) -> VerifyResult:
416
- """六层校验入口:深拷贝请求→算指纹→命中直返;未命中逐层校验并记录 token 节省度量。
417
-
418
- (深拷贝防调用方原地修改污染指纹空间——见 P0 修复记录。)"""
419
- # 不可变快照:verify 内部运行样例可能原地修改输入(如排序
420
- # arr[j], arr[j+1] = ...),必须深拷贝防止污染原始 req 对象
421
- # (否则同一 req 第二次校验指纹变化,缓存失效)
422
- req = copy.deepcopy(req)
423
- fp = req.fingerprint()
424
-
425
- # ① 缓存命中 → 直接返回
426
- cached = self.cache.get(fp)
427
-
428
- # 输入度量:经本地校验的请求内容量(本应输入 LLM 的「表 + 请求」——
429
- # 无论命中与否,走本地 = 不再输入 LLM = 省下的 token;append 审计日志)
430
- try:
431
- self.cache.record_input(len(req.task) + len(req.code)
432
- + len(str(req.cases)) + len(req.unit_id),
433
- hit=(cached is not None))
434
- except Exception:
435
- pass
436
-
437
- if cached is not None:
438
- return cached
439
-
440
- checks: List[Dict[str, Any]] = []
441
-
442
- # ② L1 语法
443
- checks.append(self._check_l1_syntax(req))
444
-
445
- # 注入型单元(expected_structure.inject,如编译-类型检查/图遍历-BFS):
446
- # 函数依赖外部注入(Graph/infer_fn/组装单元),单独运行 L2/L3 无意义
447
- # (与 code_compose verify_code 的 needs_inject 语义一致——由集成测试覆盖)
448
- injected = req.expected_structure.get("inject")
449
-
450
- # ③ L2 样例(语法通过才运行;注入型跳过)
451
- if checks[-1]["ok"] and not injected:
452
- checks.append(self._check_l2_samples(req))
453
-
454
- # ④ L3 边界(注入型跳过)
455
- if checks[-1]["ok"] and not injected:
456
- checks.append(self._check_l3_boundary(req))
457
-
458
- # ⑤ 规范符合性
459
- checks.append(self._check_spec_compliance(req))
460
-
461
- # ⑥ 集成测试(有 deps 才做)
462
- if req.deps:
463
- checks.append(self._check_integration(req))
464
-
465
- ok = all(c["ok"] for c in checks)
466
- reason = ""
467
- if not ok:
468
- failed = [c for c in checks if not c["ok"]]
469
- reason = failed[0]["evidence"] if failed else "未知失败"
470
-
471
- result = VerifyResult(ok=ok, checks=checks, fingerprint=fp, reason=reason,
472
- verified_at=time.strftime("%Y-%m-%d %H:%M:%S"),
473
- version=VERIFIER_VERSION)
474
- # 写缓存
475
- self.cache.put(result)
476
- return result
477
-
478
- # ---------- L1 语法 ----------
479
- def _check_l1_syntax(self, req: VerifyRequest) -> Dict[str, Any]:
480
- """ast.parse + 结构规则(函数定义/占位残留/括号平衡)。"""
481
- code = req.code
482
- # 语法解析
483
- try:
484
- tree = ast.parse(code)
485
- except SyntaxError as e:
486
- return {"level": "L1语法", "ok": False,
487
- "evidence": f"语法错误: {e}"}
488
-
489
- # 结构规则
490
- errors = []
491
- funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
492
- if not funcs:
493
- errors.append("无函数定义")
494
- # 占位残留({fn} 等模板占位符未替换)
495
- if "{fn}" in code or "{param" in code:
496
- errors.append("存在未替换的模板占位符")
497
- # 括号平衡:ast.parse 已通过即证明语法合法(字符串/注释内括号
498
- # 不应计入——字符计数会误判,如 pattern 注释含裸括号)
499
- # 禁止裸 import(白箱代码应自包含)——但允许标准库
500
- imports = [n for n in ast.walk(tree) if isinstance(n, (ast.Import, ast.ImportFrom))]
501
- STDLIB_OK = {"collections", "typing", "functools", "itertools", "math",
502
- "random", "json", "re", "string", "dataclasses", "abc",
503
- "os", "sys", "io", "struct", "asyncio", "heapq", "queue",
504
- "threading", "time", "socket", "hashlib", "uuid", "base64",
505
- "statistics", "bisect", "decimal", "fractions"}
506
- if imports and not req.expected_structure.get("allow_import"):
507
- bad = []
508
- for n in imports:
509
- if isinstance(n, ast.ImportFrom):
510
- top = (n.module or "").split(".")[0]
511
- if top and top not in STDLIB_OK:
512
- bad.append(n.module)
513
- else:
514
- for alias in n.names:
515
- top = alias.name.split(".")[0]
516
- if top not in STDLIB_OK:
517
- bad.append(alias.name)
518
- if bad:
519
- return {"level": "L1语法", "ok": False,
520
- "evidence": f"存在非标准库导入: {bad}"}
521
-
522
- if errors:
523
- return {"level": "L1语法", "ok": False, "evidence": "; ".join(errors)}
524
- return {"level": "L1语法", "ok": True,
525
- "evidence": f"语法通过({len(funcs)} 个函数定义)"}
526
-
527
- # ---------- L2 样例 ----------
528
- def _check_l2_samples(self, req: VerifyRequest) -> Dict[str, Any]:
529
- """每个 (输入, 期望) 运行断言。"""
530
- if not req.cases:
531
- return {"level": "L2样例", "ok": True, "evidence": "无样例(跳过)"}
532
-
533
- ns: Dict[str, Any] = {}
534
- try:
535
- exec(compile(req.code, "<verify>", "exec"), ns)
536
- except Exception as e:
537
- return {"level": "L2样例", "ok": False,
538
- "evidence": f"代码编译/执行失败: {e}"}
539
-
540
- # 找被测函数(第一个函数定义)
541
- func_name = None
542
- try:
543
- tree = ast.parse(req.code)
544
- funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
545
- func_name = funcs[0].name if funcs else None
546
- except Exception:
547
- pass
548
-
549
- if not func_name or func_name not in ns:
550
- return {"level": "L2样例", "ok": False,
551
- "evidence": f"找不到被测函数(期望 {func_name})"}
552
-
553
- fn = ns[func_name]
554
- ran = 0
555
- for case_idx, (inp, exp) in enumerate(req.cases, 1):
556
- if inp == "call":
557
- # 'call' 特殊标记:域注入型单元(L2 由域集成测试覆盖,
558
- # 与 code_compose verify_code 语义一致——不把 'call' 当输入)
559
- continue
560
- ran += 1
561
- try:
562
- got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
563
- except Exception as e:
564
- return {"level": "L2样例", "ok": False,
565
- "evidence": f"样例{case_idx} 崩溃: {inp} → {e}"}
566
- if not _assert_match(got, exp):
567
- return {"level": "L2样例", "ok": False,
568
- "evidence": f"样例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
569
- if ran == 0:
570
- return {"level": "L2样例", "ok": True,
571
- "evidence": "注入型单元(样例均为 call 标记,由集成测试覆盖)"}
572
- return {"level": "L2样例", "ok": True,
573
- "evidence": f"{ran} 组样例全部通过"}
574
-
575
- # ---------- L3 边界 ----------
576
- def _check_l3_boundary(self, req: VerifyRequest) -> Dict[str, Any]:
577
- """额外边界用例:None/空/极端值(仅单参数函数——多参数函数
578
- 边界形态依赖参数语义,统一生成会误判,由单元自带样例覆盖)。"""
579
- extra_cases = self._gen_boundary_cases(req.task)
580
- if not extra_cases:
581
- return {"level": "L3边界", "ok": True, "evidence": "无额外边界用例"}
582
-
583
- ns: Dict[str, Any] = {}
584
- try:
585
- exec(compile(req.code, "<verify>", "exec"), ns)
586
- except Exception as e:
587
- return {"level": "L3边界", "ok": False, "evidence": f"执行失败: {e}"}
588
-
589
- func_name = None
590
- n_args = None
591
- try:
592
- tree = ast.parse(req.code)
593
- funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
594
- if funcs:
595
- func_name = funcs[0].name
596
- args = funcs[0].args
597
- n_args = len(args.posonlyargs) + len(args.args)
598
- except Exception:
599
- pass
600
-
601
- if not func_name or func_name not in ns:
602
- return {"level": "L3边界", "ok": True, "evidence": "跳过(无函数)"}
603
-
604
- if n_args is not None and n_args != 1:
605
- return {"level": "L3边界", "ok": True,
606
- "evidence": f"跳过(函数 {n_args} 个参数,边界形态依赖参数语义)"}
607
-
608
- # 单参数但参数是图/状态/字典类(adj/graph/table/state…)→ 边界形态
609
- # 依赖具体语义(如 [] 传 max_clique(adj) 是类型错误而非代码缺陷)——
610
- # 由单元自带样例覆盖,不统一生成边界
611
- DICTISH_ARGS = {"adj", "graph", "g", "table", "state", "env", "cond",
612
- "nodes", "node", "map", "units", "db", "queue",
613
- "buffer", "registry", "cert"}
614
- if n_args == 1 and funcs[0].args.args \
615
- and funcs[0].args.args[0].arg in DICTISH_ARGS:
616
- return {"level": "L3边界", "ok": True,
617
- "evidence": f"跳过(参数 {funcs[0].args.args[0].arg} 为图/状态类,边界由样例覆盖)"}
618
-
619
- fn = ns[func_name]
620
- for case_idx, (inp, exp) in enumerate(extra_cases, 1):
621
- try:
622
- got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
623
- except Exception as e:
624
- # 边界用例允许异常返回(如空列表→None/报错均可接受)
625
- if exp == "any":
626
- continue
627
- return {"level": "L3边界", "ok": False,
628
- "evidence": f"边界用例{case_idx} 崩溃: {inp} → {e}"}
629
- if exp != "any" and not _assert_match(got, exp):
630
- return {"level": "L3边界", "ok": False,
631
- "evidence": f"边界用例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
632
- return {"level": "L3边界", "ok": True,
633
- "evidence": f"{len(extra_cases)} 组边界用例通过"}
634
-
635
- def _gen_boundary_cases(self, task: str) -> List[Tuple[Any, Any]]:
636
- """根据任务类型生成边界用例(白箱规则,非 LLM)。
637
-
638
- 注意:不带「反转」——字典反转等单元收 dict(值变键),序列边界
639
- [] 是类型错误而非代码缺陷;反转边界由单元自带样例覆盖。
640
- """
641
- t = task.lower()
642
- if any(w in t for w in ("排序", "sort", "去重", "dedup")):
643
- return [([], []), ([1], [1]), (None, "any")]
644
- if any(w in t for w in ("最大", "max", "最小", "min")):
645
- return [([], None), ([5], 5), (None, "any")] # 空列表→None(与单元语义一致)
646
- if any(w in t for w in ("求和", "sum")):
647
- return [([], 0), (None, "any")]
648
- if any(w in t for w in ("计数", "count", "频率", "freq")):
649
- return [([], {}), (None, "any")] # Counter 语义:空列表 → {}
650
- # 字符串族:拆分/替换/拼接/判断/对齐/哈希/分词/格式化——空串语义因函数而异
651
- # (分词空串→[]、拼接空列表→''),统一断言会误判 → 只查「空串处理不崩溃」
652
- if any(w in t for w in ("字符串", "拆分", "替换", "拼接", "判断", "对齐",
653
- "哈希", "分词", "格式化")):
654
- return [("", "any")]
655
- # 数学族:舍入/统计/数学函数/均值/众数/分位数——零输入形态因函数而异
656
- # (列表统计收 []、数值函数收 0),统一断言会误判 → 只查「零输入不崩溃」
657
- if any(w in t for w in ("舍入", "统计", "数学函数", "均值", "众数", "分位数")):
658
- return [(0, "any")]
659
- return []
660
-
661
- # ---------- ⑤ 规范符合性(白箱规则 + condition_kb) ----------
662
- def _check_spec_compliance(self, req: VerifyRequest) -> Dict[str, Any]:
663
- """结构规范 + 语义对齐(condition_kb 查条件路由表)。"""
664
- errors = []
665
-
666
- # 结构规范:任务关键词 → 应包含的结构(白箱规则)
667
- # 注意:只保留「强信号」结构(排序/去重/反转有字面可查的典型结构)。
668
- # 「循环」类任务在编译器域以字节码/展开形式实现(无 for/while 字面量),
669
- # 统一强查会误判——循环语义由 L2 样例裁决,不在此字面强查。
670
- # 排序:接受比较符、sorted(key=) 键控、reorder 重排(排序键控/报文重排序)
671
- # 反转:排除「字典反转」(值变键语义,无序列反转结构)
672
- structure_rules = {
673
- "排序": ["<", ">", "sorted", "key=", "reorder"],
674
- "sort": ["<", ">", "sorted", "key=", "reorder"],
675
- "去重": ["set", "seen"], "dedup": ["set", "seen"],
676
- "反转": ["::-1", "reverse", "reversed"],
677
- }
678
- for kw, patterns in structure_rules.items():
679
- if kw in req.task and req.expected_structure.get("skip_structure"):
680
- continue
681
- if kw in req.task:
682
- if kw in ("反转",) and "字典" in req.task:
683
- continue # 字典反转:值变键语义,无序列反转结构
684
- # 大小写不敏感:本表是**语言无关的任务语义判据**(要求「去重结构
685
- # 出现」这一事实),而字面取自 Python 命名习惯(set/seen)——
686
- # JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确实现判为
687
- # 「未见对应结构」(实测 code-js/去重 假失败,v10 修正)。
688
- # 放宽仅限大小写,令牌本身仍必须出现(不弱化判据强度)。
689
- hit = any(p.lower() in req.code.lower() for p in patterns)
690
- if not hit and not req.expected_structure.get("structure_optional"):
691
- errors.append(f"任务含「{kw}」但代码未见对应结构 {patterns}")
692
-
693
- # 语义对齐:condition_kb 查该任务的条件规律,代码应有对应处理
694
- try:
695
- from condition_kb import ConditionKB
696
- kb = ConditionKB()
697
- sem = kb.lookup(req.task)
698
- if sem and sem.get("match"):
699
- # 查到的规律描述包含条件词 → 代码应体现条件分支
700
- cond_words = ["如果", "若", "when", "if", "条件", "情况"]
701
- has_cond = any(w in req.code for w in cond_words)
702
- if any(w in sem.get("match", "") for w in ["条件", "如果", "当"]) \
703
- and not has_cond \
704
- and not req.expected_structure.get("no_cond_needed"):
705
- errors.append(f"condition_kb 提示「{sem.get('match', '')[:30]}」需条件处理但代码无分支")
706
- except Exception:
707
- pass # condition_kb 不可用时跳过(不影响主校验)
708
-
709
- # 条件论注释规范(WB-SPEC v1.2,用户条件论要求):
710
- # R1 函数/类等抽象的总体功能必须中文注释(docstring 或紧跟 # 中文注释)
711
- # R2 注释须为中文语境(英文缩写嵌入中文说明;纯英文注释行违规)
712
- # R3/R4(六要素契约)恒执行并回报达标率——见 _check_comment_spec 的纪律说明
713
- self._comment_notes = []
714
- self._ccg_stat = {}
715
- errors.extend(self._check_comment_spec(req))
716
-
717
- if errors:
718
- return {"level": "规范符合性", "ok": False, "ccg": self._ccg_stat,
719
- "evidence": "; ".join(errors)}
720
- # evidence 必须**如实**:改造前无论 R3/R4 是否执行都写「条件论注释通过」,
721
- # 等于把「没检查」冒充「检查通过」(固化记录里留下了这类伪证)。
722
- detail = ";".join(self._comment_notes) or "R3/R4 无可检符号"
723
- return {"level": "规范符合性", "ok": True, "ccg": self._ccg_stat,
724
- "evidence": f"结构规范 + 语义对齐 + 条件论注释 R1/R2 通过({detail})"}
725
-
726
- def _check_comment_spec(self, req: VerifyRequest) -> list:
727
- """条件论注释规范(WB-SPEC v1.2):R1 函数/类中文注释 + R2 注释中文语境。
728
-
729
- R1:每个 FunctionDef/AsyncFunctionDef/ClassDef 必须有 docstring 或
730
- 紧跟的 # 中文注释描述其总体功能。
731
- R2:注释行必须为中文语境(含中文字符)——英文缩写嵌入中文说明;
732
- 纯英文注释行(无任何中文)违规。
733
- 非 Python 源码分两档(见下方 except 分支):Python 模板(含 `{fn}` 占位符)
734
- 展开后走本函数的**全量**判据;真·非 Python(Rust/JS)走文本判据。
735
- expected_structure.no_comment_spec=True 可豁免(如注入型组装片段)。
736
- """
737
- if req.expected_structure.get("no_comment_spec"):
738
- return []
739
- code = req.code
740
- try:
741
- tree = ast.parse(code)
742
- except SyntaxError:
743
- # 分支 A·Python 模板:`*_UNITS` 的 pattern 是**带占位符的模板**
744
- # (`def {fn}(arr):`),ast.parse 必然 SyntaxError。改造前这类模板
745
- # 与 Rust/JS 一样落到 `return []` 静默跳过;v8 起落到文本判据——
746
- # 但那是**降级判据**(只查三要素字面,不查 R1 逐符号中文注释 / R2
747
- # 纯英文注释行)。故此优先展开占位符再进 Python 判据(v9),
748
- # 使 Python 模板与六域单元获得同强度判据。
749
- rendered = self._render_placeholders(code, req)
750
- if rendered != code:
751
- try:
752
- tree = ast.parse(rendered)
753
- except SyntaxError:
754
- return self._check_comment_spec_text(req)
755
- code = rendered
756
- else:
757
- # 分支 B·真非 Python 源码(Rust / JavaScript 模板):ast 无法解析。
758
- # 改造前此处 `return []` = **静默跳过**,等于给多语言单元发
759
- # 「假通过」——实测 RUST_UNITS/JS_UNITS 各 5 单元的 CCG 契约 0/5
760
- # 达标,而 verifier 报「R3 缺 0」(`_ccg_stat` 留空、不进分母)
761
- # → 审计报告完全隐身。
762
- return self._check_comment_spec_text(req)
763
- src_lines = code.splitlines()
764
- _has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
765
- errs = []
766
- # R1:函数/类中文注释
767
- missing = []
768
- for f in ast.walk(tree):
769
- if not isinstance(f, (ast.FunctionDef, ast.AsyncFunctionDef,
770
- ast.ClassDef)):
771
- continue
772
- doc = ast.get_docstring(f)
773
- if doc and _has_cn(doc):
774
- continue
775
- ok = False
776
- # 查 def/class 前后各 2 行内的 # 中文注释(docstring 已单独检查)
777
- for ln in src_lines[max(0, f.lineno - 2):f.lineno + 3]:
778
- if ln.strip().startswith('#') and _has_cn(ln):
779
- ok = True
780
- break
781
- if not ok:
782
- missing.append(f.name)
783
- if missing:
784
- errs.append(f"函数/类缺中文注释(条件论 R1): {missing[:5]}")
785
- # R2:注释行中文语境
786
- bad_lines = []
787
- for i, ln in enumerate(src_lines, 1):
788
- s = ln.strip()
789
- if s.startswith('#') and not _has_cn(s) and len(s) > 3:
790
- bad_lines.append(f"L{i}")
791
- if bad_lines:
792
- errs.append(f"纯英文注释行(条件论 R2,须中文说明): {bad_lines[:5]}")
793
- # R3/R4:CCG 六要素注释——**默认软模式恒执行**(2026-09-17 修复)。
794
- #
795
- # 改造前的缺陷(「规范没有被执行」的机械根因,用户 2026-09-17 定性):
796
- # ① R3/R4 被包在 `require_cond_comment` / `require_not_cond` 开关内,
797
- # 而全仓**无任何调用方**传入这两个键 → 判据为**死代码**,恒不执行;
798
- # ② 但 evidence 恒写「条件论注释通过」——R1/R2 通过即谎报六要素达标,
799
- # 于是 code_solidified.json 留下「零标记却通过」的**伪证**;
800
- # ③ 判据自身只查**首个** def、窗口只覆盖 def 前 2 行——与既有单元库
801
- # 104+ 处标记的实际形态(`# 生效条件:` 紧跟 def 行之后)物理不相容。
802
- # 现纪律:软模式**恒执行**并把达标率写入 evidence(诚实报告,不静默),
803
- # 硬拦截仅在开关被显式传入时(全库升级完成后由调用方开启)。
804
- r3_missing, total = self._cond_comment_missing(tree, src_lines)
805
- if total:
806
- self._comment_notes.append(
807
- f"R3 三要素 {total - len(r3_missing)}/{total} 达标")
808
- r4_missing, _ = self._not_cond_missing(tree, src_lines)
809
- # 结构化留痕:软模式不拦截,若只把结论写进 evidence **字符串**,
810
- # `--audit` 就无法机械汇总缺失清单——「规范没被执行」正是这样隐身的。
811
- self._ccg_stat = {"r3_total": total, "r3_missing": r3_missing,
812
- "r4_missing": r4_missing}
813
- if r3_missing:
814
- msg = f"条件论三要素注释缺失(R3): {r3_missing[:5]}"
815
- if req.expected_structure.get("require_cond_comment"):
816
- errs.append(msg)
817
- else:
818
- self._comment_notes.append(msg + "(软模式,不拦截)")
819
- if r4_missing:
820
- msg = f"不适用条件缺失(R4·代码语义条件协议): {r4_missing[:5]}"
821
- if req.expected_structure.get("require_not_cond"):
822
- errs.append(msg)
823
- else:
824
- self._comment_notes.append(msg + "(软模式,不拦截)")
825
- return errs
826
-
827
- def _render_placeholders(self, code: str, req: VerifyRequest) -> str:
828
- """展开单元模板占位符(`{fn}` → 具体标识符),使模板可被 ast 解析。
829
-
830
- 为什么需要:`*_UNITS` 的 pattern 是模板形态(`def {fn}(arr):`),
831
- 未经 `code_compose.compose_code` 替换前**不是合法 Python**——直接
832
- ast.parse 必 SyntaxError,判据就会降级(v7 静默跳过 / v8 文本判据)。
833
- 展开后 template 与「运行时真实生成的代码」同形,R1/R2/R3/R4 全量判据
834
- 才真正作用于模板本体(v9)。
835
-
836
- 占位符名真源:`expected_structure["params"]`(调用方传单元声明的参数名),
837
- 缺省回退约定名 `("fn",)`(`code_compose` 第 17 行声明的模板约定)。
838
- 只替换**声明过的**占位符——不做正则通配,避免误伤 f-string / dict 字面量
839
- (`{x}`、`{1: 1}` 形态)。
840
- 返回值与输入相同 = 无占位符可展开(调用方据此判定「真·非 Python」)。
841
- """
842
- names = req.expected_structure.get("params") or ("fn",)
843
- out = code
844
- for p in names:
845
- out = out.replace("{" + str(p) + "}", "solve")
846
- return out
847
-
848
- def _check_comment_spec_text(self, req: VerifyRequest) -> list:
849
- """非 Python 源码的 CCG 契约判据(Rust / JavaScript,`//` 或 `#` 注释)。
850
-
851
- 存在理由:`ast.parse` 对非 Python 源码抛 `SyntaxError`。改造前该分支直接
852
- `return []` = **静默跳过**——多语言单元在校验器眼里永远「合规」,
853
- `_ccg_stat` 留空不进分母,缺口在审计报告里完全隐身
854
- (实测:verifier 报 R3 缺 0,文本实缺 5/5)。
855
-
856
- 判据与 Python 分支**同口径**(分层不重复):
857
- · 入口符号 = 首个非空非注释行(Rust `fn {fn}(...)`、JS `function {fn}(...)`);
858
- · 注释块 = 入口行之后的连续 `//`/`#` 行;
859
- · R3 三要素(生效条件/子功能/执行)+ R4 不适用条件——软模式,恒执行;
860
- · R1/R2 精神:注释块须含中文(缺失则硬拦截,与 Python 分支一致)。
861
- """
862
- lines = req.code.splitlines()
863
- entry = next((i for i, ln in enumerate(lines)
864
- if ln.strip() and not ln.strip().startswith(("//", "#"))),
865
- None)
866
- if entry is None:
867
- return []
868
- block = []
869
- for ln in lines[entry + 1:]:
870
- s = ln.strip()
871
- if s.startswith(("//", "#")):
872
- block.append(s.lstrip("/#").strip())
873
- elif not s:
874
- continue
875
- else:
876
- break
877
- joined = " ".join(block)
878
- _has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
879
- errs = []
880
- if not block or not _has_cn(joined):
881
- errs.append(f"入口符号缺中文注释(条件论 R1/R2): L{entry + 1}")
882
- need = ("生效条件", "子功能", "执行")
883
- r3_missing = [k for k in need if k not in joined]
884
- r4_missing = [] if "不适用条件" in joined else [f"L{entry + 1}"]
885
- # 与 Python 分支同款结构化留痕(分母记 1 = 入口符号,不是「整个文件」)
886
- self._ccg_stat = {"r3_total": 1, "r3_missing": r3_missing,
887
- "r4_missing": r4_missing, "mode": "text"}
888
- if r3_missing:
889
- msg = f"条件论三要素注释缺失(R3·文本判据): {r3_missing}"
890
- if req.expected_structure.get("require_cond_comment"):
891
- errs.append(msg)
892
- else:
893
- self._comment_notes.append(msg + "(软模式,不拦截)")
894
- if r4_missing:
895
- msg = "不适用条件缺失(R4·文本判据)"
896
- if req.expected_structure.get("require_not_cond"):
897
- errs.append(msg)
898
- else:
899
- self._comment_notes.append(msg + "(软模式,不拦截)")
900
- return errs
901
-
902
- def _ccg_scope(self, tree) -> list:
903
- """R3/R4 共同判定面:CCG 注释的**检查对象** = 单元入口(唯一口径)。
904
-
905
- 三处判据校准(2026-09-17,依据 `--audit` 实测暴露的噪声):
906
- ① **只用 `tree.body` 顶层**——改造前用 `ast.walk`,会把**类内方法**
907
- 也展开计入(实测 `Cat` 与 `Cat.speak` 同现、`speak` 重复两次);
908
- ② **排除 `*_test` / `test_*` / `_` 前缀**——测试样例代码与私有辅助
909
- 不是契约对象(实测 `closure_test`/`gen_test`/`with_test` 等被计入分母);
910
- ③ **取首个合格符号 = 单元对外入口**——`*_units.py` 的既有约定:
911
- pattern 首符号承载「何时可调 / 做什么 / 怎么用」,内部辅助
912
- (dfs/walk/find/h1/up/down)无外部调用方,由 R1 中文注释覆盖。
913
- 与改造前注释声明的设计意图一致(「检查首个 def/class;内部辅助函数由
914
- R1 覆盖」),修的是**窗口错位与谎报**,不是检查面。
915
- """
916
- for n in getattr(tree, "body", []):
917
- if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
918
- nm = n.name
919
- if (nm.startswith("_") or nm.endswith("_test")
920
- or nm.startswith("test_")):
921
- continue
922
- return [n]
923
- return []
924
-
925
- def _ccg_block(self, f, src_lines) -> tuple:
926
- """读取单个符号的 CCG 注释块:返回 (注释行文本列表, docstring)。
927
-
928
- 窗口 = def/class 签名行之后、**函数体首个语句之前**的 `#` 注释
929
- (多行签名的续行非 `#` 起始,自然跳过)+ docstring。
930
- 该窗口即既有单元库 104+ 处标记的实际物理位置(`# 生效条件:` 紧贴
931
- def 行下一行);`f.body[0].lineno` 保证多行签名下窗口仍正确。
932
-
933
- **唯一真源**(2026-09-17 裁决 A):窗口计算委托
934
- `codeindex._body_comments`。历史问题:本函数曾自述「与
935
- `codeindex._body_comments` 同款语义」,而该名当时**并不存在**——
936
- 悬空引用即第二份真相,已由本次收敛坐实。
937
-
938
- 兜底(第 7 条):本模块有三种加载形态——`-m md_cg.whitebox_kb.wisdom.verifier`、
939
- 顶层 `wisdom.verifier`(wheel 发布态,见 code_compose.py)、直跑
940
- `python verifier.py`;后两者 import 不到 `md_cg.codeindex`,故保留
941
- **逐字等价的兜底**,等价性由 `md_cg/test_codeindex.py` 机械守卫。
942
- """
943
- body = getattr(f, "body", None)
944
- body_lineno = body[0].lineno if body else f.lineno
945
- raw = None
946
- fn = _ci_body_comments()
947
- if fn is not None:
948
- raw = fn(src_lines, f.lineno, body_lineno)
949
- if raw is None:
950
- # 兜底(第 7 条):与唯一真源**逐字等价**,等价性由
951
- # md_cg/test_codeindex.py 的委托等价断言机械守卫。
952
- raw = [ln.strip()
953
- for ln in src_lines[f.lineno:max(f.lineno, body_lineno - 1)]
954
- if ln.strip().startswith("#")]
955
- out = [s.lstrip("#").strip() for s in raw]
956
- doc = (ast.get_docstring(f) or "").strip()
957
- return out, doc
958
-
959
- def _cond_comment_missing(self, tree, src_lines) -> tuple:
960
- """R3:缺「生效条件/子功能/执行」三要素的符号清单;返回 (missing, total)。
961
-
962
- 要素名真源 = `nodefile.CCG_CONTRACT_ROLES`(六要素接口契约);
963
- R3 只抽其中三要素——功能名由 R1 覆盖、验证方式与不适用条件由
964
- `codeindex.render` 落槽(分层不重复,改造前此三要素从未被检查过)。
965
- """
966
- funcs = self._ccg_scope(tree)
967
- need = ("生效条件", "子功能", "执行")
968
- missing = []
969
- for f in funcs:
970
- block, doc = self._ccg_block(f, src_lines)
971
- joined = " ".join(block) + " " + doc
972
- if not all(k in joined for k in need):
973
- missing.append(f.name)
974
- return missing, len(funcs)
975
-
976
- def _not_cond_missing(self, tree, src_lines) -> tuple:
977
- """R4:缺「不适用条件」的符号清单;返回 (missing, total)。
978
-
979
- 不适用条件 = 拒绝域 rejection_domain(何时**不能**调用)——
980
- 知道边界才敢调用,与「何时能调用」同等重要。
981
- """
982
- funcs = self._ccg_scope(tree)
983
- missing = []
984
- for f in funcs:
985
- block, doc = self._ccg_block(f, src_lines)
986
- joined = " ".join(block) + " " + doc
987
- if "不适用条件" not in joined:
988
- missing.append(f.name)
989
- return missing, len(funcs)
990
-
991
- # ---------- ⑥ 集成测试 ----------
992
- def _check_integration(self, req: VerifyRequest) -> Dict[str, Any]:
993
- """依赖单元组装 + 端到端运行(简化:deps 代码拼接后执行)。"""
994
- if not req.deps:
995
- return {"level": "集成", "ok": True, "evidence": "无依赖(跳过)"}
996
- try:
997
- # 组装依赖 + 被测代码
998
- combined = "\n\n".join(req.deps) + "\n\n" + req.code
999
- ns: Dict[str, Any] = {}
1000
- exec(compile(combined, "<integrate>", "exec"), ns)
1001
-
1002
- # 端到端:跑第一个样例
1003
- if req.cases:
1004
- func_name = None
1005
- tree = ast.parse(req.code)
1006
- funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
1007
- func_name = funcs[0].name if funcs else None
1008
- if func_name and func_name in ns:
1009
- inp, exp = req.cases[0]
1010
- got = ns[func_name](inp) if not isinstance(inp, tuple) else ns[func_name](*inp)
1011
- if got != exp:
1012
- return {"level": "集成", "ok": False,
1013
- "evidence": f"组装后首样例失败: {inp} → {got}(期望 {exp})"}
1014
- return {"level": "集成", "ok": True,
1015
- "evidence": f"{len(req.deps)} 个依赖组装 + 端到端通过"}
1016
- except Exception as e:
1017
- return {"level": "集成", "ok": False,
1018
- "evidence": f"组装失败: {e}"}
1019
-
1020
-
1021
- # ============ 四、CLI ============
1022
-
1023
- def _build_request_from_file(path: str) -> VerifyRequest:
1024
- """从 JSON 文件构建校验请求。"""
1025
- with open(path, "r", encoding="utf-8") as f:
1026
- data = json.load(f)
1027
- return VerifyRequest(
1028
- task=data.get("task", ""),
1029
- code=data.get("code", ""),
1030
- unit_id=data.get("unit_id", ""),
1031
- lang=data.get("lang", "python"),
1032
- cases=[tuple(c) for c in data.get("cases", [])],
1033
- deps=data.get("deps", []),
1034
- expected_structure=data.get("structure", {}),
1035
- )
1036
-
1037
-
1038
- def _self_test() -> bool:
1039
- """自测:排序单元应通过,错误代码应失败。"""
1040
- v = Verifier()
1041
- passed = 0
1042
-
1043
- # 正确代码(含中文注释——条件论注释规范 R1)
1044
- good_code = (
1045
- "def solve(arr):\n"
1046
- " # 排序:冒泡法(相邻比较交换,最小元素浮到头部)\n"
1047
- " n = len(arr)\n"
1048
- " for i in range(n):\n"
1049
- " for j in range(n-1-i):\n"
1050
- " if arr[j] > arr[j+1]:\n"
1051
- " arr[j], arr[j+1] = arr[j+1], arr[j]\n"
1052
- " return arr\n"
1053
- )
1054
- r1 = v.verify(VerifyRequest(
1055
- task="排序", code=good_code, unit_id="test_sort",
1056
- cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
1057
- ))
1058
- print(f"[1] 排序正确代码: {r1.summary()}")
1059
- passed += 1 if r1.ok else 0
1060
-
1061
- # 错误代码(冒泡逻辑反了)
1062
- bad_code = good_code.replace("if arr[j] > arr[j+1]", "if arr[j] < arr[j+1]")
1063
- r2 = v.verify(VerifyRequest(
1064
- task="排序", code=bad_code, unit_id="test_sort_bad",
1065
- cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
1066
- ))
1067
- print(f"[2] 排序错误代码: {r2.summary()}")
1068
- passed += 1 if not r2.ok else 0
1069
-
1070
- # 缓存命中
1071
- r3 = v.verify(VerifyRequest(
1072
- task="排序", code=good_code, unit_id="test_sort",
1073
- cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
1074
- ))
1075
- print(f"[3] 缓存命中: cached={r3.cached} {r3.summary()}")
1076
- passed += 1 if r3.cached else 0
1077
-
1078
- # 语法错误
1079
- r4 = v.verify(VerifyRequest(
1080
- task="排序", code="def solve(arr):\n return ", unit_id="test_syntax",
1081
- cases=[([1], [1])],
1082
- ))
1083
- print(f"[4] 语法错误: {r4.summary()}")
1084
- passed += 1 if not r4.ok else 0
1085
-
1086
- # 规范符合性(排序但无比较结构)
1087
- r5 = v.verify(VerifyRequest(
1088
- task="排序", code="def solve(arr):\n return arr", unit_id="test_nosort",
1089
- cases=[([3, 1, 2], [3, 1, 2])],
1090
- ))
1091
- print(f"[5] 规范不符(排序无比较): {r5.summary()}")
1092
- passed += 1 if not r5.ok else 0
1093
-
1094
- print(f"\n自测: {passed}/5 通过")
1095
- return passed == 5
1096
-
1097
-
1098
- def _audit_all() -> None:
1099
- """全量审计:六域单元库 → 本地校验器 → 报告(通过率/失败清单/缓存/耗时)。"""
1100
- from compiler_code_units import COMPILER_UNITS
1101
- from python_code_units import PYTHON_UNITS
1102
- from graph_db_units import GRAPH_UNITS
1103
- from os_units import OS_UNITS
1104
- from browser_units import BROWSER_UNITS
1105
- from net_units import NET_UNITS
1106
- domains = [('compiler', COMPILER_UNITS), ('pylang', PYTHON_UNITS),
1107
- ('graph', GRAPH_UNITS), ('os', OS_UNITS),
1108
- ('browser', BROWSER_UNITS), ('net', NET_UNITS)]
1109
- # 语言单元表(白箱自举主干):改造前**不在审计面**——`_audit_all` 只遍历六域
1110
- # `*_units.py`,而模板生产的 CODE_UNITS/RUST_UNITS/JS_UNITS 从不进报告。
1111
- # 「规范没被执行」正是这样隐身的:生成端零契约 + 审计端不看它。
1112
- from code_compose import CODE_UNITS, RUST_UNITS, JS_UNITS
1113
- lang_domains = [('code-py', CODE_UNITS), ('code-rust', RUST_UNITS),
1114
- ('code-js', JS_UNITS)]
1115
- t0 = time.time()
1116
- v = Verifier()
1117
- total = ok = 0
1118
- fails = []
1119
- by_domain = {}
1120
- ccg_total = ccg_missing = 0
1121
- ccg_gaps = [] # [(uid, r3_missing, r4_missing)] —— 软模式缺失清单
1122
- for dname, units in domains:
1123
- d_ok = d_fail = 0
1124
- for uid, u in units.items():
1125
- total += 1
1126
- req = VerifyRequest(
1127
- task=u['task'], code=u['pattern'], unit_id=uid,
1128
- cases=list(u.get('cases', [])),
1129
- expected_structure={'inject': True} if u.get('needs_inject') else {})
1130
- r = v.verify(req)
1131
- # CCG 六要素(软模式)汇总:R3/R4 缺失**不拦截**,故不能只看 r.ok——
1132
- # 必须单独取「规范符合性」check 里的结构化 ccg 块;否则「规范未执行」
1133
- # 会再次隐身(改造前 evidence 恒写「条件论注释通过」,无从发现)。
1134
- spec = next((c for c in r.checks if isinstance(c, dict)
1135
- and c.get("level") == "规范符合性"), None)
1136
- st = (spec or {}).get("ccg") or {}
1137
- if st.get("r3_total"):
1138
- ccg_total += st["r3_total"]
1139
- ccg_missing += len(st.get("r3_missing") or [])
1140
- if st.get("r3_missing") or st.get("r4_missing"):
1141
- ccg_gaps.append((uid, st.get("r3_missing") or [],
1142
- st.get("r4_missing") or []))
1143
- if r.ok:
1144
- ok += 1; d_ok += 1
1145
- else:
1146
- fails.append((uid, r.reason[:80])); d_fail += 1
1147
- by_domain[dname] = (d_ok, d_fail)
1148
- s = v.cache.stats()
1149
- print(f"=== 全量审计(verifier v{VERIFIER_VERSION}): {ok}/{total} 通过 "
1150
- f"({100.0 * ok / total:.1f}%) 耗时 {time.time() - t0:.1f}s ===")
1151
- print("各域:", by_domain)
1152
- # CCG 六要素报告必须**显式打印**:单元"校验通过"只代表 L1-L3 + R1/R2 过了,
1153
- # 六要素接口契约是否达成是另一件事——两者混为一谈正是问题根源。
1154
- rate = (100.0 * (ccg_total - ccg_missing) / ccg_total) if ccg_total else 0.0
1155
- print(f"CCG 三要素(R3 软模式): {ccg_total - ccg_missing}/{ccg_total} 符号达标"
1156
- f"({rate:.1f}%) | 有缺失的单元 {len(ccg_gaps)}/{total} 个")
1157
- if ccg_gaps:
1158
- print("--- CCG 注释缺失清单(软模式,不拦截)---")
1159
- for uid, m3, m4 in ccg_gaps:
1160
- print(f"[{uid}] R3缺={m3[:6]} R4缺={m4[:4]}")
1161
- # --- 语言单元表(自举主干)CCG 契约审计 ---
1162
- # 与六域**分开统计**,原因有二:
1163
- # ① RUST/JS 模板非 Python 语法,L1 语法必失败属正常(校验器与目标语言
1164
- # 不匹配),此处只问「契约是否写进模板」——走文本判据
1165
- # (`_check_comment_spec_text`,mode="text");
1166
- # ② CODE_UNITS 是 Python 模板但含 `{fn}` 占位符(未替换前非法 Python),
1167
- # v9 起由 `_render_placeholders` 展开后走**全量 Python 判据**
1168
- # (mode="python":R1 逐符号中文注释 + R2 注释语境 + R3/R4),
1169
- # 故本节 `params` 必须透传单元声明的占位符名。
1170
- # 两档判据强度不同但**达标口径一致**(三要素 + 不适用条件齐备)。
1171
- lang_total = lang_ok = 0
1172
- lang_gaps = []
1173
- lang_modes = {} # 判据档位计数(python=全量判据 / text=文本判据)
1174
- for dname, units in lang_domains:
1175
- for uid, u in units.items():
1176
- lang_total += 1
1177
- r = v.verify(VerifyRequest(task=u.get('task', ''), code=u['pattern'],
1178
- unit_id=uid, cases=[],
1179
- expected_structure={
1180
- 'params': u.get('params') or ['fn']}))
1181
- spec = next((c for c in r.checks if isinstance(c, dict)
1182
- and c.get("level") == "规范符合性"), None) or {}
1183
- st = spec.get("ccg") or {}
1184
- r3m = st.get("r3_missing") or []
1185
- r4m = st.get("r4_missing") or []
1186
- md = st.get("mode", "python")
1187
- lang_modes[md] = lang_modes.get(md, 0) + 1
1188
- # 「达标」= 判据产出 + 无硬失败 + 契约齐备,三者缺一不可——
1189
- # 只统计 ccg 缺失会漏报 R1/R2 硬失败(spec.ok=False 但 ccg 齐备
1190
- # 仍会被算作达标)= 新的谎报面(同 v7「没检查冒充检查通过」同构)。
1191
- if not st.get("r3_total"):
1192
- lang_gaps.append((dname, uid, md, "判据未产出(ccg 块为空)"))
1193
- elif not spec.get("ok"):
1194
- lang_gaps.append((dname, uid, md,
1195
- f"硬失败: {str(spec.get('evidence'))[:90]}"))
1196
- elif r3m or r4m:
1197
- lang_gaps.append((dname, uid, md, f"R3缺={r3m} R4缺={r4m}"))
1198
- else:
1199
- lang_ok += 1
1200
- lrate = (100.0 * lang_ok / lang_total) if lang_total else 0.0
1201
- print(f"=== 语言单元表 CCG 契约: {lang_ok}/{lang_total} 达标({lrate:.1f}%)"
1202
- f" | 判据档位 {lang_modes}(python=展开占位符后全量判据 / text=文本判据)===")
1203
- for dname, uid, md, desc in lang_gaps:
1204
- print(f" [{dname}/{uid}] mode={md} {desc}")
1205
- print(f"缓存: 共 {s['total']} 条(通过 {s['passed']} / 失败 {s['failed']})"
1206
- f" | 本进程命中 {s['hits']} / 未命中 {s['misses']}(命中率 {s['hit_rate']}%)")
1207
- if fails:
1208
- print("--- 失败清单 ---")
1209
- for uid, reason in fails:
1210
- print(f"[{uid}] {reason}")
1211
-
1212
-
1213
- def _cost_model(daily_tokens: float = 1.3e9, hit_rate: float = 0.999,
1214
- price_hit: float = 2.0, price_full: float = 2.0,
1215
- days: int = 30) -> None:
1216
- """本地化成本对照模型(阶段 5 替换测算器)。
1217
-
1218
- 「原 LLM 校验环节」= 白箱自举的外部校准角色(LLM 查表校验):白箱自举
1219
- 总消耗 13 亿 token(用户实测),其中 99.9% 是重复查表校验(反复读同样
1220
- 的单元/代码/规则表——即 LLM 缓存命中)。本地校验器(六层确定性规则 +
1221
- sha256 指纹缓存)接管后,这部分 token 归零;仅剩 0.1% 为真·新任务
1222
- (新单元设计/语义校准)保留 LLM 价值。
1223
-
1224
- 默认参数取自设计文档实测:13 亿 token/天、99.9% 缓存命中、GLM 缓存价
1225
- 2 元/百万。原方案:全部 token 经 LLM(命中按缓存价计费);
1226
- 本地化后:命中部分走本地磁盘(≈0 成本),仅未命中部分保留 LLM 兜底。
1227
- """
1228
- per_m = 1e6
1229
- orig = daily_tokens * (hit_rate * price_hit + (1 - hit_rate) * price_full) / per_m
1230
- local = daily_tokens * (1 - hit_rate) * price_full / per_m
1231
- saved = orig - local
1232
- print(f"=== 本地化成本对照(LLM 查表校验 → 本地校验缓存)===")
1233
- print(f"背景: 白箱自举外部校准(LLM 查表校验)日消耗 {daily_tokens:.3g} token,"
1234
- f"其中 {hit_rate*100:.1f}% 为重复查表(缓存命中)")
1235
- print(f"输入: 每日 token {daily_tokens:.3g} | 命中率 {hit_rate*100:.1f}% "
1236
- f"| 命中价 {price_hit} 元/百万 | 未命中价 {price_full} 元/百万")
1237
- print(f"原方案(LLM 缓存命中付费): {orig:,.2f} 元/天 ≈ {orig*days:,.0f} 元/{days}天")
1238
- print(f"本地化后(命中零成本,仅未命中 LLM 兜底): {local:,.2f} 元/天 "
1239
- f"≈ {local*days:,.0f} 元/{days}天")
1240
- print(f"节省: {saved:,.2f} 元/天 ≈ {saved*days:,.0f} 元/{days}天 "
1241
- f"(省 {(100*saved/orig if orig else 0):.1f}%)")
1242
- print(f"对照: 白箱自举 {daily_tokens:.3g} token 中 "
1243
- f"{daily_tokens*hit_rate:.3g} token({hit_rate*100:.1f}%)为重复查表校验,"
1244
- f"由 verifier 本地缓存接管后 token 归零")
1245
- if hit_rate == 1.0:
1246
- print("命中率 100% → 本地化后每日成本归零(磁盘 I/O 可忽略)")
1247
-
1248
-
1249
- def main() -> None:
1250
- import argparse
1251
- ap = argparse.ArgumentParser(description="白箱本地校验器(零 LLM)")
1252
- ap.add_argument("--verify", type=str, help="校验请求 JSON 文件路径")
1253
- ap.add_argument("--cache-stats", action="store_true", help="缓存统计")
1254
- ap.add_argument("--audit", action="store_true", help="全量审计(六域单元)")
1255
- ap.add_argument("--cost-model", action="store_true", help="本地化成本对照模型")
1256
- ap.add_argument("--tokens", type=float, default=1.3e9, help="每日 token 量")
1257
- ap.add_argument("--hit-rate", type=float, default=0.999, help="缓存命中率")
1258
- ap.add_argument("--price-hit", type=float, default=2.0, help="命中价 元/百万")
1259
- ap.add_argument("--price-full", type=float, default=2.0, help="未命中价 元/百万")
1260
- ap.add_argument("--days", type=int, default=30, help="测算天数")
1261
- ap.add_argument("--self-test", action="store_true", help="自测")
1262
- args = ap.parse_args()
1263
-
1264
- if args.self_test:
1265
- sys.exit(0 if _self_test() else 1)
1266
-
1267
- if args.cost_model:
1268
- _cost_model(args.tokens, args.hit_rate, args.price_hit,
1269
- args.price_full, args.days)
1270
- return
1271
-
1272
- if args.audit:
1273
- _audit_all()
1274
- return
1275
-
1276
- if args.cache_stats:
1277
- stats = VerifyCache().stats()
1278
- sv = VerifyCache().savings()
1279
- print(f"缓存统计: 共 {stats['total']} 条(通过 {stats['passed']} / 失败 {stats['failed']})")
1280
- print(f"命中计数: 本进程命中 {stats['hits']} / 未命中 {stats['misses']}(命中率 {stats['hit_rate']}%)")
1281
- print(f"审计日志: {sv['requests']} 次校验(缓存命中 {sv['hits']} 次)")
1282
- print(f"减少的 LLM 输入: 累计 {sv['saved_chars']:,} 字符"
1283
- f" ≈ {sv['saved_tokens']:,} token(表+请求内容经本地处理,不再输入 LLM)")
1284
- print("(每次命中 = 一次本该发生的 LLM 查表 → 本地零计算返回)")
1285
- return
1286
-
1287
- if args.verify:
1288
- req = _build_request_from_file(args.verify)
1289
- result = Verifier().verify(req)
1290
- print(json.dumps(asdict(result), ensure_ascii=False, indent=1))
1291
- return
1292
-
1293
- ap.print_help()
1294
-
1295
-
1296
- if __name__ == "__main__":
1297
- main()
1
+ # -*- coding: utf-8 -*-
2
+ """verifier.py · 白箱本地校验器(Zero-LLM Verifier)v1
3
+
4
+ 目标:把「白箱写代码 → 外部校验(是否符合规范/能否执行)」全部本地化,
5
+ 彻底抛开大模型依赖。重复校验走本地缓存(替代 99.9% 的 LLM 缓存命中)。
6
+
7
+ 设计:docs/白箱本地校验器_ZeroLLM_Verifier.md
8
+
9
+ 校验链(六层,全部确定性):
10
+ ① 校验指纹 → 本地缓存(已校验过 → 直接返回)
11
+ ② L1 语法:ast.parse + 结构规则
12
+ ③ L2 样例:输入→期望断言运行(物理基底裁决)
13
+ ④ L3 边界:额外边界用例
14
+ ⑤ 规范符合性:condition_kb 语义对齐 + 结构规范(白箱规则)
15
+ ⑥ 集成测试:依赖单元组装 + 回归
16
+
17
+ 用法:
18
+ python verifier.py --verify "os_112|工作窃取|<code文件路径>" # 校验单个
19
+ python verifier.py --cache-stats # 缓存统计
20
+ python verifier.py --self-test # 自测
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import ast
25
+ import copy
26
+ import hashlib
27
+ import importlib
28
+ import json
29
+ import os
30
+ import subprocess
31
+ import sys
32
+ import tempfile
33
+ import time
34
+ import traceback
35
+ import types
36
+ from dataclasses import dataclass, field, asdict
37
+ from typing import Any, Callable, Dict, List, Optional, Tuple
38
+
39
+ HERE = os.path.dirname(os.path.abspath(__file__))
40
+ sys.path.insert(0, HERE)
41
+
42
+ CACHE_FILE = os.path.join(HERE, "..", "data", "verify_cache.json")
43
+ SAVINGS_LOG = os.path.join(HERE, "..", "data", "verify_savings.jsonl")
44
+ DATA_DIR = os.path.join(HERE, "..", "data")
45
+
46
+
47
+ # ============ 一、数据结构 ============
48
+
49
+ def _stable_serialize(obj: Any) -> Any:
50
+ """任意类型确定性序列化(缓存键基础)。
51
+
52
+ cases 里可能出现 set/bytes/lambda 等 json 不可序列化类型(如集合推导的
53
+ {1,2,3} 与 lambda、帧解析的 bytes)——指纹必须对它们稳定:
54
+ set/frozenset → 元素 str 排序化(去重保稳定)
55
+ bytes/bytearray → hex
56
+ callable(lambda/函数)→ __code__ 确定性摘要(co_code+常量+名,跨进程稳定,
57
+ 不含内存地址——lambda 的 repr 含地址会导致重启后指纹漂移、缓存失效)
58
+ 其余 → repr 回退(域单元 cases 无自定义类实例,足够)。
59
+ """
60
+ if obj is None or isinstance(obj, (bool, int, float, str)):
61
+ return obj
62
+ if isinstance(obj, (list, tuple)):
63
+ return [_stable_serialize(x) for x in obj]
64
+ if isinstance(obj, (set, frozenset)):
65
+ return {"__set__": sorted(str(_stable_serialize(x)) for x in obj)}
66
+ if isinstance(obj, dict):
67
+ return {"__dict__": {str(k): _stable_serialize(v)
68
+ for k, v in sorted(obj.items(), key=lambda kv: str(kv[0]))}}
69
+ if isinstance(obj, (bytes, bytearray)):
70
+ return {"__bytes__": bytes(obj).hex()}
71
+ if isinstance(obj, types.CodeType):
72
+ # 嵌套 code 对象(lambda 的 co_consts 里可能含 <listcomp>/嵌套 lambda):
73
+ # repr 含内存地址(<code object at 0x…>)跨进程漂移——必须确定性摘要
74
+ digest = hashlib.sha256(json.dumps(
75
+ [obj.co_code.hex(), list(obj.co_consts), list(obj.co_names)],
76
+ ensure_ascii=False, sort_keys=True, default=_stable_serialize,
77
+ ).encode("utf-8")).hexdigest()[:16]
78
+ return {"__code__": digest}
79
+ if callable(obj):
80
+ co = getattr(obj, "__code__", None)
81
+ if co is not None:
82
+ digest = hashlib.sha256(json.dumps(
83
+ [co.co_code.hex(), list(co.co_consts), list(co.co_names)],
84
+ ensure_ascii=False, sort_keys=True, default=_stable_serialize,
85
+ ).encode("utf-8")).hexdigest()[:16]
86
+ return {"__fn__": digest}
87
+ return {"__fn__": repr(obj)}
88
+ try:
89
+ return {"__obj__": repr(obj)}
90
+ except Exception:
91
+ return {"__obj__": type(obj).__name__}
92
+
93
+
94
+ def _assert_match(got: Any, exp: Any) -> bool:
95
+ """结果断言:dict 期望做子集匹配(got 包含 exp 全部键值即可)。
96
+
97
+ 白箱单元常返回完整状态字典(如 VM 执行循环返回
98
+ {'trust':…, 'symbols':…, 'cond':…, 'stack':…}),而样例期望只关心其中
99
+ 关键字段(如 {'trust': 0.8})——完整相等会误判,子集匹配符合「状态断言」语义。
100
+ """
101
+ if isinstance(exp, dict) and isinstance(got, dict):
102
+ return all(got.get(k) == v for k, v in exp.items())
103
+ return got == exp
104
+
105
+
106
+ @dataclass
107
+ class VerifyRequest:
108
+ """校验请求:白箱生成的代码 + 单元定义。
109
+
110
+ cases/deps 做深拷贝(不可变快照)——被校验代码可能原地修改输入
111
+ (如排序 arr[j], arr[j+1] = ...),若引用外部共享 list 会污染原始数据,
112
+ 导致相同请求产生不同指纹(缓存失效)。
113
+ """
114
+ task: str # 任务描述(排序/循环编译/图遍历…)
115
+ code: str # 白箱生成的代码
116
+ unit_id: str = "" # 单元标识(域+序号,如 os_112)
117
+ lang: str = "python" # 语言
118
+ cases: List[Tuple[Any, Any]] = field(default_factory=list) # (输入, 期望)
119
+ deps: List[str] = field(default_factory=list) # 依赖单元
120
+ expected_structure: Dict[str, Any] = field(default_factory=dict) # 规范约束
121
+
122
+ def __post_init__(self) -> None:
123
+ """深拷贝防污染 + 指纹预计算(外部原地修改会让指纹漂移·缓存错配——P0 纪律)。"""
124
+ # 深拷贝可变字段——防止被校验代码原地修改污染外部共享对象
125
+ self.cases = copy.deepcopy(self.cases)
126
+ self.deps = list(self.deps)
127
+ self.expected_structure = dict(self.expected_structure)
128
+
129
+ def fingerprint(self) -> str:
130
+ """确定性指纹:相同请求 → 相同指纹(缓存键)。
131
+
132
+ 先整体经 _stable_serialize 归一化再 dumps——json.dumps 的 default 钩子
133
+ 只处理值、不处理 dict 键(tuple 键如事件委托的 {('btn','click'):...}
134
+ 直接崩溃),故必须把整个结构(含键)先递归归一化。
135
+ """
136
+ payload = json.dumps(_stable_serialize({
137
+ "task": self.task, "code": self.code, "unit_id": self.unit_id,
138
+ "lang": self.lang, "cases": self.cases, "deps": self.deps,
139
+ "structure": self.expected_structure,
140
+ }), ensure_ascii=False, sort_keys=True)
141
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:32]
142
+
143
+
144
+ @dataclass
145
+ class VerifyResult:
146
+ """校验结果。"""
147
+ ok: bool
148
+ checks: List[Dict[str, Any]] = field(default_factory=list) # [{level, ok, evidence}]
149
+ fingerprint: str = ""
150
+ cached: bool = False
151
+ reason: str = ""
152
+ verified_at: str = "" # 校验时间戳(ISO)
153
+ version: int = 0 # 校验器规则版本(VERIFIER_VERSION 快照)
154
+
155
+ def summary(self) -> str:
156
+ parts = [f"✅ 通过" if self.ok else f"❌ 失败"]
157
+ for c in self.checks:
158
+ mark = "✓" if c["ok"] else "✗"
159
+ parts.append(f"{mark} {c['level']}")
160
+ if self.reason:
161
+ parts.append(f"原因: {self.reason}")
162
+ if self.version:
163
+ parts.append(f"v{self.version}")
164
+ return " | ".join(parts)
165
+
166
+
167
+ # ============ 二、本地缓存(替代 LLM 缓存命中) ============
168
+
169
+ # 校验器规则版本:L1/L2/L3/规范/集成规则升级时必须 +1——
170
+ # 旧版本缓存结果在规则已变的场景下不再可信,强制全部失效重验
171
+ # (等价于「协议升级 → 相关缓存刷新」,GLM 建议的 protocol_version 落地)。
172
+ # v6(2026-08-31):cases 形态修复(list→tuple)后指纹归一化同值,旧 False
173
+ # 条目对修复免疫;bump 版本作废全部旧缓存——长驻进程内存快照复活毒条目的
174
+ # 终局解(版本不符整体清空,任何进程加载即重建)。
175
+ VERIFIER_VERSION = 10 # v10:结构规范的字面匹配改大小写不敏感——该表是语言无关的
176
+ # 任务语义判据(要求去重结构出现),字面取自 Python 命名习惯
177
+ # (set/seen),JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确
178
+ # 实现判为「未见对应结构」(实测 code-js/去重 假失败)。v9:
179
+ # Python 模板(含 {fn} 占位符)先展开占位符再走 Python
180
+ # 判据——逐字复用 _render_placeholders(模板不再降级为文本判据,
181
+ # R1/R2/R3/R4 全量生效)。v8:非 Python 源码改走文本判据(Rust/JS
182
+ # 不再静默跳过假通过)+ `_audit_all` 审计面纳入语言单元表;
183
+ # v7:R3/R4 由死代码转为软模式恒执行 + 判据校准(入口/窗口/顶层)
184
+ # + evidence 诚实化——判据变更必须 bump,否则旧判定被缓存复用
185
+ _CACHE_VERSION_KEY = "_verifier_version"
186
+ _STATS_KEY = "_stats"
187
+
188
+
189
+ def estimate_tokens(text: str) -> int:
190
+ """确定性 token 估算(用于「减少的 LLM 输入」度量,非精确计费)。
191
+
192
+ 中文混合内容:中文字符按 1.2 token/字(中文 1 字 ≈ 1-1.5 token),
193
+ 其余(代码/英文/标点)按 0.3 token/字符(英文/代码 1 token ≈ 3-4 字符)。
194
+ """
195
+ cjk = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff')
196
+ other = len(text) - cjk
197
+ return int(cjk * 1.2 + other * 0.3)
198
+
199
+
200
+ def _table_payload_chars() -> int:
201
+ """「整张表」内容量:六域单元库定义(pattern+cases)总字符数。
202
+
203
+ 设计文档背景:原方案「每次把整张表塞进 LLM 上下文查一次」——
204
+ 表 = 白箱单元库(规则/模式/样例),本地化后表不再输入 LLM。
205
+ """
206
+ from compiler_code_units import COMPILER_UNITS
207
+ from python_code_units import PYTHON_UNITS
208
+ from graph_db_units import GRAPH_UNITS
209
+ from os_units import OS_UNITS
210
+ from browser_units import BROWSER_UNITS
211
+ from net_units import NET_UNITS
212
+ total = 0
213
+ for units in (COMPILER_UNITS, PYTHON_UNITS, GRAPH_UNITS,
214
+ OS_UNITS, BROWSER_UNITS, NET_UNITS):
215
+ for uid, u in units.items():
216
+ total += len(uid) + len(u.get("task", "")) + len(u.get("pattern", "")) \
217
+ + len(str(u.get("cases", [])))
218
+ return total
219
+
220
+
221
+ class VerifyCache:
222
+ """校验结果本地缓存。相同指纹 → 零计算返回。
223
+
224
+ hits/misses 为进程内命中计数;「减少的 LLM 输入」以 append-only JSONL
225
+ 审计日志持久化(data/verify_savings.jsonl,每行一次校验的输入度量)——
226
+ 跨进程累计、可审计、可重放(复现证据)。
227
+ """
228
+
229
+ def __init__(self, path: str = CACHE_FILE, version: int = VERIFIER_VERSION,
230
+ savings_log: Optional[str] = SAVINGS_LOG):
231
+ """组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
232
+ self.path = path
233
+ self.version = version
234
+ self.savings_log = savings_log
235
+ self._data: Dict[str, Dict[str, Any]] = {}
236
+ self.hits = 0
237
+ self.misses = 0
238
+ self._table_chars: Optional[int] = None
239
+ self._load()
240
+
241
+ def _load(self) -> None:
242
+ """从磁盘回放缓存快照(版本不符整体作废防脏读)。"""
243
+ try:
244
+ if os.path.exists(self.path):
245
+ with open(self.path, "r", encoding="utf-8") as f:
246
+ self._data = json.load(f)
247
+ except Exception:
248
+ self._data = {}
249
+ # 版本不符 → 规则已变,旧缓存结果不可信,清空重建
250
+ if self._data.get(_CACHE_VERSION_KEY) != self.version:
251
+ self._data = {_CACHE_VERSION_KEY: self.version}
252
+ self._flush()
253
+
254
+ def record_input(self, payload_chars: int, hit: bool) -> None:
255
+ """记录一次经本地校验的输入度量(append-only 审计日志)。
256
+
257
+ 每次校验(含缓存命中)都意味着:该内容(表 + 请求)不再输入 LLM →
258
+ 省下的 token 计入日志。hit 标记是否为缓存命中(重复校验零计算)。
259
+ savings_log=None(隔离模式,如变异测试)时不写日志。
260
+ """
261
+ if not self.savings_log:
262
+ return
263
+ table = self._table_chars
264
+ if table is None:
265
+ try:
266
+ table = _table_payload_chars()
267
+ except Exception:
268
+ table = 0
269
+ self._table_chars = table
270
+ text = payload_chars + table
271
+ half = text // 2
272
+ tokens = estimate_tokens("中" * half + "x" * (text - half))
273
+ try:
274
+ os.makedirs(DATA_DIR, exist_ok=True)
275
+ with open(self.savings_log, "a", encoding="utf-8") as f:
276
+ f.write(json.dumps({
277
+ "t": time.strftime("%Y-%m-%d %H:%M:%S"),
278
+ "payload_chars": payload_chars, "table_chars": table,
279
+ "saved_chars": text, "saved_tokens": tokens,
280
+ "hit": bool(hit),
281
+ }, ensure_ascii=False) + "\n")
282
+ except Exception:
283
+ pass
284
+
285
+ def savings(self) -> Dict[str, Any]:
286
+ """跨进程累计:从审计日志聚合「减少的 LLM 输入」。
287
+
288
+ 读 self.savings_log(与写入同源)——此前误读模块常量 SAVINGS_LOG,
289
+ 自定义 savings_log 路径时写入 A 聚合读 B,聚合恒 0(issue #4 审计发现)。
290
+ """
291
+ total_chars = total_tokens = hits = reqs = 0
292
+ log = self.savings_log
293
+ try:
294
+ if log and os.path.exists(log):
295
+ for line in open(log, encoding="utf-8"):
296
+ line = line.strip()
297
+ if not line:
298
+ continue
299
+ d = json.loads(line)
300
+ total_chars += d.get("saved_chars", 0)
301
+ total_tokens += d.get("saved_tokens", 0)
302
+ reqs += 1
303
+ if d.get("hit"):
304
+ hits += 1
305
+ except Exception:
306
+ pass
307
+ return {"requests": reqs, "hits": hits,
308
+ "saved_chars": total_chars, "saved_tokens": total_tokens}
309
+
310
+ def get(self, fingerprint: str) -> Optional[VerifyResult]:
311
+ """按指纹取缓存校验结果:命中计 hits 并标记 cached=True;未命中计 misses 返回 None。"""
312
+ entry = self._data.get(fingerprint)
313
+ if entry is None:
314
+ self.misses += 1
315
+ return None
316
+ self.hits += 1
317
+ r = VerifyResult(ok=entry["ok"], fingerprint=fingerprint, cached=True)
318
+ r.checks = entry.get("checks", [])
319
+ r.reason = entry.get("reason", "")
320
+ r.verified_at = entry.get("verified_at", "")
321
+ r.version = entry.get("version", 0)
322
+ return r
323
+
324
+ def put(self, result: VerifyResult) -> None:
325
+ """写入指纹→结果映射并落盘持久化(附版本键防跨版本脏读)。"""
326
+ self._data[result.fingerprint] = {
327
+ "ok": result.ok,
328
+ "checks": result.checks,
329
+ "reason": result.reason,
330
+ "verified_at": result.verified_at,
331
+ "version": result.version,
332
+ }
333
+ self._data[_CACHE_VERSION_KEY] = self.version
334
+ self._flush()
335
+
336
+ def _flush(self) -> None:
337
+ """内存态刷盘:读-合并-原子替换(多进程并发写友好)。
338
+
339
+ bootstrap 循环进程与交互进程共用同一缓存文件——先吸收磁盘上
340
+ 他进程新增的条目再写,消除「整文件写覆盖」;临时文件+os.replace
341
+ 原子替换,消除「读到半写状态」。残余窗口仅剩同指纹同时写的
342
+ 几毫秒,缓存可重建(最坏=丢条目重跑 verify),工程可接受。
343
+ """
344
+ try:
345
+ os.makedirs(DATA_DIR, exist_ok=True)
346
+ try:
347
+ if os.path.exists(self.path):
348
+ with open(self.path, "r", encoding="utf-8") as f:
349
+ disk = json.load(f)
350
+ # 版本一致性守卫:磁盘条目版本与本实例不同 → 全部忽略
351
+ # (版本 bump 即整体作废,不能被合并逻辑复活旧条目)
352
+ if isinstance(disk, dict) and disk.get(_CACHE_VERSION_KEY) == self.version:
353
+ for k, v in disk.items():
354
+ if k not in self._data:
355
+ self._data[k] = v
356
+ except Exception:
357
+ pass # 磁盘半写/损坏 → 以内存为准
358
+ tmp = self.path + ".tmp"
359
+ with open(tmp, "w", encoding="utf-8") as f:
360
+ json.dump(self._data, f, ensure_ascii=False, indent=1)
361
+ os.replace(tmp, self.path)
362
+ except Exception:
363
+ pass
364
+
365
+ def stats(self) -> Dict[str, Any]:
366
+ """聚合统计:条目总数、通过数、命中率(hits/(hits+misses))。"""
367
+ entries = {k: v for k, v in self._data.items() if k != _CACHE_VERSION_KEY}
368
+ n = len(entries)
369
+ n_pass = sum(1 for v in entries.values() if v.get("ok"))
370
+ total_reqs = self.hits + self.misses
371
+ hit_rate = round(100.0 * self.hits / total_reqs, 1) if total_reqs else 0.0
372
+ return {"total": n, "passed": n_pass, "failed": n - n_pass,
373
+ "hits": self.hits, "misses": self.misses,
374
+ "hit_rate": hit_rate}
375
+
376
+
377
+ # ============ 三、校验器 ============
378
+
379
+ #: 唯一真源(codeindex._body_comments)的解析缓存:None=未解析,False=不可用
380
+ _CI_BODY = None
381
+
382
+
383
+ def _ci_body_comments():
384
+ """解析并缓存 `codeindex._body_comments`(body 窗口的唯一真源)。
385
+
386
+ 三种加载形态各试一次;全部失败返回 None(调用方走等价兜底)。
387
+ 缓存避免每次校验都付一次 import 代价(暖缓存路径要求零额外开销)。
388
+ """
389
+ global _CI_BODY
390
+ if _CI_BODY is None:
391
+ fn = False
392
+ cands = ["md_cg.codeindex"]
393
+ pkg = __package__ or ""
394
+ if pkg.count(".") >= 2: # 如 md_cg.whitebox_kb.wisdom
395
+ cands.insert(0, pkg.rsplit(".", 1)[0].rsplit(".", 1)[0] + ".codeindex")
396
+ for mod in cands:
397
+ try:
398
+ fn = getattr(importlib.import_module(mod), "_body_comments", False)
399
+ except Exception: # noqa: BLE001
400
+ continue
401
+ if fn:
402
+ break
403
+ _CI_BODY = fn or False
404
+ return _CI_BODY or None
405
+
406
+
407
+ class Verifier:
408
+ """本地校验器:六层校验链,零 LLM。"""
409
+
410
+ def __init__(self, cache: Optional[VerifyCache] = None):
411
+ """组装:缓存句柄注入(默认新建;变异测试可传隔离缓存)。"""
412
+ self.cache = cache or VerifyCache()
413
+
414
+ # ---------- 主入口 ----------
415
+ def verify(self, req: VerifyRequest) -> VerifyResult:
416
+ """六层校验入口:深拷贝请求→算指纹→命中直返;未命中逐层校验并记录 token 节省度量。
417
+
418
+ (深拷贝防调用方原地修改污染指纹空间——见 P0 修复记录。)"""
419
+ # 不可变快照:verify 内部运行样例可能原地修改输入(如排序
420
+ # arr[j], arr[j+1] = ...),必须深拷贝防止污染原始 req 对象
421
+ # (否则同一 req 第二次校验指纹变化,缓存失效)
422
+ req = copy.deepcopy(req)
423
+ fp = req.fingerprint()
424
+
425
+ # ① 缓存命中 → 直接返回
426
+ cached = self.cache.get(fp)
427
+
428
+ # 输入度量:经本地校验的请求内容量(本应输入 LLM 的「表 + 请求」——
429
+ # 无论命中与否,走本地 = 不再输入 LLM = 省下的 token;append 审计日志)
430
+ try:
431
+ self.cache.record_input(len(req.task) + len(req.code)
432
+ + len(str(req.cases)) + len(req.unit_id),
433
+ hit=(cached is not None))
434
+ except Exception:
435
+ pass
436
+
437
+ if cached is not None:
438
+ return cached
439
+
440
+ checks: List[Dict[str, Any]] = []
441
+
442
+ # ② L1 语法
443
+ checks.append(self._check_l1_syntax(req))
444
+
445
+ # 注入型单元(expected_structure.inject,如编译-类型检查/图遍历-BFS):
446
+ # 函数依赖外部注入(Graph/infer_fn/组装单元),单独运行 L2/L3 无意义
447
+ # (与 code_compose verify_code 的 needs_inject 语义一致——由集成测试覆盖)
448
+ injected = req.expected_structure.get("inject")
449
+
450
+ # ③ L2 样例(语法通过才运行;注入型跳过)
451
+ if checks[-1]["ok"] and not injected:
452
+ checks.append(self._check_l2_samples(req))
453
+
454
+ # ④ L3 边界(注入型跳过)
455
+ if checks[-1]["ok"] and not injected:
456
+ checks.append(self._check_l3_boundary(req))
457
+
458
+ # ⑤ 规范符合性
459
+ checks.append(self._check_spec_compliance(req))
460
+
461
+ # ⑥ 集成测试(有 deps 才做)
462
+ if req.deps:
463
+ checks.append(self._check_integration(req))
464
+
465
+ ok = all(c["ok"] for c in checks)
466
+ reason = ""
467
+ if not ok:
468
+ failed = [c for c in checks if not c["ok"]]
469
+ reason = failed[0]["evidence"] if failed else "未知失败"
470
+
471
+ result = VerifyResult(ok=ok, checks=checks, fingerprint=fp, reason=reason,
472
+ verified_at=time.strftime("%Y-%m-%d %H:%M:%S"),
473
+ version=VERIFIER_VERSION)
474
+ # 写缓存
475
+ self.cache.put(result)
476
+ return result
477
+
478
+ # ---------- L1 语法 ----------
479
+ def _check_l1_syntax(self, req: VerifyRequest) -> Dict[str, Any]:
480
+ """ast.parse + 结构规则(函数定义/占位残留/括号平衡)。"""
481
+ code = req.code
482
+ # 语法解析
483
+ try:
484
+ tree = ast.parse(code)
485
+ except SyntaxError as e:
486
+ return {"level": "L1语法", "ok": False,
487
+ "evidence": f"语法错误: {e}"}
488
+
489
+ # 结构规则
490
+ errors = []
491
+ funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
492
+ if not funcs:
493
+ errors.append("无函数定义")
494
+ # 占位残留({fn} 等模板占位符未替换)
495
+ if "{fn}" in code or "{param" in code:
496
+ errors.append("存在未替换的模板占位符")
497
+ # 括号平衡:ast.parse 已通过即证明语法合法(字符串/注释内括号
498
+ # 不应计入——字符计数会误判,如 pattern 注释含裸括号)
499
+ # 禁止裸 import(白箱代码应自包含)——但允许标准库
500
+ imports = [n for n in ast.walk(tree) if isinstance(n, (ast.Import, ast.ImportFrom))]
501
+ STDLIB_OK = {"collections", "typing", "functools", "itertools", "math",
502
+ "random", "json", "re", "string", "dataclasses", "abc",
503
+ "os", "sys", "io", "struct", "asyncio", "heapq", "queue",
504
+ "threading", "time", "socket", "hashlib", "uuid", "base64",
505
+ "statistics", "bisect", "decimal", "fractions"}
506
+ if imports and not req.expected_structure.get("allow_import"):
507
+ bad = []
508
+ for n in imports:
509
+ if isinstance(n, ast.ImportFrom):
510
+ top = (n.module or "").split(".")[0]
511
+ if top and top not in STDLIB_OK:
512
+ bad.append(n.module)
513
+ else:
514
+ for alias in n.names:
515
+ top = alias.name.split(".")[0]
516
+ if top not in STDLIB_OK:
517
+ bad.append(alias.name)
518
+ if bad:
519
+ return {"level": "L1语法", "ok": False,
520
+ "evidence": f"存在非标准库导入: {bad}"}
521
+
522
+ if errors:
523
+ return {"level": "L1语法", "ok": False, "evidence": "; ".join(errors)}
524
+ return {"level": "L1语法", "ok": True,
525
+ "evidence": f"语法通过({len(funcs)} 个函数定义)"}
526
+
527
+ # ---------- L2 样例 ----------
528
+ def _check_l2_samples(self, req: VerifyRequest) -> Dict[str, Any]:
529
+ """每个 (输入, 期望) 运行断言。"""
530
+ if not req.cases:
531
+ return {"level": "L2样例", "ok": True, "evidence": "无样例(跳过)"}
532
+
533
+ ns: Dict[str, Any] = {}
534
+ try:
535
+ exec(compile(req.code, "<verify>", "exec"), ns)
536
+ except Exception as e:
537
+ return {"level": "L2样例", "ok": False,
538
+ "evidence": f"代码编译/执行失败: {e}"}
539
+
540
+ # 找被测函数(第一个函数定义)
541
+ func_name = None
542
+ try:
543
+ tree = ast.parse(req.code)
544
+ funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
545
+ func_name = funcs[0].name if funcs else None
546
+ except Exception:
547
+ pass
548
+
549
+ if not func_name or func_name not in ns:
550
+ return {"level": "L2样例", "ok": False,
551
+ "evidence": f"找不到被测函数(期望 {func_name})"}
552
+
553
+ fn = ns[func_name]
554
+ ran = 0
555
+ for case_idx, (inp, exp) in enumerate(req.cases, 1):
556
+ if inp == "call":
557
+ # 'call' 特殊标记:域注入型单元(L2 由域集成测试覆盖,
558
+ # 与 code_compose verify_code 语义一致——不把 'call' 当输入)
559
+ continue
560
+ ran += 1
561
+ try:
562
+ got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
563
+ except Exception as e:
564
+ return {"level": "L2样例", "ok": False,
565
+ "evidence": f"样例{case_idx} 崩溃: {inp} → {e}"}
566
+ if not _assert_match(got, exp):
567
+ return {"level": "L2样例", "ok": False,
568
+ "evidence": f"样例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
569
+ if ran == 0:
570
+ return {"level": "L2样例", "ok": True,
571
+ "evidence": "注入型单元(样例均为 call 标记,由集成测试覆盖)"}
572
+ return {"level": "L2样例", "ok": True,
573
+ "evidence": f"{ran} 组样例全部通过"}
574
+
575
+ # ---------- L3 边界 ----------
576
+ def _check_l3_boundary(self, req: VerifyRequest) -> Dict[str, Any]:
577
+ """额外边界用例:None/空/极端值(仅单参数函数——多参数函数
578
+ 边界形态依赖参数语义,统一生成会误判,由单元自带样例覆盖)。"""
579
+ extra_cases = self._gen_boundary_cases(req.task)
580
+ if not extra_cases:
581
+ return {"level": "L3边界", "ok": True, "evidence": "无额外边界用例"}
582
+
583
+ ns: Dict[str, Any] = {}
584
+ try:
585
+ exec(compile(req.code, "<verify>", "exec"), ns)
586
+ except Exception as e:
587
+ return {"level": "L3边界", "ok": False, "evidence": f"执行失败: {e}"}
588
+
589
+ func_name = None
590
+ n_args = None
591
+ try:
592
+ tree = ast.parse(req.code)
593
+ funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
594
+ if funcs:
595
+ func_name = funcs[0].name
596
+ args = funcs[0].args
597
+ n_args = len(args.posonlyargs) + len(args.args)
598
+ except Exception:
599
+ pass
600
+
601
+ if not func_name or func_name not in ns:
602
+ return {"level": "L3边界", "ok": True, "evidence": "跳过(无函数)"}
603
+
604
+ if n_args is not None and n_args != 1:
605
+ return {"level": "L3边界", "ok": True,
606
+ "evidence": f"跳过(函数 {n_args} 个参数,边界形态依赖参数语义)"}
607
+
608
+ # 单参数但参数是图/状态/字典类(adj/graph/table/state…)→ 边界形态
609
+ # 依赖具体语义(如 [] 传 max_clique(adj) 是类型错误而非代码缺陷)——
610
+ # 由单元自带样例覆盖,不统一生成边界
611
+ DICTISH_ARGS = {"adj", "graph", "g", "table", "state", "env", "cond",
612
+ "nodes", "node", "map", "units", "db", "queue",
613
+ "buffer", "registry", "cert"}
614
+ if n_args == 1 and funcs[0].args.args \
615
+ and funcs[0].args.args[0].arg in DICTISH_ARGS:
616
+ return {"level": "L3边界", "ok": True,
617
+ "evidence": f"跳过(参数 {funcs[0].args.args[0].arg} 为图/状态类,边界由样例覆盖)"}
618
+
619
+ fn = ns[func_name]
620
+ for case_idx, (inp, exp) in enumerate(extra_cases, 1):
621
+ try:
622
+ got = fn(inp) if not isinstance(inp, tuple) else fn(*inp)
623
+ except Exception as e:
624
+ # 边界用例允许异常返回(如空列表→None/报错均可接受)
625
+ if exp == "any":
626
+ continue
627
+ return {"level": "L3边界", "ok": False,
628
+ "evidence": f"边界用例{case_idx} 崩溃: {inp} → {e}"}
629
+ if exp != "any" and not _assert_match(got, exp):
630
+ return {"level": "L3边界", "ok": False,
631
+ "evidence": f"边界用例{case_idx} 失败: {inp} → {got}(期望 {exp})"}
632
+ return {"level": "L3边界", "ok": True,
633
+ "evidence": f"{len(extra_cases)} 组边界用例通过"}
634
+
635
+ def _gen_boundary_cases(self, task: str) -> List[Tuple[Any, Any]]:
636
+ """根据任务类型生成边界用例(白箱规则,非 LLM)。
637
+
638
+ 注意:不带「反转」——字典反转等单元收 dict(值变键),序列边界
639
+ [] 是类型错误而非代码缺陷;反转边界由单元自带样例覆盖。
640
+ """
641
+ t = task.lower()
642
+ if any(w in t for w in ("排序", "sort", "去重", "dedup")):
643
+ return [([], []), ([1], [1]), (None, "any")]
644
+ if any(w in t for w in ("最大", "max", "最小", "min")):
645
+ return [([], None), ([5], 5), (None, "any")] # 空列表→None(与单元语义一致)
646
+ if any(w in t for w in ("求和", "sum")):
647
+ return [([], 0), (None, "any")]
648
+ if any(w in t for w in ("计数", "count", "频率", "freq")):
649
+ return [([], {}), (None, "any")] # Counter 语义:空列表 → {}
650
+ # 字符串族:拆分/替换/拼接/判断/对齐/哈希/分词/格式化——空串语义因函数而异
651
+ # (分词空串→[]、拼接空列表→''),统一断言会误判 → 只查「空串处理不崩溃」
652
+ if any(w in t for w in ("字符串", "拆分", "替换", "拼接", "判断", "对齐",
653
+ "哈希", "分词", "格式化")):
654
+ return [("", "any")]
655
+ # 数学族:舍入/统计/数学函数/均值/众数/分位数——零输入形态因函数而异
656
+ # (列表统计收 []、数值函数收 0),统一断言会误判 → 只查「零输入不崩溃」
657
+ if any(w in t for w in ("舍入", "统计", "数学函数", "均值", "众数", "分位数")):
658
+ return [(0, "any")]
659
+ return []
660
+
661
+ # ---------- ⑤ 规范符合性(白箱规则 + condition_kb) ----------
662
+ def _check_spec_compliance(self, req: VerifyRequest) -> Dict[str, Any]:
663
+ """结构规范 + 语义对齐(condition_kb 查条件路由表)。"""
664
+ errors = []
665
+
666
+ # 结构规范:任务关键词 → 应包含的结构(白箱规则)
667
+ # 注意:只保留「强信号」结构(排序/去重/反转有字面可查的典型结构)。
668
+ # 「循环」类任务在编译器域以字节码/展开形式实现(无 for/while 字面量),
669
+ # 统一强查会误判——循环语义由 L2 样例裁决,不在此字面强查。
670
+ # 排序:接受比较符、sorted(key=) 键控、reorder 重排(排序键控/报文重排序)
671
+ # 反转:排除「字典反转」(值变键语义,无序列反转结构)
672
+ structure_rules = {
673
+ "排序": ["<", ">", "sorted", "key=", "reorder"],
674
+ "sort": ["<", ">", "sorted", "key=", "reorder"],
675
+ "去重": ["set", "seen"], "dedup": ["set", "seen"],
676
+ "反转": ["::-1", "reverse", "reversed"],
677
+ }
678
+ for kw, patterns in structure_rules.items():
679
+ if kw in req.task and req.expected_structure.get("skip_structure"):
680
+ continue
681
+ if kw in req.task:
682
+ if kw in ("反转",) and "字典" in req.task:
683
+ continue # 字典反转:值变键语义,无序列反转结构
684
+ # 大小写不敏感:本表是**语言无关的任务语义判据**(要求「去重结构
685
+ # 出现」这一事实),而字面取自 Python 命名习惯(set/seen)——
686
+ # JS 标准 API 写作 `new Set(arr)`,敏感匹配把正确实现判为
687
+ # 「未见对应结构」(实测 code-js/去重 假失败,v10 修正)。
688
+ # 放宽仅限大小写,令牌本身仍必须出现(不弱化判据强度)。
689
+ hit = any(p.lower() in req.code.lower() for p in patterns)
690
+ if not hit and not req.expected_structure.get("structure_optional"):
691
+ errors.append(f"任务含「{kw}」但代码未见对应结构 {patterns}")
692
+
693
+ # 语义对齐:condition_kb 查该任务的条件规律,代码应有对应处理
694
+ try:
695
+ from condition_kb import ConditionKB
696
+ kb = ConditionKB()
697
+ sem = kb.lookup(req.task)
698
+ if sem and sem.get("match"):
699
+ # 查到的规律描述包含条件词 → 代码应体现条件分支
700
+ cond_words = ["如果", "若", "when", "if", "条件", "情况"]
701
+ has_cond = any(w in req.code for w in cond_words)
702
+ if any(w in sem.get("match", "") for w in ["条件", "如果", "当"]) \
703
+ and not has_cond \
704
+ and not req.expected_structure.get("no_cond_needed"):
705
+ errors.append(f"condition_kb 提示「{sem.get('match', '')[:30]}」需条件处理但代码无分支")
706
+ except Exception:
707
+ pass # condition_kb 不可用时跳过(不影响主校验)
708
+
709
+ # 条件论注释规范(WB-SPEC v1.2,用户条件论要求):
710
+ # R1 函数/类等抽象的总体功能必须中文注释(docstring 或紧跟 # 中文注释)
711
+ # R2 注释须为中文语境(英文缩写嵌入中文说明;纯英文注释行违规)
712
+ # R3/R4(六要素契约)恒执行并回报达标率——见 _check_comment_spec 的纪律说明
713
+ self._comment_notes = []
714
+ self._ccg_stat = {}
715
+ errors.extend(self._check_comment_spec(req))
716
+
717
+ if errors:
718
+ return {"level": "规范符合性", "ok": False, "ccg": self._ccg_stat,
719
+ "evidence": "; ".join(errors)}
720
+ # evidence 必须**如实**:改造前无论 R3/R4 是否执行都写「条件论注释通过」,
721
+ # 等于把「没检查」冒充「检查通过」(固化记录里留下了这类伪证)。
722
+ detail = ";".join(self._comment_notes) or "R3/R4 无可检符号"
723
+ return {"level": "规范符合性", "ok": True, "ccg": self._ccg_stat,
724
+ "evidence": f"结构规范 + 语义对齐 + 条件论注释 R1/R2 通过({detail})"}
725
+
726
+ def _check_comment_spec(self, req: VerifyRequest) -> list:
727
+ """条件论注释规范(WB-SPEC v1.2):R1 函数/类中文注释 + R2 注释中文语境。
728
+
729
+ R1:每个 FunctionDef/AsyncFunctionDef/ClassDef 必须有 docstring 或
730
+ 紧跟的 # 中文注释描述其总体功能。
731
+ R2:注释行必须为中文语境(含中文字符)——英文缩写嵌入中文说明;
732
+ 纯英文注释行(无任何中文)违规。
733
+ 非 Python 源码分两档(见下方 except 分支):Python 模板(含 `{fn}` 占位符)
734
+ 展开后走本函数的**全量**判据;真·非 Python(Rust/JS)走文本判据。
735
+ expected_structure.no_comment_spec=True 可豁免(如注入型组装片段)。
736
+ """
737
+ if req.expected_structure.get("no_comment_spec"):
738
+ return []
739
+ code = req.code
740
+ try:
741
+ tree = ast.parse(code)
742
+ except SyntaxError:
743
+ # 分支 A·Python 模板:`*_UNITS` 的 pattern 是**带占位符的模板**
744
+ # (`def {fn}(arr):`),ast.parse 必然 SyntaxError。改造前这类模板
745
+ # 与 Rust/JS 一样落到 `return []` 静默跳过;v8 起落到文本判据——
746
+ # 但那是**降级判据**(只查三要素字面,不查 R1 逐符号中文注释 / R2
747
+ # 纯英文注释行)。故此优先展开占位符再进 Python 判据(v9),
748
+ # 使 Python 模板与六域单元获得同强度判据。
749
+ rendered = self._render_placeholders(code, req)
750
+ if rendered != code:
751
+ try:
752
+ tree = ast.parse(rendered)
753
+ except SyntaxError:
754
+ return self._check_comment_spec_text(req)
755
+ code = rendered
756
+ else:
757
+ # 分支 B·真非 Python 源码(Rust / JavaScript 模板):ast 无法解析。
758
+ # 改造前此处 `return []` = **静默跳过**,等于给多语言单元发
759
+ # 「假通过」——实测 RUST_UNITS/JS_UNITS 各 5 单元的 CCG 契约 0/5
760
+ # 达标,而 verifier 报「R3 缺 0」(`_ccg_stat` 留空、不进分母)
761
+ # → 审计报告完全隐身。
762
+ return self._check_comment_spec_text(req)
763
+ src_lines = code.splitlines()
764
+ _has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
765
+ errs = []
766
+ # R1:函数/类中文注释
767
+ missing = []
768
+ for f in ast.walk(tree):
769
+ if not isinstance(f, (ast.FunctionDef, ast.AsyncFunctionDef,
770
+ ast.ClassDef)):
771
+ continue
772
+ doc = ast.get_docstring(f)
773
+ if doc and _has_cn(doc):
774
+ continue
775
+ ok = False
776
+ # 查 def/class 前后各 2 行内的 # 中文注释(docstring 已单独检查)
777
+ for ln in src_lines[max(0, f.lineno - 2):f.lineno + 3]:
778
+ if ln.strip().startswith('#') and _has_cn(ln):
779
+ ok = True
780
+ break
781
+ if not ok:
782
+ missing.append(f.name)
783
+ if missing:
784
+ errs.append(f"函数/类缺中文注释(条件论 R1): {missing[:5]}")
785
+ # R2:注释行中文语境
786
+ bad_lines = []
787
+ for i, ln in enumerate(src_lines, 1):
788
+ s = ln.strip()
789
+ if s.startswith('#') and not _has_cn(s) and len(s) > 3:
790
+ bad_lines.append(f"L{i}")
791
+ if bad_lines:
792
+ errs.append(f"纯英文注释行(条件论 R2,须中文说明): {bad_lines[:5]}")
793
+ # R3/R4:CCG 六要素注释——**默认软模式恒执行**(2026-09-17 修复)。
794
+ #
795
+ # 改造前的缺陷(「规范没有被执行」的机械根因,用户 2026-09-17 定性):
796
+ # ① R3/R4 被包在 `require_cond_comment` / `require_not_cond` 开关内,
797
+ # 而全仓**无任何调用方**传入这两个键 → 判据为**死代码**,恒不执行;
798
+ # ② 但 evidence 恒写「条件论注释通过」——R1/R2 通过即谎报六要素达标,
799
+ # 于是 code_solidified.json 留下「零标记却通过」的**伪证**;
800
+ # ③ 判据自身只查**首个** def、窗口只覆盖 def 前 2 行——与既有单元库
801
+ # 104+ 处标记的实际形态(`# 生效条件:` 紧跟 def 行之后)物理不相容。
802
+ # 现纪律:软模式**恒执行**并把达标率写入 evidence(诚实报告,不静默),
803
+ # 硬拦截仅在开关被显式传入时(全库升级完成后由调用方开启)。
804
+ r3_missing, total = self._cond_comment_missing(tree, src_lines)
805
+ if total:
806
+ self._comment_notes.append(
807
+ f"R3 三要素 {total - len(r3_missing)}/{total} 达标")
808
+ r4_missing, _ = self._not_cond_missing(tree, src_lines)
809
+ # 结构化留痕:软模式不拦截,若只把结论写进 evidence **字符串**,
810
+ # `--audit` 就无法机械汇总缺失清单——「规范没被执行」正是这样隐身的。
811
+ self._ccg_stat = {"r3_total": total, "r3_missing": r3_missing,
812
+ "r4_missing": r4_missing}
813
+ if r3_missing:
814
+ msg = f"条件论三要素注释缺失(R3): {r3_missing[:5]}"
815
+ if req.expected_structure.get("require_cond_comment"):
816
+ errs.append(msg)
817
+ else:
818
+ self._comment_notes.append(msg + "(软模式,不拦截)")
819
+ if r4_missing:
820
+ msg = f"不适用条件缺失(R4·代码语义条件协议): {r4_missing[:5]}"
821
+ if req.expected_structure.get("require_not_cond"):
822
+ errs.append(msg)
823
+ else:
824
+ self._comment_notes.append(msg + "(软模式,不拦截)")
825
+ return errs
826
+
827
+ def _render_placeholders(self, code: str, req: VerifyRequest) -> str:
828
+ """展开单元模板占位符(`{fn}` → 具体标识符),使模板可被 ast 解析。
829
+
830
+ 为什么需要:`*_UNITS` 的 pattern 是模板形态(`def {fn}(arr):`),
831
+ 未经 `code_compose.compose_code` 替换前**不是合法 Python**——直接
832
+ ast.parse 必 SyntaxError,判据就会降级(v7 静默跳过 / v8 文本判据)。
833
+ 展开后 template 与「运行时真实生成的代码」同形,R1/R2/R3/R4 全量判据
834
+ 才真正作用于模板本体(v9)。
835
+
836
+ 占位符名真源:`expected_structure["params"]`(调用方传单元声明的参数名),
837
+ 缺省回退约定名 `("fn",)`(`code_compose` 第 17 行声明的模板约定)。
838
+ 只替换**声明过的**占位符——不做正则通配,避免误伤 f-string / dict 字面量
839
+ (`{x}`、`{1: 1}` 形态)。
840
+ 返回值与输入相同 = 无占位符可展开(调用方据此判定「真·非 Python」)。
841
+ """
842
+ names = req.expected_structure.get("params") or ("fn",)
843
+ out = code
844
+ for p in names:
845
+ out = out.replace("{" + str(p) + "}", "solve")
846
+ return out
847
+
848
+ def _check_comment_spec_text(self, req: VerifyRequest) -> list:
849
+ """非 Python 源码的 CCG 契约判据(Rust / JavaScript,`//` 或 `#` 注释)。
850
+
851
+ 存在理由:`ast.parse` 对非 Python 源码抛 `SyntaxError`。改造前该分支直接
852
+ `return []` = **静默跳过**——多语言单元在校验器眼里永远「合规」,
853
+ `_ccg_stat` 留空不进分母,缺口在审计报告里完全隐身
854
+ (实测:verifier 报 R3 缺 0,文本实缺 5/5)。
855
+
856
+ 判据与 Python 分支**同口径**(分层不重复):
857
+ · 入口符号 = 首个非空非注释行(Rust `fn {fn}(...)`、JS `function {fn}(...)`);
858
+ · 注释块 = 入口行之后的连续 `//`/`#` 行;
859
+ · R3 三要素(生效条件/子功能/执行)+ R4 不适用条件——软模式,恒执行;
860
+ · R1/R2 精神:注释块须含中文(缺失则硬拦截,与 Python 分支一致)。
861
+ """
862
+ lines = req.code.splitlines()
863
+ entry = next((i for i, ln in enumerate(lines)
864
+ if ln.strip() and not ln.strip().startswith(("//", "#"))),
865
+ None)
866
+ if entry is None:
867
+ return []
868
+ block = []
869
+ for ln in lines[entry + 1:]:
870
+ s = ln.strip()
871
+ if s.startswith(("//", "#")):
872
+ block.append(s.lstrip("/#").strip())
873
+ elif not s:
874
+ continue
875
+ else:
876
+ break
877
+ joined = " ".join(block)
878
+ _has_cn = lambda s: any('\u4e00' <= c <= '\u9fff' for c in s)
879
+ errs = []
880
+ if not block or not _has_cn(joined):
881
+ errs.append(f"入口符号缺中文注释(条件论 R1/R2): L{entry + 1}")
882
+ need = ("生效条件", "子功能", "执行")
883
+ r3_missing = [k for k in need if k not in joined]
884
+ r4_missing = [] if "不适用条件" in joined else [f"L{entry + 1}"]
885
+ # 与 Python 分支同款结构化留痕(分母记 1 = 入口符号,不是「整个文件」)
886
+ self._ccg_stat = {"r3_total": 1, "r3_missing": r3_missing,
887
+ "r4_missing": r4_missing, "mode": "text"}
888
+ if r3_missing:
889
+ msg = f"条件论三要素注释缺失(R3·文本判据): {r3_missing}"
890
+ if req.expected_structure.get("require_cond_comment"):
891
+ errs.append(msg)
892
+ else:
893
+ self._comment_notes.append(msg + "(软模式,不拦截)")
894
+ if r4_missing:
895
+ msg = "不适用条件缺失(R4·文本判据)"
896
+ if req.expected_structure.get("require_not_cond"):
897
+ errs.append(msg)
898
+ else:
899
+ self._comment_notes.append(msg + "(软模式,不拦截)")
900
+ return errs
901
+
902
+ def _ccg_scope(self, tree) -> list:
903
+ """R3/R4 共同判定面:CCG 注释的**检查对象** = 单元入口(唯一口径)。
904
+
905
+ 三处判据校准(2026-09-17,依据 `--audit` 实测暴露的噪声):
906
+ ① **只用 `tree.body` 顶层**——改造前用 `ast.walk`,会把**类内方法**
907
+ 也展开计入(实测 `Cat` 与 `Cat.speak` 同现、`speak` 重复两次);
908
+ ② **排除 `*_test` / `test_*` / `_` 前缀**——测试样例代码与私有辅助
909
+ 不是契约对象(实测 `closure_test`/`gen_test`/`with_test` 等被计入分母);
910
+ ③ **取首个合格符号 = 单元对外入口**——`*_units.py` 的既有约定:
911
+ pattern 首符号承载「何时可调 / 做什么 / 怎么用」,内部辅助
912
+ (dfs/walk/find/h1/up/down)无外部调用方,由 R1 中文注释覆盖。
913
+ 与改造前注释声明的设计意图一致(「检查首个 def/class;内部辅助函数由
914
+ R1 覆盖」),修的是**窗口错位与谎报**,不是检查面。
915
+ """
916
+ for n in getattr(tree, "body", []):
917
+ if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
918
+ nm = n.name
919
+ if (nm.startswith("_") or nm.endswith("_test")
920
+ or nm.startswith("test_")):
921
+ continue
922
+ return [n]
923
+ return []
924
+
925
+ def _ccg_block(self, f, src_lines) -> tuple:
926
+ """读取单个符号的 CCG 注释块:返回 (注释行文本列表, docstring)。
927
+
928
+ 窗口 = def/class 签名行之后、**函数体首个语句之前**的 `#` 注释
929
+ (多行签名的续行非 `#` 起始,自然跳过)+ docstring。
930
+ 该窗口即既有单元库 104+ 处标记的实际物理位置(`# 生效条件:` 紧贴
931
+ def 行下一行);`f.body[0].lineno` 保证多行签名下窗口仍正确。
932
+
933
+ **唯一真源**(2026-09-17 裁决 A):窗口计算委托
934
+ `codeindex._body_comments`。历史问题:本函数曾自述「与
935
+ `codeindex._body_comments` 同款语义」,而该名当时**并不存在**——
936
+ 悬空引用即第二份真相,已由本次收敛坐实。
937
+
938
+ 兜底(第 7 条):本模块有三种加载形态——`-m md_cg.whitebox_kb.wisdom.verifier`、
939
+ 顶层 `wisdom.verifier`(wheel 发布态,见 code_compose.py)、直跑
940
+ `python verifier.py`;后两者 import 不到 `md_cg.codeindex`,故保留
941
+ **逐字等价的兜底**,等价性由 `md_cg/test_codeindex.py` 机械守卫。
942
+ """
943
+ body = getattr(f, "body", None)
944
+ body_lineno = body[0].lineno if body else f.lineno
945
+ raw = None
946
+ fn = _ci_body_comments()
947
+ if fn is not None:
948
+ raw = fn(src_lines, f.lineno, body_lineno)
949
+ if raw is None:
950
+ # 兜底(第 7 条):与唯一真源**逐字等价**,等价性由
951
+ # md_cg/test_codeindex.py 的委托等价断言机械守卫。
952
+ raw = [ln.strip()
953
+ for ln in src_lines[f.lineno:max(f.lineno, body_lineno - 1)]
954
+ if ln.strip().startswith("#")]
955
+ out = [s.lstrip("#").strip() for s in raw]
956
+ doc = (ast.get_docstring(f) or "").strip()
957
+ return out, doc
958
+
959
+ def _cond_comment_missing(self, tree, src_lines) -> tuple:
960
+ """R3:缺「生效条件/子功能/执行」三要素的符号清单;返回 (missing, total)。
961
+
962
+ 要素名真源 = `nodefile.CCG_CONTRACT_ROLES`(六要素接口契约);
963
+ R3 只抽其中三要素——功能名由 R1 覆盖、验证方式与不适用条件由
964
+ `codeindex.render` 落槽(分层不重复,改造前此三要素从未被检查过)。
965
+ """
966
+ funcs = self._ccg_scope(tree)
967
+ need = ("生效条件", "子功能", "执行")
968
+ missing = []
969
+ for f in funcs:
970
+ block, doc = self._ccg_block(f, src_lines)
971
+ joined = " ".join(block) + " " + doc
972
+ if not all(k in joined for k in need):
973
+ missing.append(f.name)
974
+ return missing, len(funcs)
975
+
976
+ def _not_cond_missing(self, tree, src_lines) -> tuple:
977
+ """R4:缺「不适用条件」的符号清单;返回 (missing, total)。
978
+
979
+ 不适用条件 = 拒绝域 rejection_domain(何时**不能**调用)——
980
+ 知道边界才敢调用,与「何时能调用」同等重要。
981
+ """
982
+ funcs = self._ccg_scope(tree)
983
+ missing = []
984
+ for f in funcs:
985
+ block, doc = self._ccg_block(f, src_lines)
986
+ joined = " ".join(block) + " " + doc
987
+ if "不适用条件" not in joined:
988
+ missing.append(f.name)
989
+ return missing, len(funcs)
990
+
991
+ # ---------- ⑥ 集成测试 ----------
992
+ def _check_integration(self, req: VerifyRequest) -> Dict[str, Any]:
993
+ """依赖单元组装 + 端到端运行(简化:deps 代码拼接后执行)。"""
994
+ if not req.deps:
995
+ return {"level": "集成", "ok": True, "evidence": "无依赖(跳过)"}
996
+ try:
997
+ # 组装依赖 + 被测代码
998
+ combined = "\n\n".join(req.deps) + "\n\n" + req.code
999
+ ns: Dict[str, Any] = {}
1000
+ exec(compile(combined, "<integrate>", "exec"), ns)
1001
+
1002
+ # 端到端:跑第一个样例
1003
+ if req.cases:
1004
+ func_name = None
1005
+ tree = ast.parse(req.code)
1006
+ funcs = [n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
1007
+ func_name = funcs[0].name if funcs else None
1008
+ if func_name and func_name in ns:
1009
+ inp, exp = req.cases[0]
1010
+ got = ns[func_name](inp) if not isinstance(inp, tuple) else ns[func_name](*inp)
1011
+ if got != exp:
1012
+ return {"level": "集成", "ok": False,
1013
+ "evidence": f"组装后首样例失败: {inp} → {got}(期望 {exp})"}
1014
+ return {"level": "集成", "ok": True,
1015
+ "evidence": f"{len(req.deps)} 个依赖组装 + 端到端通过"}
1016
+ except Exception as e:
1017
+ return {"level": "集成", "ok": False,
1018
+ "evidence": f"组装失败: {e}"}
1019
+
1020
+
1021
+ # ============ 四、CLI ============
1022
+
1023
+ def _build_request_from_file(path: str) -> VerifyRequest:
1024
+ """从 JSON 文件构建校验请求。"""
1025
+ with open(path, "r", encoding="utf-8") as f:
1026
+ data = json.load(f)
1027
+ return VerifyRequest(
1028
+ task=data.get("task", ""),
1029
+ code=data.get("code", ""),
1030
+ unit_id=data.get("unit_id", ""),
1031
+ lang=data.get("lang", "python"),
1032
+ cases=[tuple(c) for c in data.get("cases", [])],
1033
+ deps=data.get("deps", []),
1034
+ expected_structure=data.get("structure", {}),
1035
+ )
1036
+
1037
+
1038
+ def _self_test() -> bool:
1039
+ """自测:排序单元应通过,错误代码应失败。"""
1040
+ v = Verifier()
1041
+ passed = 0
1042
+
1043
+ # 正确代码(含中文注释——条件论注释规范 R1)
1044
+ good_code = (
1045
+ "def solve(arr):\n"
1046
+ " # 排序:冒泡法(相邻比较交换,最小元素浮到头部)\n"
1047
+ " n = len(arr)\n"
1048
+ " for i in range(n):\n"
1049
+ " for j in range(n-1-i):\n"
1050
+ " if arr[j] > arr[j+1]:\n"
1051
+ " arr[j], arr[j+1] = arr[j+1], arr[j]\n"
1052
+ " return arr\n"
1053
+ )
1054
+ r1 = v.verify(VerifyRequest(
1055
+ task="排序", code=good_code, unit_id="test_sort",
1056
+ cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
1057
+ ))
1058
+ print(f"[1] 排序正确代码: {r1.summary()}")
1059
+ passed += 1 if r1.ok else 0
1060
+
1061
+ # 错误代码(冒泡逻辑反了)
1062
+ bad_code = good_code.replace("if arr[j] > arr[j+1]", "if arr[j] < arr[j+1]")
1063
+ r2 = v.verify(VerifyRequest(
1064
+ task="排序", code=bad_code, unit_id="test_sort_bad",
1065
+ cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
1066
+ ))
1067
+ print(f"[2] 排序错误代码: {r2.summary()}")
1068
+ passed += 1 if not r2.ok else 0
1069
+
1070
+ # 缓存命中
1071
+ r3 = v.verify(VerifyRequest(
1072
+ task="排序", code=good_code, unit_id="test_sort",
1073
+ cases=[([3, 1, 2], [1, 2, 3]), ([], []), ([1], [1])],
1074
+ ))
1075
+ print(f"[3] 缓存命中: cached={r3.cached} {r3.summary()}")
1076
+ passed += 1 if r3.cached else 0
1077
+
1078
+ # 语法错误
1079
+ r4 = v.verify(VerifyRequest(
1080
+ task="排序", code="def solve(arr):\n return ", unit_id="test_syntax",
1081
+ cases=[([1], [1])],
1082
+ ))
1083
+ print(f"[4] 语法错误: {r4.summary()}")
1084
+ passed += 1 if not r4.ok else 0
1085
+
1086
+ # 规范符合性(排序但无比较结构)
1087
+ r5 = v.verify(VerifyRequest(
1088
+ task="排序", code="def solve(arr):\n return arr", unit_id="test_nosort",
1089
+ cases=[([3, 1, 2], [3, 1, 2])],
1090
+ ))
1091
+ print(f"[5] 规范不符(排序无比较): {r5.summary()}")
1092
+ passed += 1 if not r5.ok else 0
1093
+
1094
+ print(f"\n自测: {passed}/5 通过")
1095
+ return passed == 5
1096
+
1097
+
1098
+ def _audit_all() -> None:
1099
+ """全量审计:六域单元库 → 本地校验器 → 报告(通过率/失败清单/缓存/耗时)。"""
1100
+ from compiler_code_units import COMPILER_UNITS
1101
+ from python_code_units import PYTHON_UNITS
1102
+ from graph_db_units import GRAPH_UNITS
1103
+ from os_units import OS_UNITS
1104
+ from browser_units import BROWSER_UNITS
1105
+ from net_units import NET_UNITS
1106
+ domains = [('compiler', COMPILER_UNITS), ('pylang', PYTHON_UNITS),
1107
+ ('graph', GRAPH_UNITS), ('os', OS_UNITS),
1108
+ ('browser', BROWSER_UNITS), ('net', NET_UNITS)]
1109
+ # 语言单元表(白箱自举主干):改造前**不在审计面**——`_audit_all` 只遍历六域
1110
+ # `*_units.py`,而模板生产的 CODE_UNITS/RUST_UNITS/JS_UNITS 从不进报告。
1111
+ # 「规范没被执行」正是这样隐身的:生成端零契约 + 审计端不看它。
1112
+ from code_compose import CODE_UNITS, RUST_UNITS, JS_UNITS
1113
+ lang_domains = [('code-py', CODE_UNITS), ('code-rust', RUST_UNITS),
1114
+ ('code-js', JS_UNITS)]
1115
+ t0 = time.time()
1116
+ v = Verifier()
1117
+ total = ok = 0
1118
+ fails = []
1119
+ by_domain = {}
1120
+ ccg_total = ccg_missing = 0
1121
+ ccg_gaps = [] # [(uid, r3_missing, r4_missing)] —— 软模式缺失清单
1122
+ for dname, units in domains:
1123
+ d_ok = d_fail = 0
1124
+ for uid, u in units.items():
1125
+ total += 1
1126
+ req = VerifyRequest(
1127
+ task=u['task'], code=u['pattern'], unit_id=uid,
1128
+ cases=list(u.get('cases', [])),
1129
+ expected_structure={'inject': True} if u.get('needs_inject') else {})
1130
+ r = v.verify(req)
1131
+ # CCG 六要素(软模式)汇总:R3/R4 缺失**不拦截**,故不能只看 r.ok——
1132
+ # 必须单独取「规范符合性」check 里的结构化 ccg 块;否则「规范未执行」
1133
+ # 会再次隐身(改造前 evidence 恒写「条件论注释通过」,无从发现)。
1134
+ spec = next((c for c in r.checks if isinstance(c, dict)
1135
+ and c.get("level") == "规范符合性"), None)
1136
+ st = (spec or {}).get("ccg") or {}
1137
+ if st.get("r3_total"):
1138
+ ccg_total += st["r3_total"]
1139
+ ccg_missing += len(st.get("r3_missing") or [])
1140
+ if st.get("r3_missing") or st.get("r4_missing"):
1141
+ ccg_gaps.append((uid, st.get("r3_missing") or [],
1142
+ st.get("r4_missing") or []))
1143
+ if r.ok:
1144
+ ok += 1; d_ok += 1
1145
+ else:
1146
+ fails.append((uid, r.reason[:80])); d_fail += 1
1147
+ by_domain[dname] = (d_ok, d_fail)
1148
+ s = v.cache.stats()
1149
+ print(f"=== 全量审计(verifier v{VERIFIER_VERSION}): {ok}/{total} 通过 "
1150
+ f"({100.0 * ok / total:.1f}%) 耗时 {time.time() - t0:.1f}s ===")
1151
+ print("各域:", by_domain)
1152
+ # CCG 六要素报告必须**显式打印**:单元"校验通过"只代表 L1-L3 + R1/R2 过了,
1153
+ # 六要素接口契约是否达成是另一件事——两者混为一谈正是问题根源。
1154
+ rate = (100.0 * (ccg_total - ccg_missing) / ccg_total) if ccg_total else 0.0
1155
+ print(f"CCG 三要素(R3 软模式): {ccg_total - ccg_missing}/{ccg_total} 符号达标"
1156
+ f"({rate:.1f}%) | 有缺失的单元 {len(ccg_gaps)}/{total} 个")
1157
+ if ccg_gaps:
1158
+ print("--- CCG 注释缺失清单(软模式,不拦截)---")
1159
+ for uid, m3, m4 in ccg_gaps:
1160
+ print(f"[{uid}] R3缺={m3[:6]} R4缺={m4[:4]}")
1161
+ # --- 语言单元表(自举主干)CCG 契约审计 ---
1162
+ # 与六域**分开统计**,原因有二:
1163
+ # ① RUST/JS 模板非 Python 语法,L1 语法必失败属正常(校验器与目标语言
1164
+ # 不匹配),此处只问「契约是否写进模板」——走文本判据
1165
+ # (`_check_comment_spec_text`,mode="text");
1166
+ # ② CODE_UNITS 是 Python 模板但含 `{fn}` 占位符(未替换前非法 Python),
1167
+ # v9 起由 `_render_placeholders` 展开后走**全量 Python 判据**
1168
+ # (mode="python":R1 逐符号中文注释 + R2 注释语境 + R3/R4),
1169
+ # 故本节 `params` 必须透传单元声明的占位符名。
1170
+ # 两档判据强度不同但**达标口径一致**(三要素 + 不适用条件齐备)。
1171
+ lang_total = lang_ok = 0
1172
+ lang_gaps = []
1173
+ lang_modes = {} # 判据档位计数(python=全量判据 / text=文本判据)
1174
+ for dname, units in lang_domains:
1175
+ for uid, u in units.items():
1176
+ lang_total += 1
1177
+ r = v.verify(VerifyRequest(task=u.get('task', ''), code=u['pattern'],
1178
+ unit_id=uid, cases=[],
1179
+ expected_structure={
1180
+ 'params': u.get('params') or ['fn']}))
1181
+ spec = next((c for c in r.checks if isinstance(c, dict)
1182
+ and c.get("level") == "规范符合性"), None) or {}
1183
+ st = spec.get("ccg") or {}
1184
+ r3m = st.get("r3_missing") or []
1185
+ r4m = st.get("r4_missing") or []
1186
+ md = st.get("mode", "python")
1187
+ lang_modes[md] = lang_modes.get(md, 0) + 1
1188
+ # 「达标」= 判据产出 + 无硬失败 + 契约齐备,三者缺一不可——
1189
+ # 只统计 ccg 缺失会漏报 R1/R2 硬失败(spec.ok=False 但 ccg 齐备
1190
+ # 仍会被算作达标)= 新的谎报面(同 v7「没检查冒充检查通过」同构)。
1191
+ if not st.get("r3_total"):
1192
+ lang_gaps.append((dname, uid, md, "判据未产出(ccg 块为空)"))
1193
+ elif not spec.get("ok"):
1194
+ lang_gaps.append((dname, uid, md,
1195
+ f"硬失败: {str(spec.get('evidence'))[:90]}"))
1196
+ elif r3m or r4m:
1197
+ lang_gaps.append((dname, uid, md, f"R3缺={r3m} R4缺={r4m}"))
1198
+ else:
1199
+ lang_ok += 1
1200
+ lrate = (100.0 * lang_ok / lang_total) if lang_total else 0.0
1201
+ print(f"=== 语言单元表 CCG 契约: {lang_ok}/{lang_total} 达标({lrate:.1f}%)"
1202
+ f" | 判据档位 {lang_modes}(python=展开占位符后全量判据 / text=文本判据)===")
1203
+ for dname, uid, md, desc in lang_gaps:
1204
+ print(f" [{dname}/{uid}] mode={md} {desc}")
1205
+ print(f"缓存: 共 {s['total']} 条(通过 {s['passed']} / 失败 {s['failed']})"
1206
+ f" | 本进程命中 {s['hits']} / 未命中 {s['misses']}(命中率 {s['hit_rate']}%)")
1207
+ if fails:
1208
+ print("--- 失败清单 ---")
1209
+ for uid, reason in fails:
1210
+ print(f"[{uid}] {reason}")
1211
+
1212
+
1213
+ def _cost_model(daily_tokens: float = 1.3e9, hit_rate: float = 0.999,
1214
+ price_hit: float = 2.0, price_full: float = 2.0,
1215
+ days: int = 30) -> None:
1216
+ """本地化成本对照模型(阶段 5 替换测算器)。
1217
+
1218
+ 「原 LLM 校验环节」= 白箱自举的外部校准角色(LLM 查表校验):白箱自举
1219
+ 总消耗 13 亿 token(用户实测),其中 99.9% 是重复查表校验(反复读同样
1220
+ 的单元/代码/规则表——即 LLM 缓存命中)。本地校验器(六层确定性规则 +
1221
+ sha256 指纹缓存)接管后,这部分 token 归零;仅剩 0.1% 为真·新任务
1222
+ (新单元设计/语义校准)保留 LLM 价值。
1223
+
1224
+ 默认参数取自设计文档实测:13 亿 token/天、99.9% 缓存命中、GLM 缓存价
1225
+ 2 元/百万。原方案:全部 token 经 LLM(命中按缓存价计费);
1226
+ 本地化后:命中部分走本地磁盘(≈0 成本),仅未命中部分保留 LLM 兜底。
1227
+ """
1228
+ per_m = 1e6
1229
+ orig = daily_tokens * (hit_rate * price_hit + (1 - hit_rate) * price_full) / per_m
1230
+ local = daily_tokens * (1 - hit_rate) * price_full / per_m
1231
+ saved = orig - local
1232
+ print(f"=== 本地化成本对照(LLM 查表校验 → 本地校验缓存)===")
1233
+ print(f"背景: 白箱自举外部校准(LLM 查表校验)日消耗 {daily_tokens:.3g} token,"
1234
+ f"其中 {hit_rate*100:.1f}% 为重复查表(缓存命中)")
1235
+ print(f"输入: 每日 token {daily_tokens:.3g} | 命中率 {hit_rate*100:.1f}% "
1236
+ f"| 命中价 {price_hit} 元/百万 | 未命中价 {price_full} 元/百万")
1237
+ print(f"原方案(LLM 缓存命中付费): {orig:,.2f} 元/天 ≈ {orig*days:,.0f} 元/{days}天")
1238
+ print(f"本地化后(命中零成本,仅未命中 LLM 兜底): {local:,.2f} 元/天 "
1239
+ f"≈ {local*days:,.0f} 元/{days}天")
1240
+ print(f"节省: {saved:,.2f} 元/天 ≈ {saved*days:,.0f} 元/{days}天 "
1241
+ f"(省 {(100*saved/orig if orig else 0):.1f}%)")
1242
+ print(f"对照: 白箱自举 {daily_tokens:.3g} token 中 "
1243
+ f"{daily_tokens*hit_rate:.3g} token({hit_rate*100:.1f}%)为重复查表校验,"
1244
+ f"由 verifier 本地缓存接管后 token 归零")
1245
+ if hit_rate == 1.0:
1246
+ print("命中率 100% → 本地化后每日成本归零(磁盘 I/O 可忽略)")
1247
+
1248
+
1249
+ def main() -> None:
1250
+ import argparse
1251
+ ap = argparse.ArgumentParser(description="白箱本地校验器(零 LLM)")
1252
+ ap.add_argument("--verify", type=str, help="校验请求 JSON 文件路径")
1253
+ ap.add_argument("--cache-stats", action="store_true", help="缓存统计")
1254
+ ap.add_argument("--audit", action="store_true", help="全量审计(六域单元)")
1255
+ ap.add_argument("--cost-model", action="store_true", help="本地化成本对照模型")
1256
+ ap.add_argument("--tokens", type=float, default=1.3e9, help="每日 token 量")
1257
+ ap.add_argument("--hit-rate", type=float, default=0.999, help="缓存命中率")
1258
+ ap.add_argument("--price-hit", type=float, default=2.0, help="命中价 元/百万")
1259
+ ap.add_argument("--price-full", type=float, default=2.0, help="未命中价 元/百万")
1260
+ ap.add_argument("--days", type=int, default=30, help="测算天数")
1261
+ ap.add_argument("--self-test", action="store_true", help="自测")
1262
+ args = ap.parse_args()
1263
+
1264
+ if args.self_test:
1265
+ sys.exit(0 if _self_test() else 1)
1266
+
1267
+ if args.cost_model:
1268
+ _cost_model(args.tokens, args.hit_rate, args.price_hit,
1269
+ args.price_full, args.days)
1270
+ return
1271
+
1272
+ if args.audit:
1273
+ _audit_all()
1274
+ return
1275
+
1276
+ if args.cache_stats:
1277
+ stats = VerifyCache().stats()
1278
+ sv = VerifyCache().savings()
1279
+ print(f"缓存统计: 共 {stats['total']} 条(通过 {stats['passed']} / 失败 {stats['failed']})")
1280
+ print(f"命中计数: 本进程命中 {stats['hits']} / 未命中 {stats['misses']}(命中率 {stats['hit_rate']}%)")
1281
+ print(f"审计日志: {sv['requests']} 次校验(缓存命中 {sv['hits']} 次)")
1282
+ print(f"减少的 LLM 输入: 累计 {sv['saved_chars']:,} 字符"
1283
+ f" ≈ {sv['saved_tokens']:,} token(表+请求内容经本地处理,不再输入 LLM)")
1284
+ print("(每次命中 = 一次本该发生的 LLM 查表 → 本地零计算返回)")
1285
+ return
1286
+
1287
+ if args.verify:
1288
+ req = _build_request_from_file(args.verify)
1289
+ result = Verifier().verify(req)
1290
+ print(json.dumps(asdict(result), ensure_ascii=False, indent=1))
1291
+ return
1292
+
1293
+ ap.print_help()
1294
+
1295
+
1296
+ if __name__ == "__main__":
1297
+ main()