@furongjun1999/dsh-memory 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +54 -26
  2. package/codebuddy/CODEBUDDY.md +196 -195
  3. package/codebuddy/README.md +13 -1
  4. package/codebuddy/mcp.json +9 -0
  5. package/docs/GBrain/345/217/257/345/200/237/351/211/264/347/202/271_/347/201/265/346/236/242/350/220/275/347/202/271/344/272/244/346/216/245_20260919.md +169 -0
  6. package/docs/README.md +1 -1
  7. package/docs/discipline/harnesses.yaml +18 -7
  8. package/docs/discipline/templates/full.md.tmpl +4 -3
  9. package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
  10. package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
  11. package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
  12. package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
  13. package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
  14. package/docs/hive//345/244/232/347/253/257harness/351/200/232/344/277/241/345/245/221/347/272/246_v0.1.md +42 -0
  15. package/docs/hive//346/243/200/347/264/242/346/224/266/346/225/233/345/256/236/346/265/213/344/270/216S1b/350/256/276/350/256/241_v0.1.md +43 -0
  16. package/docs/hive//346/243/200/347/264/242/350/267/257/345/276/204/344/270/216/350/256/244/347/237/245/347/273/223/346/236/204/345/245/221/347/272/246_v0.1.md +102 -0
  17. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S1b/344/270/216/345/217/254/345/233/236/346/235/203/350/241/241_v0.1.md +57 -0
  18. package/docs/hive//347/234/237/345/256/236/345/272/223/347/253/257/345/210/260/347/253/257/345/256/236/346/265/213_S7/345/200/222/346/216/222/345/200/231/351/200/211/345/261/202_v0.1.md +126 -0
  19. package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +503 -0
  20. package/docs/mdcg/D_meta_/345/267/245/347/250/213/345/214/226/346/226/271/346/241/210_v0.2.md +216 -0
  21. package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
  22. package/docs/mdcg//344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/345/245/221/347/272/246_v0.1.md +82 -0
  23. package/docs/mdcg//345/205/250/345/272/223/344/273/243/347/240/201/350/257/204/345/256/241/344/270/216/346/235/241/344/273/266/345/214/226/346/263/250/351/207/212_/350/256/241/345/210/222_v0.1.md +600 -0
  24. 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 +47 -45
  25. package/docs/mdcg//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/{206_v0.4.md → 206_v0.5.md} +92 -4
  26. package/docs/mdcg//347/201/265/346/236/242/350/256/260/345/277/206/345/212/250/350/257/215/345/215/217/350/256/256_v1.0-draft.md +172 -0
  27. package/docs/mdcg//347/216/257/344/272/214_/347/231/275/347/256/261/345/241/253/345/205/205/346/265/201/346/260/264/347/272/277_v0.1.md +31 -0
  28. package/docs/mdcg//350/267/250/347/253/257/351/252/214/350/257/201/344/270/216/345/220/214/346/255/245/345/215/217/350/256/256_v0.1.md +93 -0
  29. 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 +2 -2
  30. 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 +3 -3
  31. 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 +2 -2
  32. package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +434 -433
  33. package/docs//347/201/265/346/236/242/350/207/252/346/210/221/346/224/271/350/277/233/345/267/245/344/275/234/350/256/241/345/210/222_/345/244/226/351/203/250/347/240/224/347/251/266/347/263/273/345/210/227/345/220/270/346/224/266_v1_20260919.md +286 -0
  34. package/dsh/README.md +33 -0
  35. package/dsh/cordis.yml.example +13 -7
  36. package/dsh/hive-mcp-probe.mjs +94 -0
  37. package/dsh/hive-mcp.example.yml +62 -0
  38. package/dsh/update-lingshu.bat +11 -0
  39. package/dsh/update-lingshu.ps1 +337 -0
  40. package/lib/hooks.d.ts +3 -0
  41. package/lib/hooks.js +17 -23
  42. package/lib/index.d.ts +4 -2
  43. package/lib/index.js +29 -6
  44. package/lib/lib/datapath.d.ts +76 -1
  45. package/lib/lib/datapath.js +199 -13
  46. package/lib/lib/mdcg_client.d.ts +6 -3
  47. package/lib/lib/mdcg_client.js +6 -5
  48. package/lib/lib/mutual.js +4 -4
  49. package/lib/lib/token_store.js +4 -5
  50. package/md_cg/audit.py +17 -2
  51. package/md_cg/autonomy.py +86 -15
  52. package/md_cg/backfill.py +36 -1
  53. package/md_cg/backfill_bigdomain.py +34 -0
  54. package/md_cg/bench6_arms.py +28 -1
  55. package/md_cg/bench6_common.py +10 -1
  56. package/md_cg/bench6_competitors.py +6 -1
  57. package/md_cg/bench_axis_domain.py +9 -1
  58. package/md_cg/bench_blind_comp.py +7 -1
  59. package/md_cg/bench_en_atoms_public.py +9 -0
  60. package/md_cg/bench_governance.py +348 -0
  61. package/md_cg/bench_lme_zh.py +16 -1
  62. package/md_cg/bench_locomo.py +2 -1
  63. package/md_cg/bench_locomo_zh.py +16 -1
  64. package/md_cg/bench_locomo_zh_public.py +4 -1
  65. package/md_cg/bench_longmem.py +2 -1
  66. package/md_cg/bench_membench.py +27 -1
  67. package/md_cg/bench_p0.py +4 -1
  68. package/md_cg/bench_progressive.py +13 -1
  69. package/md_cg/bench_role_views.py +238 -0
  70. package/md_cg/bench_task_ab.py +8 -1
  71. package/md_cg/bench_task_ab_llm.py +13 -1
  72. package/md_cg/bench_unified_en.py +6 -1
  73. package/md_cg/bench_zh_mad.py +20 -1
  74. package/md_cg/blindspot_tickets.py +123 -0
  75. package/md_cg/branches.py +12 -1
  76. package/md_cg/build_postings.py +73 -0
  77. package/md_cg/ccgc.py +67 -2
  78. package/md_cg/census.py +5 -1
  79. package/md_cg/chain.py +24 -3
  80. package/md_cg/codeindex.py +134 -17
  81. package/md_cg/coldverify.py +265 -0
  82. package/md_cg/comment_gate.py +338 -0
  83. package/md_cg/cond_compose.py +190 -0
  84. package/md_cg/cond_facts.py +155 -0
  85. package/md_cg/cond_template.json +107 -0
  86. package/md_cg/condition_anchor.py +143 -0
  87. package/md_cg/conformance.py +69 -4
  88. package/md_cg/consistency.py +24 -1
  89. package/md_cg/consolidate.py +53 -2
  90. package/md_cg/corpus.py +4 -0
  91. package/md_cg/crosscheck.py +42 -2
  92. package/md_cg/crypto.py +35 -1
  93. package/md_cg/d_meta.py +310 -0
  94. package/md_cg/datapath.py +201 -26
  95. package/md_cg/docindex.py +122 -1
  96. package/md_cg/eval_common.py +29 -1
  97. package/md_cg/evidence.py +27 -1
  98. package/md_cg/evolution.py +21 -1
  99. package/md_cg/export.py +11 -1
  100. package/md_cg/forgetting.py +23 -1
  101. package/md_cg/fsutil.py +18 -1
  102. package/md_cg/hotcache.py +214 -0
  103. package/md_cg/hyperedge.py +251 -0
  104. package/md_cg/identity.py +18 -1
  105. package/md_cg/insight.py +17 -1
  106. package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
  107. package/md_cg/lexicon/build_standard_en.py +171 -168
  108. package/md_cg/lexicon/expand_en_zh.py +6 -0
  109. package/md_cg/lifecycle.py +12 -1
  110. package/md_cg/linkref.py +281 -0
  111. package/md_cg/links.py +29 -1
  112. package/md_cg/mcp_server.py +362 -43
  113. package/md_cg/md_whitebox.py +53 -1
  114. package/md_cg/mdcg.py +1003 -27
  115. package/md_cg/mdcos.py +558 -36
  116. package/md_cg/metacognition.py +37 -2
  117. package/md_cg/migrate.py +4 -0
  118. package/md_cg/migrate_aeis.py +221 -213
  119. package/md_cg/migrate_roleplay.py +8 -0
  120. package/md_cg/migrate_wisdom_graph.py +14 -1
  121. package/md_cg/mreview/__main__.py +3 -0
  122. package/md_cg/mreview/bundle.py +8 -0
  123. package/md_cg/mreview/candidates.py +9 -0
  124. package/md_cg/mreview/govern.py +21 -1
  125. package/md_cg/mreview/locate.py +34 -0
  126. package/md_cg/mreview/pipeline.py +29 -1
  127. package/md_cg/mreview/ruleset.py +16 -1
  128. package/md_cg/nodefile.py +233 -3
  129. package/md_cg/pooling.py +23 -1
  130. package/md_cg/postings.py +298 -0
  131. package/md_cg/predict.py +89 -9
  132. package/md_cg/progressive.py +3 -0
  133. package/md_cg/protect.py +14 -1
  134. package/md_cg/protocol.py +372 -0
  135. package/md_cg/provenance.py +262 -0
  136. package/md_cg/reach.py +453 -0
  137. package/md_cg/refindex.py +47 -2
  138. package/md_cg/refine.py +20 -1
  139. package/md_cg/roleviews.py +89 -0
  140. package/md_cg/routing.py +76 -0
  141. package/md_cg/scrub.py +63 -2
  142. package/md_cg/security.py +26 -1
  143. package/md_cg/self_state.py +64 -1
  144. package/md_cg/selfreport.py +151 -0
  145. package/md_cg/semantic/canonical.py +5 -0
  146. package/md_cg/semantic/en_normalizer.py +364 -355
  147. package/md_cg/semantic/zh_en_atoms.py +139 -136
  148. package/md_cg/signer.py +41 -1
  149. package/md_cg/sources.py +583 -547
  150. package/md_cg/statushdr.py +179 -0
  151. package/md_cg/stg.py +59 -18
  152. package/md_cg/subgraph.py +23 -0
  153. package/md_cg/sustain.py +56 -1
  154. package/md_cg/tasks.py +26 -2
  155. package/md_cg/test_autonomy.py +26 -0
  156. package/md_cg/test_bench_governance.py +102 -0
  157. package/md_cg/test_blindspot_tickets.py +166 -0
  158. package/md_cg/test_ccgc.py +10 -0
  159. package/md_cg/test_codeindex.py +338 -0
  160. package/md_cg/test_comment_gate.py +187 -0
  161. package/md_cg/test_cond_compose_anchors.py +76 -0
  162. package/md_cg/test_condition_anchor.py +82 -0
  163. package/md_cg/test_d_meta.py +412 -0
  164. package/md_cg/test_datapath_root.py +188 -0
  165. package/md_cg/test_gain_gate.py +47 -1
  166. package/md_cg/test_hot_cold.py +187 -0
  167. package/md_cg/test_hyperedge.py +245 -0
  168. package/md_cg/test_linkref.py +306 -0
  169. package/md_cg/test_md_access_parity.py +15 -3
  170. package/md_cg/test_mr_m1.py +108 -18
  171. package/md_cg/test_mr_m3.py +8 -1
  172. package/md_cg/test_p26_refindex.py +49 -20
  173. package/md_cg/test_p27_docindex.py +236 -2
  174. package/md_cg/test_p2_mcp.py +1 -1
  175. package/md_cg/test_p31_insight.py +24 -0
  176. package/md_cg/test_p44_md_whitebox.py +14 -1
  177. package/md_cg/test_protocol.py +243 -0
  178. package/md_cg/test_reach.py +378 -0
  179. package/md_cg/test_reach_keys.py +201 -0
  180. package/md_cg/test_reach_meta_exits.py +145 -0
  181. package/md_cg/test_read_clip.py +8 -4
  182. package/md_cg/test_retr_s1.py +340 -0
  183. package/md_cg/test_retr_s1b.py +209 -0
  184. package/md_cg/test_retr_s3.py +194 -0
  185. package/md_cg/test_retr_s4.py +163 -0
  186. package/md_cg/test_retr_s5.py +200 -0
  187. package/md_cg/test_retr_s6.py +157 -0
  188. package/md_cg/test_retr_s7.py +385 -0
  189. package/md_cg/test_retr_s8_time.py +316 -0
  190. package/md_cg/test_retr_s9_edges.py +286 -0
  191. package/md_cg/test_retr_s9_entity_ctx.py +175 -0
  192. package/md_cg/test_review_conformance.py +59 -2
  193. package/md_cg/test_role_views.py +354 -0
  194. package/md_cg/test_trust.py +361 -0
  195. package/md_cg/test_units_poll.py +71 -0
  196. package/md_cg/test_v14_fixes.py +397 -0
  197. package/md_cg/test_validity_filter.py +280 -0
  198. package/md_cg/test_wisdom_md_store.py +7 -3
  199. package/md_cg/test_writepipe.py +5 -1
  200. package/md_cg/theory.py +16 -1
  201. package/md_cg/tokens.py +40 -8
  202. package/md_cg/tool_face.py +13 -2
  203. package/md_cg/trust.py +943 -0
  204. package/md_cg/twophase.py +12 -1
  205. package/md_cg/units.py +129 -10
  206. package/md_cg/vision_evidence.py +24 -1
  207. package/md_cg/weights.py +24 -1
  208. package/md_cg/whitebox.py +32 -1
  209. package/md_cg/whitebox_kb/data/verify_cache.json +21210 -365
  210. package/md_cg/whitebox_kb/data/verify_savings.jsonl +5078 -0
  211. package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
  212. package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
  213. package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
  214. package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
  215. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
  216. package/md_cg/writelimit.py +18 -4
  217. package/md_cg/writepipe.py +178 -7
  218. package/package.json +2 -2
  219. package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
  220. package/skills/skills/designer-perspective/scripts/designer.py +17 -1
  221. package/skills/skills/designer-perspective/tests/selftest.py +3 -1
  222. package/src/hooks.ts +17 -21
  223. package/src/index.ts +33 -6
  224. package/src/lib/datapath.ts +211 -13
  225. package/src/lib/mdcg_client.ts +12 -8
  226. package/src/lib/mutual.ts +411 -411
  227. package/src/lib/token_store.ts +4 -5
  228. package/zcode/AGENTS.md +196 -195
  229. package/zcode/README.md +4 -0
  230. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-shm +0 -0
  231. package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-wal +0 -0
@@ -26,6 +26,7 @@ for _p in (_ROOT, _HERE, os.path.join(_HERE, "whitebox_kb"),
26
26
  from wisdom_book import (ConditionDex, DEFAULT_DB, DEFAULT_MD_ROOT, # noqa: E402
27
27
  BASE_ENTRIES, BASE_RELATIONS)
28
28
  from md_access import MdConn, read_conn # noqa: E402
29
+ from md_whitebox import corpus_ready # noqa: E402
29
30
  from md_store import MdStore # noqa: E402
30
31
  from aeis_core import ConditionSpace, MemoryLayer, STNode # noqa: E402
31
32
 
@@ -44,8 +45,11 @@ def ok(cond, label):
44
45
  # ---------------------------------------------------------------------------
45
46
 
46
47
  def group_a(tmp):
47
- if not (os.path.exists(DEFAULT_DB) and os.path.isdir(DEFAULT_MD_ROOT)):
48
- print("组A SKIP:生产对拍数据不全(派生库或语料根缺失)")
48
+ # 语料就绪判据 = 「目录存在**且含 .md**」(2026-09-20 v14 缺陷 F):空壳
49
+ # 目录(兄弟测试经 MdCGOS(root) 的 makedirs 留下)不算就绪,否则组A 会
50
+ # 以「md 语料行数 0 > 4000」的失败示人,而真实原因是真源根本不在位。
51
+ if not (os.path.exists(DEFAULT_DB) and corpus_ready(DEFAULT_MD_ROOT)):
52
+ print("组A SKIP:生产对拍数据不全(派生库缺失或语料根缺失/空壳)")
49
53
  return
50
54
  print("组A · 生产数据对拍(全量)")
51
55
  # sqlite 侧用副本隔离:search_content 的 increment_access 会落库,
@@ -258,7 +262,7 @@ def group_d(tmp):
258
262
  ok(not dex2._md_mode, "fresh=True → sqlite 形态(兼容 api.py 兜底)")
259
263
  dex2.close()
260
264
 
261
- if os.path.isdir(DEFAULT_MD_ROOT):
265
+ if corpus_ready(DEFAULT_MD_ROOT):
262
266
  dex3 = ConditionDex(md_root=DEFAULT_MD_ROOT)
263
267
  try:
264
268
  conn = read_conn(dex3) # duck:MdStore.conn → MdConn
@@ -129,8 +129,12 @@ def _run(tmp):
129
129
  "content": "PASSED SKIPME",
130
130
  "layer": "knowledge"})
131
131
  _check("移除即恢复(核心文件零改动)", out.get("committed") is True, repr(out))
132
+ # 默认 before 链含 linkref→deps→audit→consistency→gated(2026-09-19 deps 闸门新增)
132
133
  _check("单例默认链", singleton.names()["before"]
133
- == ["audit", "consistency", "gated"],
134
+ == ["linkref", "deps", "audit", "consistency", "gated"],
135
+ repr(singleton.names()))
136
+ _check("单例默认after链", singleton.names()["after"]
137
+ == ["linkref", "trust"],
134
138
  repr(singleton.names()))
135
139
 
136
140
  # ---------- 3 链序 / 短路 / REWRITE ----------
package/md_cg/theory.py CHANGED
@@ -52,24 +52,29 @@ class TheoryError(Exception):
52
52
  # 存储
53
53
  # --------------------------------------------------------------------------
54
54
 
55
+ # 生效条件:path 为真值时返回 path,否则取 os.environ.get(THEORY_FILE_ENV),该值为空串/未设置时回落模块常量 DEFAULT_THEORY_FILE。
55
56
  def theory_file(path: str = None) -> str:
56
57
  return path or os.environ.get(THEORY_FILE_ENV) or DEFAULT_THEORY_FILE
57
58
 
58
59
 
60
+ # 生效条件:传入可被 json 序列化的 payload(dict)时返回 json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(",", ":"))。
59
61
  def _canon(payload: dict) -> str:
60
62
  """规范化 JSON:键排序 + 紧凑分隔符(哈希可复现的唯一前提)。"""
61
63
  return json.dumps(payload, sort_keys=True, ensure_ascii=False,
62
64
  separators=(",", ":"))
63
65
 
64
66
 
67
+ # 生效条件:payload 可被 _canon 序列化时返回 hashlib.sha256(_canon(payload).encode("utf-8")).hexdigest() 的十六进制摘要。
65
68
  def declaration_hash(payload: dict) -> str:
66
69
  return hashlib.sha256(_canon(payload).encode("utf-8")).hexdigest()
67
70
 
68
71
 
72
+ # 生效条件:对 SEALED_FIELDS 中每个 k 用 decl.get(k) 取值(缺键即 None),返回同集合长度的 dict。
69
73
  def _payload(decl: dict) -> dict:
70
74
  return {k: decl.get(k) for k in SEALED_FIELDS}
71
75
 
72
76
 
77
+ # 生效条件:以 dict(decl) 为副本 d,写入 d["declaration_hash"]=declaration_hash(_payload(d)),返回该副本(不改动入参 decl)。
73
78
  def seal(decl: dict) -> dict:
74
79
  """封缄:补上 declaration_hash。"""
75
80
  d = dict(decl)
@@ -77,6 +82,7 @@ def seal(decl: dict) -> dict:
77
82
  return d
78
83
 
79
84
 
85
+ # 生效条件:p=theory_file(path) 后,os.path.exists(p) 为假返回 None;open/json.load 抛 OSError 或 ValueError 也返回 None;否则 d 是 dict 时返回 d,不是 dict 返回 None。
80
86
  def load(path: str = None):
81
87
  p = theory_file(path)
82
88
  if not os.path.exists(p):
@@ -89,6 +95,7 @@ def load(path: str = None):
89
95
  return d if isinstance(d, dict) else None
90
96
 
91
97
 
98
+ # 生效条件:p=theory_file(path),先 os.makedirs(os.path.dirname(p) or "."),把 decl 以 ensure_ascii=False, indent=1 写入 p+".tmp" 再 os.replace 到 p,os.chmod(p, 0o600) 抛 OSError 时忽略,最终返回 p。
92
99
  def save(decl: dict, path: str = None) -> str:
93
100
  p = theory_file(path)
94
101
  os.makedirs(os.path.dirname(p) or ".", exist_ok=True)
@@ -107,6 +114,7 @@ def save(decl: dict, path: str = None) -> str:
107
114
  # 声明与校验
108
115
  # --------------------------------------------------------------------------
109
116
 
117
+ # 生效条件:v=(version or PROTOCOL_VERSION).strip(),v 为空串时抛 TheoryError;accepted 为假值(None/空)时 acc=[v],否则 list(accepted) 并剔除 strip 后为空的项,v 不在 acc 时 insert(0, v);declared_by 假值回落 "designer",declared_at=float(now if now is not None else time.time()),返回含 SCHEMA 的 dict。
110
118
  def make_declaration(version: str = None, accepted=None,
111
119
  declared_by: str = "designer", now: float = None) -> dict:
112
120
  """构造(未封缄的)版本声明。accepted 默认只含自身版本。"""
@@ -122,6 +130,7 @@ def make_declaration(version: str = None, accepted=None,
122
130
  "declared_at": float(now if now is not None else time.time())}
123
131
 
124
132
 
133
+ # 生效条件:d=decl if decl is not None else load(theory_file(path));d 为假时返回 theory_ok=True、auto=True、version=PROTOCOL_VERSION 的状态;d.get("schema")!=SCHEMA、hmac.compare_digest(str(d.get("declaration_hash") or ""), declaration_hash(_payload(d))) 不通过、v=d.get("theory_version") 为假、或 v 不在 acc=[str(x) for x in (d.get("accepted_versions") or [])] 时各自返回同一个 base 且 theory_ok=False,其余情况 base 更新为 theory_ok=True、reason="ok"。
125
134
  def check(path: str = None, decl: dict = None) -> dict:
126
135
  """校验版本声明 → 状态字典(永不抛异常,供启动路径安全调用)。
127
136
 
@@ -164,6 +173,7 @@ def check(path: str = None, decl: dict = None) -> dict:
164
173
  return base
165
174
 
166
175
 
176
+ # 生效条件:p=theory_file(path),仅当 load(p) 为 None 时尝试 declare(PROTOCOL_VERSION, actor="system:autoinit", path=p, append_audit=False) 并吞掉 TheoryError/OSError,最终返回 check(p)。
167
177
  def ensure(path: str = None) -> dict:
168
178
  """启动路径:无声明则落盘默认声明(声明常态化),再返回校验状态。
169
179
 
@@ -179,6 +189,7 @@ def ensure(path: str = None) -> dict:
179
189
  return check(p)
180
190
 
181
191
 
192
+ # 生效条件:version=None/空串时在 make_declaration 内回落 PROTOCOL_VERSION(仅回落结果 strip 后仍为空才抛 TheoryError),seal 后若 append_audit 为真值则把 (old or {}).get("audit") 或 [] 追加一条(仅 old 为真时追加)写入 decl["audit"],save(decl, path) 后返回 {'ok': True, 'declaration': decl, 'status': check(path)}。
182
193
  def declare(version: str = None, accepted=None, actor: str = "designer",
183
194
  path: str = None, append_audit: bool = True) -> dict:
184
195
  """写入版本声明(版本更新权的唯一物理落点)。"""
@@ -195,11 +206,13 @@ def declare(version: str = None, accepted=None, actor: str = "designer",
195
206
  return {"ok": True, "declaration": decl, "status": check(path)}
196
207
 
197
208
 
209
+ # 生效条件:以 path 调 load、check、theory_file,返回 {"declaration": load(path), "status": check(path), "theory_file": theory_file(path)}。
198
210
  def show(path: str = None) -> dict:
199
211
  return {"declaration": load(path), "status": check(path),
200
212
  "theory_file": theory_file(path)}
201
213
 
202
214
 
215
+ # 生效条件:返回含模块常量 SCHEMA、PROTOCOL_VERSION、list(SEALED_FIELDS)、list(ESCAPE_OPS) 及 theory_file() 与 THEORY_ENV/THEORY_FILE_ENV 的自描述 dict。
203
216
  def catalog() -> dict:
204
217
  """版本层自描述(供 whoami / service_info 暴露)。"""
205
218
  return {"schema": SCHEMA, "theory_file": theory_file(),
@@ -213,10 +226,12 @@ def catalog() -> dict:
213
226
  # CLI
214
227
  # --------------------------------------------------------------------------
215
228
 
229
+ # 生效条件:把 json.dumps(obj, ensure_ascii=False, indent=1)+"\n" 写入 sys.stdout,无返回值。
216
230
  def _print(obj):
217
231
  sys.stdout.write(json.dumps(obj, ensure_ascii=False, indent=1) + "\n")
218
232
 
219
233
 
234
+ # 生效条件:argv 交给 ap.parse_args(argv 为 None 时 argparse 读 sys.argv),子解析器 required=True 缺 cmd 时由 argparse 报错退出;a.cmd 为 declare/check/ensure/show/catalog 之一且未抛 TheoryError 时打印并返回 0,抛出 TheoryError 时写 stderr 返回 2。
220
235
  def main(argv=None):
221
236
  ap = argparse.ArgumentParser(
222
237
  prog="python -m md_cg.theory",
@@ -256,4 +271,4 @@ def main(argv=None):
256
271
 
257
272
 
258
273
  if __name__ == "__main__":
259
- sys.exit(main())
274
+ sys.exit(main())
package/md_cg/tokens.py CHANGED
@@ -79,7 +79,15 @@ ALL_OPS = ("help", "info", "route", "read", "write", "goal", "task", "recent", "
79
79
  # —— 对话记录→六要素候选→**编外复核**→落库;裁定 A:编译者不得自证
80
80
  # (E041 机械拒绝 verifier == compiled_by)。复核通道优先蜂巢
81
81
  # reflect/verify 单元,不可用则提示配置或降级 harness 端子代理。
82
- "ccg")
82
+ # P4 新增(可验证记忆单元,2026-09-19):
83
+ # status 验证态 / 依赖 / 双时间轴 / 履历查询(只读)
84
+ # —— 「它还成不成立」的读面;真源 md_cg/trust.py
85
+ # P4 新增(三元组反查原语,阶段二 4.2,2026-09-20):
86
+ # edges 按任意端 / 谓词 / 时间 + 排序分页聚合反查派生边(只读)
87
+ # —— 「这条记忆从哪来 / 谁由它派生」的读面;真源
88
+ # md_cg/provenance.py(find_edges)。**只读 op**:不含任何
89
+ # 写入 path,故读面角色一律放行(与 status 同档)
90
+ "ccg", "status", "edges")
83
91
 
84
92
 
85
93
  class TokenError(Exception):
@@ -115,7 +123,8 @@ ROLE_SPECS = OrderedDict([
115
123
  # task=结构层任务台账(工程做到哪一步/结果是什么)——记录单元本职:
116
124
  # 保存过程与结果;「不得自证」由 done 时的结果必填闸承接
117
125
  "ops_allow": ["info", "route", "read", "write", "goal", "task", "recent",
118
- "session", "ingest", "maintain", "insight", "ccg"],
126
+ "session", "ingest", "maintain", "insight", "ccg", "status",
127
+ "edges"],
119
128
  "delegable": False,
120
129
  "forbidden": ["self/anchor 层", "private/secret 密级", "裁决与删除"],
121
130
  }),
@@ -128,7 +137,7 @@ ROLE_SPECS = OrderedDict([
128
137
  # maintain=反思后的前馈/模式分离候选(apply 类改写走 require_admin)
129
138
  # insight=发现差异/新路径:开窗 window + 情景重构 reconstruct + 盲区学习 learn
130
139
  "ops_allow": ["info", "route", "read", "write", "recent", "metacognition",
131
- "session", "maintain", "insight"],
140
+ "session", "maintain", "insight", "status", "edges"],
132
141
  "delegable": False,
133
142
  "forbidden": ["knowledge/self/anchor 层", "private/secret 密级", "裁决与删除"],
134
143
  }),
@@ -137,7 +146,8 @@ ROLE_SPECS = OrderedDict([
137
146
  "duty": "判断规则、执行结果和结构是否有效;只写验证证据与负记忆",
138
147
  "can_write": True, "can_admin": False, "clearance_cap": "internal",
139
148
  "layers_allow": ["rejected", "contextual"],
140
- "ops_allow": ["info", "route", "read", "write", "verify", "insight"],
149
+ "ops_allow": ["info", "route", "read", "write", "verify", "insight",
150
+ "status", "edges"],
141
151
  "delegable": False,
142
152
  "forbidden": ["knowledge/self/anchor 层(不得改被验证内容)",
143
153
  "private/secret 密级", "裁决与删除"],
@@ -150,7 +160,7 @@ ROLE_SPECS = OrderedDict([
150
160
  # session 仅开放只读 recall;note/compact 在分发层按 can_write 拦截
151
161
  # insight 仅开放只读呈现(list/report/outlook/reconstruct);写入被 can_write 拦截
152
162
  "ops_allow": ["info", "route", "read", "recent", "whitebox", "session",
153
- "insight"],
163
+ "insight", "status", "edges"],
154
164
  "delegable": False,
155
165
  "forbidden": ["全部写入", "private/secret 密级", "管理操作"],
156
166
  }),
@@ -164,7 +174,7 @@ ROLE_SPECS = OrderedDict([
164
174
  # insight=整体结构洞察 outlook(趋势/盲区/建议)+ 条件层报告 report
165
175
  "ops_allow": ["info", "read", "write", "sustain", "scrub", "evolution",
166
176
  "self_state", "metacognition", "link", "session",
167
- "maintain", "insight"],
177
+ "maintain", "insight", "status", "edges"],
168
178
  "delegable": False,
169
179
  "forbidden": ["knowledge/anchor 层", "private/secret 密级", "裁决与删除"],
170
180
  }),
@@ -173,7 +183,8 @@ ROLE_SPECS = OrderedDict([
173
183
  "duty": "无令牌时的降级身份:只读、最低密级",
174
184
  "can_write": False, "can_admin": False, "clearance_cap": "internal",
175
185
  "layers_allow": [],
176
- "ops_allow": ["info", "route", "read", "recent", "whitebox"],
186
+ "ops_allow": ["info", "route", "read", "recent", "whitebox", "status",
187
+ "edges"],
177
188
  "delegable": False,
178
189
  "forbidden": ["全部写入", "private/secret 密级", "管理操作"],
179
190
  }),
@@ -216,11 +227,13 @@ ORCH_OPS_ALLOW = ("route", "read", "write", "review", "recent", "consistency")
216
227
  ORCH_LAYERS_ALLOW = tuple(l for l in ALL_LAYERS if l not in CORE_LAYERS)
217
228
 
218
229
 
230
+ # 生效条件:role 为假值(None/空串)时按 "" 处理,经 strip().lower() 得 r,r 命中 ROLE_ALIASES 键时返回别名,否则返回 r 本身。
219
231
  def normalize_role(role: str) -> str:
220
232
  r = (role or "").strip().lower()
221
233
  return ROLE_ALIASES.get(r, r)
222
234
 
223
235
 
236
+ # 生效条件:normalize_role(role) 得 r,r 不在 ROLE_SPECS 中即抛 TokenError,否则返回 dict(ROLE_SPECS[r]) 的浅拷贝。
224
237
  def role_spec(role: str):
225
238
  r = normalize_role(role)
226
239
  if r not in ROLE_SPECS:
@@ -228,6 +241,7 @@ def role_spec(role: str):
228
241
  return dict(ROLE_SPECS[r])
229
242
 
230
243
 
244
+ # 生效条件:无 required 形参,调用即返回含 SCHEMA、token_file()、CORE_PRIVATE_SENSITIVITIES/CORE_LAYERS/ALL_LAYERS/POSITION_ROLES/DELEGABLE_ROLES/ROLE_ALIASES/ROLE_SPECS 各自 list/dict 拷贝的字典。
231
245
  def catalog():
232
246
  """角色职责矩阵(供 whoami / service_info / 文档自描述)。"""
233
247
  return {"schema": SCHEMA, "token_file": token_file(),
@@ -243,10 +257,12 @@ def catalog():
243
257
  # 存储(仓库外,0600)
244
258
  # --------------------------------------------------------------------------
245
259
 
260
+ # 生效条件:path 为真值时返回 path,否则回落 os.environ.get(TOKEN_FILE_ENV)(取到空串同为假值),仍为假值时返回 DEFAULT_TOKEN_FILE。
246
261
  def token_file(path: str = None) -> str:
247
262
  return path or os.environ.get(TOKEN_FILE_ENV) or DEFAULT_TOKEN_FILE
248
263
 
249
264
 
265
+ # 生效条件:p=token_file(path) 的 os.path.exists(p) 为真且 json.load 得到 dict 时,setdefault("tokens", {}) 后返回该 dict;路径不存在、解析结果非 dict 或抛 OSError/ValueError 时返回 {"schema": SCHEMA, "tokens": {}}。
250
266
  def _load(path: str = None) -> dict:
251
267
  p = token_file(path)
252
268
  if os.path.exists(p):
@@ -261,6 +277,7 @@ def _load(path: str = None) -> dict:
261
277
  return {"schema": SCHEMA, "tokens": {}}
262
278
 
263
279
 
280
+ # 生效条件:以 data 为内容、p=token_file(path) 为目标,先对 os.path.dirname(p) or "." 做 makedirs(exist_ok=True),写 p+".tmp" 后 os.replace 覆盖 p,再尝试 chmod 0600(仅吞 OSError)。
264
281
  def _save(data: dict, path: str = None):
265
282
  p = token_file(path)
266
283
  os.makedirs(os.path.dirname(p) or ".", exist_ok=True)
@@ -274,14 +291,17 @@ def _save(data: dict, path: str = None):
274
291
  pass
275
292
 
276
293
 
294
+ # 生效条件:secret 经 encode("utf-8") 后取 sha256 的 hexdigest;secret 非 str 时按其 encode 失败抛出。
277
295
  def _hash(secret: str) -> str:
278
296
  return hashlib.sha256(secret.encode("utf-8")).hexdigest()
279
297
 
280
298
 
299
+ # 生效条件:_rank(want) <= _rank(cap) 时返回 want,否则返回 cap。
281
300
  def _clamp_level(want: str, cap: str) -> str:
282
301
  return want if _rank(want) <= _rank(cap) else cap
283
302
 
284
303
 
304
+ # 生效条件:allow 为 None 时按 parent_allow 是否为 None 返回 None 或 list(parent_allow);否则 parent_allow 为 None 或含 "*" 时返回 list(allow);否则 allow 含 "*" 时返回 list(parent_allow);否则返回 [x for x in allow if x in parent_allow]。
285
305
  def _narrow(allow, parent_allow):
286
306
  """求交:子权限只能收窄。父为 None(不限制)时取子;子为 None 时取父。"""
287
307
  if allow is None:
@@ -293,10 +313,12 @@ def _narrow(allow, parent_allow):
293
313
  return [x for x in allow if x in parent_allow]
294
314
 
295
315
 
316
+ # 生效条件:role/token_id/secret 即使为假值也照拼,返回 f"{PREFIX}.{role}.{token_id}.{secret}"。
296
317
  def make_token(role: str, token_id: str, secret: str) -> str:
297
318
  return f"{PREFIX}.{role}.{token_id}.{secret}"
298
319
 
299
320
 
321
+ # 生效条件:token 为假值时按 "" 处理,split(".") 后长度不为 4 或 parts[0] != PREFIX 即抛 TokenError;长度与前缀合规后 role/token_id/secret 任一为空再抛 TokenError;否则返回 (role.lower(), token_id, secret)。
300
322
  def parse_token(token: str):
301
323
  parts = (token or "").strip().split(".")
302
324
  if len(parts) != 4 or parts[0] != PREFIX:
@@ -311,6 +333,7 @@ def parse_token(token: str):
311
333
  # 签发 / 校验 / 派生 / 吊销
312
334
  # --------------------------------------------------------------------------
313
335
 
336
+ # 生效条件:role 经 normalize_role+role_spec(未知角色抛 TokenError),clearance 为假值时取 spec["clearance_cap"] 再经 _clamp_level 收窄到该 cap,layers_allow/ops_allow 经 _narrow 与 spec 默认求交,actor 为假值时取 role,delegable is None 时取 spec["delegable"]、否则按所传值取 bool,ttl 为真值时 expires_at=now+float(ttl)、为假值(None/0)时 None,记录写入 token_file(path) 后返回含明文 token 的字典。
314
337
  def issue(role: str, actor: str = None, clearance: str = None,
315
338
  tenant: str = "default", ttl: float = None, label: str = "",
316
339
  issued_by: str = "root", parent: str = None, delegable: bool = None,
@@ -345,6 +368,7 @@ def issue(role: str, actor: str = None, clearance: str = None,
345
368
  "expires_at": rec["expires_at"], "token_file": token_file(path)}
346
369
 
347
370
 
371
+ # 生效条件:token 先经 parse_token(格式非法即抛 TokenError),其后 _load(path) 的 tokens 中该 token_id 无记录、rec 的 role 与解析出的 role 不等、revoked_at 为真、hash 与 _hash(secret) 经 hmac.compare_digest 不等、expires_at 为真且小于当前时间中任一成立即抛 TokenError;否则返回 Principal,tenant=tenant or rec.get("tenant") or "default"、actor=rec.get("actor") or role、clearance=rec.get("clearance") or "internal"、can_write/can_admin 取对应 rec 值的 bool。
348
372
  def verify_token(token: str, tenant: str = None, path: str = None) -> Principal:
349
373
  """校验令牌 → Principal。任何异常都抛 TokenError(fail-closed)。"""
350
374
  role, token_id, secret = parse_token(token)
@@ -371,6 +395,7 @@ def verify_token(token: str, tenant: str = None, path: str = None) -> Principal:
371
395
  ops_allow=rec.get("ops_allow"), auth_mode="token")
372
396
 
373
397
 
398
+ # 生效条件:parent_token 经 verify_token(parent_token, path=path) 成功且父记录 delegable 为真才继续,否则抛 TokenError;role 经 normalize_role+role_spec,clearance 为假值时取 spec["clearance_cap"] 再 _clamp_level,若仍高于 parent.clearance 则降为 parent.clearance 并向 clamped 追加 "clearance";layers/ops 先与 spec 求交再与父记录求交;can_write/can_admin 取 spec 与父对应值的与;ttl 为真值时 expires_at=now+float(ttl)、为假值(None/0)时沿用父记录 expires_at;新记录 delegable 恒 False、parent/issued_by 为 parent.token_id,返回含明文 token 与 clamped 的字典。
374
399
  def derive(parent_token: str, role: str, actor: str = None, ttl: float = None,
375
400
  label: str = "", path: str = None, clearance: str = None,
376
401
  layers_allow=None, ops_allow=None):
@@ -414,6 +439,7 @@ def derive(parent_token: str, role: str, actor: str = None, ttl: float = None,
414
439
  "expires_at": rec["expires_at"]}
415
440
 
416
441
 
442
+ # 生效条件:normalize_role(unit) 结果不在 POSITION_ROLES 时抛 TokenError;否则返回 Principal:unit/role=u、can_admin 恒 False、clearance=_clamp_level(spec["clearance_cap"], p.clearance)、can_write=bool(spec["can_write"]) and bool(p.can_write)、layers_allow/ops_allow=_narrow(spec 对应值, p 对应值),tenant/actor/session/harness/token_id/parent/expires_at/auth_mode/theory 等沿用 p。
417
443
  def narrowed_principal(p: Principal, unit: str) -> Principal:
418
444
  """按「单元」收窄 principal 权限(**请求级**身份,只能变小不能变大)。
419
445
 
@@ -450,6 +476,7 @@ def narrowed_principal(p: Principal, unit: str) -> Principal:
450
476
  theory_ok=p.theory_ok, theory_version=p.theory_version)
451
477
 
452
478
 
479
+ # 生效条件:token_id 在 _load(path) 的 tokens 中无记录时抛 TokenError;有记录则将其 revoked_at 置为 time.time() 并 _save,再对 tokens 中 parent 等于 token_id 的每个子令牌递归 revoke(c, path),返回 {"ok": True, "token_id": token_id, "revoked_children": children}。
453
480
  def revoke(token_id: str, path: str = None):
454
481
  data = _load(path)
455
482
  rec = (data.get("tokens") or {}).get(token_id)
@@ -464,6 +491,7 @@ def revoke(token_id: str, path: str = None):
464
491
  return {"ok": True, "token_id": token_id, "revoked_children": children}
465
492
 
466
493
 
494
+ # 生效条件:从 _load(path)(path 为 None 时取默认令牌文件)的 tokens 取值;条目的 revoked_at 为真且 include_revoked 为假时跳过;返回不含密钥材料与摘要的清单;
467
495
  def list_tokens(path: str = None, include_revoked: bool = False):
468
496
  """令牌清单(不含密钥材料与摘要)。"""
469
497
  out = []
@@ -488,10 +516,12 @@ def list_tokens(path: str = None, include_revoked: bool = False):
488
516
  # CLI:签发 / 派生 / 清单 / 吊销 / 角色矩阵
489
517
  # --------------------------------------------------------------------------
490
518
 
519
+ # 生效条件:obj 一律经 json.dumps(obj, ensure_ascii=False, indent=1) 加换行写入 sys.stdout;obj 不可 JSON 序列化时抛出 TypeError。
491
520
  def _print(obj):
492
521
  sys.stdout.write(json.dumps(obj, ensure_ascii=False, indent=1) + "\n")
493
522
 
494
523
 
524
+ # 生效条件:v 为假值(None/空串)时返回 None;否则按 "," 切分、strip 后丢弃空项,结果为空列表时同样返回 None。
495
525
  def _csv_list(v):
496
526
  """CLI 的逗号分隔白名单 → list;空值返回 None(= 不额外收窄)。
497
527
 
@@ -502,6 +532,7 @@ def _csv_list(v):
502
532
  return [x.strip() for x in str(v).split(",") if x.strip()] or None
503
533
 
504
534
 
535
+ # 生效条件:path 经 os.path.abspath 得 p,其 dirname 非空时 makedirs(exist_ok=True),text 原样写入 p+".tmp" 后 os.replace 覆盖 p,再尝试 chmod 0600(仅吞 OSError)。
505
536
  def _write_secret(path: str, text: str) -> None:
506
537
  """把令牌明文写入文件(0600,原子替换)——供 HIVE_ORCH_TOKEN_FILE 读取。"""
507
538
  p = os.path.abspath(path)
@@ -518,6 +549,7 @@ def _write_secret(path: str, text: str) -> None:
518
549
  pass
519
550
 
520
551
 
552
+ # 生效条件:argv 为 None 时取 sys.argv[1:];经 argparse 解析(--token-file 与必填子命令 issue/verify/revoke/list/roles 等);参数非法时经 argparse 退出,写盘失败抛 OSError;
521
553
  def main(argv=None):
522
554
  ap = argparse.ArgumentParser(
523
555
  prog="python -m md_cg.tokens",
@@ -629,4 +661,4 @@ def main(argv=None):
629
661
 
630
662
 
631
663
  if __name__ == "__main__":
632
- sys.exit(main())
664
+ sys.exit(main())
@@ -38,7 +38,8 @@ FULL_ENV = "MDCG_TOOL_FACE"
38
38
  CG_HELP_HINT = '完整 op/参数语义:cg(op="help", query="<op 名 | 参数名 | 工具名>")'
39
39
 
40
40
  TOOL_BRIEFS = {
41
- "cg": "灵枢认知图唯一入口(32 op:写/读/审/护/演化/元认知/预测/白箱…)。" + CG_HELP_HINT,
41
+ "cg": "灵枢认知图唯一入口(34 op:写/读/审/护/演化/元认知/预测/白箱/验证态 status/"
42
+ "三元组反查 edges…)。" + CG_HELP_HINT,
42
43
  "stg": "语义时空图 4 op:relation|timeline|anchors|consistency。" + CG_HELP_HINT,
43
44
  "mdcg_remember": "写入记忆节点(gated=true 走主动遗忘闸门;正文建议含 CCG 五要素)。",
44
45
  "mdcg_recall": "按 token 预算召回记忆包(RRF 多路融合,可开模糊/条件语义路与查询扩展)。",
@@ -93,6 +94,7 @@ GENERIC_PARAMS = frozenset({
93
94
  })
94
95
 
95
96
 
97
+ # 生效条件:desc 为 None/空串等假值时按 `desc or ""` 回落空串、`_OP_SEC_RE.finditer` 无匹配而返回空 dict;否则以每个匹配的组 1 为键、组 2 去首尾空白并去掉结尾「;/;」后为值填入并返回。
96
98
  def op_sections(desc):
97
99
  """从工具 description 里切出 {op: 段落}(机械切分,无损)。"""
98
100
  out = {}
@@ -101,6 +103,7 @@ def op_sections(desc):
101
103
  return out
102
104
 
103
105
 
106
+ # 生效条件:tools 为空时可迭代但无元素、返回空 set;否则遍历每个工具,并入其 `t.get("description") or ""` 经 op_sections 得到的键,以及其 `(t.get("inputSchema") or {}).get("properties") or {}` 中名为 op/action 的属性 description(缺省 "")里 `_ENUM_RE` 匹配串按 "|" 拆出的词,返回并集 vocab。
104
107
  def op_vocab(tools):
105
108
  """全量 op 词汇表(用于判定「这是 op 专属参数的描述」)。"""
106
109
  vocab = set()
@@ -115,11 +118,13 @@ def op_vocab(tools):
115
118
  return vocab
116
119
 
117
120
 
121
+ # 生效条件:head=(desc or "")[:prefix_len](desc 假值即空串、prefix_len 默认 18),当 vocab 中存在长度 ≥3 且作为子串出现在 head 中的元素时返回 True,否则(含 vocab 为空)返回 False。
118
122
  def _is_op_specific(desc, vocab, prefix_len=18):
119
123
  head = (desc or "")[:prefix_len]
120
124
  return any(len(o) >= 3 and o in head for o in vocab)
121
125
 
122
126
 
127
+ # 生效条件:desc 为假值返回 None;否则空白归一化后按 _ENUM_RE 结果分派——恰 1 组返回该组、多于 1 组返回 ENUM_PLACEHOLDER、0 组时若 op_specific 为真则 d 长度 ≤OP_SPECIFIC_LIMIT 原样返回否则 None,若 op_specific 为假则 d 长度 >GENERIC_LIMIT 时返回 d[:GENERIC_LIMIT].rstrip()+"…"、否则原样返回 d。
123
128
  def short_desc(desc, vocab, op_specific=False):
124
129
  """投影一条参数描述。
125
130
 
@@ -142,10 +147,12 @@ def short_desc(desc, vocab, op_specific=False):
142
147
  return d
143
148
 
144
149
 
150
+ # 生效条件:tool 的 "inputSchema" 键存在且值为真时原样返回该值;键缺失或值为 None/{} 等假值时按 `or {}` 回落返回空 dict。
145
151
  def _schema_of(tool):
146
152
  return tool.get("inputSchema") or {}
147
153
 
148
154
 
155
+ # 生效条件:tool 须含 "name" 键(缺失即 KeyError),desc=`tool.get("description") or ""`、isch=`_schema_of(tool)`;逐条属性去掉 "_req" 键后,若其 description 为真则按「key 在 GENERIC_PARAMS 或 `not _is_op_specific(d, vocab)`」判为 generic,再经 short_desc 投影——非空则替换 description、空则弹出该 description 键;返回 name 与 `TOOL_BRIEFS.get(name) or _first_sentence(desc,140)` 的 description,inputSchema 的 type 取 `isch.get("type") or "object"`,仅当 required 非空时才带 "required"。
149
156
  def slim_tool(tool, vocab):
150
157
  """投影单个工具(不修改入参)。"""
151
158
  name = tool["name"]
@@ -172,6 +179,7 @@ def slim_tool(tool, vocab):
172
179
  "inputSchema": out_schema}
173
180
 
174
181
 
182
+ # 生效条件:d=折叠 (desc or "") 空白,按 "。"、";"、";" 顺序取各字符在 d 中 `find` 的首次出现位置 i,遇到首个满足 0<i<=limit 者即返回 d[:i+1];三个字符的首次位置均不满足时,len(d)>limit 返回 d[:limit].rstrip()+"…",否则原样返回 d(limit=0 时无 i 满足,d 非空则返回 "…")。
175
183
  def _first_sentence(desc, limit):
176
184
  d = " ".join((desc or "").split())
177
185
  for ch in ("。", ";", ";"):
@@ -181,6 +189,7 @@ def _first_sentence(desc, limit):
181
189
  return (d[:limit].rstrip() + "…") if len(d) > limit else d
182
190
 
183
191
 
192
+ # 生效条件:环境变量 FULL_ENV 取值 `(os.environ.get(FULL_ENV) or "").strip().lower()=="full"`(未设或设为空串均不成立)时原样返回入参 tools;否则以 tools 算出 vocab 并返回 `[slim_tool(t, vocab) for t in tools]`(tools 为空则返回空列表)。
184
193
  def slim_tools(tools):
185
194
  """投影整个工具面。MDCG_TOOL_FACE=full 时原样返回(调试/兼容)。"""
186
195
  if (os.environ.get(FULL_ENV) or "").strip().lower() == "full":
@@ -192,6 +201,7 @@ def slim_tools(tools):
192
201
  # --------------------------------------------------------------------------
193
202
  # 按需披露:cg(op=help, query=…)
194
203
  # --------------------------------------------------------------------------
204
+ # 生效条件:取 `_schema_of(tool)` 的 properties,仅保留 description(缺省 "")满足 `_is_op_specific(desc, {op})` 的属性,每项记为 name/type/required(k in required)/desc,返回该列表的 `out[:limit]` 切片(limit 默认 40,limit=0 得空列表,负值从尾部削减)。
195
205
  def _params_for(tool, op, limit=40):
196
206
  isch = _schema_of(tool)
197
207
  props = isch.get("properties") or {}
@@ -204,6 +214,7 @@ def _params_for(tool, op, limit=40):
204
214
  return out[:limit]
205
215
 
206
216
 
217
+ # 生效条件:tools 用于建 `by_name={t["name"]: t}`(某工具缺 "name" 即 KeyError),q=(query or "").strip()——q 为空串返回 {"ok":True,"tool_index":[每工具 name 与 `TOOL_BRIEFS.get(name) or _first_sentence(desc,120)`],"usage":...};q 等于某工具名时返回该工具的 description/ops(op_sections 键排序)/params/required;否则逐工具收集「q 出现在 op 段落键中」与「pname==q 或 len(q)>=4 且 pname 以 q 开头」两类命中(不互斥、可同时追加),全部工具遍历后无命中返回 {"ok":False,"error",...,"hint",...},有命中返回 {"ok":True,"query":q,"hits":hits[:max(1, limit)]}(limit≤1 时按 1 条截取)。
207
218
  def help_text(tools, query=None, limit=40):
208
219
  """从**完整真源**按需渲染说明——被投影掉的原文在此无损取回。"""
209
220
  q = (query or "").strip()
@@ -247,4 +258,4 @@ def help_text(tools, query=None, limit=40):
247
258
  if not hits:
248
259
  return {"ok": False, "error": f"未找到 op / 参数 / 工具:{q}",
249
260
  "hint": "cg(op=help) 不带 query 可列出全部工具与一行职责"}
250
- return {"ok": True, "query": q, "hits": hits[:max(1, limit)]}
261
+ return {"ok": True, "query": q, "hits": hits[:max(1, limit)]}